add splitfire

Seto Elkahfi committed Jul 28, 2024 at 19:45 UTC 168215ec9b1512bcd8a61ca7fcbeaca544c58c83
923 files changed +52890
backend/musik88-web/.browserslistrc new
+1
@@ -0,0 +1 @@
1 +defaults
backend/musik88-web/.env-example new
+4
@@ -0,0 +1,4 @@
1 +# Use for the mp3 files and the stem files
2 +AWS_S3_BUCKET_SPLITFIRE_FILES = xxx
3 +# Use an abosulte path
4 +YOUTUBE_DL_WORKING_DIRECTORY = xxx
\ No newline at end of file
backend/musik88-web/.gitignore new
+45
@@ -0,0 +1,45 @@
1 +# See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 +#
3 +# If you find yourself ignoring temporary files generated by your text editor
4 +# or operating system, you probably want to add a global ignore instead:
5 +# git config --global core.excludesfile '~/.gitignore_global'
6 +
7 +# Ignore bundler config.
8 +/.bundle
9 +
10 +# Ignore the default SQLite database.
11 +/db/*.sqlite3
12 +/db/*.sqlite3-*
13 +
14 +# Ignore all logfiles and tempfiles.
15 +/log/*
16 +/tmp/*
17 +!/log/.keep
18 +!/tmp/.keep
19 +
20 +# Ignore pidfiles, but keep the directory.
21 +/tmp/pids/*
22 +!/tmp/pids/
23 +!/tmp/pids/.keep
24 +
25 +# Ignore uploaded files in development.
26 +/storage/*
27 +!/storage/.keep
28 +
29 +/public/assets
30 +.byebug_history
31 +
32 +# Ignore master key for decrypting credentials and more.
33 +/config/master.key
34 +
35 +/public/packs
36 +/public/packs-test
37 +/node_modules
38 +/yarn-error.log
39 +yarn-debug.log*
40 +.yarn-integrity
41 +
42 +.env
43 +
44 +# Python
45 +__pycache__
\ No newline at end of file
backend/musik88-web/.nvmrc new
+1
@@ -0,0 +1 @@
1 +v18.18.0
backend/musik88-web/.python-version new
+1
@@ -0,0 +1 @@
1 +3.8
backend/musik88-web/.rubocop.yml new
+4
@@ -0,0 +1,4 @@
1 +Documentation:
2 + Enabled: false
3 +Style/HashSyntax:
4 + Enabled: false
\ No newline at end of file
backend/musik88-web/.ruby-version new
+1
@@ -0,0 +1 @@
1 +3.2.2
backend/musik88-web/.travis.yml new
+10
@@ -0,0 +1,10 @@
1 +dist: xenial
2 +os: linux
3 +language: ruby
4 +install:
5 + - bundle install
6 +cache: bundler
7 +branches:
8 + only:
9 + - staging
10 + - development
\ No newline at end of file
backend/musik88-web/.vscode/settings.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "ruby.useBundler": true, //run non-lint commands with bundle exec
3 + "ruby.useLanguageServer": true, // use the internal language server (see below)
4 + "ruby.lint": {
5 + "rubocop": {
6 + "useBundler": true // enable rubocop via bundler
7 + },
8 + "reek": {
9 + "useBundler": true // enable reek via bundler
10 + }
11 + },
12 + "ruby.format": "rubocop", // use rubocop for formatting"
13 + "ruby.rubocop.configFilePath": ".rubocop.yml",
14 + "files.associations": {
15 + "*.html.erb": "erb"
16 + },
17 + "vscode-erb-beautify.bundlerPath": "~/.rbenv/shims/bundler",
18 + "vscode-erb-beautify.executePath": "~/.rbenv/shims/htmlbeautifier"
19 +}
\ No newline at end of file
backend/musik88-web/Gemfile new
+75
@@ -0,0 +1,75 @@
1 +source 'https://rubygems.org'
2 +ruby '3.2.2'
3 +
4 +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
5 +gem 'rails', '6.1.7.7'
6 +# Use postgresql as the database for Active Record
7 +gem 'pg'
8 +# Use SCSS for stylesheets
9 +# Use Uglifier as compressor for JavaScript assets
10 +gem 'uglifier'
11 +
12 +gem 'mini_racer'
13 +
14 +# Use jquery as the JavaScript library
15 +gem 'jquery-rails'
16 +# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
17 +gem 'turbolinks'
18 +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
19 +gem 'jbuilder'
20 +# bundle exec rake doc:rails generates the API under doc/api.
21 +gem 'sdoc', group: :doc
22 +
23 +# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
24 +gem 'spring', group: :development
25 +
26 +gem 'puma', '5.4.0'
27 +
28 +# Use Capistrano for deployment
29 +# gem 'capistrano-rails', group: :development
30 +
31 +# Use debugger
32 +# gem 'debugger', group: [:development, :test]
33 +
34 +# gem 'rails-translate-routes'
35 +gem 'activeadmin', '2.9.0'
36 +gem 'activeadmin_json_editor', '~> 0.0.7'
37 +gem 'activeadmin_quill_editor', '0.3.4'
38 +gem 'autoprefixer-rails'
39 +gem 'aws-sdk-s3'
40 +gem 'bootstrap-sass', '~> 3.2.0'
41 +gem 'browser'
42 +gem 'coffee-rails'
43 +gem 'devise'
44 +gem 'devise_invitable', '~> 2.0.0'
45 +gem 'devise-jwt'
46 +gem 'fog-aws'
47 +gem 'font-awesome-rails'
48 +gem 'google-api-client', require: 'google/apis'
49 +gem 'httpclient'
50 +gem 'letter_opener', group: :development
51 +gem 'merit'
52 +gem 'omniauth-apple'
53 +gem 'omniauth-facebook'
54 +gem 'omniauth-google-oauth2'
55 +gem 'omniauth-rails_csrf_protection'
56 +gem 'omniauth-spotify'
57 +gem 'rack-cors'
58 +gem 'redis', '4.4.0'
59 +gem 'route_translator'
60 +gem 'sass-rails'
61 +gem 'sentry-rails', '>= 5.4.2'
62 +gem 'sentry-ruby', '>= 5.4.2'
63 +gem 'sidekiq'
64 +gem 'sitemap_generator'
65 +gem 'split', require: 'split/dashboard'
66 +gem 'sprockets', '3.7.2'
67 +# gem 'tinymce-rails', '5.9.2'
68 +gem 'tolk', '4.1.0'
69 +gem 'webpacker'
70 +gem 'will_paginate', '~> 3.3.0'
71 +group :test, :development do
72 + gem 'dotenv-rails'
73 + gem 'htmlbeautifier'
74 + gem 'rubocop'
75 +end
backend/musik88-web/Gemfile.lock new
+570
@@ -0,0 +1,570 @@
1 +GEM
2 + remote: https://rubygems.org/
3 + specs:
4 + ace-rails-ap (4.5)
5 + actioncable (6.1.7.7)
6 + actionpack (= 6.1.7.7)
7 + activesupport (= 6.1.7.7)
8 + nio4r (~> 2.0)
9 + websocket-driver (>= 0.6.1)
10 + actionmailbox (6.1.7.7)
11 + actionpack (= 6.1.7.7)
12 + activejob (= 6.1.7.7)
13 + activerecord (= 6.1.7.7)
14 + activestorage (= 6.1.7.7)
15 + activesupport (= 6.1.7.7)
16 + mail (>= 2.7.1)
17 + actionmailer (6.1.7.7)
18 + actionpack (= 6.1.7.7)
19 + actionview (= 6.1.7.7)
20 + activejob (= 6.1.7.7)
21 + activesupport (= 6.1.7.7)
22 + mail (~> 2.5, >= 2.5.4)
23 + rails-dom-testing (~> 2.0)
24 + actionpack (6.1.7.7)
25 + actionview (= 6.1.7.7)
26 + activesupport (= 6.1.7.7)
27 + rack (~> 2.0, >= 2.0.9)
28 + rack-test (>= 0.6.3)
29 + rails-dom-testing (~> 2.0)
30 + rails-html-sanitizer (~> 1.0, >= 1.2.0)
31 + actiontext (6.1.7.7)
32 + actionpack (= 6.1.7.7)
33 + activerecord (= 6.1.7.7)
34 + activestorage (= 6.1.7.7)
35 + activesupport (= 6.1.7.7)
36 + nokogiri (>= 1.8.5)
37 + actionview (6.1.7.7)
38 + activesupport (= 6.1.7.7)
39 + builder (~> 3.1)
40 + erubi (~> 1.4)
41 + rails-dom-testing (~> 2.0)
42 + rails-html-sanitizer (~> 1.1, >= 1.2.0)
43 + activeadmin (2.9.0)
44 + arbre (~> 1.2, >= 1.2.1)
45 + formtastic (>= 3.1, < 5.0)
46 + formtastic_i18n (~> 0.4)
47 + inherited_resources (~> 1.7)
48 + jquery-rails (~> 4.2)
49 + kaminari (~> 1.0, >= 1.2.1)
50 + railties (>= 5.2, < 6.2)
51 + ransack (~> 2.1, >= 2.1.1)
52 + activeadmin_json_editor (0.0.10)
53 + ace-rails-ap
54 + railties (>= 3.0)
55 + activeadmin_quill_editor (0.3.4)
56 + activeadmin (~> 2.0)
57 + activejob (6.1.7.7)
58 + activesupport (= 6.1.7.7)
59 + globalid (>= 0.3.6)
60 + activemodel (6.1.7.7)
61 + activesupport (= 6.1.7.7)
62 + activerecord (6.1.7.7)
63 + activemodel (= 6.1.7.7)
64 + activesupport (= 6.1.7.7)
65 + activestorage (6.1.7.7)
66 + actionpack (= 6.1.7.7)
67 + activejob (= 6.1.7.7)
68 + activerecord (= 6.1.7.7)
69 + activesupport (= 6.1.7.7)
70 + marcel (~> 1.0)
71 + mini_mime (>= 1.1.0)
72 + activesupport (6.1.7.7)
73 + concurrent-ruby (~> 1.0, >= 1.0.2)
74 + i18n (>= 1.6, < 2)
75 + minitest (>= 5.1)
76 + tzinfo (~> 2.0)
77 + zeitwerk (~> 2.3)
78 + addressable (2.8.5)
79 + public_suffix (>= 2.0.2, < 6.0)
80 + aes_key_wrap (1.1.0)
81 + ambry (1.0.0)
82 + arbre (1.6.0)
83 + activesupport (>= 3.0.0, < 7.1)
84 + ruby2_keywords (>= 0.0.2, < 1.0)
85 + ast (2.4.2)
86 + autoprefixer-rails (10.4.15.0)
87 + execjs (~> 2)
88 + aws-eventstream (1.2.0)
89 + aws-partitions (1.833.0)
90 + aws-sdk-core (3.185.0)
91 + aws-eventstream (~> 1, >= 1.0.2)
92 + aws-partitions (~> 1, >= 1.651.0)
93 + aws-sigv4 (~> 1.5)
94 + jmespath (~> 1, >= 1.6.1)
95 + aws-sdk-kms (1.72.0)
96 + aws-sdk-core (~> 3, >= 3.184.0)
97 + aws-sigv4 (~> 1.1)
98 + aws-sdk-s3 (1.136.0)
99 + aws-sdk-core (~> 3, >= 3.181.0)
100 + aws-sdk-kms (~> 1)
101 + aws-sigv4 (~> 1.6)
102 + aws-sigv4 (1.6.0)
103 + aws-eventstream (~> 1, >= 1.0.2)
104 + base64 (0.1.1)
105 + bcrypt (3.1.19)
106 + bindata (2.4.15)
107 + bootstrap-sass (3.2.0.4)
108 + sass (~> 3.2)
109 + browser (5.3.1)
110 + builder (3.2.4)
111 + coffee-rails (5.0.0)
112 + coffee-script (>= 2.2.0)
113 + railties (>= 5.2.0)
114 + coffee-script (2.4.1)
115 + coffee-script-source
116 + execjs
117 + coffee-script-source (1.12.2)
118 + concurrent-ruby (1.2.2)
119 + connection_pool (2.4.1)
120 + crass (1.0.6)
121 + date (3.3.3)
122 + declarative (0.0.20)
123 + devise (4.9.2)
124 + bcrypt (~> 3.0)
125 + orm_adapter (~> 0.1)
126 + railties (>= 4.1.0)
127 + responders
128 + warden (~> 1.2.3)
129 + devise-jwt (0.11.0)
130 + devise (~> 4.0)
131 + warden-jwt_auth (~> 0.8)
132 + devise_invitable (2.0.8)
133 + actionmailer (>= 5.0)
134 + devise (>= 4.6)
135 + dotenv (2.8.1)
136 + dotenv-rails (2.8.1)
137 + dotenv (= 2.8.1)
138 + railties (>= 3.2)
139 + dry-auto_inject (1.0.1)
140 + dry-core (~> 1.0)
141 + zeitwerk (~> 2.6)
142 + dry-configurable (1.1.0)
143 + dry-core (~> 1.0, < 2)
144 + zeitwerk (~> 2.6)
145 + dry-core (1.0.1)
146 + concurrent-ruby (~> 1.0)
147 + zeitwerk (~> 2.6)
148 + erubi (1.12.0)
149 + excon (0.104.0)
150 + execjs (2.9.1)
151 + faraday (2.7.11)
152 + base64
153 + faraday-net_http (>= 2.0, < 3.1)
154 + ruby2_keywords (>= 0.0.4)
155 + faraday-follow_redirects (0.3.0)
156 + faraday (>= 1, < 3)
157 + faraday-net_http (3.0.2)
158 + ffi (1.16.3)
159 + fog-aws (3.21.0)
160 + fog-core (~> 2.1)
161 + fog-json (~> 1.1)
162 + fog-xml (~> 0.1)
163 + fog-core (2.3.0)
164 + builder
165 + excon (~> 0.71)
166 + formatador (>= 0.2, < 2.0)
167 + mime-types
168 + fog-json (1.2.0)
169 + fog-core
170 + multi_json (~> 1.10)
171 + fog-xml (0.1.4)
172 + fog-core
173 + nokogiri (>= 1.5.11, < 2.0.0)
174 + font-awesome-rails (4.7.0.8)
175 + railties (>= 3.2, < 8.0)
176 + formatador (1.1.0)
177 + formtastic (4.0.0)
178 + actionpack (>= 5.2.0)
179 + formtastic_i18n (0.7.0)
180 + gems (1.2.0)
181 + globalid (1.2.1)
182 + activesupport (>= 6.1)
183 + google-api-client (0.53.0)
184 + google-apis-core (~> 0.1)
185 + google-apis-generator (~> 0.1)
186 + google-apis-core (0.11.1)
187 + addressable (~> 2.5, >= 2.5.1)
188 + googleauth (>= 0.16.2, < 2.a)
189 + httpclient (>= 2.8.1, < 3.a)
190 + mini_mime (~> 1.0)
191 + representable (~> 3.0)
192 + retriable (>= 2.0, < 4.a)
193 + rexml
194 + webrick
195 + google-apis-discovery_v1 (0.14.0)
196 + google-apis-core (>= 0.11.0, < 2.a)
197 + google-apis-generator (0.12.0)
198 + activesupport (>= 5.0)
199 + gems (~> 1.2)
200 + google-apis-core (>= 0.11.0, < 2.a)
201 + google-apis-discovery_v1 (~> 0.5)
202 + thor (>= 0.20, < 2.a)
203 + googleauth (1.8.1)
204 + faraday (>= 0.17.3, < 3.a)
205 + jwt (>= 1.4, < 3.0)
206 + multi_json (~> 1.11)
207 + os (>= 0.9, < 2.0)
208 + signet (>= 0.16, < 2.a)
209 + has_scope (0.8.1)
210 + actionpack (>= 5.2)
211 + activesupport (>= 5.2)
212 + hashie (5.0.0)
213 + htmlbeautifier (1.4.2)
214 + httpclient (2.8.3)
215 + i18n (1.14.1)
216 + concurrent-ruby (~> 1.0)
217 + inherited_resources (1.13.1)
218 + actionpack (>= 5.2, < 7.1)
219 + has_scope (~> 0.6)
220 + railties (>= 5.2, < 7.1)
221 + responders (>= 2, < 4)
222 + jbuilder (2.11.5)
223 + actionview (>= 5.0.0)
224 + activesupport (>= 5.0.0)
225 + jmespath (1.6.2)
226 + jquery-rails (4.6.0)
227 + rails-dom-testing (>= 1, < 3)
228 + railties (>= 4.2.0)
229 + thor (>= 0.14, < 2.0)
230 + json (2.6.3)
231 + json-jwt (1.16.3)
232 + activesupport (>= 4.2)
233 + aes_key_wrap
234 + bindata
235 + faraday (~> 2.0)
236 + faraday-follow_redirects
237 + jwt (2.7.1)
238 + kaminari (1.2.2)
239 + activesupport (>= 4.1.0)
240 + kaminari-actionview (= 1.2.2)
241 + kaminari-activerecord (= 1.2.2)
242 + kaminari-core (= 1.2.2)
243 + kaminari-actionview (1.2.2)
244 + actionview
245 + kaminari-core (= 1.2.2)
246 + kaminari-activerecord (1.2.2)
247 + activerecord
248 + kaminari-core (= 1.2.2)
249 + kaminari-core (1.2.2)
250 + language_server-protocol (3.17.0.3)
251 + launchy (2.5.2)
252 + addressable (~> 2.8)
253 + letter_opener (1.8.1)
254 + launchy (>= 2.2, < 3)
255 + libv8-node (18.16.0.0)
256 + loofah (2.21.3)
257 + crass (~> 1.0.2)
258 + nokogiri (>= 1.12.0)
259 + mail (2.8.1)
260 + mini_mime (>= 0.1.1)
261 + net-imap
262 + net-pop
263 + net-smtp
264 + marcel (1.0.4)
265 + matrix (0.4.2)
266 + merit (4.0.3)
267 + ambry (~> 1.0.0)
268 + zeitwerk
269 + method_source (1.0.0)
270 + mime-types (3.5.1)
271 + mime-types-data (~> 3.2015)
272 + mime-types-data (3.2023.1003)
273 + mini_mime (1.1.5)
274 + mini_portile2 (2.8.4)
275 + mini_racer (0.8.0)
276 + libv8-node (~> 18.16.0.0)
277 + minitest (5.20.0)
278 + multi_json (1.15.0)
279 + multi_xml (0.6.0)
280 + mustermann (3.0.0)
281 + ruby2_keywords (~> 0.0.1)
282 + net-imap (0.4.0)
283 + date
284 + net-protocol
285 + net-pop (0.1.2)
286 + net-protocol
287 + net-protocol (0.2.1)
288 + timeout
289 + net-smtp (0.4.0)
290 + net-protocol
291 + nio4r (2.5.9)
292 + nokogiri (1.15.4)
293 + mini_portile2 (~> 2.8.2)
294 + racc (~> 1.4)
295 + oauth2 (2.0.9)
296 + faraday (>= 0.17.3, < 3.0)
297 + jwt (>= 1.0, < 3.0)
298 + multi_xml (~> 0.5)
299 + rack (>= 1.2, < 4)
300 + snaky_hash (~> 2.0)
301 + version_gem (~> 1.1)
302 + omniauth (2.1.1)
303 + hashie (>= 3.4.6)
304 + rack (>= 2.2.3)
305 + rack-protection
306 + omniauth-apple (1.3.0)
307 + json-jwt
308 + omniauth-oauth2
309 + omniauth-facebook (9.0.0)
310 + omniauth-oauth2 (~> 1.2)
311 + omniauth-google-oauth2 (1.1.1)
312 + jwt (>= 2.0)
313 + oauth2 (~> 2.0.6)
314 + omniauth (~> 2.0)
315 + omniauth-oauth2 (~> 1.8.0)
316 + omniauth-oauth2 (1.8.0)
317 + oauth2 (>= 1.4, < 3)
318 + omniauth (~> 2.0)
319 + omniauth-rails_csrf_protection (1.0.1)
320 + actionpack (>= 4.2)
321 + omniauth (~> 2.0)
322 + omniauth-spotify (0.0.13)
323 + omniauth-oauth2 (~> 1.1)
324 + orm_adapter (0.5.0)
325 + os (1.1.4)
326 + parallel (1.23.0)
327 + parser (3.2.2.4)
328 + ast (~> 2.4.1)
329 + racc
330 + pg (1.5.4)
331 + psych (5.1.0)
332 + stringio
333 + public_suffix (5.0.3)
334 + puma (5.4.0)
335 + nio4r (~> 2.0)
336 + racc (1.7.1)
337 + rack (2.2.8)
338 + rack-cors (2.0.1)
339 + rack (>= 2.0.0)
340 + rack-protection (3.1.0)
341 + rack (~> 2.2, >= 2.2.4)
342 + rack-proxy (0.7.7)
343 + rack
344 + rack-test (2.1.0)
345 + rack (>= 1.3)
346 + rails (6.1.7.7)
347 + actioncable (= 6.1.7.7)
348 + actionmailbox (= 6.1.7.7)
349 + actionmailer (= 6.1.7.7)
350 + actionpack (= 6.1.7.7)
351 + actiontext (= 6.1.7.7)
352 + actionview (= 6.1.7.7)
353 + activejob (= 6.1.7.7)
354 + activemodel (= 6.1.7.7)
355 + activerecord (= 6.1.7.7)
356 + activestorage (= 6.1.7.7)
357 + activesupport (= 6.1.7.7)
358 + bundler (>= 1.15.0)
359 + railties (= 6.1.7.7)
360 + sprockets-rails (>= 2.0.0)
361 + rails-dom-testing (2.2.0)
362 + activesupport (>= 5.0.0)
363 + minitest
364 + nokogiri (>= 1.6)
365 + rails-html-sanitizer (1.6.0)
366 + loofah (~> 2.21)
367 + nokogiri (~> 1.14)
368 + railties (6.1.7.7)
369 + actionpack (= 6.1.7.7)
370 + activesupport (= 6.1.7.7)
371 + method_source
372 + rake (>= 12.2)
373 + thor (~> 1.0)
374 + rainbow (3.1.1)
375 + rake (13.0.6)
376 + ransack (2.6.0)
377 + activerecord (>= 6.0.4)
378 + activesupport (>= 6.0.4)
379 + i18n
380 + rb-fsevent (0.11.2)
381 + rb-inotify (0.10.1)
382 + ffi (~> 1.0)
383 + rdoc (6.5.0)
384 + psych (>= 4.0.0)
385 + redis (4.4.0)
386 + redis-client (0.17.0)
387 + connection_pool
388 + regexp_parser (2.8.1)
389 + representable (3.2.0)
390 + declarative (< 0.1.0)
391 + trailblazer-option (>= 0.1.1, < 0.2.0)
392 + uber (< 0.2.0)
393 + responders (3.1.0)
394 + actionpack (>= 5.2)
395 + railties (>= 5.2)
396 + retriable (3.1.2)
397 + rexml (3.2.6)
398 + route_translator (14.0.1)
399 + actionpack (>= 6.1, < 7.1)
400 + activesupport (>= 6.1, < 7.1)
401 + rubocop (1.56.4)
402 + base64 (~> 0.1.1)
403 + json (~> 2.3)
404 + language_server-protocol (>= 3.17.0)
405 + parallel (~> 1.10)
406 + parser (>= 3.2.2.3)
407 + rainbow (>= 2.2.2, < 4.0)
408 + regexp_parser (>= 1.8, < 3.0)
409 + rexml (>= 3.2.5, < 4.0)
410 + rubocop-ast (>= 1.28.1, < 2.0)
411 + ruby-progressbar (~> 1.7)
412 + unicode-display_width (>= 2.4.0, < 3.0)
413 + rubocop-ast (1.29.0)
414 + parser (>= 3.2.1.0)
415 + ruby-progressbar (1.13.0)
416 + ruby2_keywords (0.0.5)
417 + rubystats (0.4.1)
418 + matrix
419 + safe_yaml (1.0.5)
420 + sass (3.7.4)
421 + sass-listen (~> 4.0.0)
422 + sass-listen (4.0.0)
423 + rb-fsevent (~> 0.9, >= 0.9.4)
424 + rb-inotify (~> 0.9, >= 0.9.7)
425 + sass-rails (6.0.0)
426 + sassc-rails (~> 2.1, >= 2.1.1)
427 + sassc (2.4.0)
428 + ffi (~> 1.9)
429 + sassc-rails (2.1.2)
430 + railties (>= 4.0.0)
431 + sassc (>= 2.0)
432 + sprockets (> 3.0)
433 + sprockets-rails
434 + tilt
435 + sdoc (2.6.1)
436 + rdoc (>= 5.0)
437 + semantic_range (3.0.0)
438 + sentry-rails (5.11.0)
439 + railties (>= 5.0)
440 + sentry-ruby (~> 5.11.0)
441 + sentry-ruby (5.11.0)
442 + concurrent-ruby (~> 1.0, >= 1.0.2)
443 + sidekiq (7.1.5)
444 + concurrent-ruby (< 2)
445 + connection_pool (>= 2.3.0)
446 + rack (>= 2.2.4)
447 + redis-client (>= 0.14.0)
448 + signet (0.18.0)
449 + addressable (~> 2.8)
450 + faraday (>= 0.17.5, < 3.a)
451 + jwt (>= 1.5, < 3.0)
452 + multi_json (~> 1.10)
453 + sinatra (3.1.0)
454 + mustermann (~> 3.0)
455 + rack (~> 2.2, >= 2.2.4)
456 + rack-protection (= 3.1.0)
457 + tilt (~> 2.0)
458 + sitemap_generator (6.3.0)
459 + builder (~> 3.0)
460 + snaky_hash (2.0.1)
461 + hashie
462 + version_gem (~> 1.1, >= 1.1.1)
463 + split (4.0.2)
464 + redis (>= 4.2)
465 + rubystats (>= 0.3.0)
466 + sinatra (>= 1.2.6)
467 + spring (4.1.1)
468 + sprockets (3.7.2)
469 + concurrent-ruby (~> 1.0)
470 + rack (> 1, < 3)
471 + sprockets-rails (3.4.2)
472 + actionpack (>= 5.2)
473 + activesupport (>= 5.2)
474 + sprockets (>= 3.0.0)
475 + stringio (3.0.8)
476 + thor (1.2.2)
477 + tilt (2.3.0)
478 + timeout (0.4.0)
479 + tolk (4.1.0)
480 + rails (>= 5.0)
481 + safe_yaml (>= 0.8.6)
482 + sassc
483 + trailblazer-option (0.1.2)
484 + turbolinks (5.2.1)
485 + turbolinks-source (~> 5.2)
486 + turbolinks-source (5.2.0)
487 + tzinfo (2.0.6)
488 + concurrent-ruby (~> 1.0)
489 + uber (0.1.0)
490 + uglifier (4.2.0)
491 + execjs (>= 0.3.0, < 3)
492 + unicode-display_width (2.5.0)
493 + version_gem (1.1.3)
494 + warden (1.2.9)
495 + rack (>= 2.0.9)
496 + warden-jwt_auth (0.8.0)
497 + dry-auto_inject (>= 0.8, < 2)
498 + dry-configurable (>= 0.13, < 2)
499 + jwt (~> 2.1)
500 + warden (~> 1.2)
501 + webpacker (5.4.4)
502 + activesupport (>= 5.2)
503 + rack-proxy (>= 0.6.1)
504 + railties (>= 5.2)
505 + semantic_range (>= 2.3.0)
506 + webrick (1.8.1)
507 + websocket-driver (0.7.6)
508 + websocket-extensions (>= 0.1.0)
509 + websocket-extensions (0.1.5)
510 + will_paginate (3.3.1)
511 + zeitwerk (2.6.12)
512 +
513 +PLATFORMS
514 + ruby
515 +
516 +DEPENDENCIES
517 + activeadmin (= 2.9.0)
518 + activeadmin_json_editor (~> 0.0.7)
519 + activeadmin_quill_editor (= 0.3.4)
520 + autoprefixer-rails
521 + aws-sdk-s3
522 + bootstrap-sass (~> 3.2.0)
523 + browser
524 + coffee-rails
525 + devise
526 + devise-jwt
527 + devise_invitable (~> 2.0.0)
528 + dotenv-rails
529 + fog-aws
530 + font-awesome-rails
531 + google-api-client
532 + htmlbeautifier
533 + httpclient
534 + jbuilder
535 + jquery-rails
536 + letter_opener
537 + merit
538 + mini_racer
539 + omniauth-apple
540 + omniauth-facebook
541 + omniauth-google-oauth2
542 + omniauth-rails_csrf_protection
543 + omniauth-spotify
544 + pg
545 + puma (= 5.4.0)
546 + rack-cors
547 + rails (= 6.1.7.7)
548 + redis (= 4.4.0)
549 + route_translator
550 + rubocop
551 + sass-rails
552 + sdoc
553 + sentry-rails (>= 5.4.2)
554 + sentry-ruby (>= 5.4.2)
555 + sidekiq
556 + sitemap_generator
557 + split
558 + spring
559 + sprockets (= 3.7.2)
560 + tolk (= 4.1.0)
561 + turbolinks
562 + uglifier
563 + webpacker
564 + will_paginate (~> 3.3.0)
565 +
566 +RUBY VERSION
567 + ruby 3.2.2p53
568 +
569 +BUNDLED WITH
570 + 2.4.20
backend/musik88-web/README.md new
+116
@@ -0,0 +1,116 @@
1 +# Musik88
2 +
3 +Hey 👋
4 +
5 +Welcome to this awesome project. Since you're here, I assume you love music like I do. So, turn it up!
6 +
7 +## Tech stack
8 +
9 +This is a `Ruby on Rails` 6 application with several dependencies.
10 +
11 +1. Sidekiq for background job.
12 +2. Redis for in-memory database. This one is only used by the Sidekiq for now.
13 +3. PostgreSQL for the database.
14 +4. Yarn for the frontend.
15 +5. yt-dlp to download youtube to audio.
16 +
17 +`yt-dlp` needs the `ffmpeg`:
18 +
19 +Make sure you have the latest version for yt-dlp:
20 +sudo yt-dlp -U
21 +
22 +After that you can solve this problem by installing the missing ffmpeg.
23 +
24 +Ubuntu and debian:
25 +sudo apt-get install ffmpeg
26 +
27 +macOS:
28 +brew install ffmpeg
29 +
30 +Windows:
31 +choco install ffmpeg
32 +```
33 +
34 +Several recommendation to running this project in one's development machine:
35 +
36 +1. Recommended ruby version mamager is `rbenv`.
37 +2. Use Ruby in the `.ruby-version` file.
38 +3. Progress.app client is recommended.
39 +4. Use `nvm` to manage your Node version. Use Node version in the `.nvmrc` file.
40 +5. User Postbird app to manage your PostgreSQL.
41 +6. Use `mkcert` for local certificates.
42 +
43 +## Sidekiq
44 +
45 +We use Sidekiq for background task management. Run local sidekiq: `bundle exec sidekiq`
46 +
47 +## Running local development
48 +
49 +1. Clone the app.
50 +2. Install Ruby with the `rbenv`.
51 +3. Run `bundle install`.
52 +4. Copy the `master.key` file from the server. Make sure you've uploaded your key so you can ssh to the box.
53 +```bash
54 +scp deploy@musik88.com:apps/musik88-web/config/master.key config/master.key
55 +```
56 +5. Create a `musik88_com_development` database.
57 +6. Run `rails db:migrate`.
58 +7. Run `yarn install`.
59 +8. Run `rails s`.
60 +
61 +### Database
62 +
63 +1. Dump db from production with the command from the ssh server: pg_dump -h localhost -U dbuser -Fc dbname > dbname.dump
64 +2. Download the dump file: scp deploy@musik88.com:apps/path-to-dump-file.dump
65 +3. Import to your local db: pg_restore -c -d musik88_com_development musik88_com_production.dump
66 +
67 +There's a known issue with the db production and development about the meta table. If one's copy the db content from production, update the local schema migration table by adding a new row with the content from the schema.rb
68 +
69 +## Issues found
70 +
71 +Fix libv8
72 +
73 +1. bundle config build.libv8 --with-system-v8
74 +gem install pg -v '1.2.3' -- --with-pg-config=/Applications/Postgres.app/Contents/Versions/14/bin/pg_config
75 +
76 +
77 +### Mac M1 Chip
78 +
79 +Avoid opening your terminal using Rosetta, it will make things messier.
80 +
81 +rbenv 3.0.2 Mac M1
82 +
83 +```bash
84 +$ export SDKROOT="$(xcrun -show-sdk-path -sdk macosx)" # because the /usr/bin/clang shim automatically set it
85 +$ export RUBY_CONFIGURE_OPTS="CC=$(xcrun -f clang)"
86 +$ rbenv install 3.1.3
87 +```
88 +
89 +# Issue with openssl
90 +OPENSSL_CFLAGS=-Wno-error=implicit-function-declaration RUBY_CONFIGURE_OPTS=--with-readline-dir="$(brew --prefix readline)" rbenv install 3.0.6 --verbose
91 +
92 +## Use local `.env` file
93 +Provide sets of environment variables to run this app locally.
94 +
95 +
96 +## Application documentation
97 +
98 +### Follow me
99 +
100 +This feature based on the infamous Michael Hart RoR tutorials: https://3rd-edition.railstutorial.org/book/following_users
101 +
102 +### JWT
103 +
104 +
105 +https://enlear.academy/how-to-create-a-rails-6-api-with-devise-jwt-46fa35085e85
106 +https://tergell.medium.com/rails-devise-jwt-tutorial-b5d5b03d9040
107 +
108 +### Mailers
109 +
110 +During the development, any mailer wil be handled by the `letter_opener` gem. To preview emails, visit `https://localhost:3001/rails/mailers/` during development.
111 +
112 +## Test
113 +
114 +### Run test
115 +
116 +bundle exec rake test
backend/musik88-web/Rakefile new
+6
@@ -0,0 +1,6 @@
1 +# Add your own tasks in files placed in lib/tasks ending in .rake,
2 +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 +
4 +require File.expand_path('../config/application', __FILE__)
5 +
6 +Rails.application.load_tasks
backend/musik88-web/_debug.sh new
+9
@@ -0,0 +1,9 @@
1 +curl --include \
2 + --no-buffer \
3 + --header "Connection: Upgrade" \
4 + --header "Upgrade: websocket" \
5 + --header "Host: localhost:3001" \
6 + --header "Origin: https://localhost:3001" \
7 + --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
8 + --header "Sec-WebSocket-Version: 13" \
9 + https://localhost:3001/cable
\ No newline at end of file
backend/musik88-web/app/admin/album_artists.rb new
+15
@@ -0,0 +1,15 @@
1 +# ActiveAdmin.register AlbumArtist do
2 +# See permitted parameters documentation:
3 +# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
4 +#
5 +# permit_params :list, :of, :attributes, :on, :model
6 +#
7 +# or
8 +#
9 +# permit_params do
10 +# permitted = [:permitted, :attributes]
11 +# permitted << :other if params[:action] == 'create' && current_user.admin?
12 +# permitted
13 +# end
14 + # permit_params :album_id, :artist_id, :primary
15 +# end
backend/musik88-web/app/admin/album_providers.rb new
+42
@@ -0,0 +1,42 @@
1 +ActiveAdmin.register AlbumProvider do
2 + menu priority: 3, parent: 'Music Database'
3 + permit_params :album_id, :user_id
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column :provider_type
9 + column :user do |album|
10 + album.user.email
11 + end
12 + column :album
13 + column :views_count
14 + actions
15 + end
16 +
17 + filter :name
18 + filter :provider_type
19 + filter :user
20 + filter :album
21 + filter :created_at
22 + filter :updated_at
23 +
24 + show do
25 + attributes_table do
26 + row :name
27 + row :provider_type
28 + row :user
29 + row :album
30 + row :created_at
31 + row :updated_at
32 + end
33 + end
34 +
35 + form do |f|
36 + f.inputs do
37 + f.input :name
38 + f.input :album
39 + end
40 + f.actions
41 + end
42 +end
backend/musik88-web/app/admin/albums.rb new
+73
@@ -0,0 +1,73 @@
1 +ActiveAdmin.register Album do
2 + menu priority: 4, parent: 'Music Database'
3 + permit_params :name, :description, :user_id, :locale, album_artists_attributes: %i[id artist_id primary _destroy]
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column :description do |album|
9 + strip_tags album.description.truncate 150
10 + end
11 + column 'Artists' do |album|
12 + if album.artists.count.positive?
13 + album.artists.each_with_index do |artist, _index|
14 + a artist.name, href: admin_artist_url(artist)
15 + text_node ' | '.html_safe
16 + a 'edit', href: edit_admin_artist_url(artist)
17 + text_node '</br>'.html_safe
18 + end
19 + nil
20 + end
21 + end
22 + column 'Songs' do |album|
23 + if album.songs.count.positive?
24 + album.songs.each_with_index do |song, _index|
25 + a song.name, href: admin_song_url(song)
26 + text_node ' | '.html_safe
27 + a 'edit', href: edit_admin_song_url(song)
28 + text_node '</br>'.html_safe
29 + end
30 + text_node '</br>'.html_safe
31 + end
32 + link_to 'Tambah', "#{new_admin_song_path}?album_id=#{album.id.to_s}"
33 + end
34 + column :user
35 + column :views_count
36 + actions
37 + end
38 +
39 + filter :artists
40 + filter :songs
41 + filter :views_count
42 + filter :name
43 + filter :description
44 + filter :created_at
45 + filter :updated_at
46 +
47 + show do
48 + attributes_table do
49 + row :name
50 + row :description do |album|
51 + raw album.description
52 + end
53 + row :user
54 + row :created_at
55 + row :updated_at
56 + end
57 + end
58 +
59 + form do |f|
60 + f.inputs do
61 + f.has_many :album_artists, allow_destroy: true, new_record: 'Tambah Artis' do |a|
62 + a.input :artist, include_blank: false
63 + a.input :primary
64 + end
65 + end
66 + f.inputs do
67 + f.input :name
68 + f.input :user
69 + f.input :description, as: :quill_editor
70 + end
71 + f.actions
72 + end
73 +end
backend/musik88-web/app/admin/artist_providers.rb new
+42
@@ -0,0 +1,42 @@
1 +ActiveAdmin.register ArtistProvider do
2 + menu priority: 3, parent: 'Music Database'
3 + permit_params :artist_id, :user_id
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column :provider_type
9 + column :user do |artist|
10 + artist.user.email
11 + end
12 + column :artist
13 + column :views_count
14 + actions
15 + end
16 +
17 + filter :name
18 + filter :provider_type
19 + filter :user
20 + filter :artist
21 + filter :created_at
22 + filter :updated_at
23 +
24 + show do
25 + attributes_table do
26 + row :name
27 + row :provider_type
28 + row :user
29 + row :artist
30 + row :created_at
31 + row :updated_at
32 + end
33 + end
34 +
35 + form do |f|
36 + f.inputs do
37 + f.input :name
38 + f.input :artist
39 + end
40 + f.actions
41 + end
42 +end
backend/musik88-web/app/admin/artists.rb new
+54
@@ -0,0 +1,54 @@
1 +ActiveAdmin.register Artist do
2 + menu priority: 3, parent: 'Music Database'
3 + permit_params :name, :user_id, :description
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column :description do |artist|
9 + strip_tags artist.description.truncate 150
10 + end
11 + column 'Albums', max_width: '800px', min_width: '400px' do |artist|
12 + if artist.album.count.positive?
13 + artist.album.each_with_index do |album, _index|
14 + a album.name, href: admin_album_url(album)
15 + text_node ' | '.html_safe
16 + a 'edit', href: edit_admin_album_url(album)
17 + text_node '</br>'.html_safe
18 + end
19 + text_node '</br>'.html_safe
20 + end
21 + link_to 'Tambah', new_admin_album_path + '?artist_id=' + artist.id.to_s
22 + end
23 + column :user
24 + column :views_count
25 + actions
26 + end
27 +
28 + filter :album
29 + filter :views_count
30 + filter :name
31 + filter :created_at
32 + filter :updated_at
33 +
34 + show do
35 + attributes_table do
36 + row :name
37 + row :user
38 + row :description do |artist|
39 + raw artist.description
40 + end
41 + row :created_at
42 + row :updated_at
43 + end
44 + end
45 +
46 + form do |f|
47 + f.inputs do
48 + f.input :name
49 + f.input :user
50 + f.input :description, as: :quill_editor
51 + end
52 + f.actions
53 + end
54 +end
backend/musik88-web/app/admin/audio_file.rb new
+46
@@ -0,0 +1,46 @@
1 +# frozen_string_literal: true
2 +
3 +# TODO: Fix this later
4 +# rubocop:disable Metrics/BlockLength
5 +ActiveAdmin.register AudioFile do
6 + menu priority: 6, parent: 'SplitFire 🔥'
7 + permit_params :song_provider_id, :is_public, :locale
8 +
9 + index do
10 + selectable_column
11 + column 'Image', max_width: '200px', min_width: '100px' do |file|
12 + if file.song_provider.present? && file.song_provider.image_url.present?
13 + image_tag(file.song_provider.image_url, width: 200,
14 + height: 100)
15 + end
16 + end
17 + column :song_provider
18 + column 'Split results' do |file|
19 + if file.splitfire_results.count.positive?
20 + file.splitfire_results.each_with_index do |result, _index|
21 + a result.source_file.blob.filename, href: admin_splitfire_result_url(result)
22 + text_node ' | '.html_safe
23 + a 'edit', href: admin_splitfire_result_url(result)
24 + text_node '</br>'.html_safe
25 + end
26 + nil
27 + end
28 + end
29 + actions
30 + end
31 +
32 + filter :song_provider
33 + filter :created_at
34 + filter :updated_at
35 +
36 + show do
37 + attributes_table do
38 + row :song_provider
39 + row :source_file do |file|
40 + file.source_file.blob.filename if !file.source_file.nil? && !file.source_file.blob.nil?
41 + end
42 + row :created_at
43 + row :updated_at
44 + end
45 + end
46 +end
backend/musik88-web/app/admin/authorizations.rb new
+43
@@ -0,0 +1,43 @@
1 +ActiveAdmin.register Authorization do
2 + menu priority: 2, parent: 'Users'
3 + permit_params :onboarding_status
4 +
5 + actions :all
6 +
7 + index do
8 + selectable_column
9 + column :user
10 + column :provider
11 + column :uid
12 + column :onboarding_status
13 + column :created_at
14 + column :updated_at
15 + actions
16 + end
17 +
18 + filter :user
19 + filter :provider
20 + filter :uid
21 + filter :onboarding_status
22 +
23 + show do
24 + attributes_table do
25 + row :user
26 + row :provider
27 + row :uid
28 + row :onboarding_status
29 + row :created_at
30 + row :updated_at
31 + end
32 + end
33 +
34 + form do |f|
35 + f.inputs do
36 + f.input :user
37 + f.input :provider
38 + f.input :uid
39 + f.input :onboarding_status
40 + end
41 + f.actions
42 + end
43 +end
backend/musik88-web/app/admin/chord_histories.rb new
+43
@@ -0,0 +1,43 @@
1 +ActiveAdmin.register ChordHistory do
2 + menu priority: 8, parent: 'Music Database'
3 + permit_params :chord_id, :user_id, :name
4 + actions :all, except: %i[edit destroy]
5 + config.clear_action_items!
6 +
7 + index do
8 + selectable_column
9 + column :chord
10 + column :user
11 + column :name
12 + column :created_at
13 + column :updated_at
14 + actions
15 + end
16 +
17 + filter :chord
18 + filter :user
19 + filter :name
20 + filter :created_at
21 + filter :updated_at
22 +
23 + show do
24 + attributes_table do
25 + row :chord
26 + row :user
27 + row :name
28 + row :created_at
29 + row :updated_at
30 + end
31 + end
32 +
33 + form do |f|
34 + f.inputs do
35 + f.input :chord, include_blank: false, selected: params[:chord_id]
36 + f.input :user, include_blank: false, selected: params[:user_id]
37 + f.input :name
38 + f.input :created_at
39 + f.input :updated_at
40 + end
41 + actions
42 + end
43 +end
backend/musik88-web/app/admin/chord_shapes.rb new
+37
@@ -0,0 +1,37 @@
1 +ActiveAdmin.register ChordShape do
2 + menu priority: 7, parent: 'Guitar'
3 + permit_params :chord_shape_id, :name, :youtube_link, :image_link
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column :youtube_link
9 + column :image_link
10 + actions
11 + end
12 +
13 + filter :name
14 + filter :youtube_link
15 + filter :image_link
16 + filter :created_at
17 + filter :updated_at
18 +
19 + show do
20 + attributes_table do
21 + row :name
22 + row :youtube_link
23 + row :image_link
24 + row :created_at
25 + row :updated_at
26 + end
27 + end
28 +
29 + form do |f|
30 + f.inputs do
31 + f.input :name
32 + f.input :youtube_link
33 + f.input :image_link
34 + end
35 + actions
36 + end
37 +end
backend/musik88-web/app/admin/chords.rb new
+63
@@ -0,0 +1,63 @@
1 +ActiveAdmin.register Chord do
2 + menu priority: 7, parent: 'Music Database'
3 + permit_params :song_provider_id, :chord, :user_id, :title
4 +
5 + index do
6 + selectable_column
7 + column :title
8 + column :song_provider
9 + column :user
10 + column :status
11 + column 'Histories' do |chord|
12 + if chord.chord_histories.count.positive?
13 + chord.chord_histories.order(updated_at: :desc).each_with_index do |history, _index|
14 + a history.name, href: admin_chord_history_url(history)
15 + text_node '</br>'.html_safe
16 + end
17 + nil
18 + else
19 + # Only add add link if no history.
20 + link_to 'Tambah',
21 + "#{new_admin_chord_history_path}?chord_id=#{chord.id}&created_at=#{chord.created_at}&user_id=#{chord.user_id}"
22 + end
23 + end
24 + column :views_count
25 + actions
26 + end
27 +
28 + filter :song_provider
29 + filter :views_count
30 + filter :lyric
31 + filter :status
32 + filter :created_at
33 + filter :updated_at
34 +
35 + show do
36 + attributes_table do
37 + row :title
38 + row :song_provider
39 + row :user
40 + row :status
41 + row :chord do |chord|
42 + raw chord.chord
43 + end
44 + row :created_at
45 + row :updated_at
46 + end
47 + end
48 +
49 + form do |f|
50 + f.inputs do
51 + f.input :title, include_blank: false
52 + if f.object.new_record?
53 + f.input :song_provider, include_blank: true, selected: params[:song_provider_id]
54 + else
55 + f.input :song_provider, include_blank: true
56 + end
57 + f.input :user, include_blank: true
58 + f.input :status, input_html: { readonly: true, disabled: true }, include_blank: false, selected: 0
59 + f.input :chord, as: :quill_editor
60 + end
61 + actions
62 + end
63 +end
backend/musik88-web/app/admin/comments.rb new
+28
@@ -0,0 +1,28 @@
1 +ActiveAdmin.register Comment, as: 'ContentComment' do
2 + menu priority: 4, parent: 'Music Database'
3 + actions :all, except: %i[new]
4 +
5 + index do
6 + selectable_column
7 + column :user
8 + column :body
9 + column :commentable_type
10 + column :commentable_id
11 + actions
12 + end
13 +
14 + filter :user
15 + filter :body
16 + filter :created_at
17 + filter :updated_at
18 +
19 + show do
20 + attributes_table do
21 + row :user
22 + row :body
23 + row :commentable_type
24 + row :created_at
25 + row :updated_at
26 + end
27 + end
28 +end
backend/musik88-web/app/admin/dashboard.rb new
+32
@@ -0,0 +1,32 @@
1 +ActiveAdmin.register_page 'Dashboard' do
2 + menu priority: 1, label: proc { I18n.t('active_admin.dashboard') }
3 +
4 + content title: proc { I18n.t('active_admin.dashboard') } do
5 + div class: 'blank_slate_container', id: 'dashboard_default_message' do
6 + span class: 'blank_slate' do
7 + span I18n.t('active_admin.dashboard_welcome.welcome')
8 + small I18n.t('active_admin.dashboard_welcome.call_to_action')
9 + end
10 + end
11 +
12 + # Here is an example of a simple dashboard with columns and panels.
13 + #
14 + # columns do
15 + # column do
16 + # panel "Recent Posts" do
17 + # ul do
18 + # Post.recent(5).map do |post|
19 + # li link_to(post.title, admin_post_path(post))
20 + # end
21 + # end
22 + # end
23 + # end
24 +
25 + # column do
26 + # panel "Info" do
27 + # para "Welcome to ActiveAdmin."
28 + # end
29 + # end
30 + # end
31 + end # content
32 +end
backend/musik88-web/app/admin/javanese_gending_notations.rb new
+49
@@ -0,0 +1,49 @@
1 +ActiveAdmin.register JavaneseGendingNotation do
2 + menu priority: 9, parent: 'Javanese Gending'
3 + permit_params :name, :notation, :user_id, :javanese_gending_id, :rich_format
4 + json_editor
5 +
6 + index do
7 + selectable_column
8 + column :name
9 + column :notation do |gending|
10 + strip_tags gending.notation.truncate 150
11 + end
12 + column :rich_format do |gending|
13 + gending.rich_format.nil? ? 'No ⛔️' : 'Yes ✅'
14 + end
15 + column :javanese_gending
16 + column :user
17 + column :views_count
18 + actions
19 + end
20 +
21 + show do
22 + attributes_table do
23 + row :name
24 + row :javanese_gending
25 + row :user
26 + row :notation do |gending|
27 + raw gending.notation
28 + end
29 + row :rich_format
30 + row :created_at
31 + row :updated_at
32 + end
33 + end
34 +
35 + form do |f|
36 + f.inputs do
37 + f.input :name
38 + if f.object.new_record?
39 + f.input :javanese_gending, include_blank: false, selected: params[:javanese_gending_id]
40 + else
41 + f.input :javanese_gending, include_blank: false
42 + end
43 + f.input :rich_format, as: :json
44 + f.input :user
45 + f.input :notation, as: :quill_editor
46 + end
47 + f.actions
48 + end
49 +end
backend/musik88-web/app/admin/javanese_gendings.rb new
+51
@@ -0,0 +1,51 @@
1 +ActiveAdmin.register JavaneseGending do
2 + menu priority: 8, parent: 'Javanese Gending'
3 + permit_params :name, :description, :notation, :user_id
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column 'NEW Notation' do |gending|
9 + if gending.javanese_gending_notation.count.positive?
10 + gending.javanese_gending_notation.each_with_index do |notation, _index|
11 + a "#{notation.name} #{notation.rich_format.nil? ? 'No ⛔️' : 'Yes ✅'}",
12 + href: admin_javanese_gending_notation_url(notation)
13 + text_node ' | '.html_safe
14 + a 'edit', href: edit_admin_javanese_gending_notation_url(notation)
15 + text_node '</br>'.html_safe
16 + end
17 + nil
18 + end
19 + link_to 'Tambah', new_admin_javanese_gending_notation_path(javanese_gending_id: gending.id.to_s)
20 + end
21 + column :description do |gending|
22 + strip_tags gending.description.truncate 150
23 + end
24 + column :user
25 + column :views_count
26 + actions
27 + end
28 +
29 + show do
30 + attributes_table do
31 + row :name
32 + row :notation
33 + row :user
34 + row :description do |gending|
35 + raw gending.description
36 + end
37 + row :created_at
38 + row :updated_at
39 + end
40 + end
41 +
42 + form do |f|
43 + f.inputs do
44 + f.input :name
45 + f.input :user
46 + f.input :description, as: :quill_editor
47 + f.input :notation, as: :quill_editor
48 + end
49 + f.actions
50 + end
51 +end
backend/musik88-web/app/admin/lyrics.rb new
+46
@@ -0,0 +1,46 @@
1 +ActiveAdmin.register Lyric do
2 + menu priority: 6, parent: 'Music Database'
3 + permit_params :song_id, :lyric, :locale, :user_id
4 +
5 + index do
6 + selectable_column
7 + column :lyric do |lyric|
8 + strip_tags lyric.lyric.truncate 150
9 + end
10 + column :song
11 + column :user
12 + column :views_count
13 + actions
14 + end
15 +
16 + filter :song
17 + filter :views_count
18 + filter :lyric
19 + filter :created_at
20 + filter :updated_at
21 +
22 + show do
23 + attributes_table do
24 + row :song
25 + row :user
26 + row :lyric do |song|
27 + raw song.lyric
28 + end
29 + row :created_at
30 + row :updated_at
31 + end
32 + end
33 +
34 + form do |f|
35 + f.inputs do
36 + if f.object.new_record?
37 + f.input :song, include_blank: false, selected: params[:song_id]
38 + else
39 + f.input :song, include_blank: false
40 + end
41 + f.input :user, include_blank: false
42 + f.input :lyric, :input_html => { :class => "tinymce", :style => 'width: auto' }
43 + end
44 + actions
45 + end
46 +end
backend/musik88-web/app/admin/meta.rb new
+41
@@ -0,0 +1,41 @@
1 +ActiveAdmin.register Meta do
2 + menu priority: 8, parent: 'Page'
3 + permit_params :home, :title, :description, :locale
4 +
5 + index do
6 + selectable_column
7 + column :home
8 + column :title
9 + column :description
10 + column :locale
11 + actions
12 + end
13 +
14 + filter :home
15 + filter :title
16 + filter :description
17 + filter :locale
18 + filter :created_at
19 + filter :updated_at
20 +
21 + show do
22 + attributes_table do
23 + row :home
24 + row :title
25 + row :description
26 + row :locale
27 + row :created_at
28 + row :updated_at
29 + end
30 + end
31 +
32 + form do |f|
33 + f.inputs do
34 + f.input :home
35 + f.input :title
36 + f.input :description
37 + f.input :locale
38 + end
39 + f.actions
40 + end
41 +end
backend/musik88-web/app/admin/pages.rb new
+47
@@ -0,0 +1,47 @@
1 +ActiveAdmin.register Page do
2 + menu priority: 8, parent: 'Page'
3 + permit_params :page_slug, :page_content, :locale, :title, :category
4 +
5 + index do
6 + selectable_column
7 + column :page_slug
8 + column :title
9 + column :page_content do |page|
10 + strip_tags page.page_content.truncate 150
11 + end
12 + column :locale
13 + column :category
14 + actions
15 + end
16 +
17 + filter :page_content
18 + filter :title
19 + filter :category
20 + filter :created_at
21 + filter :updated_at
22 +
23 + show do
24 + attributes_table do
25 + row :page_slug
26 + row :title
27 + row :category
28 + row :page_content do |page|
29 + raw page.page_content
30 + end
31 + row :locale
32 + row :created_at
33 + row :updated_at
34 + end
35 + end
36 +
37 + form do |f|
38 + f.inputs do
39 + f.input :page_slug
40 + f.input :title
41 + f.input :category
42 + f.input :locale
43 + f.input :page_content, as: :quill_editor
44 + end
45 + f.actions
46 + end
47 +end
backend/musik88-web/app/admin/song_artists.rb new
+15
@@ -0,0 +1,15 @@
1 +# ActiveAdmin.register SongArtist do
2 +# See permitted parameters documentation:
3 +# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
4 +#
5 +# permit_params :list, :of, :attributes, :on, :model
6 +#
7 +# or
8 +#
9 +# permit_params do
10 +# permitted = [:permitted, :attributes]
11 +# permitted << :other if params[:action] == 'create' && current_user.admin?
12 +# permitted
13 +# end
14 +# permit_params :album_id, :song_id, :artist_id, :primary
15 +# end
backend/musik88-web/app/admin/song_provider_vote.rb new
+39
@@ -0,0 +1,39 @@
1 +ActiveAdmin.register SongProviderVote do
2 + menu priority: 6, parent: 'Music Database'
3 + permit_params :user_id
4 +
5 + index do
6 + selectable_column
7 + column :user do |vote|
8 + vote.user.email
9 + end
10 + column :song_provider
11 + column :vote_type
12 + actions
13 + end
14 +
15 + filter :user
16 + filter :song_provider
17 + filter :vote_type
18 + filter :created_at
19 + filter :updated_at
20 +
21 + show do
22 + attributes_table do
23 + row :user
24 + row :song_provider
25 + row :vote_type
26 + row :created_at
27 + row :updated_at
28 + end
29 + end
30 +
31 + form do |f|
32 + f.inputs do
33 + f.input :song_provider
34 + f.input :user
35 + f.input :vote_type
36 + end
37 + f.actions
38 + end
39 +end
backend/musik88-web/app/admin/song_providers.rb new
+46
@@ -0,0 +1,46 @@
1 +ActiveAdmin.register SongProvider do
2 + menu priority: 5, parent: 'Music Database'
3 + permit_params :user_id, :status
4 +
5 + index do
6 + selectable_column
7 + column :name
8 + column :provider_type
9 + column :user do |song|
10 + song.user.email
11 + end
12 + column :song
13 + column :status
14 + column :views_count
15 + actions
16 + end
17 +
18 + filter :name
19 + filter :provider_type
20 + filter :user
21 + filter :song
22 + filter :status
23 + filter :created_at
24 + filter :updated_at
25 +
26 + show do
27 + attributes_table do
28 + row :name
29 + row :provider_type
30 + row :album_provider
31 + row :artist
32 + row :status
33 + row :created_at
34 + row :updated_at
35 + end
36 + end
37 +
38 + form do |f|
39 + f.inputs do
40 + f.input :name
41 + f.input :song
42 + f.input :status
43 + end
44 + f.actions
45 + end
46 +end
backend/musik88-web/app/admin/songs.rb new
+72
@@ -0,0 +1,72 @@
1 +ActiveAdmin.register Song do
2 + menu priority: 5, parent: 'Music Database'
3 + permit_params :album_id, :name, :user_id, :description, :locale,
4 + song_artists_attributes: %i[id artist_id primary _destroy]
5 +
6 + index do
7 + selectable_column
8 + column :name
9 + column :description do |song|
10 + strip_tags song.description.truncate 150
11 + end
12 + column :album
13 + column 'Lyrics' do |song|
14 + if song.lyrics.count.positive?
15 + song.lyrics.each_with_index do |lyric, index|
16 + a "V #{(index + 1).to_s}", href: admin_lyric_url(lyric)
17 + text_node ' '.html_safe
18 + a 'Edit', href: edit_admin_lyric_url(lyric)
19 + text_node '</br>'.html_safe
20 + end
21 + nil
22 + else
23 + link_to 'Add', "#{new_admin_lyric_path}?song_id=#{song.id.to_s}"
24 + end
25 + end
26 + column :user
27 + column :views_count
28 + actions
29 + end
30 +
31 + filter :album
32 + filter :artists
33 + filter :views_count
34 + filter :name
35 + filter :description
36 + filter :created_at
37 + filter :updated_at
38 +
39 + show do
40 + attributes_table do
41 + row :name
42 + row :user
43 + row :description do |song|
44 + raw song.description
45 + end
46 + row :created_at
47 + row :updated_at
48 + end
49 + end
50 +
51 + form do |f|
52 + f.inputs do
53 + f.has_many :song_artists, heading: 'Artist(s)',
54 + allow_destroy: true,
55 + new_record: 'Tambah Artis' do |a|
56 + a.input :artist
57 + a.input :primary
58 + end
59 + end
60 + f.inputs do
61 + if f.object.new_record?
62 + f.input :album, include_blank: false, selected: params[:album_id]
63 + else
64 + f.input :album, include_blank: false
65 + end
66 + f.input :user, include_blank: false
67 + f.input :name
68 + f.input :description, as: :quill_editor
69 + end
70 + f.actions
71 + end
72 +end
backend/musik88-web/app/admin/splitfire_result.rb new
+38
@@ -0,0 +1,38 @@
1 +# TODO: Fix this later
2 +# rubocop:disable Metrics/BlockLength
3 +ActiveAdmin.register SplitfireResult do
4 + menu priority: 7, parent: 'SplitFire 🔥'
5 + permit_params :song_id, :locale, :user_id
6 +
7 + index do
8 + selectable_column
9 + column :source_file do |file|
10 + file.source_file.blob.filename
11 + end
12 + column :split_audio_file do |file|
13 + file.audio_file.source_file.blob.filename
14 + end
15 + column :onset
16 + column :length
17 + actions
18 + end
19 +
20 + filter :user
21 + filter :created_at
22 + filter :updated_at
23 +
24 + show do
25 + attributes_table do
26 + row :source_file do |file|
27 + file.source_file.blob.filename
28 + end
29 + row :split_audio_file do |file|
30 + file.audio_file.source_file.blob.filename
31 + end
32 + row :onset
33 + row :length
34 + row :created_at
35 + row :updated_at
36 + end
37 + end
38 +end
backend/musik88-web/app/admin/user_settings.rb new
+25
@@ -0,0 +1,25 @@
1 +ActiveAdmin.register UserSetting do
2 + menu priority: 3, parent: 'Users'
3 + actions :all, except: %i[edit update]
4 +
5 + index do
6 + selectable_column
7 + column :user
8 + column :locale
9 + column :created_at
10 + column :updated_at
11 + actions
12 + end
13 +
14 + filter :user
15 + filter :locale
16 +
17 + show do
18 + attributes_table do
19 + row :user
20 + row :locale
21 + row :created_at
22 + row :updated_at
23 + end
24 + end
25 +end
backend/musik88-web/app/admin/users.rb new
+60
@@ -0,0 +1,60 @@
1 +ActiveAdmin.register User do
2 + menu priority: 2, parent: 'Users'
3 + permit_params :email, :username, :name, :instagram, :youtube_channel_id, :about, :password, :password_confirmation,
4 + :confirmed_at, :confirmation_sent_at
5 +
6 + actions :all
7 +
8 + index do
9 + selectable_column
10 + column :email
11 + column :name
12 + column :confirmation_sent_at
13 + column :confirmed_at
14 + column 'Providers' do |user|
15 + if user.authorizations.count.positive?
16 + user.authorizations.each_with_index do |auth, index|
17 + text_node "#{auth.provider}#{', ' if index != user.authorizations.size - 1}"
18 + end
19 + nil
20 + end
21 + end
22 + column :created_at
23 + column :updated_at
24 + actions
25 + end
26 +
27 + filter :email
28 + filter :name
29 + filter :created_at
30 + filter :updated_at
31 +
32 + show do
33 + attributes_table do
34 + row :email
35 + row :username
36 + row :name
37 + row :instagram
38 + row :youtube_channel_id
39 + row :about
40 + row :created_at
41 + row :updated_at
42 + end
43 + end
44 +
45 + form do |f|
46 + f.inputs do
47 + f.input :email
48 + f.input :username
49 + f.input :name
50 + f.input :instagram
51 + f.input :youtube_channel_id
52 + f.input :about
53 + f.input :confirmation_sent_at
54 + f.input :confirmed_at
55 + f.input :password
56 + f.input :password_confirmation
57 + end
58 + f.actions
59 + end
60 +end
backend/musik88-web/app/admin/wallets.rb new
+29
@@ -0,0 +1,29 @@
1 +ActiveAdmin.register Wallet do
2 + menu priority: 2, parent: 'Users'
3 + actions :all, except: %i[edit update]
4 +
5 + index do
6 + selectable_column
7 + column :user do |wallet|
8 + wallet.user.name.blank? ? wallet.user.email : wallet.user.name
9 + end
10 + column :wallet_type
11 + column :pubkey
12 + column :bach_token_account
13 + actions
14 + end
15 +
16 + filter :user
17 + filter :pubkey
18 + filter :bach_token_account
19 +
20 + show do
21 + attributes_table do
22 + row :user
23 + row :pubkey
24 + row :bach_token_account
25 + row :created_at
26 + row :updated_at
27 + end
28 + end
29 +end
backend/musik88-web/app/assets/config/manifest.js new
+7
@@ -0,0 +1,7 @@
1 +// app/assets/config/manifest.js
2 +//
3 +// CSS bundles
4 +//= link_tree ./../stylesheets
5 +//
6 +// JS bundles
7 +//= link_tree ./../javascripts
\ No newline at end of file
backend/musik88-web/app/assets/javascripts/.keep
backend/musik88-web/app/assets/stylesheets/.keep
backend/musik88-web/app/channels/application_cable/channel.rb new
+4
@@ -0,0 +1,4 @@
1 +module ApplicationCable
2 + class Channel < ActionCable::Channel::Base
3 + end
4 +end
\ No newline at end of file
backend/musik88-web/app/channels/application_cable/connection.rb new
+22
@@ -0,0 +1,22 @@
1 +module ApplicationCable
2 + class Connection < ActionCable::Connection::Base
3 + identified_by :current_user
4 +
5 + def connect
6 + params = request.query_parameters()
7 + access_token = params['accessToken']
8 +
9 + self.current_user = find_verified_user access_token
10 + end
11 +
12 + private
13 +
14 + def find_verified_user(access_token)
15 + if verified_user = User.find_by(id: 1)
16 + verified_user
17 + else
18 + reject_unauthorized_connection
19 + end
20 + end
21 + end
22 +end
backend/musik88-web/app/channels/bass64_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class Bass64Channel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from "Bass64Channel"
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/channels/bonzo_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class BonzoChannel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from "BonzoChannel"
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/channels/karokowe_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class KarokoweChannel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from "KarokoweChannel"
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/channels/splitfire_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class SplitfireChannel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from "SplitfireChannel"
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/channels/spotify_export_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class SpotifyExportChannel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from 'SpotifyExportChannel'
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/channels/wallet_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class WalletChannel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from 'WalletChannel'
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/channels/youtube_to_audio_channel.rb new
+9
@@ -0,0 +1,9 @@
1 +class YoutubeToAudioChannel < ApplicationCable::Channel
2 + def subscribed
3 + stream_from 'YoutubeToAudioChannel'
4 + end
5 +
6 + def unsubscribed
7 + stop_all_streams
8 + end
9 +end
backend/musik88-web/app/controllers/about_controller.rb new
+60
@@ -0,0 +1,60 @@
1 +class AboutController < ApplicationController
2 + def index
3 + meta_tags
4 +
5 + @page = Page
6 + .select(:page_content, :title)
7 + .where(page_slug: 'about-us', locale: [I18n.locale, I18n.default_locale])
8 + .last
9 + @page_title = t('nav_footer_about')
10 + end
11 +
12 + def contact
13 + meta_tags
14 +
15 + @page = Page
16 + .select(:page_content, :title)
17 + .where(page_slug: 'contact', locale: [I18n.locale, I18n.default_locale])
18 + .last
19 + @page_title = t('nav_footer_contact')
20 + end
21 +
22 + def term
23 + @meta_title = 'Koleksi lirik lagu baru, lirik lagu pop, informasi musik terkini | Lyrics88.Com'
24 + @meta_description = 'Temukan koleksi lirik lagu baru yang sedang populer: lirik lagu pop, ' +
25 + 'lirik lagu rock, lirik lagu Indonesia. Koleksi lagu Indonesia selalu diperbarui.'
26 +
27 + # @page = Page.find_by page_slug: 'terms-of-use', locale: I18n.locale
28 + @page_title = t('nav_footer_term')
29 + end
30 +
31 + def privacy
32 + @meta_title = 'Koleksi lirik lagu baru, lirik lagu pop, informasi musik terkini | Lyrics88.Com'
33 + @meta_description = 'Temukan koleksi lirik lagu baru yang sedang populer: lirik lagu pop, ' +
34 + 'lirik lagu rock, lirik lagu Indonesia. Koleksi lagu Indonesia selalu diperbarui.'
35 +
36 + # @page = Page.find_by page_slug: 'privacy-policy', locale: I18n.locale
37 + @page_title = t('nav_footer_privacy')
38 + end
39 +
40 + def sitemap
41 + redirect_to "https://#{ENV['S3_BUCKET']}.s3.amazonaws.com/sitemaps/sitemap.xml.gz"
42 + end
43 +
44 + def bach
45 + @page = Page.select(:id, :page_content).where(page_slug: 'bach', locale: [I18n.locale, I18n.default_locale]).last
46 + meta_tags
47 + end
48 +
49 + private
50 +
51 + def meta_tags
52 + meta = Meta
53 + .select(:title, :description)
54 + .where(home: 'bach', locale: [I18n.locale, I18n.default_locale])
55 + .last
56 + @meta_title = meta.title if meta
57 + @meta_description = meta.description if meta
58 + @meta_url = request.url
59 + end
60 +end
backend/musik88-web/app/controllers/albums_controller.rb new
+118
@@ -0,0 +1,118 @@
1 +class AlbumsController < ApplicationController
2 + def index
3 + meta_tags 'albums'
4 +
5 + @page = Page
6 + .select(:page_content, :title)
7 + .where(page_slug: 'albums', locale: [I18n.locale, I18n.default_locale])
8 + .last
9 +
10 + @albums = Album
11 + .includes(:artists)
12 + .select(:id, :name, :views_count, :description, :updated_at)
13 + .where(album_artists: { primary: true })
14 + .references(:artists)
15 + .paginate(page: params[:page], per_page: 10)
16 +
17 + @trending_albums = Album
18 + .includes(:artists)
19 + .select(:id, :name, :views_count, :description, :updated_at)
20 + .where(album_artists: { primary: true })
21 + .references(:artists)
22 + .order(views_count: :desc)
23 + .limit(10)
24 + end
25 +
26 + def detail
27 + @page_title_artist = t('album_artists')
28 + @page_title_songs = t('album_song_list')
29 +
30 + album_id = params[:slug].split('-')[-1]
31 + @album = Album
32 + .select('albums.id, albums.name, albums.user_id, albums.description, albums.views_count')
33 + .find_by id: album_id
34 +
35 + return redirect_to album_path unless @album
36 +
37 + @artists = Artist
38 + .select('artists.id, artists.name')
39 + .joins(:album_artists)
40 + .where(album_artists: { album_id: @album.id })
41 + .order('album_artists.primary DESC')
42 +
43 + @page_title = @album.name
44 +
45 + meta_tags 'album_detail'
46 + @album.increment!(:views_count) unless browser.bot?
47 + end
48 +
49 + def bridge
50 + @page_title_artist = t('album_artists')
51 + @page_title_songs = t('album_song_list')
52 +
53 + album_id = params[:slug].split('-')[-1]
54 + @album = AlbumProvider
55 + .select(:id, :name, :user_id, :provider_id, :provider_type)
56 + .find_by(id: album_id)
57 +
58 + return redirect_to album_path unless @album
59 +
60 + @page_title = @album.name
61 +
62 + meta_tags 'album_detail'
63 + end
64 +
65 + def add
66 + if request.post?
67 + album = Album.new(album_params)
68 + unless album.save
69 + flash[:warning] = "We need at the album's title and description."
70 + redirect_to album_add_path
71 + return
72 + end
73 + redirect_to album_detail_path(album.slug)
74 + end
75 +
76 + @album = Album.new(name: params[:name], description: params[:description])
77 + @user = user_signed_in? ? current_user : User.find_by(username: 'nowhereman')
78 + @page_title = 'Add Album'
79 +
80 + @meta_title = @page_title
81 + @meta_description = @page_title
82 + end
83 +
84 + def vote
85 + return redirect_to login_path unless user_signed_in?
86 +
87 + vote = AlbumVote.new(album_id: params[:id], user_id: current_user.id)
88 + vote.save
89 +
90 + redirect_back(fallback_location: root_path)
91 + end
92 +
93 + def unvote
94 + return redirect_to login_path unless user_signed_in?
95 +
96 + vote = AlbumVote.where(album_id: params[:id], user_id: current_user.id)
97 + vote.destroy_all
98 +
99 + redirect_back(fallback_location: root_path)
100 + end
101 +
102 + private
103 +
104 + def album_params
105 + params
106 + .require(:album)
107 + .permit(:name, :description, :user_id)
108 + end
109 +
110 + def meta_tags(slug)
111 + meta = Meta
112 + .select(:title, :description)
113 + .where(home: slug, locale: [I18n.locale, I18n.default_locale])
114 + .last
115 + @meta_title = meta.title if meta
116 + @meta_description = meta.description if meta
117 + end
118 +end
backend/musik88-web/app/controllers/api/v1/.keep
backend/musik88-web/app/controllers/api/v1/audio_file_controller.rb new
+350
@@ -0,0 +1,350 @@
1 +module Api
2 + module V1
3 + # Fix this later
4 + # rubocop:disable Metrics/ClassLength
5 + class AudioFileController < ApplicationController
6 + skip_before_action :verify_authenticity_token
7 +
8 + MAX_AUDIO_LENGTH_IN_MINUTES = 8
9 +
10 + def splitfire
11 + @audio_files = AudioFile.order(Arel.sql('random()')).limit(5)
12 + render_user_files
13 + end
14 +
15 + def splitfire_detail
16 + return render_error 'We need the id.' if params[:id].nil?
17 +
18 + begin
19 + @audio_file_id = Integer(params[:id])
20 + rescue ArgumentError
21 + return render_error 'id should be integer.'
22 + end
23 +
24 + @audio_file = AudioFile.find_by(id: @audio_file_id)
25 +
26 + return render_error 'File not found' unless @audio_file
27 +
28 + render_file
29 + end
30 +
31 + def user_files
32 + return render_error 'We need the userId.' if params[:userId].nil?
33 +
34 + begin
35 + user_id = Integer(params[:userId])
36 + rescue ArgumentError
37 + return render_error 'userId should be integer.'
38 + end
39 +
40 + @audio_files = AudioFile.where(user_id: user_id).all.with_attached_source_file.order(created_at: :desc)
41 + render_user_files
42 + end
43 +
44 + # Split request
45 + def split
46 + logger.info 'Split request'
47 + return render_error 'You must login.' unless current_user.present?
48 +
49 + provider_id = params[:provider_id]
50 + return render_error 'We need the provider id.' if provider_id.nil?
51 +
52 + process_song_provider(provider_id)
53 + end
54 +
55 + def karaoke
56 + return unless valid_request?
57 +
58 + if @audio_file.karaoke_file.present?
59 + render json: {
60 + code: 500,
61 + message: 'We are processing this file.'
62 + }
63 + return
64 + end
65 +
66 + AudioFileKarokoweStatus.new(
67 + audio_file_id: @audio_file.id,
68 + status: :processing,
69 + processing_progress: 3
70 + ).save
71 +
72 + KarokoweJob.perform_later(@audio_file.id)
73 +
74 + render json: {
75 + code: 200,
76 + message: 'KaroKowe is generating karaoke file for you.'
77 + }
78 + end
79 +
80 + def guitar_backing_track
81 + return unless valid_request?
82 +
83 + if @audio_file.guitar_backing_track_file.present?
84 + render json: {
85 + code: 500,
86 + message: 'We are processing this file.'
87 + }
88 + return
89 + end
90 +
91 + GuitarBackingTrackJob.perform_later(@audio_file.id)
92 +
93 + render json: {
94 + code: 200,
95 + message: 'We are generating guitar backing track file for you.'
96 + }
97 + end
98 +
99 + def bass_backing_track
100 + return unless valid_request?
101 +
102 + if @audio_file.bass_backing_track_file.present?
103 + render json: {
104 + code: 500,
105 + message: 'We are processing this file.'
106 + }
107 + return
108 + end
109 +
110 + BassBackingTrackJob.perform_later(@audio_file.id)
111 +
112 + render json: {
113 + code: 200,
114 + message: 'We are generating bass backing track file for you.'
115 + }
116 + end
117 +
118 + def drum_backing_track
119 + return unless valid_request?
120 +
121 + if @audio_file.drum_backing_track.present?
122 + render json: {
123 + code: 500,
124 + message: 'We are processing this file.'
125 + }
126 + return
127 + end
128 +
129 + DrumBackingTrackJob.perform_later(@audio_file.id)
130 +
131 + render json: {
132 + code: 200,
133 + message: 'We are generating drum backing track file for you.'
134 + }
135 + end
136 +
137 + private
138 +
139 + def render_error(message)
140 + logger.error message
141 + render json: { code: 500, message: message, audio_files: [] }, status: :unprocessable_entity
142 + end
143 +
144 + def render_file
145 + render json: {
146 + code: 200,
147 + message: 'OK.',
148 + audio_file: @audio_file.as_json_packed.merge(render_file_results)
149 + }, status: :ok
150 + end
151 +
152 + def render_file_results
153 + {
154 + results: @audio_file.splitfire_results.map do |result|
155 + result.as_json(only: :id).merge({
156 + filename: result.source_file.filename,
157 + source_file: polymorphic_url(result.source_file),
158 + length: result.length,
159 + onset: result.onset
160 + })
161 + end
162 + }
163 + end
164 +
165 + # Render audio_file as json
166 + def render_success_split_request(audio_file)
167 + render json: { code: 200, message: 'SplitFire is processing this file for you.', audio_file: audio_file },
168 + status: :ok
169 + end
170 +
171 + def render_user_files
172 + audio_files = @audio_files.map do |audio_file|
173 + filename = ''
174 + source_file = ''
175 +
176 + if !audio_file.source_file.nil? && audio_file.source_file.attached?
177 + filename = audio_file.source_file.filename
178 + source_file = polymorphic_url(audio_file.source_file) unless audio_file.source_file.nil?
179 + end
180 +
181 + # Karaoke file
182 + if !audio_file.karaoke_file.nil? && !audio_file.karaoke_file.source_file.nil? && audio_file.karaoke_file.source_file.attached?
183 + karaoke_filename = audio_file.karaoke_file.source_file.filename
184 + unless audio_file.karaoke_file.source_file.nil?
185 + karaoke_source_file = polymorphic_url(audio_file.karaoke_file.source_file)
186 + end
187 +
188 + karaoke_file = audio_file.karaoke_file.as_json.merge({
189 + filename: karaoke_filename,
190 + source_file: karaoke_source_file
191 + })
192 + end
193 +
194 + # Guitar backing track
195 + if audio_file.guitar_backing_track_file.present?
196 + if audio_file.guitar_backing_track_file.source_file.attached?
197 + filename = audio_file.guitar_backing_track_file.source_file.filename
198 + source_file = polymorphic_url(audio_file.guitar_backing_track_file.source_file)
199 + end
200 +
201 + guitar_backing_track_file = audio_file.guitar_backing_track_file.as_json.merge({
202 + filename: filename,
203 + source_file: source_file
204 + })
205 + end
206 +
207 + # Bass backing track
208 + if audio_file.bass_backing_track_file.present?
209 + if audio_file.bass_backing_track_file.source_file.attached?
210 + filename = audio_file.bass_backing_track_file.source_file.filename
211 + source_file = polymorphic_url(audio_file.bass_backing_track_file.source_file)
212 + end
213 +
214 + bass_backing_track_file = audio_file.bass_backing_track_file.as_json.merge({
215 + filename: filename,
216 + source_file: source_file
217 + })
218 + end
219 +
220 + # Drum backing track
221 + if audio_file.drum_backing_track.present?
222 + if audio_file.drum_backing_track.source_file.attached?
223 + filename = audio_file.drum_backing_track.source_file.filename
224 + source_file = polymorphic_url(audio_file.drum_backing_track.source_file)
225 + end
226 +
227 + drum_backing_track_file = audio_file.drum_backing_track.as_json.merge({
228 + filename: filename,
229 + source_file: source_file
230 + })
231 + end
232 +
233 + audio_file.as_json.merge({
234 + user: audio_file.user.as_json,
235 + status: audio_file.audio_file_split_status,
236 + status_karaoke: audio_file.audio_file_karokowe_status,
237 + filename: filename,
238 + source_file: source_file,
239 + youtube_thumbnail: audio_file.youtube_thumbnail,
240 + results: audio_file.splitfire_results.map do |result|
241 + result.as_json.merge({
242 + filename: result.source_file.filename,
243 + source_file: polymorphic_url(result.source_file)
244 + })
245 + end,
246 + karaoke_file: karaoke_file,
247 + guitar_backing_track_file: guitar_backing_track_file,
248 + bass_backing_track_file: bass_backing_track_file,
249 + drum_backing_track_file: drum_backing_track_file
250 + })
251 + end
252 +
253 + render json: {
254 + code: 200,
255 + message: 'OK',
256 + audio_files: audio_files
257 + }
258 + end
259 +
260 + # provider_id is the ActiveRecord id of the SongProvider
261 + def process_song_provider(provider_id)
262 + # Find song provider by provider_id
263 + song_provider = SongProvider.find(provider_id)
264 + # Make sure the song provider is found and provider_type is youtube
265 + return render_error 'Song provider not found.' if song_provider.nil? || song_provider.provider_type != 'youtube'
266 +
267 + # Make sure it's a valid youtube url
268 + return render_error 'Invalid youtube url.' unless valid_youtube_link?(song_provider.preview_url)
269 +
270 + # Make sure video length is valid
271 + unless valid_audio_length?(song_provider)
272 + return render_error "Less than #{MAX_AUDIO_LENGTH_IN_MINUTES} minutes, please."
273 + end
274 +
275 + # We are good to go
276 + process_youtube_link(song_provider)
277 + end
278 +
279 + def process_youtube_link(song_provider)
280 + audio_file = AudioFile.new(song_provider_id: song_provider.id, status: :downloading, progress: 0)
281 + return render_error 'Cannot create AudioFile record.' unless audio_file.save
282 +
283 + YoutubeToAudioJob.perform_later(song_provider.id)
284 + render_success_split_request(song_provider.audio_file)
285 + end
286 +
287 + def valid_audio_length?(song_provider)
288 + length_string = `yt-dlp --get-duration #{song_provider.preview_url}`
289 + length_strings = length_string.split(':')
290 + # We should have two elements, minutes and seconds
291 + return false unless length_strings.count == 2
292 +
293 + # We only care about the minutes part
294 + length_strings.first.to_i <= MAX_AUDIO_LENGTH_IN_MINUTES
295 + end
296 +
297 + def valid_url?(url)
298 + uri = URI.parse(url)
299 + uri.is_a?(URI::HTTP) && !uri.host.nil?
300 + rescue URI::InvalidURIError
301 + false
302 + end
303 +
304 + def valid_youtube_link?(youtube_video_url)
305 + uri = URI.parse(youtube_video_url)
306 +
307 + return false unless !uri.host.nil? && (uri.host.include?('youtube.com') || uri.host.include?('youtu.be'))
308 +
309 + # To make things simple, we only recognize youtube.com and youtu.be
310 + youtube_video_url =~ %r{^https*://[www.]*youtu[.]{1}*be[.com]{4}*/[watch?v=]*([a-zA-Z0-9_-]*)}
311 + end
312 +
313 + def valid_request?
314 + audio_id_param = params[:audioId]
315 +
316 + unless !audio_id_param.nil? && !audio_id_param.empty?
317 + render json: {
318 + code: 500,
319 + message: 'We need the audioId.',
320 + audio_files: []
321 + }
322 + return false
323 + end
324 +
325 + begin
326 + audio_id = Integer(audio_id_param)
327 + rescue ArgumentError
328 + render json: {
329 + code: 500,
330 + message: 'audioId should be integer.',
331 + audio_files: []
332 + }
333 + return false
334 + end
335 +
336 + @audio_file = AudioFile.find_by(id: audio_id)
337 +
338 + unless @audio_file.present?
339 + render json: {
340 + code: 500,
341 + message: 'I dont know what is happening. Sorry.'
342 + }
343 + return false
344 + end
345 +
346 + true
347 + end
348 + end
349 + end
350 +end
backend/musik88-web/app/controllers/api/v1/gamelan_controller.rb new
+14
@@ -0,0 +1,14 @@
1 +# frozen_string_literal: true
2 +
3 +module Api
4 + module V1
5 + class GamelanController < ApiController
6 +
7 + def detail
8 + id = params[:id]
9 + @notation = JavaneseGendingNotation.find(id)
10 + render json: @notation.as_json_packed
11 + end
12 + end
13 + end
14 +end
backend/musik88-web/app/controllers/api/v1/karaoke_controller.rb new
+76
@@ -0,0 +1,76 @@
1 +module Api
2 + module V1
3 + class KaraokeController < ApiController
4 + before_action :validate_request
5 +
6 + def lyric_synches
7 + render_file
8 + end
9 +
10 + def lyric_synches_post
11 + return unless valid_payload?
12 +
13 + @payload.map do |lyric_synch|
14 + lyric_synch.delete('id')
15 + lyric_synch['audio_file_id'] = params[:audio_file_id]
16 + lyric_synch['user_id'] = current_user.id
17 + end
18 +
19 + LyricSynch.insert_all(@payload)
20 + render_file
21 + end
22 +
23 + def lyric_synches_put
24 + return unless valid_payload?
25 +
26 + LyricSynch.upsert_all(@payload, unique_by: :id)
27 + render_file
28 + end
29 +
30 + private
31 +
32 + def validate_request
33 + return render_error 'You must login.' unless user_signed_in?
34 +
35 + return render_error 'We need the id.' if params[:audio_file_id].nil?
36 +
37 + begin
38 + @audio_file_id = Integer(params[:audio_file_id])
39 + rescue ArgumentError
40 + return render_error 'audio_file_id should be integer.'
41 + end
42 +
43 + true
44 + end
45 +
46 + def valid_payload?
47 + information = request.raw_post
48 + data_parsed = JSON.parse(information)
49 + @payload = data_parsed['payload']
50 + if @payload.nil?
51 + render_error 'We need the payload.'
52 + return false
53 + end
54 +
55 + true
56 + end
57 +
58 + def render_error(message)
59 + render json: {
60 + code: 500,
61 + message: message,
62 + audio_files: []
63 + }
64 + end
65 +
66 + def render_file
67 + @lyric_synches = LyricSynch.where(audio_file_id: @audio_file_id)
68 + render json: {
69 + code: 200,
70 + message: 'OK.',
71 + lyric_synches: @lyric_synches.as_json(only: %i[id lyric time])
72 + }
73 + end
74 + end
75 + end
76 +end
backend/musik88-web/app/controllers/api/v1/search_controller.rb new
+119
@@ -0,0 +1,119 @@
1 +# frozen_string_literal: true
2 +
3 +module Api
4 + module V1
5 + class SearchController < ApiController
6 + QUERY_LIMIT_AUTOCOMPLETE = 5
7 +
8 + def index
9 + keyword = params[:q].downcase
10 + client = params[:client].to_sym
11 + render json: {
12 + results: merge_search(keyword, client, QUERY_LIMIT_AUTOCOMPLETE)
13 + }
14 + end
15 +
16 + # Save search history
17 + def post
18 + search_term = params[:term]
19 + logger.info "Search term: #{search_term}"
20 + # Append cound if search term exists
21 + search_history = SearchTerm.find_by(term: search_term)
22 + if search_history
23 + search_history.increment!(:count)
24 + else
25 + SearchTerm.new(term: search_term, count: 1).save
26 + end
27 +
28 + render json: { message: 'Ok.' }, status: :ok
29 + end
30 +
31 + private
32 +
33 + def merge_search(keyword, client, limit)
34 + case client
35 + when :web_beta
36 + merge_search_web_beta(keyword, limit)
37 + when :web_v1, :web_v1_localhost
38 + merge_search_web_v1(keyword, limit)
39 + when :splitfire
40 + merge_search_splitfire(keyword, limit)
41 + else
42 + merge_search_splitfire(keyword, limit) # Remove this after we add client param to splitfire
43 + end
44 + end
45 +
46 + def merge_search_web_beta(keywords, limit)
47 + search_gending_notation(keywords, limit) + search_gending(keywords, limit) +
48 + search_artist(keywords, limit) + search_song(keywords, limit) + search_album(keywords, limit)
49 + end
50 +
51 + def merge_search_web_v1(keywords, limit)
52 + logger.debug "merge_search_web_v1: Searching for #{keywords}"
53 + search_gending_notation(keywords, limit) + search_gending(keywords, limit)
54 + end
55 +
56 + def merge_search_splitfire(keywords, limit)
57 + search_song(keywords, limit)
58 + end
59 +
60 + def search_song(keywords, limit)
61 + SongProvider
62 + .where('lower(name) LIKE ? AND provider_type = 0', "%#{keywords}%")
63 + .limit(limit)
64 + .as_json(only: %i[id name])
65 + end
66 +
67 + def search_gending_notation(keywords, limit)
68 + JavaneseGendingNotation
69 + .where('lower(name) LIKE ?', "%#{keywords}%")
70 + .limit(limit)
71 + .as_json(only: %i[id name])
72 + end
73 +
74 + def search_artist(keywords, limit)
75 + Artist
76 + .where('lower(name) LIKE ?', "%#{keywords}%")
77 + .limit(limit)
78 + .as_json(only: %i[id name]) +
79 + search_artist_provider(keywords, limit)
80 + end
81 +
82 + def search_album(keywords, limit)
83 + Album
84 + .where('lower(name) LIKE ?', "%#{keywords}%")
85 + .limit(limit)
86 + .as_json(only: %i[id name]) +
87 + search_album_provider(keywords, limit)
88 + end
89 +
90 + def search_album_provider(keywords, limit)
91 + AlbumProvider
92 + .where('lower(name) LIKE ?', "%#{keywords}%")
93 + .limit(limit)
94 + .as_json(only: %i[id name])
95 + end
96 +
97 + def search_artist_provider(keywords, limit)
98 + ArtistProvider
99 + .where('lower(name) LIKE ?', "%#{keywords}%")
100 + .limit(limit)
101 + .as_json(only: %i[id name])
102 + end
103 +
104 + def search_song_provider(keywords, limit)
105 + SongProvider
106 + .where('lower(name) LIKE ?', "%#{keywords}%")
107 + .limit(limit)
108 + .as_json(only: %i[id name])
109 + end
110 +
111 + def search_gending(keywords, limit)
112 + JavaneseGending
113 + .where('lower(name) LIKE ?', "%#{keywords}%")
114 + .limit(limit)
115 + .as_json(only: %i[id name])
116 + end
117 + end
118 + end
119 +end
backend/musik88-web/app/controllers/api/v1/song_bridge_controller.rb new
+166
@@ -0,0 +1,166 @@
1 +# frozen_string_literal: true
2 +
3 +module Api
4 + module V1
5 + class SongBridgeController < ApiController
6 + before_action :authenticate_user!, only: %i[add_song_provider vote]
7 +
8 + def add_song_provider
9 + find_song_provider
10 + if @song_provider.present?
11 + return render json: { code: 200, message: 'Song provider already exists' }, status: :ok
12 + end
13 +
14 + find_or_create_base_song
15 + create_song_provider
16 + render json: { code: 200, message: 'Song provider created', song: @song }, status: :created
17 + end
18 +
19 + def find_audio
20 + # Get the Spotify track ID from the URL
21 + song_provider = SongProvider.find(params[:id])
22 + song = song_provider.song || Song.create(name: song_provider.name, user_id: song_provider.user_id)
23 + # Find available Spotify token
24 + authorization = User.joins(:user_setting).where(user_setting: { allowed_use_spotify_token: true }).first.spotify
25 + # If no available Spotify token, return
26 + return render json: { error: 'No available Spotify token' }, status: :unprocessable_entity unless authorization
27 +
28 + # Create Spotify client
29 + client = SpotifyClient.new(authorization)
30 + # Call Spotify API
31 + response = client.get_track(song_provider.provider_id)
32 + # Parse response
33 + spotify_track = JSON.parse(response.body)
34 + p spotify_track
35 + images = []
36 + images = spotify_track['album']['images'] if spotify_track['error'].nil?
37 + album_name = ''
38 + album_name = spotify_track['album']['name'] if spotify_track['error'].nil?
39 +
40 + youtubes = SongProvider
41 + .where(
42 + 'lower(description) LIKE ? AND provider_type = 1',
43 + "%#{song_provider.name}%"
44 + )
45 + .limit(10)
46 + .as_json(only: %i[id name provider_id image_url])
47 +
48 + render json: {
49 + code: 200,
50 + name: song_provider.name,
51 + album_name: album_name,
52 + images: images,
53 + items: youtubes || []
54 + }
55 + end
56 +
57 + def vote
58 + song_provider = SongProvider.find_by(id: params[:provider_id])
59 + insert_or_remove_vote
60 +
61 + render json: { code: 200, message: 'Song provider found.', song_provider: song_provider,
62 + votes: song_provider.song_provider_vote },
63 + status: :ok
64 + end
65 +
66 + def detail
67 + @provider = SongProvider.find_by(id: params[:id], provider_type: :youtube)
68 + return render json: { code: 404, message: 'Song provider not found' }, status: :not_found unless @provider
69 +
70 + render_song_provider
71 + end
72 +
73 + def carousel
74 + @songs = SongProvider.where(provider_type: :youtube).order(Arel.sql('random()')).limit(5)
75 + render json: {
76 + code: 200,
77 + message: 'OK',
78 + audio_files: @songs
79 + }
80 + end
81 +
82 + def top_votes
83 + # Find most voted song providers
84 + @songs = SongProvider.joins(:song_provider_vote).group(:id).order('count(song_provider_votes.id) DESC').limit(20)
85 + render json: {
86 + code: 200,
87 + message: 'OK',
88 + audio_files: @songs
89 + }
90 + end
91 +
92 + def ready_to_play
93 + # Find all songs that are ready to play
94 + @songs = SongProvider.where(audio_file: { status: :done }, status: :published).joins(:audio_file)
95 + render json: {
96 + code: 200,
97 + message: 'OK',
98 + audio_files: @songs
99 + }
100 + end
101 +
102 + private
103 +
104 + def find_song_provider
105 + provider_id = params[:provider_id]
106 + provider = SongProvider.provider_types[params[:provider]]
107 + @song_provider = SongProvider.find_by(provider_id: provider_id, provider_type: provider)
108 + end
109 +
110 + def create_song_provider
111 + provider_id = params[:provider_id]
112 + name = params[:name]
113 + provider = SongProvider.provider_types[params[:provider]]
114 + @song_provider = SongProvider.create(
115 + provider_id: provider_id,
116 + name: name,
117 + provider_type: provider,
118 + user_id: current_user.id,
119 + song_id: @song.id
120 + )
121 + end
122 +
123 + def find_or_create_base_song
124 + reference = SongProvider.find(params[:reference_id])
125 + @song = if reference.song.present?
126 + reference.song
127 + else
128 + Song.create(
129 + name: reference.name,
130 + user_id: current_user.id
131 + )
132 + end
133 + end
134 +
135 + def insert_or_remove_vote
136 + vote_type = params[:vote_type]
137 + provider_id = params[:provider_id]
138 + # This should be validated in the front end
139 + @vote = SongProviderVote.find_by(user_id: current_user.id, song_provider_id: provider_id)
140 + # If vote exists, remove it.
141 + if @vote.present?
142 + @vote.destroy
143 + else
144 + @vote = SongProviderVote.new(user_id: current_user.id, song_provider_id: provider_id, vote_type: vote_type)
145 + @vote.save
146 + end
147 + end
148 +
149 + def render_song_provider
150 + render json: { code: 200, message: 'Song provider found', song_provider: @provider,
151 + votes: @provider.song_provider_vote }, status: :ok
152 + end
153 +
154 + def process_song_provider
155 + @audio_file = AudioFile.new(status: :downloading, song_provider_id: @provider.id)
156 + @audio_file.save
157 +
158 + YoutubeToAudioJob.perform_later(@provider.id)
159 +
160 + # Update provider
161 + @provider = @audio_file.song_provider
162 + render_song_provider
163 + end
164 + end
165 + end
166 +end
backend/musik88-web/app/controllers/api/v1/user_controller.rb new
+155
@@ -0,0 +1,155 @@
1 +module Api
2 + module V1
3 + class UserController < Devise::SessionsController
4 + skip_before_action :verify_authenticity_token
5 + respond_to :json
6 +
7 + def login
8 + find_user
9 + if @user&.valid_password?(params[:password])
10 + sign_in(:user, @user)
11 + render_success
12 + else
13 + render_error 'Invalid credentials.'
14 + end
15 + end
16 +
17 + def logout
18 + log_out_success && return if current_user
19 +
20 + log_out_failure
21 + end
22 +
23 + def register
24 + name = params[:name]
25 + email = params[:email]
26 + password = params[:password]
27 +
28 + unless !name.nil? && !name.empty? && !email.nil? && !email.empty? && !password.nil? && !password.empty?
29 + render_error 'Please fill name, email, and password.'
30 + return
31 + end
32 +
33 + @user = User.new(
34 + name: name,
35 + email: email,
36 + password: password
37 + )
38 +
39 + if @user.save
40 + # Deliver the signup email.
41 + UserNotifierMailer.send_signup_email(user).deliver
42 + render_success
43 + else
44 + # Get Devise proper error response.
45 + render_error 'Something wrong with the registration process. Try again later.'
46 + end
47 + end
48 +
49 + def profile
50 + find_user
51 + if @user
52 + render_success
53 + else
54 + render_error 'Unknown user.'
55 + end
56 + end
57 +
58 + def following
59 + find_user
60 + if @user
61 + render_user_following
62 + else
63 + render_error 'Unknown user.'
64 + end
65 + end
66 +
67 + def followers
68 + find_user
69 + if @user
70 + render_user_followers
71 + else
72 + render_error 'Unknown user.'
73 + end
74 + end
75 +
76 + def community
77 + username_to_exclude = params[:exclude]
78 + users = User.all.order(created_at: :asc)
79 + @filtered_users = users.reject(username_to_exclude)
80 +
81 + if @filtered_users
82 + render_community
83 + else
84 + render_error 'Unknown error.'
85 + end
86 + end
87 +
88 + private
89 +
90 + def find_user
91 + username = params[:username].downcase
92 + @user = User.find_by(username: username) || User.find_by(email: username) || User.find_by(id: username)
93 + end
94 +
95 + def render_success
96 + render json: {
97 + code: 200,
98 + message: 'OK.',
99 + user: @user.as_json_packed
100 + }
101 + end
102 +
103 + def render_error(message)
104 + render json: {
105 + code: 500,
106 + message: message
107 + }
108 + end
109 +
110 + def render_user_following
111 + render json: {
112 + code: 200,
113 + message: 'OK.',
114 + user: @user.as_json_packed,
115 + following: @user.following.map(&:as_json_packed)
116 + }
117 + end
118 +
119 + def render_user_followers
120 + render json: {
121 + code: 200,
122 + message: 'OK.',
123 + user: @user.as_json_packed,
124 + followers: @user.followers.map(&:as_json_packed)
125 + }
126 + end
127 +
128 + def render_community
129 + render json: {
130 + code: 200,
131 + message: 'OK.',
132 + users: @filtered_users.map(&:as_json_packed)
133 + }
134 + end
135 +
136 + def respond_with(_resource, _opts = {})
137 + render json: { message: 'You are logged in.' }, status: :ok
138 + end
139 +
140 + def respond_to_on_destroy
141 + log_out_success && return if current_user
142 +
143 + log_out_failure
144 + end
145 +
146 + def log_out_success
147 + render json: { message: 'You are logged out.' }, status: :ok
148 + end
149 +
150 + def log_out_failure
151 + render json: { message: 'Hmm nothing happened.' }, status: :unauthorized
152 + end
153 + end
154 + end
155 +end
backend/musik88-web/app/controllers/api/v1/user_controller.rb.bak new
+125
@@ -0,0 +1,125 @@
1 +module Api
2 + module V1
3 + class UserController < ApplicationController
4 + skip_before_action :verify_authenticity_token
5 +
6 + def login
7 + username = params[:username]
8 + password = params[:password]
9 + find_user
10 + if @user && @user.valid_password?(params[:password])
11 + render json: {
12 + code: 200,
13 + message: 'OK.',
14 + user: @user
15 + }
16 + else
17 + render_error 'Invalid credentials.'
18 + end
19 + end
20 +
21 + def register
22 + name = params[:name]
23 + email = params[:email]
24 + password = params[:password]
25 +
26 + unless !name.nil? && !name.empty? && !email.nil? && !email.empty? && !password.nil? && !password.empty?
27 + render_error 'Please fill name, email, and password.'
28 + return
29 + end
30 +
31 + user = User.new(
32 + name: name,
33 + email: email,
34 + password: password
35 + )
36 +
37 + if user.save
38 + # Deliver the signup email.
39 + UserNotifierMailer.send_signup_email(user).deliver
40 +
41 + render json: {
42 + code: 200,
43 + message: 'OK',
44 + user: user
45 + }
46 + else
47 + # Get Devise proper error response.
48 + render_error 'Something wrong with the registration process. Try again later.'
49 + end
50 + end
51 +
52 + def profile
53 + find_user
54 + if @user
55 + render json: {
56 + code: 200,
57 + message: 'OK.',
58 + user: @user.as_json_packed
59 + }
60 + else
61 + render_error 'Unknown user.'
62 + end
63 + end
64 +
65 + def following
66 + find_user
67 + if @user
68 + render json: {
69 + code: 200,
70 + message: 'OK.',
71 + user: @user.as_json_packed,
72 + following: @user.following.map(&:as_json_packed)
73 + }
74 + else
75 + render_error 'Unknown user.'
76 + end
77 + end
78 +
79 + def followers
80 + find_user
81 + if @user
82 + render json: {
83 + code: 200,
84 + message: 'OK.',
85 + user: @user.as_json_packed,
86 + followers: @user.followers.map(&:as_json_packed)
87 + }
88 + else
89 + render_error 'Unknown user.'
90 + end
91 + end
92 +
93 + def community
94 + username_to_exclude = params[:exclude]
95 +
96 + users = User.all.order(created_at: :asc)
97 + result = users.select{ |user| user.username != username_to_exclude}
98 +
99 + if users
100 + render json: {
101 + code: 200,
102 + message: 'OK.',
103 + users: result.map(&:as_json_packed)
104 + }
105 + else
106 + render_error 'Unknown error.'
107 + end
108 + end
109 +
110 + private
111 +
112 + def find_user
113 + username = params[:username]
114 + @user = User.find_by(username: username.downcase) || User.find_by(id: username.downcase)
115 + end
116 +
117 + def render_error(message)
118 + render json: {
119 + code: 500,
120 + message: message
121 + }
122 + end
123 + end
124 + end
125 +end
backend/musik88-web/app/controllers/api/v1/welcome_controller.rb new
+39
@@ -0,0 +1,39 @@
1 +module Api
2 + module V1
3 + class WelcomeController < ApplicationController
4 + def index
5 + @meta_title = 'Welcome to Musik88 API V1'
6 + @meta_description = 'Welcome to Musik88 Application Programming Interface V1'
7 + @meta_url = request.url
8 + end
9 +
10 + def root
11 + render json: {
12 + code: 200,
13 + message: 'OK',
14 + platform: params[:platform],
15 + tabs_links: tabs_links
16 + }
17 + end
18 +
19 + private
20 +
21 + def host
22 + Rails.env.production? ? 'musik88.com' : 'localhost:3001'
23 + end
24 +
25 + def tabs_links
26 + [{
27 + type: 'home',
28 + link: "https://#{host}/api/v1/home"
29 + }, {
30 + type: 'search',
31 + link: "https://#{host}/api/v1/search"
32 + }, {
33 + type: 'profile',
34 + link: "https://#{host}/api/v1/profile"
35 + }]
36 + end
37 + end
38 + end
39 +end
backend/musik88-web/app/controllers/api/welcome_controller.rb new
+9
@@ -0,0 +1,9 @@
1 +module Api
2 + class WelcomeController < ApplicationController
3 + def index
4 + @meta_title = 'Welcome to Musik88 API'
5 + @meta_description = 'Welcome to Musik88 Application Programming Interface'
6 + @meta_url = request.url
7 + end
8 + end
9 +end
backend/musik88-web/app/controllers/api_controller.rb new
+9
@@ -0,0 +1,9 @@
1 +class ApiController < ActionController::API
2 + before_action :set_default_format
3 +
4 + private
5 +
6 + def set_default_format
7 + request.format = :json
8 + end
9 +end
backend/musik88-web/app/controllers/application_controller.rb new
+27
@@ -0,0 +1,27 @@
1 +class ApplicationController < ActionController::Base
2 + # Prevent CSRF attacks by raising an exception.
3 + # For APIs, you may want to use :null_session instead.
4 + protect_from_forgery with: :exception
5 + before_action :set_locale, :remove_splitfire_user_id
6 +
7 + protected
8 +
9 + def access_denied(exception)
10 + flash[:danger] = exception.message
11 + redirect_to root_path
12 + end
13 +
14 + def authenticate_admin!
15 + redirect_to new_user_session_path unless current_user.admin?
16 + end
17 +
18 + def remove_splitfire_user_id
19 + cookies.delete(:splitfire_user_id)
20 + end
21 +
22 + private
23 +
24 + def set_locale
25 + I18n.locale = params[:locale] || I18n.default_locale
26 + end
27 +end
backend/musik88-web/app/controllers/artists_controller.rb new
+120
@@ -0,0 +1,120 @@
1 +class ArtistsController < ApplicationController
2 + def index
3 + meta = Meta
4 + .select(:title, :description)
5 + .where(home: 'artists', locale: [I18n.locale, I18n.default_locale])
6 + .last
7 +
8 + @meta_title = meta.title if meta
9 + @meta_description = meta.description if meta
10 +
11 + @page = Page
12 + .select(:page_content, :title)
13 + .where(page_slug: 'artists', locale: [I18n.locale, I18n.default_locale])
14 + .last
15 +
16 + @artists = Artist
17 + .select(:id, :name, :description, :views_count, :updated_at)
18 + .paginate(page: params[:page], per_page: 12)
19 +
20 + @trending_artists = Artist
21 + .select(:id, :name, :description, :views_count, :updated_at)
22 + .order(views_count: :desc)
23 + .limit(10)
24 + end
25 +
26 + def detail
27 + artist_id = params[:slug].split('-')[-1]
28 + @artist = Artist
29 + .select(:id, :name, :description, :user_id, :views_count)
30 + .find_by(id: artist_id)
31 +
32 + redirect_to artist_path and return if @artist.nil?
33 +
34 + @page_title = t('artist_profile_title', artist: @artist.name)
35 + @page_title_album = t('artist_profile_album_title', artist: @artist.name)
36 +
37 + @albums = Album
38 + .joins(:album_artists)
39 + .where(album_artists: { artist_id: @artist.id, primary: true })
40 +
41 + @page_title_album_contribute_to = t('artist_profile_album_contribution_title', artist: @artist.name)
42 + @albums_contribute_to = Album
43 + .select('albums.id, albums.name')
44 + .joins(:album_artists)
45 + .where(album_artists: { artist_id: @artist.id, primary: false })
46 + @primary_artists = []
47 + @albums_contribute_to.each do |album|
48 + artist = Artist
49 + .select('artists.name')
50 + .joins(:album_artists)
51 + .where(album_artists: { album_id: album.id, primary: true })
52 + .first
53 +
54 + @primary_artists.push(artist.name) if artist
55 + end
56 +
57 + description = ActionController::Base.helpers.strip_tags(@artist.description.truncate(400))
58 + @meta_title = @page_title
59 + @meta_description = description.gsub('\\n', ' ').gsub(/\r\n?/, ' ')
60 + @meta_url = request.url
61 +
62 + @artist.increment!(:views_count) unless browser.bot?
63 + end
64 +
65 + def bridge
66 + artist_id = params[:slug].split('-')[-1]
67 + @artist = ArtistProvider
68 + .select(:id, :name, :provider_type, :user_id, :provider_id)
69 + .find_by(id: artist_id)
70 +
71 + redirect_to artist_path and return if @artist.nil?
72 +
73 + @page_title = @artist.name
74 + end
75 +
76 + def add
77 + if request.post?
78 + artist = Artist.new(artist_params)
79 + unless artist.save
80 + flash[:warning] = "We need at the artist's name and description."
81 + redirect_to artist_add_path
82 + return
83 + end
84 + redirect_to artist_detail_path(artist.slug)
85 + end
86 +
87 + @artist = Artist.new(name: params[:name], description: params[:description])
88 + @user = user_signed_in? ? current_user : User.find_by(username: 'nowhereman')
89 + @page_title = 'Add artist'
90 +
91 + @meta_title = @page_title
92 + @meta_description = @page_title
93 + end
94 +
95 + def vote
96 + return redirect_to login_path unless user_signed_in?
97 +
98 + vote = ArtistVote.new(artist_id: params[:id], user_id: current_user.id)
99 + vote.save
100 +
101 + redirect_back(fallback_location: root_path)
102 + end
103 +
104 + def unvote
105 + return redirect_to login_path unless user_signed_in?
106 +
107 + vote = ArtistVote.where(artist_id: params[:id], user_id: current_user.id)
108 + vote.destroy_all
109 +
110 + redirect_back(fallback_location: root_path)
111 + end
112 +
113 + private
114 +
115 + def artist_params
116 + params
117 + .require(:artist)
118 + .permit(:name, :description, :user_id)
119 + end
120 +end
backend/musik88-web/app/controllers/audio_files_controller.rb new
+101
@@ -0,0 +1,101 @@
1 +class AudioFilesController < ApplicationController
2 + def index
3 + @page_title = 'Audio Files'
4 + @audio_files = AudioFile.all.order(created_at: :desc)
5 +
6 + @users = User.left_joins(:audio_files)
7 + .group(:id)
8 + .order('COUNT(audio_files.id) DESC')
9 + .limit(10)
10 +
11 + @meta_title = @page_title
12 + @meta_url = request.url
13 + end
14 +
15 + def guitar_backing_track
16 + id = params[:slug].split('-')[-1]
17 +
18 + @audio_file = AudioFile.find_by(id: id)
19 +
20 + redirect_to root_path and return unless @audio_file.present?
21 +
22 + if request.post? && !user_signed_in?
23 + flash[:warning] = 'I told you'
24 + redirect_to login_path(redirect_to: guitar_backing_track_path) and return
25 + end
26 +
27 + if request.post? && user_signed_in?
28 + GuitarBackingTrackJob.perform_later(@audio_file.id)
29 + flash[:warning] = 'Be patient.'
30 + redirect_to guitar_backing_track_path and return
31 + end
32 +
33 + @page_title = @audio_file.guitar_backing_track_title
34 + @users = User.left_joins(:audio_files)
35 + .group(:id)
36 + .order('COUNT(audio_files.id) DESC')
37 + .limit(10)
38 +
39 + @guitar_backing_track = GuitarBackingTrackFile.find_by(audio_file_id: @audio_file.id)
40 + @meta_title = @page_title
41 + @meta_url = request.url
42 + end
43 +
44 + def bass_backing_track
45 + id = params[:slug].split('-')[-1]
46 +
47 + @audio_file = AudioFile.find_by(id: id)
48 +
49 + redirect_to root_path and return unless @audio_file.present?
50 +
51 + if request.post? && !user_signed_in?
52 + flash[:warning] = 'I told you'
53 + redirect_to login_path(redirect_to: bass_backing_track_path) and return
54 + end
55 +
56 + if request.post? && user_signed_in?
57 + BassBackingTrackJob.perform_later(@audio_file.id)
58 + flash[:warning] = 'Be patient.'
59 + redirect_to bass_backing_track_path and return
60 + end
61 +
62 + @page_title = @audio_file.bass_backing_track_title
63 + @users = User.left_joins(:audio_files)
64 + .group(:id)
65 + .order('COUNT(audio_files.id) DESC')
66 + .limit(10)
67 +
68 + @bass_backing_track = @audio_file.bass_backing_track_file
69 + @meta_title = @page_title
70 + @meta_url = request.url
71 + end
72 +
73 + def drum_backing_track
74 + id = params[:slug].split('-')[-1]
75 +
76 + @audio_file = AudioFile.find_by(id: id)
77 +
78 + redirect_to root_path and return unless @audio_file.present?
79 +
80 + if request.post? && !user_signed_in?
81 + flash[:warning] = 'I told you'
82 + redirect_to login_path(redirect_to: drum_backing_track_path) and return
83 + end
84 +
85 + if request.post? && user_signed_in?
86 + DrumBackingTrackJob.perform_later(@audio_file.id)
87 + flash[:warning] = 'Be patient.'
88 + redirect_to drum_backing_track_path and return
89 + end
90 +
91 + @page_title = @audio_file.drum_backing_track_title
92 + @users = User.left_joins(:audio_files)
93 + .group(:id)
94 + .order('COUNT(audio_files.id) DESC')
95 + .limit(10)
96 +
97 + @drum_backing_track = @audio_file.drum_backing_track
98 + @meta_title = @page_title
99 + @meta_url = request.url
100 + end
101 +end
backend/musik88-web/app/controllers/chords_controller.rb new
+275
@@ -0,0 +1,275 @@
1 +class ChordsController < ApplicationController
2 + def index
3 + meta = Meta
4 + .select(:title, :description)
5 + .where(home: 'guitar_chords', locale: [I18n.locale, I18n.default_locale])
6 + .last
7 +
8 + @meta_title = meta.title if meta
9 + @meta_description = meta.description if meta
10 +
11 + @page = Page
12 + .select(:page_content, :title)
13 + .where(page_slug: 'guitar_chords', locale: [I18n.locale, I18n.default_locale])
14 + .last
15 +
16 + @chords = Chord
17 + .select(:id, :views_count, :song_provider_id, :updated_at, :title)
18 + .order(updated_at: :desc)
19 + .paginate(page: params[:page], per_page: 10)
20 +
21 + @trending_chords = Chord
22 + .select(:id, :views_count, :song_provider_id, :updated_at, :title)
23 + .order(views_count: :desc)
24 + .limit(10)
25 + end
26 +
27 + def guitar_shapes
28 + @meta_title = 'Koleksi chord gitar lagu artis-artis terbaru, artis pop, informasi artis terkini | Lyrics88.Com'
29 + @meta_description = 'Koleksi chord gitar lagu artis-artis terbaru, artis pop, informasi artis terkini | Lyrics88.Com'
30 +
31 + @page = Page
32 + .select(:page_content, :title)
33 + .find_by page_slug: 'guitar_chords_shapes'
34 +
35 + @chords = ChordShape
36 + .select(:id, :name)
37 + .all
38 + end
39 +
40 + def guitar_shape
41 + @meta_title = 'Koleksi chord gitar lagu artis-artis terbaru, artis pop, informasi artis terkini | Lyrics88.Com'
42 + @meta_description = 'Koleksi chord gitar lagu artis-artis terbaru, artis pop, informasi artis terkini | Lyrics88.Com'
43 +
44 + @chords = ChordShape
45 + .select(:id, :name)
46 + .all
47 + id = params[:slug].split('-')[-1]
48 + @chord = ChordShape.find_by id: id
49 + end
50 +
51 + def chord
52 + id = params[:slug].split('-')[-1]
53 + @chord = Chord.find_by(id: id)
54 +
55 + if @chord.nil?
56 + render_chord_nil
57 + meta
58 + else
59 + render_chord
60 + meta
61 + end
62 + end
63 +
64 + def history
65 + id = params[:slug].split('-')[-1]
66 + @chord = Chord.find_by(id: id)
67 +
68 + return redirect_to guitar_chords_path if @chord.nil?
69 +
70 + @chord_histories = ChordHistory.where(chord_id: id).order(updated_at: :desc)
71 +
72 + @song = @chord.song_provider
73 + @page_title = if @song.nil?
74 + @chord.title
75 + else
76 + t(
77 + 'song_chord_page_title',
78 + song_name: @song.name
79 + )
80 + end
81 +
82 + @chords = Chord.where(song_id: @song.id) unless @song.nil?
83 + @chord.increment!(:views_count) unless browser.bot?
84 +
85 + @author = @chord.user.nil? ? 'nowhereman' : @chord.user.username_or_id
86 +
87 + @meta_title = @page_title
88 + @meta_description = @page_title
89 + end
90 +
91 + def add
92 + @chord = Chord.new
93 + @user = current_user
94 + @page_title = t('add_chord_title')
95 + @meta_title = @page_title
96 + @meta_description = @page_title
97 + end
98 +
99 + def add_post
100 + @chord = Chord.new(chord_params)
101 +
102 + re_render_add && return unless @chord.save
103 +
104 + add_chord_history
105 + flash[:success] = t('add_chord_validation_success')
106 + redirect_to guitar_chord_detail_path(@chord.slug)
107 + end
108 +
109 + def re_render_add
110 + @user = current_user
111 + @page_title = t('add_chord_title')
112 + @meta_title = @page_title
113 + @meta_description = @page_title
114 + @chord_artists = chord_artists_link
115 +
116 + flash[:warning] = t('add_chord_validation')
117 + render :add
118 + end
119 +
120 + def chord_artists_link
121 + @chord.song
122 + &.artists
123 + &.map { |a| view_context.link_to(a.name, artist_detail_path(a.slug)) }
124 + &.join(' ')
125 + &.html_safe
126 + end
127 +
128 + def song_search
129 + render json: Song
130 + .where('lower(name) LIKE ?', "%#{params[:s].downcase}%")
131 + .as_json(only: %i[id name])
132 + end
133 +
134 + def review
135 + id = params[:slug].split('-')[-1]
136 + @chord = Chord.find_by(id: id)
137 +
138 + redirect_to guitar_chords_path and return if @chord.nil?
139 +
140 + unless @chord.pending?
141 + flash[:warning] = 'Already approved.'
142 + redirect_to guitar_chord_detail_path and return
143 + end
144 +
145 + if request.patch?
146 + unless user_signed_in?
147 + flash[:warning] = 'Please login to review.'
148 + redirect_to guitar_chord_detail_path and return
149 + end
150 +
151 + unless current_user.reviewer?
152 + flash[:warning] = 'You have to have 10 €BACH to review.'
153 + redirect_to guitar_chord_review_path
154 + return
155 + end
156 +
157 + @chord.update_attribute(:status, :approved)
158 +
159 + chord_history = ChordHistory.new(
160 + chord_id: @chord.id,
161 + user_id: current_user.id,
162 + name: 'Approved'
163 + )
164 + unless chord_history.save
165 + # Revert
166 + @chord.update_attribute(:status, :pending)
167 + flash[:warning] = 'Unknown error'
168 + redirect_to guitar_chord_review_path
169 + return
170 + end
171 +
172 + flash[:success] = 'Approved!'
173 + redirect_to guitar_chord_detail_path(@chord.slug)
174 + return
175 + end
176 +
177 + @song = @chord.song_provider
178 + @page_title = if @song.nil?
179 + @chord.title
180 + else
181 + t(
182 + 'song_chord_page_title',
183 + song_name: @song.name
184 + )
185 + end
186 +
187 + @chords = Chord.where(song_id: @song.id) unless @song.nil?
188 + @chord.increment!(:views_count) unless browser.bot?
189 +
190 + @author = @chord.user.nil? ? 'nowhereman' : @chord.user.username_or_id
191 +
192 + @meta_title = @page_title
193 + @meta_description = @page_title
194 + end
195 +
196 + def vote
197 + redirect_to_login && return unless user_signed_in?
198 +
199 + vote = ChordVote.new(chord_id: params[:id], user_id: current_user.id)
200 + vote.save
201 +
202 + redirect_back(fallback_location: root_path)
203 + end
204 +
205 + def unvote
206 + redirect_to_login && return unless user_signed_in?
207 +
208 + vote = ChordVote.where(chord_id: params[:id], user_id: current_user.id)
209 + vote.destroy_all
210 +
211 + redirect_back(fallback_location: root_path)
212 + end
213 +
214 + private
215 +
216 + def chord_params
217 + params
218 + .require(:chord)
219 + .permit(:title, :chord, :user_id, :song_id)
220 + end
221 +
222 + def redirect_to_login
223 + flash[:success] = t('need_to_logged_in')
224 + chord = Chord.find_by(id: params[:id])
225 + redirect_to login_path(redirect_to: guitar_chord_detail_path(chord.slug))
226 + end
227 +
228 + def add_chord_history
229 + chord_history = ChordHistory.new(
230 + chord_id: @chord.id,
231 + user_id: @chord.user_or_nowhereman.id,
232 + name: 'Submitted'
233 + )
234 + chord_history.save
235 + end
236 +
237 + def render_chord
238 + @song = @chord.song_provider
239 + @page_title = chord_page_title
240 + @chord_content = chord_content
241 + @chords = Chord.where(song_id: @song.id) unless @song.nil?
242 + @chord.increment!(:views_count) unless browser.bot?
243 + @author = @chord.user.nil? ? 'nowhereman' : @chord.user.username_or_id
244 + end
245 +
246 + def render_chord_nil
247 + @page_title = 'Chord is not found'
248 + end
249 +
250 + def chord_content
251 + if user_signed_in?
252 + @chord.chord
253 + else
254 + song_name = @song&.name || @chord.title
255 + t('join_our_chord_community', song_name: song_name)
256 + end
257 + end
258 +
259 + def chord_page_title
260 + if @song.nil?
261 + @chord.title
262 + else
263 + t(
264 + 'song_chord_page_title',
265 + song_name: @song.name
266 + # Should call Spotify API here to get the artist name
267 + )
268 + end
269 + end
270 +
271 + def meta
272 + @meta_title = @page_title
273 + @meta_description = @page_title
274 + end
275 +end
backend/musik88-web/app/controllers/comments_controller.rb new
+45
@@ -0,0 +1,45 @@
1 +class CommentsController < ApplicationController
2 + before_action :find_commentable
3 + before_action :auth_user
4 +
5 + def new
6 + @comment = Comment.new
7 + end
8 +
9 + def create
10 + @comment = @commentable.comments.new comment_params
11 +
12 + if @comment.save
13 + flash[:success] = 'Your comment was successfully posted!'
14 + else
15 + flash[:danger] = "Your comment wasn't posted! Comment cannot be empty."
16 + end
17 + redirect_to("#{URI(request.referer).path}#discussion")
18 + end
19 +
20 + private
21 +
22 + def comment_params
23 + params.require(:comment).permit(:body, :user_id)
24 + end
25 +
26 + def find_commentable
27 + @commentable = Comment.find_by_id(params[:comment_id]) if params[:comment_id]
28 + @commentable = Lyric.find_by_id(params[:lyric_id]) if params[:lyric_id]
29 + @commentable = Chord.find_by_id(params[:chord_id]) if params[:chord_id]
30 + @commentable = Artist.find_by_id(params[:artist_id]) if params[:artist_id]
31 + @commentable = Album.find_by_id(params[:album_id]) if params[:album_id]
32 + @commentable = Song.find_by_id(params[:song_id]) if params[:song_id]
33 + @commentable = Page.find_by_id(params[:page_id]) if params[:page_id]
34 + @commentable = AudioFile.find_by_id(params[:audio_file_id]) if params[:audio_file_id]
35 + if params[:javanese_gending_notation_id]
36 + @commentable = JavaneseGendingNotation.find_by_id(params[:javanese_gending_notation_id])
37 + end
38 + @commentable = JavaneseGending.find_by_id(params[:javanese_gending_id]) if params[:javanese_gending_id]
39 + @commentable = User.find_by_id(params[:user_id]) if params[:user_id]
40 + end
41 +
42 + def auth_user
43 + redirect_to login_path(redirect_to: "#{URI(request.referer).path}#discussion") unless user_signed_in?
44 + end
45 +end
backend/musik88-web/app/controllers/concerns/.keep
backend/musik88-web/app/controllers/confirmations_controller.rb new
+14
@@ -0,0 +1,14 @@
1 +class ConfirmationsController < Devise::ConfirmationsController
2 + layout 'new_confirmation', only: %i[new create]
3 + layout 'edit_confirmation', only: %i[edit update]
4 +
5 + protected
6 +
7 + def after_confirmation_path_for(_resource_name, _resource)
8 + login_path
9 + end
10 +
11 + def after_resending_confirmation_instructions_path_for(_resource_name)
12 + login_path
13 + end
14 +end
backend/musik88-web/app/controllers/errors_controller.rb new
+11
@@ -0,0 +1,11 @@
1 +class ErrorsController < ApplicationController
2 + def not_found
3 + @meta_title = 'We are lost'
4 + @meta_description = 'Sorry, we are lost.'
5 + render status: 404
6 + end
7 +
8 + def internal_server_error
9 + render status: 500
10 + end
11 +end
backend/musik88-web/app/controllers/javanese_gamelan_controller.rb new
+176
@@ -0,0 +1,176 @@
1 +class JavaneseGamelanController < ApplicationController
2 + def index
3 + meta_tags('javanese-gamelan')
4 + page('javanese-gamelan')
5 +
6 + @gendings = JavaneseGending
7 + .select(:id, :name, :views_count, :updated_at, :description)
8 + .order(created_at: :desc)
9 + .paginate(page: params[:page], per_page: 10)
10 +
11 + @trending_gendings = @gendings.order(views_count: :desc).limit(10)
12 + end
13 +
14 + def gending
15 + meta_tags('gending')
16 + page('gending')
17 + @gendings = JavaneseGending
18 + .select(:id, :name, :user_id, :views_count, :updated_at, :description)
19 + .order(created_at: :desc)
20 + .paginate(page: params[:page], per_page: 10)
21 + end
22 +
23 + def gending_detail
24 + id = params[:slug].split('-')[-1]
25 + @gending = JavaneseGending.find_by(id: id)
26 +
27 + return redirect_to javanese_gending_path unless @gending
28 +
29 + @author = @gending.user_or_nowhereman
30 + @page_title = t('gending_notation_page_title', title: @gending.name)
31 + @meta_title = @page_title
32 + @meta_description = t('gending_notation_meta_desciption', title: @gending.name)
33 +
34 + @gending.increment!(:views_count) unless browser.bot?
35 + end
36 +
37 + # The naming inconsistency between gamelan and gending is for the sake of SEO.
38 + def gamelan_notation
39 + meta_tags('javanese-gamelan-notation')
40 + page('javanese-gamelan-notation')
41 + @gending_notations = JavaneseGendingNotation
42 + .select(:id, :user_id, :name, :rich_format, :views_count, :updated_at)
43 + .order(created_at: :desc)
44 + .paginate(page: params[:page], per_page: 10)
45 + end
46 +
47 + # Detail notation
48 + def notation
49 + render_notation
50 + end
51 +
52 + def notation_new
53 + render_notation_new
54 + end
55 +
56 + def add_gending
57 + @javanese_gending = JavaneseGending.new
58 + meta_tags('javanese-gamelan-notation')
59 + end
60 +
61 + def add
62 + if request.post?
63 + @javanese_gending = JavaneseGending.new(javanese_gending_params)
64 + if @javanese_gending.save
65 + redirect_to javanese_gamelan_path
66 + else
67 + flash[:warning] = t('lyric_cannot_be_empty')
68 + redirect_to add_lyric_path(@lyric.song_id)
69 + end
70 +
71 + return
72 + end
73 +
74 + @javanese_gending = JavaneseGending.new
75 + meta_tags('javanese-gamelan-notation')
76 + end
77 +
78 + def gending_vote
79 + return redirect_to login_path unless user_signed_in?
80 +
81 + vote = GendingVote.new(javanese_gending_id: params[:id], user_id: current_user.id)
82 + vote.save
83 + redirect_back(fallback_location: root_path)
84 + end
85 +
86 + def gending_unvote
87 + return redirect_to login_path unless user_signed_in?
88 +
89 + vote = GendingVote.where(javanese_gending_id: params[:id], user_id: current_user.id)
90 + vote.destroy_all
91 + redirect_back(fallback_location: root_path)
92 + end
93 +
94 + def gending_notation_vote
95 + return redirect_to login_path unless user_signed_in?
96 +
97 + vote = GendingNotationVote.new(javanese_gending_notation_id: params[:id], user_id: current_user.id)
98 + vote.save
99 + redirect_back(fallback_location: root_path)
100 + end
101 +
102 + def gending_notation_unvote
103 + return redirect_to login_path unless user_signed_in?
104 +
105 + vote = GendingNotationVote.where(javanese_gending_notation_id: params[:id], user_id: current_user.id)
106 + vote.destroy_all
107 + redirect_back(fallback_location: root_path)
108 + end
109 +
110 + private
111 +
112 + def render_notation
113 + check_valid_slug
114 +
115 + @content_notation = content_notation
116 + notation_info
117 + end
118 +
119 + def render_notation_new
120 + check_valid_slug
121 + @content_notation = content_notation_new
122 + notation_info
123 + end
124 +
125 + def check_valid_slug
126 + id = params[:slug].split('-')[-1]
127 + @gending = JavaneseGendingNotation.find_by(id: id)
128 + return redirect_to javanese_gending_gamelan_notation_path if @gending.nil?
129 + end
130 +
131 + def content_notation
132 + if user_signed_in?
133 + @gending.notation
134 + else
135 + t('join_our_javanese_community', gending_name: @gending.name)
136 + end
137 + end
138 +
139 + def content_notation_new
140 + if user_signed_in?
141 + 'partial_new_notation'
142 + else
143 + t('join_our_javanese_community', gending_name: @gending.name)
144 + end
145 + end
146 +
147 + def notation_info
148 + @author = @gending.user_or_nowhereman
149 + @page_title = t('gending_notation_page_title', title: @gending.name)
150 + @meta_title = @page_title
151 + @meta_description = t('gending_notation_meta_desciption', title: @gending.name)
152 + @gending.increment!(:views_count) unless browser.bot?
153 + end
154 + def javanese_gending_params
155 + params
156 + .require(:javanese_gending)
157 + .permit(:name, :description, :notation)
158 + end
159 +
160 + def page(slug)
161 + @page = Page
162 + .select(:page_content, :title)
163 + .where(page_slug: slug, locale: [I18n.locale, I18n.default_locale])
164 + .last
165 + end
166 +
167 + def meta_tags(slug)
168 + meta = Meta
169 + .select(:title, :description)
170 + .where(home: slug, locale: [I18n.locale, I18n.default_locale])
171 + .last
172 +
173 + @meta_title = meta.title if meta
174 + @meta_description = meta.description if meta
175 + end
176 +end
backend/musik88-web/app/controllers/lyrics_controller.rb new
+82
@@ -0,0 +1,82 @@
1 +class LyricsController < ApplicationController
2 + def index
3 + meta = Meta
4 + .select(:title, :description)
5 + .where(home: 'song_lyrics', locale: [I18n.locale, I18n.default_locale])
6 + .last
7 +
8 + @meta_title = meta.title if meta
9 + @meta_description = meta.description if meta
10 + @meta_url = request.url
11 +
12 + @page = Page
13 + .select(:page_content, :title)
14 + .where(page_slug: 'song_lyrics', locale: [I18n.locale, I18n.default_locale])
15 + .last
16 +
17 + @lyrics = Lyric
18 + .select(:id, :views_count, :song_id)
19 + .paginate(page: params[:page], per_page: 100)
20 +
21 + @trending_lyrics = @lyrics
22 + .order(views_count: :desc)
23 + .limit(10)
24 + end
25 +
26 + def lyric
27 + lyric_id = params[:slug].split('-')[-1]
28 + @lyric = Lyric.find_by(id: lyric_id)
29 +
30 + redirect_to root_path and return unless @lyric.present?
31 +
32 + @lovers = @lyric.users
33 + @author = @lyric.user
34 + @song = @lyric.song
35 +
36 + unless user_signed_in?
37 + flash[:warning] = t(
38 + 'join_our_lyric_community',
39 + song_name: @song&.name,
40 + song_artist_name: @song.artists.first&.name || 'NaN'
41 + )
42 + redirect_to signup_path(redirect_to: request.path)
43 + end
44 +
45 + @page_title = t(
46 + 'song_lyric_page_title',
47 + song_name: @song&.name,
48 + song_artist_name: @song.artists.first&.name || 'NaN'
49 + )
50 +
51 + @lyric_shown = @lyric.lyric.gsub('\\n', '<br>')
52 + @author = @lyric.user
53 + @meta_description = "#{@page_title}. #{@lyric.lyric.gsub('\\n', ' ').gsub(/\r\n?/, '')[0..300].strip}"
54 +
55 + @lyric.increment!(:views_count) unless browser.bot?
56 +
57 + @meta_title = @page_title
58 + end
59 +
60 + def lyric_not_available
61 + if @lyric.nil?
62 + shown_text = t(
63 + 'song_lyric_unavailable_content',
64 + song_name: @song.name,
65 + song_artist_name: @song.album.artists[0].name,
66 + href: ActionController::Base.helpers.link_to(
67 + t('song_lyric_unavailable_content_add_lyric'),
68 + add_lyric_path(@song.id),
69 + class: 'bb-button bb-green bb-large'
70 + )
71 + )
72 + @lyric_shown = "<p class='lead'>#{shown_text}</p>"
73 + @meta_description = t(
74 + 'song_lyric_unavailable_meta_description',
75 + song_name: @song.name,
76 + song_artist_name: @song.album.artists[0].name,
77 + song_album_name: @song.album.name
78 + )
79 +
80 + end
81 + end
82 +end
backend/musik88-web/app/controllers/my_controller.rb new
+733
@@ -0,0 +1,733 @@
1 +class MyController < ApplicationController
2 + before_action :must_login
3 +
4 + def must_login
5 + unless user_signed_in?
6 + flash[:success] = t('need_to_logged_in_my_profile')
7 + redirect_to login_path
8 + end
9 +
10 + redirect_to current_user.splitfire_profile_link unless current_user.nil?
11 + end
12 +
13 + def index
14 + @user = current_user
15 +
16 + meta = Meta
17 + .select(:title, :description)
18 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
19 + .last
20 +
21 + @meta_title = meta.title if meta
22 + @meta_description = meta.description if meta
23 + end
24 +
25 + def edit
26 + @user = current_user
27 +
28 + meta = Meta
29 + .select(:title, :description)
30 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
31 + .last
32 +
33 + @meta_title = meta.title if meta
34 + @meta_description = meta.description if meta
35 + end
36 +
37 + def add_lyric
38 + if request.post?
39 + @lyric = Lyric.new(lyric_params)
40 + if @lyric.save
41 + redirect_to my_path
42 + else
43 + flash[:warning] = t('lyric_cannot_be_empty')
44 + redirect_to add_lyric_path(@lyric.song_id)
45 + end
46 +
47 + return
48 + end
49 +
50 + @song = Song.find_by id: params[:song_id]
51 + @lyric = Lyric.new(
52 + song_id: @song.id,
53 + user_id: current_user.id
54 + )
55 +
56 + @user = current_user
57 +
58 + meta = Meta
59 + .select(:title, :description)
60 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
61 + .last
62 +
63 + @meta_title = meta.title if meta
64 + @meta_description = meta.description if meta
65 + end
66 +
67 + def edit_lyric
68 + @lyric = Lyric.find_by(id: params[:id])
69 +
70 + if request.patch?
71 + if @lyric.update_attributes(lyric_params)
72 + redirect_to my_path
73 + else
74 + flash[:warning] = t('lyric_cannot_be_empty')
75 + redirect_to add_lyric_path(@lyric.song_id)
76 + end
77 +
78 + return
79 + end
80 +
81 + @song = @lyric.song
82 +
83 + @user = current_user
84 +
85 + meta = Meta
86 + .select(:title, :description)
87 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
88 + .last
89 +
90 + @meta_title = meta.title if meta
91 + @meta_description = meta.description if meta
92 + end
93 +
94 + def lyrics
95 + @lyrics = Lyric
96 + .select(:id, :views_count, :song_id, :user_id)
97 + .where(user_id: current_user.id, is_published: false)
98 + .all
99 +
100 + @user = current_user
101 +
102 + meta = Meta
103 + .select(:title, :description)
104 + .where(home: 'profile', locale: I18n.locale)
105 + .first
106 +
107 + @meta_title = meta.title if meta
108 + @meta_description = meta.description if meta
109 + end
110 +
111 + def files
112 + if request.post?
113 + splitfire_process
114 + return
115 + end
116 +
117 + if request.delete?
118 + audio_file = AudioFile.find_by(id: params[:id])
119 + audio_file.destroy
120 +
121 + flash[:success] = 'File has been successfully deleted.'
122 +
123 + respond_to do |format|
124 + format.html { redirect_to my_files_path }
125 + format.xml { head :ok }
126 + end
127 + end
128 +
129 + @audio_files = AudioFile
130 + .where(user_id: current_user.id)
131 + .all
132 + .order(created_at: :desc)
133 +
134 + @audio_file = AudioFile.new
135 +
136 + @user = current_user
137 +
138 + meta = Meta
139 + .select(:title, :description)
140 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
141 + .last
142 +
143 + @meta_title = meta.title if meta
144 + @meta_description = meta.description if meta
145 + end
146 +
147 + def file_detail
148 + @audio_file = AudioFile.find_by(
149 + id: params[:id],
150 + user_id: current_user.id
151 + )
152 +
153 + if @audio_file.nil?
154 + redirect_to my_files_path
155 + return
156 + end
157 +
158 + @user = current_user
159 +
160 + meta = Meta
161 + .select(:title, :description)
162 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
163 + .last
164 +
165 + @meta_title = meta.title if meta
166 + @meta_description = meta.description if meta
167 + end
168 +
169 + def admin_files
170 + @audio_files = AudioFile.all.order(created_at: :desc)
171 +
172 + meta = Meta
173 + .select(:title, :description)
174 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
175 + .last
176 +
177 + @meta_title = meta.title if meta
178 + @meta_description = meta.description if meta
179 + end
180 +
181 + def splitfire
182 + @user = current_user
183 + @audio_files = AudioFile
184 + .where(user_id: @user.id)
185 + .all
186 + .order(created_at: :desc)
187 +
188 + @splitfire_settings = UserSetting.find_by(user_id: @user.id)
189 + if @splitfire_settings.nil?
190 + @splitfire_settings = UserSetting.new(
191 + user_id: @user.id,
192 + model: :'2stems',
193 + frequency: :'11kHz'
194 + )
195 + @splitfire_settings.save
196 + end
197 +
198 + meta = Meta
199 + .select(:title, :description)
200 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
201 + .last
202 +
203 + @meta_title = meta.title if meta
204 + @meta_description = meta.description if meta
205 + end
206 +
207 + def splitfire_settings
208 + return unless request.patch?
209 +
210 + splitfire_setting = UserSetting.find_by(user_id: current_user.id)
211 + splitfire_setting.update(
212 + model: params[:splitfire_setting][:model],
213 + frequency: params[:splitfire_setting][:frequency]
214 + )
215 +
216 + flash[:success] = 'Splitfile settings have been updated.'
217 + respond_to do |format|
218 + format.html { redirect_to my_splitfire_path }
219 + format.xml { head :ok }
220 + end
221 + end
222 +
223 + def splitfire_detail
224 + @audio_file = AudioFile.find_by(
225 + id: params[:id],
226 + user_id: current_user.id
227 + )
228 +
229 + if @audio_file.nil?
230 + redirect_to my_splitfire_path
231 + return
232 + end
233 +
234 + @user = current_user
235 +
236 + meta = Meta
237 + .select(:title, :description)
238 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
239 + .last
240 +
241 + @meta_title = meta.title if meta
242 + @meta_description = meta.description if meta
243 + end
244 +
245 + def splitfire_delete
246 + splitfire_result = SplitfireResult.find_by(id: params[:id])
247 + audio_file_id = splitfire_result.audio_file_id
248 +
249 + splitfire_result.destroy
250 +
251 + # Check if no result for the given audio_file_id...
252 + splitfire_results = SplitfireResult.find_by(audio_file_id: audio_file_id)
253 + if splitfire_results.nil?
254 + # ... then set the AudioFile as :no_results.
255 + AudioFile
256 + .find_by(id: audio_file_id)
257 + .audio_file_split_status
258 + .no_results!
259 + end
260 +
261 + respond_to do |format|
262 + format.html { redirect_to my_splitfire_detail_path(audio_file_id) }
263 + format.xml { head :ok }
264 + end
265 + end
266 +
267 + def splitfire_cancel
268 + audio_file = AudioFile.find_by(id: params[:id])
269 + audio_file.audio_file_split_status.cancelled!
270 +
271 + respond_to do |format|
272 + format.html { redirect_to my_splitfire_path }
273 + format.xml { head :ok }
274 + end
275 + end
276 +
277 + def splitfire_start
278 + audio_file_id = params[:id]
279 +
280 + # Change the status here because we want to see the updated status right away.
281 + audio_file = AudioFile.find_by(id: audio_file_id)
282 + if audio_file.audio_file_split_status.nil?
283 + AudioFileSplitStatus.new(
284 + audio_file_id: audio_file_id,
285 + status: :processing,
286 + processing_progress: 5
287 + ).save
288 + elsif status = AudioFileSplitStatus.find_by(audio_file_id: audio_file_id)
289 + status.update(
290 + status: :processing,
291 + processing_progress: 5
292 + )
293 + end
294 +
295 + # Send to the ActiveJob
296 + SplitfireJob.perform_later(audio_file_id)
297 +
298 + flash[:success] = 'Sit down and relax. SplitFire is doing its job!'
299 + redirect_to my_splitfire_path
300 + end
301 +
302 + def bass64
303 + @audio_files = AudioFile
304 + .where(user_id: current_user.id)
305 + .all
306 + .order(created_at: :desc)
307 +
308 + @audio_file = AudioFile.new
309 +
310 + @user = current_user
311 + meta = Meta
312 + .select(:title, :description)
313 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
314 + .last
315 +
316 + @meta_title = meta.title if meta
317 + @meta_description = meta.description if meta
318 + end
319 +
320 + def bass64_start
321 + return unless request.post?
322 +
323 + audio_file_id = params[:id]
324 +
325 + # Change the status here because we want to see the updated status right away.
326 + audio_file = AudioFile.find_by(id: audio_file_id)
327 + if audio_file.audio_file_bass64_status.nil?
328 + AudioFileBass64Status.new(audio_file_id: audio_file_id, status: :processing).save
329 + elsif audio_file.audio_file_bass64_status.processing!
330 + end
331 +
332 + # Send to the ActiveJob
333 + Bass64Job.perform_later(audio_file_id)
334 +
335 + flash[:success] = 'Sit down and relax. Bass64 is doing its job!'
336 + redirect_to my_bass64_path
337 +
338 + nil
339 + end
340 +
341 + def bass64_cancel
342 + return unless request.patch?
343 +
344 + audio_file_id = params[:id]
345 + audio_file = AudioFile.find_by(id: audio_file_id)
346 + audio_file.audio_file_bass64_status.cancelled!
347 +
348 + respond_to do |format|
349 + format.html { redirect_to my_bass64_path }
350 + format.xml { head :ok }
351 + end
352 + end
353 +
354 + def bass64_detail
355 + @audio_file = AudioFile.find_by(
356 + id: params[:id],
357 + user_id: current_user.id
358 + )
359 +
360 + if @audio_file.nil?
361 + redirect_to my_bass64_path
362 + return
363 + end
364 +
365 + @user = current_user
366 +
367 + meta = Meta
368 + .select(:title, :description)
369 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
370 + .last
371 +
372 + @meta_title = meta.title if meta
373 + @meta_description = meta.description if meta
374 + end
375 +
376 + def bass64_delete
377 + audio_file = BassBackingTrack.find_by(id: params[:id])
378 + audio_file.destroy
379 +
380 + # Check if no result for the given audio_file_id...
381 + audio_file_results = BassBackingTrack.find_by(audio_file_id: audio_file.audio_file_id)
382 + if audio_file_results.nil?
383 + # ... then set the AudioFile as :no_results.
384 + split_audio_file = AudioFile.find_by(id: audio_file.audio_file_id)
385 + split_audio_file.audio_file_bass64_status.no_results!
386 + end
387 +
388 + respond_to do |format|
389 + format.html { redirect_to my_bass64_path }
390 + format.xml { head :ok }
391 + end
392 + end
393 +
394 + def karokowe
395 + @audio_files = current_user.audio_files.order(created_at: :desc)
396 + @user = current_user
397 +
398 + meta = Meta
399 + .select(:title, :description)
400 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
401 + .last
402 +
403 + @meta_title = meta.title if meta
404 + @meta_description = meta.description if meta
405 + end
406 +
407 + def karokowe_start
408 + return unless request.post?
409 +
410 + audio_file_id = params[:id]
411 +
412 + # Change the status here because we want to see the updated status right away.
413 + audio_file = AudioFile.find_by(id: audio_file_id)
414 + if audio_file.audio_file_karokowe_status.nil?
415 + AudioFileKarokoweStatus.new(audio_file_id: audio_file_id, status: :processing).save
416 + elsif audio_file.audio_file_karokowe_status.processing!
417 + end
418 +
419 + # Send to the ActiveJob
420 + KarokoweJob.perform_later(audio_file_id)
421 +
422 + flash[:success] = 'Sit down and relax. We are generating the karaoke file for you!'
423 + redirect_to my_karokowe_path
424 +
425 + nil
426 + end
427 +
428 + def karokowe_cancel
429 + return unless request.patch?
430 +
431 + audio_file_id = params[:id]
432 + audio_file = AudioFile.find_by(id: audio_file_id)
433 + audio_file.audio_file_karokowe_status.cancelled!
434 +
435 + respond_to do |format|
436 + format.html { redirect_to my_karokowe_path }
437 + format.xml { head :ok }
438 + end
439 + end
440 +
441 + def karokowe_detail
442 + @audio_file = AudioFile.find_by(
443 + id: params[:id],
444 + user_id: current_user.id
445 + )
446 +
447 + if @audio_file.nil?
448 + redirect_to my_karokowe_path
449 + return
450 + end
451 +
452 + @user = current_user
453 +
454 + meta = Meta
455 + .select(:title, :description)
456 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
457 + .last
458 +
459 + @meta_title = meta.title if meta
460 + @meta_description = meta.description if meta
461 + end
462 +
463 + def karokowe_delete
464 + audio_file = KaraokeFile.find_by(id: params[:id])
465 + audio_file.destroy
466 +
467 + # Check if no result for the given audio_file_id...
468 + audio_file_results = KaraokeFile.find_by(audio_file_id: audio_file.audio_file_id)
469 + if audio_file_results.nil?
470 + # ... then set the AudioFile as :no_results.
471 + split_audio_file = AudioFile.find_by(id: audio_file.audio_file_id)
472 + split_audio_file.audio_file_karokowe_status.no_results!
473 + end
474 +
475 + respond_to do |format|
476 + format.html { redirect_to my_karokowe_path }
477 + format.xml { head :ok }
478 + end
479 + end
480 +
481 + def bonzo
482 + @audio_files = AudioFile
483 + .where(user_id: current_user.id)
484 + .all
485 + .order(created_at: :desc)
486 +
487 + @audio_file = AudioFile.new
488 +
489 + @user = current_user
490 + meta = Meta
491 + .select(:title, :description)
492 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
493 + .last
494 +
495 + @meta_title = meta.title if meta
496 + @meta_description = meta.description if meta
497 + end
498 +
499 + def bonzo_start
500 + return unless request.post?
501 +
502 + audio_file_id = params[:id]
503 +
504 + # Change the status here because we want to see the updated status right away.
505 + audio_file = AudioFile.find_by(
506 + id: audio_file_id,
507 + user_id: current_user.id
508 + )
509 + if audio_file.audio_file_bonzo_status.nil?
510 + AudioFileBonzoStatus.new(
511 + audio_file_id: audio_file_id,
512 + status: :processing
513 + ).save
514 + elsif audio_file.audio_file_bonzo_status.processing!
515 + end
516 +
517 + # Send to the ActiveJob
518 + BonzoJob.perform_later(audio_file_id)
519 +
520 + flash[:success] = 'Sit down and relax. We are generating the drum backing track file for you!'
521 + redirect_to my_bonzo_path
522 +
523 + nil
524 + end
525 +
526 + def bonzo_detail
527 + @audio_file = AudioFile.find_by(
528 + id: params[:id],
529 + user_id: current_user.id
530 + )
531 +
532 + if @audio_file.nil?
533 + redirect_to my_bonzo_path
534 + return
535 + end
536 +
537 + @user = current_user
538 +
539 + meta = Meta
540 + .select(:title, :description)
541 + .where(home: 'profile', locale: [I18n.locale, I18n.default_locale])
542 + .last
543 +
544 + @meta_title = meta.title if meta
545 + @meta_description = meta.description if meta
546 + end
547 +
548 + def bonzo_delete
549 + audio_file = DrumBackingTrack.find_by(
550 + id: params[:id]
551 + )
552 + audio_file.destroy
553 +
554 + # Check if no result for the given audio_file_id...
555 + audio_file_results = DrumBackingTrack.find_by(audio_file_id: audio_file.audio_file_id)
556 + if audio_file_results.nil?
557 + # ... then set the AudioFile as :no_results.
558 + audio_file = AudioFile.find_by(id: audio_file.audio_file_id)
559 + audio_file.audio_file_bonzo_status.no_results!
560 + end
561 +
562 + respond_to do |format|
563 + format.html { redirect_to my_bonzo_path }
564 + format.xml { head :ok }
565 + end
566 + end
567 +
568 + private
569 +
570 + def lyric_params
571 + params
572 + .require(:lyric)
573 + .permit(:song_id, :lyric, :locale, :user_id)
574 + end
575 +
576 + def audio_file_params
577 + params
578 + .require(:audio_file)
579 + .permit(:user_id, :source_file, :youtube_video_url, :is_public)
580 + end
581 +
582 + def splitfire_process
583 + if request.post?
584 +
585 + # A series of validation...
586 +
587 + # ... both are empty
588 + if params[:audio_file][:source_file].nil? && params[:audio_file][:youtube_video_url].empty?
589 + flash[:danger] = "Jeez, it's 2021. Please use YouTube video link or use your own file."
590 + redirect_to my_files_path
591 + return
592 + end
593 +
594 + # ... both are not empty
595 + if !params[:audio_file][:source_file].nil? && !params[:audio_file][:youtube_video_url].empty?
596 + flash[:danger] = "OMG, don't be greedy. Please use YouTube video link OR use your own file."
597 + redirect_to my_files_path
598 + return
599 + end
600 +
601 + # ... YouTube link
602 + if !params[:audio_file][:youtube_video_url].empty? && !valid_youtube_link?(params[:audio_file][:youtube_video_url])
603 + flash[:danger] = "Jeez, you're so stubborn. Please use a valid YouTube link."
604 + redirect_to my_files_path
605 + return
606 + end
607 +
608 + # ... audio file
609 + if params[:audio_file][:youtube_video_url].empty? && params[:audio_file][:source_file].nil?
610 + flash[:danger] = "Really, what are you trying to do? Where's the file?"
611 + redirect_to my_files_path
612 + return
613 + end
614 +
615 + # ... happy path
616 +
617 + # ... but still paranoid
618 + if !params[:audio_file][:youtube_video_url].empty? && valid_youtube_link?(params[:audio_file][:youtube_video_url])
619 +
620 + youtube_video_url_and_id = get_clean_youtube_url_and_video_id(params[:audio_file][:youtube_video_url])
621 +
622 + if youtube_video_url_and_id.nil?
623 + flash[:danger] = 'The URL looks strange.'
624 + redirect_to my_files_path
625 + end
626 +
627 + length_string = `yt-dlp --get-duration #{youtube_video_url_and_id.first}`
628 + length_strings = length_string.split(':')
629 +
630 + if length_strings.count > 1 && length_strings.first.to_i >= 5
631 + flash[:danger] = 'Less than 5 minutes, please. Live is too short. This video is too long.'
632 + redirect_to my_files_path
633 + return
634 + end
635 +
636 + # TODO: Check if we've already have this video id
637 + unless AudioFile.find_by(youtube_video_id: youtube_video_url_and_id.last).nil?
638 + flash[:warning] = "We've done this. Give us new link."
639 + redirect_to my_files_path
640 + return
641 + end
642 +
643 + audio_file = AudioFile.new(audio_file_params)
644 + audio_file.assign_attributes(youtube_video_id: youtube_video_url_and_id.last)
645 +
646 + if audio_file.save
647 +
648 + AudioFileSplitStatus.new(
649 + audio_file_id: audio_file.id,
650 + status: :downloading,
651 + processing_progress: 3
652 + ).save
653 +
654 + YoutubeToAudioJob.perform_later(audio_file.id)
655 +
656 + flash[:success] = 'SplitFire is processing this YouTube video for you.'
657 +
658 + return redirect_to my_splitfire_path
659 + else
660 + flash[:success] = 'I dont know what is happening. Sorry.'
661 +
662 + return redirect_to my_files_path
663 + end
664 + end
665 +
666 + # ... audio file
667 + unless params[:audio_file][:source_file].nil?
668 +
669 + audio_file = AudioFile.new(audio_file_params)
670 +
671 + if audio_file.save
672 +
673 + AudioFileSplitStatus.new(
674 + audio_file_id: audio_file.id,
675 + status: :processing,
676 + processing_progress: 5
677 + ).save
678 +
679 + SplitfireDemoJob.perform_later(audio_file.id)
680 +
681 + flash[:success] = 'File has been successfully uploaded. SplitFire is doing its job.'
682 + redirect_to my_splitfire_path
683 + return
684 + else
685 + flash[:success] = 'I dont know what is happening. Sorry.'
686 + redirect_to my_files_path
687 + return
688 + end
689 + end
690 +
691 + end
692 +
693 + @audio_file = AudioFile.new
694 + @user = User.find_by_id(69) # Splitfire
695 + end
696 +
697 + def valid_youtube_link?(youtube_video_url)
698 + uri = URI.parse(youtube_video_url)
699 +
700 + # To make things simple, we only recognize youtube.com and youtu.be
701 + if !uri.host.nil? && (uri.host.include?('youtube.com') || uri.host.include?('youtu.be'))
702 + return youtube_video_url =~ %r{^https*://[www.]*youtu[.]{1}*be[.com]{4}*/[watch?v=]*([a-zA-Z0-9_-]*)}
703 + end
704 +
705 + false
706 + end
707 +
708 + # Return set
709 + def get_clean_youtube_url_and_video_id(youtube_video_url)
710 + uri = URI.parse(youtube_video_url)
711 +
712 + if uri.host.include?('youtube.com')
713 +
714 + split_query = uri.query.split('&')
715 +
716 + query_video_id = split_query.find { |item| item.include?('v=') }
717 +
718 + uri.query = query_video_id
719 + video_id = query_video_id.split('v=').last
720 + set = [uri.to_s, video_id]
721 + return set
722 +
723 + end
724 +
725 + if uri.host.include?('youtu.be')
726 + video_id = uri.to_s.split('/').last
727 + set = [uri.to_s, video_id]
728 + return set
729 + end
730 +
731 + nil
732 + end
733 +end
backend/musik88-web/app/controllers/pages_controller.rb new
+56
@@ -0,0 +1,56 @@
1 +class PagesController < ApplicationController
2 + def what_is_this
3 + meta_tags 'what_is_this'
4 + what_are_these
5 +
6 + # The page content itself
7 + @page = Page
8 + .select(:page_content, :title, :locale)
9 + .where(category: :about, page_slug: 'what-is-this', locale: [I18n.locale, I18n.default_locale])
10 + .last
11 + end
12 +
13 + def what_is_this_detail
14 + slug = params[:slug]
15 + @page = Page
16 + .select(:page_content, :title, :locale)
17 + .where(category: :what_is_this, page_slug: slug, locale: [I18n.locale, I18n.default_locale])
18 + .last
19 + if @page.nil?
20 + logger.info "Page not found: #{slug}"
21 + return redirect_to what_is_this_path
22 + end
23 +
24 + render_detail
25 + end
26 +
27 + private
28 +
29 + def what_are_these
30 + # List of what_is_this
31 + @what_are_these = Page
32 + .select(:page_content, :title, :page_slug)
33 + .where(category: :what_is_this, locale: [I18n.locale, I18n.default_locale])
34 + end
35 +
36 + def meta_tags(home)
37 + meta = Meta
38 + .select(:title, :description)
39 + .where(home: home, locale: [I18n.locale, I18n.default_locale])
40 + .last
41 + @meta_title = meta.title if meta
42 + @meta_description = meta.description if meta
43 + @meta_url = request.url
44 + end
45 +
46 + def render_detail
47 + meta_tags 'what_is_this_detail'
48 + what_are_these
49 +
50 + # Override meta title and meta description
51 + @meta_title = @page.title
52 + @meta_description = @page.page_content[0..100]
53 +
54 + render 'what_is_this'
55 + end
56 +end
backend/musik88-web/app/controllers/passwords_controller.rb new
+2
@@ -0,0 +1,2 @@
1 +class PasswordsController < Devise::PasswordsController
2 +end
\ No newline at end of file
backend/musik88-web/app/controllers/relationships_controller.rb new
+19
@@ -0,0 +1,19 @@
1 +class RelationshipsController < ApplicationController
2 + before_action :auth_user
3 +
4 + def auth_user
5 + redirect_to login_path(redirect_to: request.path) unless user_signed_in?
6 + end
7 +
8 + def create
9 + user = User.find(params[:followed_id])
10 + current_user.follow(user)
11 + redirect_to profile_path(user.username_or_id)
12 + end
13 +
14 + def destroy
15 + user = Relationship.find(params[:id]).followed
16 + current_user.unfollow(user)
17 + redirect_to profile_path(user.username_or_id)
18 + end
19 +end
backend/musik88-web/app/controllers/sessions_controller.rb new
+180
@@ -0,0 +1,180 @@
1 +# frozen_string_literal: true
2 +
3 +# Fix this later
4 +# rubocop:disable Metrics/ClassLength
5 +class SessionsController < ApplicationController
6 + def new
7 + redirect_to profile_path(current_user.username_or_id) if user_signed_in?
8 +
9 + session[:redirect_after_login_path] = params[:redirect_to] if params[:redirect_to].present?
10 + session[:omniauth_session_locale] = I18n.locale
11 +
12 + meta_tags('login')
13 + end
14 +
15 + def create
16 + @user = User.find_by(email: params[:session][:email].downcase)
17 + if @user&.valid_password?(params[:session][:password])
18 + perform_login_if_possible
19 + else
20 + flash.now[:danger] = t('invalid_login')
21 + render 'new'
22 + end
23 + end
24 +
25 + def create_pubkey
26 + pubkey = params[:pubkey]
27 +
28 + # Don't get fancy!
29 + pubkey_email = "#{pubkey}@musik88.com".downcase
30 +
31 + case params[:postAction]
32 + when 'createAccount'
33 + perform_create_account(pubkey_email, pubkey)
34 + perform_login_pubkey
35 + when 'checkPubkey'
36 + perform_check_pubkey(pubkey_email)
37 + end
38 + end
39 +
40 + def confirm_email
41 + return redirect_to login_path if request.get?
42 +
43 + @user = User.find_by_id(params[:id])
44 + return redirect_to login_path if @user.nil? || @user&.confirmed?
45 +
46 + @user.resend_confirmation_instructions
47 + # Render confirm_email.js.erb
48 + @resend_text = t('unconfirmed_email_resend_sent',
49 + email: @user.email,
50 + email_client_link: email_client_link)
51 + end
52 +
53 + def destroy
54 + sign_out if user_signed_in?
55 +
56 + redirect_after_logout_path = params[:redirect_to] ||= root_path
57 + redirect_to redirect_after_logout_path
58 + end
59 +
60 + private
61 +
62 + def perform_check_pubkey(pubkey)
63 + @user = User.find_by(email: pubkey)
64 + if @user.nil?
65 + render json: {
66 + shouldPromptMessageSign: true
67 + }
68 + else
69 + perform_login_pubkey
70 + end
71 + end
72 +
73 + def perform_create_account(pubkey_email, pubkey)
74 + # Create user and return next path
75 + # Generate name from 3 first and 3 last characters of pubkey
76 + name = "#{pubkey[0..2]}___#{pubkey[-3..]}"
77 + @user = User.new(email: pubkey_email, name: name, provider: 'solana', uid: pubkey)
78 + @user.skip_confirmation!
79 + @user.skip_password_validation = true
80 + @user.save
81 + save_or_update_provider(@user, pubkey)
82 + end
83 +
84 + def save_or_update_provider(user, pubkey)
85 + authorization = user.authorizations.find_by(provider: 'solana', uid: pubkey)
86 + authorization ||= Authorization.new(user_id: user.id, provider: 'solana', uid: pubkey)
87 + authorization.save
88 + logger.debug("Authorization: #{authorization}")
89 + end
90 +
91 + def perform_login_pubkey
92 + sign_in(:user, @user)
93 + redirect_to session_redirect
94 +
95 + session.delete(:redirect_after_login_path)
96 + end
97 +
98 + def session_redirect
99 + after_login_path = if current_user.need_onboarding?
100 + onboarding_path
101 + else
102 + session[:redirect_after_login_path]
103 + end
104 + after_login_path || profile_path(user.username_or_id)
105 + end
106 +
107 + def meta_tags(slug)
108 + meta = Meta
109 + .select(:title, :description)
110 + .where(home: slug, locale: [I18n.locale, I18n.default_locale])
111 + .last
112 +
113 + @meta_title = meta.title if meta
114 + @meta_description = meta.description if meta
115 + end
116 +
117 + def perform_login_if_possible
118 + return render_unconfirmed unless @user.confirmed?
119 +
120 + sign_in(:user, @user)
121 +
122 + create_wallet_if_needed
123 + sync_spotify_if_needed
124 +
125 + @user.remember_me! if params[:session][:remember_me] == '1'
126 +
127 + redirect_to session_redirect
128 +
129 + session.delete(:redirect_after_login_path)
130 + end
131 +
132 + def render_unconfirmed
133 + flash.now[:info] =
134 + t('unconfirmed_email',
135 + click_here_link: view_context.link_to(
136 + t('unconfirmed_email_click_here'),
137 + confirm_email_path(id: @user.id), method: :post, remote: true
138 + ),
139 + email_client_link: email_client_link)
140 + render 'new'
141 + end
142 +
143 + def email_client_link
144 + view_context.link_to(
145 + t('unconfirmed_email_email_app'),
146 + "https://#{@user.email_client}",
147 + target: '__blank'
148 + )
149 + end
150 +
151 + def create_wallet_if_needed
152 + create_wallet_solana
153 + create_wallet_evm
154 + end
155 +
156 + def create_wallet_solana
157 + solana_wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana])
158 + return unless solana_wallet.nil?
159 +
160 + Wallet.new(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana], status: :pending).save
161 + WalletSolanaJob.perform_later(@user.id)
162 + end
163 +
164 + def create_wallet_evm
165 + evm_wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm])
166 + return unless evm_wallet.nil?
167 +
168 + Wallet.new(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm], status: :pending).save
169 + WalletEvmJob.perform_later(@user.id)
170 + end
171 +
172 + def sync_spotify_if_needed
173 + return if current_user.need_onboarding?
174 +
175 + return unless @user.user_setting.sync_spotify?
176 +
177 + SpotifyOnboardingExportJob.perform_later(@user.id, 'short_term')
178 + SpotifySyncExportJob.perform_later(@user.id)
179 + end
180 +end
backend/musik88-web/app/controllers/songs_controller.rb new
+279
@@ -0,0 +1,279 @@
1 +class SongsController < ApplicationController
2 + def index
3 + meta_tags 'songs'
4 + @page = Page
5 + .select(:page_content, :title)
6 + .where(page_slug: 'songs', locale: [I18n.locale, I18n.default_locale])
7 + .last
8 +
9 + @songs = Song
10 + .select(:id, :name, :description, :updated_at, :album_id, :views_count)
11 + .paginate(page: params[:page], per_page: 10)
12 +
13 + @trending_songs = Song
14 + .select(:id, :name, :description, :album_id, :views_count, :updated_at)
15 + .order(views_count: :desc)
16 + .limit(10)
17 +
18 + @most_loved_songs = Song
19 + .joins(:users)
20 + .select(:id, :name, :description, :updated_at, :album_id, 'COUNT(users.id) as users_count')
21 + .group('songs.id')
22 + .order(users_count: :desc)
23 + .limit(10)
24 + end
25 +
26 + def detail
27 + unless valid_params?
28 + redirect_to root_path
29 + return
30 + end
31 +
32 + @lovers = @song.users
33 + @author = @song.user ||= User.find_by(username: 'nowhereman') # One must have nowhereman
34 +
35 + @page_title = t(
36 + 'song_detail_page_title',
37 + song_name: @song.name
38 + )
39 +
40 + @meta_title = t(
41 + 'song_detail_page_meta_title',
42 + song_name: @song.name,
43 + song_artist_name: @song.album.artists[0].name,
44 + song_album_name: @song.album.name
45 + )
46 +
47 + description = ActionController::Base.helpers.strip_tags(@song.description.truncate(400))
48 + @meta_description = description.gsub('\\n', ' ').gsub(/\r\n?/, ' ')
49 + @meta_url = request.url
50 + end
51 +
52 + def bridge
53 + meta_tags 'songs_bridge'
54 + return redirect_to song_path unless valid_bridge_params?
55 +
56 + @sf_link = "#{ENV['SF_HOST']}/split/#{@song.id}"
57 + @page_title = @song.name
58 + @meta_title = @song.name
59 + @meta_description = @song.description
60 + end
61 +
62 + def chords
63 + unless valid_params?
64 + redirect_to root_path
65 + return
66 + end
67 +
68 + @chords = Chord.where(song_id: @song.id).order(views_count: :desc)
69 + @lovers = @song.users
70 + @author = @song.user ||= User.find_by(username: 'nowhereman') # One must have nowhereman
71 +
72 + @page_title = t(
73 + 'song_chords_page_title',
74 + song_name: @song.name
75 + )
76 +
77 + @meta_title = @page_title
78 + description = ActionController::Base.helpers.strip_tags(@song.description.truncate(400))
79 + @meta_description = description.gsub('\\n', ' ').gsub(/\r\n?/, ' ')
80 + @meta_url = request.url
81 + end
82 +
83 + def audio
84 + unless valid_params?
85 + redirect_to root_path
86 + return
87 + end
88 +
89 + @audio_files = AudioFile.where(song_provider_id: @song.id)
90 + @karaoke_files = @audio_files.map(&:karaoke_file)
91 +
92 + @chords = Chord.where(song_id: @song.id).order(views_count: :desc)
93 + @lovers = @song.users
94 + @author = @song.user ||= User.find_by(username: 'nowhereman') # One must have nowhereman
95 +
96 + @page_title = t(
97 + 'song_audio_page_title',
98 + song_name: @song.name
99 + )
100 +
101 + @meta_title = @page_title
102 + description = ActionController::Base.helpers.strip_tags(@song.description.truncate(400))
103 + @meta_description = description.gsub('\\n', ' ').gsub(/\r\n?/, ' ')
104 + @meta_url = request.url
105 + end
106 +
107 + def lyrics
108 + unless valid_params?
109 + redirect_to root_path
110 + return
111 + end
112 +
113 + @lyrics = Lyric.where(song_id: @song.id).order(views_count: :desc)
114 +
115 + @page_title = t(
116 + 'song_lyrics_page_title',
117 + song_name: @song.name
118 + )
119 +
120 + @meta_title = @page_title
121 + description = ActionController::Base.helpers.strip_tags(@song.description.truncate(400))
122 + @meta_description = description.gsub('\\n', ' ').gsub(/\r\n?/, ' ')
123 + @meta_url = request.url
124 + end
125 +
126 + def karaoke
127 + # Simply redirect to splitfire
128 + redirect_to ENV['SF_HOST']
129 + end
130 +
131 + def most_loved
132 + @page_title = t('breadcrumb_most_loved_songs')
133 + @most_loved_songs = Song
134 + .joins(:users)
135 + .select(:id, :name, :description, :album_id, 'COUNT(users.id) as users_count')
136 + .group('songs.id')
137 + .order(users_count: :desc)
138 +
139 + @meta_title = @page_title
140 + @meta_description = @page_title
141 + @meta_url = request.url
142 + end
143 +
144 + def loved
145 + redirect_to_login && return unless user_signed_in?
146 +
147 + plea = SongChordPlea.new(song_id: params[:id], user_id: current_user.id)
148 + plea.save
149 +
150 + redirect_back(fallback_location: root_path)
151 + end
152 +
153 + def unloved
154 + redirect_to_login && return unless user_signed_in?
155 +
156 + plea = SongChordPlea.where(song_id: params[:id], user_id: current_user.id)
157 + plea.destroy_all
158 +
159 + redirect_back(fallback_location: root_path)
160 + end
161 +
162 + def bass_backing_track
163 + # Get the song_id
164 + id = params[:slug].split('-')[-1]
165 + @song = Song.find_by id: id
166 + @page_title = t(
167 + 'song_karaoke_page_title',
168 + song_name: @song.name,
169 + song_artist_name: @song.album.artists[0].name,
170 + song_album_name: @song.album.name
171 + )
172 +
173 + @lovers = nil
174 + @author = nil
175 + @karaoke_file_shown = nil
176 +
177 + if @song.audio_files.count == 0
178 + @shown_text = t(
179 + 'song_karaoke_unavailable_content',
180 + song_name: @song.name,
181 + song_artist_name: @song.album.artists[0].name
182 + )
183 +
184 + @meta_description = t(
185 + 'song_karaoke_unavailable_meta_description',
186 + song_name: @song.name,
187 + song_artist_name: @song.album.artists[0].name,
188 + song_album_name: @song.album.name
189 + )
190 +
191 + else
192 + @meta_description = "#{@page_title}. #{@song.album.name}"
193 + end
194 +
195 + @meta_title = @page_title
196 + end
197 +
198 + def bass_tab
199 + # Simply redirect to splitfire
200 + redirect_to ENV['SF_HOST']
201 + end
202 +
203 + def drum_backing_track
204 + # Get the song_id
205 + id = params[:slug].split('-')[-1]
206 + @song = Song.find_by id: id
207 + @page_title = t(
208 + 'song_karaoke_page_title',
209 + song_name: @song.name,
210 + song_artist_name: @song.album.artists[0].name,
211 + song_album_name: @song.album.name
212 + )
213 +
214 + @lovers = nil
215 + @author = nil
216 + @karaoke_file_shown = nil
217 +
218 + if @song.audio_files.count == 0
219 + @shown_text = t(
220 + 'song_karaoke_unavailable_content',
221 + song_name: @song.name,
222 + song_artist_name: @song.album.artists[0].name
223 + )
224 +
225 + @meta_description = t(
226 + 'song_karaoke_unavailable_meta_description',
227 + song_name: @song.name,
228 + song_artist_name: @song.album.artists[0].name,
229 + song_album_name: @song.album.name
230 + )
231 +
232 + else
233 + @meta_description = "#{@page_title}. #{@song.album.name}"
234 + end
235 +
236 + @meta_title = @page_title
237 + end
238 +
239 + private
240 +
241 + def valid_params?
242 + id = params[:slug].split('-')[-1]
243 + @song = Song.find_by(id: id)
244 + if @song.nil?
245 + flash[:danger] = t('not_found')
246 + return false
247 + end
248 + @lovers = @song.users
249 + @author = @song.user ||= User.find_by(username: 'nowhereman') # One must have nowhereman
250 + true
251 + end
252 +
253 + def valid_bridge_params?
254 + id = params[:slug].split('-')[-1]
255 + @song = SongProvider.find_by(id: id)
256 + if @song.nil?
257 + flash[:danger] = t('not_found')
258 + return false
259 + end
260 + @song_in_albums = []
261 + true
262 + end
263 +
264 + def redirect_to_login
265 + flash[:success] = t('need_to_logged_in')
266 + song = Song.find_by(id: params[:id])
267 + redirect_to login_path(redirect_to: song_detail_path(song.slug))
268 + end
269 +
270 + def meta_tags(slug)
271 + meta = Meta
272 + .select(:title, :description)
273 + .where(home: slug, locale: [I18n.locale, I18n.default_locale])
274 + .last
275 +
276 + @meta_title = meta.title if meta
277 + @meta_description = meta.description if meta
278 + end
279 +end
backend/musik88-web/app/controllers/users/omniauth_callbacks_controller.rb new
+160
@@ -0,0 +1,160 @@
1 +# frozen_string_literal: true
2 +
3 +# Fix this later
4 +# rubocop:disable Metrics/ClassLength
5 +module Users
6 + class OmniauthCallbacksController < Devise::OmniauthCallbacksController
7 + # Apple call the callback as a POST request. Just Apple being Apple.
8 + skip_before_action :verify_authenticity_token, only: [:apple]
9 + before_action :set_locale
10 +
11 + def facebook
12 + # TODO: Onboarding from Facebook
13 + @user = User.from_omniauth(request.env['omniauth.auth'])
14 + try_login
15 + end
16 +
17 + def spotify
18 + auth = request.env['omniauth.auth']
19 + # Check if this is a pubkey auth onboarding by checking onboarding_provider session
20 + return web3_setup_onboarding_provider(auth) if session[:onboarding_provider] == 'solana'
21 +
22 + @user = User.find_by(email: auth.info.email)
23 + try_login_or_signup(auth)
24 + end
25 +
26 + def google_oauth2
27 + auth = request.env['omniauth.auth']
28 + # Check if this is a pubkey auth onboarding by checking onboarding_provider session
29 + return web3_setup_onboarding_provider(auth) if session[:onboarding_provider] == 'solana'
30 +
31 + @user = User.find_by(email: auth.info.email)
32 + try_login_or_signup(auth)
33 + end
34 +
35 + def apple
36 + # TODO: Onboarding from Apple
37 + @user = User.from_omniauth(request.env['omniauth.auth'])
38 + try_login
39 + end
40 +
41 + def failure
42 + redirect_to root_path
43 + end
44 +
45 + # Setup locale from the previous omniauth call
46 + def set_locale
47 + I18n.locale = session[:omniauth_session_locale]
48 + end
49 +
50 + private
51 +
52 + def web3_setup_onboarding_provider(auth)
53 + # We should have a current_user here that is pubkey authenticated
54 + return redirect_to login_path if current_user.nil?
55 +
56 + logger.info "web3_setup_spotify_provider: #{current_user}"
57 + @user = current_user
58 + User.from_omniauth_web3(auth, current_user.email)
59 + onboarding(nil)
60 + end
61 +
62 + def try_login_or_signup(auth)
63 + return signup(auth) if @user.nil?
64 +
65 + User.from_omniauth(auth)
66 + try_login
67 + end
68 +
69 + def try_login
70 + return handle_unconfirmed_email unless @user.confirmed?
71 +
72 + return redirect_to login_path unless @user.persisted?
73 +
74 + ab_finished(:only_spotify_youtube, reset: true)
75 +
76 + sign_in @user
77 + create_wallet_if_needed
78 + sync_spotify_if_needed
79 +
80 + flash[:success] = t('welcome_user', user_name: @user.name) unless current_user.need_onboarding?
81 + redirect_to session_redirect
82 + end
83 +
84 + def handle_unconfirmed_email
85 + flash[:info] =
86 + t('unconfirmed_email',
87 + click_here_link: view_context.link_to(
88 + t('unconfirmed_email_click_here'),
89 + confirm_email_path(id: @user.id), method: :post, remote: true
90 + ),
91 + email_client_link: email_client_link)
92 + redirect_to login_path
93 + end
94 +
95 + def email_client_link
96 + view_context.link_to(
97 + t('unconfirmed_email_email_app'),
98 + "https://#{@user.email_client}",
99 + target: '__blank'
100 + )
101 + end
102 +
103 + def create_wallet_if_needed
104 + create_wallet_solana
105 + create_wallet_evm
106 + end
107 +
108 + def create_wallet_solana
109 + solana_wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana])
110 + return unless solana_wallet.nil?
111 +
112 + Wallet.new(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana], status: :pending).save
113 + WalletSolanaJob.perform_later(@user.id)
114 + end
115 +
116 + def create_wallet_evm
117 + evm_wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm])
118 + return unless evm_wallet.nil?
119 +
120 + Wallet.new(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm], status: :pending).save
121 + WalletEvmJob.perform_later(@user.id)
122 + end
123 +
124 + def sync_spotify_if_needed
125 + return if current_user.need_onboarding?
126 +
127 + return unless @user.user_setting.sync_spotify?
128 +
129 + SpotifyOnboardingExportJob.perform_later(@user.id, 'short_term')
130 + SpotifySyncExportJob.perform_later(@user.id)
131 + end
132 +
133 + def session_redirect
134 + # Override redirection if needed
135 + redirect_after_login_path = if current_user.need_onboarding?
136 + onboarding_path
137 + else
138 + session[:redirect_after_login_path]
139 + end
140 + redirect_after_login_path || profile_path(current_user.username_or_id)
141 + end
142 +
143 + def delete_session
144 + session.delete(:redirect_after_login_path)
145 + session.delete(:omniauth_session_locale)
146 + end
147 +
148 + def signup(auth)
149 + redirect_to signup_path(provider: auth.provider, email: auth.info.email, name: auth.info.name,
150 + uid: auth.uid, token: auth.credentials.token)
151 + end
152 +
153 + def onboarding(auth)
154 + return redirect_to onboarding_path if auth.nil?
155 +
156 + redirect_to onboarding_path(provider: auth.provider, email: auth.info.email, name: auth.info.name,
157 + uid: auth.uid, token: auth.credentials.token)
158 + end
159 + end
160 +end
backend/musik88-web/app/controllers/users_controller.rb new
+374
@@ -0,0 +1,374 @@
1 +# frozen_string_literal: true
2 +
3 +# Fix this later
4 +# rubocop:disable Metrics/ClassLength
5 +class UsersController < ApplicationController
6 + def initialize
7 + @youtube_likes_export_count = 0
8 + super
9 + end
10 +
11 + def index
12 + redirect_to community_path
13 + end
14 +
15 + def youtube_likes_export_max_iteration
16 + # If development, set to 2, else 10.
17 + Rails.env.development? ? 2 : 10
18 + end
19 +
20 + def community
21 + @users = User.all.paginate(page: params[:page], per_page: 12)
22 + @page_title = t('our_community')
23 + meta_tags('community')
24 + end
25 +
26 + def show_reputation
27 + verify_username_or_id
28 +
29 + redirect_to users_path unless @user
30 + end
31 +
32 + def friends
33 + verify_username_or_id
34 + return redirect_to users_path unless @user
35 + end
36 +
37 + def followers
38 + redirect_to signup_path if params[:id] == 'sign_up'
39 + redirect_to login_path if params[:id] == 'sign_in'
40 +
41 + verify_username_or_id
42 +
43 + return redirect_to users_path unless @user
44 +
45 + if !@user.nil? && @user.username == 'splitfire'
46 + # If it's splitfire page, set cookies to connect to actioncable.
47 + cookies.permanent.signed[:splitfire_user_id] = @user.id
48 + end
49 +
50 + meta_tags('profile')
51 + end
52 +
53 + def following
54 + redirect_to signup_path if params[:id] == 'sign_up'
55 + redirect_to login_path if params[:id] == 'sign_in'
56 +
57 + verify_username_or_id
58 +
59 + return redirect_to users_path unless @user
60 +
61 + if !@user.nil? && @user.username == 'splitfire'
62 + # If it's splitfire page, set cookies to connect to actioncable.
63 + cookies.permanent.signed[:splitfire_user_id] = @user.id
64 + end
65 +
66 + meta_tags('profile')
67 + end
68 +
69 + def show
70 + redirect_to signup_path if params[:id] == 'sign_up'
71 + redirect_to login_path if params[:id] == 'sign_in'
72 +
73 + verify_username_or_id
74 + return redirect_to users_path unless @user
75 +
76 + @show_comment_form = @user.id != current_user&.id
77 + meta_tags('profile')
78 + end
79 +
80 + def new
81 + redirect_to profile_path(current_user.username_or_id) if user_signed_in?
82 +
83 + meta_tags('signup')
84 + @user = User.new(name: params[:name], email: params[:email])
85 + @prefilled = params[:email].nil? == false
86 + @provider = params[:provider]
87 + @provider_name = @provider == 'spotify' ? 'Spotify' : 'YouTube'
88 +
89 + # Remember locale for omniauth
90 + session[:omniauth_session_locale] = I18n.locale
91 + end
92 +
93 + def onboarding
94 + protect_onboarding_paths
95 +
96 + session[:omniauth_session_locale] = I18n.locale
97 + @user = current_user
98 + session[:onboarding_provider] = @user.provider
99 + meta_tags('onboarding')
100 + end
101 +
102 + def onboarding_action
103 + protect_onboarding_paths
104 + compute_next_step
105 + render json: { code: 200, next_step: @next_step }
106 + end
107 +
108 + def username_check
109 + # Find username
110 + username = params[:u]
111 +
112 + validate_username username
113 +
114 + render json: {
115 + 'valid': @is_valid,
116 + 'message': @message
117 + }
118 + end
119 +
120 + def username
121 + @username = params[:user][:username].downcase
122 + current_user.update(username: @username)
123 + # See username.js.erb
124 + end
125 +
126 + def onboarding_export_spotify
127 + return unless user_signed_in?
128 +
129 + SpotifyOnboardingExportJob.perform_now(current_user.id, 'long_term')
130 +
131 + render json: {
132 + status: 200
133 + }
134 + end
135 +
136 + def onboarding_export_youtube
137 + return unless user_signed_in?
138 +
139 + onboarding_export_youtube_likes
140 + onboarding_export_youtube_playlists
141 +
142 + render json: { status: 200, message: 'Exported.' }
143 + end
144 +
145 + def onboarding_export_youtube_likes(page_token = nil)
146 + @youtube_likes_export_count += 1
147 + client = YoutubeClient.new(current_user.google)
148 + response = client.user_likes_videos(page_token)
149 + response_json = JSON.parse(response.body)
150 +
151 + export_youtube_videos(response_json['items'], current_user.id)
152 +
153 + if response_json['nextPageToken'].present? && @youtube_likes_export_count < youtube_likes_export_max_iteration
154 + onboarding_export_youtube_likes(response_json['nextPageToken'])
155 + end
156 + end
157 +
158 + def onboarding_export_youtube_playlists
159 + client = YoutubeClient.new(current_user.google)
160 + client.export_user_playlist
161 + end
162 +
163 + def create
164 + @user = User.new(user_params)
165 + if @user.save
166 + # Redirect to login path with email confirmation instruction message
167 + flash[:success] = t('welcome_user_unconfirmed')
168 + redirect_to login_path
169 + else
170 + @prefilled = params[:email].nil? == false
171 + render 'new'
172 + end
173 + end
174 +
175 + def settings
176 + verify_username_or_id
177 + return redirect_to users_path unless @user
178 +
179 + return redirect_to users_path unless @user == current_user
180 +
181 + if !@user.nil? && @user.username == 'splitfire'
182 + # If it's splitfire page, set cookies to connect to actioncable.
183 + cookies.permanent.signed[:splitfire_user_id] = @user.id
184 + end
185 +
186 + meta_tags('profile')
187 + end
188 +
189 + def settings_activate_solana
190 + verify_username_or_id
191 +
192 + return redirect_to users_path unless @user
193 + return redirect_to users_path unless @user == current_user
194 +
195 + create_wallet_if_needed
196 + redirect_to settings_path
197 + end
198 +
199 + def settings_locale
200 + return render json: { result: 'Not signed in' } unless user_signed_in?
201 + return render json: { result: 'Locale nil' } unless params[:default_locale]
202 +
203 + user_setting = UserSetting.find_or_create_by(user_id: current_user.id)
204 + user_setting.update(locale: params[:default_locale].to_i)
205 + render json: { result: true }
206 + end
207 +
208 + def update_settings
209 + return render json: { code: 500, result: 'Error.' } unless update_settings_valid_request?
210 +
211 + user_setting = UserSetting.find_or_create_by(user_id: current_user.id)
212 + user_setting.update_settings(params[:settings][:type], params[:settings][:value])
213 +
214 + render json: {
215 + code: 200,
216 + user_setting: user_setting.as_json
217 + }
218 + end
219 +
220 + def update_settings_valid_request?
221 + verify_username_or_id
222 + return false unless @user
223 +
224 + return false unless @user == current_user
225 +
226 + true
227 + end
228 +
229 + # Update from my profile.
230 + def update
231 + redirect_to root_path unless user_signed_in?
232 +
233 + if params[:user][:username] != 'seto' && params[:user][:username].length < 5
234 + flash[:warning] = 'Username minimum 5 characters.'
235 + redirect_to my_profile_edit_path
236 + return
237 + end
238 +
239 + user = User.find_by(id: params[:user][:id])
240 + # If no information about the provider and uid, update the them.
241 + user.update(
242 + name: params[:user][:name],
243 + username: params[:user][:username],
244 + instagram: params[:user][:instagram],
245 + is_private: params[:user][:is_private]
246 + )
247 +
248 + redirect_to my_profile_path
249 + end
250 +
251 + def edit; end
252 +
253 + def update_resource(resource, params)
254 + resource.update_without_password(params)
255 + end
256 +
257 + private
258 +
259 + def create_wallet_if_needed
260 + create_wallet_solana
261 + create_wallet_evm
262 + end
263 +
264 + def create_wallet_solana
265 + solana_wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana])
266 + return unless solana_wallet.nil?
267 +
268 + Wallet.new(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana], status: :pending).save
269 + WalletSolanaJob.perform_later(@user.id)
270 + end
271 +
272 + def create_wallet_evm
273 + evm_wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm])
274 + return unless evm_wallet.nil?
275 +
276 + Wallet.new(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm], status: :pending).save
277 + WalletEvmJob.perform_later(@user.id)
278 + end
279 +
280 + def protect_onboarding_paths
281 + return redirect_to root_path unless user_signed_in?
282 +
283 + redirect_to root_path unless current_user.need_onboarding?
284 + end
285 +
286 + def user_params
287 + params.require(:user).permit(:name, :email, :password, :password_confirmation)
288 + end
289 +
290 + def verify_username_or_id
291 + @user = User.find_by(username: params[:username])
292 + @user ||= User.find_by_id(params[:username])
293 +
294 + @username_string = "@#{@user&.username_or_id}"
295 + @username_string += " <i class='bi bi-check-circle-fill'></i>" if @user&.verified?
296 + end
297 +
298 + def meta_tags(slug)
299 + meta = Meta
300 + .select(:title, :description)
301 + .where(home: slug, locale: [I18n.locale, I18n.default_locale])
302 + .last
303 +
304 + @meta_title = meta.title if meta
305 + @meta_description = meta.description if meta
306 + end
307 +
308 + def validate_username(username)
309 + @is_valid = false
310 + if username.length < 4
311 + @message = t('username_length_invalid')
312 + elsif !username.ascii_only?
313 + @message = t('username_character_invalid')
314 + elsif username !~ /^[[:alnum:]]+$/
315 + @message = t('username_letters_numbers_only')
316 + elsif username[0] !~ /[A-Za-z]/
317 + @message = t('username_should_starts_with_letters')
318 + elsif User.find_by(username: username.downcase).nil? == false
319 + @message = t('username_taken')
320 + else
321 + @is_valid = true
322 + @message = t('username_available')
323 + end
324 + end
325 +
326 + # Onboarding
327 +
328 + def compute_report_complete
329 + type = params[:type]
330 + case type
331 + # This onboarding types are orderly called from the onboarding page
332 + when 'username'
333 + @next_step = 'spotify' if current_user.need_onboarding_spotify?
334 + when 'spotify'
335 + current_user.spotify.update(onboarding_status: :onboarded, onboarding_status_progress: 100)
336 + @next_step = 'google' if current_user.need_onboarding_google?
337 + when 'google'
338 + current_user.google.update(onboarding_status: :onboarded, onboarding_status_progress: 100)
339 + end
340 + end
341 +
342 + def compute_next_step
343 + action = params[:onboarding_action]
344 + @next_step = 'done'
345 + case action
346 + # This is get called from the welcome onboarding page to determine the first step
347 + when 'next'
348 + # Compute next onboarding step
349 + if current_user.username.blank?
350 + @next_step = 'username'
351 + elsif current_user.need_onboarding_spotify?
352 + @next_step = 'spotify'
353 + elsif current_user.need_onboarding_google?
354 + @next_step = 'google'
355 + end
356 + when 'report_complete'
357 + compute_report_complete
358 + end
359 + end
360 +
361 + def export_youtube_videos(items, user_id)
362 + music_videos = items.select { |item| item['snippet']['categoryId'] == '10' }
363 + music_videos.each do |video|
364 + SongProvider.upsert(
365 + { user_id: user_id, provider_type: :youtube, provider_id: video['id'], name: video['snippet']['title'],
366 + preview_url: "https://www.youtube.com/watch?v=#{video['id']}", description: video['snippet']['description'],
367 + image_url: video['snippet']['thumbnails']['high']['url'] },
368 + unique_by: :index_song_providers_on_provider_id_and_provider_type
369 + )
370 + end
371 + end
372 +
373 + # ENDOF Onboarding
374 +end
backend/musik88-web/app/controllers/welcome_controller.rb new
+95
@@ -0,0 +1,95 @@
1 +class WelcomeController < ApplicationController
2 + QUERY_LIMIT_AUTOCOMPLETE = 5
3 + QUERY_LIMIT_SEARCH_PAGE = 10_000
4 +
5 + def index
6 + meta_tags 'home'
7 + @template = 'main'
8 + end
9 +
10 + def search
11 + @keywords = params[:s]
12 + return if @keywords.nil?
13 +
14 + meta_tags 'search'
15 + @results = merge_search(QUERY_LIMIT_SEARCH_PAGE)
16 + end
17 +
18 + private
19 +
20 + def meta_tags(slug)
21 + meta = Meta
22 + .select(:title, :description)
23 + .where(home: slug, locale: [I18n.locale, I18n.default_locale])
24 + .last
25 +
26 + @meta_title = meta.title if meta
27 + @meta_description = meta.description if meta
28 + @meta_url = root_url
29 + end
30 +
31 + def merge_search(limit)
32 + keyword = params[:s].downcase
33 + search_gending_notation(keyword, limit) + search_gending(keyword, limit) +
34 + search_artist(keyword, limit) + search_song_provider(keyword, limit) + search_album(keyword, limit)
35 + end
36 +
37 + def search_song(keywords, limit)
38 + Song
39 + .where('lower(name) LIKE ?', "%#{keywords}%")
40 + .limit(limit)
41 + .as_json(only: %i[id name]) +
42 + search_song_provider(keywords, limit)
43 + end
44 +
45 + def search_artist(keywords, limit)
46 + Artist
47 + .where('lower(name) LIKE ?', "%#{keywords}%")
48 + .limit(limit)
49 + .as_json(only: %i[id name]) +
50 + search_artist_provider(keywords, limit)
51 + end
52 +
53 + def search_album(keywords, limit)
54 + Album
55 + .where('lower(name) LIKE ?', "%#{keywords}%")
56 + .limit(limit)
57 + .as_json(only: %i[id name]) +
58 + search_album_provider(keywords, limit)
59 + end
60 +
61 + def search_album_provider(keywords, limit)
62 + AlbumProvider
63 + .where('lower(name) LIKE ?', "%#{keywords}%")
64 + .limit(limit)
65 + .as_json(only: %i[id name])
66 + end
67 +
68 + def search_artist_provider(keywords, limit)
69 + ArtistProvider
70 + .where('lower(name) LIKE ?', "%#{keywords}%")
71 + .limit(limit)
72 + .as_json(only: %i[id name])
73 + end
74 +
75 + def search_song_provider(keywords, limit)
76 + SongProvider
77 + .where('lower(name) LIKE ?', "%#{keywords}%")
78 + .limit(limit)
79 + .as_json(only: %i[id name])
80 + end
81 +
82 + def search_gending(keywords, limit)
83 + JavaneseGending
84 + .where('lower(name) LIKE ?', "%#{keywords}%")
85 + .limit(limit)
86 + .as_json(only: %i[id name])
87 + end
88 +
89 + def search_gending_notation(keywords, limit)
90 + JavaneseGendingNotation
91 + .where('lower(name) LIKE ?', "%#{keywords}%")
92 + .limit(limit)
93 + .as_json(only: %i[id name])
94 + end
95 +end
backend/musik88-web/app/frontend/fonts/bootstrap-icons.woff
Binary files /dev/null and b/backend/musik88-web/app/frontend/fonts/bootstrap-icons.woff differ
backend/musik88-web/app/frontend/fonts/bootstrap-icons.woff2
Binary files /dev/null and b/backend/musik88-web/app/frontend/fonts/bootstrap-icons.woff2 differ
backend/musik88-web/app/frontend/images/.keep
backend/musik88-web/app/frontend/images/Pre-order_on_the_App_Store_Badge_ID_RGB_blk_121217.svg new
+186
@@ -0,0 +1,186 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<svg version="1.1" id="livetype" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4 + width="120.6600037px" height="41px" viewBox="0 0 120.6600037 41" enable-background="new 0 0 120.6600037 41"
5 + xml:space="preserve">
6 +<g>
7 + <g>
8 + <g>
9 + <g>
10 + <path fill="#A6A6A6" d="M110.1352768,0H9.5351868C9.1684866,0,8.8061771,0,8.4404564,0.001953
11 + C8.1343069,0.003906,7.8305969,0.009766,7.5215082,0.014648C6.8559866,0.03125,6.1821566,0.072266,5.5176067,0.191406
12 + C4.848177,0.308594,4.2261066,0.508789,3.6167269,0.818359C3.0180967,1.125,2.4712083,1.52344,1.9980768,1.99707
13 + c-0.47852,0.4736301-0.875,1.0224601-1.17822,1.6210901c-0.311039,0.6084001-0.508305,1.2334001-0.625004,1.9033201
14 + c-0.120606,0.6621099-0.162109,1.3320398-0.179199,2.0019598C0.0063758,7.83008,0.0053998,8.1377001,0.0005084,8.4443398
15 + c0,0.3622999,0,0.7255802,0,1.0917902v20.928669c0,0.3691998,0,0.7304993,0,1.0937996
16 + c0.0048914,0.3105011,0.0058674,0.6112995,0.0151454,0.9218998c0.01709,0.669899,0.058593,1.3398018,0.179199,2.0018997
17 + c0.116699,0.6699028,0.313965,1.2979012,0.625004,1.9043007c0.30322,0.5956993,0.6997,1.1445999,1.17822,1.6142998
18 + c0.4731315,0.4775009,1.0200199,0.875,1.6186501,1.1786995c0.6093798,0.3125,1.2314501,0.5098,1.9008799,0.6308022
19 + c0.6645498,0.1191978,1.3383799,0.1582985,2.0039015,0.1767998c0.3090887,0.0067978,0.6127987,0.0107002,0.9189482,0.0107002
20 + C8.8061771,40,9.1684866,40,9.5351868,40h100.60009c0.3593979,0,0.7246017,0,1.0839996-0.0019989
21 + c0.3047028,0,0.6172028-0.0039024,0.9218979-0.0107002c0.669899-0.0185013,1.3418045-0.0576019,2-0.1767998
22 + c0.669899-0.1210022,1.2929001-0.3183022,1.9082031-0.6308022c0.5976028-0.3036995,1.1445007-0.7011986,1.6172028-1.1786995
23 + c0.4764938-0.4696999,0.8730011-1.0186005,1.1815948-1.6142998c0.3066025-0.6063995,0.5059052-1.2343979,0.6191025-1.9043007
24 + c0.1231003-0.6620979,0.1621017-1.3320007,0.1856003-2.0018997c0.0038986-0.3106003,0.0038986-0.6113987,0.0038986-0.9218998
25 + c0.0078049-0.3633003,0.0078049-0.7245998,0.0078049-1.0937996V9.53613c0-0.36621,0-0.7294903-0.0078049-1.0917902
26 + c0-0.3066397,0-0.6142597-0.0038986-0.9208999c-0.0234985-0.66992-0.0625-1.3398499-0.1856003-2.0019598
27 + c-0.1131973-0.66992-0.3125-1.29492-0.6191025-1.9033201c-0.3085938-0.59863-0.705101-1.14746-1.1815948-1.6210901
28 + c-0.472702-0.47363-1.0195999-0.87207-1.6172028-1.1787109c-0.615303-0.30957-1.2383041-0.509765-1.9082031-0.626953
29 + c-0.6581955-0.11914-1.330101-0.160156-2-0.176758c-0.3046951-0.004882-0.6171951-0.010742-0.9218979-0.012695
30 + C110.8598785,0,110.4946747,0,110.1352768,0L110.1352768,0z"/>
31 + <path d="M8.4453363,39.125c-0.3046799,0-0.6020494-0.0038986-0.9042897-0.0107002
32 + c-0.5590796-0.0157013-1.2221699-0.0468979-1.8691397-0.1631012c-0.6103501-0.1103973-1.1528401-0.2901001-1.6567402-0.5478973
33 + c-0.5214901-0.2646027-0.9902401-0.6063995-1.39697-1.0166016c-0.4140699-0.4062004-0.7543999-0.8730011-1.02051-1.3964996
34 + c-0.25928-0.5047989-0.43653-1.0467987-0.5429784-1.6571999c-0.1220616-0.672802-0.1533116-1.3554993-0.1665-1.875
35 + c-0.0063416-0.2108994-0.0146416-0.9130993-0.0146416-0.9130993V8.4443398c0,0,0.00879-0.6914096,0.0146416-0.8945398
36 + C0.9013968,7.0258799,0.9326468,6.34375,1.0537368,5.6777301c0.1069399-0.61377,0.2841716-1.1552701,0.54346-1.6621003
37 + C1.8628083,3.49121,2.2036467,3.02197,2.6123369,2.6176801C3.0259068,2.2036099,3.4966068,1.86084,4.0146766,1.59521
38 + C4.5283465,1.33447,5.0698566,1.15625,5.6679969,1.05127c0.6733398-0.120606,1.3559599-0.150879,1.8754897-0.164063
39 + L8.4458265,0.875h102.7685547l0.9131012,0.012695c0.5126953,0.012696,1.1952972,0.042969,1.8583984,0.162595
40 + c0.6025009,0.1054701,1.1474991,0.28467,1.6708984,0.54785c0.5127029,0.2627,0.982399,0.6054699,1.3916016,1.01563
41 + c0.4092026,0.40625,0.7518997,0.8779299,1.0233994,1.4043002c0.2578049,0.51123,0.4336014,1.0527296,0.535202,1.6489196
42 + c0.1161957,0.6308603,0.152298,1.27881,0.1737976,1.8872104c0.0028992,0.2831998,0.0028992,0.5873995,0.0028992,0.8901396
43 + c0.0079041,0.375,0.0079041,0.7319298,0.0079041,1.0917902v20.928669c0,0.3633003,0,0.7178001-0.0079041,1.075201
44 + c0,0.3251991,0,0.6231003-0.0038986,0.9296989c-0.0205002,0.5889015-0.0566025,1.2364006-0.1708984,1.8535004
45 + c-0.1035004,0.6133003-0.2792969,1.1553001-0.5400009,1.6699982c-0.2695007,0.5195007-0.6122971,0.9892006-1.0156021,1.3857002
46 + c-0.4131012,0.4180031-0.881897,0.7588005-1.3993988,1.0225029c-0.5186005,0.2635994-1.0478973,0.4384003-1.6679993,0.5497971
47 + c-0.6406021,0.1162033-1.3037033,0.1473999-1.8692017,0.1631012c-0.2929001,0.0068016-0.5996017,0.0107002-0.8973999,0.0107002
48 + l-1.0839996,0.0019989L8.4453363,39.125z"/>
49 + </g>
50 + </g>
51 + <g id="XMLID_20_">
52 + <g id="XMLID_22_">
53 + <g id="XMLID_23_">
54 + <path id="XMLID_25_" fill="#FFFFFF" d="M24.7693844,20.3006763c-0.0250092-2.7508278,2.2523365-4.0894947,2.3565598-4.1520615
55 + c-1.289587-1.880518-3.2889233-2.137701-3.9911633-2.1576748c-1.6792431-0.1762638-3.307188,1.0048323-4.1629009,1.0048323
56 + c-0.8722649,0-2.1897697-0.9873343-3.6085024-0.9581413c-1.8263159,0.0283384-3.5356064,1.0857515-4.4729204,2.7278662
57 + c-1.9339523,3.3484154-0.4914055,8.2694664,1.3612013,10.976078c0.9269009,1.3253498,2.0101767,2.8057976,3.4276285,2.7533016
58 + c1.3870659-0.0575314,1.9051018-0.8844776,3.5793953-0.8844776c1.6587582,0,2.1447868,0.8844776,3.5910034,0.8511028
59 + c1.4883862-0.0241566,2.4261265-1.3312416,3.3205109-2.6691399c1.0711498-1.5195389,1.5012684-3.0158634,1.518425-3.092514
60 + C27.6539669,24.6881542,24.7977238,23.598732,24.7693844,20.3006763z"/>
61 + <path id="XMLID_24_" fill="#FFFFFF" d="M22.0377636,12.2108879c0.7456856-0.933217,1.2562103-2.2023182,1.1145172-3.4906235
62 + c-1.0798626,0.0478859-2.4303093,0.7464542-3.2075768,1.6596127c-0.6877289,0.8039846-1.302475,2.1223364-1.1437111,3.3613911
63 + C20.0138435,13.8313208,21.2592144,13.1294231,22.0377636,12.2108879z"/>
64 + </g>
65 + </g>
66 + <g>
67 + <path fill="#FFFFFF" d="M42.3027763,27.1396484h-4.7333984l-1.1367188,3.3564453h-2.0048828l4.4833984-12.4179688h2.0830078
68 + l4.4833984,12.4179688h-2.0390739L42.3027763,27.1396484z M38.0596123,25.5908203h3.7519531l-1.8496094-5.4472656h-0.0517578
69 + L38.0596123,25.5908203z"/>
70 + <path fill="#FFFFFF" d="M55.1601982,25.9697266c0,2.8134766-1.5058594,4.6210938-3.7783203,4.6210938
71 + c-1.2900391,0-2.3144531-0.5771484-2.8486328-1.5839844h-0.0429688v4.484375h-1.8583984V21.4423828h1.7988281v1.5058594
72 + h0.0341797c0.5166016-0.9716797,1.6181641-1.6005859,2.8828125-1.6005859
73 + C53.6455498,21.3476562,55.1601982,23.1640625,55.1601982,25.9697266z M53.250042,25.9697266
74 + c0-1.8330078-0.9472656-3.0380859-2.3925781-3.0380859c-1.4199219,0-2.375,1.2304688-2.375,3.0380859
75 + c0,1.8242188,0.9550781,3.0458984,2.375,3.0458984C52.3027763,29.015625,53.250042,27.8193359,53.250042,25.9697266z"/>
76 + <path fill="#FFFFFF" d="M65.1250381,25.9697266c0,2.8134766-1.5058556,4.6210938-3.7783165,4.6210938
77 + c-1.2900391,0-2.3144531-0.5771484-2.8486328-1.5839844h-0.0429688v4.484375h-1.8583984V21.4423828h1.7988281v1.5058594
78 + h0.0341797c0.5166016-0.9716797,1.6181641-1.6005859,2.8828125-1.6005859
79 + C63.6103935,21.3476562,65.1250381,23.1640625,65.1250381,25.9697266z M63.2148857,25.9697266
80 + c0-1.8330078-0.9472656-3.0380859-2.3925781-3.0380859c-1.4199219,0-2.375,1.2304688-2.375,3.0380859
81 + c0,1.8242188,0.9550781,3.0458984,2.375,3.0458984C62.2676201,29.015625,63.2148857,27.8193359,63.2148857,25.9697266z"/>
82 + <path fill="#FFFFFF" d="M71.7109756,27.0361328c0.1376953,1.2314453,1.3339844,2.0400391,2.96875,2.0400391
83 + c1.5664062,0,2.6933594-0.8085938,2.6933594-1.9189453c0-0.9638672-0.6796875-1.5410156-2.2890625-1.9365234
84 + l-1.609375-0.3876953c-2.2802734-0.5507812-3.3388672-1.6171875-3.3388672-3.3476562
85 + c0-2.1425781,1.8671875-3.6142578,4.5185547-3.6142578c2.6240234,0,4.4228516,1.4716797,4.4833984,3.6142578h-1.8759766
86 + c-0.1123047-1.2392578-1.1367188-1.9873047-2.6337891-1.9873047s-2.5214844,0.7568359-2.5214844,1.8583984
87 + c0,0.8779297,0.6542969,1.3945312,2.2548828,1.7900391l1.3681641,0.3359375
88 + c2.5478516,0.6025391,3.6064453,1.6259766,3.6064453,3.4423828c0,2.3232422-1.8505859,3.7783203-4.7939453,3.7783203
89 + c-2.7539062,0-4.6132812-1.4208984-4.7333984-3.6669922H71.7109756z"/>
90 + <path fill="#FFFFFF" d="M83.3467178,19.2998047v2.1425781h1.7216797v1.4716797h-1.7216797v4.9912109
91 + c0,0.7753906,0.3447266,1.1367188,1.1015625,1.1367188c0.1894531,0,0.4912109-0.0263672,0.6113281-0.0429688v1.4628906
92 + c-0.2060547,0.0517578-0.6191406,0.0859375-1.0322266,0.0859375c-1.8330078,0-2.5478516-0.6884766-2.5478516-2.4443359
93 + v-5.1894531h-1.3164062v-1.4716797h1.3164062v-2.1425781H83.3467178z"/>
94 + <path fill="#FFFFFF" d="M86.0654678,25.9697266c0-2.8486328,1.6777344-4.6386719,4.2939453-4.6386719
95 + c2.625,0,4.2949219,1.7900391,4.2949219,4.6386719c0,2.8564453-1.6611328,4.6386719-4.2949219,4.6386719
96 + C87.7266006,30.6083984,86.0654678,28.8261719,86.0654678,25.9697266z M92.7607803,25.9697266
97 + c0-1.9541016-0.8955078-3.1074219-2.4013672-3.1074219s-2.4003906,1.1621094-2.4003906,3.1074219
98 + c0,1.9619141,0.8945312,3.1064453,2.4003906,3.1064453S92.7607803,27.9316406,92.7607803,25.9697266z"/>
99 + <path fill="#FFFFFF" d="M96.1865616,21.4423828h1.7724609v1.5410156h0.0429688
100 + c0.2841797-1.0244141,1.1103516-1.6357422,2.1777344-1.6357422c0.2666016,0,0.4902344,0.0351562,0.6367188,0.0693359v1.7382812
101 + c-0.1464844-0.0605469-0.4736328-0.1123047-0.8349609-0.1123047c-1.1962891,0-1.9365234,0.8095703-1.9365234,2.0830078
102 + v5.3701172h-1.8583984V21.4423828z"/>
103 + <path fill="#FFFFFF" d="M109.3848038,27.8369141c-0.25,1.6435547-1.8505859,2.7714844-3.8984375,2.7714844
104 + c-2.6337891,0-4.2685547-1.7646484-4.2685547-4.5957031c0-2.8398438,1.6435547-4.6816406,4.1904297-4.6816406
105 + c2.5048828,0,4.0800781,1.7207031,4.0800781,4.4658203v0.6367188h-6.3945312v0.1123047
106 + c0,1.5488281,0.9726562,2.5644531,2.4355469,2.5644531c1.0322266,0,1.8417969-0.4902344,2.0908203-1.2734375H109.3848038z
107 + M103.1025772,25.1347656h4.5263672c-0.0429688-1.3857422-0.9296875-2.2978516-2.2207031-2.2978516
108 + C104.1260147,22.8369141,103.1973038,23.7666016,103.1025772,25.1347656z"/>
109 + </g>
110 + </g>
111 + </g>
112 + <g id="XMLID_1_">
113 + <g>
114 + <path fill="#FFFFFF" d="M38.0083427,8.730957c1.1660156,0,1.9677734,0.8061523,1.9677734,1.9887695
115 + c0,1.1660156-0.8261719,1.9682617-1.9970703,1.9682617h-1.3808594v2.0097656h-0.9267578V8.730957H38.0083427z
116 + M36.5981865,11.8735352h1.1660156c0.7978516,0,1.265625-0.4135742,1.265625-1.1538086
117 + c0-0.7568359-0.4511719-1.1704102-1.265625-1.1704102h-1.1660156V11.8735352z"/>
118 + <path fill="#FFFFFF" d="M41.1899834,10.1948242h0.8554688v0.6904297h0.0664062
119 + c0.1279297-0.4423828,0.6279297-0.7651367,1.2158203-0.7651367c0.1318359,0,0.3017578,0.012207,0.3964844,0.0371094v0.8769531
120 + c-0.0742188-0.0249023-0.3388672-0.0537109-0.4960938-0.0537109c-0.6738281,0-1.1494141,0.4257812-1.1494141,1.0585938v2.6586914
121 + h-0.8886719V10.1948242z"/>
122 + <path fill="#FFFFFF" d="M44.4204521,13.4243164c0-0.8105469,0.6035156-1.277832,1.6748047-1.3442383l1.2197266-0.0703125
123 + v-0.3886719c0-0.4755859-0.3144531-0.7441406-0.921875-0.7441406c-0.4960938,0-0.8398438,0.1821289-0.9384766,0.5004883
124 + h-0.8603516c0.0908203-0.7734375,0.8183594-1.2695312,1.8398438-1.2695312c1.1289062,0,1.765625,0.5620117,1.765625,1.5131836
125 + v3.0766602h-0.8554688v-0.6328125h-0.0703125c-0.2685547,0.4506836-0.7607422,0.7070312-1.3525391,0.7070312
126 + C45.0532646,14.7719727,44.4204521,14.2470703,44.4204521,13.4243164z M47.3149834,13.0395508v-0.3764648l-1.0996094,0.0703125
127 + c-0.6201172,0.0415039-0.9013672,0.2524414-0.9013672,0.6494141c0,0.4052734,0.3515625,0.6411133,0.8349609,0.6411133
128 + C46.8188896,14.0239258,47.3149834,13.5976562,47.3149834,13.0395508z"/>
129 + <path fill="#FFFFFF" d="M49.5649834,12.7294922v-0.8725586h2.7128906v0.8725586H49.5649834z"/>
130 + <path fill="#FFFFFF" d="M57.9116631,12.4443359c0,1.4306641-0.7236328,2.3276367-1.8613281,2.3276367
131 + c-0.640625,0-1.1494141-0.2807617-1.3847656-0.7563477h-0.0703125v2.1791992h-0.8886719v-6h0.8554688v0.7441406h0.0664062
132 + c0.2724609-0.5126953,0.8017578-0.8188477,1.4140625-0.8188477
133 + C57.1880302,10.1201172,57.9116631,11.0131836,57.9116631,12.4443359z M56.9976006,12.4443359
134 + c0-0.9511719-0.4501953-1.5258789-1.203125-1.5258789c-0.7490234,0-1.2158203,0.5869141-1.2158203,1.5297852
135 + c0,0.9384766,0.4667969,1.5258789,1.2119141,1.5258789C56.5474052,13.9741211,56.9976006,13.3994141,56.9976006,12.4443359z"/>
136 + <path fill="#FFFFFF" d="M62.9116631,13.4819336c-0.2021484,0.8066406-0.921875,1.3027344-1.9511719,1.3027344
137 + c-1.2900391,0-2.0800781-0.8847656-2.0800781-2.3242188c0-1.4389648,0.8066406-2.3525391,2.0761719-2.3525391
138 + c1.2529297,0,2.0087891,0.855957,2.0087891,2.2700195v0.3100586h-3.1796875v0.0498047
139 + c0.0292969,0.7895508,0.4882812,1.2900391,1.1992188,1.2900391c0.5380859,0,0.90625-0.1943359,1.0712891-0.5458984H62.9116631z
140 + M59.7856865,12.0307617h2.2744141c-0.0205078-0.7070312-0.4501953-1.1665039-1.1083984-1.1665039
141 + C60.2944756,10.8642578,59.8354912,11.3276367,59.7856865,12.0307617z"/>
142 + <path fill="#FFFFFF" d="M65.8047256,10.1079102c1.0126953,0,1.6748047,0.4711914,1.7617188,1.2651367h-0.8525391
143 + c-0.0820312-0.3305664-0.4052734-0.5415039-0.9091797-0.5415039c-0.4960938,0-0.8735352,0.2353516-0.8735352,0.5869141
144 + c0,0.269043,0.2280273,0.4384766,0.7163086,0.550293l0.7480469,0.1733398
145 + c0.8564453,0.1987305,1.2578125,0.5668945,1.2578125,1.2285156c0,0.8476562-0.7900391,1.4140625-1.8652344,1.4140625
146 + c-1.0717773,0-1.7700195-0.4838867-1.8491173-1.2817383h0.8896446c0.1113281,0.347168,0.4428711,0.5620117,0.9799805,0.5620117
147 + c0.5537109,0,0.9472656-0.2480469,0.9472656-0.6079102c0-0.2685547-0.2109375-0.4423828-0.6621094-0.5498047
148 + l-0.7851562-0.1821289c-0.8569336-0.2026367-1.253418-0.5869141-1.253418-1.2568359
149 + C64.0552139,10.6699219,64.7866592,10.1079102,65.8047256,10.1079102z"/>
150 + <path fill="#FFFFFF" d="M68.5937881,13.4243164c0-0.8105469,0.6035156-1.277832,1.6748047-1.3442383l1.2197266-0.0703125
151 + v-0.3886719c0-0.4755859-0.3144531-0.7441406-0.921875-0.7441406c-0.4960938,0-0.8398438,0.1821289-0.9384766,0.5004883
152 + h-0.8603516c0.0908203-0.7734375,0.8183594-1.2695312,1.8398438-1.2695312c1.1289062,0,1.765625,0.5620117,1.765625,1.5131836
153 + v3.0766602h-0.8554688v-0.6328125h-0.0703125c-0.2685547,0.4506836-0.7607422,0.7070312-1.3525391,0.7070312
154 + C69.2266006,14.7719727,68.5937881,14.2470703,68.5937881,13.4243164z M71.4883194,13.0395508v-0.3764648L70.38871,12.7333984
155 + c-0.6201172,0.0415039-0.9013672,0.2524414-0.9013672,0.6494141c0,0.4052734,0.3515625,0.6411133,0.8349609,0.6411133
156 + C70.9922256,14.0239258,71.4883194,13.5976562,71.4883194,13.0395508z"/>
157 + <path fill="#FFFFFF" d="M73.7646866,10.1948242h0.8554688v0.715332h0.0664062
158 + c0.21875-0.5004883,0.6660156-0.8022461,1.34375-0.8022461c1.0048828,0,1.5585938,0.6035156,1.5585938,1.6748047v2.9150391
159 + h-0.8886719v-2.6918945c0-0.7236328-0.3144531-1.0834961-0.9716797-1.0834961s-1.0751953,0.4384766-1.0751953,1.1411133
160 + v2.6342773h-0.8886719V10.1948242z"/>
161 + <path fill="#FFFFFF" d="M81.1826553,12.4443359c0-1.4228516,0.7314453-2.3242188,1.8691406-2.3242188
162 + c0.6162109,0,1.1367188,0.293457,1.3808594,0.7900391h0.0664062V8.4370117h0.8886719v6.2607422H84.536171v-0.7114258h-0.0703125
163 + c-0.2685547,0.4921875-0.7939453,0.7856445-1.4140625,0.7856445
164 + C81.9062881,14.7719727,81.1826553,13.8706055,81.1826553,12.4443359z M82.1006241,12.4443359
165 + c0,0.9550781,0.4501953,1.5297852,1.203125,1.5297852c0.7490234,0,1.2119141-0.5830078,1.2119141-1.5258789
166 + c0-0.9384766-0.4677734-1.5297852-1.2119141-1.5297852C82.5557022,10.918457,82.1006241,11.4970703,82.1006241,12.4443359z"/>
167 + <path fill="#FFFFFF" d="M86.7295303,8.8632812c0-0.3105469,0.2441406-0.5458984,0.5751953-0.5458984
168 + c0.3300781,0,0.5742188,0.2353516,0.5742188,0.5458984c0,0.3056641-0.2441406,0.5415039-0.5742188,0.5415039
169 + C86.973671,9.4047852,86.7295303,9.1689453,86.7295303,8.8632812z M86.8623428,10.1948242h0.8847656v4.5029297h-0.8847656
170 + V10.1948242z"/>
171 + </g>
172 + </g>
173 +</g>
174 +<g>
175 +</g>
176 +<g>
177 +</g>
178 +<g>
179 +</g>
180 +<g>
181 +</g>
182 +<g>
183 +</g>
184 +<g>
185 +</g>
186 +</svg>
backend/musik88-web/app/frontend/images/Pre-order_on_the_App_Store_Badge_SE_RGB_blk_121217.svg new
+187
@@ -0,0 +1,187 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<svg version="1.1" id="livetype" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4 + width="120.6600037px" height="41px" viewBox="0 0 120.6600037 41" enable-background="new 0 0 120.6600037 41"
5 + xml:space="preserve">
6 +<g>
7 + <g>
8 + <g>
9 + <g>
10 + <path fill="#A6A6A6" d="M110.1352768,0H9.5351868C9.1684866,0,8.8061771,0,8.4404564,0.001953
11 + C8.1343069,0.003906,7.8305969,0.009766,7.5215082,0.014648C6.8559866,0.03125,6.1821566,0.072266,5.5176067,0.191406
12 + C4.848177,0.308594,4.2261066,0.508789,3.6167269,0.818359C3.0180967,1.125,2.4712083,1.52344,1.9980768,1.99707
13 + c-0.47852,0.4736301-0.875,1.0224601-1.17822,1.6210901c-0.311039,0.6084001-0.508305,1.2334001-0.625004,1.9033201
14 + c-0.120606,0.6621099-0.162109,1.3320398-0.179199,2.0019598C0.0063758,7.83008,0.0053998,8.1377001,0.0005084,8.4443398
15 + c0,0.3622999,0,0.7255802,0,1.0917902v20.928669c0,0.3691998,0,0.7304993,0,1.0937996
16 + c0.0048914,0.3105011,0.0058674,0.6112995,0.0151454,0.9218998c0.01709,0.669899,0.058593,1.3398018,0.179199,2.0018997
17 + c0.116699,0.6699028,0.313965,1.2979012,0.625004,1.9043007c0.30322,0.5956993,0.6997,1.1445999,1.17822,1.6142998
18 + c0.4731315,0.4775009,1.0200199,0.875,1.6186501,1.1786995c0.6093798,0.3125,1.2314501,0.5098,1.9008799,0.6308022
19 + c0.6645498,0.1191978,1.3383799,0.1582985,2.0039015,0.1767998c0.3090887,0.0067978,0.6127987,0.0107002,0.9189482,0.0107002
20 + C8.8061771,40,9.1684866,40,9.5351868,40h100.60009c0.3593979,0,0.7246017,0,1.0839996-0.0019989
21 + c0.3047028,0,0.6172028-0.0039024,0.9218979-0.0107002c0.669899-0.0185013,1.3418045-0.0576019,2-0.1767998
22 + c0.669899-0.1210022,1.2929001-0.3183022,1.9082031-0.6308022c0.5976028-0.3036995,1.1445007-0.7011986,1.6172028-1.1786995
23 + c0.4764938-0.4696999,0.8730011-1.0186005,1.1815948-1.6142998c0.3066025-0.6063995,0.5059052-1.2343979,0.6191025-1.9043007
24 + c0.1231003-0.6620979,0.1621017-1.3320007,0.1856003-2.0018997c0.0038986-0.3106003,0.0038986-0.6113987,0.0038986-0.9218998
25 + c0.0078049-0.3633003,0.0078049-0.7245998,0.0078049-1.0937996V9.53613c0-0.36621,0-0.7294903-0.0078049-1.0917902
26 + c0-0.3066397,0-0.6142597-0.0038986-0.9208999c-0.0234985-0.66992-0.0625-1.3398499-0.1856003-2.0019598
27 + c-0.1131973-0.66992-0.3125-1.29492-0.6191025-1.9033201c-0.3085938-0.59863-0.705101-1.14746-1.1815948-1.6210901
28 + c-0.472702-0.47363-1.0195999-0.87207-1.6172028-1.1787109c-0.615303-0.30957-1.2383041-0.509765-1.9082031-0.626953
29 + c-0.6581955-0.11914-1.330101-0.160156-2-0.176758c-0.3046951-0.004882-0.6171951-0.010742-0.9218979-0.012695
30 + C110.8598785,0,110.4946747,0,110.1352768,0L110.1352768,0z"/>
31 + <path d="M8.4453363,39.125c-0.3046799,0-0.6020494-0.0038986-0.9042897-0.0107002
32 + c-0.5590796-0.0157013-1.2221699-0.0468979-1.8691397-0.1631012c-0.6103501-0.1103973-1.1528401-0.2901001-1.6567402-0.5478973
33 + c-0.5214901-0.2646027-0.9902401-0.6063995-1.39697-1.0166016c-0.4140699-0.4062004-0.7543999-0.8730011-1.02051-1.3964996
34 + c-0.25928-0.5047989-0.43653-1.0467987-0.5429784-1.6571999c-0.1220616-0.672802-0.1533116-1.3554993-0.1665-1.875
35 + c-0.0063416-0.2108994-0.0146416-0.9130993-0.0146416-0.9130993V8.4443398c0,0,0.00879-0.6914096,0.0146416-0.8945398
36 + C0.9013968,7.0258799,0.9326468,6.34375,1.0537368,5.6777301c0.1069399-0.61377,0.2841716-1.1552701,0.54346-1.6621003
37 + C1.8628083,3.49121,2.2036467,3.02197,2.6123369,2.6176801C3.0259068,2.2036099,3.4966068,1.86084,4.0146766,1.59521
38 + C4.5283465,1.33447,5.0698566,1.15625,5.6679969,1.05127c0.6733398-0.120606,1.3559599-0.150879,1.8754897-0.164063
39 + L8.4458265,0.875h102.7685547l0.9131012,0.012695c0.5126953,0.012696,1.1952972,0.042969,1.8583984,0.162595
40 + c0.6025009,0.1054701,1.1474991,0.28467,1.6708984,0.54785c0.5127029,0.2627,0.982399,0.6054699,1.3916016,1.01563
41 + c0.4092026,0.40625,0.7518997,0.8779299,1.0233994,1.4043002c0.2578049,0.51123,0.4336014,1.0527296,0.535202,1.6489196
42 + c0.1161957,0.6308603,0.152298,1.27881,0.1737976,1.8872104c0.0028992,0.2831998,0.0028992,0.5873995,0.0028992,0.8901396
43 + c0.0079041,0.375,0.0079041,0.7319298,0.0079041,1.0917902v20.928669c0,0.3633003,0,0.7178001-0.0079041,1.075201
44 + c0,0.3251991,0,0.6231003-0.0038986,0.9296989c-0.0205002,0.5889015-0.0566025,1.2364006-0.1708984,1.8535004
45 + c-0.1035004,0.6133003-0.2792969,1.1553001-0.5400009,1.6699982c-0.2695007,0.5195007-0.6122971,0.9892006-1.0156021,1.3857002
46 + c-0.4131012,0.4180031-0.881897,0.7588005-1.3993988,1.0225029c-0.5186005,0.2635994-1.0478973,0.4384003-1.6679993,0.5497971
47 + c-0.6406021,0.1162033-1.3037033,0.1473999-1.8692017,0.1631012c-0.2929001,0.0068016-0.5996017,0.0107002-0.8973999,0.0107002
48 + l-1.0839996,0.0019989L8.4453363,39.125z"/>
49 + </g>
50 + </g>
51 + <g id="XMLID_20_">
52 + <g id="XMLID_22_">
53 + <g id="XMLID_23_">
54 + <path id="XMLID_25_" fill="#FFFFFF" d="M24.7693844,20.3006763c-0.0250092-2.7508278,2.2523365-4.0894947,2.3565598-4.1520615
55 + c-1.289587-1.880518-3.2889233-2.137701-3.9911633-2.1576748c-1.6792431-0.1762638-3.307188,1.0048323-4.1629009,1.0048323
56 + c-0.8722649,0-2.1897697-0.9873343-3.6085024-0.9581413c-1.8263159,0.0283384-3.5356064,1.0857515-4.4729204,2.7278662
57 + c-1.9339523,3.3484154-0.4914055,8.2694664,1.3612013,10.976078c0.9269009,1.3253498,2.0101767,2.8057976,3.4276285,2.7533016
58 + c1.3870659-0.0575314,1.9051018-0.8844776,3.5793953-0.8844776c1.6587582,0,2.1447868,0.8844776,3.5910034,0.8511028
59 + c1.4883862-0.0241566,2.4261265-1.3312416,3.3205109-2.6691399c1.0711498-1.5195389,1.5012684-3.0158634,1.518425-3.092514
60 + C27.6539669,24.6881542,24.7977238,23.598732,24.7693844,20.3006763z"/>
61 + <path id="XMLID_24_" fill="#FFFFFF" d="M22.0377636,12.2108879c0.7456856-0.933217,1.2562103-2.2023182,1.1145172-3.4906235
62 + c-1.0798626,0.0478859-2.4303093,0.7464542-3.2075768,1.6596127c-0.6877289,0.8039846-1.302475,2.1223364-1.1437111,3.3613911
63 + C20.0138435,13.8313208,21.2592144,13.1294231,22.0377636,12.2108879z"/>
64 + </g>
65 + </g>
66 + <g>
67 + <path fill="#FFFFFF" d="M42.3027763,27.1396484h-4.7333984l-1.1367188,3.3564453h-2.0048828l4.4833984-12.4179688h2.0830078
68 + l4.4833984,12.4179688h-2.0390739L42.3027763,27.1396484z M38.0596123,25.5908203h3.7519531l-1.8496094-5.4472656h-0.0517578
69 + L38.0596123,25.5908203z"/>
70 + <path fill="#FFFFFF" d="M55.1601982,25.9697266c0,2.8134766-1.5058594,4.6210938-3.7783203,4.6210938
71 + c-1.2900391,0-2.3144531-0.5771484-2.8486328-1.5839844h-0.0429688v4.484375h-1.8583984V21.4423828h1.7988281v1.5058594
72 + h0.0341797c0.5166016-0.9716797,1.6181641-1.6005859,2.8828125-1.6005859
73 + C53.6455498,21.3476562,55.1601982,23.1640625,55.1601982,25.9697266z M53.250042,25.9697266
74 + c0-1.8330078-0.9472656-3.0380859-2.3925781-3.0380859c-1.4199219,0-2.375,1.2304688-2.375,3.0380859
75 + c0,1.8242188,0.9550781,3.0458984,2.375,3.0458984C52.3027763,29.015625,53.250042,27.8193359,53.250042,25.9697266z"/>
76 + <path fill="#FFFFFF" d="M65.1250381,25.9697266c0,2.8134766-1.5058556,4.6210938-3.7783165,4.6210938
77 + c-1.2900391,0-2.3144531-0.5771484-2.8486328-1.5839844h-0.0429688v4.484375h-1.8583984V21.4423828h1.7988281v1.5058594
78 + h0.0341797c0.5166016-0.9716797,1.6181641-1.6005859,2.8828125-1.6005859
79 + C63.6103935,21.3476562,65.1250381,23.1640625,65.1250381,25.9697266z M63.2148857,25.9697266
80 + c0-1.8330078-0.9472656-3.0380859-2.3925781-3.0380859c-1.4199219,0-2.375,1.2304688-2.375,3.0380859
81 + c0,1.8242188,0.9550781,3.0458984,2.375,3.0458984C62.2676201,29.015625,63.2148857,27.8193359,63.2148857,25.9697266z"/>
82 + <path fill="#FFFFFF" d="M71.7109756,27.0361328c0.1376953,1.2314453,1.3339844,2.0400391,2.96875,2.0400391
83 + c1.5664062,0,2.6933594-0.8085938,2.6933594-1.9189453c0-0.9638672-0.6796875-1.5410156-2.2890625-1.9365234
84 + l-1.609375-0.3876953c-2.2802734-0.5507812-3.3388672-1.6171875-3.3388672-3.3476562
85 + c0-2.1425781,1.8671875-3.6142578,4.5185547-3.6142578c2.6240234,0,4.4228516,1.4716797,4.4833984,3.6142578h-1.8759766
86 + c-0.1123047-1.2392578-1.1367188-1.9873047-2.6337891-1.9873047s-2.5214844,0.7568359-2.5214844,1.8583984
87 + c0,0.8779297,0.6542969,1.3945312,2.2548828,1.7900391l1.3681641,0.3359375
88 + c2.5478516,0.6025391,3.6064453,1.6259766,3.6064453,3.4423828c0,2.3232422-1.8505859,3.7783203-4.7939453,3.7783203
89 + c-2.7539062,0-4.6132812-1.4208984-4.7333984-3.6669922H71.7109756z"/>
90 + <path fill="#FFFFFF" d="M83.3467178,19.2998047v2.1425781h1.7216797v1.4716797h-1.7216797v4.9912109
91 + c0,0.7753906,0.3447266,1.1367188,1.1015625,1.1367188c0.1894531,0,0.4912109-0.0263672,0.6113281-0.0429688v1.4628906
92 + c-0.2060547,0.0517578-0.6191406,0.0859375-1.0322266,0.0859375c-1.8330078,0-2.5478516-0.6884766-2.5478516-2.4443359
93 + v-5.1894531h-1.3164062v-1.4716797h1.3164062v-2.1425781H83.3467178z"/>
94 + <path fill="#FFFFFF" d="M86.0654678,25.9697266c0-2.8486328,1.6777344-4.6386719,4.2939453-4.6386719
95 + c2.625,0,4.2949219,1.7900391,4.2949219,4.6386719c0,2.8564453-1.6611328,4.6386719-4.2949219,4.6386719
96 + C87.7266006,30.6083984,86.0654678,28.8261719,86.0654678,25.9697266z M92.7607803,25.9697266
97 + c0-1.9541016-0.8955078-3.1074219-2.4013672-3.1074219s-2.4003906,1.1621094-2.4003906,3.1074219
98 + c0,1.9619141,0.8945312,3.1064453,2.4003906,3.1064453S92.7607803,27.9316406,92.7607803,25.9697266z"/>
99 + <path fill="#FFFFFF" d="M96.1865616,21.4423828h1.7724609v1.5410156h0.0429688
100 + c0.2841797-1.0244141,1.1103516-1.6357422,2.1777344-1.6357422c0.2666016,0,0.4902344,0.0351562,0.6367188,0.0693359v1.7382812
101 + c-0.1464844-0.0605469-0.4736328-0.1123047-0.8349609-0.1123047c-1.1962891,0-1.9365234,0.8095703-1.9365234,2.0830078
102 + v5.3701172h-1.8583984V21.4423828z"/>
103 + <path fill="#FFFFFF" d="M109.3848038,27.8369141c-0.25,1.6435547-1.8505859,2.7714844-3.8984375,2.7714844
104 + c-2.6337891,0-4.2685547-1.7646484-4.2685547-4.5957031c0-2.8398438,1.6435547-4.6816406,4.1904297-4.6816406
105 + c2.5048828,0,4.0800781,1.7207031,4.0800781,4.4658203v0.6367188h-6.3945312v0.1123047
106 + c0,1.5488281,0.9726562,2.5644531,2.4355469,2.5644531c1.0322266,0,1.8417969-0.4902344,2.0908203-1.2734375H109.3848038z
107 + M103.1025772,25.1347656h4.5263672c-0.0429688-1.3857422-0.9296875-2.2978516-2.2207031-2.2978516
108 + C104.1260147,22.8369141,103.1973038,23.7666016,103.1025772,25.1347656z"/>
109 + </g>
110 + </g>
111 + </g>
112 + <g id="XMLID_1_">
113 + <g>
114 + <path fill="#FFFFFF" d="M35.6714287,14.6977539V8.730957h3.7265625v0.8432617h-2.7998047V11.40625h2.5634766v0.8188477
115 + h-2.5634766v2.4726562H35.6714287z"/>
116 + <path fill="#FFFFFF" d="M40.3960381,12.4443359c0-1.4516602,0.8105469-2.3364258,2.125-2.3364258
117 + c1.3115234,0,2.1220703,0.8847656,2.1220703,2.3364258c0,1.4594727-0.8066406,2.340332-2.1220703,2.340332
118 + C41.2026787,14.784668,40.3960381,13.9038086,40.3960381,12.4443359z M41.0864677,9.0244141
119 + c0-0.3144531,0.2402344-0.5581055,0.5537109-0.5581055c0.3105469,0,0.5507812,0.2436523,0.5507812,0.5581055
120 + c0,0.3139648-0.2402344,0.5541992-0.5507812,0.5541992C41.3267021,9.5786133,41.0864677,9.3383789,41.0864677,9.0244141z
121 + M43.7290459,12.4443359c0-0.9760742-0.4384766-1.546875-1.2080078-1.546875
122 + c-0.7724609,0-1.2070312,0.5708008-1.2070312,1.546875c0,0.9838867,0.4345703,1.550293,1.2070312,1.550293
123 + C43.2905693,13.9946289,43.7290459,13.4243164,43.7290459,12.4443359z M42.8520927,9.0244141
124 + c0-0.3144531,0.2441406-0.5581055,0.5546875-0.5581055c0.3095703,0,0.5498047,0.2436523,0.5498047,0.5581055
125 + c0,0.3139648-0.2402344,0.5541992-0.5498047,0.5541992C43.0962334,9.5786133,42.8520927,9.3383789,42.8520927,9.0244141z"/>
126 + <path fill="#FFFFFF" d="M45.8354912,10.1948242h0.8554688v0.6904297h0.0664062
127 + c0.1279297-0.4423828,0.6279297-0.7651367,1.2158203-0.7651367c0.1318359,0,0.3017578,0.012207,0.3964844,0.0371094v0.8769531
128 + c-0.0742188-0.0249023-0.3388672-0.0537109-0.4960938-0.0537109c-0.6738281,0-1.1494141,0.4257812-1.1494141,1.0585938v2.6586914
129 + h-0.8886719V10.1948242z"/>
130 + <path fill="#FFFFFF" d="M50.3149834,13.9863281h-0.0664062v0.7114258h-0.8554688V8.4370117h0.8886719v2.4731445h0.0703125
131 + c0.2402344-0.4921875,0.7695312-0.7900391,1.3857422-0.7900391c1.140625,0,1.8603516,0.8930664,1.8603516,2.3242188
132 + c0,1.4262695-0.7236328,2.3276367-1.8603516,2.3276367C51.1089287,14.7719727,50.5835381,14.4829102,50.3149834,13.9863281z
133 + M50.2651787,12.4482422c0.0048828,0.9428711,0.4677734,1.5258789,1.2158203,1.5258789
134 + c0.7529297,0,1.203125-0.574707,1.203125-1.5297852c0-0.9472656-0.4501953-1.5258789-1.203125-1.5258789
135 + C50.7368584,10.918457,50.2700615,11.5053711,50.2651787,12.4482422z"/>
136 + <path fill="#FFFFFF" d="M58.5991631,13.4819336c-0.2021484,0.8066406-0.921875,1.3027344-1.9511719,1.3027344
137 + c-1.2900391,0-2.0800781-0.8847656-2.0800781-2.3242188c0-1.4389648,0.8066406-2.3525391,2.0761719-2.3525391
138 + c1.2529297,0,2.0087891,0.855957,2.0087891,2.2700195v0.3100586h-3.1796875v0.0498047
139 + c0.0292969,0.7895508,0.4882812,1.2900391,1.1992188,1.2900391c0.5380859,0,0.90625-0.1943359,1.0712891-0.5458984H58.5991631z
140 + M55.4731865,12.0307617h2.2744141c-0.0205078-0.7070312-0.4501953-1.1665039-1.1083984-1.1665039
141 + C55.9819756,10.8642578,55.5229912,11.3276367,55.4731865,12.0307617z"/>
142 + <path fill="#FFFFFF" d="M61.4917412,10.1079102c1.0126953,0,1.6748047,0.4711914,1.7617188,1.2651367h-0.8525391
143 + c-0.0820312-0.3305664-0.4052734-0.5415039-0.9091797-0.5415039c-0.4960938,0-0.8730469,0.2353516-0.8730469,0.5869141
144 + c0,0.269043,0.2275391,0.4384766,0.7158203,0.550293l0.7480469,0.1733398
145 + c0.8564453,0.1987305,1.2578125,0.5668945,1.2578125,1.2285156c0,0.8476562-0.7900391,1.4140625-1.8652344,1.4140625
146 + c-1.0712891,0-1.7695312-0.4838867-1.8486328-1.2817383h0.8896484c0.1113281,0.347168,0.4423828,0.5620117,0.9794922,0.5620117
147 + c0.5537109,0,0.9472656-0.2480469,0.9472656-0.6079102c0-0.2685547-0.2109375-0.4423828-0.6621094-0.5498047
148 + l-0.7851562-0.1821289c-0.8564453-0.2026367-1.2529297-0.5869141-1.2529297-1.2568359
149 + C59.7427177,10.6699219,60.4741631,10.1079102,61.4917412,10.1079102z"/>
150 + <path fill="#FFFFFF" d="M65.708046,9.0737305v1.1416016h0.9755859v0.7485352H65.708046v2.3154297
151 + c0,0.4716797,0.1943359,0.6782227,0.6367188,0.6782227c0.1367188,0,0.2148438-0.0083008,0.3388672-0.0205078v0.7402344
152 + c-0.1445312,0.0244141-0.3095703,0.0454102-0.4833984,0.0454102c-0.9882812,0-1.3823242-0.3476562-1.3823242-1.2158203
153 + v-2.5429688h-0.7148438V10.215332h0.7148438V9.0737305H65.708046z"/>
154 + <path fill="#FFFFFF" d="M67.5996475,13.4243164c0-0.8105469,0.6035156-1.277832,1.6748047-1.3442383l1.2197266-0.0703125
155 + v-0.3886719c0-0.4755859-0.3144531-0.7441406-0.921875-0.7441406c-0.4960938,0-0.8398438,0.1821289-0.9384766,0.5004883
156 + h-0.8603516c0.0908203-0.7734375,0.8183594-1.2695312,1.8398438-1.2695312c1.1289062,0,1.765625,0.5620117,1.765625,1.5131836
157 + v3.0766602h-0.8554688v-0.6328125h-0.0703125c-0.2685547,0.4506836-0.7607422,0.7070312-1.3525391,0.7070312
158 + C68.23246,14.7719727,67.5996475,14.2470703,67.5996475,13.4243164z M68.1787491,9.0244141
159 + c0-0.3144531,0.2392578-0.5581055,0.5537109-0.5581055c0.3105469,0,0.5498047,0.2436523,0.5498047,0.5581055
160 + c0,0.3139648-0.2392578,0.5541992-0.5498047,0.5541992C68.4180069,9.5786133,68.1787491,9.3383789,68.1787491,9.0244141z
161 + M70.4941788,13.0395508v-0.3764648l-1.0996094,0.0703125c-0.6201172,0.0415039-0.9013672,0.2524414-0.9013672,0.6494141
162 + c0,0.4052734,0.3515625,0.6411133,0.8349609,0.6411133C69.998085,14.0239258,70.4941788,13.5976562,70.4941788,13.0395508z
163 + M69.9443741,9.0244141c0-0.3144531,0.2441406-0.5581055,0.5537109-0.5581055
164 + c0.3105469,0,0.5498047,0.2436523,0.5498047,0.5581055c0,0.3139648-0.2392578,0.5541992-0.5498047,0.5541992
165 + C70.1885147,9.5786133,69.9443741,9.3383789,69.9443741,9.0244141z"/>
166 + <path fill="#FFFFFF" d="M72.8115616,8.4370117h0.8886719v6.2607422h-0.8886719V8.4370117z"/>
167 + <path fill="#FFFFFF" d="M75.2002335,8.4370117h0.8886719v6.2607422h-0.8886719V8.4370117z"/>
168 + <path fill="#FFFFFF" d="M79.8750381,8.8632812c0-0.3105469,0.2441406-0.5458984,0.5751953-0.5458984
169 + c0.3300781,0,0.5742188,0.2353516,0.5742188,0.5458984c0,0.3056641-0.2441406,0.5415039-0.5742188,0.5415039
170 + C80.1191788,9.4047852,79.8750381,9.1689453,79.8750381,8.8632812z M80.0068741,10.1948242h0.8857422v4.5029297h-0.8857422
171 + V10.1948242z"/>
172 + </g>
173 + </g>
174 +</g>
175 +<g>
176 +</g>
177 +<g>
178 +</g>
179 +<g>
180 +</g>
181 +<g>
182 +</g>
183 +<g>
184 +</g>
185 +<g>
186 +</g>
187 +</svg>
backend/musik88-web/app/frontend/images/Pre-order_on_the_App_Store_Badge_US-UK_RGB_blk_121217.svg new
+192
@@ -0,0 +1,192 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<svg version="1.1" id="livetype" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4 + width="120.6600037px" height="41px" viewBox="0 0 120.6600037 41" enable-background="new 0 0 120.6600037 41"
5 + xml:space="preserve">
6 +<g>
7 + <g>
8 + <g>
9 + <g>
10 + <path fill="#A6A6A6" d="M110.1352768,0H9.5351868C9.1684866,0,8.8061771,0,8.4404564,0.001953
11 + C8.1343069,0.003906,7.8305969,0.009766,7.5215082,0.014648C6.8559866,0.03125,6.1821566,0.072266,5.5176067,0.191406
12 + C4.848177,0.308594,4.2261066,0.508789,3.6167269,0.818359C3.0180967,1.125,2.4712083,1.52344,1.9980768,1.99707
13 + c-0.47852,0.4736301-0.875,1.0224601-1.17822,1.6210901c-0.311039,0.6084001-0.508305,1.2334001-0.625004,1.9033201
14 + c-0.120606,0.6621099-0.162109,1.3320398-0.179199,2.0019598C0.0063758,7.83008,0.0053998,8.1377001,0.0005084,8.4443398
15 + c0,0.3622999,0,0.7255802,0,1.0917902v20.928669c0,0.3691998,0,0.7304993,0,1.0937996
16 + c0.0048914,0.3105011,0.0058674,0.6112995,0.0151454,0.9218998c0.01709,0.669899,0.058593,1.3398018,0.179199,2.0018997
17 + c0.116699,0.6699028,0.313965,1.2979012,0.625004,1.9043007c0.30322,0.5956993,0.6997,1.1445999,1.17822,1.6142998
18 + c0.4731315,0.4775009,1.0200199,0.875,1.6186501,1.1786995c0.6093798,0.3125,1.2314501,0.5098,1.9008799,0.6308022
19 + c0.6645498,0.1191978,1.3383799,0.1582985,2.0039015,0.1767998c0.3090887,0.0067978,0.6127987,0.0107002,0.9189482,0.0107002
20 + C8.8061771,40,9.1684866,40,9.5351868,40h100.60009c0.3593979,0,0.7246017,0,1.0839996-0.0019989
21 + c0.3047028,0,0.6172028-0.0039024,0.9218979-0.0107002c0.669899-0.0185013,1.3418045-0.0576019,2-0.1767998
22 + c0.669899-0.1210022,1.2929001-0.3183022,1.9082031-0.6308022c0.5976028-0.3036995,1.1445007-0.7011986,1.6172028-1.1786995
23 + c0.4764938-0.4696999,0.8730011-1.0186005,1.1815948-1.6142998c0.3066025-0.6063995,0.5059052-1.2343979,0.6191025-1.9043007
24 + c0.1231003-0.6620979,0.1621017-1.3320007,0.1856003-2.0018997c0.0038986-0.3106003,0.0038986-0.6113987,0.0038986-0.9218998
25 + c0.0078049-0.3633003,0.0078049-0.7245998,0.0078049-1.0937996V9.53613c0-0.36621,0-0.7294903-0.0078049-1.0917902
26 + c0-0.3066397,0-0.6142597-0.0038986-0.9208999c-0.0234985-0.66992-0.0625-1.3398499-0.1856003-2.0019598
27 + c-0.1131973-0.66992-0.3125-1.29492-0.6191025-1.9033201c-0.3085938-0.59863-0.705101-1.14746-1.1815948-1.6210901
28 + c-0.472702-0.47363-1.0195999-0.87207-1.6172028-1.1787109c-0.615303-0.30957-1.2383041-0.509765-1.9082031-0.626953
29 + c-0.6581955-0.11914-1.330101-0.160156-2-0.176758c-0.3046951-0.004882-0.6171951-0.010742-0.9218979-0.012695
30 + C110.8598785,0,110.4946747,0,110.1352768,0L110.1352768,0z"/>
31 + <path d="M8.4453363,39.125c-0.3046799,0-0.6020494-0.0038986-0.9042897-0.0107002
32 + c-0.5590796-0.0157013-1.2221699-0.0468979-1.8691397-0.1631012c-0.6103501-0.1103973-1.1528401-0.2901001-1.6567402-0.5478973
33 + c-0.5214901-0.2646027-0.9902401-0.6063995-1.39697-1.0166016c-0.4140699-0.4062004-0.7543999-0.8730011-1.02051-1.3964996
34 + c-0.25928-0.5047989-0.43653-1.0467987-0.5429784-1.6571999c-0.1220616-0.672802-0.1533116-1.3554993-0.1665-1.875
35 + c-0.0063416-0.2108994-0.0146416-0.9130993-0.0146416-0.9130993V8.4443398c0,0,0.00879-0.6914096,0.0146416-0.8945398
36 + C0.9013968,7.0258799,0.9326468,6.34375,1.0537368,5.6777301c0.1069399-0.61377,0.2841716-1.1552701,0.54346-1.6621003
37 + C1.8628083,3.49121,2.2036467,3.02197,2.6123369,2.6176801C3.0259068,2.2036099,3.4966068,1.86084,4.0146766,1.59521
38 + C4.5283465,1.33447,5.0698566,1.15625,5.6679969,1.05127c0.6733398-0.120606,1.3559599-0.150879,1.8754897-0.164063
39 + L8.4458265,0.875h102.7685547l0.9131012,0.012695c0.5126953,0.012696,1.1952972,0.042969,1.8583984,0.162595
40 + c0.6025009,0.1054701,1.1474991,0.28467,1.6708984,0.54785c0.5127029,0.2627,0.982399,0.6054699,1.3916016,1.01563
41 + c0.4092026,0.40625,0.7518997,0.8779299,1.0233994,1.4043002c0.2578049,0.51123,0.4336014,1.0527296,0.535202,1.6489196
42 + c0.1161957,0.6308603,0.152298,1.27881,0.1737976,1.8872104c0.0028992,0.2831998,0.0028992,0.5873995,0.0028992,0.8901396
43 + c0.0079041,0.375,0.0079041,0.7319298,0.0079041,1.0917902v20.928669c0,0.3633003,0,0.7178001-0.0079041,1.075201
44 + c0,0.3251991,0,0.6231003-0.0038986,0.9296989c-0.0205002,0.5889015-0.0566025,1.2364006-0.1708984,1.8535004
45 + c-0.1035004,0.6133003-0.2792969,1.1553001-0.5400009,1.6699982c-0.2695007,0.5195007-0.6122971,0.9892006-1.0156021,1.3857002
46 + c-0.4131012,0.4180031-0.881897,0.7588005-1.3993988,1.0225029c-0.5186005,0.2635994-1.0478973,0.4384003-1.6679993,0.5497971
47 + c-0.6406021,0.1162033-1.3037033,0.1473999-1.8692017,0.1631012c-0.2929001,0.0068016-0.5996017,0.0107002-0.8973999,0.0107002
48 + l-1.0839996,0.0019989L8.4453363,39.125z"/>
49 + </g>
50 + </g>
51 + <g id="XMLID_20_">
52 + <g id="XMLID_22_">
53 + <g id="XMLID_23_">
54 + <path id="XMLID_25_" fill="#FFFFFF" d="M24.7693844,20.3006763c-0.0250092-2.7508278,2.2523365-4.0894947,2.3565598-4.1520615
55 + c-1.289587-1.880518-3.2889233-2.137701-3.9911633-2.1576748c-1.6792431-0.1762638-3.307188,1.0048323-4.1629009,1.0048323
56 + c-0.8722649,0-2.1897697-0.9873343-3.6085024-0.9581413c-1.8263159,0.0283384-3.5356064,1.0857515-4.4729204,2.7278662
57 + c-1.9339523,3.3484154-0.4914055,8.2694664,1.3612013,10.976078c0.9269009,1.3253498,2.0101767,2.8057976,3.4276285,2.7533016
58 + c1.3870659-0.0575314,1.9051018-0.8844776,3.5793953-0.8844776c1.6587582,0,2.1447868,0.8844776,3.5910034,0.8511028
59 + c1.4883862-0.0241566,2.4261265-1.3312416,3.3205109-2.6691399c1.0711498-1.5195389,1.5012684-3.0158634,1.518425-3.092514
60 + C27.6539669,24.6881542,24.7977238,23.598732,24.7693844,20.3006763z"/>
61 + <path id="XMLID_24_" fill="#FFFFFF" d="M22.0377636,12.2108879c0.7456856-0.933217,1.2562103-2.2023182,1.1145172-3.4906235
62 + c-1.0798626,0.0478859-2.4303093,0.7464542-3.2075768,1.6596127c-0.6877289,0.8039846-1.302475,2.1223364-1.1437111,3.3613911
63 + C20.0138435,13.8313208,21.2592144,13.1294231,22.0377636,12.2108879z"/>
64 + </g>
65 + </g>
66 + <g>
67 + <path fill="#FFFFFF" d="M42.3027763,27.1396484h-4.7333984l-1.1367188,3.3564453h-2.0048828l4.4833984-12.4179688h2.0830078
68 + l4.4833984,12.4179688h-2.0390739L42.3027763,27.1396484z M38.0596123,25.5908203h3.7519531l-1.8496094-5.4472656h-0.0517578
69 + L38.0596123,25.5908203z"/>
70 + <path fill="#FFFFFF" d="M55.1601982,25.9697266c0,2.8134766-1.5058594,4.6210938-3.7783203,4.6210938
71 + c-1.2900391,0-2.3144531-0.5771484-2.8486328-1.5839844h-0.0429688v4.484375h-1.8583984V21.4423828h1.7988281v1.5058594
72 + h0.0341797c0.5166016-0.9716797,1.6181641-1.6005859,2.8828125-1.6005859
73 + C53.6455498,21.3476562,55.1601982,23.1640625,55.1601982,25.9697266z M53.250042,25.9697266
74 + c0-1.8330078-0.9472656-3.0380859-2.3925781-3.0380859c-1.4199219,0-2.375,1.2304688-2.375,3.0380859
75 + c0,1.8242188,0.9550781,3.0458984,2.375,3.0458984C52.3027763,29.015625,53.250042,27.8193359,53.250042,25.9697266z"/>
76 + <path fill="#FFFFFF" d="M65.1250381,25.9697266c0,2.8134766-1.5058556,4.6210938-3.7783165,4.6210938
77 + c-1.2900391,0-2.3144531-0.5771484-2.8486328-1.5839844h-0.0429688v4.484375h-1.8583984V21.4423828h1.7988281v1.5058594
78 + h0.0341797c0.5166016-0.9716797,1.6181641-1.6005859,2.8828125-1.6005859
79 + C63.6103935,21.3476562,65.1250381,23.1640625,65.1250381,25.9697266z M63.2148857,25.9697266
80 + c0-1.8330078-0.9472656-3.0380859-2.3925781-3.0380859c-1.4199219,0-2.375,1.2304688-2.375,3.0380859
81 + c0,1.8242188,0.9550781,3.0458984,2.375,3.0458984C62.2676201,29.015625,63.2148857,27.8193359,63.2148857,25.9697266z"/>
82 + <path fill="#FFFFFF" d="M71.7109756,27.0361328c0.1376953,1.2314453,1.3339844,2.0400391,2.96875,2.0400391
83 + c1.5664062,0,2.6933594-0.8085938,2.6933594-1.9189453c0-0.9638672-0.6796875-1.5410156-2.2890625-1.9365234
84 + l-1.609375-0.3876953c-2.2802734-0.5507812-3.3388672-1.6171875-3.3388672-3.3476562
85 + c0-2.1425781,1.8671875-3.6142578,4.5185547-3.6142578c2.6240234,0,4.4228516,1.4716797,4.4833984,3.6142578h-1.8759766
86 + c-0.1123047-1.2392578-1.1367188-1.9873047-2.6337891-1.9873047s-2.5214844,0.7568359-2.5214844,1.8583984
87 + c0,0.8779297,0.6542969,1.3945312,2.2548828,1.7900391l1.3681641,0.3359375
88 + c2.5478516,0.6025391,3.6064453,1.6259766,3.6064453,3.4423828c0,2.3232422-1.8505859,3.7783203-4.7939453,3.7783203
89 + c-2.7539062,0-4.6132812-1.4208984-4.7333984-3.6669922H71.7109756z"/>
90 + <path fill="#FFFFFF" d="M83.3467178,19.2998047v2.1425781h1.7216797v1.4716797h-1.7216797v4.9912109
91 + c0,0.7753906,0.3447266,1.1367188,1.1015625,1.1367188c0.1894531,0,0.4912109-0.0263672,0.6113281-0.0429688v1.4628906
92 + c-0.2060547,0.0517578-0.6191406,0.0859375-1.0322266,0.0859375c-1.8330078,0-2.5478516-0.6884766-2.5478516-2.4443359
93 + v-5.1894531h-1.3164062v-1.4716797h1.3164062v-2.1425781H83.3467178z"/>
94 + <path fill="#FFFFFF" d="M86.0654678,25.9697266c0-2.8486328,1.6777344-4.6386719,4.2939453-4.6386719
95 + c2.625,0,4.2949219,1.7900391,4.2949219,4.6386719c0,2.8564453-1.6611328,4.6386719-4.2949219,4.6386719
96 + C87.7266006,30.6083984,86.0654678,28.8261719,86.0654678,25.9697266z M92.7607803,25.9697266
97 + c0-1.9541016-0.8955078-3.1074219-2.4013672-3.1074219s-2.4003906,1.1621094-2.4003906,3.1074219
98 + c0,1.9619141,0.8945312,3.1064453,2.4003906,3.1064453S92.7607803,27.9316406,92.7607803,25.9697266z"/>
99 + <path fill="#FFFFFF" d="M96.1865616,21.4423828h1.7724609v1.5410156h0.0429688
100 + c0.2841797-1.0244141,1.1103516-1.6357422,2.1777344-1.6357422c0.2666016,0,0.4902344,0.0351562,0.6367188,0.0693359v1.7382812
101 + c-0.1464844-0.0605469-0.4736328-0.1123047-0.8349609-0.1123047c-1.1962891,0-1.9365234,0.8095703-1.9365234,2.0830078
102 + v5.3701172h-1.8583984V21.4423828z"/>
103 + <path fill="#FFFFFF" d="M109.3848038,27.8369141c-0.25,1.6435547-1.8505859,2.7714844-3.8984375,2.7714844
104 + c-2.6337891,0-4.2685547-1.7646484-4.2685547-4.5957031c0-2.8398438,1.6435547-4.6816406,4.1904297-4.6816406
105 + c2.5048828,0,4.0800781,1.7207031,4.0800781,4.4658203v0.6367188h-6.3945312v0.1123047
106 + c0,1.5488281,0.9726562,2.5644531,2.4355469,2.5644531c1.0322266,0,1.8417969-0.4902344,2.0908203-1.2734375H109.3848038z
107 + M103.1025772,25.1347656h4.5263672c-0.0429688-1.3857422-0.9296875-2.2978516-2.2207031-2.2978516
108 + C104.1260147,22.8369141,103.1973038,23.7666016,103.1025772,25.1347656z"/>
109 + </g>
110 + </g>
111 + </g>
112 + <g id="XMLID_1_">
113 + <g>
114 + <path fill="#FFFFFF" d="M38.0083427,8.730957c1.1660156,0,1.9677734,0.8061523,1.9677734,1.9887695
115 + c0,1.1660156-0.8261719,1.9682617-1.9970703,1.9682617h-1.3808594v2.0097656h-0.9267578V8.730957H38.0083427z
116 + M36.5981865,11.8735352h1.1660156c0.7978516,0,1.265625-0.4135742,1.265625-1.1538086
117 + c0-0.7568359-0.4511719-1.1704102-1.265625-1.1704102h-1.1660156V11.8735352z"/>
118 + <path fill="#FFFFFF" d="M41.1899834,10.1948242h0.8554688v0.6904297h0.0664062
119 + c0.1279297-0.4423828,0.6279297-0.7651367,1.2158203-0.7651367c0.1318359,0,0.3017578,0.012207,0.3964844,0.0371094v0.8769531
120 + c-0.0742188-0.0249023-0.3388672-0.0537109-0.4960938-0.0537109c-0.6738281,0-1.1494141,0.4257812-1.1494141,1.0585938v2.6586914
121 + h-0.8886719V10.1948242z"/>
122 + <path fill="#FFFFFF" d="M48.3979912,13.4819336c-0.2021484,0.8066406-0.921875,1.3027344-1.9511719,1.3027344
123 + c-1.2900391,0-2.0800781-0.8847656-2.0800781-2.3242188c0-1.4389648,0.8066406-2.3525391,2.0761719-2.3525391
124 + c1.2529297,0,2.0087891,0.855957,2.0087891,2.2700195v0.3100586h-3.1796875v0.0498047
125 + c0.0292969,0.7895508,0.4882812,1.2900391,1.1992188,1.2900391c0.5380859,0,0.90625-0.1943359,1.0712891-0.5458984H48.3979912z
126 + M45.2720146,12.0307617h2.2744141c-0.0205078-0.7070312-0.4501953-1.1665039-1.1083984-1.1665039
127 + C45.7808037,10.8642578,45.3218193,11.3276367,45.2720146,12.0307617z"/>
128 + <path fill="#FFFFFF" d="M49.6606865,12.7294922v-0.8725586h2.7128906v0.8725586H49.6606865z"/>
129 + <path fill="#FFFFFF" d="M53.5786552,12.4443359c0-1.4516602,0.8105469-2.3364258,2.125-2.3364258
130 + c1.3115234,0,2.1220703,0.8847656,2.1220703,2.3364258c0,1.4594727-0.8066406,2.340332-2.1220703,2.340332
131 + C54.3852959,14.784668,53.5786552,13.9038086,53.5786552,12.4443359z M56.9116631,12.4443359
132 + c0-0.9760742-0.4384766-1.546875-1.2080078-1.546875c-0.7724609,0-1.2070312,0.5708008-1.2070312,1.546875
133 + c0,0.9838867,0.4345703,1.550293,1.2070312,1.550293C56.4731865,13.9946289,56.9116631,13.4243164,56.9116631,12.4443359z"/>
134 + <path fill="#FFFFFF" d="M59.0181084,10.1948242h0.8554688v0.6904297h0.0664062
135 + c0.1279297-0.4423828,0.6279297-0.7651367,1.2158203-0.7651367c0.1318359,0,0.3017578,0.012207,0.3964844,0.0371094v0.8769531
136 + c-0.0742188-0.0249023-0.3388672-0.0537109-0.4960938-0.0537109c-0.6738281,0-1.1494141,0.4257812-1.1494141,1.0585938v2.6586914
137 + h-0.8886719V10.1948242z"/>
138 + <path fill="#FFFFFF" d="M62.1958427,12.4443359c0-1.4228516,0.7314453-2.3242188,1.8691368-2.3242188
139 + c0.6162109,0,1.137207,0.293457,1.3813477,0.7900391h0.0664062V8.4370117h0.8886719v6.2607422h-0.8515625v-0.7114258h-0.0703125
140 + c-0.2685547,0.4921875-0.7944336,0.7856445-1.4145508,0.7856445
141 + C62.9194756,14.7719727,62.1958427,13.8706055,62.1958427,12.4443359z M63.1138115,12.4443359
142 + c0,0.9550781,0.4501953,1.5297852,1.2031212,1.5297852c0.7495117,0,1.2124023-0.5830078,1.2124023-1.5258789
143 + c0-0.9384766-0.4677734-1.5297852-1.2124023-1.5297852C63.5688896,10.918457,63.1138115,11.4970703,63.1138115,12.4443359z"/>
144 + <path fill="#FFFFFF" d="M71.6670303,13.4819336c-0.2021484,0.8066406-0.921875,1.3027344-1.9511719,1.3027344
145 + c-1.2900391,0-2.0800781-0.8847656-2.0800781-2.3242188c0-1.4389648,0.8066406-2.3525391,2.0761719-2.3525391
146 + c1.2529297,0,2.0087891,0.855957,2.0087891,2.2700195v0.3100586h-3.1796875v0.0498047
147 + c0.0292969,0.7895508,0.4882812,1.2900391,1.1992188,1.2900391c0.5380859,0,0.90625-0.1943359,1.0712891-0.5458984H71.6670303z
148 + M68.5410538,12.0307617h2.2744141c-0.0205078-0.7070312-0.4501953-1.1665039-1.1083984-1.1665039
149 + C69.0498428,10.8642578,68.5908585,11.3276367,68.5410538,12.0307617z"/>
150 + <path fill="#FFFFFF" d="M72.9141006,10.1948242h0.8554688v0.6904297h0.0664062
151 + c0.1279297-0.4423828,0.6279297-0.7651367,1.2158203-0.7651367c0.1318359,0,0.3017578,0.012207,0.3964844,0.0371094v0.8769531
152 + c-0.0742188-0.0249023-0.3388672-0.0537109-0.4960938-0.0537109c-0.6738281,0-1.1494141,0.4257812-1.1494141,1.0585938v2.6586914
153 + h-0.8886719V10.1948242z"/>
154 + <path fill="#FFFFFF" d="M78.6494522,12.4443359c0-1.4516602,0.8105469-2.3364258,2.125-2.3364258
155 + c1.3115234,0,2.1220703,0.8847656,2.1220703,2.3364258c0,1.4594727-0.8066406,2.340332-2.1220703,2.340332
156 + C79.4560928,14.784668,78.6494522,13.9038086,78.6494522,12.4443359z M81.98246,12.4443359
157 + c0-0.9760742-0.4384766-1.546875-1.2080078-1.546875c-0.7724609,0-1.2070312,0.5708008-1.2070312,1.546875
158 + c0,0.9838867,0.4345703,1.550293,1.2070312,1.550293C81.5439835,13.9946289,81.98246,13.4243164,81.98246,12.4443359z"/>
159 + <path fill="#FFFFFF" d="M84.0898819,10.1948242h0.8554688v0.715332h0.0664062
160 + c0.21875-0.5004883,0.6660156-0.8022461,1.34375-0.8022461c1.0048828,0,1.5585938,0.6035156,1.5585938,1.6748047v2.9150391
161 + h-0.8886719v-2.6918945c0-0.7236328-0.3144531-1.0834961-0.9716797-1.0834961s-1.0751953,0.4384766-1.0751953,1.1411133
162 + v2.6342773h-0.8886719V10.1948242z"/>
163 + <path fill="#FFFFFF" d="M92.9346085,9.0737305v1.1416016h0.9755859v0.7485352h-0.9755859v2.3154297
164 + c0,0.4716797,0.1943359,0.6782227,0.6367188,0.6782227c0.1367188,0,0.2148438-0.0083008,0.3388672-0.0205078v0.7402344
165 + c-0.1445312,0.0244141-0.3095703,0.0454102-0.4833984,0.0454102c-0.9882812,0-1.3818359-0.3476562-1.3818359-1.2158203
166 + v-2.5429688h-0.7148438V10.215332H92.04496V9.0737305H92.9346085z"/>
167 + <path fill="#FFFFFF" d="M95.1240616,8.4370117h0.8808594v2.4814453h0.0703125
168 + c0.21875-0.5048828,0.6943359-0.8066406,1.3730469-0.8066406c0.9589844,0,1.5507812,0.6079102,1.5507812,1.6787109v2.9072266
169 + h-0.8896484v-2.6879883c0-0.7192383-0.3349609-1.0834961-0.9628906-1.0834961
170 + c-0.7285156,0-1.1337891,0.4594727-1.1337891,1.1416016v2.6298828h-0.8886719V8.4370117z"/>
171 + <path fill="#FFFFFF" d="M104.1807022,13.4819336c-0.2021484,0.8066406-0.921875,1.3027344-1.9511719,1.3027344
172 + c-1.2900391,0-2.0800781-0.8847656-2.0800781-2.3242188c0-1.4389648,0.8066406-2.3525391,2.0761719-2.3525391
173 + c1.2529297,0,2.0087891,0.855957,2.0087891,2.2700195v0.3100586h-3.1796875v0.0498047
174 + c0.0292969,0.7895508,0.4882812,1.2900391,1.1992188,1.2900391c0.5380859,0,0.90625-0.1943359,1.0712891-0.5458984H104.1807022z
175 + M101.0547256,12.0307617h2.2744141c-0.0205078-0.7070312-0.4501953-1.1665039-1.1083984-1.1665039
176 + C101.5635147,10.8642578,101.1045303,11.3276367,101.0547256,12.0307617z"/>
177 + </g>
178 + </g>
179 +</g>
180 +<g>
181 +</g>
182 +<g>
183 +</g>
184 +<g>
185 +</g>
186 +<g>
187 +</g>
188 +<g>
189 +</g>
190 +<g>
191 +</g>
192 +</svg>
backend/musik88-web/app/frontend/images/album-tracks.svg new
+12
@@ -0,0 +1,12 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve">
6 +<rect fill="#FFFFFF" width="3" height="3"/>
7 +<rect y="6" fill="#FFFFFF" width="3" height="3"/>
8 +<rect y="12" fill="#FFFFFF" width="3" height="3"/>
9 +<rect x="6" y="12" fill="#FFFFFF" width="14" height="3"/>
10 +<rect x="6" y="5.906" fill="#FFFFFF" width="14" height="3"/>
11 +<rect x="6" fill="#FFFFFF" width="14" height="3"/>
12 +</svg>
backend/musik88-web/app/frontend/images/arrow-left.svg new
+10
@@ -0,0 +1,10 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="30px" height="30px" viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
6 +<polygon fill="#FFFFFF" points="14.691,15.688 59.403,59.393 60.107,58.689 15.396,14.984 "/>
7 +<polygon fill="#FFFFFF" points="60.107,-28.72 15.396,14.984 14.691,14.281 59.403,-29.425 "/>
8 +<rect x="14.191" y="14.484" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 14.4842 35.9683)" fill="#FFFFFF" width="1" height="1"/>
9 +<rect x="29" display="none" fill="#27499F" width="1" height="1"/>
10 +</svg>
backend/musik88-web/app/frontend/images/arrow-right.svg new
+9
@@ -0,0 +1,9 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="30px" height="30px" viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
6 +<rect x="-38.114" y="36.707" transform="matrix(0.7072 -0.707 0.707 0.7072 -28.4162 5.7984)" fill="#FFFFFF" width="61.812" height="0.995"/>
7 +<rect x="-38.114" y="-7.702" transform="matrix(-0.7072 -0.7071 0.7071 -0.7072 -7.2116 -17.395)" fill="#FFFFFF" width="61.812" height="0.996"/>
8 +<rect x="14.5" y="14.5" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -6.2132 15)" fill="#FFFFFF" width="1" height="1"/>
9 +</svg>
backend/musik88-web/app/frontend/images/badge-pretty.gif
Binary files /dev/null and b/backend/musik88-web/app/frontend/images/badge-pretty.gif differ
backend/musik88-web/app/frontend/images/badge-pretty.jpeg
Binary files /dev/null and b/backend/musik88-web/app/frontend/images/badge-pretty.jpeg differ
backend/musik88-web/app/frontend/images/badge-pretty.svg new
+23
@@ -0,0 +1,23 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400" width="400" height="400">
2 + <style>
3 +</style>
4 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400" width="380" height="380" x="10" y="10" opacity="1">
5 +<g>
6 +<polygon fill="#212457" points="342.6,0 324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 57.3,308.3 200,400 342.6,308.3 "/>
7 +<polygon fill="#818657" points="342.6,0 324.1,19.8 75.8,19.8 75.8,294.2 57.3,308.3 57.3,0 "/>
8 +<polygon fill="#8cb2a8" points="324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 75.8,19.8 "/>
9 +</g>
10 +</svg>
11 + <foreignObject width="380" height="48" style="font-size: 30px;color: rgb(102, 102, 102);font-family: &quot;Arial&quot;;font-weight: 400;text-align: center;letter-spacing: 0em;line-height: 1.5;" x="10" y="176">
12 + <div xmlns="http://www.w3.org/1999/xhtml">@</div>
13 + </foreignObject>
14 + <svg viewBox="0 0 200 200" width="200" height="200" style="font-size: 30px;font-family: &quot;Arial&quot;;font-weight: 400;" x="100" y="100">
15 + <defs>
16 + <path id="2b36fc3d-f9b7-47e1-aca9-884911f519cc_outer" d="M100,175c-41.4,0-75-33.6-75-75s33.6-75,75-75s75,33.6,75,75S141.4,175,100,175z" stroke-dasharray="5, 5" style="stroke: #737373;fill: none;"/>
17 + <path id="2b36fc3d-f9b7-47e1-aca9-884911f519cc_inner" d="M100,10c-49.6,0-90,40.4-90,90s40.4,90,90,90s90-40.4,90-90S149.6,10,100,10z" stroke-dasharray="5, 5" style="stroke: #737373;fill: none;"/>
18 + </defs>
19 + <text fill="rgb(102, 102, 102)" text-anchor="middle" letter-spacing="0">
20 + <textPath xlink:href="#2b36fc3d-f9b7-47e1-aca9-884911f519cc_outer" startOffset="50%">You're pretty!</textPath>
21 + </text>
22 + </svg>
23 +</svg>
backend/musik88-web/app/frontend/images/badge-spotify-exporter.gif
Binary files /dev/null and b/backend/musik88-web/app/frontend/images/badge-spotify-exporter.gif differ
backend/musik88-web/app/frontend/images/badge-youtube-exporter.gif
Binary files /dev/null and b/backend/musik88-web/app/frontend/images/badge-youtube-exporter.gif differ
backend/musik88-web/app/frontend/images/close-button.svg new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="7px" height="7px" viewBox="0 0 7 7" enable-background="new 0 0 7 7" xml:space="preserve">
6 +<rect x="-3.969" y="3" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -1.4497 3.5)" fill="#FFFFFF" width="14.938" height="1"/>
7 +<rect x="-3.969" y="3" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 3.5 8.4497)" fill="#FFFFFF" width="14.938" height="1"/>
8 +</svg>
backend/musik88-web/app/frontend/images/favicon.svg new
+2
@@ -0,0 +1,2 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="500px" height="500px"><svg style="" width="500px" height="500px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face {font-family: "montserratbold";src: url("https://cdn.oxro.io/fonts/montserrat-bold-webfont.woff2") format("woff2"),url("https://cdn.oxro.io/fonts/montserrat-bold-webfont.woff") format("woff");font-weight: normal;font-style: normal;}</style></defs><rect x="0" y="0" width="500" height="500" rx="0" style="fill:#FFCC00"></rect><text x="50%" y="50%" dy=".1em" fill="#fff" text-anchor="middle" dominant-baseline="middle" style="font-family: &quot;Montserrat&quot;, sans-serif; font-size: 250px; line-height: 1" svgjs:data="{&quot;leading&quot;:&quot;1.3&quot;}">88</text></svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
2 +</style></svg>
\ No newline at end of file
backend/musik88-web/app/frontend/images/fire.gif
Binary files /dev/null and b/backend/musik88-web/app/frontend/images/fire.gif differ
backend/musik88-web/app/frontend/images/image-placeholder-0.jpg new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:647c653d22ef9260b693b7ce49b389b7765195b1dfacbf03f7e3b17aea4d0571
3 +size 32346
backend/musik88-web/app/frontend/images/image-placeholder-1.jpg new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:1aadb3e8f0f03b35ef509de800a29b3b6e4408cc8353c204101e75fbb0b3d0c8
3 +size 4068
backend/musik88-web/app/frontend/images/image-placeholder-2.jpg new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:b207bfc50434ff10f4d5f2b9b0c3d7892c06ff72083026e3356dd7b6c3a04b45
3 +size 102644
backend/musik88-web/app/frontend/images/image-placeholder-3.jpg new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:dd015a5ca5ee48717b88235417d66a229377541e239303df699940cd46ce5bbd
3 +size 3214
backend/musik88-web/app/frontend/images/phantom-icon-purple.svg new
+14
@@ -0,0 +1,14 @@
1 +<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<circle cx="64" cy="64" r="64" fill="url(#paint0_linear)"/>
3 +<path d="M110.584 64.9142H99.142C99.142 41.7651 80.173 23 56.7724 23C33.6612 23 14.8716 41.3057 14.4118 64.0583C13.936 87.577 36.241 108 60.0186 108H63.0094C83.9723 108 112.069 91.7667 116.459 71.9874C117.27 68.3413 114.358 64.9142 110.584 64.9142ZM39.7689 65.9454C39.7689 69.0411 37.2095 71.5729 34.0802 71.5729C30.9509 71.5729 28.3916 69.0399 28.3916 65.9454V56.8414C28.3916 53.7457 30.9509 51.2139 34.0802 51.2139C37.2095 51.2139 39.7689 53.7457 39.7689 56.8414V65.9454ZM59.5224 65.9454C59.5224 69.0411 56.9631 71.5729 53.8338 71.5729C50.7045 71.5729 48.1451 69.0399 48.1451 65.9454V56.8414C48.1451 53.7457 50.7056 51.2139 53.8338 51.2139C56.9631 51.2139 59.5224 53.7457 59.5224 56.8414V65.9454Z" fill="url(#paint1_linear)"/>
4 +<defs>
5 +<linearGradient id="paint0_linear" x1="64" y1="0" x2="64" y2="128" gradientUnits="userSpaceOnUse">
6 +<stop stop-color="#534BB1"/>
7 +<stop offset="1" stop-color="#551BF9"/>
8 +</linearGradient>
9 +<linearGradient id="paint1_linear" x1="65.4998" y1="23" x2="65.4998" y2="108" gradientUnits="userSpaceOnUse">
10 +<stop stop-color="white"/>
11 +<stop offset="1" stop-color="white" stop-opacity="0.82"/>
12 +</linearGradient>
13 +</defs>
14 +</svg>
backend/musik88-web/app/frontend/images/plus-test.svg new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="30px" height="30px" viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
6 +<rect x="14.5" fill="#FFFFFF" width="1" height="30"/>
7 +<rect y="14.844" fill="#FFFFFF" width="30" height="1"/>
8 +</svg>
backend/musik88-web/app/frontend/images/plus.svg new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="31px" height="31px" viewBox="0 0 31 31" enable-background="new 0 0 31 31" xml:space="preserve">
6 +<rect x="15" y="-12.562" fill="#FFFFFF" width="1" height="56.125"/>
7 +<rect x="-6.375" y="15" fill="#FFFFFF" width="43.75" height="1"/>
8 +</svg>
backend/musik88-web/app/frontend/images/splitfire.jpeg
Binary files /dev/null and b/backend/musik88-web/app/frontend/images/splitfire.jpeg differ
backend/musik88-web/app/frontend/images/zoom.svg new
+9
@@ -0,0 +1,9 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4 +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5 + width="31px" height="31px" viewBox="0 0 31 31" enable-background="new 0 0 31 31" xml:space="preserve">
6 +<path fill="#FFFFFF" d="M31,30.293L20.823,20.115c1.967-2.138,3.178-4.981,3.178-8.115c0-6.628-5.373-12-12-12C5.373,0,0,5.373,0,12
7 + c0,6.627,5.373,12,12,12c3.134,0,5.978-1.211,8.116-3.179L30.293,31L31,30.293z M1.001,12.001c0-6.075,4.925-11,11-11
8 + c6.076,0,11,4.925,11,11C23,18.075,18.076,23,12,23C5.926,23,1.001,18.075,1.001,12.001z"/>
9 +</svg>
backend/musik88-web/app/frontend/javascripts/application.js new
+20
@@ -0,0 +1,20 @@
1 +// This is a manifest file that'll be compiled into application.js, which will include all the files
2 +// listed below.
3 +//
4 +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 +//
7 +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 +// compiled file.
9 +//
10 +// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 +// about supported directives.
12 +//
13 +//= require jquery
14 +//= require bootstrap-sprockets
15 +//= require jquery_ujs
16 +//= require_tree .
17 +
18 +import 'jquery'
19 +import 'bootstrap'
20 +//import './channels/index'
\ No newline at end of file
backend/musik88-web/app/frontend/javascripts/channels/.keep
backend/musik88-web/app/frontend/javascripts/channels/consumer.js new
+6
@@ -0,0 +1,6 @@
1 +// Action Cable provides the framework to deal with WebSockets in Rails.
2 +// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
3 +
4 +import { createConsumer } from "@rails/actioncable"
5 +
6 +export default createConsumer()
\ No newline at end of file
backend/musik88-web/app/frontend/javascripts/channels/index.js new
+5
@@ -0,0 +1,5 @@
1 +// Load all the channels within this directory and all subdirectories.
2 +// Channel files must be named *_channel.js.
3 +
4 +const channels = require.context('.', true, /_channel\.js$/)
5 +channels.keys().forEach(channels)
\ No newline at end of file
backend/musik88-web/app/frontend/javascripts/channels/spotify_export_channel.js new
+11
@@ -0,0 +1,11 @@
1 +import consumer from "./consumer"
2 +
3 +consumer.subscriptions.create(
4 + { channel: "SpotifyExportChannel" },
5 + {
6 + connect() {},
7 + received(data) {
8 + console.log("Received data.", data)
9 + }
10 + }
11 +)
\ No newline at end of file
backend/musik88-web/app/frontend/packs/active_admin.js new
+6
@@ -0,0 +1,6 @@
1 +// Load Active Admin's styles into Webpacker,
2 +// see `active_admin.scss` for customization.
3 +import "../stylesheets/active_admin.scss";
4 +
5 +require('activeadmin_quill_editor');
6 +import "@activeadmin/activeadmin";
\ No newline at end of file
backend/musik88-web/app/frontend/packs/active_admin/print.scss new
+2
@@ -0,0 +1,2 @@
1 +/* Active Admin Print Stylesheet */
2 +@import "~@activeadmin/activeadmin/src/scss/print";
backend/musik88-web/app/frontend/packs/application.js new
+17
@@ -0,0 +1,17 @@
1 +// This file is automatically compiled by Webpack, along with any other files
2 +// present in this directory. You're encouraged to place your actual application logic in
3 +// a relevant structure within app/javascript and only use these pack files to reference
4 +// that code so it'll be compiled.
5 +
6 +import Rails from "@rails/ujs"
7 +import Turbolinks from "turbolinks"
8 +import * as ActiveStorage from "@rails/activestorage"
9 +import '../javascripts/application'
10 +import '../stylesheets/application.scss'
11 +
12 +const images = require.context('../images', true)
13 +const imagePath = (name) => images(name, true)
14 +
15 +Rails.start()
16 +Turbolinks.start()
17 +ActiveStorage.start()
backend/musik88-web/app/frontend/stylesheets/active_admin.scss new
+35
@@ -0,0 +1,35 @@
1 +// Sass variable overrides must be declared before loading up Active Admin's styles.
2 +//
3 +// To view the variables that Active Admin provides, take a look at
4 +// `app/assets/stylesheets/active_admin/mixins/_variables.scss` in the
5 +// Active Admin source.
6 +//
7 +// For example, to change the sidebar width:
8 +// $sidebar-width: 242px;
9 +
10 +// Active Admin's got SASS!
11 +@import "~@activeadmin/activeadmin/src/scss/mixins";
12 +@import "~@activeadmin/activeadmin/src/scss/base";
13 +
14 +// Overriding any non-variable Sass must be done after the fact.
15 +// For example, to change the default status-tag color:
16 +//
17 +// .status_tag { background: #6090DB; }
18 +
19 +#index_table_artists .col-albums,
20 +#index_table_albums .col-artists,
21 +#index_table_albums .col-songs,
22 +#index_table_songs .col-album {
23 + min-width: 170px;
24 + max-width: 200px;
25 +}
26 +#index_table_songs .col-lyrics {
27 + width: 70px;
28 +}
29 +
30 +p.chord {
31 + margin-bottom: 0;
32 + font-size: 110%;
33 + font-weight: bold;
34 + color: blue;
35 +}
backend/musik88-web/app/frontend/stylesheets/application.scss new
+19
@@ -0,0 +1,19 @@
1 +/*
2 + * This is a manifest file that'll be compiled into application.css, which will include all the files
3 + * listed below.
4 + *
5 + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 + *
8 + * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 + * compiled file so the styles you add here take precedence over styles defined in any styles
10 + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 + * file per style scope.
12 + *
13 + */
14 +$primary: #1740FF;
15 +$accent: #FFCC00;
16 +@import "~bootstrap/scss/bootstrap";
17 +@import "~bootstrap-icons/font/bootstrap-icons";
18 +@import "starter-template";
19 +@import "~quill/dist/quill.snow";
\ No newline at end of file
backend/musik88-web/app/frontend/stylesheets/fonts/bootstrap-icons.woff
Binary files /dev/null and b/backend/musik88-web/app/frontend/stylesheets/fonts/bootstrap-icons.woff differ
backend/musik88-web/app/frontend/stylesheets/fonts/bootstrap-icons.woff2
Binary files /dev/null and b/backend/musik88-web/app/frontend/stylesheets/fonts/bootstrap-icons.woff2 differ
backend/musik88-web/app/frontend/stylesheets/pagination.scss new
+104
@@ -0,0 +1,104 @@
1 +.digg_pagination {
2 + background: white;
3 + cursor: default;
4 + /* self-clearing method: */ }
5 + .digg_pagination a, .digg_pagination span, .digg_pagination em {
6 + padding: 0.2em 0.5em;
7 + display: block;
8 + float: left;
9 + margin-right: 1px; }
10 + .digg_pagination .disabled {
11 + color: #999999;
12 + border: 1px solid #dddddd; }
13 + .digg_pagination .current {
14 + font-style: normal;
15 + font-weight: bold;
16 + background: #2e6ab1;
17 + color: white;
18 + border: 1px solid #2e6ab1; }
19 + .digg_pagination a {
20 + text-decoration: none;
21 + color: #105cb6;
22 + border: 1px solid #9aafe5; }
23 + .digg_pagination a:hover, .digg_pagination a:focus {
24 + color: #000033;
25 + border-color: #000033; }
26 + .digg_pagination .page_info {
27 + background: #2e6ab1;
28 + color: white;
29 + padding: 0.4em 0.6em;
30 + width: 22em;
31 + margin-bottom: 0.3em;
32 + text-align: center; }
33 + .digg_pagination .page_info b {
34 + color: #000033;
35 + background: #6aa6ed;
36 + padding: 0.1em 0.25em; }
37 + .digg_pagination:after {
38 + content: ".";
39 + display: block;
40 + height: 0;
41 + clear: both;
42 + visibility: hidden; }
43 + * html .digg_pagination {
44 + height: 1%; }
45 + *:first-child + html .digg_pagination {
46 + overflow: hidden; }
47 +
48 + .apple_pagination {
49 + background: #f1f1f1;
50 + border: 1px solid #e5e5e5;
51 + text-align: center;
52 + padding: 1em;
53 + cursor: default; }
54 + .apple_pagination a, .apple_pagination span {
55 + padding: 0.2em 0.3em; }
56 + .apple_pagination .disabled {
57 + color: #aaaaaa; }
58 + .apple_pagination .current {
59 + font-style: normal;
60 + font-weight: bold;
61 + background-color: #bebebe;
62 + display: inline-block;
63 + width: 1.4em;
64 + height: 1.4em;
65 + line-height: 1.5;
66 + -moz-border-radius: 1em;
67 + -webkit-border-radius: 1em;
68 + border-radius: 1em;
69 + text-shadow: rgba(255, 255, 255, 0.8) 1px 1px 1px; }
70 + .apple_pagination a {
71 + text-decoration: none;
72 + color: black; }
73 + .apple_pagination a:hover, .apple_pagination a:focus {
74 + text-decoration: underline; }
75 +
76 + .flickr_pagination {
77 + text-align: center;
78 + padding: 0.3em;
79 + cursor: default; }
80 + .flickr_pagination a, .flickr_pagination span, .flickr_pagination em {
81 + padding: 0.2em 0.5em; }
82 + .flickr_pagination .disabled {
83 + color: #aaaaaa; }
84 + .flickr_pagination .current {
85 + font-style: normal;
86 + font-weight: bold;
87 + color: #ff0084; }
88 + .flickr_pagination a {
89 + border: 1px solid #dddddd;
90 + color: #0063dc;
91 + text-decoration: none; }
92 + .flickr_pagination a:hover, .flickr_pagination a:focus {
93 + border-color: #003366;
94 + background: #0063dc;
95 + color: white; }
96 + .flickr_pagination .page_info {
97 + color: #aaaaaa;
98 + padding-top: 0.8em; }
99 + .flickr_pagination .previous_page, .flickr_pagination .next_page {
100 + border-width: 2px; }
101 + .flickr_pagination .previous_page {
102 + margin-right: 1em; }
103 + .flickr_pagination .next_page {
104 + margin-left: 1em; }
\ No newline at end of file
backend/musik88-web/app/frontend/stylesheets/starter-template.css new
+119
@@ -0,0 +1,119 @@
1 +.bd-placeholder-img {
2 + font-size: 1.125rem;
3 + text-anchor: middle;
4 + -webkit-user-select: none;
5 + -moz-user-select: none;
6 + user-select: none;
7 +}
8 +
9 +@media (min-width: 768px) {
10 + .bd-placeholder-img-lg {
11 + font-size: 3.5rem;
12 + }
13 +}
14 +
15 +.icon-list {
16 + padding-left: 0;
17 + list-style: none;
18 +}
19 +
20 +.icon-list li {
21 + display: flex;
22 + align-items: flex-start;
23 + margin-bottom: .25rem;
24 +}
25 +
26 +.icon-list li::before {
27 + display: block;
28 + flex-shrink: 0;
29 + width: 1.5em;
30 + height: 1.5em;
31 + margin-right: .5rem;
32 + content: "";
33 + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23212529' viewBox='0 0 16 16'%3E%3Cpath d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z'/%3E%3C/svg%3E") no-repeat center center / 100% auto;
34 +}
35 +
36 +p.chord {
37 + margin-bottom: 0;
38 + font-size: 16px;
39 + font-weight: bold;
40 + color: blue;
41 +}
42 +
43 +.onboarding-gif-wrapper {
44 + background-image: url('../images/badge-pretty.gif');
45 + text-align: center;
46 + line-height: 165vh;
47 + height: 100vh;
48 + background-repeat: no-repeat;
49 + background-size: cover;
50 +}
51 +
52 +/* https://codepen.io/davidelrizzo/pen/vEYvyv */
53 +/* Shared */
54 +.vertical-separator {
55 + padding-top: 1em;
56 +}
57 +
58 +.loginBtn {
59 + box-sizing: border-box;
60 + position: relative;
61 + /* width: 13em; - apply for fixed size */
62 + margin-top: 0.2em;
63 + margin-right: 0.2em;
64 + margin-bottom: 0.2em;
65 + margin-left: 0;
66 + padding: 0 15px 0 46px;
67 + border: none;
68 + text-align: left;
69 + line-height: 34px;
70 + white-space: nowrap;
71 + border-radius: 0.2em;
72 + font-size: 16px;
73 + color: #FFF;
74 +}
75 +
76 +.loginBtn:before {
77 + content: "";
78 + box-sizing: border-box;
79 + position: absolute;
80 + top: 0;
81 + left: 0;
82 + width: 34px;
83 + height: 100%;
84 +}
85 +
86 +.loginBtn:focus {
87 + outline: none;
88 +}
89 +
90 +.loginBtn:active {
91 + box-shadow: inset 0 0 0 32px rgba(0, 0, 0, 0.1);
92 +}
93 +
94 +
95 +/* Facebook */
96 +.loginBtn--facebook {
97 + background-color: #4C69BA;
98 + background-image: linear-gradient(#4C69BA, #3B55A0);
99 + /*font-family: "Helvetica neue", Helvetica Neue, Helvetica, Arial, sans-serif;*/
100 + text-shadow: 0 -1px 0 #354C8C;
101 +}
102 +
103 +.loginBtn--facebook:before {
104 + border-right: #364e92 1px solid;
105 + background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat;
106 +}
107 +
108 +.loginBtn--facebook:hover,
109 +.loginBtn--facebook:focus {
110 + background-color: #5B7BD5;
111 + background-image: linear-gradient(#5B7BD5, #4864B1);
112 +}
113 +
114 +.flex-wrapper {
115 + display: flex;
116 + min-height: 50vh;
117 + flex-direction: column;
118 + justify-content: space-between;
119 +}
\ No newline at end of file
backend/musik88-web/app/helpers/about_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module AboutHelper
2 +end
backend/musik88-web/app/helpers/albums_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module AlbumsHelper
2 +end
backend/musik88-web/app/helpers/application_helper.rb new
+99
@@ -0,0 +1,99 @@
1 +module ApplicationHelper
2 + def current_class?(*paths)
3 + paths.each do |path|
4 + return 'current-item' if request.path == path
5 + end
6 + end
7 +
8 + def current_class_mobile?(path)
9 + request.path.start_with?(path) ? 'selected' : ''
10 + end
11 +
12 + def production?
13 + request.domain == 'wwww.musik88.com' || request.domain == 'musik88.com'
14 + end
15 +
16 + def link_to_host_locale(locale, path_with_locale)
17 + path = path_with_locale.split(locale.to_s)[1]
18 +
19 + path = '' if path.nil?
20 +
21 + host_locale = ''
22 + RouteTranslator.config.host_locales.each do |locale_array|
23 + host_locale = locale_array[0] if locale_array[1].to_s == locale.to_s
24 +
25 + host_locale + ":#{request.port}"
26 + end
27 + host_locale + path
28 + end
29 +
30 + def current_domain_locale_url(locale_code, params)
31 + domains = domains_map
32 + ## Loop through all locales and get the path
33 + I18n.available_locales.each do |locale|
34 + path = url_for(locale: locale.to_s, only_path: true, params: params)
35 + unless locale == :en || path.start_with?('/users')
36 + path = path.sub('/', '').split('/')[1..].join('/').insert(0,
37 + '/')
38 + end
39 + host = domains[locale]
40 + domains[locale] = "#{host}#{path}"
41 + end
42 +
43 + domains[locale_code]
44 + end
45 +
46 + CanonicalUrl = Struct.new(:url, :locale)
47 +
48 + def canonical_urls
49 + # The canonical url is en locale in .com domain
50 + domain = if Rails.env.development?
51 + "http://com.musik88:#{request.port}"
52 + elsif Rails.env.production?
53 + 'https://www.musik88.com'
54 + end
55 +
56 + urls_with_locale = []
57 +
58 + I18n.available_locales.each do |locale|
59 + canonical_url = CanonicalUrl.new
60 +
61 + path = url_for(locale: locale.to_s, only_path: true)
62 +
63 + canonical_url.url = domain + path
64 + canonical_url.locale = locale
65 +
66 + urls_with_locale.push(canonical_url)
67 + end
68 +
69 + urls_with_locale
70 + end
71 +
72 + def name_or_username(user)
73 + user.username.nil? ? user.name : "@#{user.username}"
74 + end
75 +
76 + def profile_path_enhanced(user)
77 + profile_path(user.username || user.id)
78 + end
79 +
80 + def artist_friendly_url(artist)
81 + "#{artist.name.downcase.parameterize}-#{artist.id}"
82 + end
83 +
84 + def album_friendly_url(album)
85 + "#{album.name.downcase.parameterize}-#{album.artists[0].name.downcase.parameterize}-#{album.id}"
86 + end
87 +
88 + private
89 +
90 + def domains_map
91 + domains = {}
92 + host = Rails.env.production? ? request.host : request.host_with_port
93 + I18n.available_locales.each do |locale|
94 + locale_suffix = "/#{locale}" unless locale == :en || request.path.start_with?('/users')
95 + domains[locale] = "#{request.protocol}#{host}#{locale_suffix}"
96 + end
97 + domains
98 + end
99 +end
backend/musik88-web/app/helpers/artists_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module ArtistsHelper
2 +end
backend/musik88-web/app/helpers/chords_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module ChordsHelper
2 +end
backend/musik88-web/app/helpers/contact_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module ContactHelper
2 +end
backend/musik88-web/app/helpers/errors_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module ErrorsHelper
2 +end
backend/musik88-web/app/helpers/javanese_gamelan_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module JavaneseGamelanHelper
2 +end
backend/musik88-web/app/helpers/lyrics_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module LyricsHelper
2 +end
backend/musik88-web/app/helpers/my_helper.rb new
+5
@@ -0,0 +1,5 @@
1 +module MyHelper
2 + def current_profile_menu?(path)
3 + request.path.start_with?(path) ? 'selected' : ''
4 + end
5 +end
backend/musik88-web/app/helpers/relationships_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module RelationshipsHelper
2 +end
backend/musik88-web/app/helpers/songs_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module SongsHelper
2 +end
backend/musik88-web/app/helpers/users_helper.rb new
+16
@@ -0,0 +1,16 @@
1 +module UsersHelper
2 + # Returns the gravatar for the given user
3 + def gravatar_for(user)
4 + gravatar_id = Digest::MD5.hexdigest(user.email.downcase)
5 + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
6 + image_tag(gravatar_url, alt: user.name, class: 'img-thumbnail rounded-circle mx-auto d-block')
7 + end
8 +
9 + def nowhereman
10 + User.find_by(username: 'nowhereman')
11 + end
12 +
13 + def locale_index(locale)
14 + I18n.available_locales.find_index(locale)
15 + end
16 +end
backend/musik88-web/app/helpers/welcome_helper.rb new
+2
@@ -0,0 +1,2 @@
1 +module WelcomeHelper
2 +end
backend/musik88-web/app/jobs/application_job.rb new
+7
@@ -0,0 +1,7 @@
1 +class ApplicationJob < ActiveJob::Base
2 + # Automatically retry jobs that encountered a deadlock
3 + # retry_on ActiveRecord::Deadlocked
4 +
5 + # Most jobs are safe to ignore if the underlying records are no longer available
6 + # discard_on ActiveJob::DeserializationError
7 +end
backend/musik88-web/app/jobs/audio_analysis_job.rb new
+23
@@ -0,0 +1,23 @@
1 +# frozen_string_literal: true
2 +
3 +class AudioAnalysisJob < ApplicationJob
4 + queue_as :default
5 +
6 + def perform(audio_file_id)
7 + # Get the file to split.
8 + @file = AudioFile.find_by(id: audio_file_id)
9 +
10 + # Return early if the file is not found.
11 + return if @file.nil?
12 +
13 + # Perform result files processing.
14 + bass = @file.bass_file
15 + AudioAnalysisSplitfireResultJob.perform_later(bass.id)
16 + vocal = @file.vocals_file
17 + AudioAnalysisSplitfireResultJob.perform_later(vocal.id)
18 + drum = @file.drums_file
19 + AudioAnalysisSplitfireResultJob.perform_later(drum.id)
20 + other = @file.other_file
21 + AudioAnalysisSplitfireResultJob.perform_later(other.id)
22 + end
23 +end
backend/musik88-web/app/jobs/audio_analysis_splitfire_result_job.rb new
+48
@@ -0,0 +1,48 @@
1 +# frozen_string_literal: true
2 +
3 +class AudioAnalysisSplitfireResultJob < ApplicationJob
4 + queue_as :default
5 +
6 + def perform(splitfire_result_id)
7 + # Get the file to split.
8 + @file = SplitfireResult.find_by(id: splitfire_result_id)
9 +
10 + # Make sure the actual file is attached.
11 + # Otherwise, we don't want to do the job.
12 + return if @file.nil? || !@file.source_file.attached?
13 +
14 + Sidekiq.logger.debug("File: #{@file}")
15 +
16 + # Change working directory to the output folder.
17 + change_working_directory
18 +
19 + length_and_onset do |length, onset|
20 + @file.update(length: length, onset: onset)
21 + Sidekiq.logger.debug("Length: #{length}, Onset: #{onset}")
22 + end
23 + end
24 +
25 + private
26 +
27 + def length_and_onset
28 + Sidekiq.logger.info('Starting AudioAnalysisJob processing...')
29 + @file.source_file.open do |file|
30 + Sidekiq.logger.debug("File path: #{file.path}")
31 + result = `python audio_length.py #{file.path}`
32 + onset = `python onset.py #{file.path}`
33 + # Process onset string into array.
34 + onset = onset_string_to_array(onset)
35 + yield(result, onset)
36 + end
37 + end
38 +
39 + def onset_string_to_array(onset)
40 + onset.delete('[]').split.map(&:to_f)
41 + end
42 +
43 + def change_working_directory
44 + Sidekiq.logger.info('Changing working directory...')
45 + Dir.chdir(ENV['AUDIO_ANALYSIS_WORKING_DIRECTORY'])
46 + system('echo $PWD')
47 + end
48 +end
backend/musik88-web/app/jobs/bass64_job.rb new
+135
@@ -0,0 +1,135 @@
1 +class Bass64Job < ApplicationJob
2 + queue_as :default
3 +
4 + def perform(audio_file_id)
5 +
6 + # Get the file to split.
7 + file = AudioFile.find_by(id: audio_file_id)
8 +
9 + # Make sure the actual file is attached.
10 + # Otherwise, we don't want to do the job.
11 + return if file.nil? || !file.source_file.attached?
12 +
13 + file_path = ActiveStorage::Blob.service.send(:path_for, file.source_file.key)
14 +
15 + # Change to spleeter working directory.
16 + # TODO: Tidy up.
17 + if Rails.env.production?
18 + Dir.chdir("/home/deploy/apps/spleeter/")
19 + else
20 + Dir.chdir("../spleeter/")
21 + end
22 +
23 + # Spleet it!
24 + # Always use the fullpath to the Spleeter.
25 + # TODO: Tidy up.
26 + if Rails.env.production?
27 + system("/home/deploy/.local/bin/spleeter separate -p spleeter:4stems-16kHz -o ./output/ #{file_path} >> spleeter_log.txt")
28 + else
29 + system("spleeter separate -p spleeter:4stems-16kHz -o ./output/ #{file_path} >> spleeter_log.txt")
30 + end
31 +
32 + file_output_directory = "output/#{file.source_file.blob.key}"
33 +
34 + # Compose results
35 + result_bass_path = "#{file_output_directory}/bass.wav"
36 + result_vocals_path = "#{file_output_directory}/vocals.wav"
37 + result_drums_path = "#{file_output_directory}/drums.wav"
38 + result_other_path = "#{file_output_directory}/other.wav"
39 +
40 + # Wait until the resilt files exist...
41 + until
42 + File.exist?(result_bass_path) &&
43 + File.exist?(result_vocals_path) &&
44 + File.exist?(result_drums_path) &&
45 + File.exist?(result_other_path)
46 +
47 + system("echo 'We are waiting for the result files...'")
48 + sleep 5
49 + end
50 +
51 +
52 + # Internal
53 + #
54 + # Save this as VocalFile and BassFile to train a speech recognition AI
55 + # Only do so if we haven't had it in the database
56 + unless !VocalFile.find_by(audio_file_id: file.id).nil?
57 + vocal_file = File.open(result_vocals_path)
58 + vocal_filename = "vocals-#{file.source_file.blob.filename}.wav"
59 + vocal = VocalFile.new(audio_file_id: file.id)
60 +
61 + vocal.source_file.attach(io: vocal_file, filename: vocal_filename)
62 + vocal.save
63 + end
64 +
65 + unless !BassFile.find_by(audio_file_id: file.id).nil?
66 + bass_file = File.open(result_bass_path)
67 + bass_filename = "bass-#{file.source_file.blob.filename}.wav"
68 + bass = BassFile.new(audio_file_id: file.id)
69 +
70 + bass.source_file.attach(io: bass_file, filename: bass_filename)
71 + bass.save
72 + end
73 +
74 + unless !DrumFile.find_by(audio_file_id: file.id).nil?
75 + drum_file = File.open(result_drums_path)
76 + drum_filename = "drums-#{file.source_file.blob.filename}.wav"
77 + drum = DrumFile.new(audio_file_id: file.id)
78 +
79 + drum.source_file.attach(io: drum_file, filename: drum_filename)
80 + drum.save
81 + end
82 +
83 + unless !OtherFile.find_by(audio_file_id: file.id).nil?
84 + other_file = File.open(result_other_path)
85 + other_filename = "other-#{file.source_file.blob.filename}.wav"
86 + other = OtherFile.new(audio_file_id: file.id)
87 +
88 + other.source_file.attach(io: other_file, filename: other_filename)
89 + other.save
90 + end
91 + #
92 + # END Internal
93 +
94 + result_bass_backing_track_filename = "bass-backing-track-#{file.source_file.blob.filename}.wav"
95 + result_bass_backing_track_path = "#{file_output_directory}/#{result_bass_backing_track_filename}"
96 +
97 + system("ffmpeg -i #{result_drums_path} -i #{result_vocals_path} -i #{result_other_path} -filter_complex amix=inputs=3:duration=longest -y '#{result_bass_backing_track_path}'")
98 +
99 + until File.exist?(result_bass_backing_track_path)
100 + system("echo 'We are waiting for #{result_bass_backing_track_path}...'")
101 + sleep 60
102 + end
103 + result_bass_backing_track_file = File.open(result_bass_backing_track_path)
104 +
105 + # We only save the result if user doesn't cancel the process
106 + # Ideally, we cancel the running process immediately
107 + if !file.audio_file_bass64_status.nil? && file.audio_file_bass64_status.cancelled?
108 + system("rm -rf #{file_output_directory}")
109 + return
110 + end
111 +
112 + result_bass_backing_track = BassBackingTrack.new(audio_file_id: file.id)
113 + result_bass_backing_track.source_file.attach(io: result_bass_backing_track_file, filename: result_bass_backing_track_filename)
114 + result_bass_backing_track.save
115 +
116 + # Cleanup Spleeter output folder
117 + system("rm -rf #{file_output_directory}")
118 +
119 + if file.audio_file_bass64_status.nil?
120 + AudioFileBass64Status
121 + .new(audio_file_id: audio_file_id, status: :done)
122 + .save
123 + elsif
124 + file
125 + .audio_file_bass64_status
126 + .done!
127 + end
128 +
129 + ActionCable.server.broadcast "Bass64Channel", {
130 + audio_file_id: file.id,
131 + message: MyController.render(partial: 'partial_bass64_row', locals: { audio_file: file} )
132 + }
133 +
134 + end
135 +end
backend/musik88-web/app/jobs/bass_backing_track_job.rb new
+64
@@ -0,0 +1,64 @@
1 +class BassBackingTrackJob < ApplicationJob
2 + # TODO: Set priority based on account type.
3 + queue_as :default
4 + def perform(audio_file_id)
5 + # Get the file to split.
6 + file = AudioFile.find_by(id: audio_file_id)
7 +
8 + # Make sure the actual file is attached.
9 + # Otherwise, we don't want to do the job.
10 + return if file.nil? || !file.source_file.attached? || !file.splitfire_results.count.positive?
11 +
12 + # Create the object now
13 + result_bass_backing_track = BassBackingTrackFile.new(audio_file_id: file.id)
14 + result_bass_backing_track.save
15 +
16 + result_drums_path = ActiveStorage::Blob.service.send(:path_for, file.drums_file.source_file.key)
17 + result_vocals_path = ActiveStorage::Blob.service.send(:path_for, file.vocals_file.source_file.key)
18 + result_piano_path = ActiveStorage::Blob.service.send(:path_for, file.piano_file.source_file.key)
19 + result_other_path = ActiveStorage::Blob.service.send(:path_for, file.other_file.source_file.key)
20 +
21 + unless result_drums_path.present? && result_vocals_path.present? && result_piano_path.present? && result_other_path.present?
22 + return
23 + end
24 +
25 + # Prepare working directory.
26 + working_directory_path = if Rails.env.production?
27 + '/home/deploy/apps/ffmpeg/'
28 + else
29 + '../ffmpeg/'
30 + end
31 +
32 + Dir.chdir(working_directory_path)
33 +
34 + codec = 'mp3'
35 +
36 + file_output_directory = file.source_file.blob.key.to_s
37 +
38 + result_bass_backing_track_filename = "bass-backing-track-#{file.id}.#{codec}"
39 +
40 + system("ffmpeg -i #{result_drums_path} -i #{result_vocals_path} -i #{result_piano_path} -i #{result_other_path} -filter_complex amix=inputs=4:duration=longest -y '#{result_bass_backing_track_filename}'")
41 +
42 + status_progress = 10
43 + until File.exist?(result_bass_backing_track_filename)
44 + system("echo 'We are waiting for #{result_bass_backing_track_filename}...'")
45 + result_bass_backing_track.update_attribute(:status_progress, status_progress)
46 + result_bass_backing_track.save
47 +
48 + status_progress += 1 if status_progress < 100
49 +
50 + sleep 1
51 + end
52 + result_bass_backing_track_file = File.open(result_bass_backing_track_filename)
53 + result_bass_backing_track.source_file.attach(
54 + io: result_bass_backing_track_file,
55 + filename: result_bass_backing_track_filename
56 + )
57 + result_bass_backing_track.update_attribute(:status, :done)
58 + result_bass_backing_track.update_attribute(:status_progress, 100)
59 + result_bass_backing_track.save
60 +
61 + # Cleanup Spleeter output folder
62 + system("rm #{result_bass_backing_track_filename}")
63 + end
64 +end
backend/musik88-web/app/jobs/bonzo_job.rb new
+135
@@ -0,0 +1,135 @@
1 +class BonzoJob < ApplicationJob
2 + queue_as :default
3 +
4 + def perform(audio_file_id)
5 +
6 + # Get the file to split.
7 + file = AudioFile.find_by(id: audio_file_id)
8 +
9 + # Make sure the actual file is attached.
10 + # Otherwise, we don't want to do the job.
11 + return if file.nil? || !file.source_file.attached?
12 +
13 + file_path = ActiveStorage::Blob.service.send(:path_for, file.source_file.key)
14 +
15 + # Change to spleeter working directory.
16 + # TODO: Tidy up.
17 + if Rails.env.production?
18 + Dir.chdir("/home/deploy/apps/spleeter/")
19 + else
20 + Dir.chdir("../spleeter/")
21 + end
22 +
23 + # Spleet it!
24 + # Always use the fullpath to the Spleeter.
25 + # TODO: Tidy up.
26 + if Rails.env.production?
27 + system("/home/deploy/.local/bin/spleeter separate -p spleeter:4stems-16kHz -o ./output/ #{file_path} >> spleeter_log.txt")
28 + else
29 + system("spleeter separate -p spleeter:4stems-16kHz -o ./output/ #{file_path} >> spleeter_log.txt")
30 + end
31 +
32 + file_output_directory = "output/#{file.source_file.blob.key}"
33 +
34 + # Compose results
35 + result_bass_path = "#{file_output_directory}/bass.wav"
36 + result_vocals_path = "#{file_output_directory}/vocals.wav"
37 + result_drums_path = "#{file_output_directory}/drums.wav"
38 + result_other_path = "#{file_output_directory}/other.wav"
39 +
40 + # Wait until the resilt files exist...
41 + until
42 + File.exist?(result_bass_path) &&
43 + File.exist?(result_vocals_path) &&
44 + File.exist?(result_drums_path) &&
45 + File.exist?(result_other_path)
46 +
47 + system("echo 'We are waiting for the result files...'")
48 + sleep 5
49 + end
50 +
51 +
52 + # Internal
53 + #
54 + # Save this as VocalFile and BassFile to train a speech recognition AI
55 + # Only do so if we haven't had it in the database
56 + unless !VocalFile.find_by(audio_file_id: file.id).nil?
57 + vocal_file = File.open(result_vocals_path)
58 + vocal_filename = "vocals-#{file.source_file.blob.filename}.wav"
59 + vocal = VocalFile.new(audio_file_id: file.id)
60 +
61 + vocal.source_file.attach(io: vocal_file, filename: vocal_filename)
62 + vocal.save
63 + end
64 +
65 + unless !BassFile.find_by(audio_file_id: file.id).nil?
66 + bass_file = File.open(result_bass_path)
67 + bass_filename = "bass-#{file.source_file.blob.filename}.wav"
68 + bass = BassFile.new(audio_file_id: file.id)
69 +
70 + bass.source_file.attach(io: bass_file, filename: bass_filename)
71 + bass.save
72 + end
73 +
74 + unless !DrumFile.find_by(audio_file_id: file.id).nil?
75 + drum_file = File.open(result_drums_path)
76 + drum_filename = "drums-#{file.source_file.blob.filename}.wav"
77 + drum = DrumFile.new(audio_file_id: file.id)
78 +
79 + drum.source_file.attach(io: drum_file, filename: drum_filename)
80 + drum.save
81 + end
82 +
83 + unless !OtherFile.find_by(audio_file_id: file.id).nil?
84 + other_file = File.open(result_other_path)
85 + other_filename = "other-#{file.source_file.blob.filename}.wav"
86 + other = OtherFile.new(audio_file_id: file.id)
87 +
88 + other.source_file.attach(io: other_file, filename: other_filename)
89 + other.save
90 + end
91 + #
92 + # END Internal
93 +
94 + result_drum_backing_track_filename = "drum-backing-track-#{file.source_file.blob.filename}.wav"
95 + result_drum_backing_track_path = "#{file_output_directory}/#{result_drum_backing_track_filename}"
96 +
97 + system("ffmpeg -i #{result_bass_path} -i #{result_vocals_path} -i #{result_other_path} -filter_complex amix=inputs=3:duration=longest -y '#{result_drum_backing_track_path}'")
98 +
99 + until File.exist?(result_drum_backing_track_path)
100 + system("echo 'We are waiting for #{result_drum_backing_track_path}...'")
101 + sleep 60
102 + end
103 + result_drum_backing_track_file = File.open(result_drum_backing_track_path)
104 +
105 + # We only save the result if user doesn't cancel the process
106 + # Ideally, we cancel the running process immediately
107 + if !file.audio_file_bonzo_status.nil? && file.audio_file_bonzo_status.cancelled?
108 + system("rm -rf #{file_output_directory}")
109 + return
110 + end
111 +
112 + result_drum_backing_track = DrumBackingTrack.new(audio_file_id: file.id)
113 + result_drum_backing_track.source_file.attach(io: result_drum_backing_track_file, filename: result_drum_backing_track_filename)
114 + result_drum_backing_track.save
115 +
116 + # Cleanup Spleeter output folder
117 + system("rm -rf #{file_output_directory}")
118 +
119 + if file.audio_file_bonzo_status.nil?
120 + AudioFileBonzoStatus
121 + .new(audio_file_id: audio_file_id, status: :done)
122 + .save
123 + elsif
124 + file
125 + .audio_file_bonzo_status
126 + .done!
127 + end
128 +
129 + ActionCable.server.broadcast "BonzoChannel", {
130 + audio_file_id: file.id,
131 + message: MyController.render(partial: 'partial_bonzo_row', locals: { audio_file: file} )
132 + }
133 +
134 + end
135 +end
backend/musik88-web/app/jobs/download_file_job.rb new
+36
@@ -0,0 +1,36 @@
1 +# The SplitFire naming, i.e; camel-cased style, is only being used for branding.
2 +# Inside the code, we refer this tool as Splitfire.
3 +
4 +# This meant tobe just a demo job.
5 +class DownloadFileJob < ApplicationJob
6 + # TODO: Set priority based on account type.
7 + queue_as :default
8 +
9 + def perform(audio_file_id, public_url)
10 + require 'open-uri'
11 +
12 + # Change to spleeter working directory.
13 + if Rails.env.production?
14 + Dir.chdir('/home/deploy/apps/spleeter/')
15 + else
16 + Dir.chdir('../spleeter/')
17 + end
18 +
19 + download = URI.open(public_url)
20 + filepath = "#{download.base_uri.to_s.split('/')[-1]}"
21 + IO.copy_stream(download, filepath)
22 +
23 + file = File.open(filepath)
24 +
25 + audio_file = AudioFile.find_by(id: audio_file_id)
26 + audio_file.source_file.attach(io: file, filename: filepath)
27 +
28 + return unless audio_file.save
29 +
30 + audio_file.audio_file_splitfire_status.update_attribute(:processing_progress, 10)
31 +
32 + SplitfireDemoJob.perform_later(audio_file.id)
33 +
34 + system("rm -rf #{filepath}")
35 + end
36 +end
backend/musik88-web/app/jobs/drum_backing_track_job.rb new
+64
@@ -0,0 +1,64 @@
1 +class DrumBackingTrackJob < ApplicationJob
2 + # TODO: Set priority based on account type.
3 + queue_as :default
4 + def perform(audio_file_id)
5 + # Get the file to split.
6 + file = AudioFile.find_by(id: audio_file_id)
7 +
8 + # Make sure the actual file is attached.
9 + # Otherwise, we don't want to do the job.
10 + return if file.nil? || !file.source_file.attached? || !file.splitfire_results.count.positive?
11 +
12 + # Create the object now
13 + result_drum_backing_track = DrumBackingTrack.new(audio_file_id: file.id)
14 + result_drum_backing_track.save
15 +
16 + result_other_path = ActiveStorage::Blob.service.send(:path_for, file.other_file.source_file.key)
17 + result_vocals_path = ActiveStorage::Blob.service.send(:path_for, file.vocals_file.source_file.key)
18 + result_piano_path = ActiveStorage::Blob.service.send(:path_for, file.piano_file.source_file.key)
19 + result_bass_path = ActiveStorage::Blob.service.send(:path_for, file.bass_file.source_file.key)
20 +
21 + unless result_other_path.present? && result_vocals_path.present? && result_piano_path.present? && result_bass_path.present?
22 + return
23 + end
24 +
25 + # Prepare working directory.
26 + working_directory_path = if Rails.env.production?
27 + '/home/deploy/apps/ffmpeg/'
28 + else
29 + '../ffmpeg/'
30 + end
31 +
32 + Dir.chdir(working_directory_path)
33 +
34 + codec = 'mp3'
35 +
36 + file_output_directory = file.source_file.blob.key.to_s
37 +
38 + result_drum_backing_track_filename = "drum-backing-track-#{file.id}.#{codec}"
39 +
40 + system("ffmpeg -i #{result_other_path} -i #{result_vocals_path} -i #{result_piano_path} -i #{result_bass_path} -filter_complex amix=inputs=4:duration=longest -y '#{result_drum_backing_track_filename}'")
41 +
42 + status_progress = 10
43 + until File.exist?(result_drum_backing_track_filename)
44 + system("echo 'We are waiting for #{result_drum_backing_track_filename}...'")
45 + result_drum_backing_track.update_attribute(:status_progress, status_progress)
46 + result_drum_backing_track.save
47 +
48 + status_progress += 1 if status_progress < 100
49 +
50 + sleep 1
51 + end
52 + result_drum_backing_track_file = File.open(result_drum_backing_track_filename)
53 + result_drum_backing_track.source_file.attach(
54 + io: result_drum_backing_track_file,
55 + filename: result_drum_backing_track_filename
56 + )
57 + result_drum_backing_track.update_attribute(:status, :done)
58 + result_drum_backing_track.update_attribute(:status_progress, 100)
59 + result_drum_backing_track.save
60 +
61 + # Cleanup Spleeter output folder
62 + system("rm #{result_drum_backing_track_filename}")
63 + end
64 +end
backend/musik88-web/app/jobs/guitar_backing_track_job.rb new
+64
@@ -0,0 +1,64 @@
1 +class GuitarBackingTrackJob < ApplicationJob
2 + # TODO: Set priority based on account type.
3 + queue_as :default
4 + def perform(audio_file_id)
5 + # Get the file to split.
6 + file = AudioFile.find_by(id: audio_file_id)
7 +
8 + # Make sure the actual file is attached.
9 + # Otherwise, we don't want to do the job.
10 + return if file.nil? || !file.source_file.attached? || !file.splitfire_results.count.positive?
11 +
12 + # Create the object now
13 + result_guitar_backing_track = GuitarBackingTrackFile.new(audio_file_id: file.id)
14 + result_guitar_backing_track.save
15 +
16 + result_drums_path = ActiveStorage::Blob.service.send(:path_for, file.drums_file.source_file.key)
17 + result_vocals_path = ActiveStorage::Blob.service.send(:path_for, file.vocals_file.source_file.key)
18 + result_piano_path = ActiveStorage::Blob.service.send(:path_for, file.piano_file.source_file.key)
19 + result_bass_path = ActiveStorage::Blob.service.send(:path_for, file.bass_file.source_file.key)
20 +
21 + unless result_drums_path.present? && result_vocals_path.present? && result_piano_path.present? && result_bass_path.present?
22 + return
23 + end
24 +
25 + # Prepare working directory.
26 + working_directory_path = if Rails.env.production?
27 + '/home/deploy/apps/ffmpeg/'
28 + else
29 + '../ffmpeg/'
30 + end
31 +
32 + Dir.chdir(working_directory_path)
33 +
34 + codec = 'mp3'
35 +
36 + file_output_directory = file.source_file.blob.key.to_s
37 +
38 + result_guitar_backing_track_filename = "guitar-backing-track-#{file.id}.#{codec}"
39 +
40 + system("ffmpeg -i #{result_drums_path} -i #{result_vocals_path} -i #{result_piano_path} -i #{result_bass_path} -filter_complex amix=inputs=4:duration=longest -y '#{result_guitar_backing_track_filename}'")
41 +
42 + status_progress = 10
43 + until File.exist?(result_guitar_backing_track_filename)
44 + system("echo 'We are waiting for #{result_guitar_backing_track_filename}...'")
45 + result_guitar_backing_track.update_attribute(:status_progress, status_progress)
46 + result_guitar_backing_track.save
47 +
48 + status_progress += 1 if status_progress < 100
49 +
50 + sleep 1
51 + end
52 + result_guitar_backing_track_file = File.open(result_guitar_backing_track_filename)
53 + result_guitar_backing_track.source_file.attach(
54 + io: result_guitar_backing_track_file,
55 + filename: result_guitar_backing_track_filename
56 + )
57 + result_guitar_backing_track.update_attribute(:status, :done)
58 + result_guitar_backing_track.update_attribute(:status_progress, 100)
59 + result_guitar_backing_track.save
60 +
61 + # Cleanup Spleeter output folder
62 + system("rm #{result_guitar_backing_track_filename}")
63 + end
64 +end
backend/musik88-web/app/jobs/karokowe_job.rb new
+69
@@ -0,0 +1,69 @@
1 +# The KaroKowe naming, i.e; camel-cased style, is only being used for branding.
2 +# Inside the code, we refer this tool as Karokowe.
3 +
4 +class KarokoweJob < ApplicationJob
5 + # TODO: Set priority based on account type.
6 + queue_as :default
7 +
8 + def perform(audio_file_id)
9 + # Get the file to split.
10 + file = AudioFile.find_by(id: audio_file_id)
11 +
12 + # Make sure the actual file is attached.
13 + # Otherwise, we don't want to do the job.
14 + return if file.nil? || !file.source_file.attached?
15 +
16 + file_path = ActiveStorage::Blob.service.send(:path_for, file.source_file.key)
17 +
18 + # Change to spleeter working directory.
19 + # TODO: Tidy up.
20 + if Rails.env.production?
21 + Dir.chdir('/home/deploy/apps/spleeter/')
22 + else
23 + Dir.chdir('../spleeter/')
24 + end
25 +
26 + codec = 'mp3'
27 +
28 + # Always use the fullpath to the Spleeter.
29 + # TODO: Tidy up.
30 + if Rails.env.production?
31 + system("/home/deploy/.local/bin/spleeter separate -p spleeter:2stems-16kHz -o ./output/ #{file_path} -c #{codec} >> karokowe_job_log.txt")
32 + else
33 + system("spleeter separate -p spleeter:2stems-16kHz -o ./output/ #{file_path} -c #{codec} >> karokowe_job_log.txt")
34 + end
35 +
36 + file_output_directory = "output/#{file.source_file.blob.key}"
37 +
38 + # Compose results for karaoke
39 + result_karaoke_path = "#{file_output_directory}/accompaniment.#{codec}"
40 +
41 + # Wait until the file exist...
42 + until File.exist?(result_karaoke_path)
43 + system("echo 'We are waiting for #{result_karaoke_path}...'")
44 + sleep 60
45 + end
46 +
47 + # We only save the result if user doesn't cancel the process
48 + # Ideally, we cancel the running process immediately
49 + if !file.audio_file_karokowe_status.nil? && file.audio_file_karokowe_status.cancelled?
50 + system("rm -rf #{file_output_directory}")
51 + return
52 + end
53 +
54 + result_karaoke_file = File.open(result_karaoke_path)
55 + result_karaoke_filename = "karaoke-version-#{file.id}.#{codec}"
56 + result_karaoke = KaraokeFile.new(audio_file_id: file.id)
57 +
58 + result_karaoke.source_file.attach(io: result_karaoke_file, filename: result_karaoke_filename)
59 + result_karaoke.save
60 +
61 + # Cleanup Spleeter output folder
62 + system("rm -rf #{file_output_directory}")
63 +
64 + if file.audio_file_karokowe_status.nil?
65 + AudioFileKarokoweStatus.new(audio_file_id: audio_file_id, status: :done).save
66 + elsif file.audio_file_karokowe_status.done!
67 + end
68 + end
69 +end
backend/musik88-web/app/jobs/splitfire_demo_job.rb new
+146
@@ -0,0 +1,146 @@
1 +# The SplitFire naming, i.e; camel-cased style, is only being used for branding.
2 +# Inside the code, we refer this tool as Splitfire.
3 +
4 +# This meant tobe just a demo job.
5 +class SplitfireDemoJob < ApplicationJob
6 + # TODO: Set priority based on account type.
7 + queue_as :default
8 +
9 + def perform(audio_file_id)
10 + # Get the file to split.
11 + @file = AudioFile.find_by(id: audio_file_id)
12 +
13 + # Make sure the actual file is attached.
14 + # Otherwise, we don't want to do the job.
15 + return if @file.nil? || !@file.source_file.attached?
16 +
17 + file_path = ActiveStorage::Blob.service.send(:path_for, @file.source_file.key)
18 +
19 + broadcast_progress(20)
20 +
21 + # Change to spleeter working directory.
22 + if Rails.env.production?
23 + Dir.chdir('/home/deploy/apps/spleeter/')
24 + else
25 + Dir.chdir('../spleeter/')
26 + end
27 +
28 + # Compute the params.
29 + splitfire_setting = UserSetting.find_by(user_id: 69) # Always use @splitfire setting.
30 + spleeter_param = splitfire_setting.model.to_s
31 + spleeter_param = "#{spleeter_param}-#{splitfire_setting.frequency}" if splitfire_setting.frequency == '16kHz'
32 + @codec = 'mp3'
33 +
34 + broadcast_progress(30)
35 +
36 + # Spleet it! Always use the fullpath to the Spleeter.
37 + if Rails.env.production?
38 + system("/home/deploy/.local/bin/spleeter separate -p spleeter:#{spleeter_param} -o ./output/ #{file_path} -c #{@codec} >> spleeter_log.txt")
39 + else
40 + system("spleeter separate -p spleeter:#{spleeter_param} -o ./output/ #{file_path} -c #{@codec} >> spleeter_log.txt")
41 + end
42 +
43 + broadcast_progress(50)
44 +
45 + @file_output_directory = "output/#{@file.source_file.blob.key}"
46 +
47 + # Start from the beginning.
48 + SplitfireResult
49 + .where(audio_file_id: @file.id)
50 + .destroy_all
51 +
52 + compose_result_vocals
53 + compose_result_bass
54 + compose_result_drums
55 + compose_result_other
56 + compose_result_piano
57 +
58 + broadcast_progress(90)
59 +
60 + # Cleanup Spleeter output folder
61 + system("rm -rf #{@file_output_directory}")
62 +
63 + @file.audio_file_splitfire_status.done!
64 +
65 + broadcast_progress(100)
66 + end
67 +
68 + private
69 +
70 + def compose_result_vocals
71 + result_vocals_path = "#{@file_output_directory}/vocals.#{@codec}"
72 +
73 + waiting_console(result_vocals_path)
74 +
75 + result_vocals_file = File.open(result_vocals_path)
76 + result_vocals_filename = "vocals-#{@file.id}.#{@codec}"
77 + result_vocals = SplitfireResult.new(audio_file_id: @file.id)
78 +
79 + result_vocals.source_file.attach(io: result_vocals_file, filename: result_vocals_filename)
80 + result_vocals.save
81 + end
82 +
83 + def compose_result_drums
84 + result_drums_path = "#{@file_output_directory}/drums.#{@codec}"
85 +
86 + waiting_console(result_drums_path)
87 +
88 + result_drums_file = File.open(result_drums_path)
89 + result_drums_filename = "drums-#{@file.id}.#{@codec}"
90 + result_drums = SplitfireResult.new(audio_file_id: @file.id)
91 +
92 + result_drums.source_file.attach(io: result_drums_file, filename: result_drums_filename)
93 + result_drums.save
94 + end
95 +
96 + def compose_result_bass
97 + result_bass_path = "#{@file_output_directory}/bass.#{@codec}"
98 +
99 + waiting_console(result_bass_path)
100 +
101 + result_bass_file = File.open(result_bass_path)
102 + result_bass_filename = "bass-#{@file.id}.#{@codec}"
103 + result_bass = SplitfireResult.new(audio_file_id: @file.id)
104 +
105 + result_bass.source_file.attach(io: result_bass_file, filename: result_bass_filename)
106 + result_bass.save
107 + end
108 +
109 + def compose_result_other
110 + result_other_path = "#{@file_output_directory}/other.#{@codec}"
111 +
112 + waiting_console(result_other_path)
113 +
114 + result_other_file = File.open(result_other_path)
115 + result_other_filename = "other-#{@file.id}.#{@codec}"
116 + result_other = SplitfireResult.new(audio_file_id: @file.id)
117 +
118 + result_other.source_file.attach(io: result_other_file, filename: result_other_filename)
119 + result_other.save
120 + end
121 +
122 + def compose_result_piano
123 + result_piano_path = "#{@file_output_directory}/piano.#{@codec}"
124 +
125 + waiting_console(result_piano_path)
126 +
127 + result_piano_file = File.open(result_piano_path)
128 + result_piano_filename = "piano-#{@file.id}.#{@codec}"
129 + result_piano = SplitfireResult.new(audio_file_id: @file.id)
130 +
131 + result_piano.source_file.attach(io: result_piano_file, filename: result_piano_filename)
132 + result_piano.save
133 + end
134 +
135 + def waiting_console(file_path)
136 + until File.exist?(file_path)
137 + system("echo 'We are waiting for #{file_path}...'")
138 + sleep 60
139 + end
140 + end
141 +
142 + def broadcast_progress(progress)
143 + @file.audio_file_splitfire_status.update_attribute(:processing_progress, progress)
144 + ActionCable.server.broadcast('FileProcessingChannel', @file.as_json_packed)
145 + end
146 +end
backend/musik88-web/app/jobs/splitfire_job.rb new
+121
@@ -0,0 +1,121 @@
1 +# The SplitFire naming, i.e; camel-cased style, is only being used for branding.
2 +# Inside the code, we refer this tool as Splitfire.
3 +
4 +class SplitfireJob < ApplicationJob
5 + queue_as :default
6 +
7 + def perform(audio_file_id)
8 + # Get the file to split.
9 + @file = AudioFile.find_by(id: audio_file_id)
10 +
11 + # Make sure the actual file is attached.
12 + # Otherwise, we don't want to do the job.
13 + return if @file.nil? || !@file.source_file.attached?
14 +
15 + # Change working directory to the Spleeter output folder.
16 + change_working_directory
17 + process_split_file do |path|
18 + process_results(path) if path.present?
19 + end
20 + end
21 +
22 + private
23 +
24 + def process_results(path)
25 + Sidekiq.logger.info("Processing results in #{path} ...")
26 + @file_output_directory = path
27 + # Process results
28 + process_vocals_file
29 + process_drums_file
30 + process_bass_file
31 + process_other_file
32 +
33 + Sidekiq.logger.info('Finishing process ...')
34 + system("rm -rf #{@file_output_directory}")
35 + @file.update(progress: 100)
36 + @file.done!
37 + end
38 +
39 + def change_working_directory
40 + Sidekiq.logger.info('Changing working directory...')
41 + Dir.chdir(ENV['AUDIO_WORKING_DIRECTORY'])
42 + system('echo $PWD')
43 + end
44 +
45 + def process_split_file
46 + Sidekiq.logger.info('Starting Splitfire processing...')
47 +
48 + @file.source_file.open do |file|
49 + system("echo 'File path: #{file.path}'")
50 + system("python3 -m demucs -n=htdemucs_ft -d cpu #{file.path} >> demucs_log.txt")
51 + # get filename from path
52 + filename = file.path.split('/').last
53 + # If filename consist of dot, split it and get the first part
54 + filename = filename.split('.').first if filename.include?('.')
55 + file_output_directory = "separated/htdemucs_ft/#{filename}"
56 + yield(file_output_directory)
57 + end
58 + end
59 +
60 + def process_vocals_file
61 + Sidekiq.logger.info('Processing vocals file...')
62 + # Compose results for vocals
63 + result_vocals_path = "#{@file_output_directory}/vocals.wav"
64 + result_vocals_file = File.open(result_vocals_path)
65 + result_vocals_filename = "vocals-#{@file.source_file.blob.filename}.wav"
66 + vocal = SplitfireResult.new(audio_file_id: @file.id)
67 + vocal.source_file.attach(io: result_vocals_file, filename: result_vocals_filename)
68 + vocal.save
69 + save_vocals_file(result_vocals_file, result_vocals_filename)
70 + end
71 +
72 + def save_vocals_file(result_vocals_path, result_vocals_filename)
73 + Sidekiq.logger.info('Saving vocals file...')
74 + result_vocals_file = File.open(result_vocals_path)
75 + vocal = VocalFile.new(audio_file_id: @file.id)
76 + vocal.source_file.attach(io: result_vocals_file, filename: result_vocals_filename)
77 + vocal.save
78 + end
79 +
80 + def process_drums_file
81 + Sidekiq.logger.info('Processing drums file...')
82 + # DRUMS
83 + result_drums_path = "#{@file_output_directory}/drums.wav"
84 + result_drums_file = File.open(result_drums_path)
85 + result_drums_filename = "drums-#{@file.source_file.blob.filename}.wav"
86 + result_drums = SplitfireResult.new(audio_file_id: @file.id)
87 + result_drums.source_file.attach(io: result_drums_file, filename: result_drums_filename)
88 + result_drums.save
89 + end
90 +
91 + def process_bass_file
92 + Sidekiq.logger.info('Processing bass file...')
93 + # BASS
94 + result_bass_path = "#{@file_output_directory}/bass.wav"
95 + result_bass_file = File.open(result_bass_path)
96 + result_bass_filename = "bass-#{@file.source_file.blob.filename}.wav"
97 + result_bass = SplitfireResult.new(audio_file_id: @file.id)
98 + result_bass.source_file.attach(io: result_bass_file, filename: result_bass_filename)
99 + result_bass.save
100 + end
101 +
102 + def process_other_file
103 + Sidekiq.logger.info('Processing other file...')
104 + # OTHER
105 + result_other_path = "#{@file_output_directory}/other.wav"
106 + result_other_file = File.open(result_other_path)
107 + result_other_filename = "other-#{@file.source_file.blob.filename}.wav"
108 + result_other = SplitfireResult.new(audio_file_id: @file.id)
109 + result_other.source_file.attach(io: result_other_file, filename: result_other_filename)
110 + result_other.save
111 + end
112 +
113 + def broadcast_progress(progress)
114 + @file.audio_file_split_status.update_attribute(:processing_progress, progress)
115 +
116 + ActionCable.server.broadcast 'SplitfireChannel', {
117 + audio_file_id: @file.id,
118 + partial_splitfire_row: MyController.render(partial: 'partial_splitfire_row', locals: { audio_file: @file })
119 + }
120 + end
121 +end
backend/musik88-web/app/jobs/spotify_onboarding_export_job.rb new
+95
@@ -0,0 +1,95 @@
1 +# frozen_string_literal: true
2 +
3 +class SpotifyOnboardingExportJob < ApplicationJob
4 + queue_as :default
5 +
6 + def perform(user_id, time_range)
7 + @user = User.find_by_id(user_id)
8 + @time_range = time_range || 'medium_term'
9 + @client = SpotifyClient.new(@user&.spotify)
10 +
11 + spotify_export_artist
12 + spotify_export_tracks
13 + end
14 +
15 + private
16 +
17 + def spotify_export_artist
18 + res = @client.user_top_items_request('artists', @time_range)
19 + response = JSON.parse(res.body)
20 + items = response['items']
21 + # p response
22 + items&.each do |item|
23 + # p "spotify_export_artist: #{item}"
24 + artist = ArtistProvider.find_by(provider_id: item['id'], provider_type: provider_type)
25 + add_artist(item) if artist.nil?
26 + end
27 + end
28 +
29 + def add_artist(item)
30 + genres = item['genres'].nil? ? '' : item['genres'].join(',')
31 +
32 + artist = ArtistProvider.new(user_id: @user.id, provider_id: item['id'],
33 + provider_type: provider_type,
34 + name: item['name'], genres: genres)
35 + return unless artist.save
36 + end
37 +
38 + def spotify_export_tracks
39 + res = @client.user_top_items_request('tracks', @time_range)
40 + response = JSON.parse(res.body)
41 + items = response['items']
42 +
43 + items&.each do |item|
44 + # p "spotify_export_tracks: #{item}"
45 + # Export tracks from the album.
46 + # Do not export track directly.
47 + export_album_if_possible(item['album'])
48 + end
49 + end
50 +
51 + def export_artists_from_tracks(artists)
52 + artists.each do |artist|
53 + artist_provider = ArtistProvider.find_by(provider_id: artist['id'],
54 + provider_type: provider_type)
55 + add_artist(artist) if artist_provider.nil?
56 + end
57 + end
58 +
59 + def export_album_if_possible(album)
60 + album_provider = AlbumProvider.find_by(provider_id: album['id'],
61 + provider_type: provider_type)
62 + if album_provider.nil?
63 + album_provider = AlbumProvider.new(user_id: @user.id, provider_id: album['id'],
64 + provider_type: provider_type,
65 + name: album['name'])
66 + return unless album_provider.save
67 + end
68 +
69 + # ActiveRecord id, Spotify id
70 + export_album_tracks(album_provider.id, album_provider.provider_id)
71 + end
72 +
73 + def export_album_tracks(album_id, album_provider_id)
74 + res = @client.album_tracks_request(album_provider_id)
75 + response = JSON.parse(res.body)
76 + items = response['items']
77 +
78 + items&.each do |item|
79 + export_song_if_possible(item, album_id)
80 + end
81 + end
82 +
83 + def export_song_if_possible(song, _album_id)
84 + song_provider = SongProvider.find_by(provider_id: song['id'], provider_type: provider_type)
85 + if song_provider.nil?
86 + song_provider = SongProvider.new(user_id: @user.id, provider_id: song['id'], provider_type: provider_type,
87 + name: song['name'], preview_url: song['preview_url'])
88 + song_provider.save
89 + end
90 + end
91 +
92 + def provider_type
93 + ArtistProvider.provider_types[:spotify]
94 + end
95 +end
backend/musik88-web/app/jobs/spotify_sync_export_job.rb new
+103
@@ -0,0 +1,103 @@
1 +# frozen_string_literal: true
2 +
3 +class SpotifySyncExportJob < ApplicationJob
4 + def perform(user_id)
5 + @user = User.find_by_id(user_id)
6 + @client = SpotifyClient.new(@user&.spotify)
7 +
8 + spotify_export_recently_played
9 + end
10 +
11 + private
12 +
13 + def spotify_export_recently_played
14 + res = @client.user_recently_played_request
15 + response = JSON.parse(res.body)
16 + # p response
17 + items = response['items']
18 +
19 + items&.each do |item|
20 + # Export tracks and artists from the album.
21 + # Do not export them directly.
22 + export_album_if_possible(item['track']['album'])
23 + end
24 + end
25 +
26 + def export_album_if_possible(album)
27 + album_provider = AlbumProvider.find_by(provider_id: album['id'],
28 + provider_type: provider_type)
29 + if album_provider.nil?
30 + album_provider = AlbumProvider.new(user_id: @user.id, provider_id: album['id'],
31 + provider_type: provider_type,
32 + name: album['name'])
33 + return unless album_provider.save
34 + end
35 +
36 + # ActiveRecord id, Spotify id
37 + export_album_tracks(album_provider.id, album_provider.provider_id)
38 + end
39 +
40 + def export_album_tracks(album_id, album_provider_id)
41 + res = @client.album_tracks_request(album_provider_id)
42 + response = JSON.parse(res.body)
43 + items = response['items']
44 +
45 + items&.each do |item|
46 + export_song_if_possible(item)
47 + end
48 + end
49 +
50 + def export_song_if_possible(song)
51 + song_provider = SongProvider.find_by(provider_id: song['id'], provider_type: provider_type)
52 + if song_provider.nil?
53 + song_provider = SongProvider.new(user_id: @user.id, provider_id: song['id'], provider_type: provider_type,
54 + name: song['name'], preview_url: song['preview_url'])
55 + song_provider.save
56 + end
57 +
58 + # Export all artists for this song, and add it to song_artists table
59 + export_song_artists(song_provider.id, song['artists'])
60 + end
61 +
62 + def export_song_artists(song_provider_id, artists)
63 + artists&.each do |artist|
64 + artist_provider = ArtistProvider.find_by(provider_id: artist['id'], provider_type: provider_type)
65 + if artist_provider.nil?
66 + export_song_artist(artist['id'], song_provider_id)
67 + next
68 + end
69 +
70 + add_song_artist_if_needed(song_provider_id, artist_provider.id)
71 + end
72 + end
73 +
74 + def add_song_artist_if_needed(song_provider_id, _artist_provider_id)
75 + song_artist = SongArtist.find_by(song_provider_id: song_provider_id)
76 + return unless song_artist.nil?
77 +
78 + song_artist = SongArtist.new(song_provider_id: song_provider_id)
79 + song_artist.save
80 + end
81 +
82 + def export_song_artist(artist_id, song_provider_id)
83 + response = @client.artist_detail_request(artist_id)
84 + response_json = JSON.parse(response.body)
85 + # p response_json
86 + add_artist_provider(response_json, song_provider_id)
87 + end
88 +
89 + def add_artist_provider(artist, song_provider_id)
90 + genres = artist['genres'].nil? ? '' : artist['genres'].join(',')
91 +
92 + artist = ArtistProvider.new(user_id: @user.id, provider_id: artist['id'],
93 + provider_type: provider_type,
94 + name: artist['name'], genres: genres)
95 + return unless artist.save
96 +
97 + add_song_artist_if_needed(song_provider_id, artist.id)
98 + end
99 +
100 + def provider_type
101 + ArtistProvider.provider_types[:spotify]
102 + end
103 +end
backend/musik88-web/app/jobs/wallet_evm_job.rb new
+47
@@ -0,0 +1,47 @@
1 +class WalletEvmJob < ApplicationJob
2 + queue_as :default
3 +
4 + def perform(user_id)
5 + @user = User.find_by_id(user_id)
6 + return unless @user
7 +
8 + change_to_working_directory
9 + generate_wallet
10 + finish
11 + end
12 +
13 + private
14 +
15 + def change_to_working_directory
16 + working_directory_path = if Rails.env.production?
17 + '/mnt/HC_Volume_15126575/apps/musik88/evm_wallets/'
18 + else
19 + '../evm_wallets_devnet/'
20 + end
21 + Dir.chdir(working_directory_path)
22 +
23 + system("echo 'Change working directory to #{working_directory_path}'")
24 + end
25 +
26 + def generate_wallet
27 + system("echo 'Generating address for #{@user.name}'")
28 +
29 + @filename = "evm_wallet_#{@user.id}.json"
30 + system("hdwallet generate -s ETH > #{@filename}")
31 +
32 + system("echo 'Wallet is generated for #{@user.name}'")
33 + end
34 +
35 + def finish
36 + wallet_information = JSON.parse(File.read(@filename))
37 +
38 + wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:evm])
39 + if wallet.nil?
40 + system("echo 'Wallet for #{@user.name} does not exist.'")
41 + return
42 + end
43 + wallet.update(pubkey: wallet_information['addresses']['p2pkh'], seed_phrase: wallet_information['mnemonic'],
44 + status: :done)
45 + wallet.save
46 + end
47 +end
backend/musik88-web/app/jobs/wallet_solana_job.rb new
+73
@@ -0,0 +1,73 @@
1 +class WalletSolanaJob < ApplicationJob
2 + queue_as :default
3 +
4 + def perform(user_id)
5 + @user = User.find_by_id(user_id)
6 + return unless @user
7 +
8 + change_to_working_directory
9 + generate_wallet
10 + setup_bach_token_address
11 + finish
12 + end
13 +
14 + private
15 +
16 + def finish
17 + wallet = Wallet.find_by(user_id: @user.id, wallet_type: Wallet.wallet_types[:solana])
18 + if wallet.nil?
19 + system("echo 'Wallet for #{@user.name} does not exist.'")
20 + return
21 + end
22 + wallet.update(pubkey: @pubkey, seed_phrase: @seed_phrase, bach_token_account: @bach_token_account, status: :done)
23 + wallet.save
24 + end
25 +
26 + def generate_wallet
27 + system("echo 'Generating address for #{@user.name}'")
28 +
29 + output = `solana-keygen new --outfile ./#{@user.id}-keypair.json --no-bip39-passphrase`
30 + output_array = output.split("\n")
31 + @pubkey = output_array[3].split(' ').last
32 + @seed_phrase = output_array[6]
33 +
34 + system("echo 'Wallet is generated for #{@user.name}'")
35 + end
36 +
37 + def change_to_working_directory
38 + working_directory_path = if Rails.env.production?
39 + '/mnt/HC_Volume_15126575/apps/musik88/solana_wallets/'
40 + else
41 + '../solana_wallets_devnet/'
42 + end
43 + Dir.chdir(working_directory_path)
44 +
45 + system("echo 'Change working directory to #{working_directory_path}'")
46 + end
47 +
48 + def setup_bach_token_address
49 + define_bach_token_id
50 + setup_solana
51 + output = `spl-token transfer --fund-recipient --allow-unfunded-recipient #{@bach_token_id} 0.001 #{@pubkey}`
52 + output_array = output.split("\n")
53 + @bach_token_account = output_array[3].split(' ').last
54 + end
55 +
56 + def setup_solana
57 + if Rails.env.production?
58 + system('solana config set --keypair ~/.config/solana/main.json')
59 + system('solana config set --url https://api.mainnet-beta.solana.com')
60 + else
61 + system('solana config set --keypair ~/solana-wallets/main.json')
62 + system('solana config set --url https://api.devnet.solana.com')
63 + end
64 + end
65 +
66 + def define_bach_token_id
67 + @bach_token_id = if Rails.env.production?
68 + 'CTQBjyrX8pYyqbNa8vAhQfnRXfu9cUxnvrxj5PvbzTmf'
69 + else
70 + 'DENNuKzCcrLhEtxZ8tm7nSeef8qvKgGGrdxX6euNkNS7'
71 + end
72 + end
73 +end
backend/musik88-web/app/jobs/youtube_to_audio_job.rb new
+75
@@ -0,0 +1,75 @@
1 +# frozen_string_literal: true
2 +
3 +# Convert YouTube video to audio.
4 +class YoutubeToAudioJob < ApplicationJob
5 + queue_as :default
6 +
7 + def perform(song_provider_id)
8 + @provider = SongProvider.find_by(id: song_provider_id, provider_type: :youtube)
9 + Sidekiq.logger.info('Starting YouTube to audio conversion...')
10 + return if @provider.nil?
11 +
12 + # Do the split if the file is already attached.
13 + if @provider.audio_file.present? && @provider.audio_file.source_file.attached?
14 + SplitfireJob.perform_later(@provider.audio_file.id)
15 + return
16 + end
17 +
18 + prepare_environments
19 + start_processing
20 + end
21 +
22 + private
23 +
24 + def prepare_environments
25 + Sidekiq.logger.info('Preparing environments...')
26 + @youtube_video_id = @provider.provider_id
27 + @youtube_video_url = @provider.preview_url
28 + @audio_output_file_location = "output/#{@youtube_video_id}.mp3"
29 + end
30 +
31 + def start_processing
32 + change_working_directory
33 + download_and_attach
34 + split
35 + end
36 +
37 + def change_working_directory
38 + Sidekiq.logger.info('Changing working directory...')
39 + system('echo $PWD')
40 + Dir.chdir(ENV['YOUTUBE_DL_WORKING_DIRECTORY'])
41 + broadcast_progress(15)
42 + end
43 +
44 + def download_and_attach
45 + Sidekiq.logger.info('Downloading and attaching...')
46 + video_output_file_format = "output/#{@youtube_video_id}.%(ext)s"
47 + system("yt-dlp --extract-audio --audio-format mp3 --output \"#{video_output_file_format}\" #{@youtube_video_url}")
48 + attach
49 + broadcast_progress(30)
50 + end
51 +
52 + def attach
53 + video_title = `yt-dlp --get-title #{@youtube_video_url}`
54 + audio_output_file = File.open(@audio_output_file_location)
55 +
56 + # Attach audio
57 + @provider.audio_file.source_file.attach(io: audio_output_file, filename: video_title)
58 + return unless @provider.audio_file.save
59 +
60 + @provider.audio_file.update(status: :done)
61 + system("rm #{@audio_output_file_location}")
62 + end
63 +
64 + def split
65 + Sidekiq.logger.info('Splitting...')
66 + @provider.audio_file.update(status: :splitting)
67 + broadcast_progress(50)
68 + SplitfireJob.perform_later(@provider.audio_file.id)
69 + end
70 +
71 + def broadcast_progress(progress)
72 + @provider.audio_file.update(progress: progress)
73 + ActionCable.server.broadcast('YoutubeToAudioChannel', @provider.as_json)
74 + end
75 +end
backend/musik88-web/app/mailers/.keep
backend/musik88-web/app/mailers/application_mailer.rb new
+5
@@ -0,0 +1,5 @@
1 +class ApplicationMailer < ActionMailer::Base
2 + default from: 'noreply@musik.com'
3 + layout 'mailer'
4 +end
5 +
backend/musik88-web/app/mailers/devise_mailer.rb new
+10
@@ -0,0 +1,10 @@
1 +class DeviseMailer < Devise::Mailer
2 + helper :application # gives access to all helpers defined within `application_helper`.
3 + include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
4 + default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views
5 +
6 + def confirmation_instructions(record, token, opts = {})
7 + @host = Rails.env.production? ? 'musik88.com' : 'localhost:3001'
8 + super
9 + end
10 +end
backend/musik88-web/app/mailers/user_notifier_mailer.rb new
+41
@@ -0,0 +1,41 @@
1 +class UserNotifierMailer < ApplicationMailer
2 + default from: 'hej@setoelkahfi.se'
3 +
4 + # send a signup email to the user, pass in the user object that contains the user's email address
5 + def send_signup_email(user)
6 + @user = user
7 + mail(
8 + to: @user.email,
9 + subject: t('user_signup_email_subject')
10 + )
11 + end
12 +
13 + def send_reset_password_instructions(user)
14 + @user = user
15 + create_reset_password_token(@user)
16 + mail(
17 + to: @user.email,
18 + subject: t('user_send_reset_password_instructions_subject')
19 + )
20 + end
21 +
22 + def send_wallet_activation_notification(user)
23 + @user = user
24 + @cluster = Rails.env.production? ? '' : '?cluster=devnet'
25 + @host = Rails.env.production? ? 'musik88.com' : 'localhost:3001'
26 + mail(
27 + to: @user.email,
28 + subject: t('user_send_wallet_activation_notification_subject')
29 + )
30 + end
31 +
32 + private
33 +
34 + def create_reset_password_token(user)
35 + raw, hashed = Devise.token_generator.generate(User, :reset_password_token)
36 + @token = raw
37 + user.reset_password_token = hashed
38 + user.reset_password_sent_at = Time.now.utc
39 + user.save
40 + end
41 +end
backend/musik88-web/app/models/.keep
backend/musik88-web/app/models/active_admin_adapter.rb new
+5
@@ -0,0 +1,5 @@
1 +class ActiveAdminAdapter < ActiveAdmin::AuthorizationAdapter
2 + def authorized?(_action, _subject = nil)
3 + user && (user.id == 1 || user.username == 'seto' || user.admin?)
4 + end
5 +end
backend/musik88-web/app/models/album.rb new
+45
@@ -0,0 +1,45 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +class Album < ApplicationRecord
5 + include ActionView::Helpers::DateHelper
6 + include Rails.application.routes.url_helpers
7 +
8 + has_many :album_artists, dependent: :destroy
9 + has_many :artists, -> { order 'album_artists.primary desc, artists.name asc' }, through: :album_artists
10 + accepts_nested_attributes_for :album_artists, allow_destroy: true
11 + has_many :songs
12 +
13 + has_many :comments, as: :commentable
14 +
15 + has_many :album_vote, dependent: :destroy
16 + has_many :users, through: :album_vote
17 +
18 + belongs_to :user
19 +
20 + validates_length_of :name, presence: true, allow_blank: false
21 + validates_length_of :description, presence: true, allow_blank: false
22 +
23 + def slug
24 + "#{name}-#{artists.collect(&:name).join('-')}-#{id}".downcase.parameterize
25 + end
26 +
27 + def as_json(options = {})
28 + options[:methods] = %i[path type created]
29 + super
30 + end
31 +
32 + private
33 +
34 + def type
35 + I18n.t(self.class.name)
36 + end
37 +
38 + def path
39 + album_detail_path(slug)
40 + end
41 +
42 + def created
43 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
44 + end
45 +end
backend/musik88-web/app/models/album_artist.rb new
+4
@@ -0,0 +1,4 @@
1 +class AlbumArtist < ApplicationRecord
2 + belongs_to :album
3 + belongs_to :artist
4 +end
backend/musik88-web/app/models/album_provider.rb new
+39
@@ -0,0 +1,39 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +class AlbumProvider < ApplicationRecord
5 + include ActionView::Helpers::DateHelper
6 + include Rails.application.routes.url_helpers
7 +
8 + belongs_to :user
9 + belongs_to :album
10 +
11 + enum provider_type: %i[
12 + spotify
13 + ]
14 +
15 + def as_json(options = {})
16 + options[:methods] = %i[path type created]
17 + super
18 + end
19 +
20 + def type
21 + I18n.t(self.class.name)
22 + end
23 +
24 + def slug
25 + "#{name}-#{id}".downcase.parameterize
26 + end
27 +
28 + def path
29 + album_bridge_path(slug)
30 + end
31 +
32 + def provider_url
33 + "https://open.spotify.com/album/#{provider_id}"
34 + end
35 +
36 + def created
37 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
38 + end
39 +end
backend/musik88-web/app/models/album_vote.rb new
+4
@@ -0,0 +1,4 @@
1 +class AlbumVote < ApplicationRecord
2 + belongs_to :user
3 + belongs_to :album
4 +end
backend/musik88-web/app/models/application_record.rb new
+3
@@ -0,0 +1,3 @@
1 +class ApplicationRecord < ActiveRecord::Base
2 + self.abstract_class = true
3 +end
backend/musik88-web/app/models/artist.rb new
+44
@@ -0,0 +1,44 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +class Artist < ApplicationRecord
5 + include ActionView::Helpers::DateHelper
6 + include Rails.application.routes.url_helpers
7 +
8 + has_many :album_artists
9 + has_many :album, through: :album_artists
10 + accepts_nested_attributes_for :album_artists
11 +
12 + belongs_to :user
13 +
14 + has_many :comments, as: :commentable
15 +
16 + has_many :artist_vote, dependent: :destroy
17 + has_many :users, through: :artist_vote
18 +
19 + validates_length_of :name, presence: true, allow_blank: false
20 + validates_length_of :description, presence: true, allow_blank: false
21 +
22 + def as_json(options = {})
23 + options[:methods] = %i[path type created]
24 + super
25 + end
26 +
27 + def slug
28 + "#{name}-#{id}".downcase.parameterize
29 + end
30 +
31 + private
32 +
33 + def type
34 + I18n.t(self.class.name)
35 + end
36 +
37 + def path
38 + artist_detail_path(slug)
39 + end
40 +
41 + def created
42 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
43 + end
44 +end
backend/musik88-web/app/models/artist_provider.rb new
+39
@@ -0,0 +1,39 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +class ArtistProvider < ApplicationRecord
5 + include ActionView::Helpers::DateHelper
6 + include Rails.application.routes.url_helpers
7 +
8 + belongs_to :user
9 + belongs_to :artist, optional: true
10 +
11 + enum provider_type: %i[
12 + spotify
13 + ]
14 +
15 + def as_json(options = {})
16 + options[:methods] = %i[path type created]
17 + super
18 + end
19 +
20 + def type
21 + I18n.t(self.class.name)
22 + end
23 +
24 + def slug
25 + "#{name}-#{id}".downcase.parameterize
26 + end
27 +
28 + def path
29 + artist_bridge_path(slug)
30 + end
31 +
32 + def provider_url
33 + "https://open.spotify.com/artist/#{provider_id}"
34 + end
35 +
36 + def created
37 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
38 + end
39 +end
backend/musik88-web/app/models/artist_vote.rb new
+4
@@ -0,0 +1,4 @@
1 +class ArtistVote < ApplicationRecord
2 + belongs_to :user
3 + belongs_to :artist
4 +end
backend/musik88-web/app/models/audio_file.rb new
+170
@@ -0,0 +1,170 @@
1 +# Fix this later
2 +# rubocop:disable Metrics/ClassLength
3 +class AudioFile < ApplicationRecord
4 + enum status: %i[
5 + downloading
6 + splitting
7 + done
8 + ]
9 +
10 + belongs_to :song_provider
11 +
12 + has_many :splitfire_results, dependent: :destroy
13 + has_one :bass_backing_track, dependent: :destroy # Deprecated
14 + has_one :karaoke_file, dependent: :destroy
15 +
16 + has_one :bass_backing_track_file, dependent: :destroy
17 + has_one :guitar_backing_track_file, dependent: :destroy
18 + has_one :drum_backing_track, dependent: :destroy
19 +
20 + # Internal use
21 + has_many :vocal_file, dependent: :nullify
22 + has_many :bass_file, dependent: :nullify
23 + has_many :drum_file, dependent: :nullify
24 + has_many :other_file, dependent: :nullify
25 + # END
26 +
27 + has_many :comments, as: :commentable
28 +
29 + # Split processig status
30 + has_one :audio_file_split_status, dependent: :destroy
31 + # END
32 +
33 + # Bass64 processig status
34 + has_one :audio_file_bass64_status, dependent: :destroy
35 + # END
36 +
37 + # KaroKowe processig status
38 + has_one :audio_file_karokowe_status, dependent: :destroy
39 + # END
40 +
41 + # KaroKowe processig status
42 + has_one :audio_file_bonzo_status, dependent: :destroy
43 + # END
44 +
45 + # ActiveStorage attachments
46 + has_one_attached :source_file
47 +
48 + def is_valid_row?
49 + source_file.attached? || (!youtube_video_url.nil? && !youtube_video_id.nil?)
50 + end
51 +
52 + def is_in_progress?
53 + !audio_file_split_status.nil? && (audio_file_split_status.processing? || audio_file_split_status.youtube_converting?)
54 + end
55 +
56 + def should_show_result_content?
57 + audio_file_split_status.youtube_converting? || splitfire_results.count.positive?
58 + end
59 +
60 + def output_filename
61 + output = ''
62 + output = '<i class="icon-youtube"></i> ' if !youtube_video_url.nil? && !youtube_video_id.nil?
63 +
64 + if audio_file_split_status.youtube_converting?
65 + output += 'Converting YouTube video to mp3'
66 + elsif source_file.attached?
67 + output += source_file.blob.filename.to_s
68 + end
69 +
70 + output
71 + end
72 +
73 + # ActiveAdmin title. See: https://stackoverflow.com/a/8429960/1137814
74 + def name
75 + song_provider&.name
76 + end
77 +
78 + def youtube_thumbnail
79 + "https://img.youtube.com/vi/#{song_provider.provider_id}/mqdefault.jpg"
80 + end
81 +
82 + def splitfire_link
83 + "https://#{host}/@#{user.username_or_id}/#{source_file.filename.to_s.downcase.parameterize}-#{id}"
84 + end
85 +
86 + def splitfire_karaoke_link
87 + "https://#{host}/@#{user.username_or_id}/#{source_file.filename.to_s.downcase.parameterize}-#{id}/karaoke-version"
88 + end
89 +
90 + def splitfire_guitar_backing_track_link
91 + "https://#{host}/@#{user.username_or_id}/#{source_file.filename.to_s.downcase.parameterize}-#{id}/guitar-backing-track-version"
92 + end
93 +
94 + def splitfire_bass_backing_track_link
95 + "https://#{host}/@#{user.username_or_id}/#{source_file.filename.to_s.downcase.parameterize}-#{id}/bass-backing-track-version"
96 + end
97 +
98 + def splitfire_drum_backing_track_link
99 + "https://#{host}/@#{user.username_or_id}/#{source_file.filename.to_s.downcase.parameterize}-#{id}/drum-backing-track-version"
100 + end
101 +
102 + def backing_track_slug
103 + "#{source_file.filename.to_s.downcase.parameterize}-#{id}"
104 + end
105 +
106 + def karaoke_title
107 + I18n.t('song_karaoke_title', filename: source_file.filename)
108 + end
109 +
110 + def guitar_backing_track_title
111 + "#{source_file.filename} Guitar Backing Track" # TODO: Localize
112 + end
113 +
114 + def bass_backing_track_title
115 + "#{source_file.filename} Bass Backing Track" # TODO: Localize
116 + end
117 +
118 + def drum_backing_track_title
119 + "#{source_file.filename} Drum Backing Track" # TODO: Localize
120 + end
121 +
122 + def bass_file
123 + splitfire_results.find do |result|
124 + result.source_file.filename.to_s.start_with?('bass')
125 + end
126 + end
127 +
128 + def vocals_file
129 + splitfire_results.find do |result|
130 + result.source_file.filename.to_s.start_with?('vocals')
131 + end
132 + end
133 +
134 + def drums_file
135 + splitfire_results.find do |result|
136 + result.source_file.filename.to_s.start_with?('drums')
137 + end
138 + end
139 +
140 + def other_file
141 + splitfire_results.find do |result|
142 + result.source_file.filename.to_s.start_with?('other')
143 + end
144 + end
145 +
146 + def piano_file
147 + splitfire_results.find do |result|
148 + result.source_file.filename.to_s.start_with?('piano')
149 + end
150 + end
151 +
152 + def attached?
153 + source_file.attached?
154 + end
155 +
156 + def as_json(options = {})
157 + options[:methods] = [:name]
158 + super
159 + end
160 +
161 + def as_json_packed
162 + as_json(only: %i[id status])
163 + end
164 +
165 + private
166 +
167 + def host
168 + Rails.env.production? ? 'splitfire.ai' : 'localhost:3002'
169 + end
170 +end
backend/musik88-web/app/models/audio_file_bass64_status.rb new
+12
@@ -0,0 +1,12 @@
1 +class AudioFileBass64Status < ApplicationRecord
2 + belongs_to :split_audio_file, optional: true
3 +
4 + # Enum, enum everywhere...
5 + enum status: [
6 + :pending, # Fresly uploaded file.
7 + :processing, # Bass64 processing.
8 + :done, # Bass64 done processing file.
9 + :cancelled, # Bass64 cancelled processing file.
10 + :no_results, # Bass64 The file has been processed before but the result files have been deleted.
11 + ]
12 +end
backend/musik88-web/app/models/audio_file_bonzo_status.rb new
+12
@@ -0,0 +1,12 @@
1 +class AudioFileBonzoStatus < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 +
4 + # Enum, enum everywhere...
5 + enum status: [
6 + :pending, # Fresly uploaded file.
7 + :processing, # Bass64 processing.
8 + :done, # Bass64 done processing file.
9 + :cancelled, # Bass64 cancelled processing file.
10 + :no_results, # Bass64 The file has been processed before but the result files have been deleted.
11 + ]
12 +end
backend/musik88-web/app/models/audio_file_karokowe_status.rb new
+12
@@ -0,0 +1,12 @@
1 +class AudioFileKarokoweStatus < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 +
4 + # Enum, enum everywhere...
5 + enum status: [
6 + :pending, # Fresly uploaded file.
7 + :processing, # Sidekiq is doing its business.
8 + :done, # Done processing file.
9 + :cancelled, # Cancelled processing file.
10 + :no_results, # The file has been processed before but the result files have been deleted.
11 + ]
12 +end
backend/musik88-web/app/models/audio_file_split_status.rb new
+12
@@ -0,0 +1,12 @@
1 +class AudioFileSplitStatus < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 +
4 + # Enum, enum everywhere...
5 + enum status: [
6 + :youtube_converting, # Fresly uploaded file.
7 + :processing, # Sidekiq is doing its business.
8 + :done, # Done processing file.
9 + :cancelled, # Cancelled processing file.
10 + :no_results # The file has been processed before but the result files have been deleted.
11 + ]
12 +end
backend/musik88-web/app/models/authorization.rb new
+14
@@ -0,0 +1,14 @@
1 +class Authorization < ApplicationRecord
2 + belongs_to :user
3 +
4 + enum onboarding_status: [
5 + :pending, # Not onboarding.
6 + :onboarding, # In progress.
7 + :onboarded # Done.
8 + ]
9 +
10 + def update_token(token, refresh_token)
11 + update(token: token)
12 + update(refresh_token: refresh_token)
13 + end
14 +end
backend/musik88-web/app/models/bass_backing_track.rb new
+4
@@ -0,0 +1,4 @@
1 +class BassBackingTrack < ApplicationRecord
2 + belongs_to :audio_file
3 + has_one_attached :source_file
4 +end
backend/musik88-web/app/models/bass_backing_track_file.rb new
+10
@@ -0,0 +1,10 @@
1 +class BassBackingTrackFile < ApplicationRecord
2 + belongs_to :audio_file
3 + has_one_attached :source_file
4 +
5 + # Enum, enum everywhere...
6 + enum status: %i[
7 + processing
8 + done
9 + ]
10 +end
backend/musik88-web/app/models/bass_file.rb new
+4
@@ -0,0 +1,4 @@
1 +class BassFile < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 + has_one_attached :source_file
4 +end
backend/musik88-web/app/models/chord.rb new
+62
@@ -0,0 +1,62 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +class Chord < ApplicationRecord
5 + include Rails.application.routes.url_helpers
6 +
7 + belongs_to :user, optional: true
8 + belongs_to :song_provider, optional: true
9 + validates_length_of :chord, minimum: 10, presence: true, allow_blank: false
10 + validates_length_of :title, presence: true, allow_blank: false
11 +
12 + has_many :chord_vote, dependent: :destroy
13 + has_many :users, through: :chord_vote
14 + has_many :chord_histories, dependent: :destroy
15 +
16 + has_many :comments, as: :commentable
17 +
18 + # Enum, enum everywhere...
19 + enum status: [
20 + :pending, # Freshly submitted.
21 + :approved, # Approved by the community.
22 + :deleted # To be deleted.
23 + ]
24 +
25 + def status_symbol
26 + pending? ? 'bi-clock' : 'bi-check2-circle'
27 + end
28 +
29 + def status_color
30 + pending? ? 'text-warning' : 'text-success'
31 + end
32 +
33 + def user_or_nowhereman
34 + user || User.find_by(username: 'nowhereman')
35 + end
36 +
37 + def link_title
38 + if song_provider.present?
39 + song_provider.name.to_s
40 + else
41 + title
42 + end
43 + end
44 +
45 + def slug
46 + slug = if song_provider.present?
47 + "#{song_provider.name}-#{id}"
48 + else
49 + "#{title}-#{id}"
50 + end
51 + slug.downcase.parameterize
52 + end
53 +
54 + def path
55 + guitar_chord_detail_path(slug)
56 + end
57 +
58 + # ActiveAdmin title. See: https://stackoverflow.com/a/8429960/1137814
59 + def name
60 + song_provider.present? ? song_provider.name : title
61 + end
62 +end
backend/musik88-web/app/models/chord_history.rb new
+5
@@ -0,0 +1,5 @@
1 +class ChordHistory < ApplicationRecord
2 + belongs_to :chord
3 + belongs_to :user
4 + validates_length_of :name, presence: true, allow_blank: false
5 +end
backend/musik88-web/app/models/chord_shape.rb new
+2
@@ -0,0 +1,2 @@
1 +class ChordShape < ApplicationRecord
2 +end
backend/musik88-web/app/models/chord_vote.rb new
+4
@@ -0,0 +1,4 @@
1 +class ChordVote < ApplicationRecord
2 + belongs_to :chord
3 + belongs_to :user
4 +end
backend/musik88-web/app/models/comment.rb new
+8
@@ -0,0 +1,8 @@
1 +class Comment < ApplicationRecord
2 + belongs_to :user
3 +
4 + belongs_to :commentable, polymorphic: true
5 + has_many :comments, as: :commentable
6 +
7 + validates :body, :user_id, presence: true
8 +end
backend/musik88-web/app/models/concerns/.keep
backend/musik88-web/app/models/drum_backing_track.rb new
+10
@@ -0,0 +1,10 @@
1 +class DrumBackingTrack < ApplicationRecord
2 + belongs_to :audio_file
3 + has_one_attached :source_file
4 +
5 + # Enum, enum everywhere...
6 + enum status: %i[
7 + processing
8 + done
9 + ]
10 +end
backend/musik88-web/app/models/drum_file.rb new
+4
@@ -0,0 +1,4 @@
1 +class DrumFile < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 + has_one_attached :source_file
4 +end
backend/musik88-web/app/models/gending_notation_vote.rb new
+4
@@ -0,0 +1,4 @@
1 +class GendingNotationVote < ApplicationRecord
2 + belongs_to :user
3 + belongs_to :javanese_gending_notation
4 +end
backend/musik88-web/app/models/gending_vote.rb new
+4
@@ -0,0 +1,4 @@
1 +class GendingVote < ApplicationRecord
2 + belongs_to :user
3 + belongs_to :javanese_gending
4 +end
backend/musik88-web/app/models/guitar_backing_track_file.rb new
+10
@@ -0,0 +1,10 @@
1 +class GuitarBackingTrackFile < ApplicationRecord
2 + belongs_to :audio_file
3 + has_one_attached :source_file
4 +
5 + # Enum, enum everywhere...
6 + enum status: %i[
7 + processing
8 + done
9 + ]
10 +end
backend/musik88-web/app/models/javanese_gending.rb new
+40
@@ -0,0 +1,40 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +class JavaneseGending < ApplicationRecord
5 + include ActionView::Helpers::DateHelper
6 + include Rails.application.routes.url_helpers
7 +
8 + belongs_to :user
9 + has_many :javanese_gending_notation
10 + has_many :comments, as: :commentable
11 + has_many :gending_vote, dependent: :destroy
12 + has_many :users, through: :gending_vote
13 +
14 + def user_or_nowhereman
15 + user || User.find_by(username: 'nowhereman')
16 + end
17 +
18 + def slug
19 + "#{name.downcase.parameterize}-#{id.to_s}"
20 + end
21 +
22 + def as_json(options = {})
23 + options[:methods] = %i[path type created]
24 + super
25 + end
26 +
27 + private
28 +
29 + def created
30 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
31 + end
32 +
33 + def path
34 + javanese_gending_detail_path(slug)
35 + end
36 +
37 + def type
38 + I18n.t(self.class.name)
39 + end
40 +end
backend/musik88-web/app/models/javanese_gending_notation.rb new
+53
@@ -0,0 +1,53 @@
1 +# frozen_string_literal: true
2 +
3 +require 'action_view'
4 +require 'action_view/helpers'
5 +
6 +# Notation of a Javanese gending
7 +# Not to be confused with JavaneseGending itself
8 +
9 +class JavaneseGendingNotation < ApplicationRecord
10 + include ActionView::Helpers::DateHelper
11 + include Rails.application.routes.url_helpers
12 +
13 + belongs_to :user
14 + belongs_to :javanese_gending
15 + has_many :comments, as: :commentable
16 + has_many :gending_notation_vote, dependent: :destroy
17 + has_many :users, through: :gending_notation_vote
18 +
19 + def link_title
20 + name.to_s.downcase
21 + end
22 +
23 + def slug
24 + "#{name}-#{id}".downcase.parameterize
25 + end
26 +
27 + def user_or_nowhereman
28 + user || User.find_by(username: 'nowhereman')
29 + end
30 +
31 + def as_json(options = {})
32 + options[:methods] = %i[path type created]
33 + super
34 + end
35 +
36 + def path
37 + javanese_gending_gamelan_notation_detail_path(slug)
38 + end
39 +
40 + def created
41 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
42 + end
43 +
44 + def as_json_packed
45 + as_json(only: %i[id name rich_format])
46 + end
47 +
48 + private
49 +
50 + def type
51 + I18n.t(self.class.name)
52 + end
53 +end
backend/musik88-web/app/models/jwt_denylist.rb new
+5
@@ -0,0 +1,5 @@
1 +class JwtDenylist < ApplicationRecord
2 + include Devise::JWT::RevocationStrategies::Denylist
3 +
4 + self.table_name = 'jwt_denylist'
5 +end
backend/musik88-web/app/models/karaoke_file.rb new
+8
@@ -0,0 +1,8 @@
1 +class KaraokeFile < ApplicationRecord
2 + belongs_to :audio_file
3 + has_one_attached :source_file
4 +
5 + # User can only love the file once
6 + has_many :karaoke_file_lover, dependent: :destroy
7 + has_many :users, through: :karaoke_file_lover
8 +end
backend/musik88-web/app/models/karaoke_file_lover.rb new
+4
@@ -0,0 +1,4 @@
1 +class KaraokeFileLover < ApplicationRecord
2 + belongs_to :karaoke_file
3 + belongs_to :user
4 +end
backend/musik88-web/app/models/lyric.rb new
+39
@@ -0,0 +1,39 @@
1 +class Lyric < ApplicationRecord
2 + belongs_to :song
3 + belongs_to :user
4 +
5 + has_many :lyric_vote, dependent: :destroy
6 + has_many :users, through: :lyric_vote
7 +
8 + has_many :comments, as: :commentable
9 +
10 + validates :lyric, presence: true
11 +
12 + # Enum, enum everywhere...
13 + enum status: [
14 + :pending, # Freshly submitted.
15 + :approved, # Approved by the community.
16 + :deleted # To be deleted.
17 + ]
18 +
19 + def status_symbol
20 + pending? ? 'bi-clock' : 'bi-check2-circle'
21 + end
22 +
23 + def status_color
24 + pending? ? 'text-warning' : 'text-success'
25 + end
26 +
27 + def user_or_nowhereman
28 + user || User.find_by(username: 'nowhereman')
29 + end
30 +
31 + def title_link
32 + song.title_link
33 + end
34 +
35 + def slug
36 + "#{song.slug}-#{id}"
37 + end
38 +
39 +end
backend/musik88-web/app/models/lyric_synch.rb new
+4
@@ -0,0 +1,4 @@
1 +class LyricSynch < ApplicationRecord
2 + belongs_to :audio_file
3 + belongs_to :user
4 +end
backend/musik88-web/app/models/lyric_vote.rb new
+4
@@ -0,0 +1,4 @@
1 +class LyricVote < ApplicationRecord
2 + belongs_to :lyric
3 + belongs_to :user
4 +end
backend/musik88-web/app/models/merit/badge_rules.rb new
+62
@@ -0,0 +1,62 @@
1 +# Be sure to restart your server when you modify this file.
2 +#
3 +# +grant_on+ accepts:
4 +# * Nothing (always grants)
5 +# * A block which evaluates to boolean (recieves the object as parameter)
6 +# * A block with a hash composed of methods to run on the target object with
7 +# expected values (+votes: 5+ for instance).
8 +#
9 +# +grant_on+ can have a +:to+ method name, which called over the target object
10 +# should retrieve the object to badge (could be +:user+, +:self+, +:follower+,
11 +# etc). If it's not defined merit will apply the badge to the user who
12 +# triggered the action (:action_user by default). If it's :itself, it badges
13 +# the created object (new user for instance).
14 +#
15 +# The :temporary option indicates that if the condition doesn't hold but the
16 +# badge is granted, then it's removed. It's false by default (badges are kept
17 +# forever).
18 +
19 +module Merit
20 + class BadgeRules
21 + include Merit::BadgeRulesMethods
22 +
23 + def initialize
24 + # If it creates user, grant badge
25 + # Should be "current_user" after registration for badge to be granted.
26 + # Find badge by badge_id, badge_id takes presidence over badge
27 + # grant_on 'users#create', badge_id: 7, badge: 'just-registered', to: :itself
28 +
29 + # If it has 10 comments, grant commenter-10 badge
30 + # grant_on 'comments#create', badge: 'commenter', level: 10 do |comment|
31 + # comment.user.comments.count == 10
32 + # end
33 +
34 + # If it has 5 votes, grant relevant-commenter badge
35 + # grant_on 'comments#vote', badge: 'relevant-commenter',
36 + # to: :user do |comment|
37 + #
38 + # comment.votes.count == 5
39 + # end
40 +
41 + # Changes his name by one wider than 4 chars (arbitrary ruby code case)
42 + # grant_on 'registrations#update', badge: 'autobiographer',
43 + # temporary: true, model_name: 'User' do |user|
44 + #
45 + # user.name.length > 4
46 + # end
47 + grant_on 'users#show', badge_id: 1, temporary: true do |user|
48 + !user.username.nil?
49 + end
50 +
51 + # Probaby not the best part to grant the badge.
52 + grant_on 'users#show', badge_id: 2, temporary: true do |user|
53 + user.spotify&.onboarded?
54 + end
55 +
56 + # Probaby not the best part to grant the badge.
57 + grant_on 'users#show', badge_id: 3, temporary: true do |user|
58 + user.google&.onboarded?
59 + end
60 + end
61 + end
62 +end
backend/musik88-web/app/models/merit/point_rules.rb new
+30
@@ -0,0 +1,30 @@
1 +# Be sure to restart your server when you modify this file.
2 +#
3 +# Points are a simple integer value which are given to "meritable" resources
4 +# according to rules in +app/models/merit/point_rules.rb+. They are given on
5 +# actions-triggered, either to the action user or to the method (or array of
6 +# methods) defined in the +:to+ option.
7 +#
8 +# 'score' method may accept a block which evaluates to boolean
9 +# (recieves the object as parameter)
10 +
11 +module Merit
12 + class PointRules
13 + include Merit::PointRulesMethods
14 +
15 + def initialize
16 + # score 10, :on => 'users#create' do |user|
17 + # user.bio.present?
18 + # end
19 + #
20 + # score 15, :on => 'reviews#create', :to => [:reviewer, :reviewed]
21 + #
22 + # score 20, :on => [
23 + # 'comments#create',
24 + # 'photos#create'
25 + # ]
26 + #
27 + # score -10, :on => 'comments#destroy'
28 + end
29 + end
30 +end
backend/musik88-web/app/models/merit/rank_rules.rb new
+31
@@ -0,0 +1,31 @@
1 +# Be sure to restart your server when you modify this file.
2 +#
3 +# 5 stars is a common ranking use case. They are not given at specified
4 +# actions like badges, you should define a cron job to test if ranks are to be
5 +# granted.
6 +#
7 +# +set_rank+ accepts:
8 +# * :+level+ ranking level (greater is better)
9 +# * :+to+ model or scope to check if new rankings apply
10 +# * :+level_name+ attribute name (default is empty and results in 'level'
11 +# attribute, if set it's appended like 'level_#{level_name}')
12 +
13 +module Merit
14 + class RankRules
15 + include Merit::RankRulesMethods
16 +
17 + def initialize
18 + # set_rank :level => 1, :to => Commiter.active do |commiter|
19 + # commiter.repositories.count > 1 && commiter.followers >= 10
20 + # end
21 + #
22 + # set_rank :level => 2, :to => Commiter.active do |commiter|
23 + # commiter.branches.count > 1 && commiter.followers >= 10
24 + # end
25 + #
26 + # set_rank :level => 3, :to => Commiter.active do |commiter|
27 + # commiter.branches.count > 2 && commiter.followers >= 20
28 + # end
29 + end
30 + end
31 +end
backend/musik88-web/app/models/meta.rb new
+2
@@ -0,0 +1,2 @@
1 +class Meta < ApplicationRecord
2 +end
backend/musik88-web/app/models/other_file.rb new
+4
@@ -0,0 +1,4 @@
1 +class OtherFile < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 + has_one_attached :source_file
4 +end
backend/musik88-web/app/models/page.rb new
+11
@@ -0,0 +1,11 @@
1 +class Page < ActiveRecord::Base
2 + validates_length_of :page_slug, presence: true, allow_blank: false
3 + validates_length_of :page_content, presence: true, allow_blank: false
4 +
5 + has_many :comments, as: :commentable
6 +
7 + enum category: %i[
8 + about
9 + what_is_this
10 + ]
11 +end
backend/musik88-web/app/models/relationship.rb new
+6
@@ -0,0 +1,6 @@
1 +class Relationship < ApplicationRecord
2 + belongs_to :follower, class_name: 'User'
3 + belongs_to :followed, class_name: 'User'
4 + validates :follower_id, presence: true
5 + validates :followed_id, presence: true
6 +end
backend/musik88-web/app/models/search_term.rb new
+2
@@ -0,0 +1,2 @@
1 +class SearchTerm < ApplicationRecord
2 +end
backend/musik88-web/app/models/song.rb new
+76
@@ -0,0 +1,76 @@
1 +require 'action_view'
2 +require 'action_view/helpers'
3 +
4 +# Our representation of a song.
5 +# Not to be confused with a song from a provider.
6 +class Song < ApplicationRecord
7 + include ActionView::Helpers::DateHelper
8 + include Rails.application.routes.url_helpers
9 +
10 + belongs_to :album, optional: true
11 + belongs_to :user
12 +
13 + has_many :song_artists, dependent: :destroy
14 + has_many :artists, through: :song_artists
15 + has_many :song_chord_plea, dependent: :destroy
16 + has_many :users, through: :song_chord_plea
17 +
18 + accepts_nested_attributes_for :song_artists, allow_destroy: true
19 +
20 + has_many :lyrics, dependent: :destroy
21 + has_many :chords, dependent: :destroy
22 +
23 + has_many :comments, as: :commentable
24 +
25 + # Song providers
26 + has_many :song_providers
27 + has_many :youtubes, -> { where(provider_type: :youtube) }, class_name: 'SongProvider'
28 + has_many :spotifies, -> { where(provider_type: :spotify) }, class_name: 'SongProvider'
29 +
30 + # More than one user may upload the same file from a song.
31 + has_many :audio_file
32 +
33 + has_many :audio_files
34 +
35 + validates_length_of :album, presence: true, allow_blank: false
36 + validates_length_of :name, presence: true, allow_blank: false
37 + validates_length_of :description, presence: true, allow_blank: false
38 +
39 + def as_json(options = {})
40 + options[:methods] = %i[songwriters path type created]
41 + super
42 + end
43 +
44 + def title_link
45 + _title_link = name
46 + _title_link += " #{album.artists.first.name}" if album&.artists&.first&.present?
47 + _title_link += " #{album.name}" if album.present?
48 + _title_link
49 + end
50 +
51 + def slug
52 + _slug = name
53 + _slug += "-#{album.artists.first.name}" if album&.artists&.first&.present?
54 + _slug += "-#{album.name}" if album.present?
55 + _slug += "-#{id}"
56 + _slug.downcase.parameterize
57 + end
58 +
59 + private
60 +
61 + def songwriters
62 + artists.as_json(only: %i[id name])
63 + end
64 +
65 + def created
66 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
67 + end
68 +
69 + def path
70 + song_detail_path(slug)
71 + end
72 +
73 + def type
74 + I18n.t(self.class.name)
75 + end
76 +end
backend/musik88-web/app/models/song_artist.rb new
+4
@@ -0,0 +1,4 @@
1 +class SongArtist < ApplicationRecord
2 + belongs_to :song
3 + belongs_to :artist
4 +end
backend/musik88-web/app/models/song_chord_plea.rb new
+4
@@ -0,0 +1,4 @@
1 +class SongChordPlea < ApplicationRecord
2 + belongs_to :song
3 + belongs_to :user
4 +end
backend/musik88-web/app/models/song_provider.rb new
+60
@@ -0,0 +1,60 @@
1 +# frozen_string_literal: true
2 +
3 +require 'action_view'
4 +require 'action_view/helpers'
5 +
6 +class SongProvider < ApplicationRecord
7 + include ActionView::Helpers::DateHelper
8 + include Rails.application.routes.url_helpers
9 +
10 + belongs_to :user
11 + belongs_to :song
12 + belongs_to :album_provider, optional: true
13 + belongs_to :artist_provider, optional: true
14 + # Should only has one chord. All contribution should be done through one chord.
15 + has_one :chord
16 + has_one :audio_file
17 +
18 + has_many :song_provider_vote, dependent: :destroy
19 + has_many :voters, through: :song_provider_vote, source: :user
20 +
21 + enum provider_type: %i[
22 + spotify
23 + youtube
24 + ]
25 +
26 + enum status: %i[
27 + published
28 + unpublished
29 + ]
30 +
31 + def as_json(options = {})
32 + options[:methods] = %i[audio_file path type provider_id provider_type created]
33 + super
34 + end
35 +
36 + def type
37 + I18n.t(self.class.name)
38 + end
39 +
40 + def slug
41 + "#{name}-#{id}".downcase.parameterize
42 + end
43 +
44 + def path
45 + song_bridge_path(slug)
46 + end
47 +
48 + def provider_url
49 + case provider_type
50 + when 'spotify'
51 + "https://open.spotify.com/track/#{provider_id}"
52 + when 'youtube'
53 + "https://www.youtube.com/watch?v=#{provider_id}"
54 + end
55 + end
56 +
57 + def created
58 + "#{time_ago_in_words(created_at)} #{I18n.t('ago')}"
59 + end
60 +end
backend/musik88-web/app/models/song_provider_vote.rb new
+19
@@ -0,0 +1,19 @@
1 +class SongProviderVote < ApplicationRecord
2 + belongs_to :song_provider
3 + belongs_to :user
4 +
5 + enum vote_type: %i[up down]
6 +
7 + def as_json(options = {})
8 + options[:methods] = %i[id voter_gravatar voter_username_or_id]
9 + super
10 + end
11 +
12 + def voter_gravatar
13 + user.gravatar_url
14 + end
15 +
16 + def voter_username_or_id
17 + user.username_or_id
18 + end
19 +end
backend/musik88-web/app/models/splitfire_result.rb new
+7
@@ -0,0 +1,7 @@
1 +# frozen_string_literal: true
2 +
3 +class SplitfireResult < ApplicationRecord
4 + belongs_to :audio_file
5 +
6 + has_one_attached :source_file
7 +end
backend/musik88-web/app/models/user.rb new
+225
@@ -0,0 +1,225 @@
1 +class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
2 + # Virtual attribute to skip password validation while saving new user from
3 + # a wallet connection.
4 + attr_accessor :skip_password_validation
5 +
6 + has_merit
7 +
8 + # Include default devise modules. Others available are:
9 + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
10 + devise :invitable, :database_authenticatable,
11 + :registerable,
12 + :confirmable,
13 + :recoverable,
14 + :rememberable,
15 + :validatable,
16 + :jwt_authenticatable,
17 + jwt_revocation_strategy: JwtDenylist
18 +
19 + before_save { email.downcase! }
20 +
21 + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
22 + validates :email, presence: true,
23 + length: { maximum: 255 },
24 + format: { with: VALID_EMAIL_REGEX },
25 + uniqueness: { case_sensitive: false }
26 +
27 + validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
28 + devise :invitable, :omniauthable, omniauth_providers: %i[facebook spotify google_oauth2 apple]
29 +
30 + has_many :chords, dependent: :destroy
31 + has_many :lyrics, dependent: :destroy
32 + has_many :songs, dependent: :destroy
33 + has_many :albums, dependent: :destroy
34 + has_many :artists, dependent: :destroy
35 + has_many :javanese_gendings, dependent: :destroy
36 + has_many :comments, dependent: :delete_all
37 +
38 + # Invitable
39 + has_many :invitees, class_name: 'User', foreign_key: :invited_by_id
40 +
41 + scope :all_public, -> { where(is_private: false).all }
42 +
43 + has_many :active_relationships, class_name: 'Relationship',
44 + foreign_key: 'follower_id',
45 + dependent: :destroy
46 + has_many :passive_relationships, class_name: 'Relationship',
47 + foreign_key: 'followed_id',
48 + dependent: :destroy
49 + has_many :following, through: :active_relationships, source: :followed
50 + has_many :followers, through: :passive_relationships, source: :follower
51 +
52 + has_many :authorizations, dependent: :destroy
53 +
54 + has_many :song_chord_pleas, dependent: :destroy
55 + has_many :javanese_gending_notations, dependent: :destroy
56 +
57 + has_one :user_setting, dependent: :destroy
58 + has_many :wallets, dependent: :destroy
59 +
60 + has_many :comments, as: :commentable
61 +
62 + # Follows a user.
63 + def follow(other_user)
64 + active_relationships.create(followed_id: other_user.id)
65 + end
66 +
67 + # Unfollows a user.
68 + def unfollow(other_user)
69 + active_relationships.find_by(followed_id: other_user.id).destroy
70 + end
71 +
72 + # Returns true if the current user is following the other user.
73 + def following?(other_user)
74 + following.include?(other_user)
75 + end
76 +
77 + # Returns the hash digest of the given string.
78 + def self.digest(string)
79 + cost = if ActiveModel::SecurePassword.min_cost
80 + BCrypt::Engine::MIN_COST
81 + else
82 + BCrypt::Engine.cost
83 + end
84 + BCrypt::Password.create(string, cost: cost)
85 + end
86 +
87 + # Returns a random token
88 + def self.new_token
89 + SecureRandom.urlsafe_base64
90 + end
91 +
92 + # Remembers a user in the database for use in persistent sessions
93 + def remember
94 + self.remember_token = User.new_token
95 + update_attribute(:remember_digest, User.digest(remember_token))
96 + end
97 +
98 + # Returns true if the given token matches the digest
99 + def authenticated?(remember_token)
100 + return false if remember_digest.nil?
101 +
102 + BCrypt::Password.new(remember_digest).is_password?(remember_token)
103 + end
104 +
105 + # Forgets a user
106 + def forget
107 + update_attribute(:remember_digest, nil)
108 + end
109 +
110 + # Omniauth helpers
111 + def self.new_with_session(params, session)
112 + super.tap do |user|
113 + if data = session['devise.facebook_data'] && session['devise.facebook_data']['extra']['raw_info'] && user.email.blank?
114 + user.email = data['email']
115 + end
116 + end
117 + end
118 +
119 + def self.from_omniauth(auth)
120 + return_user = where(email: auth.info.email).first
121 + return_user ||= create do |user|
122 + user.name = auth.info.name
123 + user.email = auth.info.email
124 + end
125 + save_or_update_provider(return_user, auth)
126 + return_user
127 + end
128 +
129 + def self.from_omniauth_web3(auth, pubkey_email)
130 + return_user = where(email: pubkey_email).first
131 + # Throw error if we could not find user
132 + raise 'User not found' if return_user.nil?
133 +
134 + save_or_update_provider(return_user, auth)
135 + return_user
136 + end
137 +
138 + def spotify
139 + authorizations.find_by(user_id: id, provider: 'spotify')
140 + end
141 +
142 + def google
143 + authorizations.find_by(user_id: id, provider: 'google_oauth2')
144 + end
145 +
146 + # General helpers
147 +
148 + def email_client
149 + email.split('@').last
150 + end
151 +
152 + def need_onboarding?
153 + username.presence.nil? || need_onboarding_spotify? || need_onboarding_google?
154 + end
155 +
156 + def need_onboarding_spotify?
157 + (spotify.nil? || spotify&.pending?)
158 + end
159 +
160 + def need_onboarding_google?
161 + (google.nil? || google&.pending?)
162 + end
163 +
164 + def as_json(options = {})
165 + options[:methods] = %i[gravatar_url followers_count following_count]
166 + super
167 + end
168 +
169 + def as_json_packed
170 + as_json(only: %i[id username name about])
171 + end
172 +
173 + def followers_count
174 + followers.size
175 + end
176 +
177 + def following_count
178 + following.size
179 + end
180 +
181 + def gravatar_url
182 + gravatar_id = Digest::MD5.hexdigest(email.downcase)
183 + "https://secure.gravatar.com/avatar/#{gravatar_id}"
184 + end
185 +
186 + def username_or_id
187 + username.presence ? username : id
188 + end
189 +
190 + def username_or_name
191 + username.presence ? username : name
192 + end
193 +
194 + def splitfire_link(item)
195 + "#{ENV['SF_HOST']}/@#{username_or_id}/#{item.source_file.filename.to_s.downcase.parameterize}-#{item.id}"
196 + end
197 +
198 + def splitfire_profile_link
199 + "#{ENV['SF_HOST']}/@#{username_or_id}"
200 + end
201 +
202 + def verified?
203 + %w[seto splitfire bonzo karokowe bass64 ahmad yovie nowhereman].include? username
204 + end
205 +
206 + def reviewer?
207 + verified?
208 + end
209 +
210 + def self.save_or_update_provider(user, auth)
211 + authorization = user.authorizations.find_by(provider: auth.provider, uid: auth.uid)
212 + authorization ||= Authorization.new(user_id: user.id, provider: auth.provider, uid: auth.uid)
213 + authorization.update_token(auth.credentials.token, auth.credentials.refresh_token)
214 + end
215 +
216 + protected
217 +
218 + # Disable password for wallet connection
219 +
220 + def password_required?
221 + return false if skip_password_validation
222 +
223 + super
224 + end
225 +end
backend/musik88-web/app/models/user_setting.rb new
+30
@@ -0,0 +1,30 @@
1 +class UserSetting < ApplicationRecord
2 + belongs_to :user
3 +
4 + enum locale: I18n.available_locales
5 +
6 + enum model: %i[
7 + 2stems
8 + 4stems
9 + 5stems
10 + ]
11 +
12 + enum frequency: %i[
13 + 11kHz
14 + 16kHz
15 + ]
16 +
17 + def update_settings(type, value)
18 + value = ActiveModel::Type::Boolean.new.cast(value)
19 + case type
20 + when 'sync_spotify'
21 + update(sync_spotify: value)
22 + when 'allowed_use_google_token'
23 + update(allowed_use_google_token: value)
24 + when 'allowed_use_spotify_token'
25 + update(allowed_use_spotify_token: value)
26 + when 'locale'
27 + update(locale: value)
28 + end
29 + end
30 +end
backend/musik88-web/app/models/vocal_file.rb new
+4
@@ -0,0 +1,4 @@
1 +class VocalFile < ApplicationRecord
2 + belongs_to :audio_file, optional: true
3 + has_one_attached :source_file
4 +end
backend/musik88-web/app/models/wallet.rb new
+6
@@ -0,0 +1,6 @@
1 +class Wallet < ApplicationRecord
2 + belongs_to :user
3 +
4 + enum status: %i[pending done]
5 + enum wallet_type: %i[evm solana]
6 +end
backend/musik88-web/app/services/spotify_client.rb new
+99
@@ -0,0 +1,99 @@
1 +# frozen_string_literal: true
2 +
3 +class SpotifyClient
4 + BASE_URI = 'https://api.spotify.com/v1'
5 + BASE_URI_ACCOUNT = 'https://accounts.spotify.com/api'
6 + LIMIT = 50
7 +
8 + def initialize(authorization)
9 + raise 'Authorization is required' if authorization.nil?
10 +
11 + @authorization = authorization
12 + @token = authorization.token
13 + end
14 +
15 + def user_top_items_request(type, time_range)
16 + uri = "#{BASE_URI}/me/top/#{type}"
17 + query = { 'time_range' => time_range, 'limit' => LIMIT }
18 + header = [
19 + ['Accept', 'application/json'],
20 + ['Content-Type', 'application/json'],
21 + ['Authorization', "Bearer #{@token}"]
22 + ]
23 + client.get(uri, query, header)
24 + end
25 +
26 + def user_recently_played_request
27 + uri = "#{BASE_URI}/me/player/recently-played"
28 + query = { 'limit' => LIMIT }
29 + header = [
30 + ['Accept', 'application/json'],
31 + ['Content-Type', 'application/json'],
32 + ['Authorization', "Bearer #{@token}"]
33 + ]
34 + client.get(uri, query, header)
35 + end
36 +
37 + def album_tracks_request(album_id)
38 + uri = "#{BASE_URI}/albums/#{album_id}/tracks"
39 + query = { 'limit' => LIMIT }
40 + header = [
41 + ['Accept', 'application/json'],
42 + ['Content-Type', 'application/json'],
43 + ['Authorization', "Bearer #{@token}"]
44 + ]
45 + client.get(uri, query, header)
46 + end
47 +
48 + def artist_detail_request(artist_id)
49 + uri = "#{BASE_URI}/artists/#{artist_id}"
50 + get(uri, {})
51 + end
52 +
53 + def get_track(track_id)
54 + uri = "#{BASE_URI}/tracks/#{track_id}"
55 + get(uri, {})
56 + end
57 +
58 + private
59 +
60 + def client
61 + HTTPClient.new
62 + end
63 +
64 + def get(uri, query)
65 + header = [
66 + ['Accept', 'application/json'],
67 + ['Content-Type', 'application/json'],
68 + ['Authorization', "Bearer #{@token}"]
69 + ]
70 + response = client.get(uri, query, header)
71 + return_or_refresh(response, uri, query)
72 + end
73 +
74 + def return_or_refresh(response, uri, query)
75 + return response if response.status != 401
76 +
77 + refresh_access_token do
78 + get(uri, query)
79 + end
80 + end
81 +
82 + def refresh_access_token
83 + header = [
84 + ['Accept', 'application/json'],
85 + ['Content-Type', 'application/x-www-form-urlencoded'],
86 + ['Authorization', "Basic #{Base64.strict_encode64("#{ENV['SPOTIFY_APP_ID']}:#{ENV['SPOTIFY_APP_SECRET']}")}"]
87 + ]
88 + body = { 'grant_type' => 'refresh_token', 'refresh_token' => @authorization.refresh_token }
89 + response = client.post("#{BASE_URI_ACCOUNT}/token", body, header)
90 + update_authorization(JSON.parse(response.body))
91 + yield
92 + end
93 +
94 + def update_authorization(response)
95 + @token = response['access_token']
96 + @authorization.update(token: @token)
97 + @authorization.update(refresh_token: response['refresh_token']) if response['refresh_token'].present?
98 + end
99 +end
backend/musik88-web/app/services/youtube_client.rb new
+153
@@ -0,0 +1,153 @@
1 +require 'google/apis/youtube_v3'
2 +require 'google/api_client/client_secrets'
3 +
4 +# Fix this later
5 +# rubocop:disable Metrics/ClassLength
6 +class YoutubeClient
7 + BASE_URI = 'https://youtube.googleapis.com/youtube/v3'.freeze
8 +
9 + def initialize(authorization)
10 + raise 'Authorization is required' if authorization.nil?
11 +
12 + @authorization = authorization
13 + @token = authorization.token
14 + @youtube_export_playlist_count = 0
15 + @youtube_export_playlist_items_count = 0
16 + end
17 +
18 + def youtube_export_playlist_max_iteration
19 + # If development, set to 2, else 10.
20 + Rails.env.development? ? 2 : 10
21 + end
22 +
23 + # Personal content
24 + def search_personal(_keywords)
25 + results = service.list_searches('snippet', for_mine: false, q: 'dewa', type: 'video',
26 + options: { authorization: auth_client })
27 + p results
28 + end
29 +
30 + def search(keywords)
31 + uri = 'https://www.googleapis.com/youtube/v3/search'
32 + client = HTTPClient.new
33 + query = { 'key' => 'AIzaSyDhs0-L3B6X_oq0cVn7GIPgvmb5--VlecE', 'q' => keywords, 'type' => 'video',
34 + 'part' => 'snippet' }
35 + header = [
36 + ['Accept', 'application/json'],
37 + ['Content-Type', 'application/json']
38 + ]
39 + client.get(uri, query, header)
40 + end
41 +
42 + def user_likes_videos(next_page_token = nil)
43 + query = { 'key' => ENV['GOOGLE_API_KEY'], 'myRating' => 'like', 'part' => 'snippet,contentDetails,statistics',
44 + 'maxResults' => '50', 'pageToken' => next_page_token }
45 + header = [
46 + ['Accept', 'application/json'],
47 + ['Authorization', "Bearer #{@token}"]
48 + ]
49 + client.get("#{BASE_URI}/videos", query, header)
50 + end
51 +
52 + # Get user's playlists list
53 + def export_user_playlist(next_page_token = nil)
54 + @youtube_export_playlist_count += 1
55 + response = get_user_playlist(next_page_token)
56 + response_json = JSON.parse(response.body)
57 + # Return if no playlist found
58 + return if response_json['items'].nil?
59 +
60 + # Loop through all playlists items
61 + response_json['items'].each do |playlists|
62 + # Get playlist items
63 + export_user_playlist_items(playlists['id'])
64 + end
65 +
66 + # Check playlist pagination
67 + if response_json['nextPageToken'].present? && @youtube_export_playlist_count < youtube_export_playlist_max_iteration
68 + export_user_playlist(response_json['nextPageToken'])
69 + end
70 + end
71 +
72 + private
73 +
74 + def client
75 + HTTPClient.new
76 + end
77 +
78 + def service
79 + @service ||= Google::Apis::YoutubeV3::YouTubeService.new
80 + end
81 +
82 + def auth_client
83 + @auth_client ||= Signet::OAuth2::Client.new(access_token: @token)
84 + end
85 +
86 + def get_user_playlist(next_page_token = nil)
87 + query = { 'key' => ENV['GOOGLE_API_KEY'], 'maxResults' => '50', 'mine' => true,
88 + 'pageToken' => next_page_token }
89 + header = [
90 + ['Accept', 'application/json'],
91 + ['Authorization', "Bearer #{@token}"]
92 + ]
93 + client.get("#{BASE_URI}/playlists", query, header)
94 + end
95 +
96 + def export_user_playlist_items(playlist_id, next_page_token = nil)
97 + @youtube_export_playlist_items_count += 1
98 + response = get_user_playlist_items(playlist_id, next_page_token)
99 + response_json = JSON.parse(response.body)
100 +
101 + # Get video ids from playlist items, separated by comma
102 + video_ids = response_json['items'].map { |item| item['contentDetails']['videoId'] }.join(',')
103 + p "Exporting playlist items: #{video_ids}"
104 + get_videos_from_playlist_video_ids(video_ids)
105 +
106 + # Check playlist items pagination
107 + if response_json['nextPageToken'].present? && @youtube_export_playlist_items_count < youtube_export_playlist_max_iteration
108 + export_user_playlist_items(playlist_id, response_json['nextPageToken'])
109 + end
110 + end
111 +
112 + def get_user_playlist_items(id, next_page_token = nil)
113 + query = { 'key' => ENV['GOOGLE_API_KEY'], 'part' => 'snippet,contentDetails', 'maxResults' => '50',
114 + 'playlistId' => id, 'pageToken' => next_page_token }
115 + header = [
116 + ['Accept', 'application/json'],
117 + ['Authorization', "Bearer #{@token}"]
118 + ]
119 + client.get("#{BASE_URI}/playlistItems", query, header)
120 + end
121 +
122 + def get_videos_from_playlist_video_ids(video_ids)
123 + response = user_videos({ 'id' => video_ids })
124 + response_json = JSON.parse(response.body)
125 + p "Exporting videos from playlist: #{response_json}"
126 + export_youtube_videos(response_json['items'], @authorization.user_id)
127 + end
128 +
129 + def export_youtube_videos(items, user_id)
130 + music_videos = items.select { |item| item['snippet']['categoryId'] == '10' }
131 + music_videos.each do |video|
132 + SongProvider.upsert(
133 + { user_id: user_id, provider_type: :youtube, provider_id: video['id'], name: video['snippet']['title'],
134 + preview_url: "https://www.youtube.com/watch?v=#{video['id']}", description: video['snippet']['description'],
135 + image_url: video['snippet']['thumbnails']['high']['url'] },
136 + unique_by: :index_song_providers_on_provider_id_and_provider_type
137 + )
138 + end
139 + end
140 +
141 + def user_videos(additional_query, next_page_token = nil)
142 + p "Additional query: #{additional_query}"
143 + query = { 'key' => ENV['GOOGLE_API_KEY'], 'part' => 'snippet,contentDetails,statistics',
144 + 'maxResults' => '50', 'pageToken' => next_page_token }
145 + query = query.merge(additional_query)
146 + p "Query: #{query}"
147 + header = [
148 + ['Accept', 'application/json'],
149 + ['Authorization', "Bearer #{@token}"]
150 + ]
151 + client.get("#{BASE_URI}/videos", query, header)
152 + end
153 +end
backend/musik88-web/app/views/about/_partial_splitfire_result.html.erb new
+5
@@ -0,0 +1,5 @@
1 +<div class="progress">
2 + <div id="splitfire-result-progress" class="progress-bar progress-bar-striped bg-danger" role="progressbar" style="width: 0%" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
3 +</div>
4 +<table style="width: 100%">
5 +</table>
\ No newline at end of file
backend/musik88-web/app/views/about/bach.html.erb new
+116
@@ -0,0 +1,116 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5 mb-5">
10 + <div class="col-md-12">
11 + <h1>BACH Tokenomics</h1>
12 + </div>
13 + </div>
14 + <div class="row g-5">
15 + <div class="col-sm-4">
16 + <div class="position-sticky" style="top: 1rem;">
17 + <ul class="list-group list-group-flush">
18 + <li class="list-group-item">
19 + <span class="fst-italic fw-light"><%= t('bach_token_name') %></span><br/>
20 + BACH Token</li>
21 + <li class="list-group-item">
22 + <span class="fst-italic fw-light"><%= t('bach_token_id') %></span><br/>
23 + <div class="badge bg-primary text-wrap text-warning" style="word-break: break-all;">
24 + CTQBjyrX8pYyqbNa8vAhQfnRXfu9cUxnvrxj5PvbzTmf
25 + </div>
26 + </li>
27 + <li class="list-group-item"><%= t('bach_token_account') %></span><br/>
28 + <div class="badge bg-primary text-wrap text-warning" style="word-break: break-all;">
29 + A4Dj3tzEU2V8mkRYXxcu76tQzSVbmppXByWCTFwC8ApN
30 + </div>
31 + </li>
32 + <li class="list-group-item">
33 + <span class="fst-italic fw-light"><%= t('bach_token_address') %><br/>
34 + <div class="badge bg-primary text-wrap text-warning" style="word-break: break-all;">
35 + <%= link_to(
36 + 'Solscan',
37 + 'https://solscan.io/token/CTQBjyrX8pYyqbNa8vAhQfnRXfu9cUxnvrxj5PvbzTmf',
38 + class: 'text-warning',
39 + target: :__blank
40 + ) %>
41 + </div></li>
42 + <li class="list-group-item"><span class="fst-italic fw-light"><%= t('bach_token_symbol') %></span><br/> €BACH</li>
43 + <li class="list-group-item"><span class="fst-italic fw-light"><%= t('bach_token_decimal') %></span><br/> 12</li>
44 + <li class="list-group-item">
45 + <span class="fst-italic fw-light"><%= t('bach_token_fractional') %></span><br/>
46 + st (<%= t('bach_token_semitone') %>)<br/>
47 + 1 €BACH = 1 <%= t('bach_token_trillion') %> st (1.000.000.000.000)
48 + </li>
49 + <li class="list-group-item"><span class="fst-italic fw-light"><%= t('bach_token_supply') %></span><br/> €BACH 10,000,000</li>
50 + <li class="list-group-item"><span class="fst-italic fw-light"><%= t('bach_token_fix_supply') %></span><br/> €BACH 59,103,435</li>
51 + </ul>
52 + </div>
53 + </div>
54 + <div class="col-sm-8">
55 + <%= @page&.page_content&.html_safe %>
56 + <iframe class="rounded text-center mb-3" width="560" height="315" src="https://www.youtube.com/embed/Il5Lk3vyhlg?controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
57 + <h2><%= t('bach_token_supply_cycles') %></h2>
58 + <ul class="list-group mb-3">
59 + <li class="list-group-item">
60 + <span class="badge bg-warning rounded-pill fw-bold text-primary">Do</span> Air on the G String:
61 + <%= link_to(
62 + '€ 4,788',
63 + 'https://solscan.io/tx/2QZxMzxNzJjw8mLeruuMEqszF976K6t3SHXqRjofwJXqMdQhvVNqdW7nVghDBMP2GJgonRcyGWquTinJJyJ7VKxn',
64 + class: 'text-light badge bg-primary rounded-pill fst-italic fw-light float-end',
65 + target: :__blank
66 + ) %>
67 + </li>
68 + <li class="list-group-item"><span class="badge bg-warning rounded-pill fw-bold text-primary">Re</span> Airdrop #1
69 + <%= link_to(
70 + '1',
71 + 'https://solscan.io/account/6JvMs2ZAWLeN4bvajvnmqUwZYpKJmhbmmMd5B1VoAdPn',
72 + class: 'text-light badge bg-primary rounded-pill fst-italic fw-light float-end',
73 + target: :__blank
74 + ) %>
75 + <%= link_to(
76 + '2',
77 + 'https://solscan.io/account/nNoMukHL6abkiNDZjAB6qiBAdsCeXCW7uASRLJqLUAf',
78 + class: 'text-light badge bg-primary rounded-pill fst-italic fw-light float-end',
79 + target: :__blank
80 + ) %>
81 + <%= link_to(
82 + '3',
83 + 'https://solscan.io/account/2mj6s55J2tQP5V8di82NPK33t6oZzWqMmzGVcBTLjGu7',
84 + class: 'text-light badge bg-primary rounded-pill fst-italic fw-light float-end',
85 + target: :__blank
86 + ) %>
87 + </li>
88 + <li class="list-group-item"><span class="badge bg-warning rounded-pill fw-bold text-primary">Mi</span> Mint
89 + <%= link_to(
90 + '€ 990,000',
91 + 'https://solscan.io/account/2mj6s55J2tQP5V8di82NPK33t6oZzWqMmzGVcBTLjGu7',
92 + class: 'text-light badge bg-primary rounded-pill fst-italic fw-light float-end',
93 + target: :__blank
94 + ) %>
95 + </li>
96 + <li class="list-group-item"><span class="badge bg-warning rounded-pill fw-bold text-primary">Fa</span> Airdrop #2 TBD</li>
97 + <li class="list-group-item"><span class="badge bg-warning rounded-pill fw-bold text-primary">Sol</span> TBD</li>
98 + <li class="list-group-item"><span class="badge bg-warning rounded-pill fw-bold text-primary">La</span> TBD</li>
99 + <li class="list-group-item"><span class="badge bg-warning rounded-pill fw-bold text-primary">Ti</span> TBD</li>
100 + </ul>
101 + <% if @page.present? %>
102 + <h2 class="mb-2 mt-2"><%= t('bach_token_discussion') %></h2>
103 + <%= render "shared/flash_message", object: flash %>
104 + <div class="mb-3">
105 + <%= form_for [@page, Comment.new] do |f| %>
106 + <%= f.text_area :body, placeholder: t('add_comment'), rows: "4" , cols: "50%" %><br/>
107 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
108 + <%= f.submit t('add_comment'), class: "btn btn-primary" %>
109 + <% end %>
110 + </div>
111 + <%= render(partial: 'comments/comment', collection: @page&.comments) %>
112 + <% end %>
113 + </div>
114 + </div>
115 + <%= render('shared/partial_breadcrumbs') %>
116 +</main>
backend/musik88-web/app/views/about/bass64_branding.html.erb new
+80
@@ -0,0 +1,80 @@
1 +<% content_for :meta_head do %>
2 + <title>Bass64 🎸 - Artificial Intelligence free bass backing track generator</title>
3 + <meta name="description" content="Bass64 🎸 - Artificial Intelligence free bass backing track generator">
4 + <meta property="og:title" content="Bass64 🎸 - Artificial Intelligence free bass backing track generator" />
5 + <meta property="og:description" content="Bass64 🎸 - Artificial Intelligence free bass backing track generator" />
6 + <meta property="og:url" content="<%= request.base_url %>/bass64" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },
21 + {
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= request.base_url %>/bass64",
26 + "name": "Bass64 🎸"
27 + }
28 + }]
29 + }
30 + </script>
31 +<% end %>
32 +<div class="row">
33 + <!-- BEGIN content -->
34 + <div class="col-md-12">
35 + <%= render("shared/partial_ai_tools", use_header: false, active_menu: 'bass64') %>
36 + <h1>Bass64 🎸 - Bass backing track generator</h1>
37 + <div>
38 + <p class="lead">
39 + Bass64 is an Artificial-Intelligence-(AI)-based bass backing track generator.
40 + </p>
41 + <p class="lead">
42 + <%= link_to("@bass64", profile_path('bass64')) %>
43 + </p>
44 + <p>
45 + When you're logged-in, you can find Bass64 in your <%= link_to("<b><i>profile page</i></b>".html_safe, my_bass64_path) %>.
46 + </p>
47 + <p>
48 + Bass64 can generate a bass backing track from a song.
49 + This tool is provided to you as it is.
50 + We might remove the files as part of our maintenance.
51 + So we advise to download them right away after generating the backing track.
52 + Use it for free for educational purpose.
53 + You can use it for free with an account.
54 + Your account is private by default.
55 + </p>
56 + </div>
57 + <h2>Connect with us</h2>
58 + <div>
59 + <p>Send your feedback through our social channels:</p>
60 + <ul>
61 + <li>
62 + <a href="https://fb.com/bass64.official" target="__blank">Facebook</a>
63 + </li>
64 + <li>
65 + <a href="https://instagram.com/bass64.official" target="__blank">Instagram</a>
66 + </li>
67 + </ul>
68 + </div>
69 + <h2>Changelog</h2>
70 + <div>
71 + <h3><a href="#v1-0-0">Version 1.0.0</a></h3>
72 + <ul>
73 + <li>Initial release.</li>
74 + </ul>
75 + </div>
76 + </div>
77 +</div>
78 +<%= render "shared/partial_breadcrumbs" do %>
79 + <li class="breadcrumb-item"><%= link_to "Bass64", bass64_branding_path %></li>
80 +<% end %>
backend/musik88-web/app/views/about/bonzo_branding.html.erb new
+72
@@ -0,0 +1,72 @@
1 +<% content_for :meta_head do %>
2 + <title>Bonzo 🥁 - Artificial intelligence free drum backing track generator</title>
3 + <meta name="description" content="Bonzo 🥁 - Artificial intelligence free drum backing track generator" />
4 + <meta property="og:title" content="Bonzo 🥁 - Artificial intelligence free drum backing track generator" />
5 + <meta property="og:description" content="Bonzo 🥁 - Artificial intelligence free drum backing track generator" />
6 + <meta property="og:url" content="<%= request.base_url %>/bonzo" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },
21 + {
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= request.base_url %>/bonzo",
26 + "name": "Bonzo 🥁"
27 + }
28 + }]
29 + }
30 + </script>
31 +<% end %>
32 +<div class="row">
33 + <!-- BEGIN content -->
34 + <div class="col-md-12">
35 + <%= render("shared/partial_ai_tools", use_header: false, active_menu: 'bonzo') %>
36 + <h1>Bonzo 🥁 - Drum backing track generator</h1>
37 + <div>
38 + <p class="lead">
39 + Bonzo is an Artificial-Intelligence-(AI)-based drum backing track generator.
40 + </p>
41 + <p class="lead">
42 + <%= link_to("@bonzo", profile_path('bonzo')) %>
43 + </p>
44 + <p>
45 + When you're logged-in, you can find Bonzo in your <%= link_to("<b><i>profile page</i></b>".html_safe, my_bonzo_path) %>.
46 + </p>
47 + <p>
48 + Bonzo can generate a drum backing track from a song.
49 + This tool is provided to you as it is.
50 + We might remove the files as part of our maintenance.
51 + So we advise to download them right away after generating the backing track.
52 + Use it for free for educational purpose.
53 + You can use it for free with an account.
54 + Your account is private by default.
55 + </p>
56 + </div>
57 + <h2>Feedback</h2>
58 + <div>
59 + <p>hej at setoelkahfi dot se</p>
60 + </div>
61 + <h2>Changelog</h2>
62 + <div>
63 + <h3><a href="#v1-0-0">Version 1.0.0</a></h3>
64 + <ul>
65 + <li>Initial release.</li>
66 + </ul>
67 + </div>
68 + </div>
69 +</div>
70 +<%= render "shared/partial_breadcrumbs" do %>
71 + <li class="breadcrumb-item"><%= link_to "Bonzo", bonzo_branding_path %></li>
72 +<% end %>
backend/musik88-web/app/views/about/contact.html.erb new
+40
@@ -0,0 +1,40 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.base_url %>/kontak" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= request.base_url %>/kontak",
25 + "name": "Kontak"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<h1><%= @page_title %></h1>
32 +<div>
33 + <p class="lead">Send us feedback from these social channels below</br></br>
34 +<a href="https://youtube.com/c/tutorialgitar88" target="__blank">YouTube</br>
35 +<a href="https://fb.com/tutorialgitar88" target="__blank">Facebook</a></br>
36 +<a href="https://instagram.com/tutorialgitar88" target="__blank">Instagram</p>
37 +</div>
38 +<%= render "shared/partial_breadcrumbs" do %>
39 + <li class="breadcrumb-item"><%= link_to t("nav_footer_contact"), contact_path %></li>
40 +<% end %>
backend/musik88-web/app/views/about/index.html.erb new
+42
@@ -0,0 +1,42 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.base_url %>/tentang-kami" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "<%= t('site_name') %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= request.base_url %>/tentang-kami",
25 + "name": "Tentang Kami"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<main>
32 + <div class="row g-5">
33 + <div class="col-12">
34 + <h1><%= @page_title %></h1>
35 + <div class="lead"><%= @page.page_content.html_safe if @page %></div>
36 + <%= link_to(t('nav_what_is_this'), what_is_this_path) %>
37 + </div>
38 + </div>
39 + <%= render "shared/partial_breadcrumbs" do %>
40 + <li class="breadcrumb-item"><%= link_to t("nav_footer_about"), about_us_path %></li>
41 + <% end %>
42 +</main>
backend/musik88-web/app/views/about/karokowe_branding.html.erb new
+76
@@ -0,0 +1,76 @@
1 +<% content_for :meta_head do %>
2 + <title>KaroKowe 🎤 - AI-based free vocal remover online</title>
3 + <meta name="description" content="KaroKowe 🎤 - AI-based free vocal remover online">
4 + <meta property="og:title" content="KaroKowe 🎤 - AI-based free vocal remover online" />
5 + <meta property="og:description" content="KaroKowe 🎤 - AI-based free vocal remover online" />
6 + <meta property="og:url" content="<%= request.base_url %>/karokowe" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },
21 + {
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= request.base_url %>/karokowe",
26 + "name": "KaroKowe 🎤"
27 + }
28 + }]
29 + }
30 + </script>
31 +<% end %>
32 +<div class="row">
33 + <!-- BEGIN content -->
34 + <div class="col-md-12">
35 + <%= render("shared/partial_ai_tools", use_header: false, active_menu: 'karokowe') %>
36 + <h1>KaroKowe 🎤 - Make karaoke version of song</h1>
37 + <%=
38 + image_tag(
39 + "https://www.pngkit.com/png/full/151-1512033_studio-microphone-microphone-emoji-jpg.png",
40 + size: "100x200",
41 + style: "display: block;margin-left: auto;margin-right: auto; margin-bottom: 20px"
42 + )
43 + %>
44 + <div>
45 + <p class="lead">
46 + KaroKowe is a karaoke file generator, a free vocal remover tool built with an Artificial Intelligence (AI) software.</a>
47 + </p>
48 + <p class="lead">
49 + <%= link_to("@karokowe", profile_path('karokowe')) %>
50 + </p>
51 + <p>
52 + When you're logged-in, you can find KaroKowe in your <%= link_to("<b><i>profile page</i></b>".html_safe, my_karokowe_path) %>.
53 + </p>
54 + <p>
55 + KaroKowe can generate a <b>karaoke version</b> of a given song in seconds.
56 + It is powered by ad advanced machine learning algorithm to isolate vocals from an audio file and then remove it.
57 + The result is a clean <b>instrumental version</b> of a song.
58 + It goes wihout saying that one should not use this tool for an instrumental song. <b>Sampai Di Sini</b> from <b>Gerarld Situmorang</b> for example.
59 + </p>
60 + </div>
61 + <h2>Send your feedback</h2>
62 + <div>
63 + <p><b><i>hej</i></b> at <b><i>setoelkahfi</i></b> dot <b><i>se</i></b></p>
64 + </div>
65 + <h2>Changelog</h2>
66 + <div>
67 + <h3><a href="#v1-0-0">Version 1.0.0</a></h3>
68 + <ul>
69 + <li>Initial release.</li>
70 + </ul>
71 + </div>
72 +</div>
73 +</div>
74 +<%= render "shared/partial_breadcrumbs" do %>
75 + <li class="breadcrumb-item"><%= link_to "KaroKowe", karokowe_branding_path %></li>
76 +<% end %>
backend/musik88-web/app/views/about/privacy.html.erb new
+38
@@ -0,0 +1,38 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.base_url %>/kebijakan-privasi" />
7 +<% end %>
8 +
9 +<% content_for :breadcrumb do %>
10 + <script type="application/ld+json">
11 + {
12 + "@context": "http://schema.org",
13 + "@type": "BreadcrumbList",
14 + "itemListElement": [{
15 + "@type": "ListItem",
16 + "position": 1,
17 + "item": {
18 + "@id": "<%= request.base_url %>",
19 + "name": "Musik 88"
20 + }
21 + },{
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= request.base_url %>/kebijakan-privasi",
26 + "name": "Kebijakan Privasi"
27 + }
28 + }]
29 + }
30 + </script>
31 +<% end %>
32 +
33 +
34 +<iframe src="https://termsfeed.com/privacy-policy/19b43dd840ee20e73c9e14e5184cc04a" style="border: 0; width: 100%; height: 1000px"></iframe>
35 +
36 +<%= render "shared/partial_breadcrumbs" do %>
37 + <li class="breadcrumb-item"><%= link_to t("nav_footer_privacy"), privacy_policy_path %></li>
38 +<% end %>
backend/musik88-web/app/views/about/splitfire.html.erb new
+105
@@ -0,0 +1,105 @@
1 +<% content_for :meta_head do %>
2 + <title>SplitFire 🔥 is an Artificial Intelligence (AI) software to remove vocal, drums, bass, piano, and guitar from a song.</title>
3 + <meta name="description" content="SplitFire 🔥 is an Artificial Intelligence (AI) software, vocal remover, karaoke file generator, bass backing track generator, drums remover, piano remover.">
4 + <meta property="og:title" content="SplitFire 🔥 is an Artificial Intelligence (AI) software, vocal remover, karaoke file generator, bass backing track generator, drums remover, piano remover." />
5 + <meta property="og:description" content="SplitFire 🔥 is an Artificial Intelligence (AI) software, vocal remover, karaoke file generator, bass backing track generator, drums remover, piano remover." />
6 + <meta property="og:url" content="<%= request.base_url %>/splitfire" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },
21 + {
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= request.base_url %>/splitfire",
26 + "name": "SplitFire 🔥"
27 + }
28 + }]
29 + }
30 + </script>
31 +<% end %>
32 +<!-- BEGIN content -->
33 +<div class="row">
34 + <div class="col-md-12">
35 + <%= render("shared/partial_ai_tools", use_header: false, active_menu: 'splitfire') %>
36 + </div>
37 +</div>
38 +<div class="row">
39 + <div class="col-md-12">
40 + <h1><%= "SplitFire #{image_pack_tag('fire.gif', :size => '34x34', :style => 'vertical-align: top')}".html_safe %></h1>
41 + <p class="lead">
42 + <%= link_to("@splitfire", profile_path('splitfire')) %>
43 + </p>
44 + </div>
45 +</div>
46 +<div class="row">
47 + <div class="col-md-12">
48 + <p class="lead">
49 + SplitFire is an Artificial Intelligence (AI) software that can split vocal, drums, bass, piano, and guitar from a song.
50 + </p>
51 + </div>
52 + <div class="row">
53 + <div class="col-md-12">
54 + <h1>Free Download SplitFire iOS Beta App</h1>
55 + <p class="lead">
56 + <%= link_to(
57 + image_pack_tag(
58 + "Pre-order_on_the_App_Store_Badge_US-UK_RGB_blk_121217.svg",
59 + size: "300x100",
60 + alt: "SplitFire iOS Beta App"
61 + ),
62 + 'https://testflight.apple.com/join/4cb0rDIo',
63 + target: "_blank"
64 + ) %>
65 + </p>
66 + <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/kDfg9GgxV1M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
67 + </div>
68 + </div>
69 + <div class="row" style="margin-top: 20px">
70 + <div class="col-md-12">
71 + <%= render "shared/partial_splitfire_version" %>
72 + </div>
73 + </div>
74 + <div class="row">
75 + <div class="col-md-12">
76 + <a href="javascript:void(0)" id="splitfire-changelog-toggle">
77 + <h2>Changelog</h2>
78 + </a>
79 + <div id="splitfire-changelog" style="display: none">
80 + <b>
81 + <a href="#v1-2-0">Version 1.2.0</a>
82 + </b>
83 + <ul>
84 + <li>Add SplitFire user settings to customize model and frequency of the output files.</li>
85 + <li>Real-time notification of the status of the file being processed (thank you, WebSocket).</li>
86 + </ul>
87 + <b>
88 + <a href="#v1-1-0">Version 1.1.0</a>
89 + </b>
90 + <ul>
91 + <li>Use the 2stems-16kHz model.</li>
92 + </ul>
93 + <b>
94 + <a href="#v1-0-0">Version 1.0.0</a>
95 + </b>
96 + <ul>
97 + <li>Initial release.</li>
98 + <li>Support 2stems parameter.</li>
99 + </ul>
100 + </div>
101 + </div>
102 + </div>
103 + <%= render "shared/partial_breadcrumbs" do %>
104 + <li class="breadcrumb-item"><%= link_to "SplitFire", splitfire_path %></li>
105 + <% end %>
backend/musik88-web/app/views/about/term.html.erb new
+37
@@ -0,0 +1,37 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.base_url %>/ketentuan-penggunaan" />
7 +<% end %>
8 +
9 +<% content_for :breadcrumb do %>
10 + <script type="application/ld+json">
11 + {
12 + "@context": "http://schema.org",
13 + "@type": "BreadcrumbList",
14 + "itemListElement": [{
15 + "@type": "ListItem",
16 + "position": 1,
17 + "item": {
18 + "@id": "<%= request.base_url %>",
19 + "name": "Musik 88"
20 + }
21 + },{
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= request.base_url %>/ketentuan-penggunaan",
26 + "name": "Ketentuan Penggunaan"
27 + }
28 + }]
29 + }
30 + </script>
31 +<% end %>
32 +
33 +<iframe src="https://termsfeed.com/terms-service/71a61af8b7605208f3e7dbd95bb6c113" style="border: 0; width: 100%; height: 1000px"></iframe>
34 +
35 +<%= render "shared/partial_breadcrumbs" do %>
36 + <li class="breadcrumb-item"><%= link_to t("nav_footer_term"), term_of_use_path %></li>
37 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/albums/_partial_aside.html.erb new
+22
@@ -0,0 +1,22 @@
1 +<div class="col-md-4">
2 + <aside>
3 + <div class="bluebox-heading">
4 + <h2><%= t("top_10_albums") %></h2>
5 + </div>
6 + <ul class="news-list">
7 + <% @trending_albums.each_with_index do |album, index| %>
8 + <li>
9 + <h4>
10 + <%= link_to "#{index + 1}. #{album.name}", album_detail_path(album.name.downcase.parameterize + "-" + album.id.to_s) %>
11 + </h4>
12 + <%= link_to t("see_album_detail"),
13 + album_detail_path(album.name.downcase.parameterize + "-" + album.id.to_s),
14 + class: "badge rounded-pill bg-warning" %>
15 + <span style="text-transform: capitalize;font-style: italic;">
16 + <%= t("song_updated_at", date: album.updated_at.strftime("%B %-d, %Y")) %>
17 + </span>
18 + </li>
19 + <% end %>
20 + </ul>
21 + </aside>
22 +</div>
\ No newline at end of file
backend/musik88-web/app/views/albums/_partial_meta.html.erb new
+39
@@ -0,0 +1,39 @@
1 +<div class="row mb-4">
2 + <div class="col-md-12 text-center mb-3">
3 + <% if user_signed_in? && users.include?(current_user) %>
4 + <%= link_to(
5 + '<i class="bi bi-heart-fill"></i>'.html_safe,
6 + album_unvote_path(id: id),
7 + remote: true,
8 + method: :delete,
9 + :id => "ajax_trigger"
10 + )
11 + %>
12 + <% else %>
13 + <%= link_to(
14 + '<i class="bi bi-heart"></i>'.html_safe,
15 + album_vote_path(id: id),
16 + remote: true,
17 + method: :post,
18 + :id => "ajax_trigger"
19 + )
20 + %>
21 + <% end %>
22 + <span class="badge rounded-pill bg-warning text-dark"><%= users.count %></span>
23 + </div>
24 + <div class="col-md-12 text-center">
25 + <i class="bi bi-graph-up-arrow"></i>
26 + <span class="badge rounded-pill bg-warning text-dark"><%= counter %></span>
27 + </div>
28 +</div>
29 +<div class="row">
30 + <div class="col-md-12">
31 + <div class="text-center">
32 + <% if author == nil %>
33 + <%= link_to(t('signup_to_claim'), login_path(redirect_to: request.path), class: 'more-link more-link-author') %>
34 + <% else %>
35 + <%= link_to("#{name_or_username(author)}".html_safe, profile_path_enhanced(author), class: 'more-link more-link-author') %>
36 + <% end %>
37 + </div>
38 + </div>
39 +</div>
\ No newline at end of file
backend/musik88-web/app/views/albums/add.html.erb new
+55
@@ -0,0 +1,55 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5">
10 + <div class="col-md-12">
11 + <h1><%= @page_title %></h1>
12 + <%= render "shared/flash_message", object: flash %>
13 + </div>
14 + </div>
15 + <div class="row g-5">
16 + <div class="col-sm-1">
17 + <div class="position-sticky" style="top: 1rem;">
18 + <div class="text-center mt-4">
19 + <%= link_to(
20 + "<i class='bi bi-journal-check' style='font-size: 1.5rem;'></i>".html_safe,
21 + album_add_path,
22 + title: "How to add chord"
23 + ) %>
24 + </div>
25 + </div>
26 + </div>
27 + <div class="col-sm-11">
28 + <%= form_with model: @album, url: { action: "add" }, html: { :class=> "row g-3" } do |form| %>
29 + <div class="col-md-6">
30 + <%= form.text_field :name, class: "form-control", placeholder: "Type your favorite album title..." %>
31 + <%= form.hidden_field :user_id, value: @user.id if @user.present? %>
32 + </div>
33 + <div class="col-12">
34 + <%= form.text_area(:description, class: "form-control" , rows: 5, placeholder: "Give it a description...") %>
35 + </div>
36 + <div class="col-12">
37 + <%=
38 + "<div class='alert alert-danger' role='alert'>
39 + You are not logged in. #{link_to('@nowhereman', profile_path('nowhereman'))} is going to claim your contribution.
40 + </div>".html_safe unless user_signed_in?
41 + %>
42 + <%= form.submit(t('home_add_album'), class: "btn btn-primary") %>
43 + </div>
44 + <% end %>
45 + </div>
46 + </div>
47 + <%= render 'shared/partial_breadcrumbs' do %>
48 + <li class="breadcrumb-item">
49 + <%= link_to(
50 + t('guitar_chord_add'),
51 + guitar_chord_add_path
52 + ) %>
53 + </li>
54 + <% end %>
55 +</main>
backend/musik88-web/app/views/albums/bridge.html.erb new
+75
@@ -0,0 +1,75 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= album_url %>",
25 + "name": "<%= t("nav_albums") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 4,
30 + "item": {
31 + "@id": "<%= album_bridge_url %>",
32 + "name": "<%= @album.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-md-12 text-center">
41 + <h1 class=""><%= @page_title %></h1>
42 + <%= render('shared/flash_message', object: flash) %>
43 + </div>
44 + </div>
45 + <div class="row g-5">
46 + <div class="col-12">
47 + <div class="text-center mt-4 mb-4">
48 + <%= image_pack_tag(
49 + "image-placeholder-#{rand(0...3)}.jpg",
50 + alt:"#{@album.name} image",
51 + class: "img-thumbnail img-fluid"
52 + ) %>
53 + </div>
54 + </div>
55 + </div>
56 + <div class="row mb-4">
57 + <div class="col-md-12 text-center mb-3">
58 + <%= link_to(
59 + "<i class='bi bi-#{@album.provider_type.to_s}' style='font-size: 3rem; color: blue;'></i>".html_safe,
60 + @album.provider_url,
61 + style: "padding: 0px",
62 + target: '__blank'
63 + ) %>
64 + </div>
65 + </div>
66 + <div class="row mb-3">
67 + <div class="col-md-12 text-center">
68 + This is album bridge page.
69 + </div>
70 + </div>
71 + <%= render 'shared/partial_breadcrumbs' do %>
72 + <li class="breadcrumb-item"><%= link_to t("nav_albums"), album_path %></li>
73 + <li class="breadcrumb-item"><%= link_to @album.name, album_detail_path %></li>
74 + <% end %>
75 +</main>
backend/musik88-web/app/views/albums/detail.html.erb new
+165
@@ -0,0 +1,165 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= album_url %>",
25 + "name": "<%= t("nav_albums") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 2,
30 + "item": {
31 + "@id": "<%= artist_url %>",
32 + "name": "<%= t("nav_artists") %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 3,
37 + "item": {
38 + "@id": "<%= artist_detail_url(@album.artists.first&.name || "NaN") %>",
39 + "name": "<%= @album.artists.first&.name || "NaN" %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 4,
44 + "item": {
45 + "@id": "<%= album_detail_url %>",
46 + "name": "<%= @album.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<main>
53 + <div class="row g-5">
54 + <div class="col-md-12">
55 + <h1><%= @page_title %></h1>
56 + <%= render('shared/flash_message', object: flash) %>
57 + </div>
58 + </div>
59 + <div class="row g-5">
60 + <div class="col-sm-3">
61 + <div class="position-sticky" style="top: 1rem;">
62 + <div class="text-center mt-4 mb-4">
63 + <%= image_pack_tag(
64 + "image-placeholder-#{rand(0...3)}.jpg",
65 + alt:"#{@album.name} image",
66 + class: "img-thumbnail rounded"
67 + ) %>
68 + </div>
69 + <%= render('partial_meta', id: @album.id, users: @album.users, author: @album.user, counter: @album.views_count) %>
70 + </div>
71 + </div>
72 + <div class="col-sm-9">
73 + <div class="row mb-3">
74 + <div class="col-md-12">
75 + <%= raw @album.description %>
76 + </div>
77 + </div>
78 + <div class="row mb-3">
79 + <div class="col-md-12">
80 + <h2><%= @page_title_artist %></h1>
81 + <div class="row mb-3">
82 + <% @album.artists.each_with_index do |artist, index| %>
83 + <div class="col-6 col-sm-3 col-md-2 text-center">
84 + <div class="mt-4 mb-4">
85 + <%= image_pack_tag(
86 + "image-placeholder-#{rand(0...3)}.jpg",
87 + alt:"#{artist.name} image",
88 + class: "img-thumbnail rounded-circle"
89 + ) %>
90 + </div>
91 + <%= link_to(
92 + artist.name,
93 + artist_detail_path(artist.name.downcase.parameterize + "-" + artist.id.to_s),
94 + class: 'badge rounded-pill bg-warning text-dark fst-italic '
95 + ) %>
96 + </div>
97 + <% end %>
98 + </div>
99 + </div>
100 + </div>
101 + <div class="row">
102 + <div class="col-md-12">
103 + <h2><%= @page_title_songs %></h1>
104 + <ul class="list-group">
105 + <% @album.songs.each_with_index do |song, index| %>
106 + <li class="list-group-item <%= index % 2 == 0 ? 'list-group-item-primary' : ' list-group-item-warning' %>">
107 + <%= index + 1 %>.
108 + <%= link_to(
109 + song.name,
110 + song_detail_path(song.slug)
111 + ) %>
112 + <span class="p-2">
113 + <i class='bi bi-graph-up-arrow'></i>
114 + <span class='badge rounded-pill bg-warning text-dark'><%= song.views_count %></span>
115 + </span>
116 + <%= link_to(
117 + '<i class="bi bi-info-circle"></i>'.html_safe,
118 + song_detail_path(song.slug),
119 + title: t('song_menu_aside_info'),
120 + class: 'float-end p-1'
121 + ) %>
122 + <%= link_to(
123 + '<i class="bi bi-music-note-list"></i>'.html_safe,
124 + song_chords_path(song.slug),
125 + title: t('song_menu_aside_chords'),
126 + class: 'float-end p-1'
127 + ) %>
128 + <%= link_to(
129 + '<i class="bi bi-music-note-beamed"></i>'.html_safe,
130 + song_chords_path(song.slug),
131 + title: t('song_menu_aside_audio'),
132 + class: 'float-end p-1'
133 + ) %>
134 + </li>
135 + <% end %>
136 + </ul>
137 + </div>
138 + </div>
139 + <div class="row">
140 + <div class="col-md-12">
141 + <div class="row mt-3">
142 + <h3 id="discussion"><%= t('discussion') %></h3>
143 + <%= render "shared/flash_message", object: flash %>
144 + <div class="mb-3">
145 + <%= form_for [@album, Comment.new] do |f| %>
146 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
147 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
148 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
149 + <% end %>
150 + </div>
151 + <%= render(partial: 'comments/comment', collection: @album.comments) %>
152 + </div>
153 + </div>
154 + </div>
155 + </div>
156 + </div>
157 + <%= render 'shared/partial_breadcrumbs' do %>
158 + <li class="breadcrumb-item"><%= link_to t("nav_albums"), album_path %></li>
159 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_path %></li>
160 + <% if @album.artists.first.present? %>
161 + <li class="breadcrumb-item"><%= link_to @album.artists[0].name, artist_detail_path(artist_friendly_url(@album.artists[0])) %></li>
162 + <% end %>
163 + <li class="breadcrumb-item"><%= link_to @album.name, album_detail_path %></li>
164 + <% end %>
165 +</main>
backend/musik88-web/app/views/albums/index.html.erb new
+81
@@ -0,0 +1,81 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= album_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= album_url %>",
25 + "name": "<%= t("nav_albums") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="row">
32 + <!-- BEGIN content -->
33 + <div class="col-md-8">
34 + <div class="bluebox-heading">
35 + <h1><%= @page.title if @page %></h1>
36 + <div class="lead">
37 + <%= raw @page.page_content if @page %>
38 + </div>
39 + </div>
40 + <div class="bluebox-heading">
41 + <h3><%= t("h2_find_new_albums") %></h3>
42 + </div>
43 + <% @albums.each_with_index do |album, index| %>
44 + <% if (index == 0 || index % 2 == 0) %>
45 + <div class="row content-row mb-3">
46 + <% end %>
47 + <div class="col-md-6">
48 + <section class="featured-post">
49 + <a class="image-wrap" href="<%= album_detail_path(album.name.downcase.parameterize + "-" + album.artists[0].name.downcase.parameterize + "-" + album.id.to_s) %>">
50 + <%= image_pack_tag(
51 + "image-placeholder-#{rand(0...3)}.jpg",
52 + alt:"#{album.name} cover image.",
53 + class: "img-thumbnail rounded mx-auto d-block"
54 + ) %>
55 + <span class="image-details"></span>
56 + </a>
57 + <h2>
58 + <%= link_to album.name,
59 + album_detail_path(album.name.downcase.parameterize + "-" + album.artists[0].name.downcase.parameterize + "-" + album.id.to_s) %>
60 + </h2>
61 + <p><%= strip_tags album.description.truncate 60 %></p>
62 + <div class="bluebox-info-line">
63 + <span class="featured-post-date"><%= album.updated_at.strftime("%B %-d, %Y") %></span>
64 + <%= link_to t("see_album_detail"),
65 + album_detail_path(album_friendly_url(album)),
66 + class: "badge rounded-pill bg-warning" %>
67 + </div>
68 + </section>
69 + </div>
70 + <% if (index == @albums.size - 1 || (index != 0 && index % 2 != 0)) %>
71 + </div>
72 + <% end %>
73 + <% end %>
74 + <%= will_paginate(@albums, class: 'flickr_pagination') %>
75 + </div>
76 + <%= render("partial_aside") %>
77 + <!-- END content -->
78 +</div>
79 +<%= render "shared/partial_breadcrumbs" do %>
80 + <li class="breadcrumb-item"><%= link_to t("nav_albums"), album_path %></li>
81 +<% end %>
backend/musik88-web/app/views/api/v1/welcome/index.html.erb new
+19
@@ -0,0 +1,19 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <h1 class="mb-5">
10 + <i class="bi bi-tools"></i> Application Programming Interface V1
11 + </h1>
12 + <div class="mb-3">
13 + <%= link_to("I'm", profile_path('seto')) %> still actively looking for contributors. <%= link_to('Contact me', 'mailto:hej@setoelkahfi.se') %> if you're interested.
14 + </div>
15 + <%= render 'shared/partial_breadcrumbs' do %>
16 + <li class="breadcrumb-item"><%= link_to('API', api_root_path) %></li>
17 + <li class="breadcrumb-item"><%= link_to('V1', request.url) %></li>
18 + <% end %>
19 +</main>
backend/musik88-web/app/views/api/welcome/index.html.erb new
+20
@@ -0,0 +1,20 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <h1 class="mb-5">
10 + <i class="bi bi-tools"></i> Application Programming Interface
11 + </h1>
12 + <div class="mb-3">
13 + Welcome to Musik88 API documentation.
14 + </div>
15 + <%= link_to('V1', "#{request.url}/v1") %>
16 +
17 + <%= render 'shared/partial_breadcrumbs' do %>
18 + <li class="breadcrumb-item"><%= link_to('API', api_root_path) %></li>
19 + <% end %>
20 +</main>
\ No newline at end of file
backend/musik88-web/app/views/artists/_partial_album_row.html.erb new
+19
@@ -0,0 +1,19 @@
1 +<% if (index == 0 || index % 3 == 0) %>
2 + <div class="row content-row">
3 + <% end %>
4 + <div class="col-md-4">
5 + <div class="album-wrap">
6 + <%= image_pack_tag(
7 + "image-placeholder-#{rand(0...3)}.jpg",
8 + alt:"#{album.name} cover image.",
9 + class: "img-thumbnail rounded mx-auto d-block"
10 + ) %>
11 + <div class="album-caption">
12 + <h4><a href="<%= album_detail_path(album.slug) %>"><%= album.name %></a></h4>
13 + </div>
14 + <a href="<%= album_detail_path(album.slug) %>" class="tracks-list"></a>
15 + </div>
16 + </div>
17 + <% if (index == (@albums.length - 1) || (index + 1) % 3 == 0) %>
18 + </div>
19 +<% end %>
backend/musik88-web/app/views/artists/_partial_aside.html.erb new
+23
@@ -0,0 +1,23 @@
1 +<aside>
2 + <h2 class="mb-4"><%= t("top_10_artists") %></h2>
3 + <% @trending_artists.each_with_index do |artist, index| %>
4 + <div class="row mb-4">
5 + <div class="col-4">
6 + <%= image_pack_tag(
7 + "image-placeholder-#{rand(0...3)}.jpg",
8 + alt:"#{artist.name} image",
9 + class: "img-thumbnail rounded-circle "
10 + )
11 + %>
12 + </div>
13 + <div class="col-8 fs-6">
14 + <h3 class="fs-6 mt-2 mb-3">
15 + <%= "#{index + 1}." %>
16 + <%= link_to artist.name, artist_detail_path(artist.slug) %>
17 + </h3>
18 + <i class="bi bi-graph-up-arrow"></i>
19 + <span class="badge rounded-pill bg-warning text-dark"><%= artist.views_count %></span>
20 + </div>
21 + </div>
22 + <% end %>
23 +</aside>
backend/musik88-web/app/views/artists/_partial_meta.html.erb new
+39
@@ -0,0 +1,39 @@
1 +<div class="row mb-4">
2 + <div class="col-md-12 text-center mb-3">
3 + <% if user_signed_in? && users.include?(current_user) %>
4 + <%= link_to(
5 + '<i class="bi bi-heart-fill"></i>'.html_safe,
6 + artist_unvote_path(id: id),
7 + remote: true,
8 + method: :delete,
9 + :id => "ajax_trigger"
10 + )
11 + %>
12 + <% else %>
13 + <%= link_to(
14 + '<i class="bi bi-heart"></i>'.html_safe,
15 + artist_vote_path(id: id),
16 + remote: true,
17 + method: :post,
18 + :id => "ajax_trigger"
19 + )
20 + %>
21 + <% end %>
22 + <span class="badge rounded-pill bg-warning text-dark"><%= users.count %></span>
23 + </div>
24 + <div class="col-md-12 text-center">
25 + <i class="bi bi-graph-up-arrow"></i>
26 + <span class="badge rounded-pill bg-warning text-dark"><%= counter %></span>
27 + </div>
28 +</div>
29 +<div class="row">
30 + <div class="col-md-12">
31 + <div class="text-center">
32 + <% if author == nil %>
33 + <%= link_to(t('signup_to_claim'), login_path, class: 'more-link more-link-author') %>
34 + <% else %>
35 + <%= link_to("#{name_or_username(author)}".html_safe, profile_path_enhanced(author), class: 'more-link more-link-author') %>
36 + <% end %>
37 + </div>
38 + </div>
39 +</div>
backend/musik88-web/app/views/artists/add.html.erb new
+55
@@ -0,0 +1,55 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5">
10 + <div class="col-md-12">
11 + <h1><%= @page_title %></h1>
12 + <%= render "shared/flash_message", object: flash %>
13 + </div>
14 + </div>
15 + <div class="row g-5">
16 + <div class="col-sm-1">
17 + <div class="position-sticky" style="top: 1rem;">
18 + <div class="text-center mt-4">
19 + <%= link_to(
20 + "<i class='bi bi-journal-check' style='font-size: 1.5rem;'></i>".html_safe,
21 + artist_add_path,
22 + title: "How to add chord"
23 + ) %>
24 + </div>
25 + </div>
26 + </div>
27 + <div class="col-sm-11">
28 + <%= form_with model: @artist, url: { action: "add" }, html: { :class=> "row g-3" } do |form| %>
29 + <div class="col-md-6">
30 + <%= form.text_field :name, class: "form-control", placeholder: "Type your favorite artist name..." %>
31 + <%= form.hidden_field :user_id, value: @user.id if @user.present? %>
32 + </div>
33 + <div class="col-12">
34 + <%= form.text_area(:description, class: "form-control" , rows: 5, placeholder: "Who is this artist? Give it a description...") %>
35 + </div>
36 + <div class="col-12">
37 + <%=
38 + "<div class='alert alert-danger' role='alert'>
39 + You are not logged in. #{link_to('@nowhereman', profile_path('nowhereman'))} is going to claim your contribution.
40 + </div>".html_safe unless user_signed_in?
41 + %>
42 + <%= form.submit(t('home_add_artist'), class: "btn btn-primary") %>
43 + </div>
44 + <% end %>
45 + </div>
46 + </div>
47 + <%= render 'shared/partial_breadcrumbs' do %>
48 + <li class="breadcrumb-item">
49 + <%= link_to(
50 + t('guitar_chord_add'),
51 + guitar_chord_add_path
52 + ) %>
53 + </li>
54 + <% end %>
55 +</main>
backend/musik88-web/app/views/artists/bridge.html.erb new
+85
@@ -0,0 +1,85 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= artist_detail_url %>",
32 + "name": "<%= @artist.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-md-12 text-center">
41 + <h1><%= @page_title %></h1>
42 + <%= render('shared/flash_message', object: flash) %>
43 + </div>
44 + </div>
45 + <div class="row g-5">
46 + <div class="col-12">
47 + <div class="text-center mt-4 mb-4">
48 + <%= image_pack_tag(
49 + "image-placeholder-#{rand(0...3)}.jpg",
50 + alt:"#{@artist.name} image",
51 + class: "img-thumbnail rounded-circle"
52 + ) %>
53 + </div>
54 + </div>
55 + </div>
56 + <div class="row mb-4">
57 + <div class="col-md-12 text-center mb-3">
58 + <%= link_to(
59 + "<i class='bi bi-#{@artist.provider_type.to_s}' style='font-size: 3rem; color: blue;'></i>".html_safe,
60 + @artist.provider_url,
61 + style: 'padding: 0px',
62 + target: '__blank'
63 + ) %>
64 + </div>
65 + </div>
66 + <div class="row mb-3">
67 + <div class="col-md-12 text-center">
68 + This is the bridge page of this artist.
69 + </div>
70 + </div>
71 + <%= render 'shared/partial_breadcrumbs' do %>
72 + <li class="breadcrumb-item">
73 + <%= link_to(
74 + t("nav_artists"),
75 + artist_path
76 + ) %>
77 + </li>
78 + <li class="breadcrumb-item">
79 + <%= link_to(
80 + @artist.name,
81 + artist_detail_path(artist_friendly_url(@artist))
82 + ) %>
83 + </li>
84 + <% end %>
85 +</main>
backend/musik88-web/app/views/artists/detail.html.erb new
+110
@@ -0,0 +1,110 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= artist_detail_url %>",
32 + "name": "<%= @artist.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-md-12">
41 + <h1><%= @page_title %></h1>
42 + <%= render('shared/flash_message', object: flash) %>
43 + </div>
44 + </div>
45 + <div class="row g-5">
46 + <div class="col-sm-3">
47 + <div class="position-sticky" style="top: 1rem;">
48 + <div class="text-center mt-4 mb-4">
49 + <%= image_pack_tag(
50 + "image-placeholder-#{rand(0...3)}.jpg",
51 + alt:"#{@artist.name} image",
52 + class: "img-thumbnail rounded-circle"
53 + ) %>
54 + </div>
55 + <%= render('partial_meta', id: @artist.id, users: @artist.users, author: @artist.user, counter: @artist.views_count) %>
56 + </div>
57 + </div>
58 + <div class="col-sm-9">
59 + <div class="row mb-3">
60 + <div class="col-md-12">
61 + <%= raw @artist.description %>
62 + </div>
63 + </div>
64 + <div class="row">
65 + <div class="col-md-12">
66 + <h2><%= @page_title_album %></h1>
67 + <% @albums.each_with_index do |album, index| %>
68 + <%= render('partial_album_row', album: album, index: index) %>
69 + <% end %>
70 + </div>
71 + </div>
72 + <% if !@albums_contribute_to.blank? %>
73 + <div class="row">
74 + <div class="col-md-12">
75 + <h2><%= @page_title_album_contribute_to %></h1>
76 + <% @albums_contribute_to.each_with_index do |album, index| %>
77 + <%= render('partial_album_row', album: album, index: index) %>
78 + <% end %>
79 + </div>
80 + </div>
81 + <% end %>
82 + <div class="row mt-3">
83 + <h3 id="discussion"><%= t('discussion') %></h3>
84 + <%= render "shared/flash_message", object: flash %>
85 + <div class="mb-3">
86 + <%= form_for [@artist, Comment.new] do |f| %>
87 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
88 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
89 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
90 + <% end %>
91 + </div>
92 + <%= render(partial: 'comments/comment', collection: @artist.comments) %>
93 + </div>
94 + </div>
95 + </div>
96 + <%= render 'shared/partial_breadcrumbs' do %>
97 + <li class="breadcrumb-item">
98 + <%= link_to(
99 + t("nav_artists"),
100 + artist_path
101 + ) %>
102 + </li>
103 + <li class="breadcrumb-item">
104 + <%= link_to(
105 + @artist.name,
106 + artist_detail_path(artist_friendly_url(@artist))
107 + ) %>
108 + </li>
109 + <% end %>
110 +</main>
backend/musik88-web/app/views/artists/index.html.erb new
+76
@@ -0,0 +1,76 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= artist_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<main>
32 + <div class="row g-5">
33 + <div class="col-md-12">
34 + <h1><%= @page.title if @page %></h1>
35 + <%= render('shared/flash_message', object: flash) %>
36 + <%= raw @page.page_content if @page %>
37 + </div>
38 + </div>
39 + <div class="row g-5">
40 + <div class="col-sm-9">
41 + <h2><%= t("h2_find_new_artists") %></h2>
42 + <div class="row mb-3">
43 + <% @artists.each_with_index do |artist, index| %>
44 + <div class="col-6 col-md-4 mb-3">
45 + <div class="card p-2 text-center">
46 + <%= link_to(
47 + image_pack_tag(
48 + "image-placeholder-#{rand(0...3)}.jpg",
49 + alt:"#{artist.name} image",
50 + class: "img-thumbnail rounded-circle mx-auto d-block"
51 + ),
52 + artist_detail_path(artist.slug)
53 + )
54 + %>
55 + <%= link_to(artist.name, artist_detail_path(artist.slug), class: 'mt-2') %>
56 + <div class="card-body">
57 + <p class="card-text"><%= strip_tags artist.description.truncate 60 %></p>
58 + </div>
59 + <div class="card-body">
60 + <span class="badge rounded-pill bg-primary"><%= artist.updated_at.strftime("%B %-d, %Y") %></span>
61 + </div>
62 + </div>
63 + </div>
64 + <% end %>
65 + </div>
66 + <%= will_paginate @artists, class: 'flickr_pagination' %>
67 + </div>
68 + <div class="col-sm-3">
69 + <div class="position-sticky" style="top: 1rem;">
70 + <%= render("partial_aside") %>
71 + </div>
72 + </div>
73 + <%= render "shared/partial_breadcrumbs" do %>
74 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_path %></li>
75 + <% end %>
76 + </main>
backend/musik88-web/app/views/audio_files/_partial_audio_file.html.erb new
+74
@@ -0,0 +1,74 @@
1 +<div class="row mb-5">
2 + <div class="col-sm-4 mb-2">
3 + <%= link_to(
4 + image_tag(
5 + item.youtube_thumbnail,
6 + class: "img-fluid rounded text-center mb-2",
7 + style: "width: 300px;"
8 + ),
9 + item.splitfire_link,
10 + target: :_blank
11 + ) %>
12 + <%= link_to(
13 + "@#{item.user.username_or_id}",
14 + profile_path(item.user),
15 + title: "Author: @#{item.user.username_or_id}"
16 + ) %>
17 + </div>
18 + <div class="col-sm-8">
19 + <ul class="list-group">
20 + <li class="list-group-item">
21 + <i class="bi bi-music-note-list"></i>
22 + <%= link_to(
23 + item.source_file.filename,
24 + item.splitfire_link,
25 + target: :_blank
26 + ) %>
27 + </li>
28 + <li class="list-group-item">
29 + <i class="bi bi-mic-fill"></i>
30 + <%= link_to(
31 + item.karaoke_title,
32 + item.splitfire_karaoke_link,
33 + target: :_blank
34 + ) %>
35 + </li>
36 + <li class="list-group-item">
37 + <i class="bi bi-music-note-list"></i>
38 + <%= link_to(
39 + item.guitar_backing_track_title,
40 + item.guitar_backing_track_file.present? ? item.splitfire_guitar_backing_track_link : guitar_backing_track_path(item.backing_track_slug),
41 + target: :_blank
42 + ) %>
43 + </li>
44 + <li class="list-group-item">
45 + <i class="bi bi-music-note-list"></i>
46 + <%= link_to(
47 + item.bass_backing_track_title,
48 + item.bass_backing_track_file.present? ? item.splitfire_bass_backing_track_link : bass_backing_track_path(item.backing_track_slug),
49 + target: :_blank
50 + ) %>
51 + </li>
52 + <li class="list-group-item">
53 + <i class="bi bi-music-note-list"></i>
54 + <%= link_to(
55 + item.drum_backing_track_title,
56 + item.drum_backing_track.present? ? item.splitfire_drum_backing_track_link : drum_backing_track_path(item.backing_track_slug),
57 + target: :_blank
58 + ) %>
59 + </li>
60 + <% if !item.song.nil? %>
61 + <li class="list-group-item list-group-item-warning">
62 + <div class="row mt-2">
63 + <div class="col-1 text-center">
64 + <i class="bi bi-info-circle-fill align-center"></i>
65 + </div>
66 + <div class="col-11">
67 + <%= render 'users/partial_audio_file_information', audio_file: item %>
68 + </div>
69 + </div>
70 + </li>
71 + <% end %>
72 + </ul>
73 + </div>
74 +</div>
backend/musik88-web/app/views/audio_files/_partial_user.html.erb new
+21
@@ -0,0 +1,21 @@
1 +<div class="row mb-5">
2 + <div class="col-5 mb-2">
3 + <%= gravatar_for user %>
4 + </div>
5 + <div class="col-7 mb-2">
6 + <ul class="list-unstyled">
7 + <li>
8 + <%= link_to(
9 + "@#{user.username_or_id}",
10 + profile_path(user.username_or_id),
11 + title: "Author: @#{user.username_or_id}"
12 + ) %>
13 + </li>
14 + <li><%= user.name %></li>
15 + <li><span class="badge bg-warning rounded-pill text-dark"><%= user.audio_files.size %></span>
16 + &nbsp;
17 + <i class="bi bi-graph-up-arrow"></i>
18 + </li>
19 + </ul>
20 + </div>
21 +</div>
backend/musik88-web/app/views/audio_files/bass_backing_track.html.erb new
+31
@@ -0,0 +1,31 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <%= render "shared/flash_message", object: flash %>
10 + <h1><%= @page_title %></h1>
11 + <div class="row lead">
12 + <div class="col-md-9">
13 + <% if @bass_backing_track.present? && @bass_backing_track.done? %>
14 + <%= link_to "Open in SplitFire", @bass_backing_track.audio_file.splitfire_bass_backing_track_link, class: "btn btn-primary", target: :__blank %>
15 + <% elsif @bass_backing_track.present? && @bass_backing_track.processing? %>
16 + <%= "Processing progress #{@bass_backing_track.status_progress}" %>
17 + <% else %>
18 + <%= 'You need to logged-in to generate this bass backing track.' unless user_signed_in? %>
19 + <%= link_to(t('Generate bass backing track now'), bass_backing_track_path, class: 'btn btn-primary btn-lg px-4', method: :post ) %>
20 + <% end %>
21 + </div>
22 + <div class="col-md-3">
23 + <h2>Top users</h2>
24 + <% @users.each do |user| %>
25 + <%= render 'partial_user', user: user %>
26 + <% end %>
27 + </div>
28 + </div>
29 +
30 + <%= render('shared/partial_breadcrumbs') %>
31 +</main>
backend/musik88-web/app/views/audio_files/drum_backing_track.html.erb new
+31
@@ -0,0 +1,31 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <%= render "shared/flash_message", object: flash %>
10 + <h1><%= @page_title %></h1>
11 + <div class="row lead">
12 + <div class="col-md-9">
13 + <% if @drum_backing_track.present? && @drum_backing_track.done? %>
14 + <%= link_to "Open in SplitFire", @drum_backing_track.audio_file.splitfire_drum_backing_track_link, class: "btn btn-primary", target: :__blank %>
15 + <% elsif @drum_backing_track.present? && @drum_backing_track.processing? %>
16 + <%= "Processing progress #{@drum_backing_track.status_progress}" %>
17 + <% else %>
18 + <%= 'You need to logged-in to generate this drum backing track.' unless user_signed_in? %>
19 + <%= link_to(t('Generate drum backing track now'), drum_backing_track_path, class: 'btn btn-primary btn-lg px-4', method: :post ) %>
20 + <% end %>
21 + </div>
22 + <div class="col-md-3">
23 + <h2>Top users</h2>
24 + <% @users.each do |user| %>
25 + <%= render 'partial_user', user: user %>
26 + <% end %>
27 + </div>
28 + </div>
29 +
30 + <%= render('shared/partial_breadcrumbs') %>
31 +</main>
backend/musik88-web/app/views/audio_files/guitar_backing_track.html.erb new
+31
@@ -0,0 +1,31 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <%= render "shared/flash_message", object: flash %>
10 + <h1><%= @page_title %></h1>
11 + <div class="row lead">
12 + <div class="col-md-9">
13 + <% if @guitar_backing_track.present? && @guitar_backing_track.done? %>
14 + <%= link_to "Open in SplitFire", @guitar_backing_track.audio_file.splitfire_guitar_backing_track_link, class: "btn btn-primary", target: :__blank %>
15 + <% elsif @guitar_backing_track.present? && @guitar_backing_track.processing? %>
16 + <%= "Processing progress #{@guitar_backing_track.status_progress}" %>
17 + <% else %>
18 + <%= 'You need to logged-in to generate this bass backing track.' unless user_signed_in? %>
19 + <%= link_to(t('Generate guitar backing track now'), guitar_backing_track_path, class: 'btn btn-primary btn-lg px-4', method: :post ) %>
20 + <% end %>
21 + </div>
22 + <div class="col-md-3">
23 + <h2>Top users</h2>
24 + <% @users.each do |user| %>
25 + <%= render 'partial_user', user: user %>
26 + <% end %>
27 + </div>
28 + </div>
29 +
30 + <%= render('shared/partial_breadcrumbs') %>
31 +</main>
backend/musik88-web/app/views/audio_files/index.html.erb new
+26
@@ -0,0 +1,26 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <%= render "shared/flash_message", object: flash %>
10 + <h1><%= @page_title %></h1>
11 + <div class="row lead">
12 + <div class="col-md-9">
13 + <% @audio_files.each do |audio_file| %>
14 + <%= render 'partial_audio_file', item: audio_file %>
15 + <% end %>
16 + </div>
17 + <div class="col-md-3">
18 + <h2>Top users</h2>
19 + <% @users.each do |user| %>
20 + <%= render 'partial_user', user: user %>
21 + <% end %>
22 + </div>
23 + </div>
24 +
25 + <%= render('shared/partial_breadcrumbs') %>
26 +</main>
backend/musik88-web/app/views/chords/add.html.erb new
+173
@@ -0,0 +1,173 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5">
10 + <div class="col-md-12">
11 + <h1><%= @page_title %></h1>
12 + <%= render "shared/flash_message", object: flash %>
13 + </div>
14 + </div>
15 + <div class="row g-5">
16 + <div class="col-sm-11">
17 + <%= form_with model: @chord, url: { action: "add" }, html: { :class=> "row g-3" } do |form| %>
18 + <div class="col-md-6">
19 + <%= form.text_field :title, class: "form-control song-title", autocomplete: "off", placeholder: t('add_chord_placeholder_title') %>
20 + <div class="valid-feedback mt-3 mb-3 add-song">
21 + Cannot find the song? <a href="javascript:void(0)" class="add-song-button" data-bs-toggle="modal" data-bs-target="#add-song-modal"><i class="bi bi-file-plus"></i> Click to add it</a>
22 + </div>
23 + <div class="list-group song-list" style="display:none"></div>
24 + <%= form.hidden_field :song_id, class: 'song-id' %>
25 + <%= form.hidden_field :user_id, value: @user.id if @user.present? %>
26 + </div>
27 + <div class="col-md-6">
28 + <p class="song-songwriters"><%= @chord_artists %></p>
29 + </div>
30 + <div class="col-12">
31 + <label for="inputAddress2" class="form-label"><%= t('add_chord_label_chord') %></label>
32 + <%= form.hidden_field :chord, class: 'chord-content' %>
33 + <div id="editor" style="height: 20rem; width: 100%;">
34 + <%= raw(@chord.chord) %>
35 + </div>
36 + </div>
37 + <div class="col-12">
38 + <%=
39 + "<div class='alert alert-danger' role='alert'>
40 + #{t('add_chord_warning', href_login: link_to(t('login'), login_path(redirect_to: guitar_chord_add_path)), href: link_to('@nowhereman', profile_path('nowhereman')))}
41 + </div>".html_safe unless user_signed_in?
42 + %>
43 + <%= form.submit(t('add_chord_share'), class: "btn btn-primary") %>
44 + </div>
45 + <% end %>
46 + </div>
47 + <div class="col-sm-1">
48 + <div class="position-sticky" style="top: 1rem;">
49 + <div class="text-center mt-4" data-bs-toggle="modal" data-bs-target="#how-to-modal">
50 + <a href="javascript:void(0)">
51 + <i class='bi bi-journal-check' style='font-size: 1.5rem;' title="<%= t('add_chord_how_to') %>"></i>
52 + </a>
53 + </div>
54 + </div>
55 + </div>
56 + </div>
57 + <div class="modal fade" id="how-to-modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
58 + <div class="modal-dialog">
59 + <div class="modal-content">
60 + <div class="modal-header">
61 + <h5 class="modal-title" id="exampleModalLabel"><%= t('add_chord_how_to') %></h5>
62 + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
63 + </div>
64 + <div class="modal-body">
65 + <%= t('add_chord_how_to_content') %>
66 + </div>
67 + </div>
68 + </div>
69 + </div>
70 + <div class="modal fade" id="add-song-modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
71 + <div class="modal-dialog">
72 + <div class="modal-content">
73 + <div class="modal-header">
74 + <h5 class="modal-title" id="exampleModalLabel"><%= t('add_chord_how_to') %></h5>
75 + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
76 + </div>
77 + <div class="modal-body">
78 + <%= t('add_chord_how_to_content') %>
79 + </div>
80 + </div>
81 + </div>
82 + </div>
83 + <%= render 'shared/partial_breadcrumbs' do %>
84 + <li class="breadcrumb-item">
85 + <%= link_to(t('add_chord_title'), guitar_chord_add_path) %>
86 + </li>
87 + <% end %>
88 +</main>
89 +<script>
90 + $.getScript("https://cdn.quilljs.com/1.3.6/quill.js", function() {
91 + if ($(".ql-toolbar").length) return;
92 +
93 + var quill = new Quill('#editor', {
94 + modules: {
95 + toolbar: [
96 + [{ header: [2, 3, 4, 5, 6, false] }],
97 + [{ color: [] }],
98 + [
99 + 'bold', 'italic', 'underline', 'strike',
100 + { 'script': 'super'},
101 + { 'script': 'sub' },
102 + 'code', 'link'
103 + ],
104 + ['blockquote', 'code-block', 'image'],
105 + [{ list: 'ordered' }, { list: 'bullet' }],
106 + [{ align: ['center', 'right', 'justify', false] }],
107 + [{ indent: '-1'}, { indent: '+1' }]
108 + ]
109 + },
110 + theme: 'snow'
111 + });
112 + window.quill = quill;
113 + quill.on('text-change', function(delta, oldDelta, source) {
114 + if (source == 'user') {
115 + $('input[class=chord-content]').val(quill.root.innerHTML)
116 + }
117 + });
118 + })
119 +
120 + $(document).on('turbolinks:load', function() {
121 +
122 + $('input.song-title').on('input',function(e) {
123 +
124 + $('.song-songwriters').hide()
125 +
126 + if ($(this).val().length < 3)
127 + return
128 +
129 + var url = "<%= guitar_chord_add_song_search_path %>?s=" + $(this).val()
130 + $.ajax({
131 + type: "GET",
132 + url: url,
133 + success: function(res) {
134 +
135 + console.log(res);
136 + var ins = ""
137 + for (var i = 0; i < res.length; i++) {
138 + const song = res[i]
139 + var songwriters = ""
140 + for (var j = 0; j < song.songwriters.length; j++) {
141 + const songwriter = song.songwriters[j]
142 + songwriters += `<a href="${songwriter.path}" target="__blank">${songwriter.name}</a> `
143 + }
144 +
145 + ins += `<div
146 + class="song list-group-item list-group-item-action flex-column align-items-start"
147 + data-id="${song.id}"
148 + data-title="${song.name}"
149 + style="cursor: pointer;">`
150 + ins += "<div class='d-flex w-100 justify-content-between'>"
151 + ins += "<h5 class='mb-1'>" + song.name + "</h5>"
152 + ins += "<small class='text-muted'>" + song.created + "</small>"
153 + ins += "</div>"
154 + ins += "<p class='mb-1 songwriters-links'>"
155 + ins += songwriters
156 + ins += "</p>"
157 + ins += "</div>"
158 + };
159 + $('.song-list').html(ins).show();
160 + }
161 + });
162 + });
163 +
164 + $('body').on('click', 'div.song', function() {
165 + //console.log($(this).attr('data-title'))
166 + $('.song-songwriters').show()
167 + $('input.song-title').val($(this).attr('data-title'))
168 + $('input.song-id').val($(this).attr('data-id'))
169 + $('.song-songwriters').html($(this).find('.songwriters-links').clone())
170 + $('.song-list').html("").hide()
171 + });
172 + })
173 +</script>
backend/musik88-web/app/views/chords/chord.html.erb new
+115
@@ -0,0 +1,115 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5">
10 + <div class="col-md-12">
11 + <h1><%= @page_title %></h1>
12 + <%= render "shared/flash_message", object: flash %>
13 + </div>
14 + </div>
15 + <% if @chord %>
16 + <div class="row g-5">
17 + <div class="col-sm-1">
18 + <div class="position-sticky" style="top: 1rem;">
19 + <div class="text-center mt-4">
20 + <%= link_to(
21 + "<i class='bi #{@chord.status_symbol} #{@chord.status_color}' style='font-size: 1.5rem;'></i>".html_safe,
22 + guitar_chord_history_path,
23 + title: "Status: #{@chord.status}"
24 + ) %>
25 + </div>
26 + <div class="text-center mt-4">
27 + <% if user_signed_in? && @chord.users.include?(current_user) %>
28 + <%= link_to(
29 + '<i class="bi bi-heart-fill"></i>'.html_safe,
30 + chord_unvote_path(id: @chord.id),
31 + remote: true,
32 + method: :delete,
33 + :id => "ajax_trigger"
34 + )
35 + %>
36 + <% else %>
37 + <%= link_to(
38 + '<i class="bi bi-heart"></i>'.html_safe,
39 + chord_vote_path(id: @chord.id),
40 + remote: true,
41 + method: :post,
42 + title: t('somebody_to_love'),
43 + :id => "ajax_trigger"
44 + )
45 + %>
46 + <% end %>
47 + <span class="badge rounded-pill bg-warning text-dark"><%= @chord.users.count %></span>
48 + </div>
49 + <div class="text-center mt-4">
50 + <i class="bi bi-graph-up-arrow"></i>
51 + <span class="badge rounded-pill bg-warning text-dark"><%= @chord.views_count %></span>
52 + </div>
53 + </div>
54 + </div>
55 + <div class="col-sm-11">
56 + <div class="row mb-3">
57 + <div class="col-sm-4 fw-bold fst-italic d-inline-flex">
58 + <%= link_to(
59 + "@#{@author}",
60 + profile_path(@author),
61 + title: "Author: @#{@author}"
62 + ) %>
63 + </div>
64 + <div class="col-sm-4 fw-bold fst-italic">
65 + <%= link_to(
66 + t('song_detail'),
67 + song_bridge_path(@song.slug),
68 + title: "#{@chord.song_provider.name}"
69 + ) if @chord.song_provider.present? %>
70 + </div>
71 + <div class="col-sm-4 fw-bold fst-italic">
72 + <span class="badge bg-primary rounded-pill fst-italic fw-light float-end">
73 + <%= @chord.created_at.strftime("%B %-d, %Y") %>
74 + </span>
75 + </div>
76 + </div>
77 + <%=
78 + "<div class='mb-5'>
79 + #{link_to('Review chord!', guitar_chord_review_path, class: 'btn btn-warning btn-lg px-4')}
80 + </div>".html_safe if @chord.pending?
81 + %>
82 + <%= @chord_content.html_safe %>
83 + <div class="row mt-3">
84 + <h3 id="discussion"><%= t('discussion') %></h3>
85 + <%= render "shared/flash_message", object: flash %>
86 + <div class="mb-3">
87 + <%= form_for [@chord, Comment.new] do |f| %>
88 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
89 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
90 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
91 + <% end %>
92 + </div>
93 + <%= render(partial: 'comments/comment', collection: @chord.comments) %>
94 + </div>
95 + </div>
96 + </div>
97 + <%= render 'shared/partial_breadcrumbs' do %>
98 + <li class="breadcrumb-item">
99 + <%= link_to(
100 + t('guitar_chord_link', slug: @chord.link_title),
101 + guitar_chord_detail_path(@chord.slug)
102 + ) %>
103 + </li>
104 + <% end %>
105 + <% else %>
106 + <div class="row g-5 mb-5">
107 + <div class="col-sm-4">
108 + <%= link_to('Share chord!', guitar_chord_add_path, class: 'btn btn-primary btn-lg px-4') %>
109 + </div>
110 + <div class="col-sm-8">
111 + The chord you are looking for is not available. Please add it.
112 + </div>
113 + </div>
114 + <% end %>
115 +</main>
backend/musik88-web/app/views/chords/guitar_shape.html.erb new
+81
@@ -0,0 +1,81 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= guitar_chord_shapes_url %>" />
7 +<% end %>
8 + <% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= guitar_chord_shapes_url %>",
25 + "name": "<%= t("nav_chords_shapes") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 + <% end %>
31 +<div class="row">
32 + <div class="col-md-4">
33 + <aside>
34 + <div class="bluebox-heading">
35 + <h2><%= t("guitar_chords_shape")%></h2>
36 + </div>
37 + <div class="events-list widget-wrap">
38 + <% @chords.each_with_index do |chord, index| %>
39 + <section class="event">
40 + <div class="date">
41 + <strong><%= index + 1 %>.</strong>
42 + </div>
43 + <div class="details">
44 + <h1>
45 + <%= link_to t("guitar_chord") + chord.name,
46 + guitar_chord_shape_path(chord.name.downcase.parameterize + "-" + chord.id.to_s) %>
47 + </h1>
48 + <%= link_to t("see_chord_detail"),
49 + guitar_chord_shape_path(chord.name.downcase.parameterize + "-" + chord.id.to_s),
50 + class: "more-link" %>
51 + </div>
52 + </section>
53 + <% end %>
54 + </div>
55 + </aside>
56 + </div>
57 + <div class="col-md-8">
58 +
59 + <h2><%= @chord.name %></h2>
60 + <div class="lead">
61 + <%= @chord.name %>
62 + </div>
63 + <% if @chord.image_link.to_s != '' %>
64 + <h2><%= t("guitar_chord_image") + " " + @chord.name %></h2>
65 + <div>
66 + <img src="<%= @chord.image_link %>" />
67 + </div>
68 + <% end %>
69 + <% if @chord.youtube_link.to_s != '' %>
70 + <h2><%= t("guitar_chord_video") + " " + @chord.name %></h2>
71 + <div>
72 + <iframe width="560" height="315" src="<%= @chord.youtube_link %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
73 + </div>
74 + <% end %>
75 +
76 + </div>
77 +</div>
78 +
79 +<%= render "shared/partial_breadcrumbs" do %>
80 + <li class="breadcrumb-item"><%= link_to t("nav_chords"), guitar_chords_path %></li>
81 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/chords/guitar_shapes.html.erb new
+70
@@ -0,0 +1,70 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= guitar_chord_shapes_url %>" />
7 +<% end %>
8 +
9 + <% content_for :breadcrumb do %>
10 + <script type="application/ld+json">
11 + {
12 + "@context": "http://schema.org",
13 + "@type": "BreadcrumbList",
14 + "itemListElement": [{
15 + "@type": "ListItem",
16 + "position": 1,
17 + "item": {
18 + "@id": "<%= root_url %>",
19 + "name": "<%= t("site_name") %>"
20 + }
21 + },{
22 + "@type": "ListItem",
23 + "position": 2,
24 + "item": {
25 + "@id": "<%= guitar_chord_shapes_url %>",
26 + "name": "<%= t("nav_chords_shapes") %>"
27 + }
28 + }]
29 + }
30 + </script>
31 + <% end %>
32 +<div class="row">
33 + <div class="col-md-4">
34 + <aside>
35 + <div class="bluebox-heading">
36 + <h2><%= t("guitar_chords_shape")%></h2>
37 + </div>
38 + <div class="events-list widget-wrap">
39 + <% @chords.each_with_index do |chord, index| %>
40 + <section class="event">
41 + <div class="date">
42 + <strong><%= index + 1 %>.</strong>
43 + </div>
44 + <div class="details">
45 + <h1>
46 + <%= link_to t("guitar_chord") + chord.name,
47 + guitar_chord_shape_path(chord.name.downcase.parameterize + "-" + chord.id.to_s) %>
48 + </h1>
49 + <%= link_to t("see_chord_detail"),
50 + guitar_chord_shape_path(chord.name.downcase.parameterize + "-" + chord.id.to_s),
51 + class: "more-link" %>
52 + </div>
53 + </section>
54 + <% end %>
55 + </div>
56 + </aside>
57 + </div>
58 + <div class="col-md-8">
59 +
60 + <h2><%= @page.title if @page %></h2>
61 + <div class="lead">
62 + <%= raw @page.page_content if @page %>
63 + </div>
64 +
65 + </div>
66 +</div>
67 +
68 +<%= render "shared/partial_breadcrumbs" do %>
69 + <li class="breadcrumb-item"><%= link_to t("nav_chords"), guitar_chords_path %></li>
70 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/chords/history.html.erb new
+87
@@ -0,0 +1,87 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5">
10 + <div class="col-md-12">
11 + <h1><%= @page_title %> / History</h1>
12 + </div>
13 + </div>
14 + <div class="row g-5">
15 + <div class="col-sm-1">
16 + <div class="position-sticky" style="top: 1rem;">
17 + <div class="text-center mt-4">
18 + <%= link_to(
19 + "<i class='bi bi-music-note-list text-primary' style='font-size: 1.5rem;'></i>".html_safe,
20 + guitar_chord_detail_path(@chord.slug),
21 + title: "Back to chord"
22 + ) %>
23 + </div>
24 + <div class="text-center mt-4">
25 + <% if user_signed_in? && @chord.users.include?(current_user) %>
26 + <%= link_to(
27 + '<i class="bi bi-heart-fill"></i>'.html_safe,
28 + chord_unvote_path(id: @chord.id),
29 + remote: true,
30 + method: :delete,
31 + :id => "ajax_trigger"
32 + )
33 + %>
34 + <% else %>
35 + <%= link_to(
36 + '<i class="bi bi-heart"></i>'.html_safe,
37 + chord_vote_path(id: @chord.id),
38 + remote: true,
39 + method: :post,
40 + title: t('somebody_to_love'),
41 + :id => "ajax_trigger"
42 + )
43 + %>
44 + <% end %>
45 + <span class="badge rounded-pill bg-warning text-dark"><%= @chord.users.count %></span>
46 + </div>
47 + <div class="text-center mt-4">
48 + <i class="bi bi-graph-up-arrow"></i>
49 + <span class="badge rounded-pill bg-warning text-dark"><%= @chord.views_count %></span>
50 + </div>
51 + </div>
52 + </div>
53 + <div class="col-sm-11">
54 + <ul class="list-group mt-5">
55 + <% @chord_histories.each_with_index do |history, index| %>
56 + <li class="list-group-item d-flex justify-content-between align-items-center">
57 + <div class="ms-2 me-auto">
58 + <div class="fw-bold fst-italic">
59 + <%= link_to(
60 + "@#{history.user.username_or_id}",
61 + profile_path(history.user.username_or_id),
62 + title: "Author: @#{history.user}"
63 + ) %>
64 + </div>
65 + <%= history.name %>
66 + </div>
67 + <span class="badge bg-primary rounded-pill fst-italic fw-light"><%= (index == @chord_histories.size - 1 ? @chord.created_at : history.updated_at).strftime("%B %-d, %Y") %></span>
68 + </li>
69 + <% end %>
70 + </ul>
71 + </div>
72 + </div>
73 + <%= render 'shared/partial_breadcrumbs' do %>
74 + <li class="breadcrumb-item">
75 + <%= link_to(
76 + t('guitar_chord_link', slug: @chord.link_title),
77 + guitar_chord_detail_path(@chord.slug)
78 + ) %>
79 + </li>
80 + <li class="breadcrumb-item">
81 + <%= link_to(
82 + t('guitar_chord_history'),
83 + guitar_chord_history_path
84 + ) %>
85 + </li>
86 + <% end %>
87 +</main>
backend/musik88-web/app/views/chords/index.html.erb new
+99
@@ -0,0 +1,99 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= guitar_chords_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= guitar_chords_url %>",
25 + "name": "<%= t("nav_chords") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="row">
32 + <!-- BEGIN content -->
33 + <div class="col-md-8">
34 + <div class="bluebox-heading">
35 + <h1><%= @page.title if @page %></h1>
36 + </div>
37 + <% @chords.each_with_index do |chord, index| %>
38 + <% if (index == 0 || index % 2 == 0) %>
39 + <div class="row content-row mb-3">
40 + <% end %>
41 + <div class="col-md-6">
42 + <section class="featured-post">
43 + <a class="itext-center" href="<%= guitar_chord_detail_path(chord.slug) %>">
44 + <%= image_pack_tag("image-placeholder-#{rand(0...3)}.jpg", alt:"#{t('guitar_chord')} #{chord.link_title}", class:"img-thumbnail rounded mx-auto d-block") %>
45 + <span class="image-details"></span>
46 + </a>
47 + <h2>
48 + <%= link_to t('guitar_chord_link', slug: chord.link_title),
49 + guitar_chord_detail_path(chord.slug) %>
50 + </h2>
51 + <div class="bluebox-info-line">
52 + <span class="fst-italic fw-light">
53 + <%= chord.updated_at.strftime("%B %-d, %Y") %>
54 + </span>
55 + <%= link_to(
56 + t("see_chord_detail"),
57 + guitar_chord_detail_path(chord.slug),
58 + class: 'badge rounded-pill bg-warning float-end'
59 + ) %>
60 + </div>
61 + </section>
62 + </div>
63 + <% if (index == @chords.size - 1 || (index != 0 && index % 2 != 0)) %>
64 + </div>
65 + <% end %>
66 + <% end %>
67 + <%= will_paginate @chords %>
68 + </div>
69 + <div class="col-md-4">
70 + <aside>
71 + <div class="bluebox-heading">
72 + <h2><%= t("top_10_guitar_chords")%></h2>
73 + </div>
74 + <div class="events-list widget-wrap">
75 + <% @trending_chords.each_with_index do |chord, index| %>
76 + <section class="mt-5">
77 + <div class="details">
78 + <h4>
79 + <%= link_to(
80 + "#{index + 1}. #{t('guitar_chord_link', slug: chord.link_title)}",
81 + guitar_chord_detail_path(chord.slug)
82 + ) %>
83 + </h4>
84 + <%= link_to t("see_chord_detail"),
85 + guitar_chord_detail_path(chord.slug),
86 + class: "badge rounded-pill bg-warning" %>
87 + <span class="fst-italic fw-light float-end">
88 + <%= chord.updated_at.strftime("%B %-d, %Y") %>
89 + </span>
90 + </div>
91 + </section>
92 + <% end %>
93 + </div>
94 + </aside>
95 + </div>
96 +</div>
97 +<%= render "shared/partial_breadcrumbs" do %>
98 + <li class="breadcrumb-item"><%= link_to t("nav_chords"), guitar_chords_path %></li>
99 +<% end %>
backend/musik88-web/app/views/chords/review.html.erb new
+89
@@ -0,0 +1,89 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <div class="row g-5">
10 + <div class="col-md-12">
11 + <h1><%= @page_title %> / Review</h1>
12 + <%= render "shared/flash_message", object: flash %>
13 + </div>
14 + </div>
15 + <div class="row g-5">
16 + <div class="col-sm-1">
17 + <div class="position-sticky" style="top: 1rem;">
18 + <div class="text-center mt-4">
19 + <%= link_to(
20 + "<i class='bi #{@chord.status_symbol} #{@chord.status_color}' style='font-size: 1.5rem;'></i>".html_safe,
21 + guitar_chord_history_path,
22 + title: "Status: #{@chord.status}"
23 + ) %>
24 + </div>
25 + <div class="text-center mt-4">
26 + <% if user_signed_in? && @chord.users.include?(current_user) %>
27 + <%= link_to(
28 + '<i class="bi bi-heart-fill"></i>'.html_safe,
29 + chord_unvote_path(id: @chord.id),
30 + remote: true,
31 + method: :delete,
32 + :id => "ajax_trigger"
33 + )
34 + %>
35 + <% else %>
36 + <%= link_to(
37 + '<i class="bi bi-heart"></i>'.html_safe,
38 + chord_vote_path(id: @chord.id),
39 + remote: true,
40 + method: :post,
41 + title: t('somebody_to_love'),
42 + :id => "ajax_trigger"
43 + )
44 + %>
45 + <% end %>
46 + <span class="badge rounded-pill bg-warning text-dark"><%= @chord.users.count %></span>
47 + </div>
48 + <div class="text-center mt-4">
49 + <i class="bi bi-graph-up-arrow"></i>
50 + <span class="badge rounded-pill bg-warning text-dark"><%= @chord.views_count %></span>
51 + </div>
52 + </div>
53 + </div>
54 + <div class="col-sm-11">
55 + <div class="mb-3">
56 + <li class="list-group-item d-flex justify-content-between align-items-center">
57 + <div class="ms-2 me-auto">
58 + <div class="fw-bold fst-italic">
59 + 1. Check the chords.
60 + </div>
61 + Chords name are standard and no mistakes.
62 + </div>
63 + <span class="badge bg-success rounded-pill fst-italic fw-light">OK</span>
64 + </li>
65 + <li class="list-group-item d-flex justify-content-between align-items-center">
66 + <div class="ms-2 me-auto">
67 + <div class="fw-bold fst-italic">
68 + 2. Play the song.
69 + </div>
70 + Chord progressions are OK. Minor mistakes are fine.
71 + </div>
72 + <span class="badge bg-success rounded-pill fst-italic fw-light">OK</span>
73 + </li>
74 + </div>
75 + <%= @chord.chord.html_safe %>
76 + <div class="mb-2 mt-2">
77 + <%= link_to('Approve chord?', guitar_chord_review_path, method: :patch, class: 'btn btn-primary btn-lg px-4') %>
78 + </div>
79 + </div>
80 + </div>
81 + <%= render 'shared/partial_breadcrumbs' do %>
82 + <li class="breadcrumb-item">
83 + <%= link_to(
84 + t('guitar_chord_link', slug: @chord.link_title),
85 + guitar_chord_detail_path(@chord.slug)
86 + ) %>
87 + </li>
88 + <% end %>
89 +</main>
backend/musik88-web/app/views/comments/_comment.html.erb new
+55
@@ -0,0 +1,55 @@
1 +<div class="col-12 mb-2 mt-2">
2 + <div class="row">
3 + <div class="col-1 text-center">
4 + <%= link_to(
5 + gravatar_for(comment.user),
6 + profile_path(comment.user.username_or_id),
7 + title: "Author: @#{comment.user.username_or_id}"
8 + ) %>
9 + </div>
10 + <div class="col-11">
11 + <%= link_to(
12 + comment.user.username_or_name,
13 + profile_path(comment.user.username_or_id),
14 + title: "@#{comment.user.username_or_id}"
15 + ) %>
16 + <span class="fst-italic fw-light">
17 + , <%= time_ago_in_words(comment.created_at) %> <%= t('ago') %>
18 + </span>
19 + </div>
20 + </div>
21 + <div class="row">
22 + <div class="col-1 text-center">
23 + <div class="border-start border-primary" style="width: 1px;height: 100%;margin-left: auto;margin-right: auto;">
24 + </div>
25 + </div>
26 + <div class="col-11">
27 + <%= comment.body %>
28 + <div class="row mt-1 mb-3">
29 + <div class="col-1">
30 + <a href="javascript:void(0)" onclick="toggleForm('reply-form-<%= comment.id %>')">
31 + <i class="bi bi-reply-fill"></i>
32 + </a>
33 + </div>
34 + </div>
35 + <%= form_for [comment, Comment.new], html:{style: 'display:none;width:100%;', class: "reply-form-#{comment.id}"} do |f| %>
36 + <%= f.text_area :body, placeholder: t('add_a_reply'), rows: "4" , cols: "50%"%><br/>
37 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
38 + <%= f.submit t('reply'), class: "btn btn-primary" %>
39 + <% end %>
40 + <div class="col-12">
41 + <%= render partial: 'comments/comment', collection: comment.comments %>
42 + </div>
43 + </div>
44 + </div>
45 +</div>
46 +<script>
47 + function toggleForm(id) {
48 + var x = document.getElementsByClassName(id)[0];
49 + if (x.style.display === "none") {
50 + x.style.display = "block";
51 + } else {
52 + x.style.display = "none";
53 + }
54 + }
55 +</script>
\ No newline at end of file
backend/musik88-web/app/views/devise/confirmations/new.html.erb new
+16
@@ -0,0 +1,16 @@
1 +<h2>Resend confirmation instructions</h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
4 + <%= render "devise/shared/error_messages", resource: resource %>
5 +
6 + <div class="field">
7 + <%= f.label :email %><br />
8 + <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
9 + </div>
10 +
11 + <div class="actions">
12 + <%= f.submit "Resend confirmation instructions" %>
13 + </div>
14 +<% end %>
15 +
16 +<%= render "devise/shared/links" %>
backend/musik88-web/app/views/devise/mailer/confirmation_instructions.html.erb new
+7
@@ -0,0 +1,7 @@
1 +<p><%= t('user_email_confirmation_header', email: @email) %></p>
2 +</br>
3 +<p><%= t('user_email_confirmation_content') %></p>
4 +<p><%= link_to t('user_email_confirmation_cta'), confirmation_url(@resource, confirmation_token: @token, locale: I18n.locale) %></p>
5 +</br></br>
6 +<p><%= t('user_email_with_love') %></p>
7 +<p>Musik88 / <a href="https://<%= @host %>/@seto" target="__blank">@seto</a></p>
\ No newline at end of file
backend/musik88-web/app/views/devise/mailer/email_changed.html.erb new
+7
@@ -0,0 +1,7 @@
1 +<p>Hello <%= @email %>!</p>
2 +
3 +<% if @resource.try(:unconfirmed_email?) %>
4 + <p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
5 +<% else %>
6 + <p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
7 +<% end %>
backend/musik88-web/app/views/devise/mailer/password_change.html.erb new
+3
@@ -0,0 +1,3 @@
1 +<p>Hello <%= @resource.email %>!</p>
2 +
3 +<p>We're contacting you to notify you that your password has been changed.</p>
backend/musik88-web/app/views/devise/mailer/reset_password_instructions.html.erb new
+8
@@ -0,0 +1,8 @@
1 +<p>Hello <%= @resource.email %>!</p>
2 +
3 +<p>Someone has requested a link to change your password. You can do this through the link below.</p>
4 +
5 +<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
6 +
7 +<p>If you didn't request this, please ignore this email.</p>
8 +<p>Your password won't change until you access the link above and create a new one.</p>
backend/musik88-web/app/views/devise/mailer/unlock_instructions.html.erb new
+7
@@ -0,0 +1,7 @@
1 +<p>Hello <%= @resource.email %>!</p>
2 +
3 +<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
4 +
5 +<p>Click the link below to unlock your account:</p>
6 +
7 +<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>
backend/musik88-web/app/views/devise/passwords/edit.html.erb new
+25
@@ -0,0 +1,25 @@
1 +<h2>Change your password</h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
4 + <%= render "devise/shared/error_messages", resource: resource %>
5 + <%= f.hidden_field :reset_password_token %>
6 +
7 + <div class="field">
8 + <%= f.label :password, "New password" %><br />
9 + <% if @minimum_password_length %>
10 + <em>(<%= @minimum_password_length %> characters minimum)</em><br />
11 + <% end %>
12 + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
13 + </div>
14 +
15 + <div class="field">
16 + <%= f.label :password_confirmation, "Confirm new password" %><br />
17 + <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
18 + </div>
19 +
20 + <div class="actions">
21 + <%= f.submit "Change my password" %>
22 + </div>
23 +<% end %>
24 +
25 +<%= render "devise/shared/links" %>
backend/musik88-web/app/views/devise/passwords/new.html.erb new
+16
@@ -0,0 +1,16 @@
1 +<h2>Forgot your password?</h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
4 + <%= render "devise/shared/error_messages", resource: resource %>
5 +
6 + <div class="field">
7 + <%= f.label :email %><br />
8 + <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 + </div>
10 +
11 + <div class="actions">
12 + <%= f.submit "Send me reset password instructions" %>
13 + </div>
14 +<% end %>
15 +
16 +<%= render "devise/shared/links" %>
backend/musik88-web/app/views/devise/registrations/edit.html.erb new
+43
@@ -0,0 +1,43 @@
1 +<h2>Edit <%= resource_name.to_s.humanize %></h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
4 + <%= render "devise/shared/error_messages", resource: resource %>
5 +
6 + <div class="field">
7 + <%= f.label :email %><br />
8 + <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 + </div>
10 +
11 + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
12 + <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
13 + <% end %>
14 +
15 + <div class="field">
16 + <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
17 + <%= f.password_field :password, autocomplete: "new-password" %>
18 + <% if @minimum_password_length %>
19 + <br />
20 + <em><%= @minimum_password_length %> characters minimum</em>
21 + <% end %>
22 + </div>
23 +
24 + <div class="field">
25 + <%= f.label :password_confirmation %><br />
26 + <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
27 + </div>
28 +
29 + <div class="field">
30 + <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
31 + <%= f.password_field :current_password, autocomplete: "current-password" %>
32 + </div>
33 +
34 + <div class="actions">
35 + <%= f.submit "Update" %>
36 + </div>
37 +<% end %>
38 +
39 +<h3>Cancel my account</h3>
40 +
41 +<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
42 +
43 +<%= link_to "Back", :back %>
backend/musik88-web/app/views/devise/registrations/new.html.erb new
+29
@@ -0,0 +1,29 @@
1 +<h2>Sign up</h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
4 + <%= render "devise/shared/error_messages", resource: resource %>
5 +
6 + <div class="field">
7 + <%= f.label :email %><br />
8 + <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 + </div>
10 +
11 + <div class="field">
12 + <%= f.label :password %>
13 + <% if @minimum_password_length %>
14 + <em>(<%= @minimum_password_length %> characters minimum)</em>
15 + <% end %><br />
16 + <%= f.password_field :password, autocomplete: "new-password" %>
17 + </div>
18 +
19 + <div class="field">
20 + <%= f.label :password_confirmation %><br />
21 + <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
22 + </div>
23 +
24 + <div class="actions">
25 + <%= f.submit "Sign up" %>
26 + </div>
27 +<% end %>
28 +
29 +<%= render "devise/shared/links" %>
backend/musik88-web/app/views/devise/sessions/new.html.erb new
+26
@@ -0,0 +1,26 @@
1 +<h2>Log in</h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
4 + <div class="field">
5 + <%= f.label :email %><br />
6 + <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
7 + </div>
8 +
9 + <div class="field">
10 + <%= f.label :password %><br />
11 + <%= f.password_field :password, autocomplete: "current-password" %>
12 + </div>
13 +
14 + <% if devise_mapping.rememberable? %>
15 + <div class="field">
16 + <%= f.check_box :remember_me %>
17 + <%= f.label :remember_me %>
18 + </div>
19 + <% end %>
20 +
21 + <div class="actions">
22 + <%= f.submit "Log in" %>
23 + </div>
24 +<% end %>
25 +
26 +<%= render "devise/shared/links" %>
backend/musik88-web/app/views/devise/shared/_error_messages.html.erb new
+15
@@ -0,0 +1,15 @@
1 +<% if resource.errors.any? %>
2 + <div id="error_explanation">
3 + <h2>
4 + <%= I18n.t("errors.messages.not_saved",
5 + count: resource.errors.count,
6 + resource: resource.class.model_name.human.downcase)
7 + %>
8 + </h2>
9 + <ul>
10 + <% resource.errors.full_messages.each do |message| %>
11 + <li><%= message %></li>
12 + <% end %>
13 + </ul>
14 + </div>
15 +<% end %>
backend/musik88-web/app/views/devise/shared/_links.html.erb new
+25
@@ -0,0 +1,25 @@
1 +<%- if controller_name != 'sessions' %>
2 + <%= link_to "Log in", new_session_path(resource_name) %><br />
3 +<% end %>
4 +
5 +<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6 + <%= link_to "Sign up", new_registration_path(resource_name) %><br />
7 +<% end %>
8 +
9 +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
10 + <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
11 +<% end %>
12 +
13 +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
14 + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
15 +<% end %>
16 +
17 +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18 + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
19 +<% end %>
20 +
21 +<%- if devise_mapping.omniauthable? %>
22 + <%- resource_class.omniauth_providers.each do |provider| %>
23 + <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
24 + <% end %>
25 +<% end %>
backend/musik88-web/app/views/devise/unlocks/new.html.erb new
+16
@@ -0,0 +1,16 @@
1 +<h2>Resend unlock instructions</h2>
2 +
3 +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
4 + <%= render "devise/shared/error_messages", resource: resource %>
5 +
6 + <div class="field">
7 + <%= f.label :email %><br />
8 + <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 + </div>
10 +
11 + <div class="actions">
12 + <%= f.submit "Resend unlock instructions" %>
13 + </div>
14 +<% end %>
15 +
16 +<%= render "devise/shared/links" %>
backend/musik88-web/app/views/errors/internal_server_error.html.erb new
+6
@@ -0,0 +1,6 @@
1 +<h1>There's something wrong with the website</h1>
2 +<p class="lead">We're moving fast, so crash happens.</p>
3 +<p class="lead">Worry not! We'll fix this ASAP if you care to tell us from our page below:</p>
4 +<a href="https://youtube.com/c/tutorialgitar88" target="__blank" class="lead">YouTube</br>
5 +<a href="https://fb.com/tutorialgitar88" target="__blank" class="lead">Facebook</a></br>
6 +<a href="https://instagram.com/tutorialgitar88" target="__blank" class="lead">Instagram</p>
\ No newline at end of file
backend/musik88-web/app/views/errors/not_found.html.erb new
+13
@@ -0,0 +1,13 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 +<% end %>
7 +<h1>We couldn't find the page</h1>
8 +<p class="lead">It's not that you just dropped your guitar pick.</p>
9 +<p class="lead">We're changing some of our cool specs, so it's understandable that sometimes you might lost your way while navigating this website.</p>
10 +<p class="lead">Worry not! We'll fix this ASAP if you care to tell us from our page below:</p>
11 +<a href="https://youtube.com/c/tutorialgitar88" target="__blank" class="lead">YouTube</br>
12 +<a href="https://fb.com/tutorialgitar88" target="__blank" class="lead">Facebook</a></br>
13 +<a href="https://instagram.com/tutorialgitar88" target="__blank" class="lead">Instagram</p>
\ No newline at end of file
backend/musik88-web/app/views/javanese_gamelan/_partial_like_this.html.erb new
+6
@@ -0,0 +1,6 @@
1 +<div class="mt-3">
2 + <% users.each_with_index do |user, index| %>
3 + <span class='badge badge-pill bg-warning text-dark'>@<%= user.username_or_name %></span>
4 + <% end %>
5 + <%= t('like_this') %>
6 +</div>
\ No newline at end of file
backend/musik88-web/app/views/javanese_gamelan/_partial_menu.html.erb new
+14
@@ -0,0 +1,14 @@
1 +<li class="nav-item">
2 + <% if active %>
3 + <a href="javascript:void(0)" class="nav-link active disabled" aria-current="page">
4 + <%= href_text %>
5 + </a>
6 + <% else %>
7 + <%= link_to(
8 + href_text,
9 + href_link,
10 + class: "nav-link"
11 + )
12 + %>
13 + <% end %>
14 +</li>
\ No newline at end of file
backend/musik88-web/app/views/javanese_gamelan/_partial_menus.html.erb new
+20
@@ -0,0 +1,20 @@
1 +<div class="row">
2 + <div class="col-md-12">
3 + <ul class="nav nav-tabs">
4 + <%= render(
5 + "partial_menu",
6 + href_text: t("legacy_view"),
7 + href_link: javanese_gending_gamelan_notation_detail_path,
8 + active: active_menu == 'legacy'
9 + )
10 + %>
11 + <%= render(
12 + "partial_menu",
13 + href_text: t("new_view"),
14 + href_link: javanese_gending_gamelan_notation_detail_new_path(@gending.slug),
15 + active: active_menu == 'new'
16 + )
17 + %>
18 + </ul>
19 + </div>
20 +</div>
\ No newline at end of file
backend/musik88-web/app/views/javanese_gamelan/_partial_new_notation.html.erb new
+40
@@ -0,0 +1,40 @@
1 +<% return unless rich_format %>
2 +<% return unless rich_format['sections'] %>
3 +<% rich_format['sections'].each_with_index do | section, index | %>
4 + <% next if section['type'] == 'title' || section['lines'].nil? %>
5 + <% if ['ompak', 'buka', 'celuk', 'ngelik', 'suwuk', 'A', 'B', 'C', '[A]', '[B]', '[C]', ].include? section['type'] %>
6 + <div class="row mb-3 mt-3">
7 + <div class="col-12">
8 + <span class="badge bg-primary fw-bold fst-italic"><%= section['type'].upcase %></span>
9 + </div>
10 + </div>
11 + <% end %>
12 + <% section['lines'].each_with_index do | line, line_index | %>
13 + <div class="row">
14 + <% line['gatra'].each_with_index do | gatra, index | %>
15 + <div class="col-3 <%= 'ml-5' if (line_index == 0 && index == 0) %>">
16 + <% next if gatra['content'].nil? %>
17 + <% if (line_index == 0 && index == 0) %>
18 + <span class="fw-bold "><%= line['prefix'] %></span>
19 + <% end %>
20 + <% gatra['content'].each_with_index do | content, index | %>
21 + <span
22 + class="text-center p-1 pt-2 pb-2 <%= 'badge bg-primary badge-pill' if content['attributes'].include? 'gong_gede' %>"
23 + style="display: inline-block; width: 20px;"
24 + >
25 + <%= content['number'] %>
26 + </span>
27 + <% end %>
28 + <% if (index == gatra['content'].count - 1) %>
29 + <span class="fw-bold "><%= line['suffix'] %></span>
30 + <% end %>
31 + </div>
32 + <% end %>
33 + </div>
34 + <% end %>
35 +<% end %>
36 +<div class="row">
37 + <div class="col-12 mt-5 fst-italic">
38 + <%= link_to('gns', 'https://github.com/smbpndk/gns', target: '__blank') %> <%= rich_format['version'] %>
39 + </div>
40 +</div>
\ No newline at end of file
backend/musik88-web/app/views/javanese_gamelan/add.html.erb new
+46
@@ -0,0 +1,46 @@
1 +<% content_for :meta_head do %>
2 + <title>
3 + <%= @meta_title %>
4 + </title>
5 + <meta name="description" content="<%= @meta_description %>">
6 +<% end %>
7 +<div class="row">
8 + <!-- BEGIN content -->
9 + <div class="col-md-12">
10 + <div class="page-header">
11 + <h1>
12 + <%= t("gending_notation_add") %>
13 + </h1>
14 + </div>
15 + </div>
16 + <div class="col-md-8">
17 + <%= form_with model: @javanese_gending, url: {action: "add" }, html: { :class=> "form-horizontal" } do |form| %>
18 + <div class="form-group">
19 + <%= form.label t("gending_name"), class: "col-sm-3 control-label" %>
20 + <div class="col-sm-9">
21 + <%= form.text_field :name, class: "col-md-6 form-control tinymce" %>
22 + </div>
23 + <%= form.label t("gending_description"), class: "col-sm-3 control-label" %>
24 + <div class="col-sm-9">
25 + <%= form.text_area :description, class: "col-md-6 form-control tinymce" , :rows=> 10, :cols => 120 %>
26 + </div>
27 + <%= form.label t("gending_notation"), class: "col-sm-3 control-label" %>
28 + <div class="col-sm-9">
29 + <%= form.text_area :notation, class: "col-md-6 form-control tinymce" , :rows=> 40, :cols => 120 %>
30 + </div>
31 + </div>
32 + <div class="form-group">
33 + <div class="col-sm-offset-3 col-sm-4">
34 + <%= form.submit t("gending_notation_add"), class: "btn btn-primary" %>
35 + </br>
36 + <div class="vertical-separator"></div>
37 + <%= link_to t("cancel"), my_path, class: "btn btn-secondary" %>
38 + </div>
39 + </div>
40 + <% end %>
41 + <%= tinymce %>
42 +</div>
43 +<!-- END content -->
44 +</div>
45 +<%= render "shared/partial_breadcrumbs" do %>
46 +<% end %>
backend/musik88-web/app/views/javanese_gamelan/add_gending.html.erb new
+43
@@ -0,0 +1,43 @@
1 +<% content_for :meta_head do %>
2 + <title>
3 + <%= @meta_title %>
4 + </title>
5 + <meta name="description" content="<%= @meta_description %>">
6 +<% end %>
7 +<div class="row">
8 + <!-- BEGIN content -->
9 + <div class="col-md-12">
10 + <div class="page-header">
11 + <h1>
12 + <%= t("gending_notation_add") %>
13 + </h1>
14 + </div>
15 + </div>
16 + <div class="col-md-8">
17 + <%= form_with model: @javanese_gending, url: { action: "add" }, html: { :class=> "form-horizontal" } do |form| %>
18 + <div class="form-group">
19 + <%= form.label t("gending_name"), class: "col-sm-3 control-label" %>
20 + <div class="col-sm-9">
21 + <%= form.text_field :name, class: "col-md-6 form-control tinymce" %>
22 + </div>
23 + <%= form.label t("gending_description"), class: "col-sm-3 control-label" %>
24 + <div class="col-sm-9">
25 + <%= form.text_area :description, class: "col-md-6 form-control tinymce" , :rows=> 10, :cols => 120 %>
26 + </div>
27 + </div>
28 + <div class="form-group">
29 + <div class="col-sm-offset-3 col-sm-4">
30 + <%= form.submit t("gending_notation_add"), class: "btn btn-primary" %>
31 + </br>
32 + </br>
33 + <div class="vertical-separator"></div>
34 + <%= link_to t("cancel"), javanese_gending_path, class: "btn btn-secondary" %>
35 + </div>
36 + </div>
37 +<% end %>
38 +<%= tinymce %>
39 +</div>
40 +<!-- END content -->
41 +</div>
42 +<%= render "shared/partial_breadcrumbs" do %>
43 +<% end %>
backend/musik88-web/app/views/javanese_gamelan/gamelan_notation.html.erb new
+80
@@ -0,0 +1,80 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= javanese_gamelan_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= javanese_gamelan_url %>",
25 + "name": "<%= t("nav_javanese_gamelan") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="row">
32 + <div class="col-12">
33 + <h2><%= @page.title if @page %></h2>
34 + <div class="lead">
35 + <%= raw @page.page_content if @page %>
36 + </div>
37 + </div>
38 +</div>
39 +<div class="row">
40 + <div class="col-12">
41 + <table class="table table-striped">
42 + <thead>
43 + <tr>
44 + <th scope="col">#</th>
45 + <th scope="col"><%= t("gending_name") %></th>
46 + <th scope="col"></th>
47 + <th scope="col"><%= t("added_by") %></th>
48 + </tr>
49 + </thead>
50 + <tbody>
51 + <% @gending_notations.each_with_index do |gending, index| %>
52 + <tr>
53 + <th scope="row"><%= @gending_notations.offset + index + 1 %></th>
54 + <td>
55 + <%= image_pack_tag("image-placeholder-#{rand(0...3)}.jpg", alt:"#{gending.name} cover image.", style:"width:100px;height:100px;", class: 'img-thumbnail rounded-circle mx-auto d-block') %>
56 + </td>
57 + <td>
58 + <%= link_to(
59 + gending.name,
60 + gending.rich_format.nil? ? javanese_gending_gamelan_notation_detail_path(gending.slug)
61 + : javanese_gending_gamelan_notation_detail_new_path(gending.slug),
62 + class: "more-link"
63 + ) %>
64 + <% if gending.users.count > 0 %>
65 + <%= render('partial_like_this', users: gending.users) %>
66 + <% end %>
67 + </td>
68 + <td>
69 + <%= link_to("@#{gending.user_or_nowhereman.username_or_id}", profile_path(gending.user_or_nowhereman.username_or_id), title: "Author: @#{gending.user_or_nowhereman.username_or_id}") %>
70 + </td>
71 + </tr>
72 + <% end %>
73 + </tbody>
74 + </table>
75 + <%= will_paginate @gending_notations, class: 'flickr_pagination' %>
76 + </div>
77 +</div>
78 +<%= render "shared/partial_breadcrumbs" do %>
79 + <li class="breadcrumb-item"><%= link_to t("nav_javanese_gamelan"), javanese_gamelan_path %></li>
80 +<% end %>
backend/musik88-web/app/views/javanese_gamelan/gending.html.erb new
+75
@@ -0,0 +1,75 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= javanese_gamelan_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= javanese_gamelan_url %>",
25 + "name": "<%= t("nav_javanese_gamelan") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="row">
32 + <div class="col-12">
33 + <h2><%= @page.title if @page %></h2>
34 + <div class="lead">
35 + <%= raw @page.page_content if @page %>
36 + </div>
37 + </div>
38 +</div>
39 +<div class="row">
40 + <div class="col-12">
41 + <table class="table table-striped">
42 + <thead>
43 + <tr>
44 + <th scope="col">#</th>
45 + <th scope="col"><%= t("gending_name") %></th>
46 + <th scope="col"></th>
47 + <th scope="col"><%= t("added_by") %></th>
48 + </tr>
49 + </thead>
50 + <tbody>
51 + <% @gendings.each_with_index do |gending, index| %>
52 + <tr>
53 + <th scope="row"><%= @gendings.offset + index + 1 %></th>
54 + <td>
55 + <%= image_pack_tag("image-placeholder-#{rand(0...3)}.jpg", alt:"#{gending.name} cover image.", style:"width:100px;height:100px;", class: 'img-thumbnail rounded-circle mx-auto d-block') %>
56 + </td>
57 + <td>
58 + <%= link_to gending.name, javanese_gending_detail_path("gending-notation-" + gending.slug) %>
59 + <% if gending.users.count > 0 %>
60 + <%= render('partial_like_this', users: gending.users) %>
61 + <% end %>
62 + </td>
63 + <td>
64 + <%= link_to("@#{gending.user_or_nowhereman.username_or_name}", profile_path(gending.user_or_nowhereman.username_or_id), title: "@#{gending.user_or_nowhereman.username_or_name}") %>
65 + </td>
66 + </tr>
67 + <% end %>
68 + </tbody>
69 + </table>
70 + <%= will_paginate @gendings, class: 'flickr_pagination' %>
71 + </div>
72 +</div>
73 +<%= render "shared/partial_breadcrumbs" do %>
74 + <li class="breadcrumb-item"><%= link_to t("nav_javanese_gamelan"), javanese_gamelan_path %></li>
75 +<% end %>
backend/musik88-web/app/views/javanese_gamelan/gending_detail.html.erb new
+149
@@ -0,0 +1,149 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= javanese_gending_detail_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= javanese_gamelan_url %>",
25 + "name": "<%= t("nav_javanese_gamelan") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= javanese_gending_detail_url %>",
32 + "name": "<%= t("gending_notation") + @gending.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-12">
41 + <div class="lead p-3">
42 + <%=
43 + t(
44 + 'gending_add_explanation',
45 + href: link_to(
46 + t('gending_add'),
47 + add_gending_path,
48 + class: 'btn btn-primary'
49 + ),
50 + bach_link: link_to("<span class='badge bg-warning text-dark'>€BACH</span>".html_safe, bach_path)
51 + ).html_safe
52 + %>
53 + </div>
54 + </div>
55 + </div>
56 + <div class="row g-5">
57 + <div class="col-md-12">
58 + <h1><%= @page_title %></h1>
59 + </div>
60 + </div>
61 + <div class="row g-5">
62 + <div class="col-sm-1">
63 + <div class="position-sticky" style="top: 1rem;">
64 + <div class="text-center mt-3">
65 + <% if user_signed_in? && @gending.users.include?(current_user) %>
66 + <%= link_to(
67 + '<i class="bi bi-heart-fill"></i>'.html_safe,
68 + javanese_gending_unvote_path(id: @gending.id),
69 + remote: true,
70 + method: :delete,
71 + :id => "ajax_trigger"
72 + )
73 + %>
74 + <% else %>
75 + <%= link_to(
76 + '<i class="bi bi-heart"></i>'.html_safe,
77 + javanese_gending_vote_path(id: @gending.id),
78 + remote: true,
79 + method: :post,
80 + :id => "ajax_trigger"
81 + )
82 + %>
83 + <% end %>
84 + <span class="badge rounded-pill bg-warning text-dark"><%= @gending.gending_vote.count %></span>
85 + </div>
86 + <div class="text-center mt-4">
87 + <i class="bi bi-graph-up-arrow"></i>
88 + <span class="badge rounded-pill bg-warning text-dark"><%= @gending.views_count %></span>
89 + </div>
90 + </div>
91 + </div>
92 + <div class="col-sm-11">
93 + <%= render('shared/partial_meta_top', author: @author, created_at: @gending.created_at) %>
94 + <%= raw @gending.description %>
95 + <div class="row">
96 + <div class="col-12">
97 + <table class="table table-striped">
98 + <thead>
99 + <tr>
100 + <th scope="col">#</th>
101 + <th scope="col"><%= t("gending_notation") %></th>
102 + <th scope="col"><%= t("added_by") %></th>
103 + </tr>
104 + </thead>
105 + <tbody>
106 + <% @gending.javanese_gending_notation.each_with_index do |gending, index| %>
107 + <tr>
108 + <th scope="row"><%= index + 1 %></th>
109 + <td>
110 + <%= link_to(
111 + gending.name,
112 + gending.rich_format.nil? ? javanese_gending_gamelan_notation_detail_path(gending.slug)
113 + : javanese_gending_gamelan_notation_detail_new_path(gending.slug),
114 + class: "more-link"
115 + ) %>
116 + <% if gending.users.count > 0 %>
117 + <%= render('partial_like_this', users: gending.users) %>
118 + <% end %>
119 + </td>
120 + <td>
121 + <%= link_to("@#{gending.user_or_nowhereman.username_or_id}", profile_path(gending.user_or_nowhereman.username_or_id), title: "Author: @#{gending.user_or_nowhereman.username_or_id}") %>
122 + </td>
123 + </tr>
124 + <% end %>
125 + </tbody>
126 + </table>
127 + </div>
128 + </div>
129 + <div class="row">
130 + <div class="col-12">
131 + <h3 id="discussion"><%= t('discussion') %></h3>
132 + <%= render "shared/flash_message", object: flash %>
133 + <div class="mb-3">
134 + <%= form_for [@gending, Comment.new] do |f| %>
135 + <%= f.text_area :body, placeholder: "Add a comment", rows: "4" , cols: "50%" %><br/>
136 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
137 + <%= f.submit "Add Comment", class: "btn btn-primary" %>
138 + <% end %>
139 + </div>
140 + <%= render(partial: 'comments/comment', collection: @gending.comments) %>
141 + </div>
142 + </div>
143 + </div>
144 + </div>
145 + <%= render "shared/partial_breadcrumbs" do %>
146 + <li class="breadcrumb-item"><%= link_to t("nav_javanese_gamelan"), javanese_gamelan_path %></li>
147 + <li class="breadcrumb-item"><%= link_to t("gending_notation") + @gending.name, javanese_gending_detail_path %></li>
148 + <% end %>
149 +</main>
backend/musik88-web/app/views/javanese_gamelan/index.html.erb new
+95
@@ -0,0 +1,95 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= javanese_gamelan_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= javanese_gamelan_url %>",
25 + "name": "<%= t("nav_javanese_gamelan") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="row">
32 + <div class="col-md-8">
33 + <div class="bluebox-heading">
34 + <h2><%= @page.title if @page %></h2>
35 + <div class="lead">
36 + <%= raw @page.page_content if @page %>
37 + </div>
38 + </div>
39 + <div class="bluebox-heading">
40 + <h3><%= t("our_javanese_gending_notation") %></h3>
41 + </div>
42 + <% @gendings.each_with_index do |gending, index| %>
43 + <% if (index == 0 || index % 2 == 0) %>
44 + <div class="row content-row">
45 + <% end %>
46 + <div class="col-md-6">
47 + <section class="featured-post">
48 + <a class="image-wrap" href="<%= javanese_gending_detail_path("gending-notation-" + gending.name.downcase.parameterize + "-" + gending.id.to_s) %>">
49 + <%= image_pack_tag("image-placeholder-#{rand(0...3)}.jpg", alt:"#{gending.name} cover image.", style:"width:267px;height:165px;") %>
50 + <span class="image-details"></span>
51 + </a>
52 + <h1>
53 + <%= link_to gending.name,
54 + javanese_gending_detail_path("gending-notation-" + gending.name.downcase.parameterize + "-" + gending.id.to_s) %>
55 + </h1>
56 + <p><%= strip_tags gending.description.truncate 60 %></p>
57 + <div class="bluebox-info-line">
58 + <span class="featured-post-date"><%= gending.updated_at.strftime("%B %-d, %Y") %></span>
59 + <%= link_to t("see_gending_detail"),
60 + javanese_gending_detail_path("gending-notation-" + gending.name.downcase.parameterize + "-" + gending.id.to_s),
61 + class: "badge rounded-pill bg-warning float-end" %>
62 + </div>
63 + </section>
64 + </div>
65 + <% if (index == @gendings.size - 1 || (index != 0 && index % 2 != 0)) %>
66 + </div>
67 + <% end %>
68 + <% end %>
69 + <%= will_paginate @gendings, class: 'flickr_pagination' %>
70 + </div>
71 + <div class="col-md-4">
72 + <aside>
73 + <div class="bluebox-heading">
74 + <h3><%= t("top_10_javanese_gending")%></h3>
75 + </div>
76 + <% @trending_gendings.each_with_index do |gending, index| %>
77 +
78 + <h4>
79 + <%= link_to "#{index + 1}. #{gending.name}",
80 + javanese_gending_detail_path("gending-notation-" + gending.name.downcase.parameterize + "-" + gending.id.to_s) %>
81 + </h4>
82 + <span class="fst-italic fw-light">
83 + <%= t("song_updated_at", date: gending.updated_at.strftime("%B %-d, %Y")) %>
84 + </span>
85 + <%= link_to t("see_gending_detail"),
86 + javanese_gending_detail_path("gending-notation-" + gending.name.downcase.parameterize + "-" + gending.id.to_s),
87 + class: "badge rounded-pill bg-warning float-end" %>
88 +
89 + <% end %>
90 + </aside>
91 + </div>
92 +</div>
93 +<%= render "shared/partial_breadcrumbs" do %>
94 + <li class="breadcrumb-item"><%= link_to t("nav_javanese_gamelan"), javanese_gamelan_path %></li>
95 +<% end %>
backend/musik88-web/app/views/javanese_gamelan/notation.html.erb new
+124
@@ -0,0 +1,124 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= javanese_gending_gamelan_notation_detail_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= javanese_gamelan_url %>",
25 + "name": "<%= t("nav_javanese_gamelan") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= javanese_gending_gamelan_notation_detail_url %>",
32 + "name": "<%= t("gending_notation") + @gending.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-12">
41 + <div class="lead p-3">
42 + <%=
43 + t(
44 + 'gending_notation_add_explanation',
45 + href: link_to(
46 + t('gending_notation_add'),
47 + add_gending_notation_path,
48 + class: 'btn btn-primary'
49 + ),
50 + bach_link: link_to("<span class='badge bg-warning text-dark'>€BACH</span>".html_safe, bach_path)
51 + ).html_safe
52 + %>
53 + </div>
54 + </div>
55 + </div>
56 + <div class="row g-5">
57 + <div class="col-md-12">
58 + <h1><%= @page_title %></h1>
59 + </div>
60 + </div>
61 + <div class="row g-5">
62 + <div class="col-sm-1">
63 + <div class="position-sticky" style="top: 1rem;">
64 + <div class="text-center mt-3">
65 + <% if user_signed_in? && @gending.users.include?(current_user) %>
66 + <%= link_to(
67 + '<i class="bi bi-heart-fill"></i>'.html_safe,
68 + javanese_gending_notation_unvote_path(id: @gending.id),
69 + remote: true,
70 + method: :delete,
71 + :id => "ajax_trigger"
72 + )
73 + %>
74 + <% else %>
75 + <%= link_to(
76 + '<i class="bi bi-heart"></i>'.html_safe,
77 + javanese_gending_notation_vote_path(id: @gending.id),
78 + remote: true,
79 + method: :post,
80 + :id => "ajax_trigger"
81 + )
82 + %>
83 + <% end %>
84 + <span class="badge rounded-pill bg-warning text-dark"><%= @gending.gending_notation_vote.count %></span>
85 + </div>
86 + <div class="text-center mt-4">
87 + <i class="bi bi-graph-up-arrow"></i>
88 + <span class="badge rounded-pill bg-warning text-dark"><%= @gending.views_count %></span>
89 + </div>
90 + </div>
91 + </div>
92 + <div class="col-sm-11">
93 + <%= render('shared/partial_meta_top', author: @author, created_at: @gending.created_at) %>
94 + <div class="row mt-5">
95 + <div class="col-md-12">
96 + <%= render('partial_menus', active_menu: 'legacy' ) %>
97 + </div>
98 + </div>
99 + <div class="row">
100 + <div class="col-md-12 mb-5 mt-3">
101 + <%= @content_notation.html_safe %>
102 + </div>
103 + </div>
104 + <div class="row">
105 + <div class="col-12">
106 + <h3 id="discussion"><%= t('discussion') %></h3>
107 + <%= render "shared/flash_message", object: flash %>
108 + <div class="mb-3">
109 + <%= form_for [@gending, Comment.new] do |f| %>
110 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
111 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
112 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
113 + <% end %>
114 + </div>
115 + <%= render(partial: 'comments/comment', collection: @gending.comments) %>
116 + </div>
117 + </div>
118 + </div>
119 + </div>
120 + <%= render "shared/partial_breadcrumbs" do %>
121 + <li class="breadcrumb-item"><%= link_to t("nav_javanese_gamelan"), javanese_gamelan_path %></li>
122 + <li class="breadcrumb-item"><%= link_to t("gending_notation") + @gending.name, javanese_gending_gamelan_notation_detail_path %></li>
123 + <% end %>
124 +</main>
\ No newline at end of file
backend/musik88-web/app/views/javanese_gamelan/notation_new.html.erb new
+124
@@ -0,0 +1,124 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= javanese_gending_gamelan_notation_detail_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= javanese_gamelan_url %>",
25 + "name": "<%= t("nav_javanese_gamelan") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= javanese_gending_gamelan_notation_detail_url %>",
32 + "name": "<%= t("gending_notation") + @gending.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-12">
41 + <div class="lead p-3">
42 + <%=
43 + t(
44 + 'gending_notation_add_explanation',
45 + href: link_to(
46 + t('gending_notation_add'),
47 + add_gending_notation_path,
48 + class: 'btn btn-primary'
49 + ),
50 + bach_link: link_to("<span class='badge bg-warning text-dark'>€BACH</span>".html_safe, bach_path)
51 + ).html_safe
52 + %>
53 + </div>
54 + </div>
55 + </div>
56 + <div class="row g-5">
57 + <div class="col-md-12">
58 + <h1><%= @page_title %></h1>
59 + </div>
60 + </div>
61 + <div class="row g-5">
62 + <div class="col-sm-1">
63 + <div class="position-sticky" style="top: 1rem;">
64 + <div class="text-center mt-3">
65 + <% if user_signed_in? && @gending.users.include?(current_user) %>
66 + <%= link_to(
67 + '<i class="bi bi-heart-fill"></i>'.html_safe,
68 + javanese_gending_notation_unvote_path(id: @gending.id),
69 + remote: true,
70 + method: :delete,
71 + :id => "ajax_trigger"
72 + )
73 + %>
74 + <% else %>
75 + <%= link_to(
76 + '<i class="bi bi-heart"></i>'.html_safe,
77 + javanese_gending_notation_vote_path(id: @gending.id),
78 + remote: true,
79 + method: :post,
80 + :id => "ajax_trigger"
81 + )
82 + %>
83 + <% end %>
84 + <span class="badge rounded-pill bg-warning text-dark"><%= @gending.gending_notation_vote.count %></span>
85 + </div>
86 + <div class="text-center mt-4">
87 + <i class="bi bi-graph-up-arrow"></i>
88 + <span class="badge rounded-pill bg-warning text-dark"><%= @gending.views_count %></span>
89 + </div>
90 + </div>
91 + </div>
92 + <div class="col-sm-11">
93 + <%= render('shared/partial_meta_top', author: @author, created_at: @gending.created_at) %>
94 + <div class="row mt-5">
95 + <div class="col-md-12">
96 + <%= render('partial_menus', active_menu: 'new' ) %>
97 + </div>
98 + </div>
99 + <div class="row">
100 + <div class="col-md-12 mb-5 mt-3">
101 + <%= user_signed_in? ? render(@content_notation, rich_format: @gending.rich_format ) : @content_notation.html_safe %>
102 + </div>
103 + </div>
104 + <div class="row">
105 + <div class="col-12">
106 + <h3 id="discussion"><%= t('discussion') %></h3>
107 + <%= render "shared/flash_message", object: flash %>
108 + <div class="mb-3">
109 + <%= form_for [@gending, Comment.new] do |f| %>
110 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
111 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
112 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
113 + <% end %>
114 + </div>
115 + <%= render(partial: 'comments/comment', collection: @gending.comments) %>
116 + </div>
117 + </div>
118 + </div>
119 + </div>
120 + <%= render "shared/partial_breadcrumbs" do %>
121 + <li class="breadcrumb-item"><%= link_to t("nav_javanese_gamelan"), javanese_gamelan_path %></li>
122 + <li class="breadcrumb-item"><%= link_to t("gending_notation") + @gending.name, javanese_gending_gamelan_notation_detail_path %></li>
123 + <% end %>
124 +</main>
\ No newline at end of file
backend/musik88-web/app/views/layouts/_footer.html.erb new
+89
@@ -0,0 +1,89 @@
1 +<!-- BEGIN footer -->
2 +<footer>
3 + <div class="row">
4 + <div class="col-md-4">
5 + <div class="bluebox-heading">
6 + <h3>Musik88</h3>
7 + </div>
8 + <span class="clear"></span>
9 + <ul class="news-list">
10 + <li>
11 + <h4>
12 + <%= link_to('SplitFire 🔥', splitfire_path) %>
13 + </h4>
14 + <span></span>
15 + </li>
16 + <li>
17 + <%= link_to('Bass backing track generator 🎸', bass64_branding_path) %>
18 + </li>
19 + <li>
20 + <%= link_to('Drum backing track generator 🥁', bonzo_branding_path) %>
21 + </li>
22 + <li>
23 + <%= link_to('Make karaoke version of song 🎤', karokowe_branding_path) %>
24 + <span></span>
25 + </li>
26 + </ul>
27 + </div>
28 +
29 + <div class="col-md-4">
30 + <div class="bluebox-heading">
31 + </div>
32 + <ul class="news-list">
33 + <li>
34 + <h4>
35 + <%= link_to t('nav_footer_about'), about_us_path %>
36 + </h4>
37 + <span></span>
38 + </li>
39 + <li>
40 + <h4>
41 + <%= link_to t('nav_footer_term'), term_of_use_path %>
42 + </h4>
43 + <span></span>
44 + </li>
45 + <li>
46 + <h4>
47 + <%= link_to t('nav_footer_privacy'), privacy_policy_path %>
48 + </h4>
49 + <span></span>
50 + </li>
51 + <li>
52 + <h4>
53 + <%= link_to t('nav_footer_contact'), contact_path %>
54 + </h4>
55 + <span></span>
56 + </li>
57 + </ul>
58 + <span class="clear"></span>
59 + </div>
60 +
61 + <div class="col-md-4">
62 + <div class="bluebox-heading">
63 + <h3><%= t('nav_footer_follow_us') %></h3>
64 + </div>
65 + <div class="social-widget">
66 + <ul>
67 + <li>
68 + <a href="https://www.facebook.com/tutorialgitar88/" target="__blank">
69 + <span><i class="icon-facebook"></i></span>
70 + <span><i class="icon-facebook"></i></span>
71 + </a>
72 + </li>
73 + <li>
74 + <a href="https://www.instagram.com/tutorialgitar88/" target="__blank">
75 + <span><i class="icon-instagram"></i></span>
76 + <span><i class="icon-instagram"></i></span>
77 + </a>
78 + </li>
79 + </ul>
80 + </div>
81 + <span class="clear"></span>
82 + </div>
83 + </div>
84 +</footer>
85 +<!-- END footer -->
86 +<div class="copyrights-content">
87 + crafted with ❤️ + <a href="https://www.ruby-lang.org/en/" target="__blank">💎</a>
88 + </br><a href="https://www.instagram.com/visitstockholm/" target="__blank">Stockholm</a>
89 +</div>
\ No newline at end of file
backend/musik88-web/app/views/layouts/_footer_new.html.erb new
+32
@@ -0,0 +1,32 @@
1 + <footer class="pt-5 text-muted border-top">
2 + <% unless current_page?(api_root_path) || current_page?(api_v1_root_path) %>
3 + <div class="row mb-3 g-3 align-items-center">
4 + <div class="col-auto">
5 + <i class="bi bi-globe"></i>
6 + </div>
7 + <div class="col-auto">
8 + <select id="select-locale" class="form-select" aria-label="Locale" onchange="changeLocale()">
9 + <% I18n.available_locales.each do |locale| %>
10 + <% if %i[ja id-id sv-se en].include? locale %>
11 + <option <%= 'selected' if I18n.locale == locale %> value="<%= current_domain_locale_url(locale, request.query_parameters) %>"><%= t('locale_name', locale: locale) %></option>
12 + <% end %>
13 + <% end %>
14 + </select>
15 + <script>
16 + function changeLocale() {url = document.getElementById("select-locale").value;window.location = url;}
17 + </script>
18 + </div>
19 + </div>
20 + <% end %>
21 + <div class="row g-3">
22 + <div class="col-md-12">
23 + <span class="fst-italic fw-light fs-6 float-end">
24 + <%= link_to(t('nav_footer_about'), about_us_path) %> |
25 + <%= link_to(t('nav_footer_term'), term_of_use_path) %> |
26 + <%= link_to(t('nav_footer_privacy'), privacy_policy_path) %> |
27 + <%= link_to(t('nav_footer_contact'), contact_path) %> |
28 + 💎 <%= Date.current.year %>
29 + </span>
30 + </div>
31 + </div>
32 + </footer>
\ No newline at end of file
backend/musik88-web/app/views/layouts/_header.html.erb new
+63
@@ -0,0 +1,63 @@
1 +<header>
2 +<nav>
3 + <ul>
4 + <li class="<%= current_class?(root_path) %>">
5 + <a href="<%= root_url %>">
6 + <i class="icon-home"></i><%= t('breadcrumb_home') %>
7 + </a>
8 + </li>
9 + <li class="subnav <%= current_class?(splitfire_path, bass64_path, karokowe_path) %>">
10 + <a href="#">🤖 Artificial Intelligence Tools</a>
11 + <ul>
12 + <li class="<%= current_class?(splitfire_path) %>">
13 + <%= link_to "SplitFire 🔥", splitfire_path %>
14 + </li>
15 + <li class="<%= current_class?(bass64_branding_path) %>">
16 + <%= link_to "Bass64 🎸", bass64_branding_path %>
17 + </li>
18 + <li class="<%= current_class?(karokowe_branding_path) %>">
19 + <%= link_to "KaroKowe 🎤", karokowe_branding_path %>
20 + </li>
21 + <li class="<%= current_class?(bonzo_branding_path) %>">
22 + <%= link_to "Bonzo 🥁", bonzo_branding_path %>
23 + </li>
24 + </ul>
25 + </li>
26 + <li class="subnav <%= current_class?(artist_path, album_path, song_path) %>">
27 + <a href="#"><%= t('nav_music_database') %></a>
28 + <ul>
29 + <li class="<%= current_class?(artist_path) %>">
30 + <%= link_to t('nav_artists'), artist_path %>
31 + </li>
32 + <li class="<%= current_class?(album_path) %>">
33 + <%= link_to t('nav_albums'), album_path %>
34 + </li>
35 + <li class="<%= current_class?(song_path) %>">
36 + <%= link_to t('nav_songs'), song_path %>
37 + </li>
38 + </ul>
39 + </li>
40 + </ul>
41 +</nav>
42 +</header>
43 +
44 +<!-- BEGIN mobile nav -->
45 +<select class="bb-mobile-nav" onChange="if (this.value) window.location.href=this.value">
46 + <option value="<%= root_url %>" <%= current_class_mobile?(root_path) %>><%= t('breadcrumb_home') %></option>
47 + <option value="<%= splitfire_path %>" <%= current_class_mobile?(splitfire_path) %>>SplitFire 🔥</option>
48 + <option value="<%= bass64_branding_path %>" <%= current_class_mobile?(bass64_branding_path) %>>Bass64 🎸</option>
49 + <option value="<%= karokowe_branding_path %>" <%= current_class_mobile?(karokowe_branding_path) %>>Karokowe 🎤</option>
50 + <option value="<%= bonzo_branding_path %>" <%= current_class_mobile?(bonzo_branding_path) %>>Bonzo 🥁</option>
51 + <% if user_signed_in? %>
52 + <option value="<%= artist_url %>" <%= current_class_mobile?(artist_path) %>><%= t('nav_artists') %></option>
53 + <option value="<%= album_url %>" <%= current_class_mobile?(album_path) %>><%= t('nav_albums') %></option>
54 + <option value="<%= song_url %>" <%= current_class_mobile?(song_path) %>><%= t('nav_songs') %></option>
55 + <option value="<%= song_lyrics_url %>" <%= current_class_mobile?(song_lyrics_path) %>><%= t('nav_lyrics') %></option>
56 + <option value="<%= guitar_chords_url %>" <%= current_class_mobile?(guitar_chords_path) %>><%= t('nav_chords') %></option>
57 + <option value="<%= guitar_chord_shapes_url %>" <%= current_class_mobile?(guitar_chord_shapes_path) %>><%= t('nav_chords_shapes') %></option>
58 + <% end %>
59 + <option value="<%= javanese_gamelan_url %>" <%= current_class_mobile?(javanese_gamelan_path) %>><%= t('nav_javanese_gamelan') %></option>
60 + <option value="<%= javanese_gending_url %>" <%= current_class_mobile?(javanese_gending_path) %>><%= t('nav_javanese_gending') %></option>
61 + <option value="<%= javanese_gending_gamelan_notation_url %>" <%= current_class_mobile?(javanese_gending_gamelan_notation_path) %>><%= t('nav_javanese_gending_notation') %></option>
62 +</select>
63 +<!-- END mobile nav -->
\ No newline at end of file
backend/musik88-web/app/views/layouts/_top_bar.html.erb new
+21
@@ -0,0 +1,21 @@
1 +<!-- BEGIN top bar -->
2 +<header class="top-bar">
3 + <div class="full-width-info left">
4 + <% unless current_page?(edit_user_password_path) %>
5 + <%= t('nav_change_language') %>
6 + <% I18n.available_locales.each do |locale| %>
7 + <% if locale != I18n.locale %>
8 + <%= link_to image_pack_tag("#{locale.to_s}-flag-icon-16.png", alt:"#{locale.to_s.upcase} flag icon", style: "margin:4px;width:24px;height:16px;"), current_domain_locale_url[locale], rel: 'alternate', hreflang: locale.to_s %>
9 + <% end %>
10 + <% end %>
11 + <% end %>
12 + </div>
13 + <div class="full-width-info right">
14 + <% if user_signed_in? %>
15 + <%= link_to current_user.name, my_path %> / <%= link_to t('nav_logout'), logout_path, method: :delete %>
16 + <% else %>
17 + <%= link_to t('nav_login'), login_path %>
18 + <% end %>
19 + </div>
20 +</header>
21 +<!-- END top bar -->
\ No newline at end of file
backend/musik88-web/app/views/layouts/application.html.erb new
+197
@@ -0,0 +1,197 @@
1 +<!-- No Masters But Ourselves -->
2 +<!-- No Masters But Ourselves -->
3 +<!-- No Masters But Ourselves -->
4 +<!-- No Masters But Ourselves -->
5 +<!-- No Masters But Ourselves -->
6 +<!-- No Masters But Ourselves -->
7 +<!-- No Masters But Ourselves -->
8 +<!-- No Masters But Ourselves -->
9 +<!-- No Masters But Ourselves -->
10 +<!-- No Masters But Ourselves -->
11 +<!-- No Masters But Ourselves -->
12 +<!-- No Masters But Ourselves -->
13 +<!-- No Masters But Ourselves -->
14 +<!-- No Masters But Ourselves -->
15 +<!-- No Masters But Ourselves -->
16 +<!-- No Masters But Ourselves -->
17 +<!-- No Masters But Ourselves -->
18 +<!-- No Masters But Ourselves -->
19 +<!-- No Masters But Ourselves -->
20 +<!-- No Masters But Ourselves -->
21 +<!-- No Masters But Ourselves -->
22 +<!-- No Masters But Ourselves -->
23 +<!-- No Masters But Ourselves -->
24 +<!-- No Masters But Ourselves -->
25 +<!-- No Masters But Ourselves -->
26 +<!-- No Masters But Ourselves -->
27 +<!-- No Masters But Ourselves -->
28 +<!-- No Masters But Ourselves -->
29 +<!-- No Masters But Ourselves -->
30 +<!-- No Masters But Ourselves -->
31 +<!-- No Masters But Ourselves -->
32 +<!-- No Masters But Ourselves -->
33 +<!-- No Masters But Ourselves -->
34 +<!-- No Masters But Ourselves -->
35 +<!-- No Masters But Ourselves -->
36 +<!-- No Masters But Ourselves -->
37 +<!-- No Masters But Ourselves -->
38 +<!-- No Masters But Ourselves -->
39 +<!-- No Masters But Ourselves -->
40 +<!-- No Masters But Ourselves -->
41 +<!-- No Masters But Ourselves -->
42 +<!-- No Masters But Ourselves -->
43 +<!-- No Masters But Ourselves -->
44 +<!-- No Masters But Ourselves -->
45 +<!-- No Masters But Ourselves -->
46 +<!-- No Masters But Ourselves -->
47 +<!-- No Masters But Ourselves -->
48 +<!-- No Masters But Ourselves -->
49 +<!-- No Masters But Ourselves -->
50 +<!-- No Masters But Ourselves -->
51 +<!-- No Masters But Ourselves -->
52 +<!-- No Masters But Ourselves -->
53 +<!-- No Masters But Ourselves -->
54 +<!-- No Masters But Ourselves -->
55 +<!-- No Masters But Ourselves -->
56 +<!-- No Masters But Ourselves -->
57 +<!-- No Masters But Ourselves -->
58 +<!-- No Masters But Ourselves -->
59 +<!-- No Masters But Ourselves -->
60 +<!-- No Masters But Ourselves -->
61 +<!-- No Masters But Ourselves -->
62 +<!-- No Masters But Ourselves -->
63 +<!-- No Masters But Ourselves -->
64 +<!-- No Masters But Ourselves -->
65 +<!-- No Masters But Ourselves -->
66 +<!-- No Masters But Ourselves -->
67 +<!-- No Masters But Ourselves -->
68 +<!-- No Masters But Ourselves -->
69 +<!-- No Masters But Ourselves -->
70 +<!-- No Masters But Ourselves -->
71 +<!-- No Masters But Ourselves -->
72 +<!-- No Masters But Ourselves -->
73 +<!-- No Masters But Ourselves -->
74 +<!-- No Masters But Ourselves -->
75 +<!-- No Masters But Ourselves -->
76 +<!-- No Masters But Ourselves -->
77 +<!-- No Masters But Ourselves -->
78 +<!-- No Masters But Ourselves -->
79 +<!-- No Masters But Ourselves -->
80 +<!-- No Masters But Ourselves -->
81 +<!-- No Masters But Ourselves -->
82 +<!-- No Masters But Ourselves -->
83 +<!-- No Masters But Ourselves -->
84 +<!-- No Masters But Ourselves -->
85 +<!-- No Masters But Ourselves -->
86 +<!-- No Masters But Ourselves -->
87 +<!-- No Masters But Ourselves -->
88 +<!-- No Masters But Ourselves -->
89 +<!doctype html>
90 +<html lang="en">
91 + <head>
92 + <meta charset="utf-8">
93 + <meta name="viewport" content="width=device-width, initial-scale=1">
94 + <meta name="author" content="Seto Elkahfi and Musik88 contributors">
95 + <% if content_for?(:meta_head) %>
96 + <%= yield(:meta_head) %>
97 + <% end %>
98 + <%= csrf_meta_tags %>
99 + <%= csp_meta_tag %>
100 + <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
101 + <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
102 + <%= favicon_pack_tag 'favicon.svg' %>
103 + <%= action_cable_meta_tag %>
104 + <% if production? %>
105 + <!-- Global site tag (gtag.js) - Google Analytics -->
106 + <script async src="https://www.googletagmanager.com/gtag/js?id=UA-119885394-1"></script>
107 + <script>
108 + window.dataLayer = window.dataLayer || [];
109 + function gtag(){dataLayer.push(arguments);}
110 + gtag('js', new Date());
111 +
112 + gtag('config', 'UA-119885394-1');
113 + </script>
114 + <!-- Google Tag Manager -->
115 + <script>
116 + (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-PLTHXCM');
117 + </script>
118 + <!-- End Google Tag Manager -->
119 + <script src="https://www.googleoptimize.com/optimize.js?id=GTM-NBND339"></script>
120 + <% end %>
121 + <% canonical_urls.each_with_index do |canonical_url, index| %>
122 + <% if index == 0 %>
123 + <link rel="canonical" href="<%= canonical_url.url %>">
124 + <% else %>
125 + <link rel="alternate" href="<%= canonical_url.url %>" hreflang="<%= canonical_url.locale %>">
126 + <% end%>
127 + <% end %>
128 + <link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
129 + <link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
130 + <link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
131 + <link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
132 + <link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
133 + <link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
134 + <link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
135 + <link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
136 + <link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
137 + <link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
138 + <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
139 + <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
140 + <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
141 + <link rel="manifest" href="/manifest.json">
142 + <meta name="msapplication-TileColor" content="#ffffff">
143 + <meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
144 + <meta name="theme-color" content="#ffffff">
145 + <script type="application/ld+json">
146 + {
147 + "@context": "http://schema.org",
148 + "@type": "Organization",
149 + "legalName": "Musik 88",
150 + "url": "<%= root_url %>",
151 + "contactPoint": [{
152 + "@type" : "ContactPoint",
153 + "telephone" : "+6281519528499",
154 + "contactType" : "customer service"
155 + }],
156 + "logo": "/apple-icon-180x180.png",
157 + "sameAs": [
158 + "http://www.facebook.com/tutorialgitar88",
159 + "https://www.youtube.com/user/tutorialgitar88",
160 + "http://www.instagram.com/tutorialgitar88"
161 + ]
162 + }
163 + </script>
164 + <% if content_for?(:breadcrumb) %>
165 + <%= yield(:breadcrumb) %>
166 + <% end %>
167 + </head>
168 + <body>
169 + <div class="col-lg-8 mx-auto p-3 py-md-5">
170 + <header class="align-items-center pb-3 mb-5 border-bottom">
171 + <a href="<%= root_url %>" class="d-inline-flex align-items-center text-dark text-decoration-none">
172 + <span class="fs-4">Musik</span>
173 + <%= image_tag('https://avatar.oxro.io/avatar.svg?name=8+8&background=FFCC00&length=3', size: '40x32') %>
174 + </a>
175 + <span class="fst-italic fw-lighter">beta</span>
176 + <span class="d-inline-flex p-2 align-items-center float-end">
177 + <%= link_to t('nav_buy_bach'), 'https://beta.raydium.io/swap/?inputMint=CTQBjyrX8pYyqbNa8vAhQfnRXfu9cUxnvrxj5PvbzTmf&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', target: '__blank' %>
178 + </span>
179 + <span class="d-inline-flex p-2 align-items-center float-end">
180 + <%= link_to(
181 + "@#{current_user.username_or_id}",
182 + profile_path(current_user.username_or_id)
183 + ) if user_signed_in? %>
184 + </span>
185 + <span class="d-inline-flex p-2 align-items-center float-end">
186 + <% if user_signed_in? %>
187 + <%= link_to t('nav_logout'), logout_path(redirect_to: request.path), method: :delete %>
188 + <% else %>
189 + <%= link_to t('nav_login'), login_path(redirect_to: request.path) %>
190 + <% end %>
191 + </span>
192 + </header>
193 + <%= yield %>
194 + <%= render 'layouts/footer_new' %>
195 + </div>
196 + </body>
197 +</html>
backend/musik88-web/app/views/layouts/mailer.text.erb new
+1
@@ -0,0 +1 @@
1 +<%= yield %>
backend/musik88-web/app/views/lyrics/_partial_aside.html.erb new
+27
@@ -0,0 +1,27 @@
1 +
2 + <aside class="mb-3">
3 + <% if @chords.present? %>
4 + <div class="bluebox-heading">
5 + <h2><%= song.name %></h2>
6 + </div>
7 + <% @chords.each do |chord| %>
8 + <h4>
9 + <%= link_to(
10 + chord.link_title,
11 + guitar_chord_detail_path(chord.slug)
12 + ) %>
13 + </h4>
14 + <span style="text-transform: capitalize;font-style: italic;">
15 + <%= t("song_updated_at", date: chord.updated_at.strftime("%B %-d, %Y")) %>
16 + </span>
17 + <% end %>
18 + <% end %>
19 + </aside>
20 + <aside class="mb-3">
21 + <div class="mb-3">
22 + <h2><%= song.album.name %></h2>
23 + </div>
24 + <% song.album.songs.each do |song| %>
25 + <h4 class="mb-3 fs-6"><%= yield(song) %></h4>
26 + <% end %>
27 + </aside>
backend/musik88-web/app/views/lyrics/_partial_menu.html.erb new
+14
@@ -0,0 +1,14 @@
1 +<li class="nav-item">
2 + <% if active %>
3 + <a href="javascript:void(0)" class="nav-link active" aria-current="page">
4 + <%= href_text %>
5 + </a>
6 + <% else %>
7 + <%= link_to(
8 + href_text,
9 + href_link,
10 + class: "nav-link"
11 + )
12 + %>
13 + <% end %>
14 +</li>
\ No newline at end of file
backend/musik88-web/app/views/lyrics/_partial_menus.html.erb new
+20
@@ -0,0 +1,20 @@
1 +<div class="row mb-5">
2 + <div class="col-md-12">
3 + <ul class="nav nav-pills">
4 + <%= render(
5 + "partial_menu",
6 + href_text: @song.name,
7 + href_link: song_detail_path(@song.slug),
8 + active: active_menu == 'song'
9 + )
10 + %>
11 + <%= render(
12 + "partial_menu",
13 + href_text: t("song_menu_lyric", song_name: ''),
14 + href_link: lyric_detail_path(@song.slug),
15 + active: active_menu == 'lyric'
16 + )
17 + %>
18 + </ul>
19 + </div>
20 +</div>
\ No newline at end of file
backend/musik88-web/app/views/lyrics/_partial_meta_footer.html.erb new
+8
@@ -0,0 +1,8 @@
1 +<div class="meta-line mt-5 mb-5">
2 + <i class="bi bi-eye-fill"></i> <%= counter %>
3 + <% if author == nil %>
4 + <%= link_to(t('signup_to_claim'), login_path, class: 'more-link more-link-author float-end') %>
5 + <% else %>
6 + <%= link_to("<i class='bi bi-person-circle'></i> @#{author.username_or_id}".html_safe, profile_path_enhanced(author), class: 'more-link more-link-author float-end') %>
7 + <% end %>
8 +</div>
\ No newline at end of file
backend/musik88-web/app/views/lyrics/_partial_meta_header.html.erb new
+36
@@ -0,0 +1,36 @@
1 +<div class="meta-line mb-5">
2 + <% if user_signed_in? && @song.users.include?(current_user) %>
3 + <%= link_to(
4 + '<i class="bi bi-heart-fill"></i>'.html_safe,
5 + song_chord_pleas_unloved_path(id: @song.id),
6 + remote: true,
7 + method: :delete,
8 + :id => "ajax_trigger"
9 + )
10 + %>
11 + <% else %>
12 + <%= link_to(
13 + '<i class="bi bi-heart"></i>'.html_safe,
14 + song_chord_pleas_loved_path(id: @song.id),
15 + remote: true,
16 + method: :post,
17 + :id => "ajax_trigger"
18 + )
19 + %>
20 + <% end %>
21 +
22 + <% if @lovers != nil && @lovers.count > 0 %>
23 + <span class="meta-line-by"><%= t('by') %><span>
24 + <% @lovers.each_with_index do |user, index| %>
25 + <%= " " if index != 0 %>
26 + <span class="meta-line-lovers">
27 + <%= link_to name_or_username(user), profile_path_enhanced(user) %>
28 + </span>
29 + <%= "," if index != @lovers.count - 1 %>
30 + <% end %>
31 + <% else %>
32 + <span class="meta-line-no-love">
33 + <%= t('somebody_to_love') %>
34 + <span>
35 + <% end %>
36 +</div>
\ No newline at end of file
backend/musik88-web/app/views/lyrics/index.html.erb new
+87
@@ -0,0 +1,87 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= request.url %>",
25 + "name": "<%= t("nav_lyrics") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<main>
32 + <div class="row g-5">
33 + <div class="col-md-12">
34 + <h1><%= @page_title %></h1>
35 + <%= render('shared/flash_message', object: flash) %>
36 + </div>
37 + </div>
38 + <div class="row g-5">
39 + <div class="col-sm-3">
40 + <div class="position-sticky" style="top: 1rem;">
41 + <h2><%= t("top_10_song_lyrics") %></h2>
42 + <% @trending_lyrics.each_with_index do |lyric, index| %>
43 + <div class="row mb-3">
44 + <div class="col-sm-9">
45 + <%= "#{index + 1}." %>
46 + <%= link_to(
47 + t("song_lyric_detail", title: lyric.title_link),
48 + lyric_detail_path(lyric.slug)
49 + ) %>
50 + </div>
51 + <div class="col-sm-3">
52 + <i class="bi bi-graph-up-arrow"></i>
53 + <span class="badge rounded-pill bg-warning text-dark"><%= lyric.views_count %></span>
54 + </div>
55 + </div>
56 + <% end %>
57 + </div>
58 + </div>
59 + <div class="col-sm-9">
60 + <h2>
61 + <%= t("our_newest_song_lyrics") %>
62 + </h2>
63 + <% @lyrics.each_with_index do |lyric, index| %>
64 + <% song = lyric.song %>
65 + <div class="row <%= index % 2 == 0 ? 'list-group-item-primary' : ' list-group-item-warning' %>">
66 + <div class="col-11 mb-3 mt-3">
67 + <%= link_to(
68 + t("song_lyric_detail", title: lyric.title_link),
69 + lyric_detail_path(lyric.slug)
70 + ) %>
71 + </div>
72 + <div class="col-1 mb-3 mt-3">
73 + <%= link_to(
74 + '<i class="bi bi-info-circle"></i>'.html_safe,
75 + song_lyrics_path(song.slug),
76 + title: t('song_menu_aside_lyrics')
77 + ) %>
78 + </div>
79 + </div>
80 + <% end %>
81 + <%= will_paginate @lyrics, class: 'flickr_pagination' %>
82 + </div>
83 + </div>
84 + <%= render 'shared/partial_breadcrumbs' do %>
85 + <li class="breadcrumb-item"><%= link_to t("nav_lyrics"), request.url %></li>
86 + <% end %>
87 +</main>
backend/musik88-web/app/views/lyrics/lyric.html.erb new
+91
@@ -0,0 +1,91 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= artist_url %>",
11 + "name": "<%= t("nav_artists") %>"
12 + }
13 + },
14 + <% if @song.artists.first.present? %>
15 + {
16 + "@type": "ListItem",
17 + "position": 3,
18 + "item": {
19 + "@id": "<%= artist_detail_url(@song.artists.first&.slug || 'NaN') %>",
20 + "name": "<%= @song.artists.first&.name || 'NaN' %>"
21 + }
22 + },
23 + <% end %>
24 + {
25 + "@type": "ListItem",
26 + "position": 4,
27 + "item": {
28 + "@id": "<%= album_detail_url(@song.album.slug) %>",
29 + "name": "<%= @song.album.name %>"
30 + }
31 + },{
32 + "@type": "ListItem",
33 + "position": 5,
34 + "item": {
35 + "@id": "<%= lyric_detail_url(@lyric.slug) %>",
36 + "name": "<%= t("song_lyric") + @song.name %>"
37 + }
38 + }
39 + <% end %>
40 +<% end %>
41 +<main>
42 + <div class="row g-5">
43 + <div class="col-md-12">
44 + <h1><%= @page_title %></h1>
45 + </div>
46 + </div>
47 + <div class="row g-5">
48 + <div class="col-sm-1">
49 + <div class="position-sticky" style="top: 1rem;">
50 + <div class="text-center mt-4">
51 + <%= "<i class='bi #{@lyric.status_symbol} #{@lyric.status_color}' style='font-size: 1.5rem;'></i>".html_safe %>
52 + </div>
53 + <div class="text-center mt-4">
54 + <i class="bi bi-graph-up-arrow"></i>
55 + <span class="badge rounded-pill bg-warning text-dark"><%= @lyric.views_count %></span>
56 + </div>
57 + </div>
58 + </div>
59 + <div class="col-sm-8">
60 + <%= raw @lyric_shown %>
61 + <div class="row mt-3">
62 + <h3 id="discussion"><%= t('discussion') %></h3>
63 + <%= render "shared/flash_message", object: flash %>
64 + <div class="mb-3">
65 + <%= form_for [@lyric, Comment.new] do |f| %>
66 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
67 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
68 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
69 + <% end %>
70 + </div>
71 + <%= render(partial: 'comments/comment', collection: @lyric.comments) %>
72 + </div>
73 + </div>
74 + <div class="col-sm-3">
75 + <%= render "partial_aside.html", song: @song do |song| %>
76 + <%= link_to(
77 + song.name,
78 + song_detail_path(song.slug)
79 + ) %>
80 + <% end %>
81 + </div>
82 + </div>
83 + <%= render "shared/partial_breadcrumbs" do %>
84 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
85 + <% if @song.artists.first.present? %>
86 + <li class="breadcrumb-item"><%= link_to @song.artists.first&.name || 'NaN', artist_detail_url(@song.artists.first&.slug || 'NaN') %></li>
87 + <% end %>
88 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
89 + <li class="breadcrumb-item"><%= link_to @song.name, lyric_detail_path(@lyric.slug) %></li>
90 + <% end %>
91 +</main>
backend/musik88-web/app/views/my/_partial_admin_files_row.html.erb new
+13
@@ -0,0 +1,13 @@
1 +<tr style="vertical-align: top">
2 + <td style="padding: 10px 10px 10px 0;">
3 + <% if audio_file.source_file.attached? %>
4 + <%= audio_file.source_file.blob.filename %>
5 + <% else %>
6 + <i>File is missing. Please delete this.</i>
7 + <% end %>
8 + <%= render "my/partial_audio_file_information", audio_file: audio_file %>
9 + </td>
10 + <td style="padding: 10px; width: 100px">
11 + <%= link_to(audio_file.user.name, profile_path(audio_file.user.username.presence ? audio_file.user.username : audio_file.user.id)) %>
12 + </td>
13 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_audio_file_information.html.erb new
+15
@@ -0,0 +1,15 @@
1 +<div class="audio-file-information" style="padding: 10px;">
2 + <% if !audio_file.song.nil? %>
3 + <div class="audio-file-information-song" style="margin-bottom: 5px;">
4 + <%= "<b>Song</b>: #{audio_file.song.name}".html_safe %>
5 + </div>
6 + <div class="audio-file-information-album" style="margin-bottom: 5px;">
7 + <%= "<b>Album</b>: #{audio_file.song.album.name}".html_safe %>
8 + </div>
9 + <div class="audio-file-information-artists" style="margin-bottom: 5px;">
10 + <%= "<b>Artist(s)</b>: #{audio_file.song.artists.map{|artist| artist.name}.join(", ")}".html_safe %>
11 + </div>
12 + <% else %>
13 + <i>Add information about this file? (WIP)</i>
14 + <% end %>
15 +</div>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_bass64_row.html.erb new
+25
@@ -0,0 +1,25 @@
1 +<tr style="vertical-align: middle" id="bass64-row-<%= audio_file.id %>">
2 + <td style="padding: 10px 10px 10px 0;">
3 + <% if audio_file.source_file.attached? %>
4 + <%= link_to(audio_file.source_file.blob.filename, my_bass64_detail_path(audio_file.id)) %>
5 + <% else %>
6 + File is missing. Please delete this.
7 + <% end %>
8 + <%= render "my/partial_audio_file_information", audio_file: audio_file %>
9 + </td>
10 + <td style="padding: 10px; width: 200px">
11 + <% if !audio_file.source_file.attached? %>
12 + Missing file.
13 + <% elsif audio_file.audio_file_bass64_status.nil? || (!audio_file.audio_file_bass64_status.nil? && audio_file.audio_file_bass64_status.pending? || audio_file.audio_file_bass64_status.cancelled?) %>
14 + <%= link_to("Bass64 🎸", my_bass64_start_path(audio_file.id), data: { confirm: '"This will take time 🎸" - Bass64' }, method: :post) %>
15 + <% elsif !audio_file.audio_file_bass64_status.nil? && audio_file.audio_file_bass64_status.processing? %>
16 + Bass64 is processing <%= image_pack_tag "fire.gif", :size => "25x25", :style => "vertical-align: bottom" %>
17 + </br>
18 + <%= link_to("<i class='icon-remove'></i> Cancel".html_safe, my_bass64_cancel_path(audio_file.id), data: { confirm: 'This will cancel the Bass64 🎸 process.' }, method: :patch) %>
19 + <% elsif !audio_file.audio_file_bass64_status.nil? && audio_file.audio_file_bass64_status.done? %>
20 + <%= link_to("<i class='icon-list'></i> See results".html_safe, my_bass64_detail_path(audio_file.id)) %>
21 + <% elsif !audio_file.audio_file_bass64_status.nil? && audio_file.audio_file_bass64_status.no_results? %>
22 + <%= link_to("Bass64 #{image_pack_tag('fire.gif', :size => '15x15', :style => 'vertical-align: top')} again?".html_safe, my_bass64_start_path(audio_file.id), data: { confirm: '"This will take time 🎸" - Bass64' }, method: :post) %>
23 + <% end %>
24 + </td>
25 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_bass64_version.html.erb new
+3
@@ -0,0 +1,3 @@
1 +<span class="modul-version" style="float: right; font-style: italic; text-decoration: underline;">
2 + <%= link_to("Bass64 v1.0.0", bass64_path(:anchor => "v1-0-0"), target: "__blank") %>
3 +<span>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_bonzo_row.html.erb new
+25
@@ -0,0 +1,25 @@
1 +<tr style="vertical-align: middle" id="bonzo-row-<%= audio_file.id %>">
2 + <td style="padding: 10px 10px 10px 0;">
3 + <% if audio_file.source_file.attached? %>
4 + <%= link_to(audio_file.source_file.blob.filename, my_bonzo_detail_path(audio_file.id)) %>
5 + <% else %>
6 + File is missing. Please delete this.
7 + <% end %>
8 + <%= render "my/partial_audio_file_information", audio_file: audio_file %>
9 + </td>
10 + <td style="padding: 10px; width: 200px">
11 + <% if !audio_file.source_file.attached? %>
12 + Missing file.
13 + <% elsif audio_file.audio_file_bonzo_status.nil? || (!audio_file.audio_file_bonzo_status.nil? && audio_file.audio_file_bonzo_status.pending? || audio_file.audio_file_bonzo_status.cancelled?) %>
14 + <%= link_to("Bonzo 🥁", my_bonzo_start_path(audio_file.id), data: { confirm: '"This will take time 🥁" - Bonzo' }, method: :post) %>
15 + <% elsif !audio_file.audio_file_bonzo_status.nil? && audio_file.audio_file_bonzo_status.processing? %>
16 + Bonzo is processing <%= image_pack_tag "fire.gif", :size => "25x25", :style => "vertical-align: bottom" %>
17 + </br>
18 + <%= link_to("<i class='icon-remove'></i> Cancel".html_safe, my_bonzo_cancel_path(audio_file.id), data: { confirm: 'This will cancel the Bonzo 🥁 process.' }, method: :patch) %>
19 + <% elsif !audio_file.audio_file_bonzo_status.nil? && audio_file.audio_file_bonzo_status.done? %>
20 + <%= link_to("<i class='icon-list'></i> See results".html_safe, my_bonzo_detail_path(audio_file.id)) %>
21 + <% elsif !audio_file.audio_file_bonzo_status.nil? && audio_file.audio_file_bonzo_status.no_results? %>
22 + <%= link_to("Bonzo 🥁 again?".html_safe, my_bonzo_start_path(audio_file.id), data: { confirm: '"This will take time 🥁" - Bonzo' }, method: :post) %>
23 + <% end %>
24 + </td>
25 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_bonzo_version.html.erb new
+3
@@ -0,0 +1,3 @@
1 +<span class="modul-version" style="float: right; font-style: italic; text-decoration: underline;">
2 + <%= link_to("Bonzo v1.0.0", bonzo_path(:anchor => "v1-0-0"), target: "__blank") %>
3 +<span>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_files_row.html.erb new
+13
@@ -0,0 +1,13 @@
1 +<tr style="vertical-align: top">
2 + <td style="padding: 10px 10px 10px 0;">
3 + <% if audio_file.source_file.attached? %>
4 + <%= link_to(audio_file.source_file.blob.filename, my_file_detail_path(audio_file.id)) %>
5 + <% else %>
6 + <i>File is missing. Please delete this.</i>
7 + <% end %>
8 + <%= render "my/partial_audio_file_information", audio_file: audio_file %>
9 + </td>
10 + <td style="padding: 10px; width: 100px">
11 + <%= link_to("<i class='icon-remove'></i> Delete".html_safe, my_delete_file_path(audio_file.id), data: { confirm: 'Warning! This will delete all SplitFire and Bass64 result files. Are you sure?' }, method: :delete) %>
12 + </td>
13 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_karokowe_result_row.html.erb new
+11
@@ -0,0 +1,11 @@
1 +<tr>
2 + <td style="padding: 10px 10px 10px 0;">
3 + <%= audio_file.source_file.blob.filename %>
4 + </td>
5 + <td style="padding: 10px; width: 130px">
6 + <%= link_to("<i class='icon-download'></i> Download".html_safe, rails_blob_path(audio_file.source_file, disposition: "attachment"))%>
7 + </td>
8 + <td style="padding: 10px; width: 100px">
9 + <%= link_to("<i class='icon-remove'></i> Delete".html_safe, my_karokowe_delete_path(audio_file.id), data: { confirm: 'Are you sure?' }, method: :delete )%>
10 + </td>
11 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_karokowe_result_status.html.erb new
+5
@@ -0,0 +1,5 @@
1 +<% if audio_file.audio_file_karokowe_status.processing? %>
2 + We are generating the karaoke file for this file</br><%= image_pack_tag('fire.gif', :size => '34x34', :style => 'vertical-align: top') %>
3 +<% elsif audio_file.karaoke_file.nil? %>
4 + No result yet. Go back and click generate karaoke file 🎤
5 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_karokowe_row.html.erb new
+25
@@ -0,0 +1,25 @@
1 +<tr style="vertical-align: middle" id="karokowe-row-<%= audio_file.id %>">
2 + <td style="padding: 10px 10px 10px 0;">
3 + <% if audio_file.source_file.attached? %>
4 + <%= link_to(audio_file.source_file.blob.filename, my_karokowe_detail_path(audio_file.id)) %>
5 + <% else %>
6 + File is missing. Please delete this.
7 + <% end %>
8 + <%= render "my/partial_audio_file_information", audio_file: audio_file %>
9 + </td>
10 + <td style="padding: 10px; width: 200px">
11 + <% if !audio_file.source_file.attached? %>
12 + Missing file.
13 + <% elsif audio_file.audio_file_karokowe_status.nil? || (!audio_file.audio_file_karokowe_status.nil? && audio_file.audio_file_karokowe_status.pending? || audio_file.audio_file_karokowe_status.cancelled?) %>
14 + <%= link_to("Generate karaoke 🎤", my_karokowe_start_path(audio_file.id), data: { confirm: '"This will take time 🎤" - KaroKowe' }, method: :post) %>
15 + <% elsif !audio_file.audio_file_karokowe_status.nil? && audio_file.audio_file_karokowe_status.processing? %>
16 + Generating karaoke <%= image_pack_tag "fire.gif", :size => "25x25", :style => "vertical-align: bottom" %>
17 + </br>
18 + <%= link_to("<i class='icon-remove'></i> Cancel".html_safe, my_karokowe_cancel_path(audio_file.id), data: { confirm: 'This will cancel KaroKowe 🔥.' }, method: :patch) %>
19 + <% elsif !audio_file.audio_file_karokowe_status.nil? && audio_file.audio_file_karokowe_status.done? %>
20 + <%= link_to("<i class='icon-list'></i> See results".html_safe, my_karokowe_detail_path(audio_file.id)) %>
21 + <% elsif !audio_file.audio_file_karokowe_status.nil? && audio_file.audio_file_karokowe_status.no_results? %>
22 + <%= link_to("Generate karaoke 🎤 again?", my_karokowe_start_path(audio_file.id), data: { confirm: '"This will take time 🔥" - karokowe' }, method: :post) %>
23 + <% end %>
24 + </td>
25 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_karokowe_version.html.erb new
+3
@@ -0,0 +1,3 @@
1 +<span class="modul-version" style="float: right; font-style: italic; text-decoration: underline;">
2 + <%= link_to("KaroKowe v1.0.0", karokowe_path(:anchor => "v1-1-0"), target: "__blank") %>
3 +<span>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_menu.html.erb new
+31
@@ -0,0 +1,31 @@
1 +<style>
2 +li.selected {
3 + font-weight: bold;
4 +}
5 +</style>
6 +
7 + <ul style="list-style: none; padding: 0px 10px 10px 10px">
8 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/profile') %>">
9 + <%= link_to "😃 Profile", my_profile_path %>
10 + </li>
11 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/files') %>">
12 + <%= link_to "📁 Arsip", my_files_path %>
13 + </li>
14 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/splitfire') %>">
15 + <%= link_to "🔥 SplitFire", my_splitfire_path %>
16 + </li>
17 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/bass64') %>">
18 + <%= link_to "Bass backing tracks 🎸", my_bass64_path %>
19 + </li>
20 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/karokowe') %>">
21 + <%= link_to "Karaoke files 🎤", my_karokowe_path %>
22 + </li>
23 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/bonzo') %>">
24 + <%= link_to "Drum backing tracks 🥁", my_bonzo_path %>
25 + </li>
26 + <% if current_user.username == 'seto' %>
27 + <li style="padding: 0px 5px 10px 0" class="<%= current_profile_menu?('/my/admin-files') %>">
28 + <%= link_to "Admin Arsip 📁", my_admin_files_path %>
29 + </li>
30 + <% end %>
31 + </ul>
backend/musik88-web/app/views/my/_partial_splitfire_result_row.html.erb new
+11
@@ -0,0 +1,11 @@
1 +<tr>
2 + <td style="padding: 10px 10px 10px 0;">
3 + <%= audio_file.source_file.blob.filename %>
4 + </td>
5 + <td style="padding: 10px; width: 130px">
6 + <%= link_to("<i class='icon-download'></i> Download".html_safe, rails_blob_path(audio_file.source_file, disposition: "attachment"))%>
7 + </td>
8 + <td style="padding: 10px; width: 100px">
9 + <%= link_to("<i class='icon-remove'></i> Delete".html_safe, my_splitfire_delete_path(audio_file.id), data: { confirm: 'Are you sure?' }, method: :delete )%>
10 + </td>
11 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_splitfire_result_status.html.erb new
+5
@@ -0,0 +1,5 @@
1 +<% if audio_file.audio_file_splitfire_status.processing? %>
2 + We are splitfiring this file</br><%= image_pack_tag('fire.gif', :size => '34x34', :style => 'vertical-align: top') %>
3 +<% elsif audio_file.splitfire_results.count == 0 %>
4 + No result yet. Go back and click SplitFire 🔥
5 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/my/_partial_splitfire_row.html.erb new
+32
@@ -0,0 +1,32 @@
1 +<tr style="vertical-align: middle" id="spitfire-row-<%= audio_file.id %>">
2 + <td style="padding: 10px 10px 10px 0;">
3 + <% if audio_file.source_file.attached? %>
4 + <%= link_to(audio_file.source_file.blob.filename, my_splitfire_detail_path(audio_file.id)) %>
5 + <% elsif audio_file.audio_file_splitfire_status.youtube_converting? %>
6 + Processing audio file...
7 + <% else %>
8 + File is missing. Please delete this.
9 + <% end %>
10 + <%= render "my/partial_audio_file_information", audio_file: audio_file %>
11 + <% if !audio_file.audio_file_splitfire_status.nil? && audio_file.audio_file_splitfire_status.processing? %>
12 + <div class="progress">
13 + <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="<%= audio_file.audio_file_splitfire_status.processing_progress %>" aria-valuemin="0" aria-valuemax="100" style="width: <%= audio_file.audio_file_splitfire_status.processing_progress %>%"></div>
14 + </div>
15 + <% end %>
16 + </td>
17 + <td style="padding: 10px; width: 200px">
18 + <% if !audio_file.source_file.attached? && !audio_file.audio_file_splitfire_status.youtube_converting? %>
19 + Missing file.
20 + <% elsif audio_file.audio_file_splitfire_status.nil? || (!audio_file.audio_file_splitfire_status.nil? && audio_file.audio_file_splitfire_status.pending? || audio_file.audio_file_splitfire_status.cancelled?) %>
21 + <%= link_to("SplitFire 🔥", my_splitfire_start_path(audio_file.id), data: { confirm: '"This will take time 🔥" - SplitFire' }, method: :post) %>
22 + <% elsif !audio_file.audio_file_splitfire_status.nil? && (audio_file.audio_file_splitfire_status.processing? || audio_file.audio_file_splitfire_status.youtube_converting?) %>
23 + Splitfiring <%= image_pack_tag "fire.gif", :size => "25x25", :style => "vertical-align: bottom" %>
24 + </br>
25 + <%= link_to("<i class='icon-remove'></i> Cancel".html_safe, my_splitfire_cancel_path(audio_file.id), data: { confirm: 'This will cancel SplitFire 🔥.' }, method: :patch) %>
26 + <% elsif !audio_file.audio_file_splitfire_status.nil? && audio_file.audio_file_splitfire_status.done? %>
27 + <%= link_to("<i class='icon-list'></i> See results".html_safe, my_splitfire_detail_path(audio_file.id)) %>
28 + <% elsif !audio_file.audio_file_splitfire_status.nil? && audio_file.audio_file_splitfire_status.no_results? %>
29 + <%= link_to("SplitFire #{image_pack_tag('fire.gif', :size => '15x15', :style => 'vertical-align: top')} again?".html_safe, my_splitfire_start_path(audio_file.id), data: { confirm: '"This will take time 🔥" - SplitFire' }, method: :post) %>
30 + <% end %>
31 + </td>
32 +</tr>
\ No newline at end of file
backend/musik88-web/app/views/my/add_lyric.html.erb new
+47
@@ -0,0 +1,47 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("song_lyric_unavailable_content_add_lyric") %> - <%= @song.name %></h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-8">
16 + <%= form_with model: @lyric, url: {action: "add_lyric"}, html: { :class => "form-horizontal" } do |form| %>
17 + <div class="form-group">
18 + <%= form.label t("lyric"), class: "col-sm-3 control-label" %>
19 + <div class="col-sm-9">
20 + <%= form.text_area :lyric, class: "col-md-6 form-control tinymce", :rows => 40, :cols => 120 %>
21 + <%= form.hidden_field :song_id, :value => @lyric.song_id %>
22 + <%= form.hidden_field :user_id, :value => @lyric.user_id %>
23 + </div>
24 + </div>
25 + <div class="form-group">
26 + <div class="col-sm-offset-3 col-sm-4">
27 + <%= form.submit t("song_lyric_unavailable_content_add_lyric"), class: "bb-button bb-green bb-large" %></br>
28 + <div class="vertical-separator"></div>
29 + <%= link_to t("cancel"), my_path, class: "bb-button bb-red bb-large" %>
30 + </div>
31 + </div>
32 + <% end %>
33 + <%= tinymce %>
34 + </div>
35 + <!-- END content -->
36 + </div>
37 +
38 + <div class="row" style="margin-top: 20px">
39 + <div class="col-md-12">
40 +
41 + <ol class="breadcrumb">
42 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
43 + </ol>
44 +
45 + </div>
46 + </div>
47 +
\ No newline at end of file
backend/musik88-web/app/views/my/admin_files.html.erb new
+50
@@ -0,0 +1,50 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | Admin</title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Admin Arsip 📁</h1>
12 + </div>
13 + <%= render "shared/flash_message", object: flash %>
14 +
15 + </div>
16 + <div class="col-md-2">
17 + <%= render "my/partial_menu" %>
18 + </div>
19 + <div class="col-md-10">
20 +
21 + <div class="row">
22 + <!-- BEGIN content -->
23 + <div class="col-md-12">
24 + <h2>Files</h2>
25 + <% if @audio_files.count == 0 %>
26 + You don't have any files.
27 + <% else %>
28 + <table style="width: 100%">
29 + <% @audio_files.each_with_index do |audio_file, index| %>
30 + <%= render "my/partial_admin_files_row", audio_file: audio_file %>
31 + <% end %>
32 + </table>
33 + <% end %>
34 + </div>
35 + </div>
36 +
37 + </div>
38 + <!-- END content -->
39 + </div>
40 +
41 + <div class="row" style="margin-top: 20px;">
42 + <div class="col-md-12">
43 +
44 + <ol class="breadcrumb">
45 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
46 + </ol>
47 +
48 + </div>
49 + </div>
50 +
\ No newline at end of file
backend/musik88-web/app/views/my/bass64.html.erb new
+56
@@ -0,0 +1,56 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Bass64 🎸</h1>
12 + </div>
13 + <%= render "shared/flash_message", object: flash %>
14 +
15 + </div>
16 + <div class="col-md-2">
17 + <%= render "my/partial_menu" %>
18 + </div>
19 + <div class="col-md-10">
20 +
21 + <div class="row">
22 + <!-- BEGIN content -->
23 + <div class="col-md-12">
24 + <div class="lead page-info">
25 + <b><i>Bass64</i></b> is your AI personal assistant to generate <b><i>bass backing track</i></b> from a song.
26 + </div>
27 + <h2>Files</h2>
28 + <% if @audio_files.count == 0 %>
29 + You don't have any files to generate the bass backing track from.
30 + <% else %>
31 + <table style="width: 100%">
32 + <% @audio_files.each_with_index do |audio_file, index| %>
33 + <%= render "my/partial_bass64_row", audio_file: audio_file %>
34 + <% end %>
35 + </table>
36 + <% end %>
37 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
38 + <%= render "my/partial_bass64_version" %>
39 + </div>
40 + </div>
41 + </div>
42 +
43 + </div>
44 + <!-- END content -->
45 + </div>
46 +
47 + <div class="row" style="margin-top: 20px;">
48 + <div class="col-md-12">
49 +
50 + <ol class="breadcrumb">
51 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
52 + </ol>
53 +
54 + </div>
55 + </div>
56 +
\ No newline at end of file
backend/musik88-web/app/views/my/bass64_detail.html.erb new
+62
@@ -0,0 +1,62 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Bass64 🎸 / Results</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-10">
19 +
20 + <div class="row">
21 + <!-- BEGIN content -->
22 + <div class="col-md-12">
23 + <div class="lead">
24 + <b>File name</b>: <%= @audio_file.source_file.blob.filename unless !@audio_file.source_file.attached? %>
25 + </div>
26 + <% if @audio_file.bass_backing_track.nil? %>
27 + No result yet. Go back and click SplitFire 🔥
28 + <% else %>
29 + <table style="width: 100%">
30 + <tr>
31 + <td style="padding: 10px 10px 10px 0;">
32 + <%= @audio_file.source_file.blob.filename %>
33 + </td>
34 + <td style="padding: 10px; width: 130px">
35 + <%= link_to("<i class='icon-download'></i> Download".html_safe, rails_blob_path(@audio_file.source_file, disposition: "attachment"))%>
36 + </td>
37 + <td style="padding: 10px; width: 100px">
38 + <%= link_to("<i class='icon-remove'></i> Delete".html_safe, my_bass64_delete_path(@audio_file.id), data: { confirm: 'Are you sure?' }, method: :delete )%>
39 + </td>
40 + </tr>
41 + </table>
42 + <% end %>
43 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
44 + <%= render "my/partial_bass64_version" %>
45 + </div>
46 + </div>
47 + </div>
48 +
49 + </div>
50 + <!-- END content -->
51 + </div>
52 +
53 + <div class="row" style="margin-top: 20px">
54 + <div class="col-md-12">
55 +
56 + <ol class="breadcrumb">
57 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
58 + </ol>
59 +
60 + </div>
61 + </div>
62 +
\ No newline at end of file
backend/musik88-web/app/views/my/bonzo.html.erb new
+56
@@ -0,0 +1,56 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Bonzo 🥁</h1>
12 + </div>
13 + <%= render "shared/flash_message", object: flash %>
14 +
15 + </div>
16 + <div class="col-md-2">
17 + <%= render "my/partial_menu" %>
18 + </div>
19 + <div class="col-md-10">
20 +
21 + <div class="row">
22 + <!-- BEGIN content -->
23 + <div class="col-md-12">
24 + <div class="lead page-info">
25 + <b><i>Bonzo</i></b> is your AI personal assistant that can generate a <b><i>drum backing track</i></b> from a song.
26 + </div>
27 + <h2>Files</h2>
28 + <% if @audio_files.count == 0 %>
29 + You don't have any files to generate the drum backing track from.
30 + <% else %>
31 + <table style="width: 100%">
32 + <% @audio_files.each_with_index do |audio_file, index| %>
33 + <%= render "my/partial_bonzo_row", audio_file: audio_file %>
34 + <% end %>
35 + </table>
36 + <% end %>
37 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
38 + <%= render "my/partial_bonzo_version" %>
39 + </div>
40 + </div>
41 + </div>
42 +
43 + </div>
44 + <!-- END content -->
45 + </div>
46 +
47 + <div class="row" style="margin-top: 20px;">
48 + <div class="col-md-12">
49 +
50 + <ol class="breadcrumb">
51 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
52 + </ol>
53 +
54 + </div>
55 + </div>
56 +
\ No newline at end of file
backend/musik88-web/app/views/my/bonzo_detail.html.erb new
+62
@@ -0,0 +1,62 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Bonzo 🥁 / Results</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-10">
19 +
20 + <div class="row">
21 + <!-- BEGIN content -->
22 + <div class="col-md-12">
23 + <div class="lead">
24 + <b>File name</b>: <%= @audio_file.source_file.blob.filename unless !@audio_file.source_file.attached? %>
25 + </div>
26 + <% if @audio_file.drum_backing_track.nil? %>
27 + No result yet. Go back and click Bonzo 🥁
28 + <% else %>
29 + <table style="width: 100%">
30 + <tr>
31 + <td style="padding: 10px 10px 10px 0;">
32 + <%= @audio_file.drum_backing_track.source_file.blob.filename %>
33 + </td>
34 + <td style="padding: 10px; width: 130px">
35 + <%= link_to("<i class='icon-download'></i> Download".html_safe, rails_blob_path(@audio_file.drum_backing_track.source_file, disposition: "attachment"))%>
36 + </td>
37 + <td style="padding: 10px; width: 100px">
38 + <%= link_to("<i class='icon-remove'></i> Delete".html_safe, my_bonzo_delete_path(@audio_file.drum_backing_track.id), data: { confirm: 'Are you sure?' }, method: :delete )%>
39 + </td>
40 + </tr>
41 + </table>
42 + <% end %>
43 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
44 + <%= render "my/partial_bonzo_version" %>
45 + </div>
46 + </div>
47 + </div>
48 +
49 + </div>
50 + <!-- END content -->
51 + </div>
52 +
53 + <div class="row" style="margin-top: 20px">
54 + <div class="col-md-12">
55 +
56 + <ol class="breadcrumb">
57 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
58 + </ol>
59 +
60 + </div>
61 + </div>
62 +
\ No newline at end of file
backend/musik88-web/app/views/my/edit.html.erb new
+78
@@ -0,0 +1,78 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Profile 😃 / Edit</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-10">
19 +
20 + <%= gravatar_for @user %>
21 +
22 +
23 + <%= render "shared/flash_message", object: flash %>
24 + <%= form_with model: @user do |form| %>
25 + <div class="form-group">
26 + <%= form.label :name, class: "col-sm-3 control-label" %>
27 + <div class="col-sm-9">
28 + <%= form.text_field :name %>
29 + </div>
30 + </div>
31 + <div class="form-group">
32 + <%= form.label :email, class: "col-sm-3 control-label" %>
33 + <div class="col-sm-9">
34 + <%= form.text_field :email, disabled: true %>
35 + <%= form.hidden_field :id, :value => @user.id %>
36 + </div>
37 + </div>
38 + <div class="form-group">
39 + <%= form.label :username, class: "col-sm-3 control-label" %>
40 + <div class="col-sm-9">
41 + <%= form.text_field :username %>
42 + </div>
43 + </div>
44 + <div class="form-group">
45 + <%= form.label :instagram, class: "col-sm-3 control-label" %>
46 + <div class="col-sm-9">
47 + <%= form.text_field :instagram %>
48 + </div>
49 + </div>
50 + <div class="form-group">
51 + <%= form.label :is_private, class: "col-sm-3 control-label" %>
52 + <div class="col-sm-9">
53 + <%= form.select :is_private, [["Define for yourself what is TRUE", "1"], ["Nah, make it public", "0"]], selected: 0 %>
54 + </div>
55 + </div>
56 + <div class="form-group">
57 + <div class="col-sm-offset-3 col-sm-9">
58 + <%= form.submit class: "bb-button bb-green bb-large" %>
59 + <%= link_to t("cancel"), my_path, class: "bb-button bb-red bb-large" %>
60 + </div>
61 + </div>
62 + <% end %>
63 +
64 +
65 + </div>
66 + <!-- END content -->
67 + </div>
68 +
69 + <div class="row" style="margin-top: 20px">
70 + <div class="col-md-12">
71 +
72 + <ol class="breadcrumb">
73 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
74 + </ol>
75 +
76 + </div>
77 + </div>
78 +
\ No newline at end of file
backend/musik88-web/app/views/my/edit_lyric.html.erb new
+47
@@ -0,0 +1,47 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("song_lyric_unavailable_content_edit_lyric") %> - <%= @song.name %></h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-8">
16 + <%= form_with model: @lyric, url: {action: "edit_lyric"}, html: { :class => "form-horizontal" } do |form| %>
17 + <div class="form-group">
18 + <%= form.label t("lyric"), class: "col-sm-3 control-label" %>
19 + <div class="col-sm-9">
20 + <%= form.text_area :lyric, class: "col-md-6 form-control tinymce", :rows => 40, :cols => 120 %>
21 + <%= form.hidden_field :song_id, :value => @lyric.song_id %>
22 + <%= form.hidden_field :user_id, :value => @lyric.user_id %>
23 + </div>
24 + </div>
25 + <div class="form-group">
26 + <div class="col-sm-offset-3 col-sm-4">
27 + <%= form.submit t("song_lyric_unavailable_content_add_lyric"), class: "bb-button bb-green bb-large" %></br>
28 + <div class="vertical-separator"></div>
29 + <%= link_to t("cancel"), my_path, class: "bb-button bb-red bb-large" %>
30 + </div>
31 + </div>
32 + <% end %>
33 + <%= tinymce %>
34 + </div>
35 + <!-- END content -->
36 + </div>
37 +
38 + <div class="row" style="margin-top: 20px">
39 + <div class="col-md-12">
40 +
41 + <ol class="breadcrumb">
42 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
43 + </ol>
44 +
45 + </div>
46 + </div>
47 +
\ No newline at end of file
backend/musik88-web/app/views/my/file_detail.html.erb new
+83
@@ -0,0 +1,83 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Files 📁 / Detail</h1>
12 + </div>
13 + <%= render "shared/flash_message", object: flash %>
14 +
15 + </div>
16 + <div class="col-md-2">
17 + <%= render "my/partial_menu" %>
18 + </div>
19 + <div class="col-md-10">
20 +
21 + <div class="lead">
22 + <b>File name</b>: <%= @audio_file.source_file.blob.filename unless !@audio_file.source_file.attached? %>
23 + </div>
24 + <div class="row">
25 + <!-- BEGIN content -->
26 + <div class="col-md-12">
27 + <h2>SplitFire 🔥</h2>
28 + <% if @audio_file.splitfire_results.count == 0 %>
29 + You don't have any SplitFire files.
30 + <% else %>
31 + <table style="width: 100%">
32 + <% @audio_file.splitfire_results.each_with_index do |audio_file, index| %>
33 + <tr style="vertical-align: bottom">
34 + <td style="padding: 10px 10px 10px 0;">
35 + <% if audio_file.source_file.attached? %>
36 + <%= link_to(audio_file.source_file.blob.filename, my_splitfire_detail_path(@audio_file.id)) %>
37 + <% else %>
38 + <i>File is missing. Please delete this.</i>
39 + <% end %>
40 + </td>
41 + </tr>
42 + <% end %>
43 + </table>
44 + <% end %>
45 + </div>
46 + </div>
47 +
48 + <div class="row">
49 + <!-- BEGIN content -->
50 + <div class="col-md-12">
51 + <h2>Bass64 🎸</h2>
52 + <% if @audio_file.bass_backing_track.nil? %>
53 + You don't have any Bass64 files.
54 + <% else %>
55 + <table style="width: 100%">
56 + <tr style="vertical-align: bottom">
57 + <td style="padding: 10px 10px 10px 0;">
58 + <% if @audio_file.bass_backing_track.source_file.attached? %>
59 + <%= link_to(@audio_file.bass_backing_track.source_file.blob.filename, my_bass64_detail_path(@audio_file.id)) %>
60 + <% else %>
61 + <i>File is missing. Please delete this.</i>
62 + <% end %>
63 + </td>
64 + </tr>
65 + </table>
66 + <% end %>
67 + </div>
68 + </div>
69 +
70 + </div>
71 + <!-- END content -->
72 + </div>
73 +
74 + <div class="row" style="margin-top: 20px;">
75 + <div class="col-md-12">
76 +
77 + <ol class="breadcrumb">
78 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
79 + </ol>
80 +
81 + </div>
82 + </div>
83 +
\ No newline at end of file
backend/musik88-web/app/views/my/files.html.erb new
+77
@@ -0,0 +1,77 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +<div class="row">
6 + <!-- BEGIN content -->
7 + <div class="col-md-12">
8 +
9 + <div class="page-header">
10 + <h1><%= t("my") %> / Arsip 📁</h1>
11 + </div>
12 + <%= render "shared/flash_message", object: flash %>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-10">
19 +
20 + <div class="row">
21 + <!-- BEGIN content -->
22 + <div class="col-md-12">
23 + <div class="lead page-info">
24 + <b><i>Arsip</i></b> is your file management system for all you <b><i>AI personal assistant</i></b> tools.
25 + </div>
26 + <%= form_with model: @audio_file, url: { action: "files" }, :method => :post, html: { :class => "form-horizontal" } do |form| %>
27 + <div class="form-group">
28 + <label for="youtubeLink" class="col-sm-3 control-label">YouTube</label>
29 + <div class="col-sm-9">
30 + <%= form.text_field :youtube_video_url, class: "form-control", placeholder: "YouTube video link" %>
31 + <%= form.hidden_field :user_id, :value => @user.id %>
32 + <%= form.hidden_field :is_public, :value => 0 %>
33 + </div>
34 + </div>
35 + <div class="form-group">
36 + <label for="audioFile" class="col-sm-3 control-label">Or use your audio file</label>
37 + <div class="col-sm-9">
38 + <%= form.file_field :source_file, accept: ".mp3,audio/*" %>
39 + <p class="help-block">.mp3 or .wav file</p>
40 + </div>
41 + </div>
42 + <div class="form-group">
43 + <div class="col-sm-offset-3 col-sm-9">
44 + <%= form.submit "Split it!", class: "bb-button bb-blue bb-large bb-round" %>
45 + </div>
46 + </div>
47 + <% end %>
48 + </div>
49 + </div>
50 + <div class="row">
51 + <!-- BEGIN content -->
52 + <div class="col-md-12">
53 + <h2>Files</h2>
54 + <% if @audio_files.count == 0 %>
55 + You don't have any files.
56 + <% else %>
57 + <table style="width: 100%">
58 + <% @audio_files.each_with_index do |audio_file, index| %>
59 + <%= render "my/partial_files_row", audio_file: audio_file %>
60 + <% end %>
61 + </table>
62 + <% end %>
63 + </div>
64 + </div>
65 +
66 + </div>
67 + <!-- END content -->
68 +</div>
69 +<div class="row" style="margin-top: 20px;">
70 + <div class="col-md-12">
71 +
72 + <ol class="breadcrumb">
73 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
74 + </ol>
75 +
76 + </div>
77 +</div>
backend/musik88-web/app/views/my/index.html.erb new
+55
@@ -0,0 +1,55 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / Profile 😃</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-4">
19 +
20 + <div class="lead">
21 + <%= gravatar_for @user %>
22 + <span style="vertical-align: top; margin-left: 10px;">
23 + <%= link_to "Public URL", profile_path(@user.username.presence ? @user.username : @user.id) %>
24 + </span>
25 + </div>
26 + <%= form_with model: @user do |form| %>
27 + <%= form.label :name %>
28 + <%= form.text_field :name, disabled: true %>
29 + <%= form.label :email %>
30 + <%= form.text_field :email, disabled: true %>
31 + <%= form.label :username %>
32 + <%= form.text_field :username, disabled: true %>
33 + <%= form.label :instagram %>
34 + <%= form.text_field :instagram, disabled: true %>
35 + <%= form.label :is_private %>
36 + <%= form.text_field :is_private, disabled: true %>
37 + <% end %>
38 + <div>
39 + <%= link_to "Edit", my_profile_edit_path, class: "bb-button bb-green bb-large" %>
40 + </div>
41 +
42 + </div>
43 + <!-- END content -->
44 + </div>
45 +
46 + <div class="row" style="margin-top: 20px">
47 + <div class="col-md-12">
48 +
49 + <ol class="breadcrumb">
50 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
51 + </ol>
52 +
53 + </div>
54 + </div>
55 +
\ No newline at end of file
backend/musik88-web/app/views/my/karokowe.html.erb new
+56
@@ -0,0 +1,56 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / KaroKowe 🎤</h1>
12 + </div>
13 + <%= render "shared/flash_message", object: flash %>
14 +
15 + </div>
16 + <div class="col-md-2">
17 + <%= render "my/partial_menu" %>
18 + </div>
19 + <div class="col-md-10">
20 +
21 + <div class="row">
22 + <!-- BEGIN content -->
23 + <div class="col-md-12">
24 + <div class="lead page-info">
25 + <b><i>KaroKowe</i></b> is your AI personal assistant to generate <b><i>karaoke version</i></b> of a song.
26 + </div>
27 + <h2>Files</h2>
28 + <% if @audio_files.count == 0 %>
29 + You don't have any files to split.
30 + <% else %>
31 + <table style="width: 100%">
32 + <% @audio_files.each_with_index do |audio_file, index| %>
33 + <%= render "my/partial_karokowe_row", audio_file: audio_file %>
34 + <% end %>
35 + </table>
36 + <% end %>
37 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
38 + <%= render "my/partial_karokowe_version" %>
39 + </div>
40 + </div>
41 + </div>
42 +
43 + </div>
44 + <!-- END content -->
45 + </div>
46 +
47 + <div class="row" style="margin-top: 20px;">
48 + <div class="col-md-12">
49 +
50 + <ol class="breadcrumb">
51 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
52 + </ol>
53 +
54 + </div>
55 + </div>
56 +
\ No newline at end of file
backend/musik88-web/app/views/my/karokowe_detail.html.erb new
+53
@@ -0,0 +1,53 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / KaroKowe 🎤 / Results</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-10">
19 +
20 + <div class="row">
21 + <!-- BEGIN content -->
22 + <div class="col-md-12">
23 + <div class="lead">
24 + <b>File name</b>: <%= @audio_file.source_file.blob.filename unless !@audio_file.source_file.attached? %>
25 + </div>
26 + <div id="karokowe-result-status" style="text-align: center;">
27 + <%= render "my/partial_karokowe_result_status", audio_file: @audio_file %>
28 + </div>
29 + <table style="width: 100%" id="karokowe-result-content">
30 + <tbody>
31 + <%= render "my/partial_karokowe_result_row", audio_file: @audio_file.karaoke_file %>
32 + </tbody>
33 + </table>
34 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
35 + <%= render "my/partial_karokowe_version" %>
36 + </div>
37 + </div>
38 + </div>
39 +
40 + </div>
41 + <!-- END content -->
42 + </div>
43 +
44 + <div class="row" style="margin-top: 20px">
45 + <div class="col-md-12">
46 +
47 + <ol class="breadcrumb">
48 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
49 + </ol>
50 +
51 + </div>
52 + </div>
53 +
\ No newline at end of file
backend/musik88-web/app/views/my/lyrics.html.erb new
+49
@@ -0,0 +1,49 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %>/Lyrics</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-4">
19 +
20 + <table>
21 + <% @lyrics.each_with_index do |lyric, index| %>
22 + <tr>
23 + <td style="padding: 10px 10px 10px 0;">
24 + <%= "#{index + 1}." %>
25 + </td>
26 + <td style="padding: 10px 10px 10px 0;">
27 + <%= link_to lyric.song.name, song_detail_path(lyric.song.id) %>
28 + </td>
29 + <td style="padding: 10px;">
30 + <%= link_to("<i class='icon-edit'></i> Edit".html_safe, edit_lyric_path(lyric.id))%>
31 + </td>
32 + </tr>
33 + <% end %>
34 + </table>
35 +
36 + </div>
37 + <!-- END content -->
38 + </div>
39 +
40 + <div class="row" style="margin-top: 20px">
41 + <div class="col-md-12">
42 +
43 + <ol class="breadcrumb">
44 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
45 + </ol>
46 +
47 + </div>
48 + </div>
49 +
\ No newline at end of file
backend/musik88-web/app/views/my/splitfire.html.erb new
+92
@@ -0,0 +1,92 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / <%= "SplitFire #{image_pack_tag('fire.gif', :size => '34x34', :style => 'vertical-align: top')}".html_safe %></h1>
12 + </div>
13 + <%= render "shared/flash_message", object: flash %>
14 +
15 + </div>
16 + <div class="col-md-2">
17 + <%= render "my/partial_menu" %>
18 + </div>
19 + <div class="col-md-10">
20 +
21 + <div class="row">
22 + <!-- BEGIN content -->
23 + <div class="col-md-12">
24 + <div class="lead page-info">
25 + <b><i>SplitFire</i></b> is your AI personal assistant that can generate <b><i>stem files</i></b> from a song. You might not want to use it directly. Instead, you can use the bass backing track generator, karaoke file generator, or drum backing track generator that are based on SplitFire.
26 + </div>
27 + <div class="splitfire-settings">
28 + <h3>Settings</h3>
29 + <%= form_with model: @splitfire_settings, url: {action: "splitfire_settings"} do |f| %>
30 + <table style="width: 100%">
31 + <tr style="vertical-align: top">
32 + <td style="padding: 10px 10px 10px 0;">
33 + <b>Model</b>
34 + </br>
35 + <i>2 stems (vocals / accompaniment)</i></br>
36 + <i>4 stems (vocals / bass / drums / other )</i></br>
37 + <i>5 stems (vocals / bass / drums / piano / other)</i>
38 + </td>
39 + <td style="padding: 10px 10px 10px 0;">
40 + <%= f.select :model, UserSetting.models.map { |key, value| [key.humanize, key] } %>
41 + </td>
42 + </tr>
43 + <tr style="vertical-align: top">
44 + <td style="padding: 10px 10px 10px 0;">
45 + <b>Frequency</b>
46 + </br>
47 + <i>High frequency setting will persist original frequency.</i></br>
48 + </td>
49 + <td style="padding: 10px 10px 10px 0;">
50 + <%= f.select :frequency, UserSetting.frequencies.map { |key, value| [key.humanize, key] } %>
51 + </td>
52 + </tr>
53 + <tr style="vertical-align: top">
54 + <td style="padding: 10px 10px 10px 0;">
55 + </td>
56 + <td style="padding: 10px 10px 10px 0;">
57 + <%= f.submit "Update settings", class: "bb-button bb-green bb-large" %>
58 + </td>
59 + </tr>
60 + </table>
61 + <% end %>
62 + </div>
63 + <h2>Files</h2>
64 + <% if @audio_files.count == 0 %>
65 + You don't have any files to split.
66 + <% else %>
67 + <table style="width: 100%">
68 + <% @audio_files.each_with_index do |audio_file, index| %>
69 + <%= render "my/partial_splitfire_row", audio_file: audio_file %>
70 + <% end %>
71 + </table>
72 + <% end %>
73 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
74 + <%= render "shared/partial_splitfire_version" %>
75 + </div>
76 + </div>
77 + </div>
78 +
79 + </div>
80 + <!-- END content -->
81 + </div>
82 +
83 + <div class="row" style="margin-top: 20px;">
84 + <div class="col-md-12">
85 +
86 + <ol class="breadcrumb">
87 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
88 + </ol>
89 +
90 + </div>
91 + </div>
92 +
\ No newline at end of file
backend/musik88-web/app/views/my/splitfire_detail.html.erb new
+55
@@ -0,0 +1,55 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %> | <%= @user.name %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 +<% end %>
5 +
6 + <div class="row">
7 + <!-- BEGIN content -->
8 + <div class="col-md-12">
9 +
10 + <div class="page-header">
11 + <h1><%= t("my") %> / <%= "SplitFire #{image_pack_tag('fire.gif', :size => '34x34', :style => 'vertical-align: top')}".html_safe %> / Results</h1>
12 + </div>
13 +
14 + </div>
15 + <div class="col-md-2">
16 + <%= render "my/partial_menu" %>
17 + </div>
18 + <div class="col-md-10">
19 +
20 + <div class="row">
21 + <!-- BEGIN content -->
22 + <div class="col-md-12">
23 + <div class="lead">
24 + <b>File name</b>: <%= @audio_file.source_file.blob.filename unless !@audio_file.source_file.attached? %>
25 + </div>
26 + <div id="splitfire-result-status" style="text-align: center;">
27 + <%= render "my/partial_splitfire_result_status", audio_file: @audio_file %>
28 + </div>
29 + <table style="width: 100%" id="splitfire-result-content">
30 + <tbody>
31 + <% @audio_file.splitfire_results.each_with_index do |audio_file, index| %>
32 + <%= render "my/partial_splitfire_result_row", audio_file: audio_file %>
33 + <% end %>
34 + </tbody>
35 + </table>
36 + <div class="modul" style="margin-top: 20px; padding-right: 6px;">
37 + <%= render "shared/partial_splitfire_version" %>
38 + </div>
39 + </div>
40 + </div>
41 +
42 + </div>
43 + <!-- END content -->
44 + </div>
45 +
46 + <div class="row" style="margin-top: 20px">
47 + <div class="col-md-12">
48 +
49 + <ol class="breadcrumb">
50 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
51 + </ol>
52 +
53 + </div>
54 + </div>
55 +
\ No newline at end of file
backend/musik88-web/app/views/pages/what_is_this.html.erb new
+78
@@ -0,0 +1,78 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= what_is_this_url %>",
25 + "name": "<%= t("nav_what_is_this") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<main>
32 + <div class="row g-5">
33 + <div class="col-sm-3">
34 + <div class="position-sticky" style="top: 1rem;">
35 + <ul class="list-group list-group-flush">
36 + <li class="list-group-item">
37 + <%= link_to(t('nav_what_is_this'), what_is_this_path) %>
38 + </li>
39 + <% @what_are_these.each do |page| %>
40 + <li class="list-group-item">
41 + <%= link_to(page.title, what_is_this_detail_path(slug: page.page_slug)) %>
42 + </li>
43 + <% end if @what_are_these %>
44 + </ul>
45 + </div>
46 + </div>
47 + <div class="col-sm-9">
48 + <div class="row">
49 + <div class="col-12">
50 + <% if I18n.locale.to_s != @page.locale %>
51 + <div class="alert alert-warning" role="alert">
52 + No content in the current language.
53 + </div>
54 + <% end %>
55 + </div>
56 + </div>
57 + <div class="row">
58 + <div class="col-10">
59 + <h1><%= @page.title if @page %></h1>
60 + </div>
61 + <div class="col-2">
62 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
63 + <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"></path>
64 + <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"></path>
65 + </svg>
66 + </div>
67 + </div>
68 + <div class="row">
69 + <div class="col-12">
70 + <%= @page.page_content.html_safe if @page %>
71 + </div>
72 + </div>
73 + </div>
74 + </div>
75 + <%= render "shared/partial_breadcrumbs" do %>
76 + <li class="breadcrumb-item"><%= link_to t('nav_what_is_this'), what_is_this_url %></li>
77 + <% end %>
78 +</main>
backend/musik88-web/app/views/sessions/confirm_email.html.erb new
+46
@@ -0,0 +1,46 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= login_url %>",
11 + "name": "<%= t("nav_confirm_email") %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <div class="row">
18 + <div class="col-12">
19 + <h1><%= t('resend_email_confirmation') %></h1>
20 + </div>
21 + </div>
22 + <div class="row">
23 + <div class="col-md-5">
24 + <%= form_for(@user, url: confirmation_path(@user), remote: true, html: { method: :post, class: 'form-horizontal' }, data: { type: "json" } ) do |f| %>
25 + <%= render "shared/flash_message" %>
26 + <div class="row mb-3">
27 + <%= f.label t("email"), class: "col-4 col-form-label" %>
28 + <div class="col-8">
29 + <%= f.email_field :email, class: 'form-control', autofocus: true, autocomplete: "email", value: (@user.pending_reconfirmation? ? @user.unconfirmed_email : @user.email) %>
30 + </div>
31 + </div>
32 + <div class="row mb-3">
33 + <div class="offset-4 col-8 mt-2">
34 + <%= f.submit t("resend_email_confirmation_button"), class: "btn btn-primary" %>
35 + </div>
36 + </div>
37 + <% end %>
38 + </div>
39 + </div>
40 + <%= render 'shared/partial_breadcrumbs' do %>
41 + <li class="breadcrumb-item"><%= link_to t("nav_confirm_email"), confirm_email_path(request.query_parameters) %></li>
42 + <% end %>
43 +</main>
44 +<script>
45 +
46 +</script>
\ No newline at end of file
backend/musik88-web/app/views/sessions/confirm_email.js.erb new
+5
@@ -0,0 +1,5 @@
1 +$('.alert').fadeOut('slow', function() {
2 + var div = $('<div class="alert alert-info"><%= @resend_text.html_safe %></div>').hide();
3 + $(this).replaceWith(div);
4 + $('.alert').fadeIn('slow');
5 +});
\ No newline at end of file
backend/musik88-web/app/views/sessions/new.html.erb new
+220
@@ -0,0 +1,220 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= login_url %>",
11 + "name": "<%= t("login") %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <div class="row">
18 + <div class="col-md-5">
19 + <div class="row mb-3">
20 + <h1><%= t("login") %></h1>
21 + <%= render "shared/flash_message" %>
22 + </div>
23 + <div class="row mb-3 text-center web3-login">
24 + <p><%= t("login_web3_explanation") %></p>
25 + <%=
26 + link_to(
27 + image_pack_tag(
28 + "phantom-icon-purple.svg",
29 + size: "300x100",
30 + alt: "Connect with Phantom"
31 + ),
32 + 'javascript:;',
33 + class: 'connect-phantom',
34 + title: 'Connect with Phantom wallet'
35 + )
36 + %>
37 + </div>
38 + <%= form_for(:session, url: login_path, :html => { :class => "form-horizontal" }) do |f| %>
39 + <div class="row mb-3 text-center">
40 + <p><%= t("login_omniauth_explanation") %></p>
41 + <% ab_test(:only_spotify_youtube, 'old', 'only_spotify_youtube') do |exp| %>
42 + <%= link_to(
43 + '<i class="bi bi-spotify" style="font-size: 3rem; color: blue;"></i>'.html_safe,
44 + user_spotify_omniauth_authorize_path,
45 + method: :post,
46 + class: exp == 'old' ? "col-2 offset-3" : "col-2 offset-4",
47 + style: "padding: 0px",
48 + title: 'Connect with Spofity'
49 + ) %>
50 + <%= link_to(
51 + '<i class="bi bi-google" style="font-size: 3rem; color: blue;"></i>'.html_safe,
52 + user_google_oauth2_omniauth_authorize_path,
53 + method: :post,
54 + class: 'col-2',
55 + title: 'Connect with YouTube Music'
56 + ) %>
57 + <% if exp == 'old' %>
58 + <%= link_to(
59 + '<i class="bi bi-facebook" style="font-size: 3rem; color: blue;"></i>'.html_safe,
60 + user_facebook_omniauth_authorize_path,
61 + method: :post,
62 + class: 'col-2',
63 + title: 'Connect with Facebook'
64 + ) %>
65 + <% end %>
66 + <% end %>
67 + </div>
68 + <% ab_test(:only_spotify_youtube, 'old', 'only_spotify_youtube') do |exp| %>
69 + <% if exp == 'old' %>
70 + <div class="row mb-3">
71 + <%= f.label t("email"), class: "col-4 col-form-label" %>
72 + <div class="col-8">
73 + <%= f.email_field :email, class: 'form-control' %>
74 + </div>
75 + </div>
76 + <div class="row mb-3">
77 + <%= f.label t("password"), class: "col-4 col-form-label" %>
78 + <div class="col-8">
79 + <%= f.password_field :password, class: 'form-control' %>
80 + </div>
81 + <%= link_to t("forgot_password"), new_password_path('user'), class: 'col-8 offset-4' %>
82 + </div>
83 + <div class="row mb-3">
84 + <div class="offset-4 col-8 mt-2">
85 + <div class="row">
86 + <%= f.check_box :remember_me, class: "custom-control-input col-2", style: 'margin-top:auto;margin-bottom:auto;' %>
87 + <label class="custom-control-label col-10" for="customControlInline"><%= t("still_login") %></label>
88 + </div>
89 + </div>
90 + </div>
91 + <div class="row mb-3">
92 + <div class="offset-4 col-8 mt-2">
93 + <%= f.submit t("login"), class: "btn btn-primary" %>
94 + </div>
95 + </div>
96 + <% end %>
97 + <% end %>
98 + <div class="row mb-3">
99 + <div class="offset-4 col-8 mt-3">
100 + <p><%= t("new_user") %> <%= link_to t("signup_now"), signup_path(request.query_parameters) %></p>
101 + </div>
102 + </div>
103 + <% end %>
104 + </div>
105 + <div class="col-md-6 offset-1 text-center p-5">
106 + <%= image_tag("https://upload.wikimedia.org/wikipedia/commons/6/6a/Johann_Sebastian_Bach.jpg", class: "img-thumbnail d-none d-md-block") %>
107 + </div>
108 + </div>
109 + <%= render 'shared/partial_breadcrumbs' do %>
110 + <li class="breadcrumb-item"><%= link_to t("nav_login"), login_path(request.query_parameters) %></li>
111 + <% end %>
112 +</main>
113 +<script>
114 +const getProvider = () => {
115 + if ('phantom' in window) {
116 + const provider = window.phantom?.solana;
117 +
118 + if (provider?.isPhantom) {
119 + return provider;
120 + }
121 + }
122 +};
123 +
124 +function post(url, params, successCompletion) {
125 + $.ajax({
126 + type: 'POST',
127 + beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
128 + url: url,
129 + data: params,
130 + success: function(res) {
131 + successCompletion(res)
132 + }
133 + })
134 +}
135 +
136 +const signMessage = async () => {
137 +
138 + let provider = getProvider();
139 + const message = `Welcome to Musik88!
140 +
141 +Click to sign in and accept the Musik88.com Terms of Service (https://musik88.com/term-of-use/).
142 +
143 +This request will not trigger a blockchain transaction or cost any gas fees.`;
144 +
145 + const encodedMessage = new TextEncoder().encode(message);
146 + try {
147 + const signedMessage = await provider.signMessage(encodedMessage, "utf8");
148 + console.log(signedMessage);
149 + const pubkey = signedMessage.publicKey.toString();
150 +
151 + let params = {
152 + postAction: 'createAccount',
153 + pubkey: pubkey
154 + }
155 + post('<%= login_pubkey_path %>', params, async function(res) {
156 + // Hanlde callback
157 + console.log(res);
158 + })
159 + }
160 + catch {
161 +
162 + }
163 +}
164 +
165 +const ready = async () => {
166 +
167 + const isPhantomAvailable = () => {
168 + if (window.phantom) {
169 + return true;
170 + }
171 + return false;
172 + }
173 +
174 + $('.web3-login').hide();
175 +
176 + if (isPhantomAvailable()) {
177 + $('.web3-login').show();
178 + }
179 +
180 + $('.connect-phantom').click(async function(e) {
181 + e.stopImmediatePropagation();
182 + let provider = getProvider();
183 +
184 + if (!provider) {
185 + window.open('https://phantom.app/', '_blank');
186 + return
187 + }
188 +
189 + try {
190 + const resp = await provider.connect();
191 + // console.log(resp.publicKey.toString());
192 + const pubkey = resp.publicKey.toString();
193 +
194 + let params = {
195 + postAction: 'checkPubkey',
196 + pubkey: pubkey
197 + }
198 +
199 + console.log('Public key: ', pubkey);
200 + post('<%= login_pubkey_path %>', params, async function(res) {
201 + // Check if we need to promp messqge sign
202 + console.log('Result: ', res);
203 + if (res.shouldPromptMessageSign) {
204 + signMessage()
205 + } else {
206 + // Redirect to onboarding or user page
207 +
208 + }
209 + })
210 + } catch (err) {
211 + // console.log(err);
212 + // Add warning to sign the message
213 + }
214 +
215 + })
216 +}
217 +
218 +$(document).ready(ready);
219 +$(document).on('turbolinks:load', ready);
220 +</script>
backend/musik88-web/app/views/shared/_error_messages.html.erb new
+11
@@ -0,0 +1,11 @@
1 +<% if @user.errors.any? %>
2 + <div id="error_explanation">
3 + <% if @user.errors.any? %>
4 + <div class="alert alert-danger">
5 + <% @user.errors.messages.each do |msg| %>
6 + <%= msg.drop(1).join(", ").capitalize %></br>
7 + <% end %>
8 + </div>
9 + <% end %>
10 + </div>
11 +<% end %>
backend/musik88-web/app/views/shared/_flash_message.html.erb new
+8
@@ -0,0 +1,8 @@
1 +<% flash.each do |message_type, message| %>
2 + <%
3 + override_message_type = message_type
4 + override_message_type = 'info' if 'notice' == message_type
5 + override_message_type = 'danger' if 'alert' == message_type
6 + %>
7 + <div class="alert alert-<%= override_message_type %>"><%= message.html_safe %></div>
8 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/shared/_partial_ai_tool.html.erb new
+14
@@ -0,0 +1,14 @@
1 +<li class="nav-item">
2 + <% if active %>
3 + <a href="javascript:void(0)" class="nav-link active">
4 + <%= href_text %>
5 + </a>
6 + <% else %>
7 + <%= link_to(
8 + href_text,
9 + href_link,
10 + class: "nav-link"
11 + )
12 + %>
13 + <% end %>
14 +</li>
\ No newline at end of file
backend/musik88-web/app/views/shared/_partial_ai_tools.html.erb new
+37
@@ -0,0 +1,37 @@
1 +<% use_header = true if local_assigns[:use_header].nil? %>
2 +
3 +<div class="promo-tools">
4 + <% if use_header %>
5 + <h2>Try These Stupid 🤖 AI Tools</h2>
6 + <% end %>
7 + <ul class="nav nav-pills">
8 + <%= render(
9 + "shared/partial_ai_tool",
10 + href_text: "SplitFire 🔥",
11 + href_link: splitfire_path,
12 + active: active_menu == 'splitfire'
13 + )
14 + %>
15 + <%= render(
16 + "shared/partial_ai_tool",
17 + href_text: "KaroKowe 🎤",
18 + href_link: karokowe_branding_path,
19 + active: active_menu == 'karokowe'
20 + )
21 + %>
22 + <%= render(
23 + "shared/partial_ai_tool",
24 + href_text: "Bass64 🎸",
25 + href_link: bass64_branding_path,
26 + active: active_menu == 'bass64'
27 + )
28 + %>
29 + <%= render(
30 + "shared/partial_ai_tool",
31 + href_text: "Bonzo 🥁",
32 + href_link: bonzo_branding_path,
33 + active: active_menu == 'bonzo'
34 + )
35 + %>
36 + </ul>
37 +</div>
\ No newline at end of file
backend/musik88-web/app/views/shared/_partial_breadcrumbs.html.erb new
+13
@@ -0,0 +1,13 @@
1 +<div class="row flex-wrapper">
2 + <div></div>
3 + <div class="col-md-12 pt-5">
4 + <nav style="--bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='currentColor'/%3E%3C/svg%3E&#34;);" aria-label="breadcrumb">
5 + <ol class="breadcrumb">
6 + <li class="breadcrumb-item">
7 + <%= link_to t("breadcrumb_home"), root_url %>
8 + </li>
9 + <%= yield %>
10 + </ol>
11 + </nav>
12 + </div>
13 +</div>
backend/musik88-web/app/views/shared/_partial_breadcrumbs_schema.html.erb new
+14
@@ -0,0 +1,14 @@
1 +<script type="application/ld+json">
2 + {
3 + "@context": "http://schema.org",
4 + "@type": "BreadcrumbList",
5 + "itemListElement": [{
6 + "@type": "ListItem",
7 + "position": 1,
8 + "item": {
9 + "@id": "<%= request.base_url %>",
10 + "name": "<%= t("site_name") %>"
11 + }
12 + },<%= yield %>]
13 + }
14 +</script>
\ No newline at end of file
backend/musik88-web/app/views/shared/_partial_meta_tag.html.erb new
+5
@@ -0,0 +1,5 @@
1 +<title><%= title %></title>
2 +<meta name="description" content="<%= description %>">
3 +<meta property="og:title" content="<%= title %>" />
4 +<meta property="og:description" content="<%= description %>" />
5 +<meta property="og:url" content="<%= request.url %>" />
\ No newline at end of file
backend/musik88-web/app/views/shared/_partial_meta_top.html.erb new
+17
@@ -0,0 +1,17 @@
1 +
2 +<div class="row mb-5 mt-3">
3 + <div class="col-sm-2">
4 + <div class="fw-bold fst-italic d-inline-flex">
5 + <%= link_to(
6 + "@#{author.username_or_id}",
7 + profile_path(author),
8 + title: "Author: @#{author.username_or_id}"
9 + ) %>
10 + </div>
11 + </div>
12 + <div class="col-sm-2 offset-sm-8">
13 + <span class="badge bg-primary rounded-pill fst-italic fw-light float-end">
14 + <%= created_at.strftime("%B %-d, %Y") %>
15 + </span>
16 + </div>
17 +</div>
backend/musik88-web/app/views/shared/_partial_splitfire_version.html.erb new
+3
@@ -0,0 +1,3 @@
1 +<span class="modul-version" style="float: right; font-style: italic; text-decoration: underline;">
2 + <%= link_to("SplitFire v1.2.0", splitfire_path(:anchor => "v1-2-0"), target: "__blank") %>
3 +<span>
\ No newline at end of file
backend/musik88-web/app/views/songs/_partial_aside_index.html.erb new
+44
@@ -0,0 +1,44 @@
1 +<div class="col-md-4">
2 + <aside>
3 + <div class="bluebox-heading">
4 + <h2><%= t("10_most_loved_songs")%></h2>
5 + </div>
6 + <div class="widget-wrap">
7 + <%= link_to t("see_all"), most_loved_songs_path, class: "bb-button bb-green bb-large" %>
8 + </div>
9 + <% @most_loved_songs.each_with_index do |song, index| %>
10 + <h4>
11 + <%= link_to "#{index + 1}. #{song.name}", song_detail_path(song.name.downcase.parameterize + "-" + song.id.to_s) %>
12 + </h4>
13 + <div style="padding: 8px;">
14 + <i class="icon-heart"></i>
15 + <%= song.users_count %>
16 + <%= link_to t("see_song_detail"),
17 + song_detail_path(song.name.downcase.parameterize + "-" + song.id.to_s),
18 + class: "badge rounded-pill bg-warning float-end" %>
19 + </div>
20 + <span style="text-transform: capitalize;font-style: italic;">
21 + <%= t("song_updated_at", date: song.updated_at.strftime("%B %-d, %Y")) %>
22 + </span>
23 + <% end %>
24 + <span class="clear"></span>
25 + <div class="bluebox-heading">
26 + <h2><%= t("top_10_songs")%></h2>
27 + </div>
28 + <% @trending_songs.each_with_index do |song, index| %>
29 + <h4>
30 + <%= link_to "#{index + 1}. #{song.name}", song_detail_path(song.name.downcase.parameterize + "-" + song.id.to_s) %>
31 + </h4>
32 + <div style="padding: 8px;">
33 + <i class="icon-eye-open"></i>
34 + <%= song.views_count %>
35 + <%= link_to t("see_song_detail"),
36 + song_detail_path(song.name.downcase.parameterize + "-" + song.id.to_s),
37 + class: "badge rounded-pill bg-warning float-end" %>
38 + </div>
39 + <span style="text-transform: capitalize;font-style: italic;">
40 + <%= t("song_updated_at", date: song.updated_at.strftime("%B %-d, %Y")) %>
41 + </span>
42 + <% end %>
43 + </aside>
44 +</div>
backend/musik88-web/app/views/songs/_partial_content_drum_backing_track.html.erb new
+8
@@ -0,0 +1,8 @@
1 +<div class="col-md-8">
2 +
3 + <div class="blog-post-single content-row chord-content">
4 +
5 + <h1 class="post-heading">Coming soon</h1>
6 + </div>
7 +
8 +</div>
\ No newline at end of file
backend/musik88-web/app/views/songs/_partial_menu_aside.html.erb new
+4
@@ -0,0 +1,4 @@
1 +<li class="nav-item">
2 + <a class="nav-link <%= 'active' if active %>" aria-current="page" href=<%= active ? "javascript:void(0)" : "#{href_link}" %>>
3 + <i class="bi <%= icon_class %>"></i> <%= href_text %></a>
4 +</li>
backend/musik88-web/app/views/songs/_partial_menus_aside.html.erb new
+34
@@ -0,0 +1,34 @@
1 +<div class="position-sticky" style="top: 1rem;">
2 + <div class="text-center mt-2">
3 + <ul class="nav flex-column nav-pills">
4 + <%= render(
5 + 'partial_menu_aside',
6 + href_text: t('song_menu_aside_info'),
7 + href_link: song_detail_path(@song.slug),
8 + icon_class: 'bi-info-circle',
9 + active: active_menu == 'info'
10 + ) %>
11 + <%= render(
12 + 'partial_menu_aside',
13 + href_text: t('song_menu_aside_chords'),
14 + href_link: song_chords_path(@song.slug),
15 + icon_class: 'bi-music-note-list',
16 + active: active_menu == 'chord'
17 + ) %>
18 + <%= render(
19 + 'partial_menu_aside',
20 + href_text: t('song_menu_aside_lyrics'),
21 + href_link: song_lyrics_path(@song.slug),
22 + icon_class: 'bi-file-text',
23 + active: active_menu == 'lyric'
24 + ) %>
25 + <%= render(
26 + 'partial_menu_aside',
27 + href_text: t('song_menu_aside_audio'),
28 + href_link: song_audio_path(@song.slug),
29 + icon_class: 'bi-music-note-beamed',
30 + active: active_menu == 'audio'
31 + ) %>
32 + </ul>
33 + </div>
34 +</div>
backend/musik88-web/app/views/songs/_partial_meta_bottom.html.erb new
+34
@@ -0,0 +1,34 @@
1 +<div class="row mt-5 g-5">
2 + <div class="col-sm-3 text-center ">
3 + <div class="mt-4 mb-4">
4 + <%= image_pack_tag(
5 + "image-placeholder-#{rand(0...3)}.jpg",
6 + alt:"#{album.name} image",
7 + class: "img-thumbnail rounded"
8 + ) %>
9 + </div>
10 + <span class="mx-2 badge bg-warning rounded-pill fst-italic">
11 + <%= album.present? ? link_to(
12 + album.name,
13 + album_detail_path(album.slug),
14 + title: "Album: #{album.name}"
15 + ) : 'Add album?' %>
16 + </span>
17 + </div>
18 + <% @song.artists.each_with_index do |artist, index| %>
19 + <div class="col-6 col-sm-3 col-md-2 text-center">
20 + <div class="mt-4 mb-4">
21 + <%= image_pack_tag(
22 + "image-placeholder-#{rand(0...3)}.jpg",
23 + alt:"#{artist.name} image",
24 + class: "img-thumbnail rounded-circle"
25 + ) %>
26 + </div>
27 + <%= link_to(
28 + artist.name,
29 + artist_detail_path(artist.name.downcase.parameterize + "-" + artist.id.to_s),
30 + class: 'badge rounded-pill bg-warning text-dark fst-italic '
31 + ) %>
32 + </div>
33 + <% end %>
34 +</div>
backend/musik88-web/app/views/songs/audio.html.erb new
+117
@@ -0,0 +1,117 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= request.base_url %>/artis/<%= @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.artists[0].id %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= request.base_url %>/album/<%= @song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.id %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= request.url %>",
46 + "name": "<%= @song.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<main>
53 + <div class="row g-5">
54 + <div class="col-md-12">
55 + <h1><%= @page_title %></h1>
56 + </div>
57 + </div>
58 + <div class="row g-5">
59 + <div class="col-sm-1">
60 + <div class="position-sticky" style="top: 1rem;">
61 + <div class="text-center mt-3">
62 + <% if user_signed_in? && @song.users.include?(current_user) %>
63 + <%= link_to(
64 + '<i class="bi bi-heart-fill"></i>'.html_safe,
65 + song_chord_pleas_unloved_path(id: @song.id),
66 + remote: true,
67 + method: :delete,
68 + :id => "ajax_trigger"
69 + )
70 + %>
71 + <% else %>
72 + <%= link_to(
73 + '<i class="bi bi-heart"></i>'.html_safe,
74 + song_chord_pleas_loved_path(id: @song.id),
75 + remote: true,
76 + method: :post,
77 + :id => "ajax_trigger"
78 + )
79 + %>
80 + <% end %>
81 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.users.count %></span>
82 + </div>
83 + <div class="text-center mt-4">
84 + <i class="bi bi-graph-up-arrow"></i>
85 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.views_count %></span>
86 + </div>
87 + </div>
88 + </div>
89 + <div class="col-sm-9">
90 + <%= render('shared/partial_meta_top', author: @author, created_at: @song.created_at) %>
91 + <h2 class="mb-2 mt-5">SplitFire AI</h2>
92 + <div class="">
93 + <div>
94 + We moved all the audio stuff to the <a href="https://splitfire.ai/" target="__target">SplitFire AI</a> app.
95 + </div>
96 + </div>
97 + <%= render "shared/flash_message", object: flash %>
98 + <div class="mb-3 mt-3">
99 + <%= form_for [@song, Comment.new] do |f| %>
100 + <%= f.text_area :body, placeholder: "Add a comment", rows: "4" , cols: "50%" %><br/>
101 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
102 + <%= f.submit "Add Comment", class: "btn btn-primary" %>
103 + <% end %>
104 + </div>
105 + <%= render(partial: 'comments/comment', collection: @song.comments) %>
106 + </div>
107 + <div class="col-sm-2">
108 + <%= render('partial_menus_aside', active_menu: 'audio') %>
109 + </div>
110 + </div>
111 + <%= render "shared/partial_breadcrumbs" do %>
112 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
113 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
114 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
115 + <li class="breadcrumb-item"><%= link_to @song.name, song_detail_path(@song.slug) %></li>
116 + <% end %>
117 +</main>
backend/musik88-web/app/views/songs/bass_backing_track.html.erb new
+68
@@ -0,0 +1,68 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= request.url %>",
46 + "name": "<%= t("guitar_chord") + @song.name + " " + @song.album.artists[0].name + " " + @song.album.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<%= render("partial_menus.html", active_menu: 'bass_backing_track' ) %>
53 +<div class="row">
54 + <%= render("partial_content_drum_backing_track.html") %>
55 + <%= render "partial_aside.html", song: @song do |song| %>
56 + <%= link_to(
57 + song.name,
58 + song_drum_backing_track_path(song.slug)
59 + )
60 + %>
61 + <% end %>
62 +</div>
63 +<%= render "shared/partial_breadcrumbs" do %>
64 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
65 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
66 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
67 + <li class="breadcrumb-item"><%= link_to @song.name, guitar_chord_detail_url(@song.slug) %></li>
68 +<% end %>
backend/musik88-web/app/views/songs/bass_tab.html.erb new
+61
@@ -0,0 +1,61 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= guitar_chord_detail_url(@song.slug) %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= guitar_chord_detail_url(@song.slug) %>",
46 + "name": "<%= t("guitar_chord") + @song.name + " " + @song.album.artists[0].name + " " + @song.album.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<%= render("partial_menus_aside", active_menu: 'bass_tab' ) %>
53 +<div class="row">
54 + <%= render("partial_content_drum_backing_track") %>
55 +</div>
56 +<%= render "shared/partial_breadcrumbs" do %>
57 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
58 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
59 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
60 + <li class="breadcrumb-item"><%= link_to @song.name, guitar_chord_detail_url(@song.slug) %></li>
61 +<% end %>
backend/musik88-web/app/views/songs/bridge.html.erb new
+113
@@ -0,0 +1,113 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= song_url %>",
25 + "name": "<%= t("nav_songs") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 4,
30 + "item": {
31 + "@id": "<%= song_bridge_url %>",
32 + "name": "<%= @song.name %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row mb-3">
40 + <div class="col-sm-3">
41 + <div class="position-sticky" style="top: 1rem;">
42 + <div class="text-center mt-4 mb-4">
43 + <% if @song.image_url %>
44 + <%= image_tag(
45 + @song.image_url,
46 + alt:"#{@song.name}",
47 + size: "300x300",
48 + class: "img-thumbnail img-fluid"
49 + ) %>
50 + <% else %>
51 + <%= image_pack_tag(
52 + "image-placeholder-#{rand(0...3)}.jpg",
53 + alt:"#{@song.name} image",
54 + size: "300x300",
55 + class: "img-thumbnail img-fluid"
56 + ) %>
57 + <% end %>
58 + <div class="provider-links mb-3">
59 + <%= link_to(
60 + "<i class='bi bi-#{@song.provider_type.to_s}' style='font-size: 2rem; color: blue;'></i>".html_safe,
61 + @song.provider_url,
62 + style: "padding: 0px",
63 + target: '__blank'
64 + ) %>
65 + </div>
66 + <% if @song.youtube? %>
67 + <div class="mb-3">
68 + <%= link_to(
69 + "<i class='bi bi-arrow-down-up'></i>".html_safe,
70 + @sf_link,
71 + class: 'btn btn-primary btn-lg px-4',
72 + title: 'Votes on SplitFire AI',
73 + target: '__blank'
74 + ) %>
75 + </div>
76 + <% end %>
77 + <div class="song-albums">
78 + <% @song_in_albums.each do |song| %>
79 + <div>
80 + <%= link_to(
81 + song.name,
82 + song.path
83 + ) %>
84 + </div>
85 + <% end %>
86 + </div>
87 + </div>
88 + </div>
89 + </div>
90 + <div class="col-sm-9">
91 + <h1 class=""><%= @page_title %></h1>
92 + <%= render('shared/flash_message', object: flash) %>
93 + <div class="chords mt-5">
94 + <% if @song.chord.present? %>
95 + <h2>
96 + <%= link_to(
97 + t('song_chords_page_title', song_name: @song.name),
98 + @song.chord.path
99 + ) %>
100 + </h2>
101 + <% else %>
102 + <div class="mb-3 mt-3">
103 + <%= link_to('Share chord!', guitar_chord_add_path, class: 'btn btn-primary btn-lg px-4') %>
104 + </div>
105 + <% end %>
106 + </div>
107 + </div>
108 + </div>
109 + <%= render 'shared/partial_breadcrumbs' do %>
110 + <li class="breadcrumb-item"><%= link_to t("nav_songs"), song_path %></li>
111 + <li class="breadcrumb-item"><%= link_to @song.name, song_detail_path %></li>
112 + <% end %>
113 +</main>
backend/musik88-web/app/views/songs/chords.html.erb new
+115
@@ -0,0 +1,115 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= request.base_url %>/artis/<%= @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.artists[0].id %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= request.base_url %>/album/<%= @song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.id %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= request.url %>",
46 + "name": "<%= @song.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<main>
53 + <div class="row g-5">
54 + <div class="col-md-12">
55 + <h1><%= @page_title %></h1>
56 + </div>
57 + </div>
58 + <div class="row g-5">
59 + <div class="col-sm-1">
60 + <div class="position-sticky" style="top: 1rem;">
61 + <div class="text-center mt-3">
62 + <% if user_signed_in? && @song.users.include?(current_user) %>
63 + <%= link_to(
64 + '<i class="bi bi-heart-fill"></i>'.html_safe,
65 + song_chord_pleas_unloved_path(id: @song.id),
66 + remote: true,
67 + method: :delete,
68 + :id => "ajax_trigger"
69 + )
70 + %>
71 + <% else %>
72 + <%= link_to(
73 + '<i class="bi bi-heart"></i>'.html_safe,
74 + song_chord_pleas_loved_path(id: @song.id),
75 + remote: true,
76 + method: :post,
77 + :id => "ajax_trigger"
78 + )
79 + %>
80 + <% end %>
81 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.users.count %></span>
82 + </div>
83 + <div class="text-center mt-4">
84 + <i class="bi bi-graph-up-arrow"></i>
85 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.views_count %></span>
86 + </div>
87 + </div>
88 + </div>
89 + <div class="col-sm-9">
90 + <%= render('shared/partial_meta_top', author: @author, created_at: @song.created_at) %>
91 + <div class="mb-3 mt-3">
92 + <%= link_to('Share chord!', guitar_chord_add_path, class: 'btn btn-primary btn-lg px-4') %>
93 + </div>
94 + <h2 class="mb-2 mb-2 mt-5">Kamu pikir kamu punya kord yang lebih baik?</h2>
95 + <%= render "shared/flash_message", object: flash %>
96 + <div class="mb-3">
97 + <%= form_for [@song, Comment.new] do |f| %>
98 + <%= f.text_area :body, placeholder: "Add a comment", rows: "4" , cols: "50%" %><br/>
99 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
100 + <%= f.submit "Add Comment", class: "btn btn-primary" %>
101 + <% end %>
102 + </div>
103 + <%= render(partial: 'comments/comment', collection: @song.comments) %>
104 + </div>
105 + <div class="col-sm-2">
106 + <%= render('partial_menus_aside', active_menu: 'chord') %>
107 + </div>
108 + </div>
109 + <%= render "shared/partial_breadcrumbs" do %>
110 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
111 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
112 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
113 + <li class="breadcrumb-item"><%= link_to @song.name, song_detail_path(@song.slug) %></li>
114 + <% end %>
115 +</main>
backend/musik88-web/app/views/songs/detail.html.erb new
+116
@@ -0,0 +1,116 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= request.base_url %>/artis/<%= @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.artists[0].id %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= request.base_url %>/album/<%= @song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.id %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= request.url %>",
46 + "name": "<%= @song.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<main>
53 + <div class="row g-5">
54 + <div class="col-md-12">
55 + <h1><%= @page_title %></h1>
56 + </div>
57 + </div>
58 + <div class="row g-5">
59 + <div class="col-sm-1">
60 + <div class="position-sticky" style="top: 1rem;">
61 + <div class="text-center mt-3">
62 + <% if user_signed_in? && @song.users.include?(current_user) %>
63 + <%= link_to(
64 + '<i class="bi bi-heart-fill"></i>'.html_safe,
65 + song_chord_pleas_unloved_path(id: @song.id),
66 + remote: true,
67 + method: :delete,
68 + :id => "ajax_trigger"
69 + )
70 + %>
71 + <% else %>
72 + <%= link_to(
73 + '<i class="bi bi-heart"></i>'.html_safe,
74 + song_chord_pleas_loved_path(id: @song.id),
75 + remote: true,
76 + method: :post,
77 + :id => "ajax_trigger"
78 + )
79 + %>
80 + <% end %>
81 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.users.count %></span>
82 + </div>
83 + <div class="text-center mt-4">
84 + <i class="bi bi-graph-up-arrow"></i>
85 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.views_count %></span>
86 + </div>
87 + </div>
88 + </div>
89 + <div class="col-sm-9">
90 + <%= render('shared/partial_meta_top', author: @author, created_at: @song.created_at) %>
91 + <%= @song.description.html_safe %>
92 + <%= render('partial_meta_bottom', artist: @song.artists, album: @song.album) %>
93 + <div class="row mt-3">
94 + <h3 id="discussion"><%= t('discussion') %></h3>
95 + <%= render "shared/flash_message", object: flash %>
96 + <div class="mb-3">
97 + <%= form_for [@song, Comment.new] do |f| %>
98 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
99 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
100 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
101 + <% end %>
102 + </div>
103 + <%= render(partial: 'comments/comment', collection: @song.comments) %>
104 + </div>
105 + </div>
106 + <div class="col-sm-2">
107 + <%= render('partial_menus_aside', active_menu: 'info') %>
108 + </div>
109 + </div>
110 + <%= render "shared/partial_breadcrumbs" do %>
111 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
112 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
113 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
114 + <li class="breadcrumb-item"><%= link_to @song.name, song_detail_path(@song.slug) %></li>
115 + <% end %>
116 +</main>
backend/musik88-web/app/views/songs/drum_backing_track.html.erb new
+68
@@ -0,0 +1,68 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= request.url %>",
46 + "name": "<%= t("guitar_chord") + @song.name + " " + @song.album.artists[0].name + " " + @song.album.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<%= render("partial_menus.html", active_menu: 'drum_backing_track' ) %>
53 +<div class="row">
54 + <%= render("partial_content_drum_backing_track.html") %>
55 + <%= render "partial_aside.html", song: @song do |song| %>
56 + <%= link_to(
57 + song.name,
58 + song_drum_backing_track_path(song.slug)
59 + )
60 + %>
61 + <% end %>
62 +</div>
63 +<%= render "shared/partial_breadcrumbs" do %>
64 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
65 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
66 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
67 + <li class="breadcrumb-item"><%= link_to @song.name, guitar_chord_detail_url(@song.slug) %></li>
68 +<% end %>
backend/musik88-web/app/views/songs/index.html.erb new
+76
@@ -0,0 +1,76 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= song_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= song_url %>",
25 + "name": "<%= t("nav_songs") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="row">
32 + <!-- BEGIN content -->
33 + <div class="col-md-8">
34 + <div class="bluebox-heading">
35 + <h2><%= @page.title if @page %></h2>
36 + <div class="lead">
37 + <%= raw @page.page_content if @page %>
38 + </div>
39 + </div>
40 + <div class="bluebox-heading">
41 + <h3><%= t("find_your_favorite_songs") %></h3>
42 + </div>
43 + <% @songs.each_with_index do |song, index| %>
44 + <% if (index == 0 || index % 2 == 0) %>
45 + <div class="row content-row">
46 + <% end %>
47 + <div class="col-md-6">
48 + <section class="featured-post">
49 + <a class="image-wrap" href="<%= song_detail_path(song.slug) %>">
50 + <%= image_pack_tag("image-placeholder-#{rand(0...3)}.jpg", alt:"#{song.name} cover image.", style:"width:267px;height:165px;") %>
51 + <span class="image-details"></span>
52 + </a>
53 + <h1>
54 + <%= link_to song.name,
55 + song_detail_path(song.slug) %>
56 + </h1>
57 + <p><%= strip_tags song.description.truncate 60 %></p>
58 + <div class="bluebox-info-line">
59 + <span class="featured-post-date"><%= song.updated_at.strftime("%B %-d, %Y") %></span>
60 + <%= link_to t("see_song_detail"),
61 + song_detail_path(song.slug),
62 + class: "badge rounded-pill bg-warning float-end" %>
63 + </div>
64 + </section>
65 + </div>
66 + <% if (index == @songs.size - 1 || (index != 0 && index % 2 != 0)) %>
67 + </div>
68 + <% end %>
69 + <% end %>
70 + <%= will_paginate @songs, class: 'flickr_pagination' %>
71 + </div>
72 + <%= render("partial_aside_index.html") %>
73 +</div>
74 +<%= render "shared/partial_breadcrumbs" do %>
75 + <li class="breadcrumb-item"><%= link_to t("nav_songs"), song_path %></li>
76 +<% end %>
backend/musik88-web/app/views/songs/karaoke.html.erb new
+81
@@ -0,0 +1,81 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= guitar_chord_detail_url(@song.slug) %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "Musik 88"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= guitar_chord_detail_url(@song.slug) %>",
46 + "name": "<%= t("guitar_chord") + @song.name + " " + @song.album.artists[0].name + " " + @song.album.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<%= render("partial_menus_aside", active_menu: 'karaoke' ) %>
53 +<div class="row">
54 + <div class="col-md-8">
55 + <div class="blog-post-single content-row chord-content">
56 + <h1 class="post-heading"><%= @page_title %></h1>
57 + <%= render("partial_meta_top") %>
58 + <% @song.audio_files.each_with_index do |audio_file, index| %>
59 + <% next if audio_file.karaoke_file.nil? %>
60 + <div class="buttons-wrap" style="width: 110px;background-color: inherit;">
61 + <audio controls>
62 + <source src="<%= audio_file.karaoke_file.source_file.blob %>" type="audio/wav">
63 + Your browser does not support the audio tag.
64 + </audio>
65 + <%= link_to("<i class='icon-download'></i> Download".html_safe, rails_blob_path(audio_file.karaoke_file.source_file, disposition: "attachment"))%>
66 + </div>
67 + <% end %>
68 + <% if @song.audio_files.count == 0 %>
69 + <div>
70 + <%= @shown_text.html_safe %>
71 + </div>
72 + <% end %>
73 + </div>
74 + </div>
75 +</div>
76 +<%= render "shared/partial_breadcrumbs" do %>
77 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
78 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
79 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
80 + <li class="breadcrumb-item"><%= link_to @song.name, guitar_chord_detail_url(@song.slug) %></li>
81 +<% end %>
backend/musik88-web/app/views/songs/lyrics.html.erb new
+137
@@ -0,0 +1,137 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= artist_url %>",
25 + "name": "<%= t("nav_artists") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 3,
30 + "item": {
31 + "@id": "<%= request.base_url %>/artis/<%= @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.artists[0].id %>",
32 + "name": "<%= @song.album.artists[0].name %>"
33 + }
34 + },{
35 + "@type": "ListItem",
36 + "position": 4,
37 + "item": {
38 + "@id": "<%= request.base_url %>/album/<%= @song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize %>-<%= @song.album.id %>",
39 + "name": "<%= @song.album.name %>"
40 + }
41 + },{
42 + "@type": "ListItem",
43 + "position": 5,
44 + "item": {
45 + "@id": "<%= request.url %>",
46 + "name": "<%= @song.name %>"
47 + }
48 + }]
49 + }
50 + </script>
51 +<% end %>
52 +<main>
53 + <div class="row g-5">
54 + <div class="col-md-12">
55 + <h1><%= @page_title %></h1>
56 + </div>
57 + </div>
58 + <div class="row g-5">
59 + <div class="col-sm-1">
60 + <div class="position-sticky" style="top: 1rem;">
61 + <div class="text-center mt-3">
62 + <% if user_signed_in? && @song.users.include?(current_user) %>
63 + <%= link_to(
64 + '<i class="bi bi-heart-fill"></i>'.html_safe,
65 + song_chord_pleas_unloved_path(id: @song.id),
66 + remote: true,
67 + method: :delete,
68 + :id => "ajax_trigger"
69 + )
70 + %>
71 + <% else %>
72 + <%= link_to(
73 + '<i class="bi bi-heart"></i>'.html_safe,
74 + song_chord_pleas_loved_path(id: @song.id),
75 + remote: true,
76 + method: :post,
77 + :id => "ajax_trigger"
78 + )
79 + %>
80 + <% end %>
81 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.users.count %></span>
82 + </div>
83 + <div class="text-center mt-4">
84 + <i class="bi bi-graph-up-arrow"></i>
85 + <span class="badge rounded-pill bg-warning text-dark"><%= @song.views_count %></span>
86 + </div>
87 + </div>
88 + </div>
89 + <div class="col-sm-9">
90 + <%= render('shared/partial_meta_top', author: @author, created_at: @song.created_at) %>
91 + <div class="mb-3 mt-3">
92 + <%= link_to('Share lyric!', add_lyric_path(@song), class: 'btn btn-primary btn-lg px-4') %>
93 + </div>
94 + <ol class="list-group list-group-numbered">
95 + <% @lyrics.each do |lyric| %>
96 + <li class="list-group-item d-flex justify-content-between align-items-start">
97 + <div class="ms-2 me-auto">
98 + <div class="fw-bold">
99 + <%= link_to(
100 + t('song_lyric_detail', title: lyric.title_link),
101 + lyric_detail_path(lyric.slug)
102 + ) %>
103 + </div>
104 + <%= link_to(
105 + "@#{lyric.user_or_nowhereman.username_or_id}",
106 + profile_path(lyric.user_or_nowhereman.username_or_id),
107 + class: 'fst-italic fw-light'
108 + ) %>
109 + </div>
110 + <span class="badge bg-warning rounded-pill text-dark"><%= lyric.views_count %></span>
111 + &nbsp;
112 + <i class="bi bi-graph-up-arrow"></i>
113 + </li>
114 + <% end %>
115 + </ol>
116 + <h2 class="mb-2 mb-2 mt-5">Kamu pikir kamu punya lirik yang lebih akurat?</h2>
117 + <%= render "shared/flash_message", object: flash %>
118 + <div class="mb-3">
119 + <%= form_for [@song, Comment.new] do |f| %>
120 + <%= f.text_area :body, placeholder: "Add a comment", rows: "4" , cols: "50%" %><br/>
121 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
122 + <%= f.submit "Add Comment", class: "btn btn-primary" %>
123 + <% end %>
124 + </div>
125 + <%= render(partial: 'comments/comment', collection: @song.comments) %>
126 + </div>
127 + <div class="col-sm-2">
128 + <%= render('partial_menus_aside', active_menu: 'lyric') %>
129 + </div>
130 + </div>
131 + <%= render "shared/partial_breadcrumbs" do %>
132 + <li class="breadcrumb-item"><%= link_to t("nav_artists"), artist_url %></li>
133 + <li class="breadcrumb-item"><%= link_to @song.album.artists[0].name, artist_detail_url(@song.album.artists[0].name.downcase.parameterize + "-" + @song.album.artists[0].id.to_s) %></li>
134 + <li class="breadcrumb-item"><%= link_to @song.album.name, album_detail_url(@song.album.name.downcase.parameterize + '-' + @song.album.artists[0].name.downcase.parameterize + "-" + @song.album.id.to_s) %></li>
135 + <li class="breadcrumb-item"><%= link_to @song.name, song_detail_path(@song.slug) %></li>
136 + <% end %>
137 +</main>
backend/musik88-web/app/views/songs/most_loved.html.erb new
+86
@@ -0,0 +1,86 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= song_path %>",
25 + "name": "<%= t("nav_songs") %>"
26 + }
27 + },{
28 + "@type": "ListItem",
29 + "position": 2,
30 + "item": {
31 + "@id": "<%= most_loved_songs_url %>",
32 + "name": "<%= t("breadcrumb_most_loved_songs") %>"
33 + }
34 + }]
35 + }
36 + </script>
37 +<% end %>
38 +<main>
39 + <div class="row g-5">
40 + <div class="col-md-12">
41 + <h1><%= @page_title %></h1>
42 + <%= render('shared/flash_message', object: flash) %>
43 + </div>
44 + </div>
45 + <div class="row">
46 + <div class="col-md-12">
47 + <ul class="list-group">
48 + <% @most_loved_songs.each_with_index do |song, index| %>
49 + <li class="list-group-item <%= index % 2 == 0 ? 'list-group-item-primary' : ' list-group-item-warning' %>">
50 + <%= index + 1 %>.
51 + <%= link_to(
52 + song.name,
53 + song_detail_path(song.slug)
54 + ) %>
55 + <span class="p-2">
56 + <i class='bi bi-heart-fill'></i>
57 + <span class='badge rounded-pill bg-warning text-dark'><%= song.users_count %></span>
58 + </span>
59 + <%= link_to(
60 + '<i class="bi bi-info-circle"></i>'.html_safe,
61 + song_detail_path(song.slug),
62 + title: t('song_menu_aside_info'),
63 + class: 'float-end p-1'
64 + ) %>
65 + <%= link_to(
66 + '<i class="bi bi-music-note-list"></i>'.html_safe,
67 + song_chords_path(song.slug),
68 + title: t('song_menu_aside_chords'),
69 + class: 'float-end p-1'
70 + ) %>
71 + <%= link_to(
72 + '<i class="bi bi-music-note-beamed"></i>'.html_safe,
73 + song_chords_path(song.slug),
74 + title: t('song_menu_aside_audio'),
75 + class: 'float-end p-1'
76 + ) %>
77 + </li>
78 + <% end %>
79 + </ul>
80 + </div>
81 + </div>
82 + <%= render 'shared/partial_breadcrumbs' do %>
83 + <li class="breadcrumb-item"><%= link_to t("nav_songs"), song_path %></li>
84 + <li class="breadcrumb-item"><%= link_to t("breadcrumb_most_loved_songs"), most_loved_songs_url %></li>
85 + <% end %>
86 +</main>
\ No newline at end of file
backend/musik88-web/app/views/user_notifier_mailer/send_reset_password_instructions.html.erb new
+29
@@ -0,0 +1,29 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
5 + </head>
6 + <body>
7 + <h1><%= t("user_reset_password_email_heading", user_name: @user.name) %></h1>
8 + <p><%= t("user_reset_password_email_heading_p1") %></p>
9 + <p>
10 + <%=
11 + t(
12 + "user_reset_password_email_heading_p2",
13 + href: link_to(
14 + t("user_reset_password_email_link"),
15 + edit_user_password_url(reset_password_token: @token)
16 + )
17 + ).html_safe
18 + %>
19 + </p>
20 + <h2><%= t("user_notifier_projects") %></h2>
21 + <p><a href="https://nft.musik88.com/" target="__blank">NFT</a></p>
22 + <p><a href="https://beta.splitfire.ai/" target="__blank">SplitFire AI</a></p>
23 + <p><a href="https://discord.gg/kdZSk6mdAE" target="__blank">Join our Discord server</a></p>
24 + </br>
25 + </br>
26 + <p><%= t('user_email_with_love') %></p>
27 + <p><a href="https://<%= @host %>/@seto" target="__blank">@seto</a></p>
28 + </body>
29 +</html>
\ No newline at end of file
backend/musik88-web/app/views/user_notifier_mailer/send_signup_email.html.erb new
+19
@@ -0,0 +1,19 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
5 + </head>
6 + <body>
7 + <h1><%= t("user_signup_email_heading", user_name: @user.name) %></h1>
8 + <p><%= t("user_signup_email_heading_p1") %></p>
9 + <p>You'll get separate email for verification.</p>
10 + <h2><%= t("user_notifier_projects") %></h2>
11 + <p><a href="https://nft.musik88.com/" target="__blank">NFT</a></p>
12 + <p><a href="https://beta.splitfire.ai/" target="__blank">SplitFire AI</a></p>
13 + <p><a href="https://discord.gg/kdZSk6mdAE" target="__blank">Join our Discord server</a></p>
14 + </br>
15 + </br>
16 + <p>With ❤️ from Stockholm,</p>
17 + <p><a href="https://<%= @host %>/@seto" target="__blank">@seto</a></p>
18 + </body>
19 +</html>
\ No newline at end of file
backend/musik88-web/app/views/user_notifier_mailer/send_wallet_activation_notification.html.erb new
+19
@@ -0,0 +1,19 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
5 + </head>
6 + <body>
7 + <h1><%= t("user_send_wallet_activation_notification_heading", user_name: @user.name) %></h1>
8 + <p><%= t("user_send_wallet_activation_notification_p1") %></p>
9 + <p><a href="https://solscan.io/account/<%= @user.wallet.pubkey %><%= @cluster %>" target="__blank"><%= t("user_send_wallet_activation_notification_address") %></a></p>
10 + <h2><%= t("user_notifier_projects") %></h2>
11 + <p><a href="https://nft.musik88.com/" target="__blank">NFT</a></p>
12 + <p><a href="https://splitfire.ai/" target="__blank">SplitFire AI</a></p>
13 + <p><a href="https://discord.gg/kdZSk6mdAE" target="__blank">Join our Discord server</a></p>
14 + </br>
15 + </br>
16 + <p>With ❤️ from Stockholm,</p>
17 + <p><a href="https://<%= @host %>/@seto" target="__blank">@seto</a></p>
18 + </body>
19 +</html>
\ No newline at end of file
backend/musik88-web/app/views/users/_follow.html.erb new
+4
@@ -0,0 +1,4 @@
1 +<%= form_for(current_user.active_relationships.build) do |f| %>
2 + <div><%= hidden_field_tag :followed_id, @user.id %></div>
3 + <%= f.submit "Follow", class: "btn btn-primary" %>
4 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_audio_file.html.erb new
+45
@@ -0,0 +1,45 @@
1 +<div class="row mb-4">
2 + <div class="col-4 text-center">
3 + <%= link_to(
4 + image_tag(
5 + item.youtube_thumbnail,
6 + class: "img-fluid rounded",
7 + style: "width: 300px;"
8 + ),
9 + item.splitfire_link,
10 + target: :_blank
11 + ) %>
12 + </div>
13 + <div class="col-8">
14 + <ul class="list-group">
15 + <li class="list-group-item">
16 + <i class="bi bi-music-note-list"></i>
17 + <%= link_to(
18 + item.source_file.filename,
19 + item.splitfire_link,
20 + target: :_blank
21 + ) %>
22 + </li>
23 + <li class="list-group-item">
24 + <i class="bi bi-mic-fill"></i>
25 + <%= link_to(
26 + item.karaoke_title,
27 + item.splitfire_karaoke_link,
28 + target: :_blank
29 + ) %>
30 + </li>
31 + <% if !item.song.nil? %>
32 + <li class="list-group-item list-group-item-warning">
33 + <div class="row mt-2">
34 + <div class="col-1 text-center">
35 + <i class="bi bi-info-circle-fill align-center"></i>
36 + </div>
37 + <div class="col-11">
38 + <%= render 'partial_audio_file_information', audio_file: item %>
39 + </div>
40 + </div>
41 + </li>
42 + <% end %>
43 + </ul>
44 + </div>
45 +</div>
backend/musik88-web/app/views/users/_partial_audio_file_information.html.erb new
+27
@@ -0,0 +1,27 @@
1 +<ul class="list-group">
2 + <li class="list-group-item">
3 + Song:
4 + <%= link_to(
5 + audio_file.song.name,
6 + song_detail_path(audio_file.song.slug)
7 + ) %>
8 + </li>
9 + <li class="list-group-item">
10 + Album:
11 + <%= link_to(
12 + audio_file.song.album.name,
13 + album_detail_path(album_friendly_url(audio_file.song.album))
14 + ) %>
15 + </li>
16 + <li class="list-group-item">
17 + Artist(s):
18 + <% audio_file.song.album.artists.each_with_index do |artist, index| %>
19 + <%= " " if index != 0 %>
20 + <%= link_to(
21 + artist.name,
22 + artist_detail_path(artist_friendly_url(artist))
23 + ) %>
24 + <%= "," if index != audio_file.song.album.artists.count - 1 %>
25 + <% end %>
26 + </li>
27 + </ul>
backend/musik88-web/app/views/users/_partial_file_row_information.html.erb new
+30
@@ -0,0 +1,30 @@
1 +<div class="row" id="audio-file-information-<%= audio_file.id %>" style="margin: 10px;">
2 + <div class="col-md-4">
3 + Song:
4 + <%= link_to(
5 + audio_file.song.name,
6 + song_detail_path(audio_file.song.slug)
7 + ) %>
8 + </div>
9 + <div class="col-md-4">
10 + Album:
11 + <%= link_to(
12 + audio_file.song.album.name,
13 + album_detail_path(album_friendly_url(audio_file.song.album))
14 + ) %>
15 + </div>
16 + <div class="col-md-4">
17 + Artist(s):
18 + <% audio_file.song.album.artists.each_with_index do |artist, index| %>
19 + <%= " " if index != 0 %>
20 +
21 + <%= link_to(
22 + artist.name,
23 + artist_detail_path(artist_friendly_url(artist))
24 + ) %>
25 +
26 + <%= "," if index != audio_file.song.album.artists.count - 1 %>
27 +
28 + <% end %>
29 + </div>
30 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_file_row_progress_bar.html.erb new
+14
@@ -0,0 +1,14 @@
1 +<div class="row">
2 + <div class="col-md-12">
3 + <div class="progress">
4 + <div
5 + class="progress-bar progress-bar-success progress-bar-striped progress-bar-animated"
6 + role="progressbar"
7 + aria-valuenow="<%= audio_file.audio_file_splitfire_status.processing_progress %>"
8 + aria-valuemin="0"
9 + aria-valuemax="100"
10 + style="width: <%= audio_file.audio_file_splitfire_status.processing_progress %>%">
11 + </div>
12 + </div>
13 + </div>
14 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_file_row_result.html.erb new
+32
@@ -0,0 +1,32 @@
1 + <div class="row" id="splitfire-result-<%= audio_file.id %>" style="display: none;">
2 + <div class="col-md-12">
3 + <% if !audio_file.youtube_video_url.nil? && !audio_file.youtube_video_id.nil? %>
4 + <div style="padding-top: 10px;">
5 + <iframe
6 + width="300"
7 + height="150"
8 + src="https://www.youtube.com/embed/<%= audio_file.youtube_video_id %>?controls=0"
9 + title="YouTube video player"
10 + frameborder="0"
11 + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture">
12 + </iframe>
13 + </div>
14 + <% end %>
15 + <% audio_file.splitfire_results.each_with_index do |audio_file, index| %>
16 + <%= render "users/partial_splitfire_result_row", audio_file: audio_file %>
17 + <% end %>
18 +
19 + <% if !audio_file.bass_backing_track.nil? %>
20 + <%= render "users/partial_splitfire_result_row", audio_file: audio_file.bass_backing_track %>
21 + <% end %>
22 +
23 + <% if !audio_file.karaoke_file.nil? %>
24 + <%= render "users/partial_splitfire_result_row", audio_file: audio_file.karaoke_file %>
25 + <% end %>
26 +
27 + <% if !audio_file.drum_backing_track.nil? %>
28 + <%= render "users/partial_splitfire_result_row", audio_file: audio_file.drum_backing_track %>
29 + <% end %>
30 +
31 + </div>
32 + </div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_file_row_top.html.erb new
+16
@@ -0,0 +1,16 @@
1 +<div class="row" style="padding: 10px 0 10px 0px; ">
2 + <div class="col-md-10" class="file-information" style="margin-bottom: 10px;">
3 + <%= link_to(
4 + audio_file.output_filename.html_safe,
5 + audio_file.user.splitfire_link(audio_file),
6 + target: :_blank
7 + ) %>
8 + </div>
9 + <div class="col-md-2 text-right">
10 + <%= link_to(
11 + '<i class="icon-play" title="Play audio"></i>'.html_safe,
12 + audio_file.user.splitfire_link(audio_file),
13 + target: :_blank
14 + ) %>
15 + </div>
16 +</div>
backend/musik88-web/app/views/users/_partial_menu.html.erb new
+14
@@ -0,0 +1,14 @@
1 +<li class="nav-item">
2 + <% if active %>
3 + <a href="javascript:void(0)" class="nav-link active disabled" aria-current="page">
4 + <%= href_text %>
5 + </a>
6 + <% else %>
7 + <%= link_to(
8 + href_text,
9 + href_link,
10 + class: "nav-link"
11 + )
12 + %>
13 + <% end %>
14 +</li>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_menus.html.erb new
+27
@@ -0,0 +1,27 @@
1 +<div class="row">
2 + <div class="col-md-12">
3 + <ul class="nav nav-tabs">
4 + <%= render(
5 + "partial_menu",
6 + href_text: t("oh_hi"),
7 + href_link: profile_path,
8 + active: active_menu == 'show'
9 + )
10 + %>
11 + <%= render(
12 + "partial_menu",
13 + href_text: t("reputation"),
14 + href_link: show_reputation_path,
15 + active: active_menu == 'reputation'
16 + )
17 + %>
18 + <%= render(
19 + "partial_menu",
20 + href_text: t("settings"),
21 + href_link: settings_path,
22 + active: active_menu == 'settings'
23 + ) if current_user == @user
24 + %>
25 + </ul>
26 + </div>
27 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_menus_friends.html.erb new
+4
@@ -0,0 +1,4 @@
1 +<ul class="nav flex-column">
2 + <%= render("partial_menu", href_text: t("followers"), href_link: followers_path, active: active_menu == 'followers') %>
3 + <%= render("partial_menu", href_text: t("following"), href_link: following_path, active: active_menu == 'following') %>
4 +</ul>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_onboarding_google_done.html.erb new
+11
@@ -0,0 +1,11 @@
1 +<div class='google-content'>
2 + <%= t('onboarding_google_done') %>
3 + <div class='row'>
4 + <div class='col-md-6 offset-md-3'>
5 + <div>
6 + <img src='<%= asset_pack_path('media/images/badge-youtube-exporter.gif') %>' class='img-fluid rounded' alt='Youtube exporter'>
7 + </div>
8 + <button type='button' class='btn btn-primary mt-3 google'><%= t('next') %></button>
9 + </div>
10 + </div>
11 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_onboarding_google_progress_bar.html.erb new
+10
@@ -0,0 +1,10 @@
1 +<div class='google-content'>
2 + <%= t('onboarding_google_processing') %>
3 + <div class='row'>
4 + <div class='col-md-6 offset-md-3 text-center'>
5 + <div class='progress google-export-progress'>
6 + <div class='progress-bar progress-bar-striped progress-bar-animated' role='progressbar' aria-valuenow='10' aria-valuemin='0' aria-valuemax='100' style='width: 10%'></div>
7 + </div>
8 + </div>
9 + </div>
10 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_onboarding_spotify_done.html.erb new
+11
@@ -0,0 +1,11 @@
1 +<div class='spotify-content'>
2 + <%= t('onboarding_spotify_done') %>
3 + <div class='row'>
4 + <div class='col-md-6 offset-md-3'>
5 + <div>
6 + <img src='<%= asset_pack_path('media/images/badge-spotify-exporter.gif') %>' class='img-fluid rounded' alt='Spotify exporter'>
7 + </div>
8 + <button type='button' class='btn btn-primary mt-3 spotify'><%= t('next') %></button>
9 + </div>
10 + </div>
11 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_onboarding_spotify_progress_bar.html.erb new
+10
@@ -0,0 +1,10 @@
1 +<div class='spotify-content'>
2 + <%= t('onboarding_spotify_processing') %>
3 + <div class='row'>
4 + <div class='col-md-6 offset-md-3 text-center'>
5 + <div class='progress spotify-export-progress'>
6 + <div class='progress-bar progress-bar-striped progress-bar-animated' role='progressbar' aria-valuenow='10' aria-valuemin='0' aria-valuemax='100' style='width: 10%'></div>
7 + </div>
8 + </div>
9 + </div>
10 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_private_profile.html.erb new
+7
@@ -0,0 +1,7 @@
1 +<div class="row">
2 + <div class="col-md-4">
3 +
4 + <%= t("my_profile_private") %>
5 +
6 + </div>
7 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_profile_followers.html.erb new
+21
@@ -0,0 +1,21 @@
1 +<div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'friends' ) %>
4 + </div>
5 +</div>
6 +<div class="row mt-3">
7 + <div class="col-md-2">
8 + <%= render('partial_menus_friends', active_menu: 'followers' ) %>
9 + </div>
10 + <div class="col-md-10">
11 + <div class="row mt-5">
12 + <% if @user.followers.count.positive? %>
13 + <% @user.followers.each_with_index do |user, index| %>
14 + <%= render('partial_user', user: user) %>
15 + <% end %>
16 + <% else %>
17 + <p>All the lonely people, where do they all come from?</p>
18 + <% end %>
19 + </div>
20 + </div>
21 +</div>
backend/musik88-web/app/views/users/_partial_profile_following.html.erb new
+21
@@ -0,0 +1,21 @@
1 +<div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'friends') %>
4 + </div>
5 +</div>
6 +<div class="row mt-3">
7 + <div class="col-md-2">
8 + <%= render('partial_menus_friends', active_menu: 'following' ) %>
9 + </div>
10 + <div class="col-md-10">
11 + <div class="row mt-5">
12 + <% if @user.following.count.positive? %>
13 + <% @user.following.each_with_index do |user, index| %>
14 + <%= render('partial_user', user: user) %>
15 + <% end %>
16 + <% else %>
17 + <p>All the lonely people, where do they all belong?</p>
18 + <% end %>
19 + </div>
20 + </div>
21 +</div>
backend/musik88-web/app/views/users/_partial_public_profile.html.erb new
+17
@@ -0,0 +1,17 @@
1 +<div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'show' ) %>
4 + </div>
5 +</div>
6 +<div class="row mt-3">
7 + <% if @show_comment_form %>
8 + <div class="mb-3">
9 + <%= form_for [@user, Comment.new] do |f| %>
10 + <%= f.text_area :body, placeholder: t('discussion_comment_placeholder'), rows: "4" , cols: "50%" %><br/>
11 + <%= f.hidden_field :user_id, value: current_user.id if user_signed_in? %>
12 + <%= f.submit t('discussion_comment_button'), class: "btn btn-primary" %>
13 + <% end %>
14 + </div>
15 + <% end %>
16 + <%= render(partial: 'comments/comment', collection: @user.comments) %>
17 +</div>
backend/musik88-web/app/views/users/_partial_public_profile_files.html.erb new
+23
@@ -0,0 +1,23 @@
1 +<div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'files' ) %>
4 + </div>
5 +</div>
6 +<div class="row mt-5">
7 + <div class="col-md-12">
8 + <% if !@audio_files.nil? && !@audio_files.empty? %>
9 + <% @audio_files.each_with_index do |audio_file, index| %>
10 + <%= render "users/partial_audio_file", item: audio_file %>
11 + <% end %>
12 + <div class="row mt-5">
13 + <div class="col-md-12">
14 + <%= render "partial_splitfire_version" %>
15 + </div>
16 + </div>
17 + <% elsif user_signed_in? %>
18 + <p>No files. <%= link_to('Check on the SplitFire website.', 'https://splitfire.ai') %></p>
19 + <% elsif !user_signed_in? %>
20 + <p>No public files. <%= link_to('Login', login_path) %> to see if this user shares privately.</p>
21 + <% end %>
22 + </div>
23 +</div>
backend/musik88-web/app/views/users/_partial_public_profile_friends.html.erb new
+13
@@ -0,0 +1,13 @@
1 +<div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'friends' ) %>
4 + </div>
5 +</div>
6 +<div class="row mt-3">
7 + <div class="col-md-2">
8 + <%= render('partial_menus_friends', active_menu: '' ) %>
9 + </div>
10 + <div class="col-md-10">
11 + <%= t('find_your_friends') %>
12 + </div>
13 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_public_profile_header.html.erb new
+9
@@ -0,0 +1,9 @@
1 +<div class="row mt-3">
2 + <div class="col-md-12">
3 + <h1 class="d-inline-flex">
4 + <%= @user.name %>
5 + </h1>
6 + <%= "<span class='fst-italic fw-lighter'>It's me!</span>".html_safe if user_signed_in? && @user.id == current_user.id %>
7 + <%= render "shared/flash_message", object: flash %>
8 + </div>
9 +</div>
backend/musik88-web/app/views/users/_partial_public_profile_reputation.html.erb new
+15
@@ -0,0 +1,15 @@
1 +<div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'reputation' ) %>
4 + </div>
5 +</div>
6 +<div class="row mt-3">
7 + <div class="col-md-12">
8 + <h2><%= t('badges') %></h2>
9 + </div>
10 + <% @user.badges.each do |badge| %>
11 + <div class="col-4 col-sm-3 col-md-3">
12 + <img src='<%= asset_pack_path(badge.custom_fields[:icon_path]) %>' class='img-fluid rounded img-thumbnail m-3' title='<%= badge.name %>' alt='Pretty badge, huh?'>
13 + </div>
14 + <% end %>
15 +</div>
backend/musik88-web/app/views/users/_partial_public_profile_user_information.html.erb new
+68
@@ -0,0 +1,68 @@
1 +<div class="row">
2 + <div class="col-md-2 text-center">
3 + <%= gravatar_for @user %>
4 + <div class="mb-3 mt-3">
5 + <i class="bi bi-graph-up-arrow"></i>&nbsp;
6 + <span class="badge bg-warning rounded-pill text-dark">
7 + <%= @user.followers.count %>
8 + </span>
9 + <%= t('followers').downcase %>
10 + </div>
11 + <div class="mb-3 mt-3">
12 + <i class="bi bi-graph-up-arrow"></i>&nbsp;
13 + <span class="badge bg-warning rounded-pill text-dark">
14 + <%= @user.following.count %>
15 + </span>
16 + <%= t('following').downcase %>
17 + </div>
18 + <% if user_signed_in? && current_user.id != @user.id %>
19 + <div id="follow_form">
20 + <% if current_user.following?(@user) %>
21 + <%= render 'unfollow' %>
22 + <% else %>
23 + <%= render 'follow' %>
24 + <% end %>
25 + </div>
26 + <% end %>
27 + </div>
28 + <div class="col-md-4">
29 + <h1 class="d-inline-flex">
30 + <%= @user.name %>
31 + </h1>
32 + <%= "<span class='fst-italic fw-lighter'>It's me!</span>".html_safe if user_signed_in? && @user.id == current_user.id %>
33 + <ul class="list-group">
34 + <li class="list-group-item d-flex align-items-center">
35 + <i class="bi bi-music-note-beamed"></i>&nbsp;
36 + <%= link_to(
37 + @username_string.html_safe,
38 + profile_path(@user.username_or_id)
39 + ) %>
40 + </li>
41 + <li class="list-group-item d-flex align-items-center">
42 + <i class="bi bi-link"></i>&nbsp;
43 + <%= link_to(
44 + "@#{@user.username_or_id}",
45 + @user.splitfire_profile_link,
46 + target: :_blank
47 + )
48 + %>
49 + </li>
50 + <% if @user.instagram.present? %>
51 + <li class="list-group-item d-flex align-items-center">
52 + <i class="bi bi-instagram"></i>&nbsp;
53 + <%= "#{link_to(@user.instagram, "https://instagr.am/#{@user.instagram}", target: :_blank)}".html_safe %>
54 + </li>
55 + <% end %>
56 + <% if @user.youtube_channel_id.present? %>
57 + <li class="list-group-item d-flex align-items-center">
58 + <i class="bi bi-youtube"></i>&nbsp;
59 + <%= "#{link_to(@user.name, "https://www.youtube.com/channel/#{@user.youtube_channel_id}", target: :_blank)}".html_safe %>
60 + </li>
61 + <% end %>
62 + </ul>
63 + </div>
64 + <div class="col-md-6">
65 + <h2 class="display-6">About</h2>
66 + <%= @user.about.present? ? @user.about : 'Emptiness...' %>
67 + </div>
68 +</div>
backend/musik88-web/app/views/users/_partial_secure_page_announcement.html.erb new
+9
@@ -0,0 +1,9 @@
1 +<div class="row">
2 + <div class="col-md-12" style="margin-bottom: 20px; font-style: italic;">
3 +
4 + <div class="page-header-profile">
5 + <%= t("my_profile_link_html", href: link_to(t("my_profile_link_href"), my_path, style: 'text-decoration-line: underline')) %>
6 + </div>
7 +
8 + </div>
9 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_splitfire_result_row.html.erb new
+15
@@ -0,0 +1,15 @@
1 +<div class="row" style="padding: 10px 0 20px 10px;">
2 + <div class="col-md-10" style="padding-bottom: 10px;">
3 + <%= audio_file.source_file.blob.filename %>
4 + </div>
5 + <div class="col-md-2 text-center">
6 + <span style="margin: 10px;">
7 + <a href="javascript:void(0)" class="audio-file-play-pause" audio-file-url="<%= rails_blob_path(audio_file.source_file, disposition: "attachment") %>">
8 + <i class="icon-play" title="Play audio"></i>
9 + <i class="icon-pause" title="Play audio" style="display: none;"></i>
10 + <i class="icon-time" title="Loading audio" style="display: none;"></i>
11 + </a>
12 + </span>
13 + <%= link_to("<i class='icon-download'></i>".html_safe, rails_blob_path(audio_file.source_file, disposition: "attachment"), title: "Download") %>
14 + </div>
15 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_splitfire_row.html.erb new
+21
@@ -0,0 +1,21 @@
1 +<div id="audio-file-row-<%= audio_file.id %>" class="audio-file-row">
2 + <% if audio_file.is_valid_row? %>
3 +
4 + <%= render "partial_file_row_top.html", audio_file: audio_file %>
5 +
6 + <% if !audio_file.song.nil? %>
7 + <%= render "partial_file_row_information.html", audio_file: audio_file %>
8 + <% end %>
9 +
10 + <% if audio_file.is_in_progress? %>
11 + <%= render "partial_file_row_progress_bar.html", audio_file: audio_file %>
12 + <% end %>
13 +
14 + <% else %>
15 + <div class="row">
16 + <div class="col-md-12" style="padding: 20px">
17 + Something wrong with the file. Please report this.
18 + </div>
19 + </div>
20 + <% end %>
21 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_splitfire_version.html.erb new
+5
@@ -0,0 +1,5 @@
1 +<div class="modul" style="margin-top: 20px; padding-right: 6px;">
2 + <span class="modul-version" style="float: right; font-style: italic; text-decoration: underline;">
3 + <%= link_to("SplitFire v1.2.0", splitfire_path(:anchor => "v1-2-0"), target: "__blank") %>
4 + <span>
5 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_sticky_player.html.erb new
+19
@@ -0,0 +1,19 @@
1 +<div class="footer-sticky-player">
2 + <div class="row">
3 + <div class="col-md-12">
4 + <% if !@audio_files.nil? %>
5 + <div id="waveform-placeholder" style="width: 100px;margin: auto;text-align: center;">
6 + <i class="icon-music"></i>
7 + <p>Press play</p>
8 + </div>
9 + <% end %>
10 + <div class="progress progress-loading-audio">
11 + <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
12 + </div>
13 + </div>
14 + <div id="waveform-view" style="margin: -88px 40px 40px 40px; display: none;">
15 + <div id="waveform"></div>
16 + </div>
17 + </div>
18 + </div>
19 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_partial_user.html.erb new
+22
@@ -0,0 +1,22 @@
1 +<div class="col-md-3 col-sm-6 mb-2">
2 + <div class="row mb-5">
3 + <div class="col-5 mb-2">
4 + <%= gravatar_for user %>
5 + </div>
6 + <div class="col-7 mb-2">
7 + <ul class="list-unstyled">
8 + <li>
9 + <%= link_to(
10 + "@#{user.username_or_id}",
11 + profile_path(user.username_or_id),
12 + title: "Author: @#{user.username_or_id}"
13 + ) %>
14 + </li>
15 + <li><%= user.name %></li>
16 + &nbsp;
17 + <i class="bi bi-graph-up-arrow"></i>
18 + </li>
19 + </ul>
20 + </div>
21 + </div>
22 +</div>
backend/musik88-web/app/views/users/_partial_username_content.html.erb new
+11
@@ -0,0 +1,11 @@
1 +<div class='username-content'>
2 + <%= t('onboarding_username_done', profile_badges_link: link_to(t('pretty_badge'), show_reputation_path(@username), target: '__blank')).html_safe %>
3 + <div class='row'>
4 + <div class='col-md-6 offset-md-3'>
5 + <div>
6 + <img src='<%= asset_pack_path('media/images/badge-pretty.gif') %>' class='img-fluid rounded' alt='Pretty badge, huh?'>
7 + </div>
8 + <button type='button' class='btn btn-primary mt-3 username'><%= t('next') %></button>
9 + </div>
10 + </div>
11 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_protected_settings.html.erb new
+182
@@ -0,0 +1,182 @@
1 + <div class="row mt-5">
2 + <div class="col-md-12">
3 + <%= render('partial_menus', active_menu: 'settings' ) %>
4 + </div>
5 + </div>
6 + <div class="row mt-3">
7 + <%= render "shared/flash_message", object: flash %>
8 + <ul class="list-group">
9 + <li class="list-group-item">
10 + <h4><%= t('your_invitees') %></h4>
11 + <p><%= t('your_invitees_explanation') %></p>
12 + <%= link_to(t('invite_button'), new_user_invitation_path, class: 'btn btn-primary', target: '__blank') %>
13 + <div class="table-responsive">
14 + <table class="table table-striped">
15 + <thead>
16 + <tr>
17 + <th scope="col"><%= t("no") %></th>
18 + <th scope="col"><%= t("your_invite_email") %></th>
19 + <th scope="col"><%= t("your_invite_date") %></th>
20 + <th scope="col"><%= t("your_invite_status") %></th>
21 + </tr>
22 + </thead>
23 + <tbody>
24 + <% @current_user.invitees.each_with_index do |invite, index| %>
25 + <tr>
26 + <th scope="row"><%= index + 1 %></th>
27 + <td>
28 + <%= invite.email %>
29 + </td>
30 + <td>
31 + <%= time_ago_in_words invite.created_at %>
32 + </td>
33 + <td>
34 + <%= invite.invitation_accepted? ? '<i class="bi bi-check"></i>'.html_safe
35 + : '<i class="bi bi-send-check"></i>'.html_safe %>
36 + </td>
37 + </tr>
38 + <% end %>
39 + </tbody>
40 + </table>
41 + </div>
42 + </li>
43 + <li class="list-group-item">
44 + <h4><%= t('your_wallet_address') %></h4>
45 + <p><%= t('your_wallet_address_explanation', href: link_to('Raydium', 'https://beta.raydium.io/swap/?inputMint=CTQBjyrX8pYyqbNa8vAhQfnRXfu9cUxnvrxj5PvbzTmf&outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', target: '_blank')).html_safe %></p>
46 + <div class="table-responsive">
47 + <table class="table table-striped">
48 + <thead>
49 + <tr>
50 + <th scope="col"><%= t("no") %></th>
51 + <th scope="col"><%= t("your_wallet_pubkey") %></th>
52 + <th scope="col"><%= t("your_wallet_creation_date") %></th>
53 + <th scope="col"><%= t("your_wallet_type") %></th>
54 + <th scope="col"><%= t("your_wallet_action") %></th>
55 + </tr>
56 + </thead>
57 + <tbody>
58 + <% @current_user.wallets.each_with_index do |wallet, index| %>
59 + <tr>
60 + <th scope="row"><%= index + 1 %></th>
61 + <td>
62 + <%= wallet.pubkey %>
63 + </td>
64 + <td>
65 + <%= time_ago_in_words wallet.created_at %>
66 + </td>
67 + <td>
68 + <%= wallet.wallet_type %>
69 + </td>
70 + <td>
71 + <% if wallet.solana? %>
72 + <%= link_to('Solscan', "https://solscan.io/account/#{wallet.pubkey}", target: '__blank') %>
73 + <%= link_to(t('activate_solana_wallet'), settings_activate_solana_path, class: 'btn btn-primary', method: :post) if !wallet.done? %>
74 + <% end %>
75 + <% if wallet.evm? %>
76 + <%= link_to('BscScan', "https://bscscan.com/address/#{wallet.pubkey}", target: '__blank') %>
77 + <% end %>
78 + </td>
79 + </tr>
80 + <% end %>
81 + </tbody>
82 + </table>
83 + </div>
84 + </li>
85 + <li class="list-group-item">
86 + <h4><%= t('your_account_connections') %></h1>
87 + <p><%= t('your_account_connections_explanation') %></p>
88 + <div class="form-check form-switch mb-3">
89 + <input class="form-check-input sync-spotify" type="checkbox" <%= 'checked' if @current_user.user_setting&.sync_spotify? %>>
90 + <label class="form-check-label" for="flexSwitchCheckChecked">Spotify</label>
91 + </div>
92 + <p><%= t('your_account_connections_spotify_token_explanation') %></p>
93 + <div class="form-check form-switch mb-3">
94 + <input class="form-check-input allowed-use-spotify-token" type="checkbox" <%= 'checked' if @current_user.user_setting&.allowed_use_spotify_token? %>>
95 + <label class="form-check-label" for="flexSwitchCheckChecked">Spotify Token</label>
96 + </div>
97 + <p><%= t('your_account_connections_google_explanation') %></p>
98 + <div class="form-check form-switch mb-3">
99 + <input class="form-check-input allowed-use-google-token" type="checkbox" <%= 'checked' if @current_user.user_setting&.allowed_use_google_token? %>>
100 + <label class="form-check-label" for="flexSwitchCheckChecked">Google Token</label>
101 + </div>
102 + <div class="alert alert-warning alert-sync" role="alert" style='display:none;'>
103 + <strong>Updated!</strong>.
104 + </div>
105 + </li>
106 + <li class="list-group-item">
107 + <h4><%= t('your_locale') %></h1>
108 + <p><%= t('your_locale_explanation') %></p>
109 + <script>
110 + function changeDefaultLocale(locale) {
111 + $.ajax({
112 + type: "POST",
113 + url: "<%= settings_locale_path %>",
114 + data: { authenticity_token: "<%= form_authenticity_token %>", default_locale: locale.value },
115 + success(data) {
116 + location.reload();
117 + return false;
118 + },
119 + error(data) {
120 + alert('Something wrong. Try again later.');
121 + return false;
122 + }
123 + })
124 + }
125 + </script>
126 + <select id="select-locale" class="form-select" aria-label="Locale" onchange="changeDefaultLocale(this)">
127 + <% I18n.available_locales.each do |locale| %>
128 + <option <%= 'selected' if I18n.locale == locale %> value="<%= locale_index(locale) %>"><%= t('locale_name', locale: locale) %></option>
129 + <% end %>
130 + </select>
131 + </li>
132 + </ul>
133 + </div>
134 +<script>
135 +// Track a timeoutID
136 +var timeoutID;
137 +
138 +function prepareUpdate() {
139 + if (timeoutID) {
140 + clearTimeout(timeoutID);
141 + }
142 + $('.alert-sync').hide();
143 +}
144 +
145 +function updateSettings(type, value) {
146 + $.ajax({
147 + method: 'PUT',
148 + url: '<%= update_settings_path %>',
149 + data: {
150 + authenticity_token: '<%= form_authenticity_token %>',
151 + settings: {
152 + type: type,
153 + value: value
154 + }
155 + },
156 + dataType: 'json',
157 + success: function(data) {
158 + if (data.code == 200) {
159 + $('.alert-sync').show();
160 + timeoutID = setTimeout(
161 + function() {
162 + $('.alert-sync').hide();
163 + }, 3000
164 + );
165 + }
166 + }
167 + });
168 +}
169 +
170 +$('.sync-spotify').on('change', function(e) {
171 + prepareUpdate();
172 + updateSettings('sync_spotify', e.target.checked);
173 +})
174 +$('.allowed-use-spotify-token').on('change', function(e) {
175 + prepareUpdate();
176 + updateSettings('allowed_use_spotify_token', e.target.checked);
177 +})
178 +$('.allowed-use-google-token').on('change', function(e) {
179 + prepareUpdate();
180 + updateSettings('allowed_use_google_token', e.target.checked);
181 +});
182 +</script>
\ No newline at end of file
backend/musik88-web/app/views/users/_template.html.erb new
+31
@@ -0,0 +1,31 @@
1 +<div class="row">
2 + <div class="col-md-2 text-center">
3 + <%= gravatar_for @user %>
4 + <div class="mb-3 mt-3">
5 + <i class="bi bi-graph-up-arrow"></i>&nbsp;
6 + <span class="badge bg-warning rounded-pill text-dark">
7 + <%= @user.followers.count %>
8 + </span>
9 + <%= t('followers').downcase %>
10 + </div>
11 + <div class="mb-3 mt-3">
12 + <i class="bi bi-graph-up-arrow"></i>&nbsp;
13 + <span class="badge bg-warning rounded-pill text-dark">
14 + <%= @user.following.count %>
15 + </span>
16 + <%= t('following').downcase %>
17 + </div>
18 + <% if user_signed_in? && current_user.id != @user.id %>
19 + <div id="follow_form">
20 + <% if current_user.following?(@user) %>
21 + <%= render 'unfollow' %>
22 + <% else %>
23 + <%= render 'follow' %>
24 + <% end %>
25 + </div>
26 + <% end %>
27 + </div>
28 + <div class="col-md-10">
29 + <%= render(page) %>
30 + </div>
31 +</div>
\ No newline at end of file
backend/musik88-web/app/views/users/_unfollow.html.erb new
+4
@@ -0,0 +1,4 @@
1 +<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
2 + html: { method: :delete }) do |f| %>
3 + <%= f.submit "Unfollow", class: "btn btn-danger" %>
4 +<% end %>
\ No newline at end of file
backend/musik88-web/app/views/users/community.html.erb new
+92
@@ -0,0 +1,92 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= community_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= root_url %>",
18 + "name": "<%= t("site_name") %>"
19 + }
20 + },{
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= users_url %>",
25 + "name": "<%= t("our_community") %>"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<main>
32 + <div class="row g-5">
33 + <div class="col-md-12">
34 + <h1><%= @page_title %></h1>
35 + </div>
36 + </div>
37 + <div class="row g-5">
38 + <div class="col-sm-3">
39 + <div class="position-sticky" style="top: 1rem;">
40 + <h2 class="mb-3 mt-3">Songs</h2>
41 + <span class="badge bg-warning rounded-pill text-dark"><%= Song.all.size %></span>
42 + &nbsp;<i class="bi bi-graph-up-arrow"></i>
43 + <h2 class="mb-3 mt-3">Lyrics</h2>
44 + <span class="badge bg-warning rounded-pill text-dark"><%= Lyric.all.size %></span>
45 + &nbsp;<i class="bi bi-graph-up-arrow"></i>
46 + <h2 class="mb-3 mt-3">Chords</h2>
47 + <span class="badge bg-warning rounded-pill text-dark"><%= Chord.all.size %></span>
48 + &nbsp;<i class="bi bi-graph-up-arrow"></i>
49 + <h2 class="mb-3 mt-3">Artists</h2>
50 + <span class="badge bg-warning rounded-pill text-dark"><%= Artist.all.size %></span>
51 + &nbsp;<i class="bi bi-graph-up-arrow"></i>
52 + <h2 class="mb-3 mt-3">Albums</h2>
53 + <span class="badge bg-warning rounded-pill text-dark"><%= Album.all.size %></span>
54 + &nbsp;<i class="bi bi-graph-up-arrow"></i>
55 + </div>
56 + </div>
57 + <div class="col-sm-9">
58 + <% @users.each_with_index do |user, index| %>
59 + <% if (index == 0 || index % 3 == 0) %>
60 + <!-- 3 items per row -->
61 + <!-- Begin new row -->
62 + <div class="row mb-3">
63 + <% end %>
64 + <div class="col-4">
65 + <div class="card p-2">
66 + <%= gravatar_for user %>
67 + <div class="card-body">
68 + <h5 class="card-title"><%= user.name %></h5>
69 + <ul class="list-unstyled">
70 + <li>
71 + <%= link_to(
72 + "@#{user.username_or_id}",
73 + profile_path(user.username_or_id),
74 + title: "Author: @#{user.username_or_id}"
75 + ) %>
76 + </li>
77 + </ul>
78 + </div>
79 + </div>
80 + </div>
81 + <% if (index == @users.size - 1 || (index != 0 && (index + 1) % 3 == 0)) %>
82 + <!-- End row -->
83 + </div>
84 + <% end %>
85 + <% end %>
86 + <%= will_paginate(@users, class: 'flickr_pagination') %>
87 + </div>
88 +</div>
89 +<%= render "shared/partial_breadcrumbs" do %>
90 + <li class="breadcrumb-item"><%= link_to t("our_community"), users_path %></li>
91 +<% end %>
92 +</main>
backend/musik88-web/app/views/users/edit.html.erb new
+65
@@ -0,0 +1,65 @@
1 +<% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= request.base_url %>" />
7 +<% end %>
8 +<% content_for :breadcrumb do %>
9 + <script type="application/ld+json">
10 + {
11 + "@context": "http://schema.org",
12 + "@type": "BreadcrumbList",
13 + "itemListElement": [{
14 + "@type": "ListItem",
15 + "position": 1,
16 + "item": {
17 + "@id": "<%= request.base_url %>",
18 + "name": "Musik 88"
19 + }
20 + }, {
21 + "@type": "ListItem",
22 + "position": 2,
23 + "item": {
24 + "@id": "<%= login_url %>",
25 + "name": "Masuk Log | Musik 88"
26 + }
27 + }]
28 + }
29 + </script>
30 +<% end %>
31 +<div class="page-header">
32 + <h1><%= t("reset_password") %></h1>
33 + <%= render "shared/flash_message", object: flash %>
34 +</div>
35 +<div class="row">
36 + <div class="col-md-6">
37 + <%= form_for(:session, url: login_path, :html => { :class => "form-horizontal" }) do |f| %>
38 + <div class="form-group">
39 + <%= f.label t("password_new"), class: "col-sm-3 control-label" %>
40 + <div class="col-sm-9">
41 + <%= f.password_field :password, class: 'form-control' %>
42 + </div>
43 + </div>
44 + <div class="form-group">
45 + <%= f.label t("password_new_confirmation"), class: "col-sm-3 control-label" %>
46 + <div class="col-sm-9">
47 + <%= f.password_field :password_confirmation, class: 'form-control' %>
48 + </div>
49 + </div>
50 + <div class="form-group">
51 + <div class="col-sm-offset-3 col-sm-4">
52 + <%= f.submit t("password_button_update"), class: "btn btn-primary" %>
53 + </div>
54 + </div>
55 + <% end %>
56 + </div>
57 +</div>
58 +<div class="row">
59 + <div class="col-12">
60 + <ol class="breadcrumb">
61 + <li><%= link_to t("breadcrumb_home"), root_path %></li>
62 + <li><%= link_to t("nav_reset_password"), login_path %></li>
63 + </ol>
64 + </div>
65 +</div>
backend/musik88-web/app/views/users/followers.html.erb new
+28
@@ -0,0 +1,28 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('partial_public_profile_user_information') %>
19 + <%= render('partial_profile_followers') %>
20 + <%= render 'shared/partial_breadcrumbs' do %>
21 + <li class="breadcrumb-item">
22 + <%= link_to(
23 + "@#{@user.username_or_name}",
24 + profile_path(@user.username_or_id)
25 + ) %>
26 + </li>
27 + <% end %>
28 +</main>
backend/musik88-web/app/views/users/following.html.erb new
+28
@@ -0,0 +1,28 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('partial_public_profile_user_information') %>
19 + <%= render('partial_profile_following') %>
20 + <%= render 'shared/partial_breadcrumbs' do %>
21 + <li class="breadcrumb-item">
22 + <%= link_to(
23 + "@#{@user.username_or_name}",
24 + profile_path(@user.username_or_id)
25 + ) %>
26 + </li>
27 + <% end %>
28 +</main>
backend/musik88-web/app/views/users/friends.html.erb new
+28
@@ -0,0 +1,28 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('partial_public_profile_user_information') %>
19 + <%= render('partial_public_profile_friends') %>
20 + <%= render 'shared/partial_breadcrumbs' do %>
21 + <li class="breadcrumb-item">
22 + <%= link_to(
23 + "@#{@user.username_or_name}",
24 + profile_path(@user.username_or_id)
25 + ) %>
26 + </li>
27 + <% end %>
28 +</main>
backend/musik88-web/app/views/users/hi.html.erb new
+28
@@ -0,0 +1,28 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('partial_public_profile_user_information') %>
19 +
20 + <%= render 'shared/partial_breadcrumbs' do %>
21 + <li class="breadcrumb-item">
22 + <%= link_to(
23 + "@#{@user.username_or_name}",
24 + profile_path(@user.username_or_id)
25 + ) %>
26 + </li>
27 + <% end %>
28 +</main>
backend/musik88-web/app/views/users/new.html.erb new
+111
@@ -0,0 +1,111 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= signup_url %>",
11 + "name": "<%= t("signup") %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <div class="row">
18 + <div class="col-md-5">
19 + <div class="row mb-3">
20 + <h1><%= t("signup") %></h1>
21 + <% if @provider %>
22 + <p><%= t("signup_omniauth_explanation_provider", provider_name: @provider_name) %></p>
23 + <% else %>
24 + <p><%= t("signup_omniauth_explanation") %></p>
25 + <% end %>
26 + </div>
27 + <%= form_for(@user, url: signup_path(request.query_parameters), :html => { :class => "form-horizontal" }) do |f| %>
28 + <%= render "shared/flash_message" %>
29 + <%= render "shared/error_messages" %>
30 + <% if !@prefilled %>
31 + <div class="row mb-3 text-center">
32 + <% ab_test(:only_spotify_youtube, 'old', 'only_spotify_youtube') do |exp| %>
33 + <%= link_to(
34 + '<i class="bi bi-spotify" style="font-size: 3rem; color: blue;"></i>'.html_safe,
35 + user_spotify_omniauth_authorize_path,
36 + method: :post,
37 + class: exp == 'old' ? "col-2 offset-3" : "col-2 offset-4",
38 + style: "padding: 0px",
39 + title: 'Connect with Spofity'
40 + ) %>
41 + <%= link_to(
42 + '<i class="bi bi-google" style="font-size: 3rem; color: blue;"></i>'.html_safe,
43 + user_google_oauth2_omniauth_authorize_path,
44 + method: :post,
45 + class: 'col-2',
46 + title: 'Connect with YouTube Music'
47 + ) %>
48 + <% if exp == 'old' %>
49 + <%= link_to(
50 + '<i class="bi bi-facebook" style="font-size: 3rem; color: blue;"></i>'.html_safe,
51 + user_facebook_omniauth_authorize_path,
52 + method: :post,
53 + class: 'col-2',
54 + title: 'Connect with Facebook'
55 + ) %>
56 + <% end %>
57 + <% end %>
58 + </div>
59 + <div class="row mb-3">
60 + <hr class="col-5">
61 + <div class="col-2 text-center">
62 + <span><%= t("or") %></span>
63 + </div>
64 + <hr class="col-5">
65 + </div>
66 + <% end %>
67 + <% ab_test(:only_spotify_youtube, 'old', 'only_spotify_youtube') do |exp| %>
68 + <% if exp == 'old' %>
69 + <div class="row mb-3">
70 + <%= f.label t("name"), class: "col-4 col-form-label" %>
71 + <div class="col-8">
72 + <%= f.text_field :name, class: 'form-control', placeholder: 'Optional' %>
73 + </div>
74 + </div>
75 + <div class="row mb-3">
76 + <%= f.label t("email"), class: "col-4 col-form-label" %>
77 + <div class="col-8">
78 + <%= f.email_field :email, class: 'form-control', readonly: @prefilled %>
79 + </div>
80 + </div>
81 + <div class="row mb-3">
82 + <%= f.label t("password"), class: "col-4 col-form-label" %>
83 + <div class="col-8">
84 + <%= f.password_field :password, class: 'form-control' %>
85 + </div>
86 + </div>
87 + <div class="row mb-3">
88 + <%= f.label t("password_confirmation"), class: "col-4 col-form-label" %>
89 + <div class="col-8">
90 + <%= f.password_field :password_confirmation, class: 'form-control' %>
91 + </div>
92 + </div>
93 + <div class="row mb-3">
94 + <div class="offset-4 col-4 mt-2">
95 + <%= f.submit t("create_account"), class: "btn btn-primary" %>
96 + </div>
97 + </div>
98 + <% end %>
99 + <% end %>
100 + <div class="row mb-3">
101 + <div class="offset-4 col-8 mt-3">
102 + <p><%= t("have_account") %> <%= link_to t("login"), login_path(request.query_parameters) %></p>
103 + </div>
104 + </div>
105 + <% end %>
106 + </div>
107 + </div>
108 + <%= render 'shared/partial_breadcrumbs' do %>
109 + <li class="breadcrumb-item"><%= link_to t("signup"), signup_path(request.query_parameters) %></li>
110 + <% end %>
111 +</main>
\ No newline at end of file
backend/musik88-web/app/views/users/omniauth_callbacks/spotify.html.erb new
+1
@@ -0,0 +1 @@
1 +Spotify!
\ No newline at end of file
backend/musik88-web/app/views/users/onboarding.html.erb new
+254
@@ -0,0 +1,254 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= signup_url %>",
11 + "name": "<%= t("signup") %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <div class="row welcome">
18 + <div class="col-md-12 text-center">
19 + <h1 class="text-center"><%= t('welcome_user', user_name: @user.name) %></h1>
20 + <p><%= t('onboarding_explanation') %></p>
21 + <button type="button" class="btn btn-primary mt-3 welcome"><%= t('onboarding_sounds_good') %></button>
22 + </div>
23 + </div>
24 + <div class="row username" style="display: none;">
25 + <div class="col-md-6 offset-md-3 text-center">
26 + <h2 class="mt-3"><%= t('onboarding_username_heading') %></h2>
27 + <div class="username-content">
28 + <%= t('onboarding_username_explanation') %>
29 + <div class="row">
30 + <div class="col-md-6 offset-md-3">
31 + <%= form_for(@user, url: username_path, remote: true, method: :post, :html => { :class => "form-horizontal" }) do |f| %>
32 + <div class="row mb-3">
33 + <div class="col-10 mt-2">
34 + <%= f.text_field :username, class: 'form-control username', autocomplete: 'off' %>
35 + </div>
36 + <div class="col-2 mt-2 status"></div>
37 + </div>
38 + <div class="message"></div>
39 + <%= f.submit t("save"), class: "btn btn-primary", disabled: true %>
40 + <% end %>
41 + </div>
42 + </div>
43 + </div>
44 + </div>
45 + </div>
46 + <div class="row spotify" style="display: none;">
47 + <div class="col-md-6 offset-md-3 text-center">
48 + <h2 class="mt-3"><%= t('onboarding_spotify_heading') %></h2>
49 + <div class="spotify-content">
50 + <%= t('onboarding_spotify_explanation') %>
51 + <div class="row">
52 + <div class="col-md-6 offset-md-3">
53 + <%= link_to(
54 + '<i class="bi bi-spotify" style="font-size: 3rem; color: blue;"></i>'.html_safe,
55 + user_spotify_omniauth_authorize_path(show_dialog: true),
56 + method: :post,
57 + class: 'col-2 spotify-connect',
58 + style: 'padding: 0px',
59 + title: 'Connect with Spofity'
60 + ) %>
61 + </div>
62 + </div>
63 + </div>
64 + </div>
65 + </div>
66 + <div class="row google" style="display: none;">
67 + <div class="col-md-6 offset-md-3 text-center">
68 + <h2 class="mt-3"><%= t('onboarding_google_heading') %></h2>
69 + <div class="google-content">
70 + <%= t('onboarding_google_explanation') %>
71 + <div class="row">
72 + <div class="col-md-6 offset-md-3">
73 + <%= link_to(
74 + '<i class="bi bi-youtube" style="font-size: 3rem; color: blue;"></i>'.html_safe,
75 + user_google_oauth2_omniauth_authorize_path(show_dialog: true),
76 + method: :post,
77 + class: 'col-2 google-connect',
78 + style: 'padding: 0px',
79 + title: 'Connect with Youtube'
80 + ) %>
81 + </div>
82 + </div>
83 + </div>
84 + </div>
85 + </div>
86 + <div class="row done" style="display: none;">
87 + <div class="col-md-12 text-center">
88 + <h1 class="text-center"><%= t('onboarding_done', user_name: @user.name) %></h1>
89 + <p><%= t('onboarding_explanation_done') %></p>
90 + <%= link_to(t('onboarding_done_button'), root_path, class: 'btn btn-primary') %>
91 + </div>
92 + </div>
93 + <%= render 'shared/partial_breadcrumbs' do %>
94 + <li class="breadcrumb-item"><%= link_to t("nav_onboarding"), onboarding_path %></li>
95 + <% end %>
96 +</main>
97 +<script>
98 + $(document).on('turbolinks:load', function() {
99 +
100 + let currentStepElement = $('.welcome');
101 +
102 + function post(url, params, successCompletion) {
103 + $.ajax({
104 + type: 'POST',
105 + beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
106 + url: url,
107 + data: params,
108 + success: function(res) {
109 + successCompletion(res)
110 + }
111 + })
112 + }
113 +
114 + function fadeInNextStep(nextStep) {
115 + $('.' + nextStep).fadeIn('slow');
116 + currentStepElement = $('.' + nextStep);
117 + }
118 +
119 + function updateProgress() {
120 + let currentValue = parseFloat($('.progress-bar').attr('aria-valuenow'));
121 + //console.log(currentValue)
122 + if (currentValue < 95) {
123 + let nextValue = currentValue + 5;
124 + //console.log(nextValue)
125 + $('.progress-bar')
126 + .css({'width': nextValue + '%'})
127 + .attr('aria-valuenow', nextValue);;
128 + }
129 + }
130 +
131 + $('button.welcome').click(function() {
132 + let params = { onboarding_action: 'next' }
133 + post('<%= onboarding_action_path %>', params, function(res) {
134 + currentStepElement.fadeOut('slow', fadeInNextStep(res.next_step));
135 + })
136 + });
137 +
138 + $('input.username').on('input', function(e) {
139 + $('div.status').html('<%= t('onboarding_username_checking') %>')
140 + const url = "<%= username_check_path %>?u=" + $(this).val()
141 + $.ajax({
142 + type: "GET",
143 + url: url,
144 + success: function(res) {
145 + if (res.valid) {
146 + $('div.status').html('<i class="bi bi-check2-square" style="color: green;"></i>')
147 + $('div.message').removeClass('alert alert-danger')
148 + $('div.message').addClass('alert alert-success')
149 + } else {
150 + $('div.status').html('<i class="bi bi-x-square-fill" style="color: red;"></i>')
151 + $('div.message').removeClass('alert alert-success')
152 + $('div.message').addClass('alert alert-danger')
153 + }
154 + $('input.btn-primary').prop('disabled', !res.valid)
155 + $('div.message').text(res.message)
156 + }
157 + })
158 + })
159 +
160 + $('.username').on('click', 'button.username', function() {
161 + let params = { onboarding_action: 'report_complete', type: 'username' }
162 + post('<%= onboarding_action_path %>', params, function(res) {
163 + $('.username').fadeOut('slow', fadeInNextStep(res.next_step));
164 + })
165 + });
166 +
167 + // Remember that we need to query Spotify data
168 + $('.spotify-connect').click(function() {
169 + sessionStorage.setItem('should_request_spotify_data', true);
170 + return true;
171 + })
172 +
173 + function spotifyExportDataDone() {
174 + $(this).replaceWith('<%= j render 'partial_onboarding_spotify_done' %>');
175 + $('.spotify-content').fadeIn('slow');
176 + }
177 +
178 + $('.spotify').on('click', 'button.spotify', function() {
179 + let params = { onboarding_action: 'report_complete', type: 'spotify' }
180 + post('<%= onboarding_action_path %>', params, function(res) {
181 + $('.spotify').fadeOut('slow', fadeInNextStep(res.next_step));
182 + })
183 + });
184 +
185 + function spotifyExportData() {
186 + $('.spotify').fadeIn('slow');
187 + $('.spotify-content').fadeOut("slow", function() {
188 + $(this).replaceWith('<%= j render 'partial_onboarding_spotify_progress_bar' %>').hide();
189 +
190 + $('.spotify-export-progress').hide();
191 +
192 + $('.spotify-content').fadeIn('slow', function() {
193 + $('.spotify-export-progress').show();
194 +
195 + let refreshIntervalId = setInterval(updateProgress, 1000);
196 +
197 + post('<%= onboarding_export_spotify_path %>', {}, function(res) {
198 + clearInterval(refreshIntervalId);
199 + $('.progress-bar').css('width', '100%');
200 + $('.spotify-content').fadeOut("slow", spotifyExportDataDone)
201 + })
202 + });
203 + });
204 + }
205 +
206 + if (sessionStorage.getItem('should_request_spotify_data')) {
207 + sessionStorage.removeItem('should_request_spotify_data')
208 + $('.welcome').fadeOut('slow', spotifyExportData);
209 + }
210 +
211 + // Youtube stuff
212 + // Remember that we need to query YouTube data
213 + $('.google-connect').click(function() {
214 + sessionStorage.setItem('should_request_youtube_data', true);
215 + return true;
216 + })
217 +
218 + $('.google').on('click', 'button.google', function() {
219 + let params = { onboarding_action: 'report_complete', type: 'google' }
220 + post('<%= onboarding_action_path %>', params, function(res) {
221 + $('.google').fadeOut('slow', fadeInNextStep(res.next_step));
222 + })
223 + });
224 +
225 + function youtubeExportDataDone() {
226 + $(this).replaceWith('<%= j render 'partial_onboarding_google_done' %>');
227 + $('.google-content').fadeIn('slow');
228 + }
229 +
230 + function youtubeExportData() {
231 + $('.google').fadeIn('slow');
232 + $('.google-content').fadeOut('slow', function() {
233 + $(this).replaceWith('<%= j render 'partial_onboarding_google_progress_bar' %>').hide();
234 + $('.google-export-progress').hide();
235 + $(this).fadeIn('slow', function() {
236 + $('.google-export-progress').show();
237 +
238 + let refreshIntervalId = setInterval(updateProgress, 1000);
239 +
240 + post('<%= onboarding_export_youtube_path %>', {}, function(res) {
241 + clearInterval(refreshIntervalId);
242 + $('.progress-bar').css('width', '100%');
243 + $('.google-content').fadeOut("slow", youtubeExportDataDone);
244 + })
245 + });
246 + });
247 + }
248 +
249 + if (sessionStorage.getItem('should_request_youtube_data')) {
250 + sessionStorage.removeItem('should_request_youtube_data')
251 + $('.welcome').fadeOut('slow', youtubeExportData);
252 + }
253 + })
254 +</script>
backend/musik88-web/app/views/users/settings.erb new
+27
@@ -0,0 +1,27 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('template', page: 'protected_settings') %>
19 + <%= render 'shared/partial_breadcrumbs' do %>
20 + <li class="breadcrumb-item">
21 + <%= link_to(
22 + "@#{@user.username_or_name}",
23 + profile_path(@user.username_or_id)
24 + ) %>
25 + </li>
26 + <% end %>
27 +</main>
backend/musik88-web/app/views/users/show.html.erb new
+27
@@ -0,0 +1,27 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('template', page: 'partial_public_profile') %>
19 + <%= render 'shared/partial_breadcrumbs' do %>
20 + <li class="breadcrumb-item">
21 + <%= link_to(
22 + "@#{@user.username_or_name}",
23 + profile_path(@user.username_or_id)
24 + ) %>
25 + </li>
26 + <% end %>
27 +</main>
backend/musik88-web/app/views/users/show_reputation.html.erb new
+27
@@ -0,0 +1,27 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("user_profile") %> <%= @user.name %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <%= render('shared/flash_message', object: flash) %>
18 + <%= render('template', page: 'partial_public_profile_reputation') %>
19 + <%= render 'shared/partial_breadcrumbs' do %>
20 + <li class="breadcrumb-item">
21 + <%= link_to(
22 + "@#{@user.username_or_name}",
23 + profile_path(@user.username_or_id)
24 + ) %>
25 + </li>
26 + <% end %>
27 +</main>
backend/musik88-web/app/views/users/username.js.erb new
+4
@@ -0,0 +1,4 @@
1 +$('.username-content').fadeOut('slow', function() {
2 + $(this).replaceWith('<%= j render 'partial_username_content' %>');
3 + $('.username-content').fadeIn('slow');
4 +});
\ No newline at end of file
backend/musik88-web/app/views/welcome/_main.html.erb new
+2
@@ -0,0 +1,2 @@
1 +<h1><%= t('home_headline') %></h1>
2 +<%= render('search') %>
\ No newline at end of file
backend/musik88-web/app/views/welcome/_search.html.erb new
+64
@@ -0,0 +1,64 @@
1 +<h2 class='fst-italic fw-light'><%= t('search') %></h2>
2 +<div class="row g-5">
3 + <div class="col-8">
4 + <%= form_with url: search_path, method: :get, html: { :class=> "row g-3" } do |form| %>
5 + <div class="col-12">
6 + <%= form.text_field :s, class: 'form-control search', autocomplete: 'off', placeholder: t('search_placeholder_title') %>
7 + <div class="list-group result-list" style="display:none"></div>
8 + </div>
9 + <% end %>
10 + </div>
11 + <div class="col-4">
12 + <%= link_to("<span class='badge bg-warning text-dark'>#{t('nav_chords').downcase}</span>".html_safe, guitar_chords_path).html_safe %>
13 + <%= link_to("<span class='badge bg-warning text-dark'>#{t('nav_javanese_gending_notation').downcase}</span>".html_safe, javanese_gending_gamelan_notation_path).html_safe %>
14 + </div>
15 +</div>
16 +
17 +<script>
18 + $(document).on('turbolinks:load', function() {
19 + $('input.search').on('input',function(e) {
20 +
21 + if ($(this).val().length < 3)
22 + return
23 +
24 + const url = "<%= api_v1_search_path %>?q=" + $(this).val() + "&client=web_beta&locale=<%= I18n.locale %>"
25 + $.ajax({
26 + type: "GET",
27 + url: url,
28 + success: function(res) {
29 + var ins = ""
30 + for (var i = 0; i < res.results.length; i++) {
31 + const item = res.results[i]
32 + ins += `<div
33 + class="item list-group-item list-group-item-action flex-column align-items-start"
34 + data-path="${item.path}" data-name="${item.name}"
35 + style="cursor: pointer;">`
36 + ins += "<div class='d-flex w-100 justify-content-between'>"
37 + ins += "<h5 class='mb-1'>" + item.name + "</h5>"
38 + ins += "<small class='text-muted fst-italic fw-light'>" + item.type + "</small>"
39 + ins += "</div>"
40 + ins += "</div>"
41 + };
42 + $('.result-list').html(ins).show();
43 + }
44 + })
45 + })
46 + $('body').on('click', 'div.item', function() {
47 +
48 + const destination = $(this).attr('data-path')
49 +
50 + function redirectToPath(res) {
51 + // console.log(res)
52 + window.location.href = destination
53 + }
54 +
55 + $.ajax({
56 + type: 'POST',
57 + url: '<%= api_v1_search_path %>',
58 + data: 'term=' + $(this).attr('data-name'),
59 + success: redirectToPath,
60 + error: redirectToPath
61 + })
62 + });
63 + })
64 +</script>
backend/musik88-web/app/views/welcome/api.html.erb new
+4
@@ -0,0 +1,4 @@
1 +Hello, API!
2 +<br/>
3 +<br/>
4 +Check out our <a href="https://stackshare.io/musik88/musik88-web" target="__blank">stack</a>.
\ No newline at end of file
backend/musik88-web/app/views/welcome/index.html.erb new
+12
@@ -0,0 +1,12 @@
1 + <% content_for :meta_head do %>
2 + <title><%= @meta_title %></title>
3 + <meta name="description" content="<%= @meta_description %>">
4 + <meta property="og:title" content="<%= @meta_title %>" />
5 + <meta property="og:description" content="<%= @meta_description %>" />
6 + <meta property="og:url" content="<%= @meta_url %>" />
7 +<% end %>
8 +<main>
9 + <%= render "shared/flash_message", object: flash %>
10 + <%= render(@template) %>
11 + <%= render('shared/partial_breadcrumbs') %>
12 +</main>
backend/musik88-web/app/views/welcome/search.html.erb new
+43
@@ -0,0 +1,43 @@
1 +<% content_for :meta_head do %>
2 + <%= render('shared/partial_meta_tag', title: @meta_title, description: @meta_description) %>
3 +<% end %>
4 +<% content_for :breadcrumb do %>
5 + <%= render 'shared/partial_breadcrumbs_schema' do %>
6 + {
7 + "@type": "ListItem",
8 + "position": 2,
9 + "item": {
10 + "@id": "<%= users_url %>",
11 + "name": "<%= t("search") %>"
12 + }
13 + }
14 + <% end %>
15 +<% end %>
16 +<main>
17 + <div class="row g-5 mb-3">
18 + <div class="col-md-12">
19 + <%= render('search') %>
20 + </div>
21 + </div>
22 + <div class="row g-5">
23 + <div class="col-12">
24 + <%= t('search_result_for', keywords: @keywords).html_safe %>
25 +
26 + <% @results.each do |result| %>
27 + <div class="row g-5">
28 + <div class="col-12">
29 + <%= link_to(result['name'], result['path']) %>
30 + </div>
31 + </div>
32 + <% end unless @results.nil? %>
33 + </div>
34 + </div>
35 + <%= render 'shared/partial_breadcrumbs' do %>
36 + <li class="breadcrumb-item">
37 + <%= link_to(
38 + t('search'),
39 + search_path
40 + ) %>
41 + </li>
42 + <% end %>
43 +</main>
\ No newline at end of file
backend/musik88-web/audio-analysis/.gitignore new
+1
@@ -0,0 +1 @@
1 +__pycache__
\ No newline at end of file
backend/musik88-web/audio-analysis/.python-version new
+1
@@ -0,0 +1 @@
1 +3.11
backend/musik88-web/audio-analysis/README.md new
+6
@@ -0,0 +1,6 @@
1 +# Audio length
2 +
3 +```python
4 +$ python audio_length.py "/Users/setoelka/.sfai-dev/audio/359/bass-Naif - Air & Api (Official Lyric Video).wav"
5 +```
6 +
backend/musik88-web/audio-analysis/audio_file.py new
+31
@@ -0,0 +1,31 @@
1 +import librosa
2 +
3 +class AudioFile:
4 +
5 + def __init__(self, path, decimal_places=8):
6 + self.path = path
7 + self.decimal_places = decimal_places
8 +
9 + def duration(self):
10 + duration = self.__get_duration()
11 + return round(duration, self.decimal_places)
12 +
13 + def duration_pretty(self):
14 + duration = self.__get_duration()
15 + m, s = divmod(duration, 60)
16 + return str(round(m, self.decimal_places)) + ':' + str(round(s, self.decimal_places))
17 +
18 + def samplerate(self):
19 + y, sr = librosa.load(self.path)
20 + return sr
21 +
22 + def onset_detect(self):
23 + y, sr = librosa.load(self.path)
24 + onsets = librosa.onset.onset_detect(y=y, sr=sr, units='time')
25 + return onsets
26 +
27 + # Private methods
28 + def __get_duration(self):
29 + y, sr = librosa.load(self.path)
30 + duration = librosa.get_duration(y=y, sr=sr)
31 + return duration
\ No newline at end of file
backend/musik88-web/audio-analysis/audio_length.py new
+8
@@ -0,0 +1,8 @@
1 +#!/usr/bin/env python3
2 +
3 +import sys
4 +from audio_file import AudioFile
5 +
6 +filepath = sys.argv[1]
7 +audiofile = AudioFile(filepath)
8 +print(str(audiofile.duration()))
\ No newline at end of file
backend/musik88-web/audio-analysis/onset.py new
+8
@@ -0,0 +1,8 @@
1 +#!/usr/bin/env python3
2 +
3 +import sys
4 +from audio_file import AudioFile
5 +
6 +filepath = sys.argv[1]
7 +audiofile = AudioFile(filepath, 3)
8 +print(str(audiofile.onset_detect()))
\ No newline at end of file
backend/musik88-web/babel.config.js new
+82
@@ -0,0 +1,82 @@
1 +module.exports = function(api) {
2 + var validEnv = ['development', 'test', 'production']
3 + var currentEnv = api.env()
4 + var isDevelopmentEnv = api.env('development')
5 + var isProductionEnv = api.env('production')
6 + var isTestEnv = api.env('test')
7 +
8 + if (!validEnv.includes(currentEnv)) {
9 + throw new Error(
10 + 'Please specify a valid `NODE_ENV` or ' +
11 + '`BABEL_ENV` environment variables. Valid values are "development", ' +
12 + '"test", and "production". Instead, received: ' +
13 + JSON.stringify(currentEnv) +
14 + '.'
15 + )
16 + }
17 +
18 + return {
19 + presets: [
20 + isTestEnv && [
21 + '@babel/preset-env',
22 + {
23 + targets: {
24 + node: 'current'
25 + }
26 + }
27 + ],
28 + (isProductionEnv || isDevelopmentEnv) && [
29 + '@babel/preset-env',
30 + {
31 + forceAllTransforms: true,
32 + useBuiltIns: 'entry',
33 + corejs: 3,
34 + modules: false,
35 + exclude: ['transform-typeof-symbol']
36 + }
37 + ]
38 + ].filter(Boolean),
39 + plugins: [
40 + 'babel-plugin-macros',
41 + '@babel/plugin-syntax-dynamic-import',
42 + isTestEnv && 'babel-plugin-dynamic-import-node',
43 + '@babel/plugin-transform-destructuring',
44 + [
45 + '@babel/plugin-proposal-class-properties',
46 + {
47 + loose: true
48 + }
49 + ],
50 + [
51 + '@babel/plugin-proposal-object-rest-spread',
52 + {
53 + useBuiltIns: true
54 + }
55 + ],
56 + [
57 + '@babel/plugin-proposal-private-methods',
58 + {
59 + loose: true
60 + }
61 + ],
62 + [
63 + '@babel/plugin-proposal-private-property-in-object',
64 + {
65 + loose: true
66 + }
67 + ],
68 + [
69 + '@babel/plugin-transform-runtime',
70 + {
71 + helpers: false
72 + }
73 + ],
74 + [
75 + '@babel/plugin-transform-regenerator',
76 + {
77 + async: false
78 + }
79 + ]
80 + ].filter(Boolean)
81 + }
82 +}
backend/musik88-web/bin/bundle new
+3
@@ -0,0 +1,3 @@
1 +#!/usr/bin/env ruby
2 +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 +load Gem.bin_path('bundler', 'bundle')
backend/musik88-web/bin/rails new
+4
@@ -0,0 +1,4 @@
1 +#!/usr/bin/env ruby
2 +APP_PATH = File.expand_path('../../config/application', __FILE__)
3 +require_relative '../config/boot'
4 +require 'rails/commands'
backend/musik88-web/bin/rake new
+4
@@ -0,0 +1,4 @@
1 +#!/usr/bin/env ruby
2 +require_relative '../config/boot'
3 +require 'rake'
4 +Rake.application.run
backend/musik88-web/bin/webpack new
+18
@@ -0,0 +1,18 @@
1 +#!/usr/bin/env ruby
2 +
3 +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 +ENV["NODE_ENV"] ||= "development"
5 +
6 +require "pathname"
7 +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 + Pathname.new(__FILE__).realpath)
9 +
10 +require "bundler/setup"
11 +
12 +require "webpacker"
13 +require "webpacker/webpack_runner"
14 +
15 +APP_ROOT = File.expand_path("..", __dir__)
16 +Dir.chdir(APP_ROOT) do
17 + Webpacker::WebpackRunner.run(ARGV)
18 +end
backend/musik88-web/bin/webpack-dev-server new
+18
@@ -0,0 +1,18 @@
1 +#!/usr/bin/env ruby
2 +
3 +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 +ENV["NODE_ENV"] ||= "development"
5 +
6 +require "pathname"
7 +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 + Pathname.new(__FILE__).realpath)
9 +
10 +require "bundler/setup"
11 +
12 +require "webpacker"
13 +require "webpacker/dev_server_runner"
14 +
15 +APP_ROOT = File.expand_path("..", __dir__)
16 +Dir.chdir(APP_ROOT) do
17 + Webpacker::DevServerRunner.run(ARGV)
18 +end
backend/musik88-web/config.ru new
+4
@@ -0,0 +1,4 @@
1 +# This file is used by Rack-based servers to start the application.
2 +
3 +require ::File.expand_path('config/environment', __dir__)
4 +run Rails.application
backend/musik88-web/config/application.rb new
+23
@@ -0,0 +1,23 @@
1 +require File.expand_path('boot', __dir__)
2 +
3 +require 'rails/all'
4 +
5 +# Require the gems listed in Gemfile, including any gems
6 +# you've limited to :test, :development, or :production.
7 +Bundler.require(*Rails.groups)
8 +
9 +module Musik88
10 + class Application < Rails::Application
11 + # Settings in config/environments/* take precedence over those specified here.
12 + # Application configuration should go into files in config/initializers
13 + # -- all .rb files in that directory are automatically loaded.
14 +
15 + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
16 + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
17 + # config.time_zone = 'Central Time (US & Canada)'
18 +
19 + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
20 + config.generators.javascript_engine = :js
21 + config.exceptions_app = routes
22 + end
23 +end
backend/musik88-web/config/boot.rb new
+7
@@ -0,0 +1,7 @@
1 + # make the ExecJs use NodeJs
2 + ENV['EXECJS_RUNTIME'] = 'Node'
3 +
4 +# Set up gems listed in the Gemfile.
5 +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
6 +
7 +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
backend/musik88-web/config/cable.yml new
+7
@@ -0,0 +1,7 @@
1 +redis: &redis
2 + adapter: redis
3 + url: redis://localhost:6379/1
4 +
5 +production: *redis
6 +development: *redis
7 +test: *redis
\ No newline at end of file
backend/musik88-web/config/credentials.yml.enc new
+1
@@ -0,0 +1 @@
1 +eilJ8coXPdYy7ucihCLEVs6dsRyCrs1tJ3KCKud8uu/zLaEyuU1izfpZMb+Iv1iR/9PiPsGZBCzZZabo1i8prPbyTfMIgHGy0QFVsnU36/6w2otVw6LS3hxyrvC9h/kJlf43HNn24WxgAhGb9c6rNfVYpp3o5ess+IkJZoCn0KdaVOaUHuhSMxbVpS7XRsc4VvlHVHxFTkWDN3FnaTT26PsW0kAfzMk935mGAweiu3KM5Pdu183BR817Gk4QWPNZ7WTcsfST8vbId8YljNFeM4+YeLL01ub2fzc+ESHiRtJY7UL7baAGxYd3d7g51jkvj5h/dcZaoWX/44SCJcxSsHy6Xusu6Hgl6qLrObU6vywmtxUyuMQ8mvrwuYp9R/FigzFiaTA/HxH7aEW47ehN4lUVe4GIApAChZivC+pRapFwSJSh3ud9r6+G76//QyhSN5YwEmQJo3Lc6DZiOh3CmJGvBOmPJtVaDQKw5/xiLxTSLWFLHt094eS5sMmNZiAEED+MYsHFv4Z8SlJNLSfT2yjm/RD4vqdN/l13iLXFC6xGPbafgaDA1c+hOnpPfc+Y2y+rOXTOl4P8KuJrcrLU3Zwca4/BClCs/DXwcGVIYHWaQEnyzuAjxltaN2S2dEBRbB3M+xEuETaWDY0UT+EX5GeRhKdonkTl0h7anSCVk0hb+iT/XtX5WWRfsrmBFZkg+xVI--Nek6x3fyZehAYYCC--3LR5pjfIvPia7DeWBGrvKg==
\ No newline at end of file
backend/musik88-web/config/database.yml new
+85
@@ -0,0 +1,85 @@
1 +# PostgreSQL. Versions 8.2 and up are supported.
2 +#
3 +# Install the pg driver:
4 +# gem install pg
5 +# On OS X with Homebrew:
6 +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7 +# On OS X with MacPorts:
8 +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9 +# On Windows:
10 +# gem install pg
11 +# Choose the win32 build.
12 +# Install PostgreSQL and put its /bin directory on your path.
13 +#
14 +# Configure Using Gemfile
15 +# gem 'pg'
16 +#
17 +default: &default
18 + adapter: postgresql
19 + encoding: unicode
20 + # For details on connection pooling, see rails configuration guide
21 + # http://guides.rubyonrails.org/configuring.html#database-pooling
22 + pool: 5
23 +
24 +development:
25 + <<: *default
26 + database: musik88_com_development
27 +
28 + # The specified database role being used to connect to postgres.
29 + # To create additional roles in postgres see `$ createuser --help`.
30 + # When left blank, postgres will use the default role. This is
31 + # the same name as the operating system user that initialized the database.
32 + username: postgres
33 +
34 + # The password associated with the postgres role (username).
35 + password: 'thisisjustdevpassword'
36 +
37 + # Connect on a TCP socket. Omitted by default since the client uses a
38 + # domain socket that doesn't need configuration. Windows does not have
39 + # domain sockets, so uncomment these lines.
40 + host: localhost
41 +
42 + # The TCP port the server listens on. Defaults to 5432.
43 + # If your server runs on a different port number, change accordingly.
44 + #port: 5432
45 +
46 + # Schema search path. The server defaults to $user,public
47 + #schema_search_path: myapp,sharedapp,public
48 +
49 + # Minimum log levels, in increasing order:
50 + # debug5, debug4, debug3, debug2, debug1,
51 + # log, notice, warning, error, fatal, and panic
52 + # Defaults to warning.
53 + #min_messages: notice
54 +
55 +# Warning: The database defined as "test" will be erased and
56 +# re-generated from your development database when you run "rake".
57 +# Do not set this db to the same as development or production.
58 +test:
59 + <<: *default
60 + database: musik88_com_test
61 +
62 +# As with config/secrets.yml, you never want to store sensitive information,
63 +# like your database password, in your source code. If your source code is
64 +# ever seen by anyone, they now have access to your database.
65 +#
66 +# Instead, provide the password as a unix environment variable when you boot
67 +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
68 +# for a full rundown on how to provide these environment variables in a
69 +# production deployment.
70 +#
71 +# On Heroku and other platform providers, you may have a full connection URL
72 +# available as an environment variable. For example:
73 +#
74 +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
75 +#
76 +# You can use this database configuration with:
77 +#
78 +# production:
79 +# url: <%= ENV['DATABASE_URL'] %>
80 +#
81 +production:
82 + <<: *default
83 + database: musik88_com_production
84 + username: musik88
85 + password: <%= ENV['MUSIK88_COM_DATABASE_PASSWORD'] %>
backend/musik88-web/config/environment.rb new
+5
@@ -0,0 +1,5 @@
1 +# Load the Rails application.
2 +require File.expand_path('../application', __FILE__)
3 +
4 +# Initialize the Rails application.
5 +Rails.application.initialize!
backend/musik88-web/config/environments/development.rb new
+63
@@ -0,0 +1,63 @@
1 +Rails.application.configure do
2 + # Settings specified here will take precedence over those in config/application.rb.
3 +
4 + # In the development environment your application's code is reloaded on
5 + # every request. This slows down response time but is perfect for development
6 + # since you don't have to restart the web server when you make code changes.
7 + config.cache_classes = false
8 +
9 + # Do not eager load code on boot.
10 + config.eager_load = false
11 +
12 + # Show full error reports and disable caching.
13 + config.consider_all_requests_local = true
14 + config.action_controller.perform_caching = false
15 +
16 + # Don't care if the mailer can't send.
17 + config.action_mailer.raise_delivery_errors = false
18 +
19 + # Print deprecation notices to the Rails logger.
20 + config.active_support.deprecation = :log
21 +
22 + # Raise an error on page load if there are pending migrations.
23 + config.active_record.migration_error = :page_load
24 +
25 + # Debug mode disables concatenation and preprocessing of assets.
26 + # This option may cause significant delays in view rendering with a large
27 + # number of complex assets.
28 + config.assets.debug = true
29 +
30 + # Adds additional error checking when serving assets at runtime.
31 + # Checks for improperly declared sprockets dependencies.
32 + # Raises helpful error messages.
33 + config.assets.raise_runtime_errors = true
34 +
35 + # Raises error for missing translations
36 + # config.action_view.raise_on_missing_translations = true
37 +
38 + config.hosts = [
39 + 'localhost',
40 + '0.0.0.0',
41 + '127.0.0.1', # Puma
42 + '65.21.240.91',
43 + '65.21.240.91',
44 + 'musik88.comlocal',
45 + 'api.musik88.comlocal',
46 + 'lvh.me',
47 + 'api.lvh.me'
48 + ]
49 +
50 + # Mailer
51 + config.action_mailer.delivery_method = :letter_opener
52 + config.action_mailer.perform_deliveries = true
53 + config.action_mailer.default_url_options = { host: 'https://localhost:3001' }
54 + # END
55 +
56 + # Storage
57 + config.active_storage.service = :amazon_dev
58 + # END
59 +
60 + # Background job
61 + config.active_job.queue_adapter = :sidekiq
62 + # END
63 +end
backend/musik88-web/config/environments/production.rb new
+114
@@ -0,0 +1,114 @@
1 +Rails.application.configure do
2 + # Settings specified here will take precedence over those in config/application.rb.
3 +
4 + # Code is not reloaded between requests.
5 + config.cache_classes = true
6 +
7 + # Eager load code on boot. This eager loads most of Rails and
8 + # your application in memory, allowing both threaded web servers
9 + # and those relying on copy on write to perform better.
10 + # Rake tasks automatically ignore this option for performance.
11 + config.eager_load = true
12 +
13 + # Full error reports are disabled and caching is turned on.
14 + config.consider_all_requests_local = false
15 + config.action_controller.perform_caching = true
16 +
17 + # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 + # Add `rack-cache` to your Gemfile before enabling this.
19 + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 + # config.action_dispatch.rack_cache = true
21 +
22 + # Disable Rails's static asset server (Apache or nginx will already do this).
23 + config.serve_static_assets = false
24 +
25 + # Compress JavaScripts and CSS.
26 + config.assets.js_compressor = Uglifier.new(harmony: true)
27 + # config.assets.css_compressor = :sass
28 +
29 + # Do not fallback to assets pipeline if a precompiled asset is missed.
30 + config.assets.compile = false
31 +
32 + # Generate digests for assets URLs.
33 + config.assets.digest = true
34 +
35 + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
36 +
37 + # Specifies the header that your server uses for sending files.
38 + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
39 + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
40 +
41 + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
42 + config.force_ssl = true
43 +
44 + # Set to :debug to see everything in the log.
45 + config.log_level = :info
46 +
47 + # Prepend all log lines with the following tags.
48 + # config.log_tags = [ :subdomain, :uuid ]
49 +
50 + # Use a different logger for distributed setups.
51 + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
52 +
53 + # Use a different cache store in production.
54 + # config.cache_store = :mem_cache_store
55 +
56 + # Enable serving of images, stylesheets, and JavaScripts from an asset server.
57 + # config.action_controller.asset_host = "http://assets.example.com"
58 +
59 + # Ignore bad email addresses and do not raise email delivery errors.
60 + # Set this to true and configure the email server for immediate delivery to raise delivery errors.
61 + # config.action_mailer.raise_delivery_errors = false
62 +
63 + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
64 + # the I18n.default_locale when a translation cannot be found).
65 + config.i18n.fallbacks = true
66 +
67 + # Send deprecation notices to registered listeners.
68 + config.active_support.deprecation = :notify
69 +
70 + # Disable automatic flushing of the log to improve performance.
71 + # config.autoflush_log = false
72 +
73 + # Use default logging formatter so that PID and timestamp are not suppressed.
74 + config.log_formatter = ::Logger::Formatter.new
75 +
76 + # Do not dump schema after migrations.
77 + config.active_record.dump_schema_after_migration = false
78 +
79 + config.hosts = [
80 + 'musik88-staging.herokuapp.com', # Default staging domain from heroku
81 + 'musik88.herokuapp.com', # Default production domain from heroku
82 + 'staging.musik88.com',
83 + 'www.musik88.com',
84 + 'musik88.com',
85 + 'staging.musik88.id',
86 + 'www.musik88.id',
87 + 'staging.musik88.se',
88 + 'www.musik88.se',
89 + '65.21.240.91',
90 + 'api.musik88.com'
91 + ]
92 +
93 + config.action_mailer.default_url_options = { host: 'musik88.com' }
94 + ActionMailer::Base.smtp_settings = {
95 + user_name: 'apikey',
96 + password: ENV['SENDGRID_API_KEY'],
97 + domain: 'musik88.com',
98 + address: 'smtp.sendgrid.net',
99 + port: 587,
100 + authentication: :plain,
101 + enable_starttls_auto: true
102 + }
103 +
104 + # Storage
105 + config.active_storage.service = :amazon
106 + # END
107 +
108 + # Background job
109 + config.active_job.queue_adapter = :sidekiq
110 + # END
111 +
112 + config.action_cable.url = 'wss://musik88.com/cable'
113 + config.action_cable.allowed_request_origins = ['https://musik88.com', 'https://splitfire.ai', 'https://beta.splitfire.ai']
114 +end
backend/musik88-web/config/environments/test.rb new
+44
@@ -0,0 +1,44 @@
1 +Rails.application.configure do
2 + # Settings specified here will take precedence over those in config/application.rb.
3 +
4 + # The test environment is used exclusively to run your application's
5 + # test suite. You never need to work with it otherwise. Remember that
6 + # your test database is "scratch space" for the test suite and is wiped
7 + # and recreated between test runs. Don't rely on the data there!
8 + config.cache_classes = true
9 +
10 + # Do not eager load code on boot. This avoids loading your whole application
11 + # just for the purpose of running a single test. If you are using a tool that
12 + # preloads Rails for running tests, you may have to set it to true.
13 + config.eager_load = false
14 +
15 + # Configure static asset server for tests with Cache-Control for performance.
16 + config.serve_static_assets = true
17 + config.static_cache_control = 'public, max-age=3600'
18 +
19 + # Show full error reports and disable caching.
20 + config.consider_all_requests_local = true
21 + config.action_controller.perform_caching = false
22 +
23 + # Raise exceptions instead of rendering exception templates.
24 + config.action_dispatch.show_exceptions = false
25 +
26 + # Disable request forgery protection in test environment.
27 + config.action_controller.allow_forgery_protection = false
28 +
29 + # Tell Action Mailer not to deliver emails to the real world.
30 + # The :test delivery method accumulates sent emails in the
31 + # ActionMailer::Base.deliveries array.
32 + config.action_mailer.delivery_method = :test
33 +
34 + # Print deprecation notices to the stderr.
35 + config.active_support.deprecation = :stderr
36 +
37 + # Raises error for missing translations
38 + # config.action_view.raise_on_missing_translations = true
39 + config.action_mailer.default_url_options = { :host => "test.musik88.com" }
40 +
41 + # Store uploaded files on the local file system in a temporary directory.
42 + config.active_storage.service = :test
43 +
44 +end
backend/musik88-web/config/initializers/active_admin.rb new
+335
@@ -0,0 +1,335 @@
1 +ActiveAdmin.setup do |config|
2 + # == Site Title
3 + #
4 + # Set the title that is displayed on the main layout
5 + # for each of the active admin pages.
6 + #
7 + config.site_title = 'Musik88'
8 +
9 + # Set the link url for the title. For example, to take
10 + # users to your main site. Defaults to no link.
11 + #
12 + config.site_title_link = '/'
13 +
14 + # Set an optional image to be displayed for the header
15 + # instead of a string (overrides :site_title)
16 + #
17 + # Note: Aim for an image that's 21px high so it fits in the header.
18 + #
19 + # config.site_title_image = "logo.png"
20 +
21 + # == Default Namespace
22 + #
23 + # Set the default namespace each administration resource
24 + # will be added to.
25 + #
26 + # eg:
27 + # config.default_namespace = :hello_world
28 + #
29 + # This will create resources in the HelloWorld module and
30 + # will namespace routes to /hello_world/*
31 + #
32 + # To set no namespace by default, use:
33 + # config.default_namespace = false
34 + #
35 + # Default:
36 + # config.default_namespace = :admin
37 + #
38 + # You can customize the settings for each namespace by using
39 + # a namespace block. For example, to change the site title
40 + # within a namespace:
41 + #
42 + # config.namespace :admin do |admin|
43 + # admin.site_title = "Custom Admin Title"
44 + # end
45 + #
46 + # This will ONLY change the title for the admin section. Other
47 + # namespaces will continue to use the main "site_title" configuration.
48 +
49 + # == User Authentication
50 + #
51 + # Active Admin will automatically call an authentication
52 + # method in a before filter of all controller actions to
53 + # ensure that there is a currently logged in admin user.
54 + #
55 + # This setting changes the method which Active Admin calls
56 + # within the application controller.
57 + config.authentication_method = :authenticate_user!
58 +
59 + # == User Authorization
60 + #
61 + # Active Admin will automatically call an authorization
62 + # method in a before filter of all controller actions to
63 + # ensure that there is a user with proper rights. You can use
64 + # CanCanAdapter or make your own. Please refer to documentation.
65 + config.authorization_adapter = ActiveAdminAdapter
66 +
67 + # In case you prefer Pundit over other solutions you can here pass
68 + # the name of default policy class. This policy will be used in every
69 + # case when Pundit is unable to find suitable policy.
70 + # config.pundit_default_policy = "MyDefaultPunditPolicy"
71 +
72 + # If you wish to maintain a separate set of Pundit policies for admin
73 + # resources, you may set a namespace here that Pundit will search
74 + # within when looking for a resource's policy.
75 + # config.pundit_policy_namespace = :admin
76 +
77 + # You can customize your CanCan Ability class name here.
78 + # config.cancan_ability_class = "Ability"
79 +
80 + # You can specify a method to be called on unauthorized access.
81 + # This is necessary in order to prevent a redirect loop which happens
82 + # because, by default, user gets redirected to Dashboard. If user
83 + # doesn't have access to Dashboard, he'll end up in a redirect loop.
84 + # Method provided here should be defined in application_controller.rb.
85 + config.on_unauthorized_access = :access_denied
86 +
87 + # == Current User
88 + #
89 + # Active Admin will associate actions with the current
90 + # user performing them.
91 + #
92 + # This setting changes the method which Active Admin calls
93 + # (within the application controller) to return the currently logged in user.
94 + config.current_user_method = :current_user
95 +
96 + # == Logging Out
97 + #
98 + # Active Admin displays a logout link on each screen. These
99 + # settings configure the location and method used for the link.
100 + #
101 + # This setting changes the path where the link points to. If it's
102 + # a string, the strings is used as the path. If it's a Symbol, we
103 + # will call the method to return the path.
104 + #
105 + # Default:
106 + config.logout_link_path = :logout_path
107 +
108 + # This setting changes the http method used when rendering the
109 + # link. For example :get, :delete, :put, etc..
110 + #
111 + # Default:
112 + config.logout_link_method = :delete
113 +
114 + # == Root
115 + #
116 + # Set the action to call for the root path. You can set different
117 + # roots for each namespace.
118 + #
119 + # Default:
120 + # config.root_to = 'dashboard#index'
121 +
122 + # == Admin Comments
123 + #
124 + # This allows your users to comment on any resource registered with Active Admin.
125 + #
126 + # You can completely disable comments:
127 + config.comments = false
128 + #
129 + # You can change the name under which comments are registered:
130 + # config.comments_registration_name = 'AdminComment'
131 + #
132 + # You can change the order for the comments and you can change the column
133 + # to be used for ordering:
134 + # config.comments_order = 'created_at ASC'
135 + #
136 + # You can disable the menu item for the comments index page:
137 + config.comments_menu = false
138 + #
139 + # You can customize the comment menu:
140 + # config.comments_menu = { parent: 'Admin', priority: 1 }
141 +
142 + # == Batch Actions
143 + #
144 + # Enable and disable Batch Actions
145 + #
146 + config.batch_actions = true
147 +
148 + # == Controller Filters
149 + #
150 + # You can add before, after and around filters to all of your
151 + # Active Admin resources and pages from here.
152 + #
153 + # config.before_action :do_something_awesome
154 +
155 + # == Attribute Filters
156 + #
157 + # You can exclude possibly sensitive model attributes from being displayed,
158 + # added to forms, or exported by default by ActiveAdmin
159 + #
160 + config.filter_attributes = %i[encrypted_password password password_confirmation]
161 +
162 + # == Localize Date/Time Format
163 + #
164 + # Set the localize format to display dates and times.
165 + # To understand how to localize your app with I18n, read more at
166 + # https://guides.rubyonrails.org/i18n.html
167 + #
168 + # You can run `bin/rails runner 'puts I18n.t("date.formats")'` to see the
169 + # available formats in your application.
170 + #
171 + config.localize_format = :long
172 +
173 + # == Setting a Favicon
174 + #
175 + # config.favicon = 'favicon.ico'
176 +
177 + # == Meta Tags
178 + #
179 + # Add additional meta tags to the head element of active admin pages.
180 + #
181 + # Add tags to all pages logged in users see:
182 + # config.meta_tags = { author: 'My Company' }
183 +
184 + # By default, sign up/sign in/recover password pages are excluded
185 + # from showing up in search engine results by adding a robots meta
186 + # tag. You can reset the hash of meta tags included in logged out
187 + # pages:
188 + # config.meta_tags_for_logged_out_pages = {}
189 +
190 + # == Removing Breadcrumbs
191 + #
192 + # Breadcrumbs are enabled by default. You can customize them for individual
193 + # resources or you can disable them globally from here.
194 + #
195 + # config.breadcrumb = false
196 +
197 + # == Create Another Checkbox
198 + #
199 + # Create another checkbox is disabled by default. You can customize it for individual
200 + # resources or you can enable them globally from here.
201 + #
202 + # config.create_another = true
203 +
204 + # == Register Stylesheets & Javascripts
205 + #
206 + # We recommend using the built in Active Admin layout and loading
207 + # up your own stylesheets / javascripts to customize the look
208 + # and feel.
209 + #
210 + # To load a stylesheet:
211 + # config.register_stylesheet 'my_stylesheet.css'
212 + #
213 + # You can provide an options hash for more control, which is passed along to stylesheet_link_tag():
214 + # config.register_stylesheet 'my_print_stylesheet.css', media: :print
215 + #
216 + # To load a javascript file:
217 + # config.register_javascript 'my_javascript.js'
218 + # config.register_javascript 'tinymce.js'
219 + # == CSV options
220 + #
221 + # Set the CSV builder separator
222 + # config.csv_options = { col_sep: ';' }
223 + #
224 + # Force the use of quotes
225 + # config.csv_options = { force_quotes: true }
226 +
227 + # == Menu System
228 + #
229 + # You can add a navigation menu to be used in your application, or configure a provided menu
230 + #
231 + # To change the default utility navigation to show a link to your website & a logout btn
232 + #
233 + # config.namespace :admin do |admin|
234 + # admin.build_menu :utility_navigation do |menu|
235 + # menu.add label: "My Great Website", url: "http://www.mygreatwebsite.com", html_options: { target: :blank }
236 + # admin.add_logout_button_to_menu menu
237 + # end
238 + # end
239 + #
240 + # If you wanted to add a static menu item to the default menu provided:
241 + #
242 + # config.namespace :admin do |admin|
243 + # admin.build_menu :default do |menu|
244 + # menu.add label: "My Great Website", url: "http://www.mygreatwebsite.com", html_options: { target: :blank }
245 + # end
246 + # end
247 +
248 + # == Download Links
249 + #
250 + # You can disable download links on resource listing pages,
251 + # or customize the formats shown per namespace/globally
252 + #
253 + # To disable/customize for the :admin namespace:
254 + #
255 + # config.namespace :admin do |admin|
256 + #
257 + # # Disable the links entirely
258 + # admin.download_links = false
259 + #
260 + # # Only show XML & PDF options
261 + # admin.download_links = [:xml, :pdf]
262 + #
263 + # # Enable/disable the links based on block
264 + # # (for example, with cancan)
265 + # admin.download_links = proc { can?(:view_download_links) }
266 + #
267 + # end
268 +
269 + # == Pagination
270 + #
271 + # Pagination is enabled by default for all resources.
272 + # You can control the default per page count for all resources here.
273 + #
274 + # config.default_per_page = 30
275 + #
276 + # You can control the max per page count too.
277 + #
278 + # config.max_per_page = 10_000
279 +
280 + # == Filters
281 + #
282 + # By default the index screen includes a "Filters" sidebar on the right
283 + # hand side with a filter for each attribute of the registered model.
284 + # You can enable or disable them for all resources here.
285 + #
286 + # config.filters = true
287 + #
288 + # By default the filters include associations in a select, which means
289 + # that every record will be loaded for each association (up
290 + # to the value of config.maximum_association_filter_arity).
291 + # You can enabled or disable the inclusion
292 + # of those filters by default here.
293 + #
294 + # config.include_default_association_filters = true
295 +
296 + # config.maximum_association_filter_arity = 256 # default value of :unlimited will change to 256 in a future version
297 + # config.filter_columns_for_large_association = [
298 + # :display_name,
299 + # :full_name,
300 + # :name,
301 + # :username,
302 + # :login,
303 + # :title,
304 + # :email,
305 + # ]
306 + # config.filter_method_for_large_association = '_starts_with'
307 +
308 + # == Head
309 + #
310 + # You can add your own content to the site head like analytics. Make sure
311 + # you only pass content you trust.
312 + #
313 + # config.head = ''.html_safe
314 +
315 + # == Footer
316 + #
317 + # By default, the footer shows the current Active Admin version. You can
318 + # override the content of the footer here.
319 + #
320 + # config.footer = 'my custom footer text'
321 +
322 + # == Sorting
323 + #
324 + # By default ActiveAdmin::OrderClause is used for sorting logic
325 + # You can inherit it with own class and inject it for all resources
326 + #
327 + # config.order_clause = MyOrderClause
328 +
329 + # == Webpacker
330 + #
331 + # By default, Active Admin uses Sprocket's asset pipeline.
332 + # You can switch to using Webpacker here.
333 + #
334 + config.use_webpacker = true
335 +end
backend/musik88-web/config/initializers/assets.rb new
+10
@@ -0,0 +1,10 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# Version of your assets, change this if you want to expire all your assets.
4 +Rails.application.config.assets.version = '1.0'
5 +
6 +# Precompile additional assets.
7 +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
8 +# Rails.application.config.assets.precompile += %w( search.js )
9 +Rails.application.config.assets.precompile = ['manifest.js']
10 +Rails.application.config.assets.precompile += ['tolk/application.css', 'tolk/application.js']
backend/musik88-web/config/initializers/backtrace_silencers.rb new
+7
@@ -0,0 +1,7 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 +
6 +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 +# Rails.backtrace_cleaner.remove_silencers!
backend/musik88-web/config/initializers/cookies_serializer.rb new
+3
@@ -0,0 +1,3 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +Rails.application.config.action_dispatch.cookies_serializer = :json
\ No newline at end of file
backend/musik88-web/config/initializers/cors.rb new
+7
@@ -0,0 +1,7 @@
1 +Rails.application.config.middleware.insert_before 0, Rack::Cors do
2 + allow do
3 + origins '192.168.0.12:3002', '127.0.0.1:3002', 'localhost:3002', 'setoelkahfi.com', 'setoelkahfi.se',
4 + 'beta.splitfire.ai', 'splitfire.ai', 'localhost:3003', 'msk88-web.vercel.app'
5 + resource '*', headers: :any, methods: %i[get post patch put delete], expose: ['Authorization']
6 + end
7 +end
backend/musik88-web/config/initializers/devise.rb new
+364
@@ -0,0 +1,364 @@
1 +# frozen_string_literal: true
2 +
3 +# Use this hook to configure devise mailer, warden hooks and so forth.
4 +# Many of these configuration options can be set straight in your model.
5 +Devise.setup do |config|
6 + # The secret key used by Devise. Devise uses this key to generate
7 + # random tokens. Changing this key will render invalid all existing
8 + # confirmation, reset password and unlock tokens in the database.
9 + # Devise will use the `secret_key_base` as its `secret_key`
10 + # by default. You can change it below and use your own secret key.
11 + # config.secret_key = 'd60b36ddacc39800c1748311ad3badf8eda316edf53f5fb878a828d4d23685ff30d944c968b780fe0ddbd6e6f8064bf4947ae501d1edd5ad915c1bc4ffb8b0ef'
12 +
13 + # ==> Controller configuration
14 + # Configure the parent class to the devise controllers.
15 + # config.parent_controller = 'DeviseController'
16 +
17 + # ==> Mailer Configuration
18 + # Configure the e-mail address which will be shown in Devise::Mailer,
19 + # note that it will be overwritten if you use your own mailer class
20 + # with default "from" parameter.
21 + config.mailer_sender = 'noreply@musik88.com'
22 +
23 + # Configure the class responsible to send e-mails.
24 + config.mailer = 'DeviseMailer'
25 +
26 + # Configure the parent class responsible to send e-mails.
27 + # config.parent_mailer = 'ActionMailer::Base'
28 +
29 + # ==> ORM configuration
30 + # Load and configure the ORM. Supports :active_record (default) and
31 + # :mongoid (bson_ext recommended) by default. Other ORMs may be
32 + # available as additional gems.
33 + require 'devise/orm/active_record'
34 +
35 + # ==> Configuration for any authentication mechanism
36 + # Configure which keys are used when authenticating a user. The default is
37 + # just :email. You can configure it to use [:username, :subdomain], so for
38 + # authenticating a user, both parameters are required. Remember that those
39 + # parameters are used only when authenticating and not when retrieving from
40 + # session. If you need permissions, you should implement that in a before filter.
41 + # You can also supply a hash where the value is a boolean determining whether
42 + # or not authentication should be aborted when the value is not present.
43 + # config.authentication_keys = [:email]
44 +
45 + # Configure parameters from the request object used for authentication. Each entry
46 + # given should be a request method and it will automatically be passed to the
47 + # find_for_authentication method and considered in your model lookup. For instance,
48 + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
49 + # The same considerations mentioned for authentication_keys also apply to request_keys.
50 + # config.request_keys = []
51 +
52 + # Configure which authentication keys should be case-insensitive.
53 + # These keys will be downcased upon creating or modifying a user and when used
54 + # to authenticate or find a user. Default is :email.
55 + config.case_insensitive_keys = [:email]
56 +
57 + # Configure which authentication keys should have whitespace stripped.
58 + # These keys will have whitespace before and after removed upon creating or
59 + # modifying a user and when used to authenticate or find a user. Default is :email.
60 + config.strip_whitespace_keys = [:email]
61 +
62 + # Tell if authentication through request.params is enabled. True by default.
63 + # It can be set to an array that will enable params authentication only for the
64 + # given strategies, for example, `config.params_authenticatable = [:database]` will
65 + # enable it only for database (email + password) authentication.
66 + # config.params_authenticatable = true
67 +
68 + # Tell if authentication through HTTP Auth is enabled. False by default.
69 + # It can be set to an array that will enable http authentication only for the
70 + # given strategies, for example, `config.http_authenticatable = [:database]` will
71 + # enable it only for database authentication. The supported strategies are:
72 + # :database = Support basic authentication with authentication key + password
73 + # config.http_authenticatable = false
74 +
75 + # If 401 status code should be returned for AJAX requests. True by default.
76 + # config.http_authenticatable_on_xhr = true
77 +
78 + # The realm used in Http Basic Authentication. 'Application' by default.
79 + # config.http_authentication_realm = 'Application'
80 +
81 + # It will change confirmation, password recovery and other workflows
82 + # to behave the same regardless if the e-mail provided was right or wrong.
83 + # Does not affect registerable.
84 + # config.paranoid = true
85 +
86 + # By default Devise will store the user in session. You can skip storage for
87 + # particular strategies by setting this option.
88 + # Notice that if you are skipping storage for all authentication paths, you
89 + # may want to disable generating routes to Devise's sessions controller by
90 + # passing skip: :sessions to `devise_for` in your config/routes.rb
91 + config.skip_session_storage = [:http_auth]
92 +
93 + # By default, Devise cleans up the CSRF token on authentication to
94 + # avoid CSRF token fixation attacks. This means that, when using AJAX
95 + # requests for sign in and sign up, you need to get a new CSRF token
96 + # from the server. You can disable this option at your own risk.
97 + # config.clean_up_csrf_token_on_authentication = true
98 +
99 + # When false, Devise will not attempt to reload routes on eager load.
100 + # This can reduce the time taken to boot the app but if your application
101 + # requires the Devise mappings to be loaded during boot time the application
102 + # won't boot properly.
103 + # config.reload_routes = true
104 +
105 + # ==> Configuration for :database_authenticatable
106 + # For bcrypt, this is the cost for hashing the password and defaults to 11. If
107 + # using other algorithms, it sets how many times you want the password to be hashed.
108 + #
109 + # Limiting the stretches to just one in testing will increase the performance of
110 + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
111 + # a value less than 10 in other environments. Note that, for bcrypt (the default
112 + # algorithm), the cost increases exponentially with the number of stretches (e.g.
113 + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
114 + config.stretches = Rails.env.test? ? 1 : 11
115 +
116 + # Set up a pepper to generate the hashed password.
117 + # config.pepper = '60f7bc7ce3b1c6da3d1651bff697698e24cb0d44cb3411496227d47ae991ef60ee9c2869472fe47da3067f007a0991a885acc77925121d205a95121857d33b40'
118 +
119 + # Send a notification to the original email when the user's email is changed.
120 + # config.send_email_changed_notification = false
121 +
122 + # Send a notification email when the user's password is changed.
123 + # config.send_password_change_notification = false
124 +
125 + # ==> Configuration for :invitable
126 + # The period the generated invitation token is valid.
127 + # After this period, the invited resource won't be able to accept the invitation.
128 + # When invite_for is 0 (the default), the invitation won't expire.
129 + # config.invite_for = 2.weeks
130 +
131 + # Number of invitations users can send.
132 + # - If invitation_limit is nil, there is no limit for invitations, users can
133 + # send unlimited invitations, invitation_limit column is not used.
134 + # - If invitation_limit is 0, users can't send invitations by default.
135 + # - If invitation_limit n > 0, users can send n invitations.
136 + # You can change invitation_limit column for some users so they can send more
137 + # or less invitations, even with global invitation_limit = 0
138 + # Default: nil
139 + # config.invitation_limit = 5
140 +
141 + # The key to be used to check existing users when sending an invitation
142 + # and the regexp used to test it when validate_on_invite is not set.
143 + # config.invite_key = { email: /\A[^@]+@[^@]+\z/ }
144 + # config.invite_key = { email: /\A[^@]+@[^@]+\z/, username: nil }
145 +
146 + # Ensure that invited record is valid.
147 + # The invitation won't be sent if this check fails.
148 + # Default: false
149 + # config.validate_on_invite = true
150 +
151 + # Resend invitation if user with invited status is invited again
152 + # Default: true
153 + # config.resend_invitation = false
154 +
155 + # The class name of the inviting model. If this is nil,
156 + # the #invited_by association is declared to be polymorphic.
157 + # Default: nil
158 + # config.invited_by_class_name = 'User'
159 +
160 + # The foreign key to the inviting model (if invited_by_class_name is set)
161 + # Default: :invited_by_id
162 + # config.invited_by_foreign_key = :invited_by_id
163 +
164 + # The column name used for counter_cache column. If this is nil,
165 + # the #invited_by association is declared without counter_cache.
166 + # Default: nil
167 + # config.invited_by_counter_cache = :invitations_count
168 +
169 + # Auto-login after the user accepts the invite. If this is false,
170 + # the user will need to manually log in after accepting the invite.
171 + # Default: true
172 + # config.allow_insecure_sign_in_after_accept = false
173 +
174 + # ==> Configuration for :confirmable
175 + # A period that the user is allowed to access the website even without
176 + # confirming their account. For instance, if set to 2.days, the user will be
177 + # able to access the website for two days without confirming their account,
178 + # access will be blocked just in the third day. Default is 0.days, meaning
179 + # the user cannot access the website without confirming their account.
180 + # config.allow_unconfirmed_access_for = 2.days
181 +
182 + # A period that the user is allowed to confirm their account before their
183 + # token becomes invalid. For example, if set to 3.days, the user can confirm
184 + # their account within 3 days after the mail was sent, but on the fourth day
185 + # their account can't be confirmed with the token any more.
186 + # Default is nil, meaning there is no restriction on how long a user can take
187 + # before confirming their account.
188 + # config.confirm_within = 3.days
189 +
190 + # If true, requires any email changes to be confirmed (exactly the same way as
191 + # initial account confirmation) to be applied. Requires additional unconfirmed_email
192 + # db field (see migrations). Until confirmed, new email is stored in
193 + # unconfirmed_email column, and copied to email column on successful confirmation.
194 + config.reconfirmable = true
195 +
196 + # Defines which key will be used when confirming an account
197 + # config.confirmation_keys = [:email]
198 +
199 + # ==> Configuration for :rememberable
200 + # The time the user will be remembered without asking for credentials again.
201 + # config.remember_for = 2.weeks
202 +
203 + # Invalidates all the remember me tokens when the user signs out.
204 + config.expire_all_remember_me_on_sign_out = true
205 +
206 + # If true, extends the user's remember period when remembered via cookie.
207 + # config.extend_remember_period = false
208 +
209 + # Options to be passed to the created cookie. For instance, you can set
210 + # secure: true in order to force SSL only cookies.
211 + # config.rememberable_options = {}
212 +
213 + # ==> Configuration for :validatable
214 + # Range for password length.
215 + config.password_length = 6..128
216 +
217 + # Email regex used to validate email formats. It simply asserts that
218 + # one (and only one) @ exists in the given string. This is mainly
219 + # to give user feedback and not to assert the e-mail validity.
220 + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
221 +
222 + # ==> Configuration for :timeoutable
223 + # The time you want to timeout the user session without activity. After this
224 + # time the user will be asked for credentials again. Default is 30 minutes.
225 + # config.timeout_in = 30.minutes
226 +
227 + # ==> Configuration for :lockable
228 + # Defines which strategy will be used to lock an account.
229 + # :failed_attempts = Locks an account after a number of failed attempts to sign in.
230 + # :none = No lock strategy. You should handle locking by yourself.
231 + # config.lock_strategy = :failed_attempts
232 +
233 + # Defines which key will be used when locking and unlocking an account
234 + # config.unlock_keys = [:email]
235 +
236 + # Defines which strategy will be used to unlock an account.
237 + # :email = Sends an unlock link to the user email
238 + # :time = Re-enables login after a certain amount of time (see :unlock_in below)
239 + # :both = Enables both strategies
240 + # :none = No unlock strategy. You should handle unlocking by yourself.
241 + # config.unlock_strategy = :both
242 +
243 + # Number of authentication tries before locking an account if lock_strategy
244 + # is failed attempts.
245 + # config.maximum_attempts = 20
246 +
247 + # Time interval to unlock the account if :time is enabled as unlock_strategy.
248 + # config.unlock_in = 1.hour
249 +
250 + # Warn on the last attempt before the account is locked.
251 + # config.last_attempt_warning = true
252 +
253 + # ==> Configuration for :recoverable
254 + #
255 + # Defines which key will be used when recovering the password for an account
256 + config.reset_password_keys = [:email]
257 +
258 + # Time interval you can reset your password with a reset password key.
259 + # Don't put a too small interval or your users won't have the time to
260 + # change their passwords.
261 + config.reset_password_within = 6.hours
262 +
263 + # When set to false, does not sign a user in automatically after their password is
264 + # reset. Defaults to true, so a user is signed in automatically after a reset.
265 + # config.sign_in_after_reset_password = true
266 +
267 + # ==> Configuration for :encryptable
268 + # Allow you to use another hashing or encryption algorithm besides bcrypt (default).
269 + # You can use :sha1, :sha512 or algorithms from others authentication tools as
270 + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20
271 + # for default behavior) and :restful_authentication_sha1 (then you should set
272 + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper).
273 + #
274 + # Require the `devise-encryptable` gem when using anything other than bcrypt
275 + # config.encryptor = :sha512
276 +
277 + # ==> Scopes configuration
278 + # Turn scoped views on. Before rendering "sessions/new", it will first check for
279 + # "users/sessions/new". It's turned off by default because it's slower if you
280 + # are using only default views.
281 + # config.scoped_views = false
282 +
283 + # Configure the default scope given to Warden. By default it's the first
284 + # devise role declared in your routes (usually :user).
285 + # config.default_scope = :user
286 +
287 + # Set this configuration to false if you want /users/sign_out to sign out
288 + # only the current scope. By default, Devise signs out all scopes.
289 + # config.sign_out_all_scopes = true
290 +
291 + # ==> Navigation configuration
292 + # Lists the formats that should be treated as navigational. Formats like
293 + # :html, should redirect to the sign in page when the user does not have
294 + # access, but formats like :xml or :json, should return 401.
295 + #
296 + # If you have any extra navigational formats, like :iphone or :mobile, you
297 + # should add them to the navigational formats lists.
298 + #
299 + # The "*/*" below is required to match Internet Explorer requests.
300 + # config.navigational_formats = ['*/*', :html]
301 +
302 + # The default HTTP method used to sign out a resource. Default is :delete.
303 + config.sign_out_via = :delete
304 +
305 + # ==> OmniAuth
306 + # Add a new OmniAuth provider. Check the wiki for more information on setting
307 + # up on your models and hooks.
308 + config.omniauth :facebook, ENV['FB_APP_ID'], ENV['FB_APP_SECRET']
309 +
310 + config.omniauth :spotify, ENV['SPOTIFY_APP_ID'], ENV['SPOTIFY_APP_SECRET'], scope: %w[
311 + playlist-read-private
312 + user-top-read
313 + user-read-private
314 + user-read-email
315 + user-read-recently-played
316 + ].join(' ')
317 +
318 + config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: %w[
319 + email
320 + profile
321 + https://www.googleapis.com/auth/youtube
322 + https://www.googleapis.com/auth/youtube.force-ssl
323 + ].join(' ')
324 +
325 + config.omniauth :apple, ENV['APPLE_CLIENT_ID'], '', {
326 + scope: 'email name',
327 + team_id: ENV['APPLE_TEAM_ID'],
328 + key_id: ENV['APPLE_KEY_ID'],
329 + pem: ENV['APPLE_PRIVATE_KEY']
330 + }
331 +
332 + # ==> Warden configuration
333 + # If you want to use other strategies, that are not supported by Devise, or
334 + # change the failure app, you can configure them inside the config.warden block.
335 + #
336 + # config.warden do |manager|
337 + # manager.intercept_401 = false
338 + # manager.default_strategies(scope: :user).unshift :some_external_strategy
339 + # end
340 +
341 + # ==> Mountable engine configurations
342 + # When using Devise inside an engine, let's call it `MyEngine`, and this engine
343 + # is mountable, there are some extra configurations to be taken into account.
344 + # The following options are available, assuming the engine is mounted as:
345 + #
346 + # mount MyEngine, at: '/my_engine'
347 + #
348 + # The router that invoked `devise_for`, in the example above, would be:
349 + # config.router_name = :my_engine
350 + #
351 + # When using OmniAuth, Devise cannot automatically set OmniAuth path,
352 + # so you need to do it manually. For the users scope, it would be:
353 + # config.omniauth_path_prefix = '/my_engine/users/auth'
354 + config.jwt do |jwt|
355 + jwt.secret = Rails.application.credentials.devise[:jwt_secret_key]
356 + jwt.dispatch_requests = [
357 + ['POST', %r{^/api/v1/login$}]
358 + ]
359 + jwt.revocation_requests = [
360 + ['DELETE', %r{^/api/v1/logout$}]
361 + ]
362 + jwt.expiration_time = 30.days.to_i
363 + end
364 +end
backend/musik88-web/config/initializers/filter_parameter_logging.rb new
+4
@@ -0,0 +1,4 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# Configure sensitive parameters which will be filtered from the log file.
4 +Rails.application.config.filter_parameters += [:password]
backend/musik88-web/config/initializers/inflections.rb new
+16
@@ -0,0 +1,16 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# Add new inflection rules using the following format. Inflections
4 +# are locale specific, and you may define rules for as many different
5 +# locales as you wish. All of these examples are active by default:
6 +# ActiveSupport::Inflector.inflections(:en) do |inflect|
7 +# inflect.plural /^(ox)$/i, '\1en'
8 +# inflect.singular /^(ox)en/i, '\1'
9 +# inflect.irregular 'person', 'people'
10 +# inflect.uncountable %w( fish sheep )
11 +# end
12 +
13 +# These inflection rules are supported but not enabled by default:
14 +# ActiveSupport::Inflector.inflections(:en) do |inflect|
15 +# inflect.acronym 'RESTful'
16 +# end
backend/musik88-web/config/initializers/locale.rb new
+6
@@ -0,0 +1,6 @@
1 +# Where the I18n library should search for translation files
2 +I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
3 +
4 +# Permitted locales available for the application
5 +# ISO 639-1 Code
6 +I18n.available_locales = %i[en id-id sv-se ja]
backend/musik88-web/config/initializers/merit.rb new
+21
@@ -0,0 +1,21 @@
1 +# Create application badges (uses https://github.com/norman/ambry)
2 +Rails.application.reloader.to_prepare do
3 + Merit::Badge.create!(
4 + id: 1,
5 + name: 'pretty-path',
6 + description: 'A user has a username. Pretty, huh?',
7 + custom_fields: { icon_path: 'media/images/badge-pretty.gif' }
8 + )
9 + Merit::Badge.create!(
10 + id: 2,
11 + name: 'spotify-exporter',
12 + description: 'I am doing an export-import.',
13 + custom_fields: { icon_path: 'media/images/badge-spotify-exporter.gif' }
14 + )
15 + Merit::Badge.create!(
16 + id: 3,
17 + name: 'youtube-exporter',
18 + description: 'I am doing an export-import.',
19 + custom_fields: { icon_path: 'media/images/badge-youtube-exporter.gif' }
20 + )
21 +end
backend/musik88-web/config/initializers/mime_types.rb new
+4
@@ -0,0 +1,4 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# Add new mime types for use in respond_to blocks:
4 +# Mime::Type.register "text/richtext", :rtf
backend/musik88-web/config/initializers/route_translator.rb new
+2
@@ -0,0 +1,2 @@
1 +RouteTranslator.config do |config|
2 +end
\ No newline at end of file
backend/musik88-web/config/initializers/sentry.rb new
+14
@@ -0,0 +1,14 @@
1 +Sentry.init do |config|
2 + config.dsn = 'https://358d28fdbcc64ec8adf63f104dbe262e@o933906.ingest.sentry.io/5883180'
3 + config.breadcrumbs_logger = %i[active_support_logger http_logger]
4 +
5 + # Set tracesSampleRate to 1.0 to capture 100%
6 + # of transactions for performance monitoring.
7 + # We recommend adjusting this value in production
8 + config.traces_sample_rate = 1.0
9 + # or
10 + config.traces_sampler = lambda do |_context|
11 + true
12 + end
13 + config.enabled_environments = %w[production]
14 +end
backend/musik88-web/config/initializers/session_store.rb new
+3
@@ -0,0 +1,3 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +Rails.application.config.session_store :cookie_store, key: '_musik88_session'
backend/musik88-web/config/initializers/split.rb new
+3
@@ -0,0 +1,3 @@
1 +Split.configure do |config|
2 + config.ignore_ip_addresses << '178.78.220.155'
3 +end
backend/musik88-web/config/initializers/staging_auth.rb new
+10
@@ -0,0 +1,10 @@
1 +Musik88::Application.configure do
2 + # Restrict access with HTTP Basic Auth for staging environments
3 + unless ENV['STAGING_AUTH'].blank?
4 + config.middleware.use ::Rack::Auth::Basic do |username, password|
5 + ENV['STAGING_AUTH'].split(';').any? do |pair|
6 + [username, password] == pair.split(':')
7 + end
8 + end
9 + end
10 +end
\ No newline at end of file
backend/musik88-web/config/initializers/tolk.rb new
+422
@@ -0,0 +1,422 @@
1 +# encoding: utf-8
2 +
3 +# Tolk config file. Generated on September 06, 2021 22:14
4 +# See github.com/tolk/tolk for more informations
5 +
6 +Tolk.config do |config|
7 +
8 + # If you need to add a mapping do it like this :
9 + # May we suggest you use http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
10 + # config.mapping['fr-ES'] = 'Frañol !'
11 +
12 + # config.mapping['aa'] = 'Afar'
13 + # config.mapping['aa-DJ'] = 'Afar (Djibouti)'
14 + # config.mapping['aa-ER'] = 'Afar (Eritrea)'
15 + # config.mapping['aa-ET'] = 'Afar (Ethiopia)'
16 + # config.mapping['af'] = 'Afrikaans'
17 + # config.mapping['af-NA'] = 'Afrikaans (Namibia)'
18 + # config.mapping['af-ZA'] = 'Afrikaans (South Africa)'
19 + # config.mapping['ak'] = 'Akan'
20 + # config.mapping['ak-GH'] = 'Akan (Ghana)'
21 + # config.mapping['am'] = 'Amharic'
22 + # config.mapping['am-ET'] = 'Amharic (Ethiopia)'
23 + # config.mapping['ar'] = 'Arabic'
24 + # config.mapping['ar-AE'] = 'Arabic (United Arab Emirates)'
25 + # config.mapping['ar-BH'] = 'Arabic (Bahrain)'
26 + # config.mapping['ar-DZ'] = 'Arabic (Algeria)'
27 + # config.mapping['ar-EG'] = 'Arabic (Egypt)'
28 + # config.mapping['ar-IQ'] = 'Arabic (Iraq)'
29 + # config.mapping['ar-JO'] = 'Arabic (Jordan)'
30 + # config.mapping['ar-KW'] = 'Arabic (Kuwait)'
31 + # config.mapping['ar-LB'] = 'Arabic (Lebanon)'
32 + # config.mapping['ar-LY'] = 'Arabic (Libya)'
33 + # config.mapping['ar-MA'] = 'Arabic (Morocco)'
34 + # config.mapping['ar-OM'] = 'Arabic (Oman)'
35 + # config.mapping['ar-QA'] = 'Arabic (Qatar)'
36 + # config.mapping['ar-SA'] = 'Arabic (Saudi Arabia)'
37 + # config.mapping['ar-SD'] = 'Arabic (Sudan)'
38 + # config.mapping['ar-SY'] = 'Arabic (Syria)'
39 + # config.mapping['ar-TN'] = 'Arabic (Tunisia)'
40 + # config.mapping['ar-YE'] = 'Arabic (Yemen)'
41 + # config.mapping['as'] = 'Assamese'
42 + # config.mapping['as-IN'] = 'Assamese (India)'
43 + # config.mapping['az'] = 'Azerbaijani'
44 + # config.mapping['az-AZ'] = 'Azerbaijani (Azerbaijan)'
45 + # config.mapping['be'] = 'Belarusian'
46 + # config.mapping['be-BY'] = 'Belarusian (Belarus)'
47 + # config.mapping['bg'] = 'Bulgarian'
48 + # config.mapping['bg-BG'] = 'Bulgarian (Bulgaria)'
49 + # config.mapping['bn'] = 'Bengali'
50 + # config.mapping['bn-BD'] = 'Bengali (Bangladesh)'
51 + # config.mapping['bn-IN'] = 'Bengali (India)'
52 + # config.mapping['bo'] = 'Tibetan'
53 + # config.mapping['bo-CN'] = 'Tibetan (China)'
54 + # config.mapping['bn-IN'] = 'Tibetan (India)'
55 + # config.mapping['bs'] = 'Bosnian'
56 + # config.mapping['bs-BA'] = 'Bosnian (Bosnia and Herzegovina)'
57 + # config.mapping['byn'] = 'Blin'
58 + # config.mapping['byn-ER'] = 'Blin (Eritrea)'
59 + # config.mapping['ca'] = 'Catalan'
60 + # config.mapping['ca-ES'] = 'Catalan (Spain)'
61 + # config.mapping['cch'] = 'Atsam'
62 + # config.mapping['cch-NG'] = 'Atsam (Nigeria)'
63 + # config.mapping['cop'] = 'Coptic'
64 + # config.mapping['cs'] = 'Czech'
65 + # config.mapping['cs-CZ'] = 'Czech (Czech Republic)'
66 + # config.mapping['cy'] = 'Welsh'
67 + # config.mapping['cy-GB'] = 'Welsh (United Kingdom)'
68 + # config.mapping['da'] = 'Danish'
69 + # config.mapping['da-DK'] = 'Danish (Denmark)'
70 + # config.mapping['de'] = 'German'
71 + # config.mapping['de-AT'] = 'German (Austria)'
72 + # config.mapping['de-BE'] = 'German (Belgium)'
73 + # config.mapping['de-CH'] = 'German (Switzerland)'
74 + config.mapping['de-de'] = 'German (Germany)'
75 + # config.mapping['de-LI'] = 'German (Liechtenstein)'
76 + # config.mapping['de-LU'] = 'German (Luxembourg)'
77 + # config.mapping['dv'] = 'Divehi'
78 + # config.mapping['dv-MV'] = 'Divehi (Maldives)'
79 + # config.mapping['dz'] = 'Dzongkha'
80 + # config.mapping['dz-BT'] = 'Dzongkha (Bhutan)'
81 + # config.mapping['ee'] = 'Ewe'
82 + # config.mapping['ee-GH'] = 'Ewe (Ghana)'
83 + # config.mapping['ee-TG'] = 'Ewe (Togo)'
84 + # config.mapping['el'] = 'Greek'
85 + # config.mapping['el-CY'] = 'Greek (Cyprus)'
86 + # config.mapping['el-GR'] = 'Greek (Greece)'
87 + # config.mapping['en'] = 'English'
88 + # config.mapping['en-AS'] = 'English (American Samoa)'
89 + # config.mapping['en-AU'] = 'English (Australia)'
90 + # config.mapping['en-BE'] = 'English (Belgium)'
91 + # config.mapping['en-BW'] = 'English (Botswana)'
92 + # config.mapping['en-BZ'] = 'English (Belize)'
93 + # config.mapping['en-CA'] = 'English (Canada)'
94 + # config.mapping['en-GB'] = 'English (United Kingdom)'
95 + # config.mapping['en-GU'] = 'English (Guam)'
96 + # config.mapping['en-HK'] = 'English (Hong Kong)'
97 + # config.mapping['en-IE'] = 'English (Ireland)'
98 + # config.mapping['en-IN'] = 'English (India)'
99 + # config.mapping['en-JM'] = 'English (Jamaica)'
100 + # config.mapping['en-MH'] = 'English (Marshall Islands)'
101 + # config.mapping['en-MP'] = 'English (Northern Mariana Islands)'
102 + # config.mapping['en-MT'] = 'English (Malta)'
103 + # config.mapping['en-NA'] = 'English (Namibia)'
104 + # config.mapping['en-NZ'] = 'English (New Zealand)'
105 + # config.mapping['en-PH'] = 'English (Philippines)'
106 + # config.mapping['en-PK'] = 'English (Pakistan)'
107 + # config.mapping['en-SG'] = 'English (Singapore)'
108 + # config.mapping['en-TT'] = 'English (Trinidad and Tobago)'
109 + # config.mapping['en-UM'] = 'English (United States Minor Outlying Islands)'
110 + # config.mapping['en-US'] = 'English (United States)'
111 + # config.mapping['en-VI'] = 'English (U.S. Virgin Islands)'
112 + # config.mapping['en-ZA'] = 'English (South Africa)'
113 + # config.mapping['en-ZW'] = 'English (Zimbabwe)'
114 + # config.mapping['eo'] = 'Esperanto'
115 + # config.mapping['es'] = 'Spanish'
116 + # config.mapping['es-AR'] = 'Spanish (Argentina)'
117 + # config.mapping['es-BO'] = 'Spanish (Bolivia)'
118 + # config.mapping['es-CL'] = 'Spanish (Chile)'
119 + # config.mapping['es-CO'] = 'Spanish (Colombia)'
120 + # config.mapping['es-CR'] = 'Spanish (Costa Rica)'
121 + # config.mapping['es-DO'] = 'Spanish (Dominican Republic)'
122 + # config.mapping['es-EC'] = 'Spanish (Ecuador)'
123 + # config.mapping['es-ES'] = 'Spanish (Spain)'
124 + # config.mapping['es-GT'] = 'Spanish (Guatemala)'
125 + # config.mapping['es-HN'] = 'Spanish (Honduras)'
126 + # config.mapping['es-MX'] = 'Spanish (Mexico)'
127 + # config.mapping['es-NI'] = 'Spanish (Nicaragua)'
128 + # config.mapping['es-PA'] = 'Spanish (Panama)'
129 + # config.mapping['es-PE'] = 'Spanish (Peru)'
130 + # config.mapping['es-PR'] = 'Spanish (Puerto Rico)'
131 + # config.mapping['es-PY'] = 'Spanish (Paraguay)'
132 + # config.mapping['es-SV'] = 'Spanish (El Salvador)'
133 + # config.mapping['es-US'] = 'Spanish (United States)'
134 + # config.mapping['es-UY'] = 'Spanish (Uruguay)'
135 + # config.mapping['es-VE'] = 'Spanish (Venezuela)'
136 + # config.mapping['et'] = 'Estonian'
137 + # config.mapping['et-EE'] = 'Estonian (Estonia)'
138 + # config.mapping['eu'] = 'Basque'
139 + # config.mapping['eu-ES'] = 'Basque (Spain)'
140 + # config.mapping['fa'] = 'Persian'
141 + # config.mapping['fa-AF'] = 'Persian (Afghanistan)'
142 + # config.mapping['fa-IR'] = 'Persian (Iran)'
143 + # config.mapping['fi'] = 'Finnish'
144 + # config.mapping['fi-FI'] = 'Finnish (Finland)'
145 + # config.mapping['fil'] = 'Filipino'
146 + # config.mapping['fil-PH'] = 'Filipino (Philippines)'
147 + # config.mapping['fo'] = 'Faroese'
148 + # config.mapping['fo-FO'] = 'Faroese (Faroe Islands)'
149 + # config.mapping['fr'] = 'French'
150 + # config.mapping['fr-BE'] = 'French (Belgium)'
151 + # config.mapping['fr-CA'] = 'French (Canada)'
152 + # config.mapping['fr-CH'] = 'French (Switzerland)'
153 + config.mapping['fr-fr'] = 'French (France)'
154 + # config.mapping['fr-LU'] = 'French (Luxembourg)'
155 + # config.mapping['fr-MC'] = 'French (Monaco)'
156 + # config.mapping['fr-SN'] = 'French (Senegal)'
157 + # config.mapping['fur'] = 'Friulian'
158 + # config.mapping['fur-IT'] = 'Friulian (Italy)'
159 + # config.mapping['ga'] = 'Irish'
160 + # config.mapping['ga-IE'] = 'Irish (Ireland)'
161 + # config.mapping['gaa'] = 'Ga'
162 + # config.mapping['gaa-GH'] = 'Ga (Ghana)'
163 + # config.mapping['gez'] = 'Geez'
164 + # config.mapping['gez-ER'] = 'Geez (Eritrea)'
165 + # config.mapping['gez-ET'] = 'Geez (Ethiopia)'
166 + # config.mapping['gl'] = 'Gallegan'
167 + # config.mapping['gl-ES'] = 'Gallegan (Spain)'
168 + # config.mapping['gsw'] = 'Swiss German'
169 + # config.mapping['gsw-CH'] = 'Swiss German (Swiss)'
170 + # config.mapping['gu'] = 'Gujarati'
171 + # config.mapping['gu-IN'] = 'Gujarati (India)'
172 + # config.mapping['gv'] = 'Manx'
173 + # config.mapping['gv-GB'] = 'Manx (United Kingdom)'
174 + # config.mapping['ha'] = 'Hausa'
175 + # config.mapping['ha-GH'] = 'Hausa (Ghana)'
176 + # config.mapping['ha-NE'] = 'Hausa (Niger)'
177 + # config.mapping['ha-NG'] = 'Hausa (Nigeria)'
178 + # config.mapping['ha-SD'] = 'Hausa (Sudan)'
179 + # config.mapping['haw'] = 'Hawaiian'
180 + # config.mapping['haw-US'] = 'Hawaiian (United States)'
181 + # config.mapping['he'] = 'Hebrew'
182 + # config.mapping['he-IL'] = 'Hebrew (Israel)'
183 + config.mapping['hi'] = 'Hindi'
184 + # config.mapping['hi-IN'] = 'Hindi (India)'
185 + # config.mapping['hr'] = 'Croatian'
186 + # config.mapping['hr-HR'] = 'Croatian (Croatia)'
187 + # config.mapping['hu'] = 'Hungarian'
188 + # config.mapping['hu-HU'] = 'Hungarian (Hungary)'
189 + # config.mapping['hy'] = 'Armenian'
190 + # config.mapping['ia'] = 'Interlingua'
191 + # config.mapping['id'] = 'Indonesian'
192 + config.mapping['id-id'] = 'Indonesian (Indonesia)'
193 + config.mapping['jv-id'] = 'Javanese (Indonesia)'
194 + # config.mapping['ig'] = 'Igbo'
195 + # config.mapping['ig-NG'] = 'Igbo (Nigeria)'
196 + # config.mapping['ii'] = 'Sichuan Yi'
197 + # config.mapping['ii-CN'] = 'Sichuan Yi (China)'
198 + # config.mapping['in'] = 'Indonesian'
199 + # config.mapping['is'] = 'Icelandic'
200 + # config.mapping['is-IS'] = 'Icelandic (Iceland)'
201 + # config.mapping['it'] = 'Italian'
202 + # config.mapping['it-CH'] = 'Italian (Switzerland)'
203 + # config.mapping['it-IT'] = 'Italian (Italy)'
204 + # config.mapping['iu'] = 'Inuktitut'
205 + # config.mapping['iw'] = 'Hebrew'
206 + # config.mapping['ja'] = 'Japanese'
207 + config.mapping['ja-jp'] = 'Japanese (Japan)'
208 + # config.mapping['ka'] = 'Georgian'
209 + # config.mapping['ka-GE'] = 'Georgian (Georgia)'
210 + # config.mapping['kaj'] = 'Jju'
211 + # config.mapping['kaj-NG'] = 'Jju (Nigeria)'
212 + # config.mapping['kam'] = 'Kamba'
213 + # config.mapping['kam-KE'] = 'Kamba (Kenya)'
214 + # config.mapping['kcg'] = 'Tyap'
215 + # config.mapping['kcg-NG'] = 'Tyap (Nigeria)'
216 + # config.mapping['kfo'] = 'Koro'
217 + # config.mapping['kfo-CI'] = 'Koro (Ivory Coast)'
218 + # config.mapping['kk'] = 'Kazakh'
219 + # config.mapping['kk-KZ'] = 'Kazakh (Kazakhstan)'
220 + # config.mapping['kl'] = 'Kalaallisut'
221 + # config.mapping['kl-GL'] = 'Kalaallisut (Greenland)'
222 + # config.mapping['km'] = 'Khmer'
223 + # config.mapping['km-KH'] = 'Khmer (Cambodia)'
224 + # config.mapping['kn'] = 'Kannada'
225 + # config.mapping['kn-IN'] = 'Kannada (India)'
226 + # config.mapping['ko'] = 'Korean'
227 + config.mapping['ko-kr'] = 'Korean (South Korea)'
228 + # config.mapping['kok'] = 'Konkani'
229 + # config.mapping['kok-IN'] = 'Konkani (India)'
230 + # config.mapping['kpe'] = 'Kpelle'
231 + # config.mapping['kpe-GN'] = 'Kpelle (Guinea)'
232 + # config.mapping['kpe-LR'] = 'Kpelle (Liberia)'
233 + # config.mapping['ku'] = 'Kurdish'
234 + # config.mapping['ku-IQ'] = 'Kurdish (Iraq)'
235 + # config.mapping['Iran'] = 'Kurdish (Iran)'
236 + # config.mapping['Syria'] = 'Kurdish (Syria)'
237 + # config.mapping['Turkey'] = 'Kurdish (Turkey)'
238 + # config.mapping['kw'] = 'Cornish'
239 + # config.mapping['kw-GB'] = 'Cornish (United Kingdom)'
240 + # config.mapping['ky'] = 'Kirghiz'
241 + # config.mapping['ky-KG'] = 'Kirghiz (Kyrgyzstan)'
242 + # config.mapping['ln'] = 'Lingala'
243 + # config.mapping['ln-CD'] = 'Lingala (Congo - Kinshasa)'
244 + # config.mapping['ln-CG'] = 'Lingala (Congo - Brazzaville)'
245 + # config.mapping['lo'] = 'Lao'
246 + # config.mapping['lo-LA'] = 'Lao (Laos)'
247 + # config.mapping['lt'] = 'Lithuanian'
248 + # config.mapping['lt-LT'] = 'Lithuanian (Lithuania)'
249 + # config.mapping['lv'] = 'Latvian'
250 + # config.mapping['lv-LV'] = 'Latvian (Latvia)'
251 + # config.mapping['mk'] = 'Macedonian'
252 + # config.mapping['mk-MK'] = 'Macedonian (Macedonia)'
253 + # config.mapping['ml'] = 'Malayalam'
254 + # config.mapping['ml-IN'] = 'Malayalam (India)'
255 + # config.mapping['mn'] = 'Mongolian'
256 + # config.mapping['mn-CN'] = 'Mongolian (China)'
257 + # config.mapping['mn-MN'] = 'Mongolian (Mongolia)'
258 + # config.mapping['mo'] = 'Romanian'
259 + # config.mapping['mr'] = 'Marathi'
260 + # config.mapping['mr-IN'] = 'Marathi (India)'
261 + # config.mapping['ms'] = 'Malay'
262 + # config.mapping['ms-BN'] = 'Malay (Brunei)'
263 + # config.mapping['ms-MY'] = 'Malay (Malaysia)'
264 + # config.mapping['mt'] = 'Maltese'
265 + # config.mapping['mt-MT'] = 'Maltese (Malta)'
266 + # config.mapping['my'] = 'Burmese'
267 + # config.mapping['my-MM'] = 'Burmese (Myanmar)'
268 + # config.mapping['nb'] = 'Norwegian Bokmal'
269 + # config.mapping['nb-NO'] = 'Norwegian Bokmal (Norway)'
270 + # config.mapping['nds'] = 'Low German'
271 + # config.mapping['nds-DE'] = 'Low German (Germany)'
272 + # config.mapping['ne'] = 'Nepali'
273 + # config.mapping['ne-IN'] = 'Nepali (India)'
274 + # config.mapping['ne-NP'] = 'Nepali (Nepal)'
275 + # config.mapping['nl'] = 'Dutch'
276 + # config.mapping['nl-BE'] = 'Dutch (Belgium)'
277 + # config.mapping['nl-NL'] = 'Dutch (Netherlands)'
278 + # config.mapping['nn'] = 'Norwegian Nynorsk'
279 + # config.mapping['nn-NO'] = 'Norwegian Nynorsk (Norway)'
280 + # config.mapping['no'] = 'Norwegian'
281 + # config.mapping['nr'] = 'South Ndebele'
282 + # config.mapping['nr-ZA'] = 'South Ndebele (South Africa)'
283 + # config.mapping['nso'] = 'Northern Sotho'
284 + # config.mapping['nso-ZA'] = 'Northern Sotho (South Africa)'
285 + # config.mapping['ny'] = 'Nyanja'
286 + # config.mapping['ny-MW'] = 'Nyanja (Malawi)'
287 + # config.mapping['oc'] = 'Occitan'
288 + # config.mapping['oc-FR'] = 'Occitan (France)'
289 + # config.mapping['om'] = 'Oromo'
290 + # config.mapping['om-ET'] = 'Oromo (Ethiopia)'
291 + # config.mapping['om-KE'] = 'Oromo (Kenya)'
292 + # config.mapping['or'] = 'Oriya'
293 + # config.mapping['or-IN'] = 'Oriya (India)'
294 + # config.mapping['pa'] = 'Punjabi'
295 + # config.mapping['pa-IN'] = 'Punjabi (India)'
296 + # config.mapping['pa-PK'] = 'Punjabi (Pakistan)'
297 + # config.mapping['pl'] = 'Polish'
298 + # config.mapping['pl-PL'] = 'Polish (Poland)'
299 + # config.mapping['ps'] = 'Polish'
300 + # config.mapping['ps-AF'] = 'Polish (Afghanistan)'
301 + config.mapping['pt'] = 'Portuguese'
302 + # config.mapping['pt-BR'] = 'Portuguese (Brazil)'
303 + # config.mapping['pt-PT'] = 'Portuguese (Portugal)'
304 + # config.mapping['ro'] = 'Romanian'
305 + # config.mapping['ro-MD'] = 'Romanian (Moldova)'
306 + # config.mapping['ro-RO'] = 'Romanian (Romania)'
307 + # config.mapping['ru'] = 'Russian'
308 + # config.mapping['ru-RU'] = 'Russian (Russia)'
309 + # config.mapping['ru-UA'] = 'Russian (Ukraine)'
310 + # config.mapping['rw'] = 'Kinyarwanda'
311 + # config.mapping['rw-RW'] = 'Kinyarwanda (Rwanda)'
312 + # config.mapping['sa'] = 'Sanskrit'
313 + # config.mapping['sa-IN'] = 'Sanskrit (India)'
314 + # config.mapping['se'] = 'Northern Sami'
315 + # config.mapping['se-FI'] = 'Northern Sami (Finland)'
316 + # config.mapping['se-NO'] = 'Northern Sami (Norway)'
317 + # config.mapping['sh'] = 'Serbo-Croatian'
318 + # config.mapping['sh-BA'] = 'Serbo-Croatian (Bosnia and Herzegovina)'
319 + # config.mapping['sh-CS'] = 'Serbo-Croatian (Serbia and Montenegro)'
320 + # config.mapping['sh-YU'] = 'Serbo-Croatian (Serbia)'
321 + # config.mapping['si'] = 'Sinhala'
322 + # config.mapping['si-LK'] = 'Sinhala (Sri Lanka)'
323 + # config.mapping['sid'] = 'Sidamo'
324 + # config.mapping['sid-ET'] = 'Sidamo (Ethiopia)'
325 + # config.mapping['sk'] = 'Slovak'
326 + # config.mapping['sk-SK'] = 'Slovak (Slovakia)'
327 + # config.mapping['sl'] = 'Slovenian'
328 + # config.mapping['sl-SI'] = 'Slovenian (Slovenia)'
329 + # config.mapping['so'] = 'Slovenian'
330 + # config.mapping['so-DJ'] = 'Slovenian (Djibouti)'
331 + # config.mapping['so-ET'] = 'Slovenian (Ethiopia)'
332 + # config.mapping['so-KE'] = 'Slovenian (Kenya)'
333 + # config.mapping['so-SO'] = 'Slovenian (Somalia)'
334 + # config.mapping['sq'] = 'Albanian'
335 + # config.mapping['sq-AL'] = 'Albanian (Albania)'
336 + # config.mapping['sr'] = 'Serbian'
337 + # config.mapping['sr-BA'] = 'Serbian (Bosnia and Herzegovina)'
338 + # config.mapping['sr-CS'] = 'Serbian (Serbia and Montenegro)'
339 + # config.mapping['sr-ME'] = 'Serbian (Montenegro)'
340 + # config.mapping['sr-RS'] = 'Serbian (Serbia)'
341 + # config.mapping['sr-YU'] = 'Serbian (Serbia)'
342 + # config.mapping['ss'] = 'Swati'
343 + # config.mapping['ss-SZ'] = 'Swati (Swaziland)'
344 + # config.mapping['ss-ZA'] = 'Swati (South Africa)'
345 + # config.mapping['st'] = 'Southern Sotho'
346 + # config.mapping['st-LS'] = 'Southern Sotho (Lesotho)'
347 + # config.mapping['st-ZA'] = 'Southern Sotho (South Africa)'
348 + # config.mapping['sv'] = 'Swedish'
349 + # config.mapping['sv-FI'] = 'Swedish (Finland)'
350 + config.mapping['sv-se'] = 'Swedish (Sweden)'
351 + # config.mapping['sw'] = 'Swahili'
352 + # config.mapping['sw-KE'] = 'Swahili (Kenya)'
353 + # config.mapping['sw-TZ'] = 'Swahili (Tanzania)'
354 + # config.mapping['syr'] = 'Syriac'
355 + # config.mapping['syr-SY'] = 'Syriac (Syria)'
356 + # config.mapping['ta'] = 'Tamil'
357 + # config.mapping['ta-IN'] = 'Tamil (India)'
358 + # config.mapping['te'] = 'Telugu'
359 + # config.mapping['te-IN'] = 'Telugu (India)'
360 + # config.mapping['tg'] = 'Tajik'
361 + # config.mapping['tg-TJ'] = 'Tajik (Tajikistan)'
362 + # config.mapping['th'] = 'Thai'
363 + # config.mapping['th-TH'] = 'Thai (Thailand)'
364 + # config.mapping['ti'] = 'Tigrinya'
365 + # config.mapping['ti-ER'] = 'Tigrinya (Eritrea)'
366 + # config.mapping['ti-ET'] = 'Tigrinya (Ethiopia)'
367 + # config.mapping['tig'] = 'Tigre'
368 + # config.mapping['tig-ER'] = 'Tigre (Eritrea)'
369 + # config.mapping['tl'] = 'Tagalog'
370 + # config.mapping['tn'] = 'Tswana'
371 + # config.mapping['tn-ZA'] = 'Tswana (South Africa)'
372 + # config.mapping['to'] = 'Tonga'
373 + # config.mapping['to-TO'] = 'Tonga (Tonga)'
374 + # config.mapping['tr'] = 'Turkish'
375 + # config.mapping['tr-TR'] = 'Turkish (Turkey)'
376 + # config.mapping['trv'] = 'Taroko'
377 + # config.mapping['trv-TW'] = 'Taroko (Taiwan)'
378 + # config.mapping['ts'] = 'Tsonga'
379 + # config.mapping['ts-ZA'] = 'Tsonga (South Africa)'
380 + # config.mapping['tt'] = 'Tatar'
381 + # config.mapping['tt-RU'] = 'Tatar (Russia)'
382 + # config.mapping['ug'] = 'Uighur'
383 + # config.mapping['ug-CN'] = 'Uighur (China)'
384 + # config.mapping['uk'] = 'Ukrainian'
385 + # config.mapping['uk-UA'] = 'Ukrainian (Ukraine)'
386 + # config.mapping['ur'] = 'Urdu'
387 + # config.mapping['ur-IN'] = 'Urdu (India)'
388 + # config.mapping['ur-PK'] = 'Urdu (Pakistan)'
389 + # config.mapping['uz'] = 'Uzbek'
390 + # config.mapping['uz-AF'] = 'Uzbek (Afghanistan)'
391 + # config.mapping['uz-UZ'] = 'Uzbek (Uzbekistan)'
392 + # config.mapping['ve'] = 'Venda'
393 + # config.mapping['ve-ZA'] = 'Venda (South Africa)'
394 + # config.mapping['vi'] = 'Vietnamese'
395 + # config.mapping['vi-VN'] = 'Vietnamese (Vietnam)'
396 + # config.mapping['wal'] = 'Walamo'
397 + # config.mapping['wal-ET'] = 'Walamo (Ethiopia)'
398 + # config.mapping['wo'] = 'Wolof'
399 + # config.mapping['wo-SN'] = 'Wolof (Senegal)'
400 + # config.mapping['xh'] = 'Xhosa'
401 + # config.mapping['xh-ZA'] = 'Xhosa (South Africa)'
402 + # config.mapping['yo'] = 'Yoruba'
403 + # config.mapping['yo-NG'] = 'Yoruba (Nigeria)'
404 + # config.mapping['zh'] = 'Chinese'
405 + config.mapping['zh-cn'] = 'Chinese (Simplified)'
406 + # config.mapping['zh-HK'] = 'Chinese (Hong Kong)'
407 + # config.mapping['zh-MO'] = 'Chinese (Macau)'
408 + # config.mapping['zh-SG'] = 'Chinese (Singapore)'
409 + # config.mapping['zh-TW'] = 'Chinese (Traditional)'
410 + # config.mapping['zu'] = 'Zulu'
411 + # config.mapping['zu-ZA'] = 'Zulu (South Africa)
412 +
413 + # config.base_controller = 'ApplicationController'
414 +end
415 +
416 +if Rails.env.production?
417 + Tolk::ApplicationController.authenticator = proc {
418 + authenticate_or_request_with_http_basic do |user_name, password|
419 + user_name == 'JohnRonaldReuel' && password == 'Andyoqenya'
420 + end
421 + }
422 +end
\ No newline at end of file
backend/musik88-web/config/initializers/tolk.rb.example new
+413
@@ -0,0 +1,413 @@
1 +# encoding: utf-8
2 +
3 +# Tolk config file. Generated on December 19, 2021 22:28
4 +# See github.com/tolk/tolk for more informations
5 +
6 +Tolk.config do |config|
7 +
8 + # If you need to add a mapping do it like this :
9 + # May we suggest you use http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
10 + # config.mapping['fr-ES'] = 'Frañol !'
11 +
12 + # config.mapping['aa'] = 'Afar'
13 + # config.mapping['aa-DJ'] = 'Afar (Djibouti)'
14 + # config.mapping['aa-ER'] = 'Afar (Eritrea)'
15 + # config.mapping['aa-ET'] = 'Afar (Ethiopia)'
16 + # config.mapping['af'] = 'Afrikaans'
17 + # config.mapping['af-NA'] = 'Afrikaans (Namibia)'
18 + # config.mapping['af-ZA'] = 'Afrikaans (South Africa)'
19 + # config.mapping['ak'] = 'Akan'
20 + # config.mapping['ak-GH'] = 'Akan (Ghana)'
21 + # config.mapping['am'] = 'Amharic'
22 + # config.mapping['am-ET'] = 'Amharic (Ethiopia)'
23 + # config.mapping['ar'] = 'Arabic'
24 + # config.mapping['ar-AE'] = 'Arabic (United Arab Emirates)'
25 + # config.mapping['ar-BH'] = 'Arabic (Bahrain)'
26 + # config.mapping['ar-DZ'] = 'Arabic (Algeria)'
27 + # config.mapping['ar-EG'] = 'Arabic (Egypt)'
28 + # config.mapping['ar-IQ'] = 'Arabic (Iraq)'
29 + # config.mapping['ar-JO'] = 'Arabic (Jordan)'
30 + # config.mapping['ar-KW'] = 'Arabic (Kuwait)'
31 + # config.mapping['ar-LB'] = 'Arabic (Lebanon)'
32 + # config.mapping['ar-LY'] = 'Arabic (Libya)'
33 + # config.mapping['ar-MA'] = 'Arabic (Morocco)'
34 + # config.mapping['ar-OM'] = 'Arabic (Oman)'
35 + # config.mapping['ar-QA'] = 'Arabic (Qatar)'
36 + # config.mapping['ar-SA'] = 'Arabic (Saudi Arabia)'
37 + # config.mapping['ar-SD'] = 'Arabic (Sudan)'
38 + # config.mapping['ar-SY'] = 'Arabic (Syria)'
39 + # config.mapping['ar-TN'] = 'Arabic (Tunisia)'
40 + # config.mapping['ar-YE'] = 'Arabic (Yemen)'
41 + # config.mapping['as'] = 'Assamese'
42 + # config.mapping['as-IN'] = 'Assamese (India)'
43 + # config.mapping['az'] = 'Azerbaijani'
44 + # config.mapping['az-AZ'] = 'Azerbaijani (Azerbaijan)'
45 + # config.mapping['be'] = 'Belarusian'
46 + # config.mapping['be-BY'] = 'Belarusian (Belarus)'
47 + # config.mapping['bg'] = 'Bulgarian'
48 + # config.mapping['bg-BG'] = 'Bulgarian (Bulgaria)'
49 + # config.mapping['bn'] = 'Bengali'
50 + # config.mapping['bn-BD'] = 'Bengali (Bangladesh)'
51 + # config.mapping['bn-IN'] = 'Bengali (India)'
52 + # config.mapping['bo'] = 'Tibetan'
53 + # config.mapping['bo-CN'] = 'Tibetan (China)'
54 + # config.mapping['bn-IN'] = 'Tibetan (India)'
55 + # config.mapping['bs'] = 'Bosnian'
56 + # config.mapping['bs-BA'] = 'Bosnian (Bosnia and Herzegovina)'
57 + # config.mapping['byn'] = 'Blin'
58 + # config.mapping['byn-ER'] = 'Blin (Eritrea)'
59 + # config.mapping['ca'] = 'Catalan'
60 + # config.mapping['ca-ES'] = 'Catalan (Spain)'
61 + # config.mapping['cch'] = 'Atsam'
62 + # config.mapping['cch-NG'] = 'Atsam (Nigeria)'
63 + # config.mapping['cop'] = 'Coptic'
64 + # config.mapping['cs'] = 'Czech'
65 + # config.mapping['cs-CZ'] = 'Czech (Czech Republic)'
66 + # config.mapping['cy'] = 'Welsh'
67 + # config.mapping['cy-GB'] = 'Welsh (United Kingdom)'
68 + # config.mapping['da'] = 'Danish'
69 + # config.mapping['da-DK'] = 'Danish (Denmark)'
70 + # config.mapping['de'] = 'German'
71 + # config.mapping['de-AT'] = 'German (Austria)'
72 + # config.mapping['de-BE'] = 'German (Belgium)'
73 + # config.mapping['de-CH'] = 'German (Switzerland)'
74 + # config.mapping['de-DE'] = 'German (Germany)'
75 + # config.mapping['de-LI'] = 'German (Liechtenstein)'
76 + # config.mapping['de-LU'] = 'German (Luxembourg)'
77 + # config.mapping['dv'] = 'Divehi'
78 + # config.mapping['dv-MV'] = 'Divehi (Maldives)'
79 + # config.mapping['dz'] = 'Dzongkha'
80 + # config.mapping['dz-BT'] = 'Dzongkha (Bhutan)'
81 + # config.mapping['ee'] = 'Ewe'
82 + # config.mapping['ee-GH'] = 'Ewe (Ghana)'
83 + # config.mapping['ee-TG'] = 'Ewe (Togo)'
84 + # config.mapping['el'] = 'Greek'
85 + # config.mapping['el-CY'] = 'Greek (Cyprus)'
86 + # config.mapping['el-GR'] = 'Greek (Greece)'
87 + # config.mapping['en'] = 'English'
88 + # config.mapping['en-AS'] = 'English (American Samoa)'
89 + # config.mapping['en-AU'] = 'English (Australia)'
90 + # config.mapping['en-BE'] = 'English (Belgium)'
91 + # config.mapping['en-BW'] = 'English (Botswana)'
92 + # config.mapping['en-BZ'] = 'English (Belize)'
93 + # config.mapping['en-CA'] = 'English (Canada)'
94 + # config.mapping['en-GB'] = 'English (United Kingdom)'
95 + # config.mapping['en-GU'] = 'English (Guam)'
96 + # config.mapping['en-HK'] = 'English (Hong Kong)'
97 + # config.mapping['en-IE'] = 'English (Ireland)'
98 + # config.mapping['en-IN'] = 'English (India)'
99 + # config.mapping['en-JM'] = 'English (Jamaica)'
100 + # config.mapping['en-MH'] = 'English (Marshall Islands)'
101 + # config.mapping['en-MP'] = 'English (Northern Mariana Islands)'
102 + # config.mapping['en-MT'] = 'English (Malta)'
103 + # config.mapping['en-NA'] = 'English (Namibia)'
104 + # config.mapping['en-NZ'] = 'English (New Zealand)'
105 + # config.mapping['en-PH'] = 'English (Philippines)'
106 + # config.mapping['en-PK'] = 'English (Pakistan)'
107 + # config.mapping['en-SG'] = 'English (Singapore)'
108 + # config.mapping['en-TT'] = 'English (Trinidad and Tobago)'
109 + # config.mapping['en-UM'] = 'English (United States Minor Outlying Islands)'
110 + # config.mapping['en-US'] = 'English (United States)'
111 + # config.mapping['en-VI'] = 'English (U.S. Virgin Islands)'
112 + # config.mapping['en-ZA'] = 'English (South Africa)'
113 + # config.mapping['en-ZW'] = 'English (Zimbabwe)'
114 + # config.mapping['eo'] = 'Esperanto'
115 + # config.mapping['es'] = 'Spanish'
116 + # config.mapping['es-AR'] = 'Spanish (Argentina)'
117 + # config.mapping['es-BO'] = 'Spanish (Bolivia)'
118 + # config.mapping['es-CL'] = 'Spanish (Chile)'
119 + # config.mapping['es-CO'] = 'Spanish (Colombia)'
120 + # config.mapping['es-CR'] = 'Spanish (Costa Rica)'
121 + # config.mapping['es-DO'] = 'Spanish (Dominican Republic)'
122 + # config.mapping['es-EC'] = 'Spanish (Ecuador)'
123 + # config.mapping['es-ES'] = 'Spanish (Spain)'
124 + # config.mapping['es-GT'] = 'Spanish (Guatemala)'
125 + # config.mapping['es-HN'] = 'Spanish (Honduras)'
126 + # config.mapping['es-MX'] = 'Spanish (Mexico)'
127 + # config.mapping['es-NI'] = 'Spanish (Nicaragua)'
128 + # config.mapping['es-PA'] = 'Spanish (Panama)'
129 + # config.mapping['es-PE'] = 'Spanish (Peru)'
130 + # config.mapping['es-PR'] = 'Spanish (Puerto Rico)'
131 + # config.mapping['es-PY'] = 'Spanish (Paraguay)'
132 + # config.mapping['es-SV'] = 'Spanish (El Salvador)'
133 + # config.mapping['es-US'] = 'Spanish (United States)'
134 + # config.mapping['es-UY'] = 'Spanish (Uruguay)'
135 + # config.mapping['es-VE'] = 'Spanish (Venezuela)'
136 + # config.mapping['et'] = 'Estonian'
137 + # config.mapping['et-EE'] = 'Estonian (Estonia)'
138 + # config.mapping['eu'] = 'Basque'
139 + # config.mapping['eu-ES'] = 'Basque (Spain)'
140 + # config.mapping['fa'] = 'Persian'
141 + # config.mapping['fa-AF'] = 'Persian (Afghanistan)'
142 + # config.mapping['fa-IR'] = 'Persian (Iran)'
143 + # config.mapping['fi'] = 'Finnish'
144 + # config.mapping['fi-FI'] = 'Finnish (Finland)'
145 + # config.mapping['fil'] = 'Filipino'
146 + # config.mapping['fil-PH'] = 'Filipino (Philippines)'
147 + # config.mapping['fo'] = 'Faroese'
148 + # config.mapping['fo-FO'] = 'Faroese (Faroe Islands)'
149 + # config.mapping['fr'] = 'French'
150 + # config.mapping['fr-BE'] = 'French (Belgium)'
151 + # config.mapping['fr-CA'] = 'French (Canada)'
152 + # config.mapping['fr-CH'] = 'French (Switzerland)'
153 + # config.mapping['fr-FR'] = 'French (France)'
154 + # config.mapping['fr-LU'] = 'French (Luxembourg)'
155 + # config.mapping['fr-MC'] = 'French (Monaco)'
156 + # config.mapping['fr-SN'] = 'French (Senegal)'
157 + # config.mapping['fur'] = 'Friulian'
158 + # config.mapping['fur-IT'] = 'Friulian (Italy)'
159 + # config.mapping['ga'] = 'Irish'
160 + # config.mapping['ga-IE'] = 'Irish (Ireland)'
161 + # config.mapping['gaa'] = 'Ga'
162 + # config.mapping['gaa-GH'] = 'Ga (Ghana)'
163 + # config.mapping['gez'] = 'Geez'
164 + # config.mapping['gez-ER'] = 'Geez (Eritrea)'
165 + # config.mapping['gez-ET'] = 'Geez (Ethiopia)'
166 + # config.mapping['gl'] = 'Gallegan'
167 + # config.mapping['gl-ES'] = 'Gallegan (Spain)'
168 + # config.mapping['gsw'] = 'Swiss German'
169 + # config.mapping['gsw-CH'] = 'Swiss German (Swiss)'
170 + # config.mapping['gu'] = 'Gujarati'
171 + # config.mapping['gu-IN'] = 'Gujarati (India)'
172 + # config.mapping['gv'] = 'Manx'
173 + # config.mapping['gv-GB'] = 'Manx (United Kingdom)'
174 + # config.mapping['ha'] = 'Hausa'
175 + # config.mapping['ha-GH'] = 'Hausa (Ghana)'
176 + # config.mapping['ha-NE'] = 'Hausa (Niger)'
177 + # config.mapping['ha-NG'] = 'Hausa (Nigeria)'
178 + # config.mapping['ha-SD'] = 'Hausa (Sudan)'
179 + # config.mapping['haw'] = 'Hawaiian'
180 + # config.mapping['haw-US'] = 'Hawaiian (United States)'
181 + # config.mapping['he'] = 'Hebrew'
182 + # config.mapping['he-IL'] = 'Hebrew (Israel)'
183 + # config.mapping['hi'] = 'Hindi'
184 + # config.mapping['hi-IN'] = 'Hindi (India)'
185 + # config.mapping['hr'] = 'Croatian'
186 + # config.mapping['hr-HR'] = 'Croatian (Croatia)'
187 + # config.mapping['hu'] = 'Hungarian'
188 + # config.mapping['hu-HU'] = 'Hungarian (Hungary)'
189 + # config.mapping['hy'] = 'Armenian'
190 + # config.mapping['ia'] = 'Interlingua'
191 + # config.mapping['id'] = 'Indonesian'
192 + # config.mapping['id-ID'] = 'Indonesian (Indonesia)'
193 + # config.mapping['ig'] = 'Igbo'
194 + # config.mapping['ig-NG'] = 'Igbo (Nigeria)'
195 + # config.mapping['ii'] = 'Sichuan Yi'
196 + # config.mapping['ii-CN'] = 'Sichuan Yi (China)'
197 + # config.mapping['in'] = 'Indonesian'
198 + # config.mapping['is'] = 'Icelandic'
199 + # config.mapping['is-IS'] = 'Icelandic (Iceland)'
200 + # config.mapping['it'] = 'Italian'
201 + # config.mapping['it-CH'] = 'Italian (Switzerland)'
202 + # config.mapping['it-IT'] = 'Italian (Italy)'
203 + # config.mapping['iu'] = 'Inuktitut'
204 + # config.mapping['iw'] = 'Hebrew'
205 + # config.mapping['ja'] = 'Japanese'
206 + # config.mapping['ja-JP'] = 'Japanese (Japan)'
207 + # config.mapping['ka'] = 'Georgian'
208 + # config.mapping['ka-GE'] = 'Georgian (Georgia)'
209 + # config.mapping['kaj'] = 'Jju'
210 + # config.mapping['kaj-NG'] = 'Jju (Nigeria)'
211 + # config.mapping['kam'] = 'Kamba'
212 + # config.mapping['kam-KE'] = 'Kamba (Kenya)'
213 + # config.mapping['kcg'] = 'Tyap'
214 + # config.mapping['kcg-NG'] = 'Tyap (Nigeria)'
215 + # config.mapping['kfo'] = 'Koro'
216 + # config.mapping['kfo-CI'] = 'Koro (Ivory Coast)'
217 + # config.mapping['kk'] = 'Kazakh'
218 + # config.mapping['kk-KZ'] = 'Kazakh (Kazakhstan)'
219 + # config.mapping['kl'] = 'Kalaallisut'
220 + # config.mapping['kl-GL'] = 'Kalaallisut (Greenland)'
221 + # config.mapping['km'] = 'Khmer'
222 + # config.mapping['km-KH'] = 'Khmer (Cambodia)'
223 + # config.mapping['kn'] = 'Kannada'
224 + # config.mapping['kn-IN'] = 'Kannada (India)'
225 + # config.mapping['ko'] = 'Korean'
226 + # config.mapping['ko-KR'] = 'Korean (South Korea)'
227 + # config.mapping['kok'] = 'Konkani'
228 + # config.mapping['kok-IN'] = 'Konkani (India)'
229 + # config.mapping['kpe'] = 'Kpelle'
230 + # config.mapping['kpe-GN'] = 'Kpelle (Guinea)'
231 + # config.mapping['kpe-LR'] = 'Kpelle (Liberia)'
232 + # config.mapping['ku'] = 'Kurdish'
233 + # config.mapping['ku-IQ'] = 'Kurdish (Iraq)'
234 + # config.mapping['Iran'] = 'Kurdish (Iran)'
235 + # config.mapping['Syria'] = 'Kurdish (Syria)'
236 + # config.mapping['Turkey'] = 'Kurdish (Turkey)'
237 + # config.mapping['kw'] = 'Cornish'
238 + # config.mapping['kw-GB'] = 'Cornish (United Kingdom)'
239 + # config.mapping['ky'] = 'Kirghiz'
240 + # config.mapping['ky-KG'] = 'Kirghiz (Kyrgyzstan)'
241 + # config.mapping['ln'] = 'Lingala'
242 + # config.mapping['ln-CD'] = 'Lingala (Congo - Kinshasa)'
243 + # config.mapping['ln-CG'] = 'Lingala (Congo - Brazzaville)'
244 + # config.mapping['lo'] = 'Lao'
245 + # config.mapping['lo-LA'] = 'Lao (Laos)'
246 + # config.mapping['lt'] = 'Lithuanian'
247 + # config.mapping['lt-LT'] = 'Lithuanian (Lithuania)'
248 + # config.mapping['lv'] = 'Latvian'
249 + # config.mapping['lv-LV'] = 'Latvian (Latvia)'
250 + # config.mapping['mk'] = 'Macedonian'
251 + # config.mapping['mk-MK'] = 'Macedonian (Macedonia)'
252 + # config.mapping['ml'] = 'Malayalam'
253 + # config.mapping['ml-IN'] = 'Malayalam (India)'
254 + # config.mapping['mn'] = 'Mongolian'
255 + # config.mapping['mn-CN'] = 'Mongolian (China)'
256 + # config.mapping['mn-MN'] = 'Mongolian (Mongolia)'
257 + # config.mapping['mo'] = 'Romanian'
258 + # config.mapping['mr'] = 'Marathi'
259 + # config.mapping['mr-IN'] = 'Marathi (India)'
260 + # config.mapping['ms'] = 'Malay'
261 + # config.mapping['ms-BN'] = 'Malay (Brunei)'
262 + # config.mapping['ms-MY'] = 'Malay (Malaysia)'
263 + # config.mapping['mt'] = 'Maltese'
264 + # config.mapping['mt-MT'] = 'Maltese (Malta)'
265 + # config.mapping['my'] = 'Burmese'
266 + # config.mapping['my-MM'] = 'Burmese (Myanmar)'
267 + # config.mapping['nb'] = 'Norwegian Bokmal'
268 + # config.mapping['nb-NO'] = 'Norwegian Bokmal (Norway)'
269 + # config.mapping['nds'] = 'Low German'
270 + # config.mapping['nds-DE'] = 'Low German (Germany)'
271 + # config.mapping['ne'] = 'Nepali'
272 + # config.mapping['ne-IN'] = 'Nepali (India)'
273 + # config.mapping['ne-NP'] = 'Nepali (Nepal)'
274 + # config.mapping['nl'] = 'Dutch'
275 + # config.mapping['nl-BE'] = 'Dutch (Belgium)'
276 + # config.mapping['nl-NL'] = 'Dutch (Netherlands)'
277 + # config.mapping['nn'] = 'Norwegian Nynorsk'
278 + # config.mapping['nn-NO'] = 'Norwegian Nynorsk (Norway)'
279 + # config.mapping['no'] = 'Norwegian'
280 + # config.mapping['nr'] = 'South Ndebele'
281 + # config.mapping['nr-ZA'] = 'South Ndebele (South Africa)'
282 + # config.mapping['nso'] = 'Northern Sotho'
283 + # config.mapping['nso-ZA'] = 'Northern Sotho (South Africa)'
284 + # config.mapping['ny'] = 'Nyanja'
285 + # config.mapping['ny-MW'] = 'Nyanja (Malawi)'
286 + # config.mapping['oc'] = 'Occitan'
287 + # config.mapping['oc-FR'] = 'Occitan (France)'
288 + # config.mapping['om'] = 'Oromo'
289 + # config.mapping['om-ET'] = 'Oromo (Ethiopia)'
290 + # config.mapping['om-KE'] = 'Oromo (Kenya)'
291 + # config.mapping['or'] = 'Oriya'
292 + # config.mapping['or-IN'] = 'Oriya (India)'
293 + # config.mapping['pa'] = 'Punjabi'
294 + # config.mapping['pa-IN'] = 'Punjabi (India)'
295 + # config.mapping['pa-PK'] = 'Punjabi (Pakistan)'
296 + # config.mapping['pl'] = 'Polish'
297 + # config.mapping['pl-PL'] = 'Polish (Poland)'
298 + # config.mapping['ps'] = 'Polish'
299 + # config.mapping['ps-AF'] = 'Polish (Afghanistan)'
300 + # config.mapping['pt'] = 'Portuguese'
301 + # config.mapping['pt-BR'] = 'Portuguese (Brazil)'
302 + # config.mapping['pt-PT'] = 'Portuguese (Portugal)'
303 + # config.mapping['ro'] = 'Romanian'
304 + # config.mapping['ro-MD'] = 'Romanian (Moldova)'
305 + # config.mapping['ro-RO'] = 'Romanian (Romania)'
306 + # config.mapping['ru'] = 'Russian'
307 + # config.mapping['ru-RU'] = 'Russian (Russia)'
308 + # config.mapping['ru-UA'] = 'Russian (Ukraine)'
309 + # config.mapping['rw'] = 'Kinyarwanda'
310 + # config.mapping['rw-RW'] = 'Kinyarwanda (Rwanda)'
311 + # config.mapping['sa'] = 'Sanskrit'
312 + # config.mapping['sa-IN'] = 'Sanskrit (India)'
313 + # config.mapping['se'] = 'Northern Sami'
314 + # config.mapping['se-FI'] = 'Northern Sami (Finland)'
315 + # config.mapping['se-NO'] = 'Northern Sami (Norway)'
316 + # config.mapping['sh'] = 'Serbo-Croatian'
317 + # config.mapping['sh-BA'] = 'Serbo-Croatian (Bosnia and Herzegovina)'
318 + # config.mapping['sh-CS'] = 'Serbo-Croatian (Serbia and Montenegro)'
319 + # config.mapping['sh-YU'] = 'Serbo-Croatian (Serbia)'
320 + # config.mapping['si'] = 'Sinhala'
321 + # config.mapping['si-LK'] = 'Sinhala (Sri Lanka)'
322 + # config.mapping['sid'] = 'Sidamo'
323 + # config.mapping['sid-ET'] = 'Sidamo (Ethiopia)'
324 + # config.mapping['sk'] = 'Slovak'
325 + # config.mapping['sk-SK'] = 'Slovak (Slovakia)'
326 + # config.mapping['sl'] = 'Slovenian'
327 + # config.mapping['sl-SI'] = 'Slovenian (Slovenia)'
328 + # config.mapping['so'] = 'Slovenian'
329 + # config.mapping['so-DJ'] = 'Slovenian (Djibouti)'
330 + # config.mapping['so-ET'] = 'Slovenian (Ethiopia)'
331 + # config.mapping['so-KE'] = 'Slovenian (Kenya)'
332 + # config.mapping['so-SO'] = 'Slovenian (Somalia)'
333 + # config.mapping['sq'] = 'Albanian'
334 + # config.mapping['sq-AL'] = 'Albanian (Albania)'
335 + # config.mapping['sr'] = 'Serbian'
336 + # config.mapping['sr-BA'] = 'Serbian (Bosnia and Herzegovina)'
337 + # config.mapping['sr-CS'] = 'Serbian (Serbia and Montenegro)'
338 + # config.mapping['sr-ME'] = 'Serbian (Montenegro)'
339 + # config.mapping['sr-RS'] = 'Serbian (Serbia)'
340 + # config.mapping['sr-YU'] = 'Serbian (Serbia)'
341 + # config.mapping['ss'] = 'Swati'
342 + # config.mapping['ss-SZ'] = 'Swati (Swaziland)'
343 + # config.mapping['ss-ZA'] = 'Swati (South Africa)'
344 + # config.mapping['st'] = 'Southern Sotho'
345 + # config.mapping['st-LS'] = 'Southern Sotho (Lesotho)'
346 + # config.mapping['st-ZA'] = 'Southern Sotho (South Africa)'
347 + # config.mapping['sv'] = 'Swedish'
348 + # config.mapping['sv-FI'] = 'Swedish (Finland)'
349 + # config.mapping['sv-SE'] = 'Swedish (Sweden)'
350 + # config.mapping['sw'] = 'Swahili'
351 + # config.mapping['sw-KE'] = 'Swahili (Kenya)'
352 + # config.mapping['sw-TZ'] = 'Swahili (Tanzania)'
353 + # config.mapping['syr'] = 'Syriac'
354 + # config.mapping['syr-SY'] = 'Syriac (Syria)'
355 + # config.mapping['ta'] = 'Tamil'
356 + # config.mapping['ta-IN'] = 'Tamil (India)'
357 + # config.mapping['te'] = 'Telugu'
358 + # config.mapping['te-IN'] = 'Telugu (India)'
359 + # config.mapping['tg'] = 'Tajik'
360 + # config.mapping['tg-TJ'] = 'Tajik (Tajikistan)'
361 + # config.mapping['th'] = 'Thai'
362 + # config.mapping['th-TH'] = 'Thai (Thailand)'
363 + # config.mapping['ti'] = 'Tigrinya'
364 + # config.mapping['ti-ER'] = 'Tigrinya (Eritrea)'
365 + # config.mapping['ti-ET'] = 'Tigrinya (Ethiopia)'
366 + # config.mapping['tig'] = 'Tigre'
367 + # config.mapping['tig-ER'] = 'Tigre (Eritrea)'
368 + # config.mapping['tl'] = 'Tagalog'
369 + # config.mapping['tn'] = 'Tswana'
370 + # config.mapping['tn-ZA'] = 'Tswana (South Africa)'
371 + # config.mapping['to'] = 'Tonga'
372 + # config.mapping['to-TO'] = 'Tonga (Tonga)'
373 + # config.mapping['tr'] = 'Turkish'
374 + # config.mapping['tr-TR'] = 'Turkish (Turkey)'
375 + # config.mapping['trv'] = 'Taroko'
376 + # config.mapping['trv-TW'] = 'Taroko (Taiwan)'
377 + # config.mapping['ts'] = 'Tsonga'
378 + # config.mapping['ts-ZA'] = 'Tsonga (South Africa)'
379 + # config.mapping['tt'] = 'Tatar'
380 + # config.mapping['tt-RU'] = 'Tatar (Russia)'
381 + # config.mapping['ug'] = 'Uighur'
382 + # config.mapping['ug-CN'] = 'Uighur (China)'
383 + # config.mapping['uk'] = 'Ukrainian'
384 + # config.mapping['uk-UA'] = 'Ukrainian (Ukraine)'
385 + # config.mapping['ur'] = 'Urdu'
386 + # config.mapping['ur-IN'] = 'Urdu (India)'
387 + # config.mapping['ur-PK'] = 'Urdu (Pakistan)'
388 + # config.mapping['uz'] = 'Uzbek'
389 + # config.mapping['uz-AF'] = 'Uzbek (Afghanistan)'
390 + # config.mapping['uz-UZ'] = 'Uzbek (Uzbekistan)'
391 + # config.mapping['ve'] = 'Venda'
392 + # config.mapping['ve-ZA'] = 'Venda (South Africa)'
393 + # config.mapping['vi'] = 'Vietnamese'
394 + # config.mapping['vi-VN'] = 'Vietnamese (Vietnam)'
395 + # config.mapping['wal'] = 'Walamo'
396 + # config.mapping['wal-ET'] = 'Walamo (Ethiopia)'
397 + # config.mapping['wo'] = 'Wolof'
398 + # config.mapping['wo-SN'] = 'Wolof (Senegal)'
399 + # config.mapping['xh'] = 'Xhosa'
400 + # config.mapping['xh-ZA'] = 'Xhosa (South Africa)'
401 + # config.mapping['yo'] = 'Yoruba'
402 + # config.mapping['yo-NG'] = 'Yoruba (Nigeria)'
403 + # config.mapping['zh'] = 'Chinese'
404 + # config.mapping['zh-CN'] = 'Chinese (Simplified)'
405 + # config.mapping['zh-HK'] = 'Chinese (Hong Kong)'
406 + # config.mapping['zh-MO'] = 'Chinese (Macau)'
407 + # config.mapping['zh-SG'] = 'Chinese (Singapore)'
408 + # config.mapping['zh-TW'] = 'Chinese (Traditional)'
409 + # config.mapping['zu'] = 'Zulu'
410 + # config.mapping['zu-ZA'] = 'Zulu (South Africa)
411 +
412 + # config.base_controller = 'ApplicationController'
413 +end
backend/musik88-web/config/initializers/will_paginate.rb new
+19
@@ -0,0 +1,19 @@
1 +if defined?(WillPaginate)
2 + module WillPaginate
3 + module ActiveRecord
4 + module RelationMethods
5 + def per(value = nil)
6 + per_page(value)
7 + end
8 +
9 + def total_count
10 + count
11 + end
12 + end
13 + end
14 +
15 + module CollectionMethods
16 + alias num_pages total_pages
17 + end
18 + end
19 +end
backend/musik88-web/config/initializers/wrap_parameters.rb new
+14
@@ -0,0 +1,14 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# This file contains settings for ActionController::ParamsWrapper which
4 +# is enabled by default.
5 +
6 +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 +ActiveSupport.on_load(:action_controller) do
8 + wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 +end
10 +
11 +# To enable root element in JSON for ActiveRecord objects.
12 +# ActiveSupport.on_load(:active_record) do
13 +# self.include_root_in_json = true
14 +# end
backend/musik88-web/config/locales/ar.yml new
+520
@@ -0,0 +1,520 @@
1 +---
2 +"ar":
3 + "10_most_loved_songs": >-
4 + 10 Most Loved Songs
5 + "active_admin":
6 + "filters":
7 + "predicates":
8 + "from": >-
9 + dari tanggal
10 + "to": >-
11 + sampai tanggal
12 + "activerecord":
13 + "errors":
14 + "models":
15 + "album":
16 + "attributes":
17 + "description":
18 + "too_short": >-
19 + Deskripsi artis tidak boleh kosong
20 + "name":
21 + "too_short": >-
22 + Nama album tidak boleh kosong
23 + "artist":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + Deskripsi artis tidak boleh kosong
28 + "name":
29 + "too_short": >-
30 + Nama artis tidak boleh kosong
31 + "chord":
32 + "attributes":
33 + "chord":
34 + "too_short": >-
35 + Chord lagu tidak boleh kosong
36 + "lyric":
37 + "attributes":
38 + "lyric":
39 + "too_short": >-
40 + Lirik lagu tidak boleh kosong
41 + "page":
42 + "attributes":
43 + "page_content":
44 + "too_short": >-
45 + Konten halaman tidak boleh kosong
46 + "page_slug":
47 + "too_short": >-
48 + Slug halaman tidak boleh kosong
49 + "song":
50 + "attributes":
51 + "album":
52 + "too_short": >-
53 + Album tidak boleh kosong
54 + "description":
55 + "too_short": >-
56 + Deskripsi lagu tidak boleh kosong
57 + "name":
58 + "too_short": >-
59 + Nama lagu tidak boleh kosong
60 + "user":
61 + "attributes":
62 + "email":
63 + "blank": >-
64 + Email tidak boleh kosong
65 + "invalid": >-
66 + Email tidak valid
67 + "name":
68 + "blank": >-
69 + Nama tidak boleh kosong
70 + "password":
71 + "blank": >-
72 + Password tidak boleh kosong
73 + "too_short": >-
74 + Password terlalu pendek
75 + "password_confirmation":
76 + "confirmation": >-
77 + Konfirmasi password tidak sama
78 + "album_artists": >-
79 + Artists
80 + "album_song_list": >-
81 + Song list
82 + "artist_profile_album_contribution_title": >-
83 + %{artist} also appears on:
84 + "artist_profile_album_title": >-
85 + %{artist}'s Albums
86 + "artist_profile_title": >-
87 + %{artist} Profile
88 + "breadcrumb_home": >-
89 + Home
90 + "breadcrumb_most_loved_songs": >-
91 + Most Loved Songs
92 + "by": >-
93 + Loved by
94 + "cancel": >-
95 + Cancel
96 + "confirm_email": >-
97 + أكد بريدك الإلكتروني
98 + "create_account": >-
99 + Create account
100 + "discussion": >-
101 + Discussion
102 + "email": >-
103 + Email
104 + "find_your_artist": >-
105 + Find Your Favorite Artist
106 + "find_your_favorite_album": >-
107 + Find Your New Favorite Album
108 + "find_your_favorite_songs": >-
109 + Find Your Favorite Songs
110 + "followers": >-
111 + Follower
112 + "following": >-
113 + Following
114 + "footer": >-
115 + Created for fun by %{profile} from all around the %{instagram} &middot; &copy;
116 + 2021
117 + "footer_world": >-
118 + world
119 + "forgot_password": >-
120 + Forgot password?
121 + "gending_add": >-
122 + Share your gending notation
123 + "gending_add_explanation": >-
124 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
125 + token.
126 + "gending_description": >-
127 + Description
128 + "gending_name": >-
129 + Gending Name
130 + "gending_notation": >-
131 + Gending Notation
132 + "gending_notation_add": >-
133 + Add gending notation
134 + "gending_notation_link": >-
135 + Javanese gending notation %{slug}
136 + "gending_notation_meta_desciption": >-
137 + This is the javanese gending notation for %{title} gending.
138 + "gending_notation_page_title": >-
139 + Javanese Gending Notation %{title}
140 + "guitar_chord_image": >-
141 + Guitar Chord Image
142 + "guitar_chord_link": >-
143 + Guitar chord %{slug}
144 + "guitar_chord_video": >-
145 + Guitar Chord Video
146 + "guitar_chords_shape": >-
147 + Guitar Chords Shape
148 + "h2_find_new_albums": >-
149 + Find New Albums Here!
150 + "h2_find_new_artists": >-
151 + Find New Artists Here!
152 + "h2_find_new_songs": >-
153 + Find New Musics Here!
154 + "have_account": >-
155 + Already have account?
156 + "hello": >-
157 + Welcome to Musik 88
158 + "home_guitar_chords": >-
159 + Guitar Chords
160 + "home_guitar_chords_headline": >-
161 + Submit your guitar chords and earn %{bach_link} reputation token for your contribution.
162 + "home_headline": >-
163 + أكبر قاعدة بيانات موسيقية في العالم!
164 + "home_javanese_gending_notation": >-
165 + Javanese Gending Notation
166 + "home_javanese_gending_notation_subheadline": >-
167 + Digitalize Javanese gamelan and earn %{bach_link} reputation token for your contribution.
168 + "home_lyrics": >-
169 + Song Lyrics
170 + "home_new_entries": >-
171 + New Entries
172 + "home_others": >-
173 + Others
174 + "home_others_headline": >-
175 + Check out my other projects.
176 + "home_popular": >-
177 + Popular
178 + "home_see_all": >-
179 + See all
180 + "home_share_gamelan_notation_now": >-
181 + Share gending notation!
182 + "home_share_now": >-
183 + Share your chord!
184 + "home_signup_now": >-
185 + Signup now!
186 + "home_songs": >-
187 + Songs
188 + "home_subheadline": >-
189 + أوتار الجيتار؟ كلمات الاغنية؟ استعراض الألبوم؟ تدوين جنس الجاوية؟ إلخ! اشترك وساهم
190 + (أضف فنانين وألبومات وكلمات غنائية وما إلى ذلك) واكسب %{bach_link} رمز سمعة مميز.
191 + "home_youtube_karaoke": >-
192 + Youtube Karaoke
193 + "home_youtube_karaoke_headline": >-
194 + Karaoke version of your favorite YouTube music video powered by AI.
195 + "invalid_login": >-
196 + Invalid email/password combination
197 + "locale_name": >-
198 + العربية الفصحى
199 + "login": >-
200 + Login!
201 + "login_with_facebook": >-
202 + Login with Facebook
203 + "lyric": >-
204 + Song Lyric
205 + "lyric_cannot_be_empty": >-
206 + Lyric can't be empty
207 + "my_profile": >-
208 + My Profile
209 + "my_profile_link_href": >-
210 + this secure link
211 + "my_profile_link_html": >-
212 + This is your public profile. Follow %{href} to edit your profile.
213 + "my_profile_private": >-
214 + This profile is private
215 + "name": >-
216 + Name
217 + "nav_albums": >-
218 + Albums
219 + "nav_artists": >-
220 + Artists
221 + "nav_change_language": >-
222 + Change Language:
223 + "nav_chords": >-
224 + Guitar Chords
225 + "nav_chords_shapes": >-
226 + Guitar Chords Diagram
227 + "nav_community": >-
228 + Community
229 + "nav_en": >-
230 + English
231 + "nav_footer_about": >-
232 + About Us
233 + "nav_footer_company": >-
234 + Company
235 + "nav_footer_contact": >-
236 + Contact
237 + "nav_footer_follow_us": >-
238 + Follow Us
239 + "nav_footer_privacy": >-
240 + Privacy Policy
241 + "nav_footer_term": >-
242 + Term of Use
243 + "nav_guitar": >-
244 + Guitar
245 + "nav_id": >-
246 + Bahasa Indonesia
247 + "nav_javanese_gamelan": >-
248 + Javanese Gamelan
249 + "nav_javanese_gamelan_about": >-
250 + About Javanese Gamelan
251 + "nav_javanese_gending": >-
252 + Javanese Gending
253 + "nav_javanese_gending_notation": >-
254 + Javanese Gending Notation
255 + "nav_login": >-
256 + Login
257 + "nav_login_dropdown": >-
258 + Account
259 + "nav_logout": >-
260 + Log out
261 + "nav_lyrics": >-
262 + Song Lyrics
263 + "nav_music_database": >-
264 + Musics
265 + "nav_new_entries": >-
266 + New Entries
267 + "nav_signup": >-
268 + Signup
269 + "nav_songs": >-
270 + Songs
271 + "nav_ukulele_chords": >-
272 + Ukulele Chords (soon)
273 + "need_to_logged_in": >-
274 + You need to logged in before giving this love. Don't have account yet? Just signup,
275 + it's so easy. You can use your Facebook account if you want.
276 + "need_to_logged_in_my_profile": >-
277 + You need to be logged in to perform this task.
278 + "new_user": >-
279 + New user?
280 + "or": >-
281 + Or
282 + "our_community": >-
283 + Our Community
284 + "our_guitar_chords_collection": >-
285 + Our Newest Guitar Chords Collections
286 + "our_newest_song_lyrics": >-
287 + Our Newest Song Lyrics Collection
288 + "password": >-
289 + Password
290 + "password_button_update": >-
291 + Update
292 + "password_confirmation": >-
293 + Password confirmation
294 + "password_new": >-
295 + Enter new password
296 + "password_new_confirmation": >-
297 + Confirm password
298 + "routes":
299 + "about_us": >-
300 + about-us
301 + "add_gending": >-
302 + add-gending-gamelan
303 + "add_javanese_gamelan_notation": >-
304 + add-javanese-gamelan-notation
305 + "album": >-
306 + albums
307 + "album_add": >-
308 + add-album
309 + "album_detail": >-
310 + albums
311 + "artist": >-
312 + artists
313 + "artist_add": >-
314 + add-artist
315 + "artist_detail": >-
316 + artists
317 + "audio_files": >-
318 + audio-files
319 + "bass_backing_track": >-
320 + bass-backing-track-:slug
321 + "community": >-
322 + community
323 + "contact": >-
324 + contact
325 + "drum_backing_track": >-
326 + drum-backing-track-:slug
327 + "followers": >-
328 + followers
329 + "following": >-
330 + following
331 + "guitar_backing_track": >-
332 + guitar-backing-track-:slug
333 + "guitar_chord_shape": >-
334 + guitar-chord-:slug
335 + "guitar_chord_shapes": >-
336 + all-guitar-chords
337 + "guitar_chords": >-
338 + guitar-chords
339 + "guitar_chords_add": >-
340 + add-chord
341 + "guitar_chords_detail": >-
342 + guitar-chords-:slug
343 + "guitar_chords_history": >-
344 + guitar-chords-:slug/history
345 + "guitar_chords_review": >-
346 + guitar-chords-:slug/review-chord
347 + "javanese_gamelan": >-
348 + javanese-gamelan
349 + "javanese_gamelan_notation": >-
350 + javanese-gamelan
351 + "javanese_gending": >-
352 + javanese-gending
353 + "javanese_gending_detail": >-
354 + javanese-gending-gamelan-:slug
355 + "javanese_gending_gamelan_notation": >-
356 + notation-for-javanese-gamelan
357 + "javanese_gending_gamelan_notation_detail": >-
358 + notation-for-javanese-gamelan-:slug
359 + "login": >-
360 + login
361 + "most_loved_songs": >-
362 + most-loved-songs
363 + "privacy_policy": >-
364 + privacy-policy
365 + "signup": >-
366 + signup
367 + "song": >-
368 + songs
369 + "song_add": >-
370 + add-song
371 + "song_audio": >-
372 + song-audio
373 + "song_bass_tab": >-
374 + bass-tab-:slug
375 + "song_chords": >-
376 + song-chords
377 + "song_detail": >-
378 + songs
379 + "song_karaoke_detail": >-
380 + karaoke-song-:slug
381 + "song_lyrics": >-
382 + song-lyrics
383 + "song_lyrics_detail": >-
384 + song-lyrics-:slug
385 + "term_of_use": >-
386 + term-of-use
387 + "user_rep": >-
388 + reputation
389 + "see_album_detail": >-
390 + See album detail
391 + "see_artist_detail": >-
392 + See artist detail
393 + "see_chord_detail": >-
394 + See chord detail
395 + "see_song_detail": >-
396 + See song detail
397 + "signup": >-
398 + Signup
399 + "signup_now": >-
400 + Signup now!
401 + "signup_to_claim": >-
402 + Is this yours?
403 + "signup_with_facebook": >-
404 + Signup with Facebook
405 + "site_name": >-
406 + Musik88.com
407 + "somebody_to_love": >-
408 + Love me
409 + "song_audio_page_title": >-
410 + Audio files for %{song_name}
411 + "song_chord_page_title": >-
412 + Guitar chord %{song_name} by %{song_artist_name}
413 + "song_chord_unavailable_content": >-
414 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
415 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
416 + that's all we need.
417 + "song_chord_unavailable_meta_description": >-
418 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
419 + album.
420 + "song_chords_page_title": >-
421 + %{song_name} Chords
422 + "song_detail": >-
423 + Song detail
424 + "song_detail_page_meta_title": >-
425 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
426 + album/single.
427 + "song_detail_page_title": >-
428 + %{song_name} Song Review
429 + "song_generate_your_own_bass_backing_track": >-
430 + Generate %{song_name} Bass Backing Track
431 + "song_generate_your_own_drum_backing_track": >-
432 + Generate %{song_name} Drum Backing Track
433 + "song_generate_your_own_karaoke": >-
434 + Generate %{song_name} Karaoke File
435 + "song_generate_your_own_stem_files": >-
436 + Generate STEM Files For %{song_name}
437 + "song_karaoke_download": >-
438 + Download Karaoke Version
439 + "song_karaoke_page_title": >-
440 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
441 + Album
442 + "song_karaoke_title": >-
443 + %{filename} Karaoke Version
444 + "song_karaoke_unavailable_content": >-
445 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
446 + "song_karaoke_unavailable_meta_description": >-
447 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
448 + Album
449 + "song_lyric_detail": >-
450 + Song lyric %{title}
451 + "song_lyric_page_title": >-
452 + %{song_name} Lyric by %{song_artist_name}
453 + "song_lyric_unavailable_content": >-
454 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
455 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
456 + or %{href} to this song so we can just focus on finding the chords.
457 + "song_lyric_unavailable_content_add_lyric": >-
458 + Add Lyric
459 + "song_lyric_unavailable_meta_description": >-
460 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
461 + album.
462 + "song_lyrics_page_title": >-
463 + Song Lyrics %{song_name}
464 + "song_menu_aside_audio": >-
465 + Audio
466 + "song_menu_aside_chords": >-
467 + Chords
468 + "song_menu_aside_info": >-
469 + Info
470 + "song_menu_aside_lyrics": >-
471 + Lyrics
472 + "song_menu_bass_backing_track": >-
473 + Bass backing track %{song_name}
474 + "song_menu_bass_tab": >-
475 + Bass tab %{song_name}
476 + "song_menu_drum_backing_track": >-
477 + Drum backing track %{song_name}
478 + "song_menu_guitar_chord": >-
479 + Guitar Chord %{song_name}
480 + "song_menu_karaoke": >-
481 + Karaoke Version %{song_name}
482 + "song_menu_lyric": >-
483 + Song Lyric %{song_name}
484 + "song_updated_at": >-
485 + Updated: %{date}
486 + "still_login": >-
487 + Remember me
488 + "top_10_albums": >-
489 + Top 10 Albums
490 + "top_10_artists": >-
491 + Top 10 Artists
492 + "top_10_guitar_chords": >-
493 + Top 10 Guitar Chords
494 + "top_10_song_lyrics": >-
495 + Top 10 Song Lyrics
496 + "top_10_songs": >-
497 + Top 10 Songs
498 + "user_profile": >-
499 + Profile
500 + "user_reset_password_email_heading": >-
501 + Reset password link only for you, %{user_name}!
502 + "user_reset_password_email_heading_p1": >-
503 + You are getting this email because you are trying to access your account.
504 + "user_reset_password_email_heading_p2": >-
505 + Click %{href} to reset your password.
506 + "user_reset_password_email_link": >-
507 + this one time link
508 + "user_send_reset_password_instructions_subject": >-
509 + Reset password instruction
510 + "user_signup_email_heading": >-
511 + Thanks for signing up, %{user_name}!
512 + "user_signup_email_heading_p1": >-
513 + Thanks for joining us!
514 + "user_signup_email_heading_p2": >-
515 + We are in a mission to digitalize underrepresented musics in the world. %{href}
516 + and start contributing :)
517 + "user_signup_email_subject": >-
518 + Welcome to Musik88!
519 + "welcome_user": >-
520 + Welcome, %{user_name}!
backend/musik88-web/config/locales/de-de.yml new
+437
@@ -0,0 +1,437 @@
1 +---
2 +"de-de":
3 + "10_most_loved_songs": >-
4 + 10 beliebteste Lieder
5 + "add_chord_validation_success": >-
6 + Vielen Dank für Ihren Beitrag!
7 + "album_artists": >-
8 + Künstler
9 + "artist_profile_album_contribution_title": >-
10 + %{artist} erscheint auch auf:
11 + "artist_profile_album_title": >-
12 + Alben von %{artist}
13 + "artist_profile_title": >-
14 + %{artist}-Profil
15 + "breadcrumb_home": >-
16 + Heim
17 + "breadcrumb_most_loved_songs": >-
18 + Die beliebtesten Lieder
19 + "by": >-
20 + Loved by
21 + "cancel": >-
22 + Cancel
23 + "confirm_email": >-
24 + Bestätige deine E-Mail
25 + "create_account": >-
26 + Benutzerkonto erstellen
27 + "email": >-
28 + Email
29 + "find_your_artist": >-
30 + Find Your Favorite Artist
31 + "find_your_favorite_album": >-
32 + Finden Sie Ihr neues Lieblingsalbum
33 + "find_your_favorite_songs": >-
34 + Find Your Favorite Songs
35 + "followers": >-
36 + Anhängerin
37 + "following": >-
38 + Folge
39 + "footer": >-
40 + Aus Spaß erstellt von %{profile} aus der ganzen %{instagram} &middot; &copy; 2021
41 + "footer_world": >-
42 + Welt
43 + "forgot_password": >-
44 + Passwort vergessen?
45 + "gending_add": >-
46 + Share your gending notation
47 + "gending_add_explanation": >-
48 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
49 + token.
50 + "gending_description": >-
51 + Description
52 + "gending_name": >-
53 + Gending Name
54 + "gending_notation": >-
55 + Gending Notation
56 + "gending_notation_add": >-
57 + Add gending notation
58 + "gending_notation_link": >-
59 + Javanese gending notation %{slug}
60 + "gending_notation_meta_desciption": >-
61 + This is the javanese gending notation for %{title} gending.
62 + "gending_notation_page_title": >-
63 + Javanese Gending Notation %{title}
64 + "guitar_chord_image": >-
65 + Gitarren-Akkord-Bild
66 + "guitar_chord_link": >-
67 + Gitarrenakkord %{slug}
68 + "guitar_chord_video": >-
69 + Video zu Gitarrenakkorden
70 + "guitar_chords_shape": >-
71 + Gitarrenakkorde Form
72 + "h2_find_new_albums": >-
73 + Finden Sie hier neue Alben!
74 + "h2_find_new_artists": >-
75 + Finden Sie hier neue Künstler!
76 + "have_account": >-
77 + Sie haben bereits ein Konto?
78 + "hello": >-
79 + Welcome to Musik 88
80 + "home_guitar_chords": >-
81 + Gitarrenakkorde
82 + "home_guitar_chords_headline": >-
83 + Reichen Sie Ihre Gitarrenakkorde ein und verdienen Sie %{bach_link} Rufzeichen
84 + für Ihren Beitrag.
85 + "home_headline": >-
86 + Die größte Musikdatenbank der Welt!
87 + "home_javanese_gending_notation": >-
88 + Javanische Gender-Notation
89 + "home_javanese_gending_notation_subheadline": >-
90 + Digitalisiere javanisches Gamelan und verdiene %{bach_link} Reputationstoken für
91 + deinen Beitrag.
92 + "home_lyrics": >-
93 + Liedtext
94 + "home_new_entries": >-
95 + Neue Einträge
96 + "home_others": >-
97 + Andere
98 + "home_others_headline": >-
99 + Schauen Sie sich meine anderen Projekte an.
100 + "home_popular": >-
101 + Beliebt
102 + "home_see_all": >-
103 + Alles sehen
104 + "home_share_gamelan_notation_now": >-
105 + Teilen Sie die Geschlechternotation!
106 + "home_share_now": >-
107 + Teile deinen Akkord!
108 + "home_signup_now": >-
109 + Jetzt registrieren!
110 + "home_songs": >-
111 + Lieder
112 + "home_subheadline": >-
113 + Gitarrenakkorde? Liedtext? Albumkritiken? Javanische Geschlechtsnotation? etc!
114 + Melden Sie sich an, leisten Sie einen Beitrag (fügen Sie Künstler, Alben, Texte
115 + usw. hinzu) und verdienen Sie %{bach_link} Reputationstoken.
116 + "home_youtube_karaoke": >-
117 + Youtube Karaoke
118 + "home_youtube_karaoke_headline": >-
119 + Karaoke-Version Ihres Lieblings-YouTube-Musikvideos powered by AI.
120 + "invalid_login": >-
121 + Ungültige E-Mail/Passwort-Kombination
122 + "locale_name": "\U0001F1E9\U0001F1EA Deutsch"
123 + "login": >-
124 + Anmeldung!
125 + "login_with_facebook": >-
126 + Mit Facebook einloggen
127 + "lyric": >-
128 + Liedtext
129 + "lyric_cannot_be_empty": >-
130 + Text darf nicht leer sein
131 + "my_profile": >-
132 + Mein Profil
133 + "my_profile_link_href": >-
134 + dieser sichere Link
135 + "my_profile_link_html": >-
136 + Dies ist Ihr öffentliches Profil. Folgen Sie %{href}, um Ihr Profil zu bearbeiten.
137 + "my_profile_private": >-
138 + Dieses Profil ist privat
139 + "name": >-
140 + Name
141 + "nav_albums": >-
142 + Alben
143 + "nav_artists": >-
144 + Künstler
145 + "nav_chords": >-
146 + Gitarrenakkorde
147 + "nav_chords_shapes": >-
148 + Gitarren-Akkorde-Diagramm
149 + "nav_community": >-
150 + Gemeinschaft
151 + "nav_footer_about": >-
152 + Über uns
153 + "nav_footer_company": >-
154 + Begleitung
155 + "nav_footer_contact": >-
156 + Kontakt
157 + "nav_footer_follow_us": >-
158 + Follow Us
159 + "nav_footer_privacy": >-
160 + Datenschutz-Bestimmungen
161 + "nav_footer_term": >-
162 + Nutzungsbedinungen
163 + "nav_guitar": >-
164 + Gitarre
165 + "nav_javanese_gamelan": >-
166 + Javanisches Gamelan
167 + "nav_javanese_gamelan_about": >-
168 + Über das javanische Gamelan
169 + "nav_javanese_gending": >-
170 + Javanisches Geschlecht
171 + "nav_javanese_gending_notation": >-
172 + Javanische Gender-Notation
173 + "nav_login": >-
174 + Anmeldung
175 + "nav_login_dropdown": >-
176 + Konto
177 + "nav_logout": >-
178 + Ausloggen
179 + "nav_lyrics": >-
180 + Liedtext
181 + "nav_music_database": >-
182 + Musics
183 + "nav_new_entries": >-
184 + Neue Einträge
185 + "nav_signup": >-
186 + Anmelden
187 + "nav_songs": >-
188 + Lieder
189 + "nav_ukulele_chords": >-
190 + Ukulele-Akkorde (bald)
191 + "need_to_logged_in": >-
192 + You need to logged in before giving this love. Don't have account yet? Just signup,
193 + it's so easy. You can use your Facebook account if you want.
194 + "need_to_logged_in_my_profile": >-
195 + Sie müssen angemeldet sein, um diese Aufgabe auszuführen.
196 + "new_user": >-
197 + Neuer Benutzer?
198 + "or": >-
199 + Oder
200 + "our_community": >-
201 + Unsere Gemeinschaft
202 + "our_guitar_chords_collection": >-
203 + Unsere neuesten Gitarrenakkord-Sammlungen
204 + "our_newest_song_lyrics": >-
205 + Unsere neueste Songtext-Sammlung
206 + "password": >-
207 + Passwort
208 + "password_button_update": >-
209 + Aktualisieren
210 + "password_confirmation": >-
211 + Passwort Bestätigung
212 + "password_new": >-
213 + Neues Passwort eingeben
214 + "password_new_confirmation": >-
215 + Kennwort bestätigen
216 + "routes":
217 + "about_us": >-
218 + über-uns
219 + "add_gending": >-
220 + add-gending-gamelan
221 + "add_javanese_gamelan_notation": >-
222 + add-javanese-gamelan-notation
223 + "album": >-
224 + alben
225 + "album_add": >-
226 + add-album
227 + "album_detail": >-
228 + albums
229 + "artist": >-
230 + künstler
231 + "artist_add": >-
232 + add-artist
233 + "artist_detail": >-
234 + artists
235 + "audio_files": >-
236 + audio-files
237 + "bass_backing_track": >-
238 + bass-backing-track-:slug
239 + "community": >-
240 + gemeinschaft
241 + "contact": >-
242 + kontakt
243 + "drum_backing_track": >-
244 + drum-backing-track-:slug
245 + "followers": >-
246 + anhänger
247 + "following": >-
248 + folgendes
249 + "guitar_backing_track": >-
250 + guitar-backing-track-:slug
251 + "guitar_chord_shape": >-
252 + gitarrenakkord-:slug
253 + "guitar_chord_shapes": >-
254 + all-gitarren-akkorde
255 + "guitar_chords": >-
256 + gitarrenakkorde
257 + "guitar_chords_add": >-
258 + add-chord
259 + "guitar_chords_detail": >-
260 + guitar-chords-:slug
261 + "guitar_chords_history": >-
262 + guitar-chords-:slug/history
263 + "guitar_chords_review": >-
264 + guitar-chords-:slug/review-chord
265 + "javanese_gamelan": >-
266 + javanese-gamelan
267 + "javanese_gamelan_notation": >-
268 + javanese-gamelan
269 + "javanese_gending": >-
270 + javanese-gending
271 + "javanese_gending_detail": >-
272 + javanese-gending-gamelan-:slug
273 + "javanese_gending_gamelan_notation": >-
274 + notation-for-javanese-gamelan
275 + "javanese_gending_gamelan_notation_detail": >-
276 + notation-for-javanese-gamelan-:slug
277 + "login": >-
278 + anmeldung
279 + "most_loved_songs": >-
280 + beliebteste-songs
281 + "privacy_policy": >-
282 + datenschutz-bestimmungen
283 + "signup": >-
284 + anmelden
285 + "song": >-
286 + lieder
287 + "song_add": >-
288 + add-song
289 + "song_audio": >-
290 + song-audio
291 + "song_bass_tab": >-
292 + bass-tab-:slug
293 + "song_chords": >-
294 + song-chords
295 + "song_detail": >-
296 + songs
297 + "song_karaoke_detail": >-
298 + karaoke-song-:slug
299 + "song_lyrics": >-
300 + liedtext
301 + "song_lyrics_detail": >-
302 + liedtext-:slug
303 + "term_of_use": >-
304 + nutzungsbedinungen
305 + "user_rep": >-
306 + ruf
307 + "see_album_detail": >-
308 + Siehe Albumdetails
309 + "see_artist_detail": >-
310 + Siehe Künstlerdetail
311 + "see_chord_detail": >-
312 + See chord detail
313 + "see_song_detail": >-
314 + See song detail
315 + "signup": >-
316 + Anmelden
317 + "signup_now": >-
318 + Jetzt registrieren!
319 + "signup_to_claim": >-
320 + Ist das deins?
321 + "signup_with_facebook": >-
322 + Mit Facebook anmelden
323 + "site_name": >-
324 + Musik88.com
325 + "somebody_to_love": >-
326 + Liebe mich
327 + "song_audio_page_title": >-
328 + Audio files for %{song_name}
329 + "song_chord_page_title": >-
330 + Guitar chord %{song_name} by %{song_artist_name}
331 + "song_chord_unavailable_content": >-
332 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
333 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
334 + that's all we need.
335 + "song_chord_unavailable_meta_description": >-
336 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
337 + album.
338 + "song_chords_page_title": >-
339 + %{song_name} Chords
340 + "song_detail": >-
341 + Song detail
342 + "song_detail_page_meta_title": >-
343 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
344 + album/single.
345 + "song_detail_page_title": >-
346 + %{song_name} Song Review
347 + "song_generate_your_own_bass_backing_track": >-
348 + Generate %{song_name} Bass Backing Track
349 + "song_generate_your_own_drum_backing_track": >-
350 + Erzeuge %{song_name} Schlagzeug-Backing-Track
351 + "song_generate_your_own_karaoke": >-
352 + Generate %{song_name} Karaoke File
353 + "song_generate_your_own_stem_files": >-
354 + Generate STEM Files For %{song_name}
355 + "song_karaoke_download": >-
356 + Download Karaoke Version
357 + "song_karaoke_page_title": >-
358 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
359 + Album
360 + "song_karaoke_title": >-
361 + %{filename} Karaoke Version
362 + "song_karaoke_unavailable_content": >-
363 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
364 + "song_karaoke_unavailable_meta_description": >-
365 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
366 + Album
367 + "song_lyric_detail": >-
368 + Liedtext %{title}
369 + "song_lyric_page_title": >-
370 + %{song_name} Liedtext von %{song_artist_name}
371 + "song_lyric_unavailable_content": >-
372 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
373 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
374 + or %{href} to this song so we can just focus on finding the chords.
375 + "song_lyric_unavailable_content_add_lyric": >-
376 + Liedtext hinzufügen
377 + "song_lyric_unavailable_meta_description": >-
378 + Hier ist der Liedtext %{song_name} von %{song_artist_name} aus dem Album %{song_album_name}.
379 + "song_lyrics_page_title": >-
380 + Liedtext %{song_name}
381 + "song_menu_aside_audio": >-
382 + Audio
383 + "song_menu_aside_chords": >-
384 + Chords
385 + "song_menu_aside_info": >-
386 + Info
387 + "song_menu_aside_lyrics": >-
388 + Text
389 + "song_menu_bass_backing_track": >-
390 + Bass backing track %{song_name}
391 + "song_menu_bass_tab": >-
392 + Bass tab %{song_name}
393 + "song_menu_drum_backing_track": >-
394 + Drum-Backing-Track %{song_name}
395 + "song_menu_guitar_chord": >-
396 + Gitarrenakkord %{song_name}
397 + "song_menu_karaoke": >-
398 + Karaoke Version %{song_name}
399 + "song_menu_lyric": >-
400 + Liedtext %{song_name}
401 + "song_updated_at": >-
402 + Updated: %{date}
403 + "still_login": >-
404 + Melden Sie sich weiterhin auf diesem Computer an
405 + "top_10_albums": >-
406 + Top 10 Alben
407 + "top_10_artists": >-
408 + Top 10 Künstler
409 + "top_10_guitar_chords": >-
410 + Top 10 Gitarrenakkorde
411 + "top_10_song_lyrics": >-
412 + Top 10 Songtexte
413 + "top_10_songs": >-
414 + Top 10 Songs
415 + "user_profile": >-
416 + Profil
417 + "user_reset_password_email_heading": >-
418 + Reset password link only for you, %{user_name}!
419 + "user_reset_password_email_heading_p1": >-
420 + You are getting this email because you are trying to access your account.
421 + "user_reset_password_email_heading_p2": >-
422 + Click %{href} to reset your password.
423 + "user_reset_password_email_link": >-
424 + this one time link
425 + "user_send_reset_password_instructions_subject": >-
426 + Reset password instruction
427 + "user_signup_email_heading": >-
428 + Danke für die Anmeldung, %{user_name}!
429 + "user_signup_email_heading_p1": >-
430 + Danke für den Beitritt zu uns!
431 + "user_signup_email_heading_p2": >-
432 + Wir haben es uns zur Aufgabe gemacht, weltweit unterrepräsentierte Musik zu digitalisieren.
433 + %{href} und beginne mitzumachen :)
434 + "user_signup_email_subject": >-
435 + Willkommen bei Musik88!
436 + "welcome_user": >-
437 + Herzlich willkommen, %{user_name}!
backend/musik88-web/config/locales/devise.en.yml new
+64
@@ -0,0 +1,64 @@
1 +# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 +
3 +en:
4 + devise:
5 + confirmations:
6 + confirmed: "Your email address has been successfully confirmed."
7 + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8 + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9 + failure:
10 + already_authenticated: "You are already signed in."
11 + inactive: "Your account is not activated yet."
12 + invalid: "Invalid %{authentication_keys} or password."
13 + locked: "Your account is locked."
14 + last_attempt: "You have one more attempt before your account is locked."
15 + not_found_in_database: "Invalid %{authentication_keys} or password."
16 + timeout: "Your session expired. Please sign in again to continue."
17 + unauthenticated: "You need to sign in or sign up before continuing."
18 + unconfirmed: "You have to confirm your email address before continuing."
19 + mailer:
20 + confirmation_instructions:
21 + subject: "Confirmation instructions"
22 + reset_password_instructions:
23 + subject: "Reset password instructions"
24 + unlock_instructions:
25 + subject: "Unlock instructions"
26 + email_changed:
27 + subject: "Email Changed"
28 + password_change:
29 + subject: "Password Changed"
30 + omniauth_callbacks:
31 + failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
32 + success: "Successfully authenticated from %{kind} account."
33 + passwords:
34 + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
35 + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
36 + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
37 + updated: "Your password has been changed successfully. You are now signed in."
38 + updated_not_active: "Your password has been changed successfully."
39 + registrations:
40 + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
41 + signed_up: "Welcome! You have signed up successfully."
42 + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
43 + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
44 + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
45 + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
46 + updated: "Your account has been updated successfully."
47 + sessions:
48 + signed_in: "Signed in successfully."
49 + signed_out: "Signed out successfully."
50 + already_signed_out: "Signed out successfully."
51 + unlocks:
52 + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
53 + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
54 + unlocked: "Your account has been unlocked successfully. Please sign in to continue."
55 + errors:
56 + messages:
57 + already_confirmed: "was already confirmed, please try signing in"
58 + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
59 + expired: "has expired, please request a new one"
60 + not_found: "not found"
61 + not_locked: "was not locked"
62 + not_saved:
63 + one: "1 error prohibited this %{resource} from being saved:"
64 + other: "%{count} errors prohibited this %{resource} from being saved:"
backend/musik88-web/config/locales/devise.id.yml new
+60
@@ -0,0 +1,60 @@
1 +# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 +
3 +id:
4 + devise:
5 + confirmations:
6 + confirmed: "Email anda telah berhasil dikonfirmasi."
7 + send_instructions: "Anda akan menerima sebuah email dengan instruksi bagaimana cara untuk mengkonfirmasikan email anda."
8 + send_paranoid_instructions: "Jika email anda telah terdaftar, anda akan menerima email dengan instruksi bagaimana cara untuk mengkonfirmasikan email anda."
9 + failure:
10 + already_authenticated: "Anda telah masuk."
11 + inactive: "Akun anda belum diaktifkan."
12 + invalid: "%{authentication_keys} atau sandi anda tidak cocok."
13 + locked: "Akun anda terkunci."
14 + last_attempt: "Anda memiliki 1 kali kesempatan sebelum akun anda dikunci."
15 + not_found_in_database: "%{authentication_keys} atau sandi anda tidak cocok."
16 + timeout: "Sesi anda telah berakhir. Silahkan masuk kembali untuk melanjutkan"
17 + unauthenticated: "Anda perlu untuk masuk atau daftar sebelum melanjutkan."
18 + unconfirmed: "Anda harus mengkonfirmasi email anda terlebih dahulu sebelum melanjutkan."
19 + mailer:
20 + confirmation_instructions:
21 + subject: "Instruksi untuk Konfirmasi"
22 + reset_password_instructions:
23 + subject: "Instruksi untuk Mereset Sandi"
24 + unlock_instructions:
25 + subject: "Instruksi untuk Membuka Kunci"
26 + omniauth_callbacks:
27 + failure: "Tidak dapat mengautentikasi anda dari %{kind} dikarenakan \"%{reason}\"."
28 + success: "Autentikasi berhasil dari akun %{kind}."
29 + passwords:
30 + no_token: "Anda tidak dapat mengakses halaman ini tanpa melalui email yang menginstruksikan cara untuk mereset sandi. Jika anda benar membuka tautan dari email reset password, maka pastikan anda memasukkan URL secara utuh."
31 + send_instructions: "Anda akan menerima email dengan instruksi cara untuk mereset sandi anda."
32 + send_paranoid_instructions: "Jika email anda telah terdaftar, anda akan menerima tautan untuk memulihkan sandi anda."
33 + updated: "Sandi anda berhasil diubah dan anda telah dimasukkan."
34 + updated_not_active: "Sandi anda berhasil diubah."
35 + registrations:
36 + destroyed: "Sampai jumpa! Akun anda telah dibatalkan. Kami berharap bertemu lagi dengan anda."
37 + signed_up: "Selamat Datang! Anda telah berhasil masuk."
38 + signed_up_but_inactive: "Anda telah berhasil terdaftar. Tetapi, kami tidak dapat memasukkan anda dikarenakan akun anda belum diaktifkan."
39 + signed_up_but_locked: "Anda telah berhasil terdaftar. Tetapi, kami tidak dapat memasukkan anda dikarenakan akun anda terkunci."
40 + signed_up_but_unconfirmed: "Sebuah pesan dengan tautan untuk konfirmasi telah di kirim ke alamat email anda. Mohon untuk mengikuti tautan yang ada untuk mengaktifkan akun anda."
41 + update_needs_confirmation: "Akun anda telah berhasil diubah, mohon verifikasi email anda. Cek email anda dan ikuti cara yang dicantumkan untuk mengkonfirmasi email anda."
42 + updated: "Akun anda telah berhasil diubah."
43 + sessions:
44 + signed_in: "Anda telah berhasil masuk."
45 + signed_out: "Anda telah berhasil keluar."
46 + already_signed_out: "Anda telah berhasil keluar."
47 + unlocks:
48 + send_instructions: "Anda akan menerima email dengan instruksi bagaimana cara untuk membuka kunci pada akun anda."
49 + send_paranoid_instructions: "Jika akun anda terdaftar, anda akan menerima email dengan instruksi bagaimana cara untuk membuka kunci pada akun anda."
50 + unlocked: "Akun anda telah berhasil dibuka. Silahkan masuk untuk melanjutkan."
51 + errors:
52 + messages:
53 + already_confirmed: "telah dikonfirmasi, Silahkan coba untuk masuk"
54 + confirmation_period_expired: "butuh dikonfirmasi dalam waktu %{period}, tolong anda minta kembali"
55 + expired: "telah habis masa berlakunya, tolong anda minta kembali"
56 + not_found: "tidak dapat kami temukan"
57 + not_locked: "tidak terkunci"
58 + not_saved:
59 + one: "1 masalah menghalangi %{resource} untuk dapat disimpan:"
60 + other: "%{count} masalah menghalangi %{resource} untuk dapat disimpan:"
backend/musik88-web/config/locales/devise_invitable.en.yml new
+31
@@ -0,0 +1,31 @@
1 +en:
2 + devise:
3 + failure:
4 + invited: "You have a pending invitation, accept it to finish creating your account."
5 + invitations:
6 + send_instructions: "An invitation email has been sent to %{email}."
7 + invitation_token_invalid: "The invitation token provided is not valid!"
8 + updated: "Your password was set successfully. You are now signed in."
9 + updated_not_active: "Your password was set successfully."
10 + no_invitations_remaining: "No invitations remaining"
11 + invitation_removed: "Your invitation was removed."
12 + new:
13 + header: "Send invitation"
14 + submit_button: "Send an invitation"
15 + edit:
16 + header: "Set your password"
17 + submit_button: "Set my password"
18 + mailer:
19 + invitation_instructions:
20 + subject: "Invitation instructions"
21 + hello: "Hello %{email}"
22 + someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below."
23 + accept: "Accept invitation"
24 + accept_until: "This invitation will be due in %{due_date}."
25 + ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password."
26 + time:
27 + formats:
28 + devise:
29 + mailer:
30 + invitation_instructions:
31 + accept_until_format: "%B %d, %Y %I:%M %p"
backend/musik88-web/config/locales/en.yml new
+467
@@ -0,0 +1,467 @@
1 +en:
2 + locale_name: "🇺🇸 English"
3 + routes:
4 + search: search
5 + privacy_policy: privacy-policy
6 + term_of_use: term-of-use
7 + contact: contact
8 + about_us: about-us
9 + artist: artists
10 + artist_add: add-artist
11 + album: albums
12 + album_add: add-album
13 + song: songs
14 + song_add: add-song
15 + most_loved_songs: most-loved-songs
16 + guitar_chords: guitar-chords
17 + guitar_chord_shapes: all-guitar-chords
18 + guitar_chord_shape: 'guitar-chord-:slug'
19 + song_lyrics: song-lyrics
20 + artist_detail: artists
21 + artist_bridge: artists-bridge
22 + album_detail: albums
23 + album_bridge: albums-bridge
24 + song_detail: songs
25 + song_bridge: songs-bridge
26 + song_chords: song-chords
27 + song_audio: song-audio
28 + song_lyrics_detail: 'song-lyrics-:slug'
29 + guitar_chords_detail: 'guitar-chords-:slug'
30 + guitar_chords_history: 'guitar-chords-:slug/history'
31 + guitar_chords_add: 'add-chord'
32 + guitar_chords_review: 'guitar-chords-:slug/review-chord'
33 + song_karaoke_detail: 'karaoke-song-:slug'
34 + #song_bass_backing_track: 'bass-backing-track-:slug' Deprecated
35 + song_bass_tab: 'bass-tab-:slug'
36 + audio_files: audio-files
37 + guitar_backing_track: 'guitar-backing-track-:slug'
38 + bass_backing_track: 'bass-backing-track-:slug'
39 + drum_backing_track: 'drum-backing-track-:slug'
40 +
41 + # Javanese gamelan
42 + javanese_gamelan: javanese-gamelan
43 +
44 + javanese_gending: javanese-gending
45 + javanese_gending_detail: javanese-gending-gamelan-:slug
46 + javanese_gending_gamelan_notation: notation-for-javanese-gamelan
47 + javanese_gending_gamelan_notation_detail: notation-for-javanese-gamelan-:slug
48 + javanese_gending_gamelan_notation_detail_new: gending-notation-standard-:slug
49 + javanese_gamelan_notation: javanese-gamelan # Obsolete
50 + add_gending: add-gending-gamelan
51 + add_javanese_gamelan_notation: add-javanese-gamelan-notation
52 +
53 + login: login
54 + signup: signup
55 + confirm_email: confirm-email
56 + community: community
57 + user_rep: reputation
58 + followers: followers
59 + following: following
60 +
61 + what_is_this: what-is-this
62 + what_is_this_detail: what-is-:slug
63 + site_name: "Musik88.com"
64 + hello: "Welcome to Musik 88"
65 + nav_music_database: "Musics"
66 + nav_artists: "Artists"
67 + nav_albums: "Albums"
68 + nav_songs: "Songs"
69 + nav_new_entries: "New Entries"
70 + nav_change_language: "Change Language: "
71 + nav_en: "English"
72 + nav_id: "Bahasa Indonesia"
73 + nav_footer_about: "About Us"
74 + nav_footer_term: "Term of Use"
75 + nav_footer_privacy: "Privacy Policy"
76 + nav_footer_contact: "Contact"
77 + nav_footer_company: "Company"
78 + nav_footer_follow_us: "Follow Us"
79 + nav_lyrics: "Song Lyrics"
80 + nav_guitar: "Guitar"
81 + nav_chords_shapes: "Guitar Chords Diagram"
82 + nav_chords: "Guitar Chords"
83 + nav_javanese_gamelan: "Javanese Gamelan"
84 + nav_javanese_gamelan_about: "About Javanese Gamelan"
85 + nav_javanese_gending: "Javanese Gendings"
86 + nav_javanese_gending_notation: "Javanese Gending Notation"
87 + nav_ukulele_chords: "Ukulele Chords (soon)"
88 + nav_login_dropdown: "Account"
89 + nav_community: "Community"
90 + nav_login: "Login (web3 Ready!)"
91 + nav_logout: "Log out"
92 + nav_signup: "Signup"
93 + nav_confirm_email: "Confirm email"
94 + nav_onboarding: "Onboarding"
95 + nav_what_is_this: 'What Is This?'
96 + nav_buy_bach: 'Buy €BACH'
97 + home_new_entries: "New Entries"
98 + home_popular: "Popular"
99 + footer: "Created for fun by %{profile} from all around the %{instagram} &middot; &copy; 2021"
100 + footer_world: "world"
101 +
102 + # Homepage
103 + search_result_for: 'Search result for "<span class="fst-italic fw-bold">%{keywords}</span>"'
104 +
105 + # General
106 + discussion: "Discussion"
107 + added_by: "Added by"
108 + discussion_comment_placeholder: "Write your comment here"
109 + discussion_comment_button: "Submit"
110 + like_this: "like this"
111 + legacy_view: "Old design"
112 + new_view: "New design"
113 + change_language: "Change language"
114 + search: "Search"
115 + search_placeholder_title: "Search artist, song, or javanese gending..."
116 + save: 'Save'
117 + next: 'Next'
118 + no: 'No.'
119 +
120 + # Badges
121 + badges: 'Badges'
122 + pretty_badge: 'Pretty Badge'
123 +
124 + #Models
125 + JavaneseGending: 'Javanese Gending'
126 + JavaneseGendingNotation: 'Javanese Gending Notation'
127 + Song: 'Song'
128 + Artist: 'Artist'
129 + Album: 'Album'
130 + SongProvider: 'Song bridge'
131 + ArtistProvider: 'Artist bridge'
132 + AlbumProvider: 'Album bridge'
133 +
134 + #Breadcrumbs
135 + breadcrumb_home: "Home"
136 + breadcrumb_most_loved_songs: "Most Loved Songs"
137 +
138 + #Home page
139 + home_headline: "World's biggest music database!"
140 + home_subheadline: "Guitar chords? Song lyrics? Album reviews? Javanese gending notation? etc! Signup, contribute (add artists, albums, lyrics, etc), and earn %{bach_link} cryptocurrency."
141 + home_signup_now: "Signup now!"
142 + home_see_all: "See all"
143 + home_guitar_chords: "Guitar Chords"
144 + home_guitar_chords_headline: "Submit your guitar chords and earn %{bach_link} crypto for your contribution."
145 + home_share_now: "Share your chord!"
146 + home_others: "Others"
147 + home_others_headline: "Check out my other projects."
148 + home_youtube_karaoke: "Youtube Karaoke"
149 + home_youtube_karaoke_headline: "Karaoke version of your favorite YouTube music video powered by AI."
150 + home_javanese_gending_notation: "Javanese Gending Notation"
151 + home_javanese_gending_notation_subheadline: "Digitalize Javanese gamelan and earn %{bach_link} cryptocurrency for your contribution."
152 + home_share_gamelan_notation_now: "Share gending notation!"
153 + home_lyrics: "Song Lyrics"
154 + home_songs: "Songs"
155 + home_add_artist: "Add artist"
156 + home_add_album: "Add album"
157 + home_what_is: 'What is Musik88?'
158 +
159 + h2_find_new_songs: "Find New Musics Here!"
160 + h2_find_new_albums: "Find New Albums Here!"
161 + h2_find_new_artists: "Find New Artists Here!"
162 +
163 + #Artist page
164 + find_your_artist: "Find Your Favorite Artist"
165 + see_artist_detail: "See artist detail"
166 + top_10_artists: "Top 10 Artists"
167 + artist_profile_title: "%{artist} Profile"
168 + artist_profile_album_title: "%{artist}'s Albums"
169 + artist_profile_album_contribution_title: "%{artist} also appears on:"
170 + #Album page
171 + find_your_favorite_album: "Find Your New Favorite Album"
172 + see_album_detail: "See album detail"
173 + top_10_albums: "Top 10 Albums"
174 + album_artists: "Artists"
175 + album_song_list: "Song list"
176 + #Songs page
177 + find_your_favorite_songs: "Find Your Favorite Songs"
178 + see_song_detail: "See song detail"
179 + top_10_songs: "Top 10 Songs"
180 + 10_most_loved_songs: "10 Most Loved Songs"
181 + by: "Loved by"
182 + somebody_to_love: "Love me"
183 + song_detail_page_title: "%{song_name} Song Review"
184 + song_chords_page_title: "%{song_name} Guitar Chord"
185 + song_detail_page_meta_title: "%{song_name} from %{song_artist_name} song review, from the %{song_album_name} album/single."
186 + need_to_logged_in: "You need to logged in before giving this love. Don't have account yet? Just signup, it's so easy. You can use your Facebook account if you want."
187 + song_lyric_page_title: "%{song_name} Lyric by %{song_artist_name}"
188 + song_lyric_unavailable_content: "The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span> isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span> or %{href} to this song so we can just focus on finding the chords."
189 + song_lyric_unavailable_content_add_lyric: "Add Lyric"
190 + song_menu_aside_info: "Info"
191 + song_menu_aside_chords: "Chords"
192 + song_menu_aside_lyrics: "Lyrics"
193 + song_menu_aside_audio: "Audio"
194 + song_detail: "Song detail"
195 + lyric: "Song Lyric"
196 + cancel: "Cancel"
197 + lyric_cannot_be_empty: "Lyric can't be empty"
198 + song_lyric_unavailable_meta_description: "Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name} album."
199 + song_chord_page_title: "Guitar Chord %{song_name}"
200 + song_chord_unavailable_content: "The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span> isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>, that's all we need."
201 + song_chord_unavailable_meta_description: "Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name} album."
202 + song_audio_page_title: "Audio files for %{song_name}"
203 + song_detail_page_from_album: "from album %{album_link}"
204 + song_detail_page_by: "by %{artist_link}"
205 + #Song lyrics page
206 + song_lyrics_page_title: "Song Lyrics %{song_name}"
207 + song_lyric_detail: "Song lyric %{title}"
208 + our_newest_song_lyrics: "Our Newest Song Lyrics Collection"
209 + top_10_song_lyrics: "Top 10 Song Lyrics"
210 + song_menu_lyric: "Song Lyric %{song_name}"
211 + song_menu_bass_backing_track: "Bass backing track %{song_name}"
212 + song_menu_bass_tab: "Bass tab %{song_name}"
213 + song_menu_drum_backing_track: "Drum backing track %{song_name}"
214 + song_updated_at: "Updated: %{date}"
215 + #Add chords page
216 + add_chord_title: "Add chord"
217 + add_chord_validation: "We need the title and the chord"
218 + add_chord_validation_success: "Thank you for contributing!"
219 + add_chord_how_to: "How to add chord"
220 + add_chord_label_song: "Attach to song.."
221 + add_chord_placeholder_chose_song: "Choose song"
222 + add_chord_label_title: ".. or add title"
223 + add_chord_placeholder_title: "Title"
224 + add_chord_placeholder_artist: "Artist name"
225 + add_chord_label_chord: "Chord"
226 + add_chord_placeholder_chord: "Put your chord here..."
227 + add_chord_warning: "You haven't signed in yet. %{href_login} and claim your contribution, or %{href} will claim your chords and contribution."
228 + add_chord_share: "Share!"
229 + add_chord_how_to_content: "Add your chord by writing it down line by line. Add the chord sequences on top of each line. Add spacing and paragraph if needed. Also, add song section (reff, verse, bridge) in every part of the song."
230 + #Guitar chords page
231 + guitar_chord_link: "Guitar chord %{slug}"
232 + our_guitar_chords_collection: "Our Newest Guitar Chords Collections"
233 + top_10_guitar_chords: "Top 10 Guitar Chords"
234 + song_menu_guitar_chord: "Guitar Chord %{song_name}"
235 + see_chord_detail: "See chord detail"
236 + guitar_chords_shape: "Guitar Chords Shape"
237 + guitar_chord_image: "Guitar Chord Image"
238 + guitar_chord_video: "Guitar Chord Video"
239 + #Javanese gamelan
240 + gending_add: "Share your gending notation"
241 + gending_add_explanation: "Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} cryptocurrency."
242 + gending_notation: "Gending Notation "
243 + gending_notation_add_explanation: "Wanna share a javanese gending notation? %{href} and earn %{bach_link} cryptocurrency."
244 + gending_notation_add: "Add gending notation"
245 + gending_name: "Gending Name"
246 + gending_description: "Description"
247 + gending_notation: "Gending Notation"
248 + gending_notation_page_title: "Javanese Gending Notation %{title}"
249 + gending_notation_meta_desciption: "This is the javanese gending notation for %{title} gending."
250 + gending_notation_link: "Javanese gending notation %{slug}"
251 + #Login page
252 + login: "Login"
253 + login_omniauth_explanation: 'Login with your Spotify, Google, or Facebook account.'
254 + login_web3_explanation: 'Login with your web3 wallet provider. Currently only support Phantom wallet.'
255 + forgot_password: "Forgot password?"
256 + confirm_email: "Confirm your email"
257 + email: "Email"
258 + password: "Password"
259 + password_confirmation: "Password confirmation"
260 + still_login: "Remember me"
261 + new_user: "New user?"
262 + signup_now: "Signup now!"
263 + invalid_login: "Invalid email/password combination"
264 + login_with_facebook: "Login with Facebook"
265 + signup_with_facebook: "Signup with Facebook"
266 + or: "Or"
267 + resend_email_confirmation: "Resend confirmation instructions"
268 + resend_email_confirmation_button: "Resend instructions"
269 + unconfirmed_email: "You've already registered and need to confirm your email. Check your %{email_client_link}'s inbox or spam folder. Or %{click_here_link} to resend your email confirmation."
270 + unconfirmed_email_click_here: 'click here'
271 + unconfirmed_email_resend_sent: 'Email confirmation is sent to %{email}. Open your %{email_client_link}. If you cannot find it, check the junk folder. It might be detected as spam.'
272 + unconfirmed_email_email_app: 'email app'
273 + join_our_chord_community: "Join our community to see <span class='badge bg-primary rounded-pill fw-bold fst-italic'>%{song_name}</span> chord. You can also earn €BACH cryptocurrency by activating your wallet in Profile -> Settings."
274 + join_our_lyric_community: "Join our community to see <span class='badge bg-primary rounded-pill fw-bold fst-italic'>%{song_name}</span> lyric from <span class='badge bg-primary rounded-pill fw-bold fst-italic'>%{song_artist_name}</span>. You can also earn €BACH cryptocurrency by activating your wallet in Profile -> Settings."
275 + join_our_javanese_community: "Join our community to see <span class='badge bg-primary rounded-pill fw-bold fst-italic'>%{gending_name}</span> notation. You can also earn €BACH cryptocurrency by activating your wallet in Profile -> Settings."
276 + #Users
277 + our_community: "Our Community"
278 + user_profile: "Profile"
279 + signup: "Signup"
280 + signup_omniauth_explanation: 'Signup with your Spotify, Google, or Facebook account.'
281 + signup_omniauth_explanation_provider: 'Synching your %{provider_name} account.'
282 + have_account: "Already have account?"
283 + create_account: "Create account"
284 + name: "Name"
285 + welcome_user: "Welcome, %{user_name}!"
286 + welcome_user_unconfirmed: "Welcome! Check your email to confirm your account."
287 + my_profile: "My Profile"
288 + my_profile_link_html: "This is your public profile. Follow %{href} to edit your profile."
289 + my_profile_link_href: "this secure link"
290 + my_profile_private: "This profile is private"
291 + signup_to_claim: "Is this yours?"
292 + need_to_logged_in_my_profile: "You need to be logged in to perform this task."
293 + password_new: "Enter new password"
294 + password_new_confirmation: "Confirm password"
295 + password_button_update: "Update"
296 + oh_hi: "Oh, hi!"
297 + reputation: 'Reputation'
298 + friends: "Friends"
299 + find_your_friends: "Find your friends here..."
300 + following: "Following"
301 + followers: "Follower"
302 + settings: "Settings"
303 + your_wallet_address: "Your wallet address"
304 + your_wallet_address_explanation: "We store your cryptocurrency in your wallet address. You can buy, sell, and stake (coming soon) your cryptocurrency in an open market, for example %{href}."
305 + generating_solana_wallet: "Generating your wallet.... Refresh this page later."
306 + activate_solana_wallet: "Activate your wallet"
307 + your_locale: 'Language'
308 + your_locale_explanation: 'Your language preference of this website'
309 + your_invitees: 'Your Invitees'
310 + your_invitees_explanation: 'You get 0.1 BACH for every accepted invite.'
311 + invite_button: 'Invite'
312 + your_invite_email: 'Email'
313 + your_invite_date: 'Date'
314 + your_invite_status: 'Status'
315 + your_account_connections: 'Connection Settings'
316 + your_account_connections_explanation: 'We sync your music providers everytime you log in.'
317 + your_account_connections_spotify_token_explanation: 'Let us use your Spotify token to search content on Spotify.'
318 + your_account_connections_google_explanation: 'Let us use your Google token to search content on YouTube.'
319 + onboarding_explanation: 'By verifying your email, you got 0.1 BACH! Get more BACH by following these steps.'
320 + onboarding_username_heading: '@username'
321 + onboarding_username_explanation: 'Create a unique @username and get another 0.1 BACH!'
322 + onboarding_username_done: 'Yeay! You got your username and a %{profile_badges_link}.'
323 + username_length_invalid: 'Username minimum 4 letters.'
324 + username_character_invalid: 'Contains illegal character.'
325 + username_letters_numbers_only: 'Username should only contain letters and numbers.'
326 + username_should_starts_with_letters: 'Should start with letters.'
327 + username_taken: 'Username is taken.'
328 + username_available: 'Congratulations! Your username is available.'
329 + onboarding_spotify_heading: 'Export your Spotify Data'
330 + onboarding_spotify_explanation: 'Get up to 50 BACH when you export your playlist!'
331 + onboarding_spotify_processing: 'Exporting your data. It takes sometime.'
332 + onboarding_spotify_done: 'Done. You exported your Spotify data.'
333 + onboarding_done: "That's it, %{user_name}!"
334 + onboarding_explanation_done: 'We are done for now. Check your profile and start contributing more to the community.'
335 + onboarding_sounds_good: 'Sounds good!'
336 + onboarding_username_checking: 'Checking...'
337 + onboarding_done_button: "I'm ready!"
338 + onboarding_google_heading: 'Are you a YouTube exporter?'
339 + onboarding_google_explanation: 'Export your YouTube data and get up to 50 BACH!'
340 + onboarding_google_processing: 'Exporting your music data. It takes sometime.'
341 + onboarding_google_done: 'Yes, you are! You exported your YouTube data for our community.'
342 + #BACH
343 + bach_token_name: "Token name"
344 + bach_token_id: "Token ID"
345 + bach_token_account: "Token Account"
346 + bach_token_address: "Token Address"
347 + bach_token_symbol: "Symbol"
348 + bach_token_decimal: "Decimal Place"
349 + bach_token_fractional: "Fractional"
350 + bach_token_supply: "Current Supply"
351 + bach_token_fix_supply: "Total Supply"
352 + bach_token_semitone: "semitone"
353 + bach_token_trillion: "trillion"
354 + bach_token_supply_cycles: "Supply cycles"
355 + bach_token_discussion: "What do you think? Are you interested?"
356 +
357 + # Karaoke
358 + song_karaoke_title: "%{filename} Karaoke Version"
359 + song_karaoke_page_title: "Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name} Album"
360 + song_karaoke_unavailable_content: "The %{song_name} karaoke version from %{song_artist_name} is not available yet."
361 + song_karaoke_unavailable_meta_description: "Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name} Album"
362 + song_karaoke_download: "Download Karaoke Version"
363 + song_menu_karaoke: "Karaoke Version %{song_name}"
364 + song_generate_your_own_karaoke: "Generate %{song_name} Karaoke File"
365 + song_generate_your_own_bass_backing_track: "Generate %{song_name} Bass Backing Track"
366 + song_generate_your_own_stem_files: "Generate STEM Files For %{song_name}"
367 + song_generate_your_own_drum_backing_track: "Generate %{song_name} Drum Backing Track"
368 +
369 + # Mailer
370 + user_notifier_projects: 'Check out our other projects'
371 + user_signup_email_subject: 'Welcome to Musik88!'
372 + user_signup_email_heading: 'Thanks for signing up, %{user_name}!'
373 + user_signup_email_heading_p1: 'Thanks for joining us!'
374 + user_signup_email_heading_p2: 'We are in a mission to digitalize underrepresented musics in the world. %{href} and start contributing :)'
375 + user_send_reset_password_instructions_subject: 'Reset password instruction'
376 + user_reset_password_email_heading: 'Reset password link only for you, %{user_name}!'
377 + user_reset_password_email_heading_p1: 'You are getting this email because you are trying to access your account.'
378 + user_reset_password_email_heading_p2: 'Click %{href} to reset your password.'
379 + user_reset_password_email_link: 'this one time link'
380 + user_send_wallet_activation_notification_subject: 'You have a new wallet!'
381 + user_send_wallet_activation_notification_heading: 'Congratulations, %{user_name}. You now have a new crypto wallet!'
382 + user_send_wallet_activation_notification_p1: 'Your BACH crypto is stored on your wallet. You can see your balance in the link below.'
383 + user_send_wallet_activation_notification_address: 'Your wallet address'
384 + user_email_confirmation_header: 'Welcome %{email}!'
385 + user_email_confirmation_content: 'You can confirm your account email through the link below:'
386 + user_email_confirmation_cta: 'Confirm my account'
387 + user_email_with_love: 'With ❤️ from Stockholm,'
388 +
389 + #General translations or partials
390 + unknown_error: "Unknown error"
391 +
392 + #Comments
393 + ago: "ago"
394 + add_comment: "Add a comment"
395 + reply: "Reply"
396 + add_a_reply: "Add a reply"
397 +
398 + #Active record translation
399 + activerecord:
400 + errors:
401 + models:
402 + song:
403 + attributes:
404 + album:
405 + too_short: "Album tidak boleh kosong"
406 + name:
407 + too_short: "Nama lagu tidak boleh kosong"
408 + description:
409 + too_short: "Deskripsi lagu tidak boleh kosong"
410 + lyric:
411 + attributes:
412 + lyric:
413 + too_short: "Lirik lagu tidak boleh kosong"
414 + chord:
415 + attributes:
416 + chord:
417 + too_short: "Chord lagu tidak boleh kosong"
418 + artist:
419 + attributes:
420 + name:
421 + too_short: "Nama artis tidak boleh kosong"
422 + description:
423 + too_short: "Deskripsi artis tidak boleh kosong"
424 + album:
425 + attributes:
426 + name:
427 + too_short: "Nama album tidak boleh kosong"
428 + description:
429 + too_short: "Deskripsi album tidak boleh kosong"
430 + too_short: "Deskripsi artis tidak boleh kosong"
431 + page:
432 + attributes:
433 + page_slug:
434 + too_short: "Slug halaman tidak boleh kosong"
435 + page_content:
436 + too_short: "Konten halaman tidak boleh kosong"
437 + user:
438 + attributes:
439 + name:
440 + blank: "Name cannot be empty"
441 + email:
442 + blank: "Email cannot be empty"
443 + invalid: "Email is not valid"
444 + taken: "Email has already been taken"
445 + password:
446 + blank: "Password cannot be empty"
447 + too_short: "Password is too short"
448 + password_confirmation:
449 + confirmation: "Password confirmation is not the same"
450 +
451 +# Activeadmin translation
452 + active_admin:
453 + filters:
454 + predicates:
455 + from: "dari tanggal"
456 + to: "sampai tanggal"
457 + datetime:
458 + distance_in_words:
459 + x_days:
460 + one: 1 hour
461 + other: "%{count} hours"
462 +
463 +# Devise translation
464 + devise:
465 + mailer:
466 + confirmation_instructions:
467 + user_subject: 'Email Confirmation Musik88.com'
\ No newline at end of file
backend/musik88-web/config/locales/es.yml new
+545
@@ -0,0 +1,545 @@
1 +---
2 +"es":
3 + "10_most_loved_songs": >-
4 + 10 Most Loved Songs
5 + "active_admin":
6 + "filters":
7 + "predicates":
8 + "from": >-
9 + dari tanggal
10 + "to": >-
11 + sampai tanggal
12 + "activerecord":
13 + "errors":
14 + "models":
15 + "album":
16 + "attributes":
17 + "description":
18 + "too_short": >-
19 + Deskripsi artis tidak boleh kosong
20 + "name":
21 + "too_short": >-
22 + Nama album tidak boleh kosong
23 + "artist":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + Deskripsi artis tidak boleh kosong
28 + "name":
29 + "too_short": >-
30 + Nama artis tidak boleh kosong
31 + "chord":
32 + "attributes":
33 + "chord":
34 + "too_short": >-
35 + Chord lagu tidak boleh kosong
36 + "lyric":
37 + "attributes":
38 + "lyric":
39 + "too_short": >-
40 + Lirik lagu tidak boleh kosong
41 + "page":
42 + "attributes":
43 + "page_content":
44 + "too_short": >-
45 + Konten halaman tidak boleh kosong
46 + "page_slug":
47 + "too_short": >-
48 + Slug halaman tidak boleh kosong
49 + "song":
50 + "attributes":
51 + "album":
52 + "too_short": >-
53 + Album tidak boleh kosong
54 + "description":
55 + "too_short": >-
56 + Deskripsi lagu tidak boleh kosong
57 + "name":
58 + "too_short": >-
59 + Nama lagu tidak boleh kosong
60 + "user":
61 + "attributes":
62 + "email":
63 + "blank": >-
64 + Email tidak boleh kosong
65 + "invalid": >-
66 + Email tidak valid
67 + "name":
68 + "blank": >-
69 + Nama tidak boleh kosong
70 + "password":
71 + "blank": >-
72 + Password tidak boleh kosong
73 + "too_short": >-
74 + Password terlalu pendek
75 + "password_confirmation":
76 + "confirmation": >-
77 + Konfirmasi password tidak sama
78 + "add_chord_how_to": >-
79 + Acorde
80 + "add_chord_label_chord": >-
81 + Acorde
82 + "add_chord_label_song": >-
83 + Bifoga till låten..
84 + "add_chord_label_title": >-
85 + .. o añadir título
86 + "add_chord_placeholder_chord": >-
87 + Pon tu acorde aquí...
88 + "add_chord_placeholder_chose_song": >-
89 + elegir canción
90 + "add_chord_placeholder_title": >-
91 + Se requiere titulo...
92 + "add_chord_share": >-
93 + ¡Cuota!
94 + "add_chord_title": >-
95 + Añadir acorde
96 + "add_chord_validation": >-
97 + Necesitamos el titulo y el acorde
98 + "add_chord_validation_success": >-
99 + ¡Gracias por contribuir!
100 + "add_chord_warning": >-
101 + Aún no has iniciado sesión. %{href_login} y reclama tu contribución, o %{href}
102 + reclamará tus acordes y contribución.
103 + "album_artists": >-
104 + Artistas
105 + "album_song_list": >-
106 + Lista de canciones
107 + "artist_profile_album_contribution_title": >-
108 + %{artist} también aparece en:
109 + "artist_profile_album_title": >-
110 + Álbumes de %{artist}
111 + "artist_profile_title": >-
112 + %{artist} Profile
113 + "breadcrumb_home": >-
114 + Casa
115 + "breadcrumb_most_loved_songs": >-
116 + Canciones más queridas
117 + "by": >-
118 + Loved by
119 + "cancel": >-
120 + Cancel
121 + "confirm_email": >-
122 + Confirme su email
123 + "create_account": >-
124 + Crear una cuenta
125 + "discussion": >-
126 + Discussion
127 + "email": >-
128 + Correo electrónico
129 + "find_your_artist": >-
130 + Find Your Favorite Artist
131 + "find_your_favorite_album": >-
132 + Encuentra tu nuevo álbum favorito
133 + "find_your_favorite_songs": >-
134 + Find Your Favorite Songs
135 + "followers": >-
136 + Follower
137 + "following": >-
138 + Following
139 + "footer": >-
140 + Creado por diversión por %{profile} de todo %{instagram} &middot; &copy; 2021
141 + "footer_world": >-
142 + world
143 + "forgot_password": >-
144 + ¿Se te olvidó tu contraseña?
145 + "gending_add": >-
146 + Share your gending notation
147 + "gending_add_explanation": >-
148 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
149 + token.
150 + "gending_description": >-
151 + Description
152 + "gending_name": >-
153 + Nombre de la cancion
154 + "gending_notation": >-
155 + Gending Notation
156 + "gending_notation_add": >-
157 + Add gending notation
158 + "gending_notation_link": >-
159 + Javanese gending notation %{slug}
160 + "gending_notation_meta_desciption": >-
161 + This is the javanese gending notation for %{title} gending.
162 + "gending_notation_page_title": >-
163 + Javanese Gending Notation %{title}
164 + "guitar_chord_image": >-
165 + Guitar Chord Image
166 + "guitar_chord_link": >-
167 + Guitar chord %{slug}
168 + "guitar_chord_video": >-
169 + Guitar Chord Video
170 + "guitar_chords_shape": >-
171 + Forma de acordes de guitarra
172 + "h2_find_new_albums": >-
173 + ¡Encuentra nuevos álbumes aquí!
174 + "h2_find_new_artists": >-
175 + ¡Encuentra nuevas artistas aquí!
176 + "h2_find_new_songs": >-
177 + ¡Encuentra nuevas músicas aquí!
178 + "have_account": >-
179 + ¿Ya tienes cuenta?
180 + "hello": >-
181 + Welcome to Musik 88
182 + "home_guitar_chords": >-
183 + Cuerdas de guitarra
184 + "home_guitar_chords_headline": >-
185 + Envíe sus acordes de guitarra y gane %{bach_link} token de reputación por su contribución.
186 + "home_headline": >-
187 + ¡La base de datos de música más grande del mundo!
188 + "home_javanese_gending_notation": >-
189 + Notación de canciones javanesas
190 + "home_javanese_gending_notation_subheadline": >-
191 + Digitalice el gamelan javanés y gane %{bach_link} token de reputación por su contribución.
192 + "home_lyrics": >-
193 + Letras de canciones
194 + "home_new_entries": >-
195 + Nuevas entradas
196 + "home_others": >-
197 + Otras
198 + "home_others_headline": >-
199 + Mira mis otros proyectos.
200 + "home_popular": >-
201 + Popular
202 + "home_see_all": >-
203 + Ver todo
204 + "home_share_gamelan_notation_now": >-
205 + Share gending notation!
206 + "home_share_now": >-
207 + ¡Comparte tu acorde!
208 + "home_signup_now": >-
209 + ¡Regístrate ahora!
210 + "home_songs": >-
211 + Canciones
212 + "home_subheadline": >-
213 + ¿Cuerdas de guitarra? ¿Letras de canciones? ¿Reseñas de álbumes? ¿Notación de
214 + genero javanés? etc! Regístrese, contribuya (agregue artistas, álbumes, letras,
215 + etc.) y gane %{bach_link} token de reputación.
216 + "home_youtube_karaoke": >-
217 + Youtube Karaoke
218 + "home_youtube_karaoke_headline": >-
219 + Versión de karaoke de tu video musical favorito de YouTube con tecnología de IA.
220 + "invalid_login": >-
221 + Combinación de correo electrónico/contraseña no válida
222 + "locale_name": >-
223 + Español
224 + "login": >-
225 + ¡Acceso!
226 + "login_with_facebook": >-
227 + Iniciar sesión con Facebook
228 + "lyric": >-
229 + Song Lyric
230 + "lyric_cannot_be_empty": >-
231 + Lyric can't be empty
232 + "my_profile": >-
233 + My Profile
234 + "my_profile_link_href": >-
235 + this secure link
236 + "my_profile_link_html": >-
237 + This is your public profile. Follow %{href} to edit your profile.
238 + "my_profile_private": >-
239 + This profile is private
240 + "name": >-
241 + Nombre
242 + "nav_albums": >-
243 + Álbumes
244 + "nav_artists": >-
245 + Artistas
246 + "nav_change_language": >-
247 + Cambiar idioma:
248 + "nav_chords": >-
249 + Cuerdas de guitarra
250 + "nav_chords_shapes": >-
251 + Diagrama de acordes de guitarra
252 + "nav_community": >-
253 + Comunidad
254 + "nav_en": >-
255 + Inglesa
256 + "nav_footer_about": >-
257 + Sobre nosotras
258 + "nav_footer_company": >-
259 + Compañía
260 + "nav_footer_contact": >-
261 + Contacto
262 + "nav_footer_follow_us": >-
263 + Síganos
264 + "nav_footer_privacy": >-
265 + Política de privacidad
266 + "nav_footer_term": >-
267 + Terminos de uso
268 + "nav_guitar": >-
269 + Guitarra
270 + "nav_id": >-
271 + bahasa indonesia
272 + "nav_javanese_gamelan": >-
273 + gamelán javanés
274 + "nav_javanese_gamelan_about": >-
275 + Acerca de Gamelan javanés
276 + "nav_javanese_gending": >-
277 + Gending javanés
278 + "nav_javanese_gending_notation": >-
279 + Notación de canciones javanesas
280 + "nav_login": >-
281 + Acceso
282 + "nav_login_dropdown": >-
283 + Cuenta
284 + "nav_logout": >-
285 + Cerrar sesión
286 + "nav_lyrics": >-
287 + Letras de canciones
288 + "nav_music_database": >-
289 + musicas
290 + "nav_new_entries": >-
291 + Nuevas entradas
292 + "nav_signup": >-
293 + Inscribirse
294 + "nav_songs": >-
295 + Canciones
296 + "nav_ukulele_chords": >-
297 + Acordes de ukelele (próximamente)
298 + "need_to_logged_in": >-
299 + You need to logged in before giving this love. Don't have account yet? Just signup,
300 + it's so easy. You can use your Facebook account if you want.
301 + "need_to_logged_in_my_profile": >-
302 + You need to be logged in to perform this task.
303 + "new_user": >-
304 + ¿Nueva usuaria?
305 + "or": >-
306 + O
307 + "our_community": >-
308 + Our Community
309 + "our_guitar_chords_collection": >-
310 + Our Newest Guitar Chords Collections
311 + "our_newest_song_lyrics": >-
312 + Our Newest Song Lyrics Collection
313 + "password": >-
314 + Contraseña
315 + "password_button_update": >-
316 + Update
317 + "password_confirmation": >-
318 + Confirmación de contraseña
319 + "password_new": >-
320 + Enter new password
321 + "password_new_confirmation": >-
322 + Confirm password
323 + "routes":
324 + "about_us": >-
325 + about-us
326 + "add_gending": >-
327 + add-gending-gamelan
328 + "add_javanese_gamelan_notation": >-
329 + add-javanese-gamelan-notation
330 + "album": >-
331 + albums
332 + "album_add": >-
333 + add-album
334 + "album_detail": >-
335 + albums
336 + "artist": >-
337 + artists
338 + "artist_add": >-
339 + add-artist
340 + "artist_detail": >-
341 + artists
342 + "audio_files": >-
343 + audio-files
344 + "bass_backing_track": >-
345 + bass-backing-track-:slug
346 + "community": >-
347 + community
348 + "contact": >-
349 + contact
350 + "drum_backing_track": >-
351 + drum-backing-track-:slug
352 + "followers": >-
353 + followers
354 + "following": >-
355 + following
356 + "guitar_backing_track": >-
357 + guitar-backing-track-:slug
358 + "guitar_chord_shape": >-
359 + guitar-chord-:slug
360 + "guitar_chord_shapes": >-
361 + all-guitar-chords
362 + "guitar_chords": >-
363 + guitar-chords
364 + "guitar_chords_add": >-
365 + add-chord
366 + "guitar_chords_detail": >-
367 + guitar-chords-:slug
368 + "guitar_chords_history": >-
369 + guitar-chords-:slug/history
370 + "guitar_chords_review": >-
371 + guitar-chords-:slug/review-chord
372 + "javanese_gamelan": >-
373 + javanese-gamelan
374 + "javanese_gamelan_notation": >-
375 + javanese-gamelan
376 + "javanese_gending": >-
377 + javanese-gending
378 + "javanese_gending_detail": >-
379 + javanese-gending-gamelan-:slug
380 + "javanese_gending_gamelan_notation": >-
381 + notation-for-javanese-gamelan
382 + "javanese_gending_gamelan_notation_detail": >-
383 + notation-for-javanese-gamelan-:slug
384 + "login": >-
385 + login
386 + "most_loved_songs": >-
387 + most-loved-songs
388 + "privacy_policy": >-
389 + privacy-policy
390 + "signup": >-
391 + signup
392 + "song": >-
393 + songs
394 + "song_add": >-
395 + add-song
396 + "song_audio": >-
397 + song-audio
398 + "song_bass_tab": >-
399 + bass-tab-:slug
400 + "song_chords": >-
401 + song-chords
402 + "song_detail": >-
403 + songs
404 + "song_karaoke_detail": >-
405 + karaoke-song-:slug
406 + "song_lyrics": >-
407 + song-lyrics
408 + "song_lyrics_detail": >-
409 + song-lyrics-:slug
410 + "term_of_use": >-
411 + term-of-use
412 + "user_rep": >-
413 + reputation
414 + "see_album_detail": >-
415 + Ver detalle del álbum
416 + "see_artist_detail": >-
417 + Ver detalle del artista
418 + "see_chord_detail": >-
419 + See chord detail
420 + "see_song_detail": >-
421 + See song detail
422 + "signup": >-
423 + Inscribirse
424 + "signup_now": >-
425 + ¡Regístrate ahora!
426 + "signup_to_claim": >-
427 + ¿Esto es tuyo?
428 + "signup_with_facebook": >-
429 + Registrate con Facebook
430 + "site_name": >-
431 + Musik88.com
432 + "somebody_to_love": >-
433 + Love me
434 + "song_audio_page_title": >-
435 + Audio files for %{song_name}
436 + "song_chord_page_title": >-
437 + Guitar chord %{song_name} by %{song_artist_name}
438 + "song_chord_unavailable_content": >-
439 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
440 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
441 + that's all we need.
442 + "song_chord_unavailable_meta_description": >-
443 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
444 + album.
445 + "song_chords_page_title": >-
446 + %{song_name} Chords
447 + "song_detail": >-
448 + Song detail
449 + "song_detail_page_meta_title": >-
450 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
451 + album/single.
452 + "song_detail_page_title": >-
453 + %{song_name} Song Review
454 + "song_generate_your_own_bass_backing_track": >-
455 + Generate %{song_name} Bass Backing Track
456 + "song_generate_your_own_drum_backing_track": >-
457 + Generate %{song_name} Drum Backing Track
458 + "song_generate_your_own_karaoke": >-
459 + Generate %{song_name} Karaoke File
460 + "song_generate_your_own_stem_files": >-
461 + Generate STEM Files For %{song_name}
462 + "song_karaoke_download": >-
463 + Download Karaoke Version
464 + "song_karaoke_page_title": >-
465 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
466 + Album
467 + "song_karaoke_title": >-
468 + %{filename} Karaoke Version
469 + "song_karaoke_unavailable_content": >-
470 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
471 + "song_karaoke_unavailable_meta_description": >-
472 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
473 + Album
474 + "song_lyric_detail": >-
475 + Song lyric %{title}
476 + "song_lyric_page_title": >-
477 + %{song_name} Lyric by %{song_artist_name}
478 + "song_lyric_unavailable_content": >-
479 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
480 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
481 + or %{href} to this song so we can just focus on finding the chords.
482 + "song_lyric_unavailable_content_add_lyric": >-
483 + Add Lyric
484 + "song_lyric_unavailable_meta_description": >-
485 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
486 + album.
487 + "song_lyrics_page_title": >-
488 + Song Lyrics %{song_name}
489 + "song_menu_aside_audio": >-
490 + Audio
491 + "song_menu_aside_chords": >-
492 + Acordes
493 + "song_menu_aside_info": >-
494 + Información
495 + "song_menu_aside_lyrics": >-
496 + Lyrics
497 + "song_menu_bass_backing_track": >-
498 + Bass backing track %{song_name}
499 + "song_menu_bass_tab": >-
500 + Bass tab %{song_name}
501 + "song_menu_drum_backing_track": >-
502 + Drum backing track %{song_name}
503 + "song_menu_guitar_chord": >-
504 + Guitar Chord %{song_name}
505 + "song_menu_karaoke": >-
506 + Karaoke Version %{song_name}
507 + "song_menu_lyric": >-
508 + Song Lyric %{song_name}
509 + "song_updated_at": >-
510 + Updated: %{date}
511 + "still_login": >-
512 + Recuérdame
513 + "top_10_albums": >-
514 + Las 10 mejores álbumes
515 + "top_10_artists": >-
516 + Las 10 mejores artistas
517 + "top_10_guitar_chords": >-
518 + Los 10 mejores acordes de guitarra
519 + "top_10_song_lyrics": >-
520 + Las 10 mejores letras de canciones
521 + "top_10_songs": >-
522 + Las 10 mejores canciones
523 + "user_profile": >-
524 + Profile
525 + "user_reset_password_email_heading": >-
526 + Reset password link only for you, %{user_name}!
527 + "user_reset_password_email_heading_p1": >-
528 + You are getting this email because you are trying to access your account.
529 + "user_reset_password_email_heading_p2": >-
530 + Click %{href} to reset your password.
531 + "user_reset_password_email_link": >-
532 + este enlace de una sola vez
533 + "user_send_reset_password_instructions_subject": >-
534 + Reset password instruction
535 + "user_signup_email_heading": >-
536 + ¡Gracias por registrarte, %{user_name}!
537 + "user_signup_email_heading_p1": >-
538 + ¡Gracias por unirte a nosotros!
539 + "user_signup_email_heading_p2": >-
540 + Estamos en una misión para digitalizar músicas subrepresentadas en el mundo. %{href}
541 + y comienza a contribuir :)
542 + "user_signup_email_subject": >-
543 + ¡Bienvenido a Musik88!
544 + "welcome_user": >-
545 + Welcome, %{user_name}!
backend/musik88-web/config/locales/fr-fr.yml new
+440
@@ -0,0 +1,440 @@
1 +---
2 +"fr-fr":
3 + "10_most_loved_songs": >-
4 + 10 chansons les plus aimées
5 + "add_chord_validation_success": >-
6 + Merci d'avoir contribué !
7 + "album_artists": >-
8 + Artistes
9 + "album_song_list": >-
10 + Liste des chansons
11 + "artist_profile_album_contribution_title": >-
12 + %{artist} apparaît également sur :
13 + "artist_profile_album_title": >-
14 + Albums de %{artist}
15 + "breadcrumb_home": >-
16 + Accueil
17 + "breadcrumb_most_loved_songs": >-
18 + Chansons les plus aimées
19 + "by": >-
20 + Loved by
21 + "cancel": >-
22 + Cancel
23 + "confirm_email": >-
24 + Подтвердите ваш адрес электронной почты
25 + "create_account": >-
26 + Create account
27 + "email": >-
28 + E-mail
29 + "find_your_artist": >-
30 + Find Your Favorite Artist
31 + "find_your_favorite_album": >-
32 + Trouvez votre nouvel album préféré
33 + "find_your_favorite_songs": >-
34 + Trouvez vos chansons préférées
35 + "followers": >-
36 + Follower
37 + "following": >-
38 + Following
39 + "footer": >-
40 + Créé pour le plaisir par %{profile} de partout dans le %{instagram} &middot; &copy;
41 + 2021
42 + "footer_world": >-
43 + monde
44 + "forgot_password": >-
45 + Mot de passe oublié?
46 + "gending_add": >-
47 + Share your gending notation
48 + "gending_add_explanation": >-
49 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
50 + token.
51 + "gending_description": >-
52 + Description
53 + "gending_name": >-
54 + Gending Name
55 + "gending_notation": >-
56 + Gending Notation
57 + "gending_notation_add": >-
58 + Add gending notation
59 + "gending_notation_link": >-
60 + Javanese gending notation %{slug}
61 + "gending_notation_meta_desciption": >-
62 + This is the javanese gending notation for %{title} gending.
63 + "gending_notation_page_title": >-
64 + Javanese Gending Notation %{title}
65 + "guitar_chord_image": >-
66 + Guitar Chord Image
67 + "guitar_chord_link": >-
68 + Guitar chord %{slug}
69 + "guitar_chord_video": >-
70 + Guitar Chord Video
71 + "guitar_chords_shape": >-
72 + Guitar Chords Shape
73 + "h2_find_new_albums": >-
74 + Trouvez de nouveaux albums ici !
75 + "h2_find_new_songs": >-
76 + Trouvez de nouvelles musiques ici !
77 + "have_account": >-
78 + Already have account?
79 + "hello": >-
80 + Welcome to Musik 88
81 + "home_guitar_chords": >-
82 + Accords de guitare
83 + "home_guitar_chords_headline": >-
84 + Soumettez vos accords de guitare et gagnez %{bach_link} jeton de réputation pour
85 + votre contribution.
86 + "home_headline": >-
87 + La plus grande base de données musicale au monde !
88 + "home_javanese_gending_notation": >-
89 + Notation de genre javanaise
90 + "home_javanese_gending_notation_subheadline": >-
91 + Numérisez le gamelan javanais et gagnez %{bach_link} jeton de réputation pour
92 + votre contribution.
93 + "home_new_entries": >-
94 + New Entries
95 + "home_others": >-
96 + Autres
97 + "home_others_headline": >-
98 + Découvrez mes autres projets.
99 + "home_popular": >-
100 + Popular
101 + "home_see_all": >-
102 + Voir tout
103 + "home_share_gamelan_notation_now": >-
104 + Partagez la notation de genre !
105 + "home_share_now": >-
106 + Partagez votre accord!
107 + "home_signup_now": >-
108 + S'inscrire maintenant!
109 + "home_songs": >-
110 + Chansons
111 + "home_subheadline": >-
112 + Accords de guitare? Paroles de chansons? Des critiques d'albums ? Notation de
113 + genre javanaise ? etc! Inscrivez-vous, contribuez (ajoutez des artistes, des albums,
114 + des paroles, etc.) et gagnez %{bach_link} jeton de réputation.
115 + "home_youtube_karaoke": >-
116 + Karaoké Youtube
117 + "home_youtube_karaoke_headline": >-
118 + Version karaoké de votre vidéo musicale YouTube préférée optimisée par l'IA.
119 + "invalid_login": >-
120 + Combinaison email/mot de passe invalide
121 + "locale_name": "\U0001F1EB\U0001F1F7 Française"
122 + "login": >-
123 + Login!
124 + "login_with_facebook": >-
125 + Se connecter avec Facebook
126 + "lyric": >-
127 + Song Lyric
128 + "lyric_cannot_be_empty": >-
129 + Lyric can't be empty
130 + "my_profile": >-
131 + My Profile
132 + "my_profile_link_href": >-
133 + this secure link
134 + "my_profile_link_html": >-
135 + This is your public profile. Follow %{href} to edit your profile.
136 + "my_profile_private": >-
137 + This profile is private
138 + "name": >-
139 + Name
140 + "nav_albums": >-
141 + Albums
142 + "nav_artists": >-
143 + Artists
144 + "nav_change_language": >-
145 + Change Language:
146 + "nav_chords": >-
147 + Guitar Chords
148 + "nav_chords_shapes": >-
149 + Guitar Chords Diagram
150 + "nav_community": >-
151 + Community
152 + "nav_en": >-
153 + English
154 + "nav_footer_about": >-
155 + About Us
156 + "nav_footer_company": >-
157 + Company
158 + "nav_footer_contact": >-
159 + Contact
160 + "nav_footer_follow_us": >-
161 + Follow Us
162 + "nav_footer_privacy": >-
163 + Privacy Policy
164 + "nav_footer_term": >-
165 + Term of Use
166 + "nav_guitar": >-
167 + Guitar
168 + "nav_id": >-
169 + Bahasa Indonesia
170 + "nav_javanese_gamelan": >-
171 + Javanese Gamelan
172 + "nav_javanese_gamelan_about": >-
173 + About Javanese Gamelan
174 + "nav_javanese_gending": >-
175 + Javanese Gending
176 + "nav_javanese_gending_notation": >-
177 + Javanese Gending Notation
178 + "nav_login": >-
179 + Login
180 + "nav_login_dropdown": >-
181 + Account
182 + "nav_logout": >-
183 + Se déconnecter
184 + "nav_lyrics": >-
185 + Song Lyrics
186 + "nav_music_database": >-
187 + Musics
188 + "nav_new_entries": >-
189 + New Entries
190 + "nav_songs": >-
191 + Chansons
192 + "nav_ukulele_chords": >-
193 + Ukulele Chords (soon)
194 + "need_to_logged_in": >-
195 + You need to logged in before giving this love. Don't have account yet? Just signup,
196 + it's so easy. You can use your Facebook account if you want.
197 + "need_to_logged_in_my_profile": >-
198 + You need to be logged in to perform this task.
199 + "new_user": >-
200 + Nouvel utilisateur?
201 + "or": >-
202 + Ou
203 + "our_community": >-
204 + Our Community
205 + "our_guitar_chords_collection": >-
206 + Our Newest Guitar Chords Collections
207 + "our_newest_song_lyrics": >-
208 + Notre nouvelle collection de paroles de chansons
209 + "password": >-
210 + Mot de passe
211 + "password_button_update": >-
212 + Update
213 + "password_confirmation": >-
214 + Confirmation mot de passe
215 + "password_new": >-
216 + Enter new password
217 + "password_new_confirmation": >-
218 + Confirm password
219 + "routes":
220 + "about_us": >-
221 + about-us
222 + "add_gending": >-
223 + add-gending-gamelan
224 + "add_javanese_gamelan_notation": >-
225 + add-javanese-gamelan-notation
226 + "album": >-
227 + albums
228 + "album_add": >-
229 + add-album
230 + "album_detail": >-
231 + albums
232 + "artist": >-
233 + artists
234 + "artist_add": >-
235 + add-artist
236 + "artist_detail": >-
237 + artists
238 + "audio_files": >-
239 + audio-files
240 + "bass_backing_track": >-
241 + bass-backing-track-:slug
242 + "community": >-
243 + community
244 + "contact": >-
245 + contact
246 + "drum_backing_track": >-
247 + drum-backing-track-:slug
248 + "followers": >-
249 + followers
250 + "following": >-
251 + following
252 + "guitar_backing_track": >-
253 + guitar-backing-track-:slug
254 + "guitar_chord_shape": >-
255 + guitar-chord-:slug
256 + "guitar_chord_shapes": >-
257 + all-guitar-chords
258 + "guitar_chords": >-
259 + guitar-chords
260 + "guitar_chords_add": >-
261 + add-chord
262 + "guitar_chords_detail": >-
263 + guitar-chords-:slug
264 + "guitar_chords_history": >-
265 + guitar-chords-:slug/history
266 + "guitar_chords_review": >-
267 + guitar-chords-:slug/review-chord
268 + "javanese_gamelan": >-
269 + javanese-gamelan
270 + "javanese_gamelan_notation": >-
271 + javanese-gamelan
272 + "javanese_gending": >-
273 + javanese-gending
274 + "javanese_gending_detail": >-
275 + javanese-gending-gamelan-:slug
276 + "javanese_gending_gamelan_notation": >-
277 + notation-for-javanese-gamelan
278 + "javanese_gending_gamelan_notation_detail": >-
279 + notation-for-javanese-gamelan-:slug
280 + "login": >-
281 + login
282 + "most_loved_songs": >-
283 + chansons-les-plus-aimées
284 + "privacy_policy": >-
285 + privacy-policy
286 + "signup": >-
287 + signup
288 + "song": >-
289 + chansons
290 + "song_add": >-
291 + add-song
292 + "song_audio": >-
293 + chanson-audio
294 + "song_bass_tab": >-
295 + bass-tab-:slug
296 + "song_chords": >-
297 + song-chords
298 + "song_detail": >-
299 + songs
300 + "song_karaoke_detail": >-
301 + karaoke-song-:slug
302 + "song_lyrics": >-
303 + song-lyrics
304 + "song_lyrics_detail": >-
305 + song-lyrics-:slug
306 + "term_of_use": >-
307 + term-of-use
308 + "user_rep": >-
309 + reputation
310 + "see_album_detail": >-
311 + Voir le détail de l'album
312 + "see_artist_detail": >-
313 + See artist detail
314 + "see_chord_detail": >-
315 + See chord detail
316 + "see_song_detail": >-
317 + Voir le détail de la chanson
318 + "signup": >-
319 + Signup
320 + "signup_now": >-
321 + S'inscrire maintenant!
322 + "signup_to_claim": >-
323 + Is this yours?
324 + "signup_with_facebook": >-
325 + Inscrivez-vous avec Facebook
326 + "site_name": >-
327 + Musik88.com
328 + "somebody_to_love": >-
329 + Love me
330 + "song_audio_page_title": >-
331 + Fichiers audio pour %{song_name}
332 + "song_chord_page_title": >-
333 + Accord de guitare %{song_name} par %{song_artist_name}
334 + "song_chord_unavailable_content": >-
335 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
336 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
337 + that's all we need.
338 + "song_chord_unavailable_meta_description": >-
339 + Voici l'accord %{song_name} de %{song_artist_name} tiré de l'album %{song_album_name}.
340 + "song_chords_page_title": >-
341 + Accords de %{song_name}
342 + "song_detail": >-
343 + Détail de la chanson
344 + "song_detail_page_meta_title": >-
345 + %{song_name} de la critique de la chanson %{song_artist_name}, de l'album/single
346 + %{song_album_name}.
347 + "song_detail_page_title": >-
348 + Revue de la chanson %{song_name}
349 + "song_generate_your_own_bass_backing_track": >-
350 + Générer %{song_name} piste d'accompagnement de basse
351 + "song_generate_your_own_drum_backing_track": >-
352 + Générer %{song_name} piste d'accompagnement de batterie
353 + "song_generate_your_own_karaoke": >-
354 + Générer %{song_name} fichier karaoké
355 + "song_generate_your_own_stem_files": >-
356 + Générer des fichiers STEM pour %{song_name}
357 + "song_karaoke_download": >-
358 + Télécharger la version karaoké
359 + "song_karaoke_page_title": >-
360 + Télécharger la version karaoké de %{song_name} à partir de %{song_artist_name}
361 + à partir de l'album %{song_album_name}
362 + "song_karaoke_title": >-
363 + Version karaoké de %{filename}
364 + "song_karaoke_unavailable_content": >-
365 + La version karaoké de %{song_name} de %{song_artist_name} n'est pas encore disponible.
366 + "song_karaoke_unavailable_meta_description": >-
367 + Télécharger la version karaoké de %{song_name} à partir de %{song_artist_name}
368 + à partir de l'album %{song_album_name}
369 + "song_lyric_detail": >-
370 + Parole de chanson %{title}
371 + "song_lyric_page_title": >-
372 + Paroles de %{song_name} par %{song_artist_name}
373 + "song_lyric_unavailable_content": >-
374 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
375 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
376 + or %{href} to this song so we can just focus on finding the chords.
377 + "song_lyric_unavailable_content_add_lyric": >-
378 + Ajouter des paroles
379 + "song_lyric_unavailable_meta_description": >-
380 + Voici les paroles de %{song_name} de %{song_artist_name} extraites de l'album
381 + %{song_album_name}.
382 + "song_lyrics_page_title": >-
383 + Paroles de chanson %{song_name}
384 + "song_menu_aside_audio": >-
385 + l'audio
386 + "song_menu_aside_chords": >-
387 + Accords
388 + "song_menu_aside_info": >-
389 + Info
390 + "song_menu_aside_lyrics": >-
391 + paroles de chanson
392 + "song_menu_bass_backing_track": >-
393 + Piste d'accompagnement de basse %{song_name}
394 + "song_menu_bass_tab": >-
395 + Tablature basse %{song_name}
396 + "song_menu_drum_backing_track": >-
397 + Piste d'accompagnement de batterie %{song_name}
398 + "song_menu_guitar_chord": >-
399 + Accord de guitare %{song_name}
400 + "song_menu_karaoke": >-
401 + Version karaoké %{song_name}
402 + "song_menu_lyric": >-
403 + Parole de la chanson %{song_name}
404 + "song_updated_at": >-
405 + Mis à jour : %{date}
406 + "still_login": >-
407 + Souviens-toi de moi
408 + "top_10_albums": >-
409 + Les 10 meilleurs albums
410 + "top_10_artists": >-
411 + Top 10 Artists
412 + "top_10_guitar_chords": >-
413 + Top 10 Guitar Chords
414 + "top_10_song_lyrics": >-
415 + Top 10 des paroles de chansons
416 + "top_10_songs": >-
417 + Top 10 des chansons
418 + "user_profile": >-
419 + Profile
420 + "user_reset_password_email_heading": >-
421 + Reset password link only for you, %{user_name}!
422 + "user_reset_password_email_heading_p1": >-
423 + You are getting this email because you are trying to access your account.
424 + "user_reset_password_email_heading_p2": >-
425 + Click %{href} to reset your password.
426 + "user_reset_password_email_link": >-
427 + this one time link
428 + "user_send_reset_password_instructions_subject": >-
429 + Reset password instruction
430 + "user_signup_email_heading": >-
431 + Thanks for signing up, %{user_name}!
432 + "user_signup_email_heading_p1": >-
433 + Thanks for joining us!
434 + "user_signup_email_heading_p2": >-
435 + We are in a mission to digitalize underrepresented musics in the world. %{href}
436 + and start contributing :)
437 + "user_signup_email_subject": >-
438 + Welcome to Musik88!
439 + "welcome_user": >-
440 + Welcome, %{user_name}!
backend/musik88-web/config/locales/hi.yml new
+525
@@ -0,0 +1,525 @@
1 +---
2 +"hi":
3 + "10_most_loved_songs": >-
4 + 10 Most Loved Songs
5 + "active_admin":
6 + "filters":
7 + "predicates":
8 + "from": >-
9 + dari tanggal
10 + "to": >-
11 + sampai tanggal
12 + "activerecord":
13 + "errors":
14 + "models":
15 + "album":
16 + "attributes":
17 + "description":
18 + "too_short": >-
19 + Deskripsi artis tidak boleh kosong
20 + "name":
21 + "too_short": >-
22 + Nama album tidak boleh kosong
23 + "artist":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + Deskripsi artis tidak boleh kosong
28 + "name":
29 + "too_short": >-
30 + Nama artis tidak boleh kosong
31 + "chord":
32 + "attributes":
33 + "chord":
34 + "too_short": >-
35 + Chord lagu tidak boleh kosong
36 + "lyric":
37 + "attributes":
38 + "lyric":
39 + "too_short": >-
40 + Lirik lagu tidak boleh kosong
41 + "page":
42 + "attributes":
43 + "page_content":
44 + "too_short": >-
45 + Konten halaman tidak boleh kosong
46 + "page_slug":
47 + "too_short": >-
48 + Slug halaman tidak boleh kosong
49 + "song":
50 + "attributes":
51 + "album":
52 + "too_short": >-
53 + Album tidak boleh kosong
54 + "description":
55 + "too_short": >-
56 + Deskripsi lagu tidak boleh kosong
57 + "name":
58 + "too_short": >-
59 + Nama lagu tidak boleh kosong
60 + "user":
61 + "attributes":
62 + "email":
63 + "blank": >-
64 + Email tidak boleh kosong
65 + "invalid": >-
66 + Email tidak valid
67 + "name":
68 + "blank": >-
69 + Nama tidak boleh kosong
70 + "password":
71 + "blank": >-
72 + Password tidak boleh kosong
73 + "too_short": >-
74 + Password terlalu pendek
75 + "password_confirmation":
76 + "confirmation": >-
77 + Konfirmasi password tidak sama
78 + "album_artists": >-
79 + कलाकार की
80 + "album_song_list": >-
81 + गानो की सूची
82 + "artist_profile_album_contribution_title": >-
83 + %{artist} इस पर भी दिखाई देता है:
84 + "artist_profile_album_title": >-
85 + %{artist} के एल्बम
86 + "artist_profile_title": >-
87 + %{artist} Profile
88 + "breadcrumb_home": >-
89 + घर
90 + "breadcrumb_most_loved_songs": >-
91 + सबसे ज्यादा पसंद किए जाने वाले गाने
92 + "by": >-
93 + Loved by
94 + "cancel": >-
95 + Cancel
96 + "create_account": >-
97 + Create account
98 + "discussion": >-
99 + Discussion
100 + "email": >-
101 + Email
102 + "find_your_artist": >-
103 + Find Your Favorite Artist
104 + "find_your_favorite_album": >-
105 + अपना नया पसंदीदा एल्बम खोजें
106 + "find_your_favorite_songs": >-
107 + Find Your Favorite Songs
108 + "followers": >-
109 + Follower
110 + "following": >-
111 + Following
112 + "footer": >-
113 + Created for fun by %{profile} from all around the %{instagram} &middot; &copy;
114 + 2021
115 + "footer_world": >-
116 + world
117 + "forgot_password": >-
118 + Forgot password?
119 + "gending_add": >-
120 + Share your gending notation
121 + "gending_add_explanation": >-
122 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
123 + token.
124 + "gending_description": >-
125 + Description
126 + "gending_name": >-
127 + Gending Name
128 + "gending_notation": >-
129 + Gending Notation
130 + "gending_notation_add": >-
131 + Add gending notation
132 + "gending_notation_link": >-
133 + Javanese gending notation %{slug}
134 + "gending_notation_meta_desciption": >-
135 + This is the javanese gending notation for %{title} gending.
136 + "gending_notation_page_title": >-
137 + Javanese Gending Notation %{title}
138 + "guitar_chord_image": >-
139 + Guitar Chord Image
140 + "guitar_chord_link": >-
141 + Guitar chord %{slug}
142 + "guitar_chord_video": >-
143 + Guitar Chord Video
144 + "guitar_chords_shape": >-
145 + Guitar Chords Shape
146 + "h2_find_new_albums": >-
147 + यहां नए एल्बम खोजें!
148 + "h2_find_new_artists": >-
149 + Find New Artists Here!
150 + "h2_find_new_songs": >-
151 + Find New Musics Here!
152 + "have_account": >-
153 + Already have account?
154 + "hello": >-
155 + Welcome to Musik 88
156 + "home_add_album": >-
157 + एल्बम जोड़ें
158 + "home_add_artist": >-
159 + कलाकार जोड़ें
160 + "home_guitar_chords": >-
161 + गिटार की तारें
162 + "home_guitar_chords_headline": >-
163 + अपने गिटार कॉर्ड सबमिट करें और अपने योगदान के लिए %{bach_link} प्रतिष्ठा टोकन
164 + अर्जित करें।
165 + "home_headline": >-
166 + दुनिया का सबसे बड़ा संगीत डेटाबेस!
167 + "home_javanese_gending_notation": >-
168 + Javanese Gending Notation
169 + "home_javanese_gending_notation_subheadline": >-
170 + जावानीस गैमेलन को डिजिटाइज़ करें और अपने योगदान के लिए %{bach_link} प्रतिष्ठा
171 + टोकन अर्जित करें।
172 + "home_lyrics": >-
173 + गाने के बोल
174 + "home_new_entries": >-
175 + नयी प्रविष्टियां
176 + "home_others": >-
177 + अन्य
178 + "home_others_headline": >-
179 + मेरे अन्य प्रोजेक्ट देखें।
180 + "home_popular": >-
181 + लोकप्रिय
182 + "home_see_all": >-
183 + सभी देखें
184 + "home_share_gamelan_notation_now": >-
185 + लिंग संकेतन साझा करें!
186 + "home_share_now": >-
187 + अपना राग साझा करें!
188 + "home_signup_now": >-
189 + अभी साइनअप करें!
190 + "home_songs": >-
191 + गीत
192 + "home_subheadline": >-
193 + गिटार की तारें? गाने के बोल? एल्बम समीक्षा? जावानीस लिंग संकेतन? आदि! साइन अप
194 + करें, योगदान दें (कलाकार, एल्बम, गीत आदि जोड़ें), और %{bach_link} प्रतिष्ठा टोकन
195 + अर्जित करें।
196 + "home_youtube_karaoke": >-
197 + Youtube Karaoke
198 + "home_youtube_karaoke_headline": >-
199 + AI द्वारा संचालित आपके पसंदीदा YouTube संगीत वीडियो का कराओके संस्करण।
200 + "invalid_login": >-
201 + Invalid email/password combination
202 + "locale_name": >-
203 + हिन्दी
204 + "login": >-
205 + Login!
206 + "login_with_facebook": >-
207 + Login with Facebook
208 + "lyric": >-
209 + Song Lyric
210 + "lyric_cannot_be_empty": >-
211 + Lyric can't be empty
212 + "my_profile": >-
213 + My Profile
214 + "my_profile_link_href": >-
215 + this secure link
216 + "my_profile_link_html": >-
217 + This is your public profile. Follow %{href} to edit your profile.
218 + "my_profile_private": >-
219 + This profile is private
220 + "name": >-
221 + Name
222 + "nav_albums": >-
223 + एलबम
224 + "nav_artists": >-
225 + कलाकार की
226 + "nav_change_language": >-
227 + भाषा बदलो:
228 + "nav_chords": >-
229 + गिटार की तारें
230 + "nav_chords_shapes": >-
231 + गिटार तार आरेख
232 + "nav_community": >-
233 + समुदाय
234 + "nav_en": >-
235 + अंग्रेज़ी
236 + "nav_footer_about": >-
237 + हमारे बारे में
238 + "nav_footer_company": >-
239 + Company
240 + "nav_footer_contact": >-
241 + Contact
242 + "nav_footer_follow_us": >-
243 + Follow Us
244 + "nav_footer_privacy": >-
245 + Privacy Policy
246 + "nav_footer_term": >-
247 + Term of Use
248 + "nav_guitar": >-
249 + Guitar
250 + "nav_id": >-
251 + Bahasa Indonesia
252 + "nav_javanese_gamelan": >-
253 + Javanese Gamelan
254 + "nav_javanese_gamelan_about": >-
255 + About Javanese Gamelan
256 + "nav_javanese_gending": >-
257 + Javanese Gending
258 + "nav_javanese_gending_notation": >-
259 + Javanese Gending Notation
260 + "nav_login": >-
261 + Login
262 + "nav_login_dropdown": >-
263 + Account
264 + "nav_logout": >-
265 + Log out
266 + "nav_lyrics": >-
267 + Song Lyrics
268 + "nav_music_database": >-
269 + Musics
270 + "nav_new_entries": >-
271 + New Entries
272 + "nav_signup": >-
273 + Signup
274 + "nav_songs": >-
275 + Songs
276 + "nav_ukulele_chords": >-
277 + Ukulele Chords (soon)
278 + "need_to_logged_in": >-
279 + You need to logged in before giving this love. Don't have account yet? Just signup,
280 + it's so easy. You can use your Facebook account if you want.
281 + "need_to_logged_in_my_profile": >-
282 + You need to be logged in to perform this task.
283 + "new_user": >-
284 + New user?
285 + "or": >-
286 + Or
287 + "our_community": >-
288 + Our Community
289 + "our_guitar_chords_collection": >-
290 + Our Newest Guitar Chords Collections
291 + "our_newest_song_lyrics": >-
292 + Our Newest Song Lyrics Collection
293 + "password": >-
294 + Password
295 + "password_button_update": >-
296 + Update
297 + "password_confirmation": >-
298 + Password confirmation
299 + "password_new": >-
300 + Enter new password
301 + "password_new_confirmation": >-
302 + Confirm password
303 + "routes":
304 + "about_us": >-
305 + about-us
306 + "add_gending": >-
307 + add-gending-gamelan
308 + "add_javanese_gamelan_notation": >-
309 + add-javanese-gamelan-notation
310 + "album": >-
311 + albums
312 + "album_add": >-
313 + add-album
314 + "album_detail": >-
315 + albums
316 + "artist": >-
317 + artists
318 + "artist_add": >-
319 + add-artist
320 + "artist_detail": >-
321 + artists
322 + "audio_files": >-
323 + audio-files
324 + "bass_backing_track": >-
325 + bass-backing-track-:slug
326 + "community": >-
327 + community
328 + "contact": >-
329 + contact
330 + "drum_backing_track": >-
331 + drum-backing-track-:slug
332 + "followers": >-
333 + followers
334 + "following": >-
335 + following
336 + "guitar_backing_track": >-
337 + guitar-backing-track-:slug
338 + "guitar_chord_shape": >-
339 + guitar-chord-:slug
340 + "guitar_chord_shapes": >-
341 + all-guitar-chords
342 + "guitar_chords": >-
343 + guitar-chords
344 + "guitar_chords_add": >-
345 + add-chord
346 + "guitar_chords_detail": >-
347 + guitar-chords-:slug
348 + "guitar_chords_history": >-
349 + guitar-chords-:slug/history
350 + "guitar_chords_review": >-
351 + guitar-chords-:slug/review-chord
352 + "javanese_gamelan": >-
353 + javanese-gamelan
354 + "javanese_gamelan_notation": >-
355 + javanese-gamelan
356 + "javanese_gending": >-
357 + javanese-gending
358 + "javanese_gending_detail": >-
359 + javanese-gending-gamelan-:slug
360 + "javanese_gending_gamelan_notation": >-
361 + notation-for-javanese-gamelan
362 + "javanese_gending_gamelan_notation_detail": >-
363 + notation-for-javanese-gamelan-:slug
364 + "login": >-
365 + login
366 + "most_loved_songs": >-
367 + most-loved-songs
368 + "privacy_policy": >-
369 + privacy-policy
370 + "signup": >-
371 + signup
372 + "song": >-
373 + songs
374 + "song_add": >-
375 + add-song
376 + "song_audio": >-
377 + song-audio
378 + "song_bass_tab": >-
379 + bass-tab-:slug
380 + "song_chords": >-
381 + song-chords
382 + "song_detail": >-
383 + songs
384 + "song_karaoke_detail": >-
385 + karaoke-song-:slug
386 + "song_lyrics": >-
387 + song-lyrics
388 + "song_lyrics_detail": >-
389 + song-lyrics-:slug
390 + "term_of_use": >-
391 + term-of-use
392 + "user_rep": >-
393 + reputation
394 + "see_album_detail": >-
395 + एल्बम विवरण देखें
396 + "see_artist_detail": >-
397 + See artist detail
398 + "see_chord_detail": >-
399 + See chord detail
400 + "see_song_detail": >-
401 + See song detail
402 + "signup": >-
403 + Signup
404 + "signup_now": >-
405 + Signup now!
406 + "signup_to_claim": >-
407 + Is this yours?
408 + "signup_with_facebook": >-
409 + Signup with Facebook
410 + "site_name": >-
411 + Musik88.com
412 + "somebody_to_love": >-
413 + Love me
414 + "song_audio_page_title": >-
415 + Audio files for %{song_name}
416 + "song_chord_page_title": >-
417 + Guitar chord %{song_name} by %{song_artist_name}
418 + "song_chord_unavailable_content": >-
419 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
420 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
421 + that's all we need.
422 + "song_chord_unavailable_meta_description": >-
423 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
424 + album.
425 + "song_chords_page_title": >-
426 + %{song_name} Chords
427 + "song_detail": >-
428 + Song detail
429 + "song_detail_page_meta_title": >-
430 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
431 + album/single.
432 + "song_detail_page_title": >-
433 + %{song_name} Song Review
434 + "song_generate_your_own_bass_backing_track": >-
435 + Generate %{song_name} Bass Backing Track
436 + "song_generate_your_own_drum_backing_track": >-
437 + Generate %{song_name} Drum Backing Track
438 + "song_generate_your_own_karaoke": >-
439 + Generate %{song_name} Karaoke File
440 + "song_generate_your_own_stem_files": >-
441 + Generate STEM Files For %{song_name}
442 + "song_karaoke_download": >-
443 + Download Karaoke Version
444 + "song_karaoke_page_title": >-
445 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
446 + Album
447 + "song_karaoke_title": >-
448 + %{filename} Karaoke Version
449 + "song_karaoke_unavailable_content": >-
450 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
451 + "song_karaoke_unavailable_meta_description": >-
452 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
453 + Album
454 + "song_lyric_detail": >-
455 + Song lyric %{title}
456 + "song_lyric_page_title": >-
457 + %{song_name} Lyric by %{song_artist_name}
458 + "song_lyric_unavailable_content": >-
459 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
460 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
461 + or %{href} to this song so we can just focus on finding the chords.
462 + "song_lyric_unavailable_content_add_lyric": >-
463 + Add Lyric
464 + "song_lyric_unavailable_meta_description": >-
465 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
466 + album.
467 + "song_lyrics_page_title": >-
468 + Song Lyrics %{song_name}
469 + "song_menu_aside_audio": >-
470 + Audio
471 + "song_menu_aside_chords": >-
472 + Chords
473 + "song_menu_aside_info": >-
474 + Info
475 + "song_menu_aside_lyrics": >-
476 + Lyrics
477 + "song_menu_bass_backing_track": >-
478 + Bass backing track %{song_name}
479 + "song_menu_bass_tab": >-
480 + Bass tab %{song_name}
481 + "song_menu_drum_backing_track": >-
482 + Drum backing track %{song_name}
483 + "song_menu_guitar_chord": >-
484 + Guitar Chord %{song_name}
485 + "song_menu_karaoke": >-
486 + Karaoke Version %{song_name}
487 + "song_menu_lyric": >-
488 + Song Lyric %{song_name}
489 + "song_updated_at": >-
490 + Updated: %{date}
491 + "still_login": >-
492 + Remember me
493 + "top_10_albums": >-
494 + शीर्ष 10 एल्बम
495 + "top_10_artists": >-
496 + Top 10 Artists
497 + "top_10_guitar_chords": >-
498 + Top 10 Guitar Chords
499 + "top_10_song_lyrics": >-
500 + Top 10 Song Lyrics
501 + "top_10_songs": >-
502 + Top 10 Songs
503 + "user_profile": >-
504 + Profile
505 + "user_reset_password_email_heading": >-
506 + Reset password link only for you, %{user_name}!
507 + "user_reset_password_email_heading_p1": >-
508 + You are getting this email because you are trying to access your account.
509 + "user_reset_password_email_heading_p2": >-
510 + Click %{href} to reset your password.
511 + "user_reset_password_email_link": >-
512 + this one time link
513 + "user_send_reset_password_instructions_subject": >-
514 + Reset password instruction
515 + "user_signup_email_heading": >-
516 + Thanks for signing up, %{user_name}!
517 + "user_signup_email_heading_p1": >-
518 + Thanks for joining us!
519 + "user_signup_email_heading_p2": >-
520 + We are in a mission to digitalize underrepresented musics in the world. %{href}
521 + and start contributing :)
522 + "user_signup_email_subject": >-
523 + Welcome to Musik88!
524 + "welcome_user": >-
525 + Welcome, %{user_name}!
backend/musik88-web/config/locales/id-id.yml new
+938
@@ -0,0 +1,938 @@
1 +---
2 +"id-id":
3 + "10_most_loved_songs": >-
4 + 10 Lagu paling banyak disuka
5 + "Album": >-
6 + Album
7 + "AlbumProvider": >-
8 + Jembatan album
9 + "Artist": >-
10 + Artis
11 + "ArtistProvider": >-
12 + Jembatan artis
13 + "JavaneseGending": >-
14 + Gending Jawa
15 + "JavaneseGendingNotation": >-
16 + Notasi Gending Jawa
17 + "Song": >-
18 + Lagu
19 + "SongProvider": >-
20 + Jembatan lagu
21 + "activate_solana_wallet": >-
22 + Aktifkan dompet kamu
23 + "active_admin":
24 + "comments":
25 + "add": >-
26 + Tambahkan Komentar
27 + "devise":
28 + "change_password":
29 + "submit": >-
30 + Ganti password saya
31 + "title": >-
32 + Ganti password kamu
33 + "email":
34 + "title": >-
35 + Email
36 + "links":
37 + "forgot_your_password": >-
38 + Lupa password?
39 + "resend_confirmation_instructions": >-
40 + Kirim kembali instruksi
41 + "resend_unlock_instructions": >-
42 + Kirim instruksi buka kunci
43 + "sign_in": >-
44 + Masuk
45 + "sign_in_with_omniauth_provider": >-
46 + Masuk dengan %{provider}
47 + "sign_up": >-
48 + Daftar
49 + "login":
50 + "remember_me": >-
51 + Ingat saya
52 + "submit": >-
53 + Masuk
54 + "title": >-
55 + Masuk
56 + "password":
57 + "title": >-
58 + Password
59 + "password_confirmation":
60 + "title": >-
61 + Konfirmasi password
62 + "resend_confirmation_instructions":
63 + "submit": >-
64 + Kirim kembali instruksi konfirmasi
65 + "title": >-
66 + Kirim kembali instruksi konfirmasi
67 + "username":
68 + "title": >-
69 + Username
70 + "filters":
71 + "predicates":
72 + "from": >-
73 + dari tanggal
74 + "to": >-
75 + sampai tanggal
76 + "next": >-
77 + Selanjutnya
78 + "activerecord":
79 + "errors":
80 + "models":
81 + "album":
82 + "attributes":
83 + "description":
84 + "too_short": >-
85 + Deskripsi artis tidak boleh kosong
86 + "name":
87 + "too_short": >-
88 + Nama album tidak boleh kosong
89 + "artist":
90 + "attributes":
91 + "description":
92 + "too_short": >-
93 + Deskripsi artis tidak boleh kosong
94 + "name":
95 + "too_short": >-
96 + Nama artis tidak boleh kosong
97 + "chord":
98 + "attributes":
99 + "chord":
100 + "too_short": >-
101 + Chord lagu tidak boleh kosong
102 + "lyric":
103 + "attributes":
104 + "lyric":
105 + "too_short": >-
106 + Lirik lagu tidak boleh kosong
107 + "page":
108 + "attributes":
109 + "page_content":
110 + "too_short": >-
111 + Konten halaman tidak boleh kosong
112 + "page_slug":
113 + "too_short": >-
114 + Slug halaman tidak boleh kosong
115 + "song":
116 + "attributes":
117 + "album":
118 + "too_short": >-
119 + Album tidak boleh kosong
120 + "description":
121 + "too_short": >-
122 + Deskripsi lagu tidak boleh kosong
123 + "name":
124 + "too_short": >-
125 + Nama lagu tidak boleh kosong
126 + "user":
127 + "attributes":
128 + "email":
129 + "blank": >-
130 + Email tidak boleh kosong
131 + "invalid": >-
132 + Email tidak valid
133 + "taken": >-
134 + Email sudah terdaftar. Silahkan gunakan email yang lain.
135 + "name":
136 + "blank": >-
137 + Nama tidak boleh kosong
138 + "password":
139 + "blank": >-
140 + Password tidak boleh kosong
141 + "too_short": >-
142 + Password terlalu pendek
143 + "password_confirmation":
144 + "confirmation": >-
145 + Konfirmasi password tidak sama
146 + "add_a_reply": >-
147 + Tambahkan balasan
148 + "add_chord_how_to": >-
149 + Cara menambahkan kord
150 + "add_chord_how_to_content": "- Tambahkan akord Anda dengan menuliskannya baris demi
151 + baris.\r\n- Tambahkan urutan akord di atas setiap baris.\r\n- Tambahkan spasi
152 + dan paragraf jika diperlukan.\r\n- Juga tambahkan bagian lagu (reff, verse, bridge)
153 + di setiap bagian lagu."
154 + "add_chord_label_chord": >-
155 + Kunci gitar
156 + "add_chord_label_song": >-
157 + Sematkan ke lagu...
158 + "add_chord_label_title": >-
159 + ... atau tambahkan judul lagu
160 + "add_chord_placeholder_chord": >-
161 + Tulis kunci gitarmu disini...
162 + "add_chord_placeholder_chose_song": >-
163 + Pilih lagu
164 + "add_chord_placeholder_title": >-
165 + Judul lagu harus diisi...
166 + "add_chord_share": >-
167 + Bagikan!
168 + "add_chord_title": >-
169 + Bagikan kord kamu
170 + "add_chord_validation": >-
171 + Tambahkan judul dan kord kamu
172 + "add_chord_validation_success": >-
173 + Terimakasih karena telah berkontribusi!
174 + "add_chord_warning": >-
175 + Anda belum masuk. %{href_login} dan klaim kontribusi Anda, atau %{href} akan mengklaim
176 + akord dan kontribusi Anda.
177 + "add_comment": >-
178 + Tambahkan komentar
179 + "added_by": >-
180 + Ditambahkan oleh
181 + "ago": >-
182 + yang lalu
183 + "album_artists": >-
184 + Artis
185 + "album_song_list": >-
186 + Daftar lagu
187 + "artist_profile_album_contribution_title": >-
188 + %{artist} juga muncul dalam album:
189 + "artist_profile_album_title": >-
190 + Album dari %{artist}
191 + "artist_profile_title": >-
192 + Profil %{artist}
193 + "bach_token_account": >-
194 + Akun Token
195 + "bach_token_address": >-
196 + Alamat Token
197 + "bach_token_decimal": >-
198 + Jumlah Desimal
199 + "bach_token_discussion": >-
200 + Bagaimana menurut kamu? Apakah kamu tertarik?
201 + "bach_token_fix_supply": >-
202 + Jumlah Suplai
203 + "bach_token_fractional": >-
204 + Pecahan
205 + "bach_token_id": >-
206 + ID Token
207 + "bach_token_name": >-
208 + Nama Token
209 + "bach_token_semitone": >-
210 + setengah nada
211 + "bach_token_supply": >-
212 + Suplai saat ini
213 + "bach_token_supply_cycles": >-
214 + Siklus Suplai
215 + "bach_token_symbol": >-
216 + Simbol
217 + "bach_token_trillion": >-
218 + triliun
219 + "badges": >-
220 + Lencana
221 + "breadcrumb_home": >-
222 + Beranda
223 + "breadcrumb_most_loved_songs": >-
224 + Lagu-lagu Paling Banyak Disuka
225 + "by": >-
226 + Dicintai oleh
227 + "cancel": >-
228 + Batalkan
229 + "change_language": >-
230 + Ganti bahasa
231 + "confirm_email": >-
232 + Konfirmasi email kamu
233 + "create_account": >-
234 + Buat akun
235 + "datetime":
236 + "distance_in_words":
237 + "about_x_hours":
238 + "one": >-
239 + kurang lebih 1 jam
240 + "other": >-
241 + kurang lebih %{count} jam
242 + "about_x_months":
243 + "one": >-
244 + kurang lebih 1 bulan
245 + "other": >-
246 + kurang lebih %{count} bulan
247 + "about_x_years":
248 + "one": >-
249 + kurang lebih 1 tahun
250 + "other": >-
251 + kurang lebih %{count} tahun
252 + "almost_x_years":
253 + "one": >-
254 + hampir 1 tahun
255 + "other": >-
256 + hampir %{count} tahun
257 + "less_than_x_minutes":
258 + "one": >-
259 + kurang dari semenit
260 + "other": >-
261 + kurang dari %{count} menit
262 + "less_than_x_seconds":
263 + "one": >-
264 + kurang dari 1 detik
265 + "other": >-
266 + kurang dari %{count} detik
267 + "over_x_years":
268 + "one": >-
269 + lebih dari 1 tahun
270 + "other": >-
271 + lebih dari %{count} tahun
272 + "x_days":
273 + "one": >-
274 + 1 jam
275 + "other": >-
276 + %{count} jam
277 + "x_minutes":
278 + "one": >-
279 + 1 menit
280 + "other": >-
281 + %{count} menit
282 + "x_months":
283 + "one": >-
284 + 1 bulan
285 + "other": >-
286 + %{count} bulan
287 + "x_seconds":
288 + "one": >-
289 + 1 detik
290 + "other": >-
291 + %{count} detik
292 + "devise":
293 + "confirmations":
294 + "confirmed": >-
295 + Email kamu sudah tervierfikasi
296 + "send_instructions": >-
297 + Kamu akan mendapat sebuah email berisi instruksi
298 + "send_paranoid_instructions": >-
299 + Jika email kamu ada di database kami, kamu akan menerima sebuah email berisi
300 + instruksi bagaimana mengkonfirmasi email kamu
301 + "failure":
302 + "already_authenticated": >-
303 + Kamu sudah masuk
304 + "inactive": >-
305 + Akun kamu belum aktif
306 + "invited": >-
307 + Kamu memiliki undangan yang tertunda, terimalah untuk menyelesaikan pembuatan
308 + akun kamu.
309 + "unconfirmed": >-
310 + Sebelum melanjutkan, konfirmasi alamat email kamu dulu.
311 + "mailer":
312 + "confirmation_instructions":
313 + "subject": >-
314 + Instruksi konfirmasi email
315 + "user_subject": >-
316 + Konfirmasi email Musik88.com
317 + "email_changed":
318 + "subject": >-
319 + Email diganti
320 + "invitation_instructions":
321 + "someone_invited_you": >-
322 + Seseorang telah mengundang kamu ke %{url}, kamu dapat menerimanya melalui
323 + tautan di bawah ini.
324 + "password_change":
325 + "subject": >-
326 + Kata sandi diganti
327 + "reset_password_instructions":
328 + "subject": >-
329 + Instrucksi atur ulang kata sandi
330 + "unlock_instructions":
331 + "subject": >-
332 + Instruksi buka kunci
333 + "registrations":
334 + "signed_up_but_unconfirmed": >-
335 + Kami telah mengirim sebuah email konfirmasi. Klik tautan pada email untuk
336 + mengaktifasi akun kamu.
337 + "discussion": >-
338 + Diskusi
339 + "discussion_comment_button": >-
340 + Kirim
341 + "discussion_comment_placeholder": >-
342 + Tulis komentarmu disini
343 + "email": >-
344 + Email
345 + "find_your_artist": >-
346 + Temukan Artist Idola Kamu
347 + "find_your_favorite_album": >-
348 + Temukan Album Baru Kesukaanmu
349 + "find_your_favorite_songs": >-
350 + Temukan Lagu Kesukaanmu
351 + "find_your_friends": >-
352 + Temukan temanmu disini...
353 + "followers": >-
354 + Pengikut
355 + "following": >-
356 + Mengikuti
357 + "footer": >-
358 + Iseng dibuat oleh %{profile} dari seluruh %{instagram} &middot; &copy; 2021
359 + "footer_world": >-
360 + dunia
361 + "forgot_password": >-
362 + Lupa password?
363 + "friends": >-
364 + Teman
365 + "gending_add": >-
366 + Bagikan notasi gendingmu
367 + "gending_add_explanation": >-
368 + Bantu kami mendigitalkan gamelan Jawa online. %{href} dan dapatkan uang kripto
369 + %{bach_link}.
370 + "gending_description": >-
371 + Deskripsi
372 + "gending_name": >-
373 + Judul Gending
374 + "gending_notation": >-
375 + Notasi gending
376 + "gending_notation_add": >-
377 + Tambah notasi gending
378 + "gending_notation_add_explanation": >-
379 + Ada notasi gending yang pingin kamu bagi? %{href} dan dapatkan uang kripto %{bach_link}.
380 + "gending_notation_link": >-
381 + Notasi gending Jawa %{slug}
382 + "gending_notation_meta_desciption": >-
383 + Berikut adalah notasi gending jawa untuk gending %{title}
384 + "gending_notation_page_title": >-
385 + Notasi Gending %{title}
386 + "generating_solana_wallet": >-
387 + Mengaktifkan dompet kamu...
388 + "guitar_chord_image": >-
389 + Gambar Kunci Gitar
390 + "guitar_chord_link": >-
391 + Kunci gitar %{slug}
392 + "guitar_chord_video": >-
393 + Video Kunci Gitar
394 + "guitar_chords_shape": >-
395 + Bentuk Kunci Gitar
396 + "have_account": >-
397 + Sudah punya akun?
398 + "hello": >-
399 + Lirik lagu terbaru! Koleksi lirik lagu terlengkap.
400 + "home_guitar_chords": >-
401 + Kunci Gitar
402 + "home_guitar_chords_headline": >-
403 + Bagikan koleksi kunci gitar Anda dan dapatkan uang kripto %{bach_link} untuk kontribusi
404 + Anda.
405 + "home_headline": >-
406 + Database musik terbesar didunia!
407 + "home_javanese_gending_notation": >-
408 + Notasi Gending Jawa
409 + "home_javanese_gending_notation_subheadline": >-
410 + Digitalkan gamelan jawa dan dapatkan uang kripto %{bach_link} untuk kontribusimu.
411 + "home_lyrics": >-
412 + Lirik Lagu
413 + "home_others": >-
414 + Lainnya
415 + "home_others_headline": >-
416 + Cek proyek-proyek saya yang lain.
417 + "home_popular": >-
418 + Populer
419 + "home_see_all": >-
420 + Lihat semua
421 + "home_share_gamelan_notation_now": >-
422 + Bagikan notasi gending!
423 + "home_share_now": >-
424 + Bagikan kunci gitarmu!
425 + "home_signup_now": >-
426 + Daftar sekarang!
427 + "home_songs": >-
428 + Lagu
429 + "home_subheadline": >-
430 + Kunci gitar? Lirik lagu? Ulasan album? Notasi gending Jawa? dsb! Daftar, berkontribusi
431 + (tambah lirik, artis, album, dsb), dan dapatkan uang kripto %{bach_link} kamu.
432 + "home_what_is": >-
433 + Apa itu Musik88?
434 + "home_youtube_karaoke": >-
435 + Youtube Karaoke
436 + "home_youtube_karaoke_headline": >-
437 + Versi karaoke dari video musik YouTube favorit kamu didukung oleh kecerdasan buatan.
438 + "invalid_login": >-
439 + Email/kata sandi salah
440 + "invite_button": >-
441 + Undang
442 + "join_our_chord_community": >-
443 + Gabung komunitas kita untuk melihat kord lagu <span class='badge bg-primary rounded-pill
444 + fw-bold fst-italic'>%{song_name}</span>. Kamu juga bisa mendapatkan uang kripto
445 + €BACH dengan mengaktifkan dompet kamu di Profil -> Pengaturan.
446 + "join_our_javanese_community": >-
447 + Gabung komunitas kita untuk melihat notasi <span class='badge bg-primary rounded-pill
448 + fw-bold fst-italic'>%{gending_name}</span>. Kamu juga bisa mendapatkan uang kripto
449 + €BACH dengan mengaktifkan dompet kamu di Profil -> Pengaturan.
450 + "join_our_lyric_community": >-
451 + Gabung komunitas kita untuk melihat lirik lagu <span class='badge bg-primary rounded-pill
452 + fw-bold fst-italic'>%{song_name}</span> dari <span class='badge bg-primary rounded-pill
453 + fw-bold fst-italic'>%{song_artist_name}</span>. Kamu juga bisa mendapatkan uang
454 + kripto €BACH dengan mengaktifkan dompet kamu di Profil -> Pengaturan.
455 + "legacy_view": >-
456 + Desain lama
457 + "like_this": >-
458 + menyukai ini
459 + "locale_name": "\U0001F1EE\U0001F1E9 Bahasa Indonesia"
460 + "login": >-
461 + Masuk
462 + "login_omniauth_explanation": >-
463 + Login dengan akun Spotify, Google, atau Facebook kamu.
464 + "login_web3_explanation": >-
465 + Masuk dengan penyedia dompet web3. Saat ini hanya mendukung Phantom wallet.
466 + "login_with_facebook": >-
467 + Masuk dengan Facebook
468 + "lyric": >-
469 + Lirik Lagu
470 + "lyric_cannot_be_empty": >-
471 + Lirik tidak boleh kosong
472 + "merit":
473 + "granted_badge": >-
474 + diberikan lencana %{badge_name}
475 + "removed_badge": >-
476 + menghapus lencana %{badge_name}
477 + "my_profile": >-
478 + Profil gue
479 + "my_profile_link_href": >-
480 + tautan aman ini
481 + "my_profile_link_html": >-
482 + Ini adalah halaman publik lo. Ikuti %{href} kalo mau perbarui data lo.
483 + "my_profile_private": >-
484 + Profil ini bersifat pribadi
485 + "name": >-
486 + Nama
487 + "nav_albums": >-
488 + Album
489 + "nav_artists": >-
490 + Artis
491 + "nav_buy_bach": >-
492 + Beli €BACH
493 + "nav_change_language": >-
494 + Ganti Bahasa:
495 + "nav_chords": >-
496 + Kunci Gitar
497 + "nav_chords_shapes": >-
498 + Bentuk Kunci Gitar
499 + "nav_community": >-
500 + Komunitas
501 + "nav_en": >-
502 + English
503 + "nav_footer_about": >-
504 + Tentang Kami
505 + "nav_footer_company": >-
506 + Perusahaan
507 + "nav_footer_contact": >-
508 + Kontak
509 + "nav_footer_follow_us": >-
510 + Ikuti kami
511 + "nav_footer_privacy": >-
512 + Kebijakan Privasi
513 + "nav_footer_term": >-
514 + Ketentuan Penggunaan
515 + "nav_guitar": >-
516 + Gitar
517 + "nav_id": >-
518 + Bahasa Indonesia
519 + "nav_javanese_gamelan": >-
520 + Gamelan Jawa
521 + "nav_javanese_gamelan_about": >-
522 + Tentang Gamelan Jawa
523 + "nav_javanese_gending": >-
524 + Gending Jawa
525 + "nav_javanese_gending_notation": >-
526 + Notasi Gending Jawa
527 + "nav_login": >-
528 + Masuk (web3 Didukung!)
529 + "nav_login_dropdown": >-
530 + Akun
531 + "nav_logout": >-
532 + Keluar
533 + "nav_lyrics": >-
534 + Lirik Lagu
535 + "nav_music_database": >-
536 + Musik
537 + "nav_onboarding": >-
538 + Onboarding
539 + "nav_signup": >-
540 + Daftar Akun
541 + "nav_songs": >-
542 + Lagu
543 + "nav_ukulele_chords": >-
544 + Kunci Ukulele (segera)
545 + "nav_what_is_this": >-
546 + Apa ini?
547 + "need_to_logged_in": >-
548 + Masuk log dulu sebelum kasih love. Belum punya akun? Daftar aja dulu, gampang
549 + kok. Bisa pake akun Facebook kamu lho.
550 + "need_to_logged_in_my_profile": >-
551 + You need to be logged in to perform this task.
552 + "new_user": >-
553 + Pengguna baru?
554 + "new_view": >-
555 + Desain baru
556 + "next": >-
557 + Selanjutnya
558 + "oh_hi": >-
559 + Oh, halo!
560 + "onboarding_done": >-
561 + Begitulah, %{user_name}!
562 + "onboarding_done_button": >-
563 + Saya siap!
564 + "onboarding_explanation": >-
565 + Dengan memverifikasi email, kamu sudah mendapatkan 0.1 BACH! Sekarang, dapatkan
566 + lebih banyak BACH dengan mengikuti langkah-langkah berikut.
567 + "onboarding_explanation_done": >-
568 + Kami sudah selesai untuk saat ini. Periksa profil kamu dan mulailah berkontribusi
569 + lebih banyak kepada komunitas.
570 + "onboarding_sounds_good": >-
571 + Baiklah!
572 + "onboarding_spotify_done": >-
573 + Selesai. Kamu mengekspor data Spotify kamu.
574 + "onboarding_spotify_explanation": >-
575 + Dapatkan hingga 50 BACH saat kamu mengekspor daftar putar kamu!
576 + "onboarding_spotify_heading": >-
577 + Ekspor Data Spotify Kamu
578 + "onboarding_spotify_processing": >-
579 + Mengekspor data kamu. Butuh waktu.
580 + "onboarding_username_checking": >-
581 + Memeriksa...
582 + "onboarding_username_done": >-
583 + Yeay! Kamu dapat username dan %{profile_badges_link}.
584 + "onboarding_username_explanation": >-
585 + Buat @username yang unik dan dapatkan 0,1 BACH lagi!
586 + "onboarding_username_heading": >-
587 + Buat @username
588 + "or": >-
589 + Atau
590 + "our_community": >-
591 + Komunitas Kami
592 + "our_guitar_chords_collection": >-
593 + Koleksi kunci gitar lagu terbaru dari kami
594 + "our_newest_song_lyrics": >-
595 + Koleksi Lirik Lagu Terbaru Kami
596 + "password": >-
597 + Kata sandi
598 + "password_confirmation": >-
599 + Konfirmasi kata sandi
600 + "password_new_confirmation": >-
601 + Konfirmasi sandi
602 + "pretty_badge": >-
603 + Lencana Cantik
604 + "reply": >-
605 + Balasan
606 + "reputation": >-
607 + Reputasi
608 + "resend_email_confirmation": >-
609 + Kirim ulang petunjuk konfirmasi
610 + "resend_email_confirmation_button": >-
611 + Kirim ulang instruksi
612 + "routes":
613 + "about_us": >-
614 + tentang-kami
615 + "add_gending": >-
616 + tambah-gending-gamelan
617 + "add_javanese_gamelan_notation": >-
618 + tambah-notasi-gamelan-jawa
619 + "album": >-
620 + album
621 + "album_add": >-
622 + tambah-album
623 + "album_bridge": >-
624 + albums-bridge
625 + "album_detail": >-
626 + album
627 + "artist": >-
628 + artis
629 + "artist_add": >-
630 + tambah-artist
631 + "artist_bridge": >-
632 + artists-bridge
633 + "artist_detail": >-
634 + artis
635 + "audio_files": >-
636 + audio-files
637 + "bass_backing_track": >-
638 + bass-backing-track-:slug
639 + "community": >-
640 + komunitas
641 + "confirm_email": >-
642 + konfirmasi-email
643 + "contact": >-
644 + kontak
645 + "drum_backing_track": >-
646 + drum-backing-track-:slug
647 + "followers": >-
648 + pengikut
649 + "following": >-
650 + mengikuti
651 + "guitar_backing_track": >-
652 + guitar-backing-track-:slug
653 + "guitar_chord_shape": >-
654 + bentuk-kunci-gitar-:slug
655 + "guitar_chord_shapes": >-
656 + bentuk-kunci-gitar
657 + "guitar_chords": >-
658 + kunci-gitar
659 + "guitar_chords_add": >-
660 + tambah-akord
661 + "guitar_chords_detail": >-
662 + kunci-gitar-:slug
663 + "guitar_chords_history": >-
664 + kunci-gitar-:slug/sejarah
665 + "guitar_chords_review": >-
666 + kunci-gitar-:slug/review-akord
667 + "javanese_gamelan": >-
668 + gamelan-jawa
669 + "javanese_gamelan_notation": >-
670 + gamelan-jawa
671 + "javanese_gending": >-
672 + gending-jawa
673 + "javanese_gending_detail": >-
674 + gending-gamelan-jawa-:slug
675 + "javanese_gending_gamelan_notation": >-
676 + notasi-gending-gamelan-jawa
677 + "javanese_gending_gamelan_notation_detail": >-
678 + notasi-gending-gamelan-jawa-:slug
679 + "javanese_gending_gamelan_notation_detail_new": >-
680 + notasi-gending-standar-:slug
681 + "login": >-
682 + masuk
683 + "most_loved_songs": >-
684 + lagu-paling-banyak-disuka
685 + "privacy_policy": >-
686 + kebijakan-privasi
687 + "search": >-
688 + cari
689 + "signup": >-
690 + daftar
691 + "song": >-
692 + lagu
693 + "song_add": >-
694 + tambah-lagu
695 + "song_audio": >-
696 + song-audio
697 + "song_bass_tab": >-
698 + bass-tab-:slug
699 + "song_bridge": >-
700 + songs-bridge
701 + "song_chords": >-
702 + kord-lagu
703 + "song_detail": >-
704 + lagu
705 + "song_karaoke_detail": >-
706 + karaoke-lagu-:slug
707 + "song_lyrics": >-
708 + lirik-lagu
709 + "song_lyrics_detail": >-
710 + lirik-lagu-:slug
711 + "term_of_use": >-
712 + ketentuan-penggunaan
713 + "user_rep": >-
714 + reputasi
715 + "what_is_this": >-
716 + apa-itu
717 + "what_is_this_detail": >-
718 + apa-itu-:slug
719 + "save": >-
720 + Simpan
721 + "search": >-
722 + Cari
723 + "search_placeholder_title": >-
724 + Cari artis, lagu, atau gending jawa...
725 + "search_result_for": >-
726 + Hasil pencarian untuk "<span class="fst-italic fw-bold">%{keywords}</span>"
727 + "see_album_detail": >-
728 + Lihat detail album
729 + "see_artist_detail": >-
730 + Lihat detail artis
731 + "see_chord_detail": >-
732 + Lihat detail kord
733 + "see_song_detail": >-
734 + Lihat detail lagu
735 + "settings": >-
736 + Pengaturan
737 + "signup": >-
738 + Daftar
739 + "signup_now": >-
740 + Daftar sekarang!
741 + "signup_omniauth_explanation": >-
742 + Daftar dengan akun Spotify, Google, atau Facebook kamu.
743 + "signup_to_claim": >-
744 + Ini punya lo?
745 + "signup_with_facebook": >-
746 + Daftar dengan Facebook
747 + "site_name": >-
748 + Musik88.id
749 + "somebody_to_love": >-
750 + Cintai aku
751 + "song_audio_page_title": >-
752 + File audio untuk %{song_name}
753 + "song_chord_page_title": >-
754 + Kunci Gitar %{song_name}
755 + "song_chord_unavailable_content": >-
756 + <span class='song-lyric-unavailable'>Kunci gitar %{song_name} dari %{song_artist_name}</span>
757 + belum tersedia. Beri cinta <span class='glyphicon glyphicon-heart'></span> pada
758 + lagu ini. Kami perlu setidaknya 2 cinta dari elo buat ngulik lagunya.
759 + "song_chord_unavailable_meta_description": >-
760 + Berikut adalah kunci gitar %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
761 + "song_chords_page_title": >-
762 + Kunci Gitar %{song_name}
763 + "song_detail": >-
764 + Detail lagu
765 + "song_detail_page_by": >-
766 + oleh %{artist_link}
767 + "song_detail_page_from_album": >-
768 + dari album %{album_link}
769 + "song_detail_page_meta_title": >-
770 + Arti lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
771 + "song_detail_page_title": >-
772 + Arti Lagu %{song_name}
773 + "song_generate_your_own_bass_backing_track": >-
774 + Buat sendiri backing track bas lagu %{song_name}
775 + "song_generate_your_own_drum_backing_track": >-
776 + Buat sendiri backing track drum lagu %{song_name}
777 + "song_generate_your_own_karaoke": >-
778 + Buat Versi Karaoke Lagu %{song_name}
779 + "song_generate_your_own_stem_files": >-
780 + Buat STEM Files dari lagu %{song_name}
781 + "song_karaoke_download": >-
782 + Download Lagu Karaoke
783 + "song_karaoke_page_title": >-
784 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
785 + "song_karaoke_title": >-
786 + %{filename} Versi Karaoke
787 + "song_karaoke_unavailable_content": >-
788 + Lagu karaoke %{song_name} dari %{song_artist_name} belum tersedia. Login untuk
789 + request.
790 + "song_karaoke_unavailable_meta_description": >-
791 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
792 + "song_lyric_page_title": >-
793 + Lirik Lagu %{song_name} dari %{song_artist_name}
794 + "song_lyric_unavailable_content": >-
795 + <span class='song-lyric-unavailable'>Lirik lagu %{song_name} dari %{song_artist_name}</span>
796 + belum tersedia. Beri setidaknya 2 cinta <span class='glyphicon glyphicon-heart'></span>
797 + atau %{href} pada lagu ini jadi kita bisa fokus buat ngulik kunci gitarnya.
798 + "song_lyric_unavailable_content_add_lyric": >-
799 + Tambah Lirik
800 + "song_lyric_unavailable_meta_description": >-
801 + Berikut adalah lirik lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
802 + "song_lyrics_page_title": >-
803 + Lirik Lagu %{song_name}
804 + "song_menu_aside_audio": >-
805 + Audio
806 + "song_menu_aside_chords": >-
807 + Kord
808 + "song_menu_aside_info": >-
809 + Info
810 + "song_menu_aside_lyrics": >-
811 + Lirik
812 + "song_menu_bass_backing_track": >-
813 + Bass backing track %{song_name}
814 + "song_menu_bass_tab": >-
815 + Bass tab %{song_name}
816 + "song_menu_drum_backing_track": >-
817 + Drum backing track %{song_name}
818 + "song_menu_guitar_chord": >-
819 + Kunci Gitar %{song_name}
820 + "song_menu_karaoke": >-
821 + Karaoke Version %{song_name}
822 + "song_menu_lyric": >-
823 + Lirik Lagu %{song_name}
824 + "song_updated_at": >-
825 + Diperbarui: %{date}
826 + "still_login": >-
827 + Tetap masuk
828 + "time":
829 + "formats":
830 + "long": >-
831 + %Y-%m-%d %H:%M:%S
832 + "top_10_albums": >-
833 + 10 Album Teratas
834 + "top_10_artists": >-
835 + 10 Artis Teratas
836 + "top_10_guitar_chords": >-
837 + Top 10 Kunci Gitar
838 + "top_10_song_lyrics": >-
839 + Top 10 Lirik Lagu
840 + "top_10_songs": >-
841 + 10 Lagu Teratas
842 + "unconfirmed_email": >-
843 + Kamu sudah terdaftar dan perlu mengkonfirmasi email Anda. Periksa kotak masuk
844 + atau folder spam %{email_client_link} kamu. Atau %{click_here_link} untuk mengirim
845 + ulang konfirmasi email kamu.
846 + "unconfirmed_email_click_here": >-
847 + klik disini
848 + "unconfirmed_email_email_app": >-
849 + aplikasi email
850 + "unconfirmed_email_resend_sent": >-
851 + Konfirmasi email dikirim ke %{email}. Buka %{email_client_link} kamu. Jika kamu
852 + tidak dapat menemukannya, periksa folder junk. Mungkin terdeteksi sebagai spam.
853 + "unknown_error": >-
854 + Kesalahan tidak diketahui
855 + "user_email_confirmation_content": >-
856 + Kamu bisa mengkonfirmasi email akun kamu melalui link di bawah ini:
857 + "user_email_confirmation_cta": >-
858 + Konfirmasi akun saya
859 + "user_email_confirmation_header": >-
860 + Selamat datang, %{email}!
861 + "user_email_with_love": >-
862 + Dengan ❤️ dari Stockholm,
863 + "user_notifier_projects": >-
864 + Lihat proyek kami yang lain
865 + "user_profile": >-
866 + Profil
867 + "user_reset_password_email_heading": >-
868 + Setel ulang tautan sandi hanya untuk Anda, %{user_name}!
869 + "user_reset_password_email_heading_p1": >-
870 + Anda mendapatkan email ini karena Anda mencoba mengakses akun Anda.
871 + "user_reset_password_email_heading_p2": >-
872 + Klik %{href} untuk menyetel ulang sandi Anda.
873 + "user_reset_password_email_link": >-
874 + tautan satu kali ini
875 + "user_send_reset_password_instructions_subject": >-
876 + Setel ulang instruksi kata sandi
877 + "user_send_wallet_activation_notification_address": >-
878 + Alamat dompet Anda
879 + "user_send_wallet_activation_notification_heading": >-
880 + Selamat, %{user_name}. Anda sekarang memiliki dompet kripto baru!
881 + "user_send_wallet_activation_notification_p1": >-
882 + Uang kripto BACH Anda disimpan di dompet Anda. Anda dapat melihat saldo Anda di
883 + tautan di bawah ini.
884 + "user_send_wallet_activation_notification_subject": >-
885 + Anda memiliki dompet baru!
886 + "user_signup_email_heading": >-
887 + Terimakasih telah mendaftar, %{user_name}!
888 + "user_signup_email_heading_p1": >-
889 + Selamat bergabung!
890 + "user_signup_email_heading_p2": >-
891 + Kami dalam sebuah misi untuk mendigitalkan musik-musik yang kurang terekpose.
892 + %{href} dan mulai berkontribusi :)
893 + "user_signup_email_subject": >-
894 + Selamat datang di Musik88!
895 + "username_available": >-
896 + Selamat! Username tersedia.
897 + "username_length_invalid": >-
898 + Username minimal 4 karakter
899 + "username_letters_numbers_only": >-
900 + Username terdiri dari huruf dan angka
901 + "username_should_starts_with_letters": >-
902 + Username harus diawali dengan huruf
903 + "username_taken": >-
904 + Username tidak tersedia
905 + "views":
906 + "pagination":
907 + "next": >-
908 + Selanjutnya &rsaquo;
909 + "welcome_user": >-
910 + Selamat datang, %{user_name}!
911 + "welcome_user_unconfirmed": >-
912 + Selamat datang! Periksa email Anda untuk mengonfirmasi akun Anda.
913 + "will_paginate":
914 + "next_label": >-
915 + Selanjutnya &#8594;
916 + "your_account_connections": >-
917 + Pengaturan Koneksi
918 + "your_account_connections_explanation": >-
919 + Kami menyinkronkan data dari provider musik kamu setiap kamu login.
920 + "your_invite_date": >-
921 + Tanggal
922 + "your_invite_email": >-
923 + Email
924 + "your_invite_status": >-
925 + Status
926 + "your_invitees": >-
927 + Orang yang kamu undang
928 + "your_invitees_explanation": >-
929 + Kamu akan mendapatkan 0.1 BACH untuk setiap undangan yang diterima.
930 + "your_locale": >-
931 + Bahasa
932 + "your_locale_explanation": >-
933 + Pilihan bahasamu untuk web ini
934 + "your_wallet_address": >-
935 + Alamat dompet kamu
936 + "your_wallet_address_explanation": >-
937 + Kami menyimpan uang kripto kamu di alamat dompet kamu. Kamu bisa membeli, menjual,
938 + dan menstake (segera hadir) uang kripto kamu di pasar terbuka, seperti %{href}.
backend/musik88-web/config/locales/id.yml new
+48
@@ -0,0 +1,48 @@
1 +---
2 +"id":
3 + "AlbumProvider": >-
4 + Jembatan album
5 + "ArtistProvider": >-
6 + Jembatan artis
7 + "SongProvider": >-
8 + Jembatan lagu
9 + "invalid_login": >-
10 + Email/password salah.
11 + "login": >-
12 + Login
13 + "login_omniauth_explanation": >-
14 + Login dengan akun Spotify, Google, atau Facebook kamu.
15 + "login_with_facebook": >-
16 + Login dengan Facebook
17 + "nav_login": >-
18 + Login
19 + "nav_login_dropdown": >-
20 + Akun
21 + "nav_onboarding": >-
22 + Onboarding
23 + "onboarding_done": >-
24 + Begitulah, %{user_name}!
25 + "onboarding_explanation": >-
26 + Dengan memverifikasi email, kamu mendapatkan 0,1 BACH! Dapatkan lebih banyak BACH
27 + dengan mengikuti langkah-langkah ini.
28 + "onboarding_explanation_done": >-
29 + Selesai. Kamu mengekspor data Spotify kamu.
30 + "onboarding_spotify_done": >-
31 + Done. You exported your Spotify data.
32 + "onboarding_spotify_explanation": >-
33 + Dapatkan sampai 50 BACH saat kamu mengekspor data daftar putar kamu!
34 + "onboarding_spotify_heading": >-
35 + Ekspor data Spotify kamu
36 + "onboarding_spotify_processing": >-
37 + Mengekspor data. Butuh beberapa waktu.
38 + "onboarding_username_done": >-
39 + Yeay! Kamu mendapatkan username dan %{profile_badges_link}.
40 + "onboarding_username_explanation": >-
41 + Buat @username yang unik dan dapatkan 0.1 BACH!
42 + "routes":
43 + "login": >-
44 + masuk
45 + "signup_omniauth_explanation": >-
46 + Daftar dengan akun Spotify, Google, atau Facebook kamu.
47 + "still_login": >-
48 + Tetap masuk
backend/musik88-web/config/locales/ja-jp.yml new
+500
@@ -0,0 +1,500 @@
1 +---
2 +"ja-jp":
3 + "10_most_loved_songs": >-
4 + 最も好きな曲トップ10
5 + "active_admin":
6 + "filters":
7 + "predicates":
8 + "from": >-
9 + dari tanggal
10 + "to": >-
11 + sampai tanggal
12 + "activerecord":
13 + "errors":
14 + "models":
15 + "album":
16 + "attributes":
17 + "description":
18 + "too_short": >-
19 + アーティストの説明を空にすることはできません
20 + "name":
21 + "too_short": >-
22 + アルバム名を空にすることはできません
23 + "artist":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + アーティストの説明を空にすることはできません
28 + "name":
29 + "too_short": >-
30 + アーティスト名を空にすることはできません
31 + "chord":
32 + "attributes":
33 + "chord":
34 + "too_short": >-
35 + Chord lagu tidak boleh kosong
36 + "lyric":
37 + "attributes":
38 + "lyric":
39 + "too_short": >-
40 + Lirik lagu tidak boleh kosong
41 + "page":
42 + "attributes":
43 + "page_content":
44 + "too_short": >-
45 + Konten halaman tidak boleh kosong
46 + "page_slug":
47 + "too_short": >-
48 + Slug halaman tidak boleh kosong
49 + "song":
50 + "attributes":
51 + "album":
52 + "too_short": >-
53 + アルバムを空にすることはできません
54 + "description":
55 + "too_short": >-
56 + 曲の説明を空にすることはできません
57 + "name":
58 + "too_short": >-
59 + 曲名を空にすることはできません
60 + "user":
61 + "attributes":
62 + "email":
63 + "blank": >-
64 + Email tidak boleh kosong
65 + "invalid": >-
66 + Email tidak valid
67 + "name":
68 + "blank": >-
69 + Nama tidak boleh kosong
70 + "password":
71 + "blank": >-
72 + Password tidak boleh kosong
73 + "too_short": >-
74 + Password terlalu pendek
75 + "password_confirmation":
76 + "confirmation": >-
77 + Konfirmasi password tidak sama
78 + "add_chord_validation_success": >-
79 + 貢献していただきありがとうございます!
80 + "album_artists": >-
81 + アーティスト
82 + "album_song_list": >-
83 + 曲リスト
84 + "artist_profile_album_contribution_title": >-
85 + %{artist} にも表示されます:
86 + "artist_profile_album_title": >-
87 + %{artist}のアルバム
88 + "artist_profile_title": >-
89 + %{artist} プロフィール
90 + "breadcrumb_home": >-
91 + ホーム
92 + "breadcrumb_most_loved_songs": >-
93 + 最も好きな曲
94 + "by": >-
95 + Dicintai oleh
96 + "cancel": >-
97 + Batalkan
98 + "confirm_email": >-
99 + メールを確認する
100 + "create_account": >-
101 + アカウントを作成する
102 + "discussion": >-
103 + 討論
104 + "email": >-
105 + Eメール
106 + "find_your_artist": >-
107 + Temukan Artist Idola Kamu
108 + "find_your_favorite_album": >-
109 + お気に入りの新しいアルバムを探す
110 + "find_your_favorite_songs": >-
111 + お気に入りの曲を探す
112 + "followers": >-
113 + フォロワー
114 + "following": >-
115 + 従う
116 + "footer": >-
117 + すべての %{instagram} の %{profile} によって作成された流行&middot; &copy;2021年
118 + "footer_world": >-
119 + 世界
120 + "forgot_password": >-
121 + パスワードを忘れましたか?
122 + "gending_add": >-
123 + あなたの性別表記を共有する
124 + "gending_add_explanation": >-
125 + ジャワガムランをオンラインでデジタル化するのを手伝ってください。 %{href}を選択し、%{bach_link}トークンを取得します。
126 + "gending_description": >-
127 + 説明
128 + "gending_name": >-
129 + 性別タイトル
130 + "gending_notation": >-
131 + 性別表記
132 + "gending_notation_add": >-
133 + 性別表記を追加
134 + "gending_notation_link": >-
135 + ジャワのジェンディング表記 %{slug}
136 + "gending_notation_meta_desciption": >-
137 + %{title}を表示するためのジャワ語のgending表記は次のとおりです。
138 + "gending_notation_page_title": >-
139 + 性別表記%{title}
140 + "guitar_chord_image": >-
141 + Gambar Kunci Gitar
142 + "guitar_chord_link": >-
143 + ギターコード %{slug}
144 + "guitar_chord_video": >-
145 + Video Kunci Gitar
146 + "guitar_chords_shape": >-
147 + Bentuk Kunci Gitar
148 + "h2_find_new_albums": >-
149 + ここで新しいアルバムを見つけてください!
150 + "h2_find_new_artists": >-
151 + ここで新しいアーティストを見つけよう!
152 + "h2_find_new_songs": >-
153 + ここで新しい音楽を見つけよう!
154 + "have_account": >-
155 + すでにアカウントをお持ちですか?
156 + "hello": >-
157 + Lirik lagu terbaru! Koleksi lirik lagu terlengkap.
158 + "home_guitar_chords": >-
159 + ギターコード
160 + "home_guitar_chords_headline": >-
161 + ギターコードを送信して、貢献に対して%{bach_link}レピュテーショントークンを獲得してください。
162 + "home_headline": >-
163 + 世界最大の音楽データベース!
164 + "home_javanese_gending_notation": >-
165 + ジャワの性別表記
166 + "home_javanese_gending_notation_subheadline": >-
167 + ジャワガムランをデジタル化し、貢献に対して%{bach_link}レピュテーショントークンを獲得します。
168 + "home_lyrics": >-
169 + 歌詞
170 + "home_new_entries": >-
171 + 新規エントリー
172 + "home_others": >-
173 + その他
174 + "home_others_headline": >-
175 + 私の他のプロジェクトをチェックしてください。
176 + "home_popular": >-
177 + 人気
178 + "home_see_all": >-
179 + すべてを見る
180 + "home_share_gamelan_notation_now": >-
181 + ジェンディング表記を共有しましょう!
182 + "home_share_now": >-
183 + あなたのコードを共有してください!
184 + "home_signup_now": >-
185 + 今すぐサインアップ!
186 + "home_songs": >-
187 + 曲
188 + "home_subheadline": >-
189 + 英国の和音、インディーズバンドの和音、インドネシアの和音、スウェーデンの和音、日本のアーティストの和音、スウェーデンのアルバムなど! 貢献して %{bach_link}レピュテーショントークンを獲得してください。
190 + "home_youtube_karaoke": >-
191 + Youtubeカラオケ
192 + "home_youtube_karaoke_headline": >-
193 + AIを搭載したお気に入りのYouTubeミュージックビデオのカラオケバージョン。
194 + "invalid_login": >-
195 + 無効なメールアドレス/パスワード
196 + "locale_name": "\U0001F1EF\U0001F1F5 日本"
197 + "login": >-
198 + ログイン!
199 + "login_with_facebook": >-
200 + Facebookでサインイン
201 + "lyric": >-
202 + Lirik Lagu
203 + "lyric_cannot_be_empty": >-
204 + Lirik tidak boleh kosong
205 + "my_profile": >-
206 + 私のプロフィール
207 + "my_profile_link_href": >-
208 + tautan aman ini
209 + "my_profile_link_html": >-
210 + Ini adalah halaman publik lo. Ikuti %{href} kalo mau perbarui data lo.
211 + "my_profile_private": >-
212 + このプロファイルは非公開です
213 + "name": >-
214 + 名前
215 + "nav_albums": >-
216 + アルバム
217 + "nav_artists": >-
218 + アーティスト
219 + "nav_buy_bach": >-
220 + 買う €BACH
221 + "nav_change_language": >-
222 + Ganti Bahasa:
223 + "nav_chords": >-
224 + ギターコード
225 + "nav_chords_shapes": >-
226 + Bentuk Kunci Gitar
227 + "nav_community": >-
228 + Komunitas
229 + "nav_en": >-
230 + English
231 + "nav_footer_about": >-
232 + Tentang Kami
233 + "nav_footer_company": >-
234 + Perusahaan
235 + "nav_footer_contact": >-
236 + Kontak
237 + "nav_footer_follow_us": >-
238 + Ikuti kami
239 + "nav_footer_privacy": >-
240 + Kebijakan Privasi
241 + "nav_footer_term": >-
242 + Ketentuan Penggunaan
243 + "nav_guitar": >-
244 + Gitar
245 + "nav_id": >-
246 + Bahasa Indonesia
247 + "nav_javanese_gamelan": >-
248 + Gamelan Jawa
249 + "nav_javanese_gamelan_about": >-
250 + Tentang Gamelan Jawa
251 + "nav_javanese_gending": >-
252 + Gending Jawa
253 + "nav_javanese_gending_notation": >-
254 + Notasi Gending Jawa
255 + "nav_login": >-
256 + ログイン
257 + "nav_login_dropdown": >-
258 + アカウント
259 + "nav_logout": >-
260 + 外出
261 + "nav_lyrics": >-
262 + 歌詞
263 + "nav_music_database": >-
264 + Musik
265 + "nav_signup": >-
266 + アカウントリスト
267 + "nav_songs": >-
268 + 歌
269 + "nav_ukulele_chords": >-
270 + Kunci Ukulele (segera)
271 + "need_to_logged_in": >-
272 + Masuk log dulu sebelum kasih love. Belum punya akun? Daftar aja dulu, gampang
273 + kok. Bisa pake akun Facebook kamu lho.
274 + "need_to_logged_in_my_profile": >-
275 + このタスクを実行するには、ログインする必要があります。
276 + "new_user": >-
277 + 新しいユーザー?
278 + "or": >-
279 + または
280 + "our_community": >-
281 + 私たちのコミュニティ
282 + "our_guitar_chords_collection": >-
283 + Koleksi kunci gitar lagu terbaru dari kami
284 + "our_newest_song_lyrics": >-
285 + 私たちの最新の歌の歌詞コレクション
286 + "password": >-
287 + パスワード
288 + "password_confirmation": >-
289 + パスワードを認証する
290 + "routes":
291 + "about_us": >-
292 + 私たちに関しては
293 + "add_gending": >-
294 + add-gending-gamelan
295 + "add_javanese_gamelan_notation": >-
296 + add-javanese-gamelan-notation
297 + "album": >-
298 + アルバム
299 + "album_add": >-
300 + add-album
301 + "album_detail": >-
302 + アルバム
303 + "artist": >-
304 + アーティスト
305 + "artist_add": >-
306 + add-artist
307 + "artist_detail": >-
308 + アーティスト
309 + "audio_files": >-
310 + audio-files
311 + "bass_backing_track": >-
312 + bass-backing-track-:slug
313 + "community": >-
314 + コミュニティ
315 + "contact": >-
316 + kontak
317 + "drum_backing_track": >-
318 + drum-backing-track-:slug
319 + "followers": >-
320 + followers
321 + "following": >-
322 + following
323 + "guitar_backing_track": >-
324 + guitar-backing-track-:slug
325 + "guitar_chord_shape": >-
326 + bentuk-kunci-gitar-:slug
327 + "guitar_chord_shapes": >-
328 + bentuk-kunci-gitar
329 + "guitar_chords": >-
330 + ギターコード
331 + "guitar_chords_add": >-
332 + tambah-akord
333 + "guitar_chords_detail": >-
334 + kunci-gitar-:slug
335 + "guitar_chords_history": >-
336 + kunci-gitar-:slug/sejarah
337 + "guitar_chords_review": >-
338 + kunci-gitar-:slug/review-akord
339 + "javanese_gamelan": >-
340 + gamelan-jawa
341 + "javanese_gamelan_notation": >-
342 + gamelan-jawa
343 + "javanese_gending": >-
344 + gending-jawa
345 + "javanese_gending_detail": >-
346 + gending-gamelan-jawa-:slug
347 + "javanese_gending_gamelan_notation": >-
348 + notasi-gending-gamelan-jawa
349 + "javanese_gending_gamelan_notation_detail": >-
350 + notasi-gending-gamelan-jawa-:slug
351 + "login": >-
352 + ログイン
353 + "most_loved_songs": >-
354 + 最も人気のある曲
355 + "privacy_policy": >-
356 + kebijakan-privasi
357 + "signup": >-
358 + daftar
359 + "song": >-
360 + lagu
361 + "song_add": >-
362 + add-song
363 + "song_audio": >-
364 + song-audio
365 + "song_bass_tab": >-
366 + bass-tab-:slug
367 + "song_chords": >-
368 + kord-lagu
369 + "song_detail": >-
370 + lagu
371 + "song_karaoke_detail": >-
372 + karaoke-lagu-:slug
373 + "song_lyrics": >-
374 + 歌詞
375 + "song_lyrics_detail": >-
376 + 歌詞-:slug
377 + "term_of_use": >-
378 + ketentuan-penggunaan
379 + "user_rep": >-
380 + reputasi
381 + "see_album_detail": >-
382 + アルバムの詳細を表示
383 + "see_artist_detail": >-
384 + アーティストの詳細を表示
385 + "see_chord_detail": >-
386 + Lihat detail kord
387 + "see_song_detail": >-
388 + 曲の詳細を表示
389 + "signup": >-
390 + リスト
391 + "signup_now": >-
392 + 今すぐサインアップ!
393 + "signup_to_claim": >-
394 + これはあなたの物です?
395 + "signup_with_facebook": >-
396 + Facebookでサインアップ
397 + "site_name": >-
398 + Musik88.id
399 + "somebody_to_love": >-
400 + Cintai aku
401 + "song_audio_page_title": >-
402 + のオーディオファイル %{song_name}
403 + "song_chord_page_title": >-
404 + ギターキー%{song_name} by %{song_artist_name}
405 + "song_chord_unavailable_content": >-
406 + <span class='song-lyric-unavailable'>Kunci gitar %{song_name} dari %{song_artist_name}</span>
407 + belum tersedia. Beri cinta <span class='glyphicon glyphicon-heart'></span> pada
408 + lagu ini. Kami perlu setidaknya 2 cinta dari elo buat ngulik lagunya.
409 + "song_chord_unavailable_meta_description": >-
410 + Berikut adalah kunci gitar %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
411 + "song_chords_page_title": >-
412 + ソングキー %{song_name}
413 + "song_detail": >-
414 + 曲の詳細
415 + "song_detail_page_meta_title": >-
416 + Arti lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
417 + "song_detail_page_title": >-
418 + 歌の意味 %{song_name}
419 + "song_generate_your_own_bass_backing_track": >-
420 + Buat sendiri backing track bas lagu %{song_name}
421 + "song_generate_your_own_drum_backing_track": >-
422 + Buat sendiri backing track drum lagu %{song_name}
423 + "song_generate_your_own_karaoke": >-
424 + Buat Versi Karaoke Lagu %{song_name}
425 + "song_generate_your_own_stem_files": >-
426 + Buat STEM Files dari lagu %{song_name}
427 + "song_karaoke_download": >-
428 + カラオケラグをダウンロード
429 + "song_karaoke_page_title": >-
430 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
431 + "song_karaoke_title": >-
432 + %{filename} カラオケバージョン
433 + "song_karaoke_unavailable_content": >-
434 + Lagu karaoke %{song_name} dari %{song_artist_name} belum tersedia. Login untuk
435 + request.
436 + "song_karaoke_unavailable_meta_description": >-
437 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
438 + "song_lyric_page_title": >-
439 + Lirik Lagu %{song_name} dari %{song_artist_name}
440 + "song_lyric_unavailable_content": >-
441 + <span class='song-lyric-unavailable'>Lirik lagu %{song_name} dari %{song_artist_name}</span>
442 + belum tersedia. Beri setidaknya 2 cinta <span class='glyphicon glyphicon-heart'></span>
443 + atau %{href} pada lagu ini jadi kita bisa fokus buat ngulik kunci gitarnya.
444 + "song_lyric_unavailable_content_add_lyric": >-
445 + 歌詞を追加
446 + "song_lyric_unavailable_meta_description": >-
447 + Berikut adalah lirik lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
448 + "song_lyrics_page_title": >-
449 + 歌詞 %{song_name}
450 + "song_menu_aside_audio": >-
451 + オーディオ
452 + "song_menu_aside_chords": >-
453 + 和音
454 + "song_menu_aside_info": >-
455 + 情報
456 + "song_menu_aside_lyrics": >-
457 + 歌詞
458 + "song_menu_bass_backing_track": >-
459 + Bass backing track %{song_name}
460 + "song_menu_bass_tab": >-
461 + Bass tab %{song_name}
462 + "song_menu_drum_backing_track": >-
463 + Drum backing track %{song_name}
464 + "song_menu_guitar_chord": >-
465 + Kunci Gitar %{song_name}
466 + "song_menu_karaoke": >-
467 + Karaoke Version %{song_name}
468 + "song_menu_lyric": >-
469 + Lirik Lagu %{song_name}
470 + "song_updated_at": >-
471 + Diperbarui: %{date}
472 + "still_login": >-
473 + 私を覚えてますか
474 + "time":
475 + "formats":
476 + "long": >-
477 + %Y-%m-%d %H:%M:%S
478 + "top_10_albums": >-
479 + トップ10アルバム
480 + "top_10_artists": >-
481 + Top 10 アーティスト
482 + "top_10_guitar_chords": >-
483 + Top 10 ギターコード
484 + "top_10_song_lyrics": >-
485 + トップ10の歌の歌詞
486 + "top_10_songs": >-
487 + トップ10曲
488 + "user_profile": >-
489 + プロフィール
490 + "user_signup_email_heading": >-
491 + Terimakasih telah mendaftar, %{user_name}!
492 + "user_signup_email_heading_p1": >-
493 + クラブへようこそ!
494 + "user_signup_email_heading_p2": >-
495 + Kami dalam sebuah misi untuk mendigitalkan musik-musik yang kurang terekpose.
496 + %{href} dan mulai berkontribusi :)
497 + "user_signup_email_subject": >-
498 + Musik88へようこそ!
499 + "welcome_user": >-
500 + ようこそ、%{user_name}!
backend/musik88-web/config/locales/ja.yml new
+610
@@ -0,0 +1,610 @@
1 +---
2 +"ja":
3 + "10_most_loved_songs": >-
4 + 最も好きな曲トップ10
5 + "Album": >-
6 + アルバム
7 + "AlbumProvider": >-
8 + アルバムブリッジ
9 + "Artist": >-
10 + アーティスト
11 + "ArtistProvider": >-
12 + アーティストブリッジ
13 + "Song": >-
14 + 歌
15 + "SongProvider": >-
16 + 歌橋
17 + "activate_solana_wallet": >-
18 + ウォレットを有効にする
19 + "active_admin":
20 + "devise":
21 + "links":
22 + "forgot_your_password": >-
23 + パスワードをお忘れですか?
24 + "filters":
25 + "predicates":
26 + "from": >-
27 + dari tanggal
28 + "to": >-
29 + sampai tanggal
30 + "activerecord":
31 + "errors":
32 + "models":
33 + "album":
34 + "attributes":
35 + "description":
36 + "too_short": >-
37 + アーティストの説明を空にすることはできません
38 + "name":
39 + "too_short": >-
40 + アルバム名を空にすることはできません
41 + "artist":
42 + "attributes":
43 + "description":
44 + "too_short": >-
45 + アーティストの説明を空にすることはできません
46 + "name":
47 + "too_short": >-
48 + アーティスト名を空にすることはできません
49 + "chord":
50 + "attributes":
51 + "chord":
52 + "too_short": >-
53 + Chord lagu tidak boleh kosong
54 + "lyric":
55 + "attributes":
56 + "lyric":
57 + "too_short": >-
58 + Lirik lagu tidak boleh kosong
59 + "page":
60 + "attributes":
61 + "page_content":
62 + "too_short": >-
63 + Konten halaman tidak boleh kosong
64 + "page_slug":
65 + "too_short": >-
66 + Slug halaman tidak boleh kosong
67 + "song":
68 + "attributes":
69 + "album":
70 + "too_short": >-
71 + アルバムを空にすることはできません
72 + "description":
73 + "too_short": >-
74 + 曲の説明を空にすることはできません
75 + "name":
76 + "too_short": >-
77 + 曲名を空にすることはできません
78 + "user":
79 + "attributes":
80 + "email":
81 + "blank": >-
82 + Email tidak boleh kosong
83 + "invalid": >-
84 + Email tidak valid
85 + "name":
86 + "blank": >-
87 + Nama tidak boleh kosong
88 + "password":
89 + "blank": >-
90 + Password tidak boleh kosong
91 + "too_short": >-
92 + Password terlalu pendek
93 + "password_confirmation":
94 + "confirmation": >-
95 + Konfirmasi password tidak sama
96 + "add_chord_validation_success": >-
97 + 貢献していただきありがとうございます!
98 + "album_artists": >-
99 + アーティスト
100 + "album_song_list": >-
101 + 曲リスト
102 + "artist_profile_album_contribution_title": >-
103 + %{artist} にも表示されます:
104 + "artist_profile_album_title": >-
105 + %{artist}のアルバム
106 + "artist_profile_title": >-
107 + %{artist} プロフィール
108 + "bach_token_discussion": >-
109 + どう思いますか? 興味ある?
110 + "badges": >-
111 + バッジ
112 + "breadcrumb_home": >-
113 + ホーム
114 + "breadcrumb_most_loved_songs": >-
115 + 最も好きな曲
116 + "by": >-
117 + Dicintai oleh
118 + "cancel": >-
119 + Batalkan
120 + "confirm_email": >-
121 + メールを確認する
122 + "create_account": >-
123 + アカウントを作成する
124 + "discussion": >-
125 + 討論
126 + "discussion_comment_button": >-
127 + 送信
128 + "discussion_comment_placeholder": >-
129 + ここにコメントを書いてください
130 + "email": >-
131 + Eメール
132 + "find_your_artist": >-
133 + お気に入りのアーティストを探す
134 + "find_your_favorite_album": >-
135 + お気に入りの新しいアルバムを探す
136 + "find_your_favorite_songs": >-
137 + お気に入りの曲を探す
138 + "find_your_friends": >-
139 + ここで友達を見つけてください...
140 + "followers": >-
141 + フォロワー
142 + "following": >-
143 + 従う
144 + "footer": >-
145 + すべての %{instagram} の %{profile} によって作成された流行&middot; &copy;2021年
146 + "footer_world": >-
147 + 世界
148 + "forgot_password": >-
149 + パスワードを忘れましたか?
150 + "gending_add": >-
151 + あなたの性別表記を共有する
152 + "gending_add_explanation": >-
153 + ジャワガムランをオンラインでデジタル化するのを手伝ってください。 %{href}を選択し、%{bach_link}トークンを取得します。
154 + "gending_description": >-
155 + 説明
156 + "gending_name": >-
157 + 性別タイトル
158 + "gending_notation": >-
159 + 性別表記
160 + "gending_notation_add": >-
161 + 性別表記を追加
162 + "gending_notation_add_explanation": >-
163 + ジャワ語のジェンダー表記を共有したいですか? %{href} して %{bach_link} 暗号通貨を獲得しましょう。
164 + "gending_notation_link": >-
165 + ジャワのジェンディング表記 %{slug}
166 + "gending_notation_meta_desciption": >-
167 + %{title}を表示するためのジャワ語のgending表記は次のとおりです。
168 + "gending_notation_page_title": >-
169 + 性別表記%{title}
170 + "generating_solana_wallet": >-
171 + ウォレットを生成しています....後でこのページを更新してください。
172 + "guitar_chord_image": >-
173 + Gambar Kunci Gitar
174 + "guitar_chord_link": >-
175 + ギターコード %{slug}
176 + "guitar_chord_video": >-
177 + Video Kunci Gitar
178 + "guitar_chords_shape": >-
179 + Bentuk Kunci Gitar
180 + "h2_find_new_albums": >-
181 + ここで新しいアルバムを見つけてください!
182 + "h2_find_new_artists": >-
183 + ここで新しいアーティストを見つけよう!
184 + "h2_find_new_songs": >-
185 + ここで新しい音楽を見つけよう!
186 + "have_account": >-
187 + すでにアカウントをお持ちですか?
188 + "hello": >-
189 + Lirik lagu terbaru! Koleksi lirik lagu terlengkap.
190 + "home_guitar_chords": >-
191 + ギターコード
192 + "home_guitar_chords_headline": >-
193 + ギターコードを送信して、貢献に対して%{bach_link}レピュテーショントークンを獲得してください。
194 + "home_headline": >-
195 + 世界最大の音楽データベース!
196 + "home_javanese_gending_notation": >-
197 + ジャワの性別表記
198 + "home_javanese_gending_notation_subheadline": >-
199 + ジャワガムランをデジタル化し、貢献に対して%{bach_link}レピュテーショントークンを獲得します。
200 + "home_lyrics": >-
201 + 歌詞
202 + "home_new_entries": >-
203 + 新規エントリー
204 + "home_others": >-
205 + その他
206 + "home_others_headline": >-
207 + 私の他のプロジェクトをチェックしてください。
208 + "home_popular": >-
209 + 人気
210 + "home_see_all": >-
211 + すべてを見る
212 + "home_share_gamelan_notation_now": >-
213 + ジェンディング表記を共有しましょう!
214 + "home_share_now": >-
215 + あなたのコードを共有してください!
216 + "home_signup_now": >-
217 + 今すぐサインアップ!
218 + "home_songs": >-
219 + 曲
220 + "home_subheadline": >-
221 + 英国の和音、インディーズバンドの和音、インドネシアの和音、スウェーデンの和音、日本のアーティストの和音、スウェーデンのアルバムなど! 貢献して %{bach_link}レピュテーショントークンを獲得してください。
222 + "home_what_is": >-
223 + Musik88とは?
224 + "home_youtube_karaoke": >-
225 + Youtubeカラオケ
226 + "home_youtube_karaoke_headline": >-
227 + AIを搭載したお気に入りのYouTubeミュージックビデオのカラオケバージョン。
228 + "invalid_login": >-
229 + 無効なメールアドレス/パスワード
230 + "locale_name": "\U0001F1EF\U0001F1F5 日本"
231 + "login": >-
232 + ログイン!
233 + "login_omniauth_explanation": >-
234 + Spotify、Google、または Facebook アカウントでログインします。
235 + "login_with_facebook": >-
236 + Facebookでサインイン
237 + "lyric": >-
238 + Lirik Lagu
239 + "lyric_cannot_be_empty": >-
240 + Lirik tidak boleh kosong
241 + "merit":
242 + "granted_badge": >-
243 + %{badge_name} バッジを付与
244 + "removed_badge": >-
245 + %{badge_name} バッジを削除しました
246 + "my_profile": >-
247 + 私のプロフィール
248 + "my_profile_link_href": >-
249 + tautan aman ini
250 + "my_profile_link_html": >-
251 + Ini adalah halaman publik lo. Ikuti %{href} kalo mau perbarui data lo.
252 + "my_profile_private": >-
253 + このプロファイルは非公開です
254 + "name": >-
255 + 名前
256 + "nav_albums": >-
257 + アルバム
258 + "nav_artists": >-
259 + アーティスト
260 + "nav_buy_bach": >-
261 + 買う €BACH
262 + "nav_change_language": >-
263 + Ganti Bahasa:
264 + "nav_chords": >-
265 + ギターコード
266 + "nav_chords_shapes": >-
267 + Bentuk Kunci Gitar
268 + "nav_community": >-
269 + Komunitas
270 + "nav_en": >-
271 + English
272 + "nav_footer_about": >-
273 + 私たちに関しては
274 + "nav_footer_company": >-
275 + Perusahaan
276 + "nav_footer_contact": >-
277 + コンタクト
278 + "nav_footer_follow_us": >-
279 + Ikuti kami
280 + "nav_footer_privacy": >-
281 + プライバシーポリシー
282 + "nav_footer_term": >-
283 + 利用規約
284 + "nav_guitar": >-
285 + Gitar
286 + "nav_id": >-
287 + Bahasa Indonesia
288 + "nav_javanese_gamelan": >-
289 + Gamelan Jawa
290 + "nav_javanese_gamelan_about": >-
291 + ジャワのガムランについて
292 + "nav_javanese_gending": >-
293 + ジャワのジェンダー
294 + "nav_javanese_gending_notation": >-
295 + ジャワ語表記法
296 + "nav_login": >-
297 + ログイン
298 + "nav_login_dropdown": >-
299 + アカウント
300 + "nav_logout": >-
301 + 外出
302 + "nav_lyrics": >-
303 + 歌詞
304 + "nav_music_database": >-
305 + Musik
306 + "nav_signup": >-
307 + アカウントリスト
308 + "nav_songs": >-
309 + 歌
310 + "nav_ukulele_chords": >-
311 + Kunci Ukulele (segera)
312 + "nav_what_is_this": >-
313 + これは何ですか?
314 + "need_to_logged_in": >-
315 + Masuk log dulu sebelum kasih love. Belum punya akun? Daftar aja dulu, gampang
316 + kok. Bisa pake akun Facebook kamu lho.
317 + "need_to_logged_in_my_profile": >-
318 + このタスクを実行するには、ログインする必要があります。
319 + "new_user": >-
320 + 新しいユーザー?
321 + "oh_hi": >-
322 + あこんにちは!
323 + "or": >-
324 + または
325 + "our_community": >-
326 + 私たちのコミュニティ
327 + "our_guitar_chords_collection": >-
328 + Koleksi kunci gitar lagu terbaru dari kami
329 + "our_newest_song_lyrics": >-
330 + 私たちの最新の歌の歌詞コレクション
331 + "password": >-
332 + パスワード
333 + "password_confirmation": >-
334 + パスワードを認証する
335 + "pretty_badge": >-
336 + かわいいバッジ
337 + "ransack":
338 + "search": >-
339 + 探す
340 + "reputation": >-
341 + 評判
342 + "routes":
343 + "about_us": >-
344 + 私たちに関しては
345 + "add_gending": >-
346 + add-gending-gamelan
347 + "add_javanese_gamelan_notation": >-
348 + add-javanese-gamelan-notation
349 + "album": >-
350 + アルバム
351 + "album_add": >-
352 + add-album
353 + "album_bridge": >-
354 + アルバムブリッジ
355 + "album_detail": >-
356 + アルバム
357 + "artist": >-
358 + アーティスト
359 + "artist_add": >-
360 + add-artist
361 + "artist_bridge": >-
362 + アーティストブリッジ
363 + "artist_detail": >-
364 + アーティスト
365 + "audio_files": >-
366 + audio-files
367 + "bass_backing_track": >-
368 + bass-backing-track-:slug
369 + "community": >-
370 + コミュニティ
371 + "contact": >-
372 + コンタクト
373 + "drum_backing_track": >-
374 + drum-backing-track-:slug
375 + "followers": >-
376 + followers
377 + "following": >-
378 + following
379 + "guitar_backing_track": >-
380 + guitar-backing-track-:slug
381 + "guitar_chord_shape": >-
382 + bentuk-kunci-gitar-:slug
383 + "guitar_chord_shapes": >-
384 + bentuk-kunci-gitar
385 + "guitar_chords": >-
386 + ギターコード
387 + "guitar_chords_add": >-
388 + tambah-akord
389 + "guitar_chords_detail": >-
390 + kunci-gitar-:slug
391 + "guitar_chords_history": >-
392 + kunci-gitar-:slug/sejarah
393 + "guitar_chords_review": >-
394 + kunci-gitar-:slug/review-akord
395 + "javanese_gamelan": >-
396 + gamelan-jawa
397 + "javanese_gamelan_notation": >-
398 + gamelan-jawa
399 + "javanese_gending": >-
400 + ジャワジェンダー
401 + "javanese_gending_detail": >-
402 + gending-gamelan-jawa-:slug
403 + "javanese_gending_gamelan_notation": >-
404 + notasi-gending-gamelan-jawa
405 + "javanese_gending_gamelan_notation_detail": >-
406 + notasi-gending-gamelan-jawa-:slug
407 + "javanese_gending_gamelan_notation_detail_new": >-
408 + ゲンディング表記基準-:slug
409 + "login": >-
410 + ログイン
411 + "most_loved_songs": >-
412 + 最も人気のある曲
413 + "privacy_policy": >-
414 + プライバシーポリシー
415 + "search": >-
416 + 探す
417 + "signup": >-
418 + サインアップ
419 + "song": >-
420 + lagu
421 + "song_add": >-
422 + add-song
423 + "song_audio": >-
424 + song-audio
425 + "song_bass_tab": >-
426 + bass-tab-:slug
427 + "song_bridge": >-
428 + 歌橋
429 + "song_chords": >-
430 + kord-lagu
431 + "song_detail": >-
432 + lagu
433 + "song_karaoke_detail": >-
434 + karaoke-lagu-:slug
435 + "song_lyrics": >-
436 + 歌詞
437 + "song_lyrics_detail": >-
438 + 歌詞-:slug
439 + "term_of_use": >-
440 + 利用規約
441 + "user_rep": >-
442 + reputasi
443 + "what_is_this": >-
444 + これは何ですか
445 + "what_is_this_detail": >-
446 + とは-:slug
447 + "search": >-
448 + 探す
449 + "search_placeholder_title": >-
450 + アーティスト、曲、またはジャワの性別を検索...
451 + "search_result_for": >-
452 + の検索結果 "<span class="fst-italic fw-bold">%{keywords}</span>"
453 + "see_album_detail": >-
454 + アルバムの詳細を表示
455 + "see_artist_detail": >-
456 + アーティストの詳細を表示
457 + "see_chord_detail": >-
458 + Lihat detail kord
459 + "see_song_detail": >-
460 + 曲の詳細を表示
461 + "settings": >-
462 + 設定
463 + "signup": >-
464 + リスト
465 + "signup_now": >-
466 + 今すぐサインアップ!
467 + "signup_omniauth_explanation": >-
468 + Spotify、Google、または Facebook アカウントでサインアップします。
469 + "signup_to_claim": >-
470 + これはあなたの物です?
471 + "signup_with_facebook": >-
472 + Facebookでサインアップ
473 + "site_name": >-
474 + Musik88.id
475 + "somebody_to_love": >-
476 + Cintai aku
477 + "song_audio_page_title": >-
478 + のオーディオファイル %{song_name}
479 + "song_chord_page_title": >-
480 + ギターキー%{song_name}
481 + "song_chord_unavailable_content": >-
482 + <span class='song-lyric-unavailable'>Kunci gitar %{song_name} dari %{song_artist_name}</span>
483 + belum tersedia. Beri cinta <span class='glyphicon glyphicon-heart'></span> pada
484 + lagu ini. Kami perlu setidaknya 2 cinta dari elo buat ngulik lagunya.
485 + "song_chord_unavailable_meta_description": >-
486 + Berikut adalah kunci gitar %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
487 + "song_chords_page_title": >-
488 + ソングキー %{song_name}
489 + "song_detail": >-
490 + 曲の詳細
491 + "song_detail_page_meta_title": >-
492 + Arti lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
493 + "song_detail_page_title": >-
494 + 歌の意味 %{song_name}
495 + "song_generate_your_own_bass_backing_track": >-
496 + Buat sendiri backing track bas lagu %{song_name}
497 + "song_generate_your_own_drum_backing_track": >-
498 + Buat sendiri backing track drum lagu %{song_name}
499 + "song_generate_your_own_karaoke": >-
500 + Buat Versi Karaoke Lagu %{song_name}
501 + "song_generate_your_own_stem_files": >-
502 + Buat STEM Files dari lagu %{song_name}
503 + "song_karaoke_download": >-
504 + カラオケラグをダウンロード
505 + "song_karaoke_page_title": >-
506 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
507 + "song_karaoke_title": >-
508 + %{filename} カラオケバージョン
509 + "song_karaoke_unavailable_content": >-
510 + Lagu karaoke %{song_name} dari %{song_artist_name} belum tersedia. Login untuk
511 + request.
512 + "song_karaoke_unavailable_meta_description": >-
513 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
514 + "song_lyric_page_title": >-
515 + Lirik Lagu %{song_name} dari %{song_artist_name}
516 + "song_lyric_unavailable_content": >-
517 + <span class='song-lyric-unavailable'>Lirik lagu %{song_name} dari %{song_artist_name}</span>
518 + belum tersedia. Beri setidaknya 2 cinta <span class='glyphicon glyphicon-heart'></span>
519 + atau %{href} pada lagu ini jadi kita bisa fokus buat ngulik kunci gitarnya.
520 + "song_lyric_unavailable_content_add_lyric": >-
521 + 歌詞を追加
522 + "song_lyric_unavailable_meta_description": >-
523 + Berikut adalah lirik lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
524 + "song_lyrics_page_title": >-
525 + 歌詞 %{song_name}
526 + "song_menu_aside_audio": >-
527 + オーディオ
528 + "song_menu_aside_chords": >-
529 + 和音
530 + "song_menu_aside_info": >-
531 + 情報
532 + "song_menu_aside_lyrics": >-
533 + 歌詞
534 + "song_menu_bass_backing_track": >-
535 + Bass backing track %{song_name}
536 + "song_menu_bass_tab": >-
537 + Bass tab %{song_name}
538 + "song_menu_drum_backing_track": >-
539 + Drum backing track %{song_name}
540 + "song_menu_guitar_chord": >-
541 + Kunci Gitar %{song_name}
542 + "song_menu_karaoke": >-
543 + Karaoke Version %{song_name}
544 + "song_menu_lyric": >-
545 + Lirik Lagu %{song_name}
546 + "song_updated_at": >-
547 + Diperbarui: %{date}
548 + "still_login": >-
549 + 私を覚えてますか
550 + "time":
551 + "formats":
552 + "long": >-
553 + %Y-%m-%d %H:%M:%S
554 + "top_10_albums": >-
555 + トップ10アルバム
556 + "top_10_artists": >-
557 + Top 10 アーティスト
558 + "top_10_guitar_chords": >-
559 + Top 10 ギターコード
560 + "top_10_song_lyrics": >-
561 + トップ10の歌の歌詞
562 + "top_10_songs": >-
563 + トップ10曲
564 + "unknown_error": >-
565 + 未知のエラー
566 + "user_notifier_projects": >-
567 + 私たちの他のプロジェクトをチェックしてください
568 + "user_profile": >-
569 + プロフィール
570 + "user_send_wallet_activation_notification_address": >-
571 + あなたのウォレットアドレス
572 + "user_send_wallet_activation_notification_heading": >-
573 + おめでとうございます、%{user_name}。 これで、新しい暗号ウォレットができました!
574 + "user_send_wallet_activation_notification_p1": >-
575 + BACH 暗号はウォレットに保存されます。 以下のリンクで残高を確認できます。
576 + "user_send_wallet_activation_notification_subject": >-
577 + あなたは新しい財布を持っています!
578 + "user_signup_email_heading": >-
579 + %{user_name} さん、サインアップしていただきありがとうございます。
580 + "user_signup_email_heading_p1": >-
581 + クラブへようこそ!
582 + "user_signup_email_heading_p2": >-
583 + 私たちは、世界で過小評価されている音楽をデジタル化することを使命としています。 %{href} と貢献を開始:)
584 + "user_signup_email_subject": >-
585 + Musik88へようこそ!
586 + "welcome_user": >-
587 + ようこそ、%{user_name}!
588 + "your_account_connections": >-
589 + 接続設定
590 + "your_account_connections_explanation": >-
591 + ログインするたびに音楽プロバイダーを同期します。
592 + "your_invite_date": >-
593 + 日にち
594 + "your_invite_email": >-
595 + Eメール
596 + "your_invite_status": >-
597 + 状態
598 + "your_invitees": >-
599 + 招待者
600 + "your_invitees_explanation": >-
601 + 承認された招待ごとに 0.1 BACH を獲得できます。
602 + "your_locale": >-
603 + 言語
604 + "your_locale_explanation": >-
605 + このウェブサイトの言語設定
606 + "your_wallet_address": >-
607 + あなたのウォレットアドレス
608 + "your_wallet_address_explanation": >-
609 + 私たちはあなたの暗号通貨をあなたのウォレットアドレスに保管します。 %{href} などのオープン マーケットで仮想通貨を購入、販売、ステーク (近日公開予定)
610 + できます。
backend/musik88-web/config/locales/jv-id.yml new
+705
@@ -0,0 +1,705 @@
1 +---
2 +"jv-id":
3 + "10_most_loved_songs": >-
4 + 10 Lagu paling banyak disuka
5 + "Artist": >-
6 + Artis
7 + "JavaneseGending": >-
8 + Gending Jawa
9 + "JavaneseGendingNotation": >-
10 + Notasi Gending Jawa
11 + "Song": >-
12 + Tembang
13 + "activate_solana_wallet": >-
14 + Aktifake dompet sampeyan
15 + "active_admin":
16 + "devise":
17 + "change_password":
18 + "submit": >-
19 + Ganti sandi
20 + "title": >-
21 + Ganti tembung sandhi
22 + "links":
23 + "forgot_your_password": >-
24 + Kelalen sandi?
25 + "password":
26 + "title": >-
27 + Sandi
28 + "password_confirmation":
29 + "title": >-
30 + Konfirmasi Sandi
31 + "reset_password":
32 + "submit": >-
33 + Reset sandi
34 + "title": >-
35 + Kelalen sandi?
36 + "username":
37 + "title": >-
38 + Jeneng panganggo
39 + "filters":
40 + "predicates":
41 + "from": >-
42 + dari tanggal
43 + "to": >-
44 + sampai tanggal
45 + "next": >-
46 + Saterusne
47 + "activerecord":
48 + "attributes":
49 + "active_admin/comment":
50 + "namespace": >-
51 + Ruang jeneng
52 + "errors":
53 + "models":
54 + "album":
55 + "attributes":
56 + "description":
57 + "too_short": >-
58 + Deskripsi artis tidak boleh kosong
59 + "name":
60 + "too_short": >-
61 + Jeneng album ora bisa kosong
62 + "artist":
63 + "attributes":
64 + "description":
65 + "too_short": >-
66 + Deskripsi artis tidak boleh kosong
67 + "name":
68 + "too_short": >-
69 + Jeneng artis ora bisa kosong
70 + "chord":
71 + "attributes":
72 + "chord":
73 + "too_short": >-
74 + Chord lagu tidak boleh kosong
75 + "lyric":
76 + "attributes":
77 + "lyric":
78 + "too_short": >-
79 + Lirik lagu tidak boleh kosong
80 + "page":
81 + "attributes":
82 + "page_content":
83 + "too_short": >-
84 + Konten halaman tidak boleh kosong
85 + "page_slug":
86 + "too_short": >-
87 + Slug halaman tidak boleh kosong
88 + "song":
89 + "attributes":
90 + "album":
91 + "too_short": >-
92 + Album tidak boleh kosong
93 + "description":
94 + "too_short": >-
95 + Deskripsi lagu tidak boleh kosong
96 + "name":
97 + "too_short": >-
98 + Jeneng lagu ora bisa kosong
99 + "user":
100 + "attributes":
101 + "email":
102 + "blank": >-
103 + Email tidak boleh kosong
104 + "invalid": >-
105 + Email tidak valid
106 + "name":
107 + "blank": >-
108 + Jeneng ora bisa kosong
109 + "password":
110 + "blank": >-
111 + Tembung sandhi ora bisa kosong
112 + "too_short": >-
113 + Sandi cendhak banget
114 + "password_confirmation":
115 + "confirmation": >-
116 + Konfirmasi sandhi ora padha
117 + "bach_token_discussion": >-
118 + Piye pendapatmu? Tertarik?
119 + "bach_token_name": >-
120 + Jeneng token
121 + "breadcrumb_home": >-
122 + Beranda
123 + "breadcrumb_most_loved_songs": >-
124 + Lagu-lagu Paling Banyak Disuka
125 + "by": >-
126 + Dicintai oleh
127 + "cancel": >-
128 + Batalkan
129 + "change_language": >-
130 + Ganti basa
131 + "create_account": >-
132 + Gawe akun
133 + "devise":
134 + "failure":
135 + "unconfirmed": >-
136 + Sampeyan kudu konfirmasi alamat email sadurunge nerusake.
137 + "mailer":
138 + "confirmation_instructions":
139 + "subject": >-
140 + Instruksi konfirmasi
141 + "user_subject": >-
142 + Konfirmasi email Musik88.com
143 + "email_changed":
144 + "subject": >-
145 + Email telah diganti
146 + "password_change":
147 + "subject": >-
148 + Sandi diganti
149 + "reset_password_instructions":
150 + "subject": >-
151 + Reset instruksi sandi
152 + "unlock_instructions":
153 + "subject": >-
154 + Instruksi buka kunci
155 + "passwords":
156 + "no_token": >-
157 + Sampeyan ora bisa ngakses kaca iki tanpa teka saka email reset sandi. Yen
158 + sampeyan teka saka email reset sandhi, priksa manawa sampeyan nggunakake URL
159 + lengkap sing diwenehake.
160 + "send_instructions": >-
161 + Sampeyan bakal nampa email kanthi instruksi babagan carane ngreset sandhi
162 + ing sawetara menit.
163 + "send_paranoid_instructions": >-
164 + Yen alamat email sampeyan ana ing database kita, sampeyan bakal nampa link
165 + pemulihan sandi ing alamat email sampeyan ing sawetara menit.
166 + "updated": >-
167 + Tembung sandhi panjenengan wis kasil diganti. Sampeyan saiki wis mlebu.
168 + "updated_not_active": >-
169 + Tembung sandhi panjenengan wis kasil diganti.
170 + "registrations":
171 + "signed_up_but_unconfirmed": >-
172 + Pesen kanthi link konfirmasi wis dikirim menyang alamat email sampeyan. Mangga
173 + tindakake link kanggo ngaktifake akun.
174 + "discussion": >-
175 + Rembugan
176 + "discussion_comment_button": >-
177 + Kirim
178 + "discussion_comment_placeholder": >-
179 + Tulis komentar njenengan nang kene
180 + "email": >-
181 + Surel
182 + "find_your_artist": >-
183 + Temukan Artist Idola Kamu
184 + "find_your_favorite_album": >-
185 + Temukan Album Baru Kesukaanmu
186 + "find_your_favorite_songs": >-
187 + Temukan Lagu Kesukaanmu
188 + "find_your_friends": >-
189 + Kancane sampeyan nang kene...
190 + "followers": >-
191 + Pengikut
192 + "following": >-
193 + Ngikuti
194 + "footer": >-
195 + Anu gaweane iseng %{profile} kit seluruh %{instagram} &middot; &copy; 2021
196 + "footer_world": >-
197 + jagad
198 + "forgot_password": >-
199 + Kelalen password?
200 + "friends": >-
201 + Kanca
202 + "gending_add": >-
203 + Bagikan notasi gendingmu
204 + "gending_add_explanation": >-
205 + Bantu digitalisasi gamelan jawa online. %{href} lan entuk %{bach_link} duit kripto.
206 + "gending_description": >-
207 + Deskripsi
208 + "gending_name": >-
209 + Judul Gending
210 + "gending_notation": >-
211 + Notasi gending
212 + "gending_notation_add": >-
213 + Tambah notasi gending
214 + "gending_notation_add_explanation": >-
215 + Ada notasi gending yang pingin kamu bagi? %{href} lan entuk duit kripto %{bach_link}
216 + "gending_notation_link": >-
217 + Notasi gending Jawa %{slug}
218 + "gending_notation_meta_desciption": >-
219 + Berikut adalah notasi gending jawa untuk gending %{title}
220 + "gending_notation_page_title": >-
221 + Notasi Gending %{title}
222 + "guitar_chord_image": >-
223 + Gambar Kunci Gitar
224 + "guitar_chord_link": >-
225 + Kunci gitar %{slug}
226 + "guitar_chord_video": >-
227 + Video Kunci Gitar
228 + "guitar_chords_shape": >-
229 + Bentuk Kunci Gitar
230 + "have_account": >-
231 + Wis duwe akun?
232 + "hello": >-
233 + Lirik lagu terbaru! Koleksi lirik lagu terlengkap.
234 + "home_guitar_chords": >-
235 + Kunci Gitar
236 + "home_guitar_chords_headline": >-
237 + Bagikan koleksi kunci gitar sampeyan lan entuk duit kripto %{bach_link} kanggo
238 + kontribusi sampeyan.
239 + "home_headline": >-
240 + Database musik paling lengkap sejagat raya!
241 + "home_javanese_gending_notation": >-
242 + Notasi Gending Jawa
243 + "home_javanese_gending_notation_subheadline": >-
244 + Digitalkan gamelan jawa dan dapatkan duit kripto %{bach_link} untuk kontribusimu.
245 + "home_others": >-
246 + Liyane
247 + "home_others_headline": >-
248 + Cek proyek-proyek ku sing liya.
249 + "home_see_all": >-
250 + Cek kabeh
251 + "home_share_gamelan_notation_now": >-
252 + Bagikan notasi gending!
253 + "home_share_now": >-
254 + Bagikan kunci gitarmu!
255 + "home_signup_now": >-
256 + Daftar siki!
257 + "home_subheadline": >-
258 + Kunci lagu Britis, kunci lagu band indie, kunci lagu Indonesia, kunci lagu Swedia,
259 + kunci lagu Jepang, dsb! Daftar, mulai kontribusi (nambah artis, lagu, album, lsb)
260 + ben ulih duit kripto %{bach_link} .
261 + "home_what_is": >-
262 + Apa kuwi Musik88?
263 + "home_youtube_karaoke": >-
264 + Youtube Karaoke
265 + "home_youtube_karaoke_headline": >-
266 + Versi karaoke dari video musik YouTube favorit kamu didukung oleh kecerdasan buatan.
267 + "invalid_login": >-
268 + Surel/kata sandi salah
269 + "join_our_chord_community": >-
270 + Gabung karo komunitas kita kanggo ndeleng %{song_name} saka %{song_artist_name}.
271 + Sampeyan uga bisa entuk duit kripto €BACH kanthi ngaktifake dompet ing Profil
272 + -> Setelan.
273 + "join_our_javanese_community": >-
274 + Gabung karo komunitas kita kanggo ndeleng notasi %{gending_name}. Sampeyan uga
275 + bisa entuk duit kripto €BACH kanthi ngaktifake dompet ing Profil -> Setelan.
276 + "join_our_lyric_community": >-
277 + Gabung karo komunitas kita kanggo ndeleng %{song_name} lirik saka %{song_artist_name}.
278 + Sampeyan uga bisa entuk duit kripto €BACH kanthi ngaktifake dompet ing Profil
279 + -> Setelan.
280 + "legacy_view": >-
281 + Desain lawas
282 + "like_this": >-
283 + seneng iki
284 + "locale_name": "\U0001F1EE\U0001F1E9 Basa Jawa"
285 + "login": >-
286 + Masuk log!
287 + "login_with_facebook": >-
288 + Mlebu nganggo Facebook
289 + "lyric": >-
290 + Lirik Lagu
291 + "lyric_cannot_be_empty": >-
292 + Lirik tidak boleh kosong
293 + "my_profile": >-
294 + Profil gue
295 + "my_profile_link_href": >-
296 + tautan aman ini
297 + "my_profile_link_html": >-
298 + Ini adalah halaman publik lo. Ikuti %{href} kalo mau perbarui data lo.
299 + "my_profile_private": >-
300 + Profil ini bersifat pribadi
301 + "name": >-
302 + Jeneng
303 + "nav_albums": >-
304 + Album
305 + "nav_artists": >-
306 + Artis
307 + "nav_change_language": >-
308 + Ganti Basa:
309 + "nav_chords": >-
310 + Kunci Gitar
311 + "nav_chords_shapes": >-
312 + Bentuk Kunci Gitar
313 + "nav_community": >-
314 + Komunitas
315 + "nav_en": >-
316 + English
317 + "nav_footer_about": >-
318 + Tentang Kami
319 + "nav_footer_company": >-
320 + Perusahaan
321 + "nav_footer_contact": >-
322 + Kontak
323 + "nav_footer_follow_us": >-
324 + Ikuti kami
325 + "nav_footer_privacy": >-
326 + Kebijakan Privasi
327 + "nav_footer_term": >-
328 + Ketentuan Penggunaan
329 + "nav_guitar": >-
330 + Gitar
331 + "nav_id": >-
332 + Bahasa Indonesia
333 + "nav_javanese_gamelan": >-
334 + Gamelan Jawa
335 + "nav_javanese_gamelan_about": >-
336 + Tentang Gamelan Jawa
337 + "nav_javanese_gending": >-
338 + Gending Jawa
339 + "nav_javanese_gending_notation": >-
340 + Notasi Gending Jawa
341 + "nav_login": >-
342 + Mlebu
343 + "nav_login_dropdown": >-
344 + Akun
345 + "nav_logout": >-
346 + Keluar
347 + "nav_lyrics": >-
348 + Lirik Lagu
349 + "nav_music_database": >-
350 + Musik
351 + "nav_onboarding": >-
352 + Onboarding
353 + "nav_signup": >-
354 + Daftar Akun
355 + "nav_songs": >-
356 + Lagu
357 + "nav_ukulele_chords": >-
358 + Kunci Ukulele (segera)
359 + "need_to_logged_in": >-
360 + Masuk log dulu sebelum kasih love. Belum punya akun? Daftar aja dulu, gampang
361 + kok. Bisa pake akun Facebook kamu lho.
362 + "need_to_logged_in_my_profile": >-
363 + You need to be logged in to perform this task.
364 + "new_user": >-
365 + Pengguna anyar?
366 + "new_view": >-
367 + Desain anyar
368 + "next": >-
369 + Saterusne
370 + "oh_hi": >-
371 + Oh, hi!
372 + "onboarding_done": >-
373 + Kaya kuwe, %{user_name}!
374 + "onboarding_done_button": >-
375 + Aku siyap!
376 + "onboarding_explanation": >-
377 + Kanthi verifikasi email sampeyan, sampeyan entuk 0,1 BACH! Entuk luwih akeh BACH
378 + kanthi tindakake langkah iki.
379 + "onboarding_explanation_done": >-
380 + Wis rampung kanggo saiki. Priksa profil sampeyan lan wiwiti nyumbang luwih akeh
381 + kanggo komunitas.
382 + "onboarding_sounds_good": >-
383 + Koyone apik!
384 + "onboarding_spotify_done": >-
385 + Rampung. Sampeyan wis ngekspor data Spotify.
386 + "onboarding_spotify_explanation": >-
387 + Entuk nganti 50 BACH nalika sampeyan ngekspor dhaptar lagu!
388 + "onboarding_spotify_heading": >-
389 + Ekspor Data Spotify sampeyan
390 + "onboarding_spotify_processing": >-
391 + Ngekspor data sampeyan. Butuh wektu.
392 + "onboarding_username_checking": >-
393 + Priksa...
394 + "onboarding_username_done": >-
395 + Yeay! Sampeyan entuk jeneng pangguna sampeyan!
396 + "onboarding_username_explanation": >-
397 + Gawe @username sing unik lan entuk 0.1 BACH liyane!
398 + "onboarding_username_heading": >-
399 + @username
400 + "or": >-
401 + Utawa
402 + "our_community": >-
403 + Komunitas Kami
404 + "our_guitar_chords_collection": >-
405 + Koleksi kunci gitar lagu terbaru dari kami
406 + "our_newest_song_lyrics": >-
407 + Koleksi Lirik Lagu Terbaru Kami
408 + "password": >-
409 + Kata sandi
410 + "password_button_update": >-
411 + Nganyari
412 + "password_confirmation": >-
413 + Konfirmasi kata sandi
414 + "password_new": >-
415 + Ketik sandhi anyar
416 + "password_new_confirmation": >-
417 + Konfirmasi sandhi
418 + "resend_email_confirmation": >-
419 + Kirim maneh instruksi konfirmasi
420 + "resend_email_confirmation_button": >-
421 + Kirim maneh instruksi
422 + "routes":
423 + "about_us": >-
424 + tentang-kami
425 + "add_gending": >-
426 + tambah-gending-gamelan
427 + "add_javanese_gamelan_notation": >-
428 + tambah-notasi-gamelan-jawa
429 + "album": >-
430 + album
431 + "album_add": >-
432 + add-album
433 + "album_bridge": >-
434 + albums-bridge
435 + "album_detail": >-
436 + album
437 + "artist": >-
438 + artis
439 + "artist_add": >-
440 + add-artist
441 + "artist_bridge": >-
442 + artists-bridge
443 + "artist_detail": >-
444 + artis
445 + "audio_files": >-
446 + audio-files
447 + "bass_backing_track": >-
448 + bass-backing-track-:slug
449 + "community": >-
450 + komunitas
451 + "confirm_email": >-
452 + konfirmasi-email
453 + "contact": >-
454 + kontak
455 + "drum_backing_track": >-
456 + drum-backing-track-:slug
457 + "followers": >-
458 + pengikut
459 + "following": >-
460 + mengikuti
461 + "guitar_backing_track": >-
462 + guitar-backing-track-:slug
463 + "guitar_chord_shape": >-
464 + bentuk-kunci-gitar-:slug
465 + "guitar_chord_shapes": >-
466 + bentuk-kunci-gitar
467 + "guitar_chords": >-
468 + kunci-gitar
469 + "guitar_chords_add": >-
470 + tambah-akord
471 + "guitar_chords_detail": >-
472 + kunci-gitar-:slug
473 + "guitar_chords_history": >-
474 + kunci-gitar-:slug/sejarah
475 + "guitar_chords_review": >-
476 + kunci-gitar-:slug/review-akord
477 + "javanese_gamelan": >-
478 + gamelan-jawa
479 + "javanese_gamelan_notation": >-
480 + gamelan-jawa
481 + "javanese_gending": >-
482 + gending-jawa
483 + "javanese_gending_detail": >-
484 + gending-gamelan-jawa-:slug
485 + "javanese_gending_gamelan_notation": >-
486 + notasi-gending-gamelan-jawa
487 + "javanese_gending_gamelan_notation_detail": >-
488 + notasi-gending-gamelan-jawa-:slug
489 + "javanese_gending_gamelan_notation_detail_new": >-
490 + notasi-gending-standar-:slug
491 + "login": >-
492 + masuk
493 + "most_loved_songs": >-
494 + lagu-paling-banyak-disuka
495 + "privacy_policy": >-
496 + kebijakan-privasi
497 + "signup": >-
498 + daftar
499 + "song": >-
500 + lagu
501 + "song_add": >-
502 + add-song
503 + "song_audio": >-
504 + song-audio
505 + "song_bass_tab": >-
506 + bass-tab-:slug
507 + "song_bridge": >-
508 + artists-bridge
509 + "song_chords": >-
510 + kord-lagu
511 + "song_detail": >-
512 + lagu
513 + "song_karaoke_detail": >-
514 + karaoke-lagu-:slug
515 + "song_lyrics": >-
516 + lirik-lagu
517 + "song_lyrics_detail": >-
518 + lirik-lagu-:slug
519 + "term_of_use": >-
520 + ketentuan-penggunaan
521 + "user_rep": >-
522 + reputasi
523 + "save": >-
524 + Simpen
525 + "search": >-
526 + Nggoleki
527 + "search_placeholder_title": >-
528 + Golek artis, tembang, utawa gending jawa...
529 + "see_album_detail": >-
530 + Lihat detail album
531 + "see_artist_detail": >-
532 + Lihat detail artis
533 + "see_chord_detail": >-
534 + Lihat detail kord
535 + "see_song_detail": >-
536 + Lihat detail lagu
537 + "settings": >-
538 + Setelan
539 + "signup": >-
540 + Daftar
541 + "signup_now": >-
542 + Daftar siki!
543 + "signup_to_claim": >-
544 + Iki duwekmu?
545 + "signup_with_facebook": >-
546 + Daftar nganggo Facebook
547 + "site_name": >-
548 + Musik88
549 + "somebody_to_love": >-
550 + Cintai aku
551 + "song_audio_page_title": >-
552 + File audio kanggo %{song_name}
553 + "song_chord_page_title": >-
554 + Kunci gitar %{song_name} by %{song_artist_name}
555 + "song_chord_unavailable_content": >-
556 + <span class='song-lyric-unavailable'>Kunci gitar %{song_name} dari %{song_artist_name}</span>
557 + belum tersedia. Beri cinta <span class='glyphicon glyphicon-heart'></span> pada
558 + lagu ini. Kami perlu setidaknya 2 cinta dari elo buat ngulik lagunya.
559 + "song_chord_unavailable_meta_description": >-
560 + Berikut adalah kunci gitar %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
561 + "song_chords_page_title": >-
562 + Kunci Lagu %{song_name}
563 + "song_detail": >-
564 + Detail lagu
565 + "song_detail_page_meta_title": >-
566 + Arti lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
567 + "song_detail_page_title": >-
568 + Arti Lagu %{song_name}
569 + "song_generate_your_own_bass_backing_track": >-
570 + Buat sendiri backing track bas lagu %{song_name}
571 + "song_generate_your_own_drum_backing_track": >-
572 + Buat sendiri backing track drum lagu %{song_name}
573 + "song_generate_your_own_karaoke": >-
574 + Buat Versi Karaoke Lagu %{song_name}
575 + "song_generate_your_own_stem_files": >-
576 + Buat STEM Files dari lagu %{song_name}
577 + "song_karaoke_download": >-
578 + Download Lagu Karaoke
579 + "song_karaoke_page_title": >-
580 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
581 + "song_karaoke_title": >-
582 + %{filename} Versi Karaoke
583 + "song_karaoke_unavailable_content": >-
584 + Lagu karaoke %{song_name} dari %{song_artist_name} belum tersedia. Login untuk
585 + request.
586 + "song_karaoke_unavailable_meta_description": >-
587 + Download Lagu Karaoke %{song_name} Dari %{song_artist_name} Album %{song_album_name}
588 + "song_lyric_page_title": >-
589 + Lirik Lagu %{song_name} dari %{song_artist_name}
590 + "song_lyric_unavailable_content": >-
591 + <span class='song-lyric-unavailable'>Lirik lagu %{song_name} dari %{song_artist_name}</span>
592 + belum tersedia. Beri setidaknya 2 cinta <span class='glyphicon glyphicon-heart'></span>
593 + atau %{href} pada lagu ini jadi kita bisa fokus buat ngulik kunci gitarnya.
594 + "song_lyric_unavailable_content_add_lyric": >-
595 + Tambah Lirik
596 + "song_lyric_unavailable_meta_description": >-
597 + Berikut adalah lirik lagu %{song_name} dari %{song_artist_name} dari album %{song_album_name}.
598 + "song_menu_aside_audio": >-
599 + Audio
600 + "song_menu_aside_chords": >-
601 + Kord
602 + "song_menu_aside_info": >-
603 + Info
604 + "song_menu_bass_backing_track": >-
605 + Bass backing track %{song_name}
606 + "song_menu_bass_tab": >-
607 + Bass tab %{song_name}
608 + "song_menu_drum_backing_track": >-
609 + Drum backing track %{song_name}
610 + "song_menu_guitar_chord": >-
611 + Kunci Gitar %{song_name}
612 + "song_menu_karaoke": >-
613 + Karaoke Version %{song_name}
614 + "song_menu_lyric": >-
615 + Lirik Lagu %{song_name}
616 + "song_updated_at": >-
617 + Diperbarui: %{date}
618 + "still_login": >-
619 + Elingi aku
620 + "time":
621 + "formats":
622 + "long": >-
623 + %Y-%m-%d %H:%M:%S
624 + "top_10_albums": >-
625 + 10 Album Teratas
626 + "top_10_artists": >-
627 + 10 Artis Teratas
628 + "top_10_guitar_chords": >-
629 + Top 10 Kunci Gitar
630 + "top_10_song_lyrics": >-
631 + Top 10 Lirik Lagu
632 + "top_10_songs": >-
633 + 10 Lagu Teratas
634 + "user_email_confirmation_content": >-
635 + Sampeyan bisa konfirmasi email akun liwat link ing ngisor iki:
636 + "user_email_confirmation_cta": >-
637 + Konfirmasi akunku
638 + "user_email_confirmation_header": >-
639 + Sugeng rawuh, %{email}!
640 + "user_email_with_love": >-
641 + Kanthi ❤️ saka Stockholm,
642 + "user_notifier_projects": >-
643 + Priksa proyek kita liyane
644 + "user_profile": >-
645 + Profil
646 + "user_reset_password_email_heading": >-
647 + Reset link sandi mung kanggo sampeyan, %{user_name}!
648 + "user_reset_password_email_heading_p1": >-
649 + Sampeyan entuk email iki amarga sampeyan nyoba ngakses akun sampeyan.
650 + "user_reset_password_email_heading_p2": >-
651 + Klik %{href} kanggo ngreset tembung sandhi.
652 + "user_reset_password_email_link": >-
653 + link siji iki
654 + "user_send_reset_password_instructions_subject": >-
655 + Reset instruksi sandi
656 + "user_send_wallet_activation_notification_address": >-
657 + Alamat dompet sampeyan
658 + "user_send_wallet_activation_notification_heading": >-
659 + Sugeng, %{user_name}. Sampeyan saiki duwe dompet crypto anyar!
660 + "user_send_wallet_activation_notification_p1": >-
661 + Duit kripto BACH sampeyan disimpen ing dompet sampeyan. Sampeyan bisa ndeleng
662 + imbangan sampeyan ing link ing ngisor iki.
663 + "user_send_wallet_activation_notification_subject": >-
664 + Sampeyan duwe dompet anyar!
665 + "user_signup_email_heading": >-
666 + Terimakasih telah mendaftar, %{user_name}!
667 + "user_signup_email_heading_p1": >-
668 + Selamat bergabung!
669 + "user_signup_email_heading_p2": >-
670 + Kami dalam sebuah misi untuk mendigitalkan musik-musik yang kurang terekpose.
671 + %{href} dan mulai berkontribusi :)
672 + "user_signup_email_subject": >-
673 + Selamat datang di Musik88!
674 + "username_available": >-
675 + Sugeng rawuh! Jeneng pangguna sampeyan kasedhiya.
676 + "username_character_invalid": >-
677 + Ngandhut karakter ilegal.
678 + "username_length_invalid": >-
679 + Jeneng pangguna minimal 4 huruf.
680 + "username_letters_numbers_only": >-
681 + Jeneng pangguna mung kudu ngemot huruf lan angka.
682 + "username_should_starts_with_letters": >-
683 + Sampeyan kudu miwiti karo huruf.
684 + "username_taken": >-
685 + Jeneng pangguna dijupuk.
686 + "views":
687 + "pagination":
688 + "next": >-
689 + Saterusne &rsaquo;
690 + "welcome_user": >-
691 + Sugeng rawuh, %{user_name}!
692 + "welcome_user_unconfirmed": >-
693 + Sugeng rawuh, %{user_name}! Priksa email kanggo konfirmasi akun.
694 + "will_paginate":
695 + "next_label": >-
696 + Saterusne &#8594;
697 + "your_locale": >-
698 + Basa
699 + "your_locale_explanation": >-
700 + Preferensi basa sampeyan nang situs web iki
701 + "your_wallet_address": >-
702 + Alamat dompet sampeyan
703 + "your_wallet_address_explanation": >-
704 + Kita nyimpen duit kripto sampeyan ing alamat dompet sampeyan. Sampeyan bisa tuku,
705 + ngedol, lan saham (stake) duit kripto sampeyan ing pasar mbukak, contone %{href}.
backend/musik88-web/config/locales/ko-kr.yml new
+437
@@ -0,0 +1,437 @@
1 +---
2 +"ko-kr":
3 + "10_most_loved_songs": >-
4 + 가장 사랑받는 노래 10곡
5 + "add_chord_validation_success": >-
6 + 참여해주셔서 감사합니다!
7 + "album_artists": >-
8 + 아티스트
9 + "album_song_list": >-
10 + 노래 목록
11 + "artist_profile_album_contribution_title": >-
12 + %{artist} 에도 나타납니다:
13 + "artist_profile_album_title": >-
14 + %{artist} 앨범
15 + "artist_profile_title": >-
16 + %{artist} 프로필
17 + "breadcrumb_home": >-
18 + 집
19 + "breadcrumb_most_loved_songs": >-
20 + 가장 사랑받은 노래
21 + "by": >-
22 + 사랑받는 사람
23 + "cancel": >-
24 + 취소
25 + "create_account": >-
26 + 계정 만들기
27 + "discussion": >-
28 + 논의
29 + "email": >-
30 + 이메일
31 + "find_your_artist": >-
32 + 좋아하는 아티스트 찾기
33 + "find_your_favorite_album": >-
34 + 좋아하는 새 앨범 찾기
35 + "find_your_favorite_songs": >-
36 + 좋아하는 노래 찾기
37 + "followers": >-
38 + 수행원
39 + "following": >-
40 + 수행원
41 + "footer": >-
42 + %{instagram} 전역에서 %{profile}이(가) 재미를 위해 만들었습니다. &middot; &copy; 2021
43 + "footer_world": >-
44 + 세계
45 + "forgot_password": >-
46 + 비밀번호를 잊으 셨나요?
47 + "gending_add": >-
48 + 성별 표기법 공유
49 + "gending_add_explanation": >-
50 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
51 + token.
52 + "gending_description": >-
53 + 설명
54 + "gending_name": >-
55 + Gending 이름
56 + "gending_notation": >-
57 + Gending Notation
58 + "gending_notation_add": >-
59 + Add gending notation
60 + "gending_notation_link": >-
61 + Javanese gending notation %{slug}
62 + "gending_notation_meta_desciption": >-
63 + This is the javanese gending notation for %{title} gending.
64 + "gending_notation_page_title": >-
65 + Javanese Gending Notation %{title}
66 + "guitar_chord_image": >-
67 + Guitar Chord Image
68 + "guitar_chord_link": >-
69 + Guitar chord %{slug}
70 + "guitar_chord_video": >-
71 + 기타 코드 비디오
72 + "guitar_chords_shape": >-
73 + 기타 코드 모양
74 + "h2_find_new_albums": >-
75 + 여기에서 새 앨범을 찾으세요!
76 + "h2_find_new_artists": >-
77 + 여기에서 새로운 아티스트를 찾으세요!
78 + "have_account": >-
79 + 이미 계정이 있습니까?
80 + "hello": >-
81 + Welcome to Musik 88
82 + "home_guitar_chords": >-
83 + 기타 코드
84 + "home_guitar_chords_headline": >-
85 + 기타 코드를 제출하고 기여에 대한 %{bach_link} 평판 토큰을 받으세요.
86 + "home_headline": >-
87 + 세계 최대의 음악 데이터베이스!
88 + "home_javanese_gending_notation": >-
89 + 자바어 성별 표기법
90 + "home_javanese_gending_notation_subheadline": >-
91 + Javanese gamelan을 디지털화하고 기여에 대해 %{bach_link} 평판 토큰을 받으세요.
92 + "home_lyrics": >-
93 + 노래 가사
94 + "home_new_entries": >-
95 + 새 항목
96 + "home_others": >-
97 + 기타
98 + "home_others_headline": >-
99 + 내 다른 프로젝트를 확인하십시오.
100 + "home_popular": >-
101 + 인기있는
102 + "home_see_all": >-
103 + 모두 보기
104 + "home_share_gamelan_notation_now": >-
105 + 성별 표기법을 공유하세요!
106 + "home_share_now": >-
107 + 코드를 공유하세요!
108 + "home_signup_now": >-
109 + 지금 등록하세요!
110 + "home_songs": >-
111 + 인기곡
112 + "home_subheadline": >-
113 + 기타 코드? 노래 가사? 앨범 리뷰? 자바어 성별 표기법? 등! 가입하고 기여(아티스트, 앨범, 가사 등 추가)하고 %{bach_link}
114 + 평판 토큰을 받으세요.
115 + "home_youtube_karaoke": >-
116 + 유튜브 가라오케
117 + "home_youtube_karaoke_headline": >-
118 + AI가 제공하는 좋아하는 YouTube 뮤직 비디오의 가라오케 버전.
119 + "invalid_login": >-
120 + Invalid email/password combination
121 + "locale_name": "\U0001F1F0\U0001F1F7 한국인"
122 + "login": >-
123 + 로그인!
124 + "login_with_facebook": >-
125 + Login with Facebook
126 + "lyric": >-
127 + Song Lyric
128 + "lyric_cannot_be_empty": >-
129 + Lyric can't be empty
130 + "my_profile": >-
131 + My Profile
132 + "my_profile_link_href": >-
133 + this secure link
134 + "my_profile_link_html": >-
135 + This is your public profile. Follow %{href} to edit your profile.
136 + "my_profile_private": >-
137 + This profile is private
138 + "name": >-
139 + 이름
140 + "nav_albums": >-
141 + 앨범
142 + "nav_artists": >-
143 + 아티스트
144 + "nav_change_language": >-
145 + Change Language:
146 + "nav_chords": >-
147 + Guitar Chords
148 + "nav_chords_shapes": >-
149 + Guitar Chords Diagram
150 + "nav_community": >-
151 + Community
152 + "nav_en": >-
153 + English
154 + "nav_footer_about": >-
155 + About Us
156 + "nav_footer_company": >-
157 + Company
158 + "nav_footer_contact": >-
159 + Contact
160 + "nav_footer_follow_us": >-
161 + Follow Us
162 + "nav_footer_privacy": >-
163 + Privacy Policy
164 + "nav_footer_term": >-
165 + Term of Use
166 + "nav_guitar": >-
167 + Guitar
168 + "nav_id": >-
169 + Bahasa Indonesia
170 + "nav_javanese_gamelan": >-
171 + Javanese Gamelan
172 + "nav_javanese_gamelan_about": >-
173 + About Javanese Gamelan
174 + "nav_javanese_gending": >-
175 + Javanese Gending
176 + "nav_javanese_gending_notation": >-
177 + Javanese Gending Notation
178 + "nav_login": >-
179 + 로그인
180 + "nav_login_dropdown": >-
181 + Account
182 + "nav_logout": >-
183 + 로그 아웃
184 + "nav_lyrics": >-
185 + Song Lyrics
186 + "nav_music_database": >-
187 + Musics
188 + "nav_new_entries": >-
189 + New Entries
190 + "nav_signup": >-
191 + 가입하기
192 + "nav_songs": >-
193 + Songs
194 + "nav_ukulele_chords": >-
195 + Ukulele Chords (soon)
196 + "need_to_logged_in": >-
197 + You need to logged in before giving this love. Don't have account yet? Just signup,
198 + it's so easy. You can use your Facebook account if you want.
199 + "need_to_logged_in_my_profile": >-
200 + You need to be logged in to perform this task.
201 + "new_user": >-
202 + 새로운 사용자?
203 + "or": >-
204 + 또는
205 + "our_community": >-
206 + Our Community
207 + "our_guitar_chords_collection": >-
208 + Our Newest Guitar Chords Collections
209 + "our_newest_song_lyrics": >-
210 + Our Newest Song Lyrics Collection
211 + "password": >-
212 + 비밀번호
213 + "password_button_update": >-
214 + Update
215 + "password_confirmation": >-
216 + 비밀번호 확인
217 + "password_new": >-
218 + Enter new password
219 + "password_new_confirmation": >-
220 + Confirm password
221 + "routes":
222 + "about_us": >-
223 + about-us
224 + "add_gending": >-
225 + add-gending-gamelan
226 + "add_javanese_gamelan_notation": >-
227 + add-javanese-gamelan-notation
228 + "album": >-
229 + albums
230 + "album_add": >-
231 + add-album
232 + "album_detail": >-
233 + albums
234 + "artist": >-
235 + artists
236 + "artist_add": >-
237 + add-artist
238 + "artist_detail": >-
239 + artists
240 + "audio_files": >-
241 + audio-files
242 + "bass_backing_track": >-
243 + bass-backing-track-:slug
244 + "community": >-
245 + community
246 + "contact": >-
247 + contact
248 + "drum_backing_track": >-
249 + drum-backing-track-:slug
250 + "followers": >-
251 + followers
252 + "following": >-
253 + following
254 + "guitar_backing_track": >-
255 + guitar-backing-track-:slug
256 + "guitar_chord_shape": >-
257 + guitar-chord-:slug
258 + "guitar_chord_shapes": >-
259 + all-guitar-chords
260 + "guitar_chords": >-
261 + guitar-chords
262 + "guitar_chords_add": >-
263 + add-chord
264 + "guitar_chords_detail": >-
265 + guitar-chords-:slug
266 + "guitar_chords_history": >-
267 + guitar-chords-:slug/history
268 + "guitar_chords_review": >-
269 + guitar-chords-:slug/review-chord
270 + "javanese_gamelan": >-
271 + javanese-gamelan
272 + "javanese_gamelan_notation": >-
273 + javanese-gamelan
274 + "javanese_gending": >-
275 + javanese-gending
276 + "javanese_gending_detail": >-
277 + javanese-gending-gamelan-:slug
278 + "javanese_gending_gamelan_notation": >-
279 + notation-for-javanese-gamelan
280 + "javanese_gending_gamelan_notation_detail": >-
281 + notation-for-javanese-gamelan-:slug
282 + "login": >-
283 + login
284 + "most_loved_songs": >-
285 + most-loved-songs
286 + "privacy_policy": >-
287 + privacy-policy
288 + "signup": >-
289 + signup
290 + "song": >-
291 + songs
292 + "song_add": >-
293 + add-song
294 + "song_audio": >-
295 + song-audio
296 + "song_bass_tab": >-
297 + bass-tab-:slug
298 + "song_chords": >-
299 + song-chords
300 + "song_detail": >-
301 + songs
302 + "song_karaoke_detail": >-
303 + karaoke-song-:slug
304 + "song_lyrics": >-
305 + song-lyrics
306 + "song_lyrics_detail": >-
307 + song-lyrics-:slug
308 + "term_of_use": >-
309 + term-of-use
310 + "user_rep": >-
311 + reputation
312 + "see_album_detail": >-
313 + See album detail
314 + "see_artist_detail": >-
315 + See artist detail
316 + "see_chord_detail": >-
317 + See chord detail
318 + "see_song_detail": >-
319 + See song detail
320 + "signup": >-
321 + 가입하기
322 + "signup_now": >-
323 + 지금 등록하세요!
324 + "signup_to_claim": >-
325 + 이거 당신 껍니까?
326 + "signup_with_facebook": >-
327 + 페이스북으로 가입하기
328 + "site_name": >-
329 + Musik88.com
330 + "somebody_to_love": >-
331 + Love me
332 + "song_audio_page_title": >-
333 + Audio files for %{song_name}
334 + "song_chord_page_title": >-
335 + Guitar chord %{song_name} by %{song_artist_name}
336 + "song_chord_unavailable_content": >-
337 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
338 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
339 + that's all we need.
340 + "song_chord_unavailable_meta_description": >-
341 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
342 + album.
343 + "song_chords_page_title": >-
344 + %{song_name} Chords
345 + "song_detail": >-
346 + Song detail
347 + "song_detail_page_meta_title": >-
348 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
349 + album/single.
350 + "song_detail_page_title": >-
351 + %{song_name} Song Review
352 + "song_generate_your_own_bass_backing_track": >-
353 + Generate %{song_name} Bass Backing Track
354 + "song_generate_your_own_drum_backing_track": >-
355 + Generate %{song_name} Drum Backing Track
356 + "song_generate_your_own_karaoke": >-
357 + Generate %{song_name} Karaoke File
358 + "song_generate_your_own_stem_files": >-
359 + Generate STEM Files For %{song_name}
360 + "song_karaoke_download": >-
361 + Download Karaoke Version
362 + "song_karaoke_page_title": >-
363 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
364 + Album
365 + "song_karaoke_title": >-
366 + %{filename} Karaoke Version
367 + "song_karaoke_unavailable_content": >-
368 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
369 + "song_karaoke_unavailable_meta_description": >-
370 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
371 + Album
372 + "song_lyric_page_title": >-
373 + %{song_name} Lyric by %{song_artist_name}
374 + "song_lyric_unavailable_content": >-
375 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
376 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
377 + or %{href} to this song so we can just focus on finding the chords.
378 + "song_lyric_unavailable_content_add_lyric": >-
379 + Add Lyric
380 + "song_lyric_unavailable_meta_description": >-
381 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
382 + album.
383 + "song_menu_aside_audio": >-
384 + Audio
385 + "song_menu_aside_chords": >-
386 + Chords
387 + "song_menu_aside_info": >-
388 + Info
389 + "song_menu_bass_backing_track": >-
390 + Bass backing track %{song_name}
391 + "song_menu_bass_tab": >-
392 + Bass tab %{song_name}
393 + "song_menu_drum_backing_track": >-
394 + Drum backing track %{song_name}
395 + "song_menu_guitar_chord": >-
396 + Guitar Chord %{song_name}
397 + "song_menu_karaoke": >-
398 + Karaoke Version %{song_name}
399 + "song_menu_lyric": >-
400 + Song Lyric %{song_name}
401 + "song_updated_at": >-
402 + Updated: %{date}
403 + "still_login": >-
404 + 날 기억해
405 + "top_10_albums": >-
406 + 상위 10개 앨범
407 + "top_10_artists": >-
408 + 상위 10명의 아티스트
409 + "top_10_guitar_chords": >-
410 + Top 10 Guitar Chords
411 + "top_10_song_lyrics": >-
412 + Top 10 Song Lyrics
413 + "top_10_songs": >-
414 + Top 10 Songs
415 + "user_profile": >-
416 + Profile
417 + "user_reset_password_email_heading": >-
418 + Reset password link only for you, %{user_name}!
419 + "user_reset_password_email_heading_p1": >-
420 + You are getting this email because you are trying to access your account.
421 + "user_reset_password_email_heading_p2": >-
422 + Click %{href} to reset your password.
423 + "user_reset_password_email_link": >-
424 + this one time link
425 + "user_send_reset_password_instructions_subject": >-
426 + Reset password instruction
427 + "user_signup_email_heading": >-
428 + Thanks for signing up, %{user_name}!
429 + "user_signup_email_heading_p1": >-
430 + Thanks for joining us!
431 + "user_signup_email_heading_p2": >-
432 + We are in a mission to digitalize underrepresented musics in the world. %{href}
433 + and start contributing :)
434 + "user_signup_email_subject": >-
435 + Welcome to Musik88!
436 + "welcome_user": >-
437 + Welcome, %{user_name}!
backend/musik88-web/config/locales/nl.yml new
+525
@@ -0,0 +1,525 @@
1 +---
2 +"nl":
3 + "10_most_loved_songs": >-
4 + 10 Most Loved Songs
5 + "active_admin":
6 + "filters":
7 + "predicates":
8 + "from": >-
9 + dari tanggal
10 + "to": >-
11 + sampai tanggal
12 + "activerecord":
13 + "errors":
14 + "models":
15 + "album":
16 + "attributes":
17 + "description":
18 + "too_short": >-
19 + Deskripsi artis tidak boleh kosong
20 + "name":
21 + "too_short": >-
22 + Nama album tidak boleh kosong
23 + "artist":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + Deskripsi artis tidak boleh kosong
28 + "name":
29 + "too_short": >-
30 + Nama artis tidak boleh kosong
31 + "chord":
32 + "attributes":
33 + "chord":
34 + "too_short": >-
35 + Chord lagu tidak boleh kosong
36 + "lyric":
37 + "attributes":
38 + "lyric":
39 + "too_short": >-
40 + Lirik lagu tidak boleh kosong
41 + "page":
42 + "attributes":
43 + "page_content":
44 + "too_short": >-
45 + Konten halaman tidak boleh kosong
46 + "page_slug":
47 + "too_short": >-
48 + Slug halaman tidak boleh kosong
49 + "song":
50 + "attributes":
51 + "album":
52 + "too_short": >-
53 + Album tidak boleh kosong
54 + "description":
55 + "too_short": >-
56 + Deskripsi lagu tidak boleh kosong
57 + "name":
58 + "too_short": >-
59 + Nama lagu tidak boleh kosong
60 + "user":
61 + "attributes":
62 + "email":
63 + "blank": >-
64 + Email tidak boleh kosong
65 + "invalid": >-
66 + Email tidak valid
67 + "name":
68 + "blank": >-
69 + Nama tidak boleh kosong
70 + "password":
71 + "blank": >-
72 + Password tidak boleh kosong
73 + "too_short": >-
74 + Password terlalu pendek
75 + "password_confirmation":
76 + "confirmation": >-
77 + Konfirmasi password tidak sama
78 + "album_artists": >-
79 + Artists
80 + "album_song_list": >-
81 + Song list
82 + "artist_profile_album_contribution_title": >-
83 + %{artist} also appears on:
84 + "artist_profile_album_title": >-
85 + %{artist}'s Albums
86 + "artist_profile_title": >-
87 + %{artist} Profile
88 + "breadcrumb_home": >-
89 + Home
90 + "breadcrumb_most_loved_songs": >-
91 + Most Loved Songs
92 + "by": >-
93 + Loved by
94 + "cancel": >-
95 + Cancel
96 + "confirm_email": >-
97 + Bevestig je email
98 + "create_account": >-
99 + Create account
100 + "discussion": >-
101 + Discussion
102 + "email": >-
103 + Email
104 + "find_your_artist": >-
105 + Find Your Favorite Artist
106 + "find_your_favorite_album": >-
107 + Find Your New Favorite Album
108 + "find_your_favorite_songs": >-
109 + Find Your Favorite Songs
110 + "followers": >-
111 + Follower
112 + "following": >-
113 + Following
114 + "footer": >-
115 + Created for fun by %{profile} from all around the %{instagram} &middot; &copy;
116 + 2021
117 + "footer_world": >-
118 + world
119 + "forgot_password": >-
120 + Forgot password?
121 + "gending_add": >-
122 + Share your gending notation
123 + "gending_add_explanation": >-
124 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
125 + token.
126 + "gending_description": >-
127 + Description
128 + "gending_name": >-
129 + Gending Name
130 + "gending_notation": >-
131 + Gending Notation
132 + "gending_notation_add": >-
133 + Add gending notation
134 + "gending_notation_link": >-
135 + Javanese gending notation %{slug}
136 + "gending_notation_meta_desciption": >-
137 + This is the javanese gending notation for %{title} gending.
138 + "gending_notation_page_title": >-
139 + Javanese Gending Notation %{title}
140 + "guitar_chord_image": >-
141 + Guitar Chord Image
142 + "guitar_chord_link": >-
143 + Guitar chord %{slug}
144 + "guitar_chord_video": >-
145 + Guitar Chord Video
146 + "guitar_chords_shape": >-
147 + Guitar Chords Shape
148 + "h2_find_new_albums": >-
149 + Find New Albums Here!
150 + "h2_find_new_artists": >-
151 + Find New Artists Here!
152 + "h2_find_new_songs": >-
153 + Find New Musics Here!
154 + "have_account": >-
155 + Already have account?
156 + "hello": >-
157 + Welcome to Musik 88
158 + "home_add_album": >-
159 + Album toevoegen
160 + "home_add_artist": >-
161 + Artiest toevoegen
162 + "home_guitar_chords": >-
163 + Gitaarakkoorden
164 + "home_guitar_chords_headline": >-
165 + Submit your guitar chords and earn %{bach_link} reputation token for your contribution.
166 + "home_headline": >-
167 + 's Werelds grootste muziekdatabase!
168 + "home_javanese_gending_notation": >-
169 + Javanese Gending Notation
170 + "home_javanese_gending_notation_subheadline": >-
171 + Digitalize Javanese gamelan and earn %{bach_link} reputation token for your contribution.
172 + "home_lyrics": >-
173 + Song Lyrics
174 + "home_new_entries": >-
175 + New Entries
176 + "home_others": >-
177 + Others
178 + "home_others_headline": >-
179 + Check out my other projects.
180 + "home_popular": >-
181 + Popular
182 + "home_see_all": >-
183 + See all
184 + "home_share_gamelan_notation_now": >-
185 + Share gending notation!
186 + "home_share_now": >-
187 + Share your chord!
188 + "home_signup_now": >-
189 + Signup now!
190 + "home_songs": >-
191 + Songs
192 + "home_subheadline": >-
193 + Gitaarakkoorden? Songtekst? Albumrecensies? Javaanse geslachtsnotatie? enzovoort!
194 + Meld u aan, draag bij (voeg artiesten, albums, songteksten, enz. toe) en verdien
195 + %{bach_link} reputatietoken.
196 + "home_youtube_karaoke": >-
197 + Youtube Karaoke
198 + "home_youtube_karaoke_headline": >-
199 + Karaoke version of your favorite YouTube music video powered by AI.
200 + "invalid_login": >-
201 + Invalid email/password combination
202 + "locale_name": >-
203 + Nederlands
204 + "login": >-
205 + Login!
206 + "login_with_facebook": >-
207 + Login with Facebook
208 + "lyric": >-
209 + Song Lyric
210 + "lyric_cannot_be_empty": >-
211 + Lyric can't be empty
212 + "my_profile": >-
213 + My Profile
214 + "my_profile_link_href": >-
215 + this secure link
216 + "my_profile_link_html": >-
217 + This is your public profile. Follow %{href} to edit your profile.
218 + "my_profile_private": >-
219 + This profile is private
220 + "name": >-
221 + Name
222 + "nav_albums": >-
223 + Albums
224 + "nav_artists": >-
225 + Artists
226 + "nav_change_language": >-
227 + Change Language:
228 + "nav_chords": >-
229 + Guitar Chords
230 + "nav_chords_shapes": >-
231 + Guitar Chords Diagram
232 + "nav_community": >-
233 + Community
234 + "nav_en": >-
235 + English
236 + "nav_footer_about": >-
237 + About Us
238 + "nav_footer_company": >-
239 + Company
240 + "nav_footer_contact": >-
241 + Contact
242 + "nav_footer_follow_us": >-
243 + Follow Us
244 + "nav_footer_privacy": >-
245 + Privacy Policy
246 + "nav_footer_term": >-
247 + Term of Use
248 + "nav_guitar": >-
249 + Guitar
250 + "nav_id": >-
251 + Bahasa Indonesia
252 + "nav_javanese_gamelan": >-
253 + Javanese Gamelan
254 + "nav_javanese_gamelan_about": >-
255 + About Javanese Gamelan
256 + "nav_javanese_gending": >-
257 + Javanese Gending
258 + "nav_javanese_gending_notation": >-
259 + Javanese Gending Notation
260 + "nav_login": >-
261 + Login
262 + "nav_login_dropdown": >-
263 + Account
264 + "nav_logout": >-
265 + Log out
266 + "nav_lyrics": >-
267 + Song Lyrics
268 + "nav_music_database": >-
269 + Musics
270 + "nav_new_entries": >-
271 + New Entries
272 + "nav_signup": >-
273 + Signup
274 + "nav_songs": >-
275 + Songs
276 + "nav_ukulele_chords": >-
277 + Ukulele Chords (soon)
278 + "need_to_logged_in": >-
279 + You need to logged in before giving this love. Don't have account yet? Just signup,
280 + it's so easy. You can use your Facebook account if you want.
281 + "need_to_logged_in_my_profile": >-
282 + You need to be logged in to perform this task.
283 + "new_user": >-
284 + New user?
285 + "or": >-
286 + Or
287 + "our_community": >-
288 + Our Community
289 + "our_guitar_chords_collection": >-
290 + Our Newest Guitar Chords Collections
291 + "our_newest_song_lyrics": >-
292 + Our Newest Song Lyrics Collection
293 + "password": >-
294 + Password
295 + "password_button_update": >-
296 + Update
297 + "password_confirmation": >-
298 + Password confirmation
299 + "password_new": >-
300 + Enter new password
301 + "password_new_confirmation": >-
302 + Confirm password
303 + "routes":
304 + "about_us": >-
305 + about-us
306 + "add_gending": >-
307 + add-gending-gamelan
308 + "add_javanese_gamelan_notation": >-
309 + add-javanese-gamelan-notation
310 + "album": >-
311 + albums
312 + "album_add": >-
313 + add-album
314 + "album_detail": >-
315 + albums
316 + "artist": >-
317 + artists
318 + "artist_add": >-
319 + add-artist
320 + "artist_detail": >-
321 + artists
322 + "audio_files": >-
323 + audio-files
324 + "bass_backing_track": >-
325 + bass-backing-track-:slug
326 + "community": >-
327 + community
328 + "contact": >-
329 + contact
330 + "drum_backing_track": >-
331 + drum-backing-track-:slug
332 + "followers": >-
333 + followers
334 + "following": >-
335 + following
336 + "guitar_backing_track": >-
337 + guitar-backing-track-:slug
338 + "guitar_chord_shape": >-
339 + guitar-chord-:slug
340 + "guitar_chord_shapes": >-
341 + all-guitar-chords
342 + "guitar_chords": >-
343 + guitar-chords
344 + "guitar_chords_add": >-
345 + add-chord
346 + "guitar_chords_detail": >-
347 + guitar-chords-:slug
348 + "guitar_chords_history": >-
349 + guitar-chords-:slug/history
350 + "guitar_chords_review": >-
351 + guitar-chords-:slug/review-chord
352 + "javanese_gamelan": >-
353 + javanese-gamelan
354 + "javanese_gamelan_notation": >-
355 + javanese-gamelan
356 + "javanese_gending": >-
357 + javanese-gending
358 + "javanese_gending_detail": >-
359 + javanese-gending-gamelan-:slug
360 + "javanese_gending_gamelan_notation": >-
361 + notation-for-javanese-gamelan
362 + "javanese_gending_gamelan_notation_detail": >-
363 + notation-for-javanese-gamelan-:slug
364 + "login": >-
365 + login
366 + "most_loved_songs": >-
367 + most-loved-songs
368 + "privacy_policy": >-
369 + privacy-policy
370 + "signup": >-
371 + signup
372 + "song": >-
373 + songs
374 + "song_add": >-
375 + add-song
376 + "song_audio": >-
377 + song-audio
378 + "song_bass_tab": >-
379 + bass-tab-:slug
380 + "song_chords": >-
381 + song-chords
382 + "song_detail": >-
383 + songs
384 + "song_karaoke_detail": >-
385 + karaoke-song-:slug
386 + "song_lyrics": >-
387 + song-lyrics
388 + "song_lyrics_detail": >-
389 + song-lyrics-:slug
390 + "term_of_use": >-
391 + term-of-use
392 + "user_rep": >-
393 + reputation
394 + "see_album_detail": >-
395 + See album detail
396 + "see_artist_detail": >-
397 + See artist detail
398 + "see_chord_detail": >-
399 + See chord detail
400 + "see_song_detail": >-
401 + See song detail
402 + "signup": >-
403 + Signup
404 + "signup_now": >-
405 + Signup now!
406 + "signup_to_claim": >-
407 + Is this yours?
408 + "signup_with_facebook": >-
409 + Signup with Facebook
410 + "site_name": >-
411 + Musik88.com
412 + "somebody_to_love": >-
413 + Love me
414 + "song_audio_page_title": >-
415 + Audio files for %{song_name}
416 + "song_chord_page_title": >-
417 + Guitar chord %{song_name} by %{song_artist_name}
418 + "song_chord_unavailable_content": >-
419 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
420 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
421 + that's all we need.
422 + "song_chord_unavailable_meta_description": >-
423 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
424 + album.
425 + "song_chords_page_title": >-
426 + %{song_name} Chords
427 + "song_detail": >-
428 + Song detail
429 + "song_detail_page_meta_title": >-
430 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
431 + album/single.
432 + "song_detail_page_title": >-
433 + %{song_name} Song Review
434 + "song_generate_your_own_bass_backing_track": >-
435 + Generate %{song_name} Bass Backing Track
436 + "song_generate_your_own_drum_backing_track": >-
437 + Generate %{song_name} Drum Backing Track
438 + "song_generate_your_own_karaoke": >-
439 + Generate %{song_name} Karaoke File
440 + "song_generate_your_own_stem_files": >-
441 + Generate STEM Files For %{song_name}
442 + "song_karaoke_download": >-
443 + Download Karaoke Version
444 + "song_karaoke_page_title": >-
445 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
446 + Album
447 + "song_karaoke_title": >-
448 + %{filename} Karaoke Version
449 + "song_karaoke_unavailable_content": >-
450 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
451 + "song_karaoke_unavailable_meta_description": >-
452 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
453 + Album
454 + "song_lyric_detail": >-
455 + Song lyric %{title}
456 + "song_lyric_page_title": >-
457 + %{song_name} Lyric by %{song_artist_name}
458 + "song_lyric_unavailable_content": >-
459 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
460 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
461 + or %{href} to this song so we can just focus on finding the chords.
462 + "song_lyric_unavailable_content_add_lyric": >-
463 + Add Lyric
464 + "song_lyric_unavailable_meta_description": >-
465 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
466 + album.
467 + "song_lyrics_page_title": >-
468 + Song Lyrics %{song_name}
469 + "song_menu_aside_audio": >-
470 + Audio
471 + "song_menu_aside_chords": >-
472 + Chords
473 + "song_menu_aside_info": >-
474 + Info
475 + "song_menu_aside_lyrics": >-
476 + Lyrics
477 + "song_menu_bass_backing_track": >-
478 + Bass backing track %{song_name}
479 + "song_menu_bass_tab": >-
480 + Bass tab %{song_name}
481 + "song_menu_drum_backing_track": >-
482 + Drum backing track %{song_name}
483 + "song_menu_guitar_chord": >-
484 + Guitar Chord %{song_name}
485 + "song_menu_karaoke": >-
486 + Karaoke Version %{song_name}
487 + "song_menu_lyric": >-
488 + Song Lyric %{song_name}
489 + "song_updated_at": >-
490 + Updated: %{date}
491 + "still_login": >-
492 + Remember me
493 + "top_10_albums": >-
494 + Top 10 Albums
495 + "top_10_artists": >-
496 + Top 10 Artists
497 + "top_10_guitar_chords": >-
498 + Top 10 Guitar Chords
499 + "top_10_song_lyrics": >-
500 + Top 10 Song Lyrics
501 + "top_10_songs": >-
502 + Top 10 Songs
503 + "user_profile": >-
504 + Profile
505 + "user_reset_password_email_heading": >-
506 + Reset password link only for you, %{user_name}!
507 + "user_reset_password_email_heading_p1": >-
508 + You are getting this email because you are trying to access your account.
509 + "user_reset_password_email_heading_p2": >-
510 + Click %{href} to reset your password.
511 + "user_reset_password_email_link": >-
512 + this one time link
513 + "user_send_reset_password_instructions_subject": >-
514 + Reset password instruction
515 + "user_signup_email_heading": >-
516 + Thanks for signing up, %{user_name}!
517 + "user_signup_email_heading_p1": >-
518 + Thanks for joining us!
519 + "user_signup_email_heading_p2": >-
520 + We are in a mission to digitalize underrepresented musics in the world. %{href}
521 + and start contributing :)
522 + "user_signup_email_subject": >-
523 + Welcome to Musik88!
524 + "welcome_user": >-
525 + Welcome, %{user_name}!
backend/musik88-web/config/locales/pt.yml new
+524
@@ -0,0 +1,524 @@
1 +---
2 +"pt":
3 + "10_most_loved_songs": >-
4 + 10 Most Loved Songs
5 + "active_admin":
6 + "filters":
7 + "predicates":
8 + "from": >-
9 + dari tanggal
10 + "to": >-
11 + sampai tanggal
12 + "activerecord":
13 + "errors":
14 + "models":
15 + "album":
16 + "attributes":
17 + "description":
18 + "too_short": >-
19 + Deskripsi artis tidak boleh kosong
20 + "name":
21 + "too_short": >-
22 + Nama album tidak boleh kosong
23 + "artist":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + Deskripsi artis tidak boleh kosong
28 + "name":
29 + "too_short": >-
30 + Nama artis tidak boleh kosong
31 + "chord":
32 + "attributes":
33 + "chord":
34 + "too_short": >-
35 + Chord lagu tidak boleh kosong
36 + "lyric":
37 + "attributes":
38 + "lyric":
39 + "too_short": >-
40 + Lirik lagu tidak boleh kosong
41 + "page":
42 + "attributes":
43 + "page_content":
44 + "too_short": >-
45 + Konten halaman tidak boleh kosong
46 + "page_slug":
47 + "too_short": >-
48 + Slug halaman tidak boleh kosong
49 + "song":
50 + "attributes":
51 + "album":
52 + "too_short": >-
53 + Album tidak boleh kosong
54 + "description":
55 + "too_short": >-
56 + Deskripsi lagu tidak boleh kosong
57 + "name":
58 + "too_short": >-
59 + Nama lagu tidak boleh kosong
60 + "user":
61 + "attributes":
62 + "email":
63 + "blank": >-
64 + Email tidak boleh kosong
65 + "invalid": >-
66 + Email tidak valid
67 + "name":
68 + "blank": >-
69 + Nama tidak boleh kosong
70 + "password":
71 + "blank": >-
72 + Password tidak boleh kosong
73 + "too_short": >-
74 + Password terlalu pendek
75 + "password_confirmation":
76 + "confirmation": >-
77 + Konfirmasi password tidak sama
78 + "add_chord_validation_success": >-
79 + Obrigado por contribuir!
80 + "album_artists": >-
81 + Artists
82 + "album_song_list": >-
83 + Song list
84 + "artist_profile_album_contribution_title": >-
85 + %{artist} also appears on:
86 + "artist_profile_album_title": >-
87 + %{artist}'s Albums
88 + "artist_profile_title": >-
89 + %{artist} Profile
90 + "breadcrumb_home": >-
91 + Casa
92 + "breadcrumb_most_loved_songs": >-
93 + Músicas mais queridas
94 + "by": >-
95 + Loved by
96 + "cancel": >-
97 + Cancel
98 + "confirm_email": >-
99 + Confirme seu email
100 + "create_account": >-
101 + Create account
102 + "discussion": >-
103 + Discussion
104 + "email": >-
105 + Email
106 + "find_your_artist": >-
107 + Find Your Favorite Artist
108 + "find_your_favorite_album": >-
109 + Find Your New Favorite Album
110 + "find_your_favorite_songs": >-
111 + Find Your Favorite Songs
112 + "followers": >-
113 + Follower
114 + "following": >-
115 + Following
116 + "footer": >-
117 + Created for fun by %{profile} from all around the %{instagram} &middot; &copy;
118 + 2021
119 + "footer_world": >-
120 + world
121 + "forgot_password": >-
122 + Forgot password?
123 + "gending_add": >-
124 + Share your gending notation
125 + "gending_add_explanation": >-
126 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
127 + token.
128 + "gending_description": >-
129 + Description
130 + "gending_name": >-
131 + Gending Name
132 + "gending_notation": >-
133 + Gending Notation
134 + "gending_notation_add": >-
135 + Add gending notation
136 + "gending_notation_link": >-
137 + Javanese gending notation %{slug}
138 + "gending_notation_meta_desciption": >-
139 + This is the javanese gending notation for %{title} gending.
140 + "gending_notation_page_title": >-
141 + Javanese Gending Notation %{title}
142 + "guitar_chord_image": >-
143 + Guitar Chord Image
144 + "guitar_chord_link": >-
145 + Guitar chord %{slug}
146 + "guitar_chord_video": >-
147 + Guitar Chord Video
148 + "guitar_chords_shape": >-
149 + Guitar Chords Shape
150 + "h2_find_new_albums": >-
151 + Find New Albums Here!
152 + "h2_find_new_artists": >-
153 + Find New Artists Here!
154 + "h2_find_new_songs": >-
155 + Find New Musics Here!
156 + "have_account": >-
157 + Already have account?
158 + "hello": >-
159 + Welcome to Musik 88
160 + "home_guitar_chords": >-
161 + Cordas da guitarra
162 + "home_guitar_chords_headline": >-
163 + Envie seus acordes de guitarra e ganhe %{bach_link} token de reputação por sua
164 + contribuição.
165 + "home_headline": >-
166 + O maior banco de dados de música do mundo!
167 + "home_javanese_gending_notation": >-
168 + Javanese Gending Notation
169 + "home_javanese_gending_notation_subheadline": >-
170 + Digitalize gamelan javanês e ganhe %{bach_link} token de reputação por sua contribuição.
171 + "home_lyrics": >-
172 + Letras de canciones
173 + "home_new_entries": >-
174 + Nuevas entradas
175 + "home_others": >-
176 + Otras
177 + "home_others_headline": >-
178 + Mira mis otros proyectos.
179 + "home_popular": >-
180 + Popular
181 + "home_see_all": >-
182 + Ver todo
183 + "home_share_gamelan_notation_now": >-
184 + Share gending notation!
185 + "home_share_now": >-
186 + ¡Comparte tu acorde!
187 + "home_signup_now": >-
188 + ¡Regístrate ahora!
189 + "home_songs": >-
190 + Canciones
191 + "home_subheadline": >-
192 + Acordes de violão? Letra da música? Resenhas do álbum? Notação de gênero javanês?
193 + etc! Inscreva-se, contribua (adicione artistas, álbuns, letras, etc.) e ganhe
194 + %{bach_link} token de reputação.
195 + "home_youtube_karaoke": >-
196 + Youtube Karaoke
197 + "home_youtube_karaoke_headline": >-
198 + Versão de karaokê do seu videoclipe favorito do YouTube com tecnologia de IA.
199 + "invalid_login": >-
200 + Invalid email/password combination
201 + "locale_name": >-
202 + Português
203 + "login": >-
204 + Login!
205 + "login_with_facebook": >-
206 + Login with Facebook
207 + "lyric": >-
208 + Song Lyric
209 + "lyric_cannot_be_empty": >-
210 + Lyric can't be empty
211 + "my_profile": >-
212 + My Profile
213 + "my_profile_link_href": >-
214 + this secure link
215 + "my_profile_link_html": >-
216 + This is your public profile. Follow %{href} to edit your profile.
217 + "my_profile_private": >-
218 + This profile is private
219 + "name": >-
220 + Name
221 + "nav_albums": >-
222 + Álbumes
223 + "nav_artists": >-
224 + Artistas
225 + "nav_change_language": >-
226 + Mudar idioma:
227 + "nav_chords": >-
228 + Acordes de violão
229 + "nav_chords_shapes": >-
230 + Diagrama de acordes de guitarra
231 + "nav_community": >-
232 + Comunidade
233 + "nav_en": >-
234 + inglês
235 + "nav_footer_about": >-
236 + Sobre nós
237 + "nav_footer_company": >-
238 + Company
239 + "nav_footer_contact": >-
240 + Contato
241 + "nav_footer_follow_us": >-
242 + Siga-nos
243 + "nav_footer_privacy": >-
244 + Política de Privacidade
245 + "nav_footer_term": >-
246 + Termo de Uso
247 + "nav_guitar": >-
248 + Guitar
249 + "nav_id": >-
250 + Bahasa Indonesia
251 + "nav_javanese_gamelan": >-
252 + Javanese Gamelan
253 + "nav_javanese_gamelan_about": >-
254 + About Javanese Gamelan
255 + "nav_javanese_gending": >-
256 + Javanese Gending
257 + "nav_javanese_gending_notation": >-
258 + Javanese Gending Notation
259 + "nav_login": >-
260 + Login
261 + "nav_login_dropdown": >-
262 + Account
263 + "nav_logout": >-
264 + Log out
265 + "nav_lyrics": >-
266 + Song Lyrics
267 + "nav_music_database": >-
268 + Músicas
269 + "nav_new_entries": >-
270 + Novas entradas
271 + "nav_signup": >-
272 + Inscrever-se
273 + "nav_songs": >-
274 + Músicas
275 + "nav_ukulele_chords": >-
276 + Acordes de Ukulele (em breve)
277 + "need_to_logged_in": >-
278 + You need to logged in before giving this love. Don't have account yet? Just signup,
279 + it's so easy. You can use your Facebook account if you want.
280 + "need_to_logged_in_my_profile": >-
281 + You need to be logged in to perform this task.
282 + "new_user": >-
283 + New user?
284 + "or": >-
285 + Or
286 + "our_community": >-
287 + Our Community
288 + "our_guitar_chords_collection": >-
289 + Our Newest Guitar Chords Collections
290 + "our_newest_song_lyrics": >-
291 + Our Newest Song Lyrics Collection
292 + "password": >-
293 + Password
294 + "password_button_update": >-
295 + Update
296 + "password_confirmation": >-
297 + Password confirmation
298 + "password_new": >-
299 + Enter new password
300 + "password_new_confirmation": >-
301 + Confirm password
302 + "routes":
303 + "about_us": >-
304 + about-us
305 + "add_gending": >-
306 + add-gending-gamelan
307 + "add_javanese_gamelan_notation": >-
308 + add-javanese-gamelan-notation
309 + "album": >-
310 + albums
311 + "album_add": >-
312 + add-album
313 + "album_detail": >-
314 + albums
315 + "artist": >-
316 + artists
317 + "artist_add": >-
318 + add-album
319 + "artist_detail": >-
320 + artists
321 + "audio_files": >-
322 + audio-files
323 + "bass_backing_track": >-
324 + bass-backing-track-:slug
325 + "community": >-
326 + community
327 + "contact": >-
328 + contact
329 + "drum_backing_track": >-
330 + drum-backing-track-:slug
331 + "followers": >-
332 + followers
333 + "following": >-
334 + following
335 + "guitar_backing_track": >-
336 + guitar-backing-track-:slug
337 + "guitar_chord_shape": >-
338 + guitar-chord-:slug
339 + "guitar_chord_shapes": >-
340 + all-guitar-chords
341 + "guitar_chords": >-
342 + guitar-chords
343 + "guitar_chords_add": >-
344 + add-chord
345 + "guitar_chords_detail": >-
346 + guitar-chords-:slug
347 + "guitar_chords_history": >-
348 + guitar-chords-:slug/history
349 + "guitar_chords_review": >-
350 + guitar-chords-:slug/review-chord
351 + "javanese_gamelan": >-
352 + javanese-gamelan
353 + "javanese_gamelan_notation": >-
354 + javanese-gamelan
355 + "javanese_gending": >-
356 + javanese-gending
357 + "javanese_gending_detail": >-
358 + javanese-gending-gamelan-:slug
359 + "javanese_gending_gamelan_notation": >-
360 + notation-for-javanese-gamelan
361 + "javanese_gending_gamelan_notation_detail": >-
362 + notation-for-javanese-gamelan-:slug
363 + "login": >-
364 + login
365 + "most_loved_songs": >-
366 + most-loved-songs
367 + "privacy_policy": >-
368 + privacy-policy
369 + "signup": >-
370 + signup
371 + "song": >-
372 + songs
373 + "song_add": >-
374 + add-song
375 + "song_audio": >-
376 + song-audio
377 + "song_bass_tab": >-
378 + bass-tab-:slug
379 + "song_chords": >-
380 + song-chords
381 + "song_detail": >-
382 + songs
383 + "song_karaoke_detail": >-
384 + karaoke-song-:slug
385 + "song_lyrics": >-
386 + song-lyrics
387 + "song_lyrics_detail": >-
388 + song-lyrics-:slug
389 + "term_of_use": >-
390 + term-of-use
391 + "user_rep": >-
392 + reputation
393 + "see_album_detail": >-
394 + See album detail
395 + "see_artist_detail": >-
396 + See artist detail
397 + "see_chord_detail": >-
398 + See chord detail
399 + "see_song_detail": >-
400 + See song detail
401 + "signup": >-
402 + Signup
403 + "signup_now": >-
404 + Signup now!
405 + "signup_to_claim": >-
406 + Is this yours?
407 + "signup_with_facebook": >-
408 + Signup with Facebook
409 + "site_name": >-
410 + Musik88.com
411 + "somebody_to_love": >-
412 + Love me
413 + "song_audio_page_title": >-
414 + Audio files for %{song_name}
415 + "song_chord_page_title": >-
416 + Guitar chord %{song_name} by %{song_artist_name}
417 + "song_chord_unavailable_content": >-
418 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
419 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
420 + that's all we need.
421 + "song_chord_unavailable_meta_description": >-
422 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
423 + album.
424 + "song_chords_page_title": >-
425 + %{song_name} Chords
426 + "song_detail": >-
427 + Song detail
428 + "song_detail_page_meta_title": >-
429 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
430 + album/single.
431 + "song_detail_page_title": >-
432 + %{song_name} Song Review
433 + "song_generate_your_own_bass_backing_track": >-
434 + Generate %{song_name} Bass Backing Track
435 + "song_generate_your_own_drum_backing_track": >-
436 + Generate %{song_name} Drum Backing Track
437 + "song_generate_your_own_karaoke": >-
438 + Generate %{song_name} Karaoke File
439 + "song_generate_your_own_stem_files": >-
440 + Generate STEM Files For %{song_name}
441 + "song_karaoke_download": >-
442 + Download Karaoke Version
443 + "song_karaoke_page_title": >-
444 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
445 + Album
446 + "song_karaoke_title": >-
447 + %{filename} Karaoke Version
448 + "song_karaoke_unavailable_content": >-
449 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
450 + "song_karaoke_unavailable_meta_description": >-
451 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
452 + Album
453 + "song_lyric_detail": >-
454 + Song lyric %{title}
455 + "song_lyric_page_title": >-
456 + %{song_name} Lyric by %{song_artist_name}
457 + "song_lyric_unavailable_content": >-
458 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
459 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
460 + or %{href} to this song so we can just focus on finding the chords.
461 + "song_lyric_unavailable_content_add_lyric": >-
462 + Add Lyric
463 + "song_lyric_unavailable_meta_description": >-
464 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
465 + album.
466 + "song_lyrics_page_title": >-
467 + Song Lyrics %{song_name}
468 + "song_menu_aside_audio": >-
469 + Audio
470 + "song_menu_aside_chords": >-
471 + Chords
472 + "song_menu_aside_info": >-
473 + Info
474 + "song_menu_aside_lyrics": >-
475 + Lyrics
476 + "song_menu_bass_backing_track": >-
477 + Bass backing track %{song_name}
478 + "song_menu_bass_tab": >-
479 + Bass tab %{song_name}
480 + "song_menu_drum_backing_track": >-
481 + Drum backing track %{song_name}
482 + "song_menu_guitar_chord": >-
483 + Guitar Chord %{song_name}
484 + "song_menu_karaoke": >-
485 + Karaoke Version %{song_name}
486 + "song_menu_lyric": >-
487 + Song Lyric %{song_name}
488 + "song_updated_at": >-
489 + Updated: %{date}
490 + "still_login": >-
491 + Remember me
492 + "top_10_albums": >-
493 + Top 10 Albums
494 + "top_10_artists": >-
495 + Top 10 Artists
496 + "top_10_guitar_chords": >-
497 + Top 10 Guitar Chords
498 + "top_10_song_lyrics": >-
499 + Top 10 Song Lyrics
500 + "top_10_songs": >-
501 + Top 10 Songs
502 + "user_profile": >-
503 + Profile
504 + "user_reset_password_email_heading": >-
505 + Reset password link only for you, %{user_name}!
506 + "user_reset_password_email_heading_p1": >-
507 + You are getting this email because you are trying to access your account.
508 + "user_reset_password_email_heading_p2": >-
509 + Click %{href} to reset your password.
510 + "user_reset_password_email_link": >-
511 + this one time link
512 + "user_send_reset_password_instructions_subject": >-
513 + Reset password instruction
514 + "user_signup_email_heading": >-
515 + Thanks for signing up, %{user_name}!
516 + "user_signup_email_heading_p1": >-
517 + Thanks for joining us!
518 + "user_signup_email_heading_p2": >-
519 + We are in a mission to digitalize underrepresented musics in the world. %{href}
520 + and start contributing :)
521 + "user_signup_email_subject": >-
522 + Welcome to Musik88!
523 + "welcome_user": >-
524 + Welcome, %{user_name}!
backend/musik88-web/config/locales/ru.yml new
+558
@@ -0,0 +1,558 @@
1 +---
2 +"ru":
3 + "10_most_loved_songs": >-
4 + 10 Most Loved Songs
5 + "active_admin":
6 + "devise":
7 + "login":
8 + "remember_me": >-
9 + Запомните меня
10 + "submit": >-
11 + Авторизоваться
12 + "title": >-
13 + Авторизоваться
14 + "filters":
15 + "predicates":
16 + "from": >-
17 + dari tanggal
18 + "to": >-
19 + sampai tanggal
20 + "activerecord":
21 + "errors":
22 + "models":
23 + "album":
24 + "attributes":
25 + "description":
26 + "too_short": >-
27 + Deskripsi artis tidak boleh kosong
28 + "name":
29 + "too_short": >-
30 + Nama album tidak boleh kosong
31 + "artist":
32 + "attributes":
33 + "description":
34 + "too_short": >-
35 + Deskripsi artis tidak boleh kosong
36 + "name":
37 + "too_short": >-
38 + Nama artis tidak boleh kosong
39 + "chord":
40 + "attributes":
41 + "chord":
42 + "too_short": >-
43 + Chord lagu tidak boleh kosong
44 + "lyric":
45 + "attributes":
46 + "lyric":
47 + "too_short": >-
48 + Lirik lagu tidak boleh kosong
49 + "page":
50 + "attributes":
51 + "page_content":
52 + "too_short": >-
53 + Konten halaman tidak boleh kosong
54 + "page_slug":
55 + "too_short": >-
56 + Slug halaman tidak boleh kosong
57 + "song":
58 + "attributes":
59 + "album":
60 + "too_short": >-
61 + Album tidak boleh kosong
62 + "description":
63 + "too_short": >-
64 + Deskripsi lagu tidak boleh kosong
65 + "name":
66 + "too_short": >-
67 + Nama lagu tidak boleh kosong
68 + "user":
69 + "attributes":
70 + "email":
71 + "blank": >-
72 + Email tidak boleh kosong
73 + "invalid": >-
74 + Email tidak valid
75 + "name":
76 + "blank": >-
77 + Nama tidak boleh kosong
78 + "password":
79 + "blank": >-
80 + Password tidak boleh kosong
81 + "too_short": >-
82 + Password terlalu pendek
83 + "password_confirmation":
84 + "confirmation": >-
85 + Konfirmasi password tidak sama
86 + "add_chord_how_to": >-
87 + Как добавить аккорд
88 + "add_chord_how_to_content": "- Добавьте свой аккорд, записывая его построчно.\r\n-
89 + Добавьте последовательности аккордов поверх каждой строки.\r\n- При необходимости
90 + добавьте интервал и абзац.\r\n- Также добавьте раздел песни в каждой части песни."
91 + "add_chord_label_chord": >-
92 + Аккорд
93 + "add_chord_label_song": >-
94 + Прикрепить к песне..
95 + "add_chord_label_title": >-
96 + .. или добавить заголовок
97 + "add_chord_placeholder_chord": >-
98 + Поставь свой аккорд здесь...
99 + "add_chord_placeholder_chose_song": >-
100 + Выбрать песню
101 + "add_chord_placeholder_title": >-
102 + Требуется титул...
103 + "add_chord_share": >-
104 + Доля!
105 + "add_chord_title": >-
106 + Добавить аккорд
107 + "add_chord_validation": >-
108 + Нам нужно название и аккорд
109 + "add_chord_validation_success": >-
110 + Спасибо за участие!
111 + "add_chord_warning": >-
112 + Вы еще не вошли в систему. %{href_login} и заявите свой вклад, или %{href} заберет
113 + ваши аккорды и вклад.
114 + "album_artists": >-
115 + Artists
116 + "album_song_list": >-
117 + Song list
118 + "artist_profile_album_contribution_title": >-
119 + %{artist} also appears on:
120 + "artist_profile_album_title": >-
121 + %{artist}'s Albums
122 + "artist_profile_title": >-
123 + %{artist} Profile
124 + "breadcrumb_home": >-
125 + Домой
126 + "breadcrumb_most_loved_songs": >-
127 + Самые любимые песни
128 + "by": >-
129 + Loved by
130 + "cancel": >-
131 + Cancel
132 + "create_account": >-
133 + Create account
134 + "discussion": >-
135 + Discussion
136 + "email": >-
137 + Email
138 + "find_your_artist": >-
139 + Find Your Favorite Artist
140 + "find_your_favorite_album": >-
141 + Find Your New Favorite Album
142 + "find_your_favorite_songs": >-
143 + Find Your Favorite Songs
144 + "followers": >-
145 + Follower
146 + "following": >-
147 + Following
148 + "footer": >-
149 + Created for fun by %{profile} from all around the %{instagram} &middot; &copy;
150 + 2021
151 + "footer_world": >-
152 + world
153 + "forgot_password": >-
154 + Forgot password?
155 + "gending_add": >-
156 + Share your gending notation
157 + "gending_add_explanation": >-
158 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
159 + token.
160 + "gending_description": >-
161 + Description
162 + "gending_name": >-
163 + Gending Name
164 + "gending_notation": >-
165 + Gending Notation
166 + "gending_notation_add": >-
167 + Add gending notation
168 + "gending_notation_link": >-
169 + Javanese gending notation %{slug}
170 + "gending_notation_meta_desciption": >-
171 + This is the javanese gending notation for %{title} gending.
172 + "gending_notation_page_title": >-
173 + Javanese Gending Notation %{title}
174 + "guitar_chord_image": >-
175 + Guitar Chord Image
176 + "guitar_chord_link": >-
177 + Guitar chord %{slug}
178 + "guitar_chord_video": >-
179 + Guitar Chord Video
180 + "guitar_chords_shape": >-
181 + Guitar Chords Shape
182 + "h2_find_new_albums": >-
183 + Find New Albums Here!
184 + "h2_find_new_artists": >-
185 + Find New Artists Here!
186 + "h2_find_new_songs": >-
187 + Find New Musics Here!
188 + "have_account": >-
189 + Already have account?
190 + "hello": >-
191 + Welcome to Musik 88
192 + "home_guitar_chords": >-
193 + Гитарные аккорды
194 + "home_guitar_chords_headline": >-
195 + Отправьте свои гитарные аккорды и заработайте %{bach_link} жетона репутации за
196 + свой вклад.
197 + "home_headline": >-
198 + Самая большая музыкальная база данных в мире!
199 + "home_javanese_gending_notation": >-
200 + Javanese Gending Notation
201 + "home_javanese_gending_notation_subheadline": >-
202 + Оцифруйте яванский гамелан и заработайте %{bach_link} жетон репутации за свой
203 + вклад.
204 + "home_lyrics": >-
205 + Песни Текст
206 + "home_new_entries": >-
207 + Новые записи
208 + "home_others": >-
209 + Другие
210 + "home_others_headline": >-
211 + Посмотрите другие мои проекты.
212 + "home_popular": >-
213 + Популярный
214 + "home_see_all": >-
215 + Увидеть все
216 + "home_share_gamelan_notation_now": >-
217 + Share gending notation!
218 + "home_share_now": >-
219 + Поделитесь своим аккордом!
220 + "home_signup_now": >-
221 + Войти Сейчас!
222 + "home_songs": >-
223 + Песни
224 + "home_subheadline": >-
225 + Гитарные аккорды? Песни Текст? Рецензии на альбом? Яванское родовое обозначение?
226 + и т.д! Зарегистрируйтесь, внесите свой вклад (добавьте исполнителей, альбомы,
227 + тексты песен и т. д.) и заработайте %{bach_link} жетон репутации.
228 + "home_youtube_karaoke": >-
229 + YouTune Караоке
230 + "home_youtube_karaoke_headline": >-
231 + Караоке-версия вашего любимого музыкального видео на YouTube с использованием
232 + искусственного интеллекта.
233 + "invalid_login": >-
234 + Неверная комбинация электронной почты и пароля
235 + "locale_name": >-
236 + русский
237 + "login": >-
238 + Авторизоваться!
239 + "login_with_facebook": >-
240 + Войти с Facebook
241 + "lyric": >-
242 + Song Lyric
243 + "lyric_cannot_be_empty": >-
244 + Lyric can't be empty
245 + "my_profile": >-
246 + My Profile
247 + "my_profile_link_href": >-
248 + this secure link
249 + "my_profile_link_html": >-
250 + This is your public profile. Follow %{href} to edit your profile.
251 + "my_profile_private": >-
252 + This profile is private
253 + "name": >-
254 + Name
255 + "nav_albums": >-
256 + Альбомы
257 + "nav_artists": >-
258 + Художники
259 + "nav_change_language": >-
260 + Изменить язык:
261 + "nav_chords": >-
262 + Гитарные аккорды
263 + "nav_chords_shapes": >-
264 + Схема гитарных аккордов
265 + "nav_community": >-
266 + Сообщество
267 + "nav_en": >-
268 + английский
269 + "nav_footer_about": >-
270 + О нас
271 + "nav_footer_company": >-
272 + Компания
273 + "nav_footer_contact": >-
274 + Контакт
275 + "nav_footer_follow_us": >-
276 + Подписывайтесь на нас
277 + "nav_footer_privacy": >-
278 + Политика конфиденциальности
279 + "nav_footer_term": >-
280 + Условия пользования
281 + "nav_guitar": >-
282 + Гитара
283 + "nav_id": >-
284 + Бахаса Индонезия
285 + "nav_javanese_gamelan": >-
286 + Javanese Gamelan
287 + "nav_javanese_gamelan_about": >-
288 + About Javanese Gamelan
289 + "nav_javanese_gending": >-
290 + Javanese Gending
291 + "nav_javanese_gending_notation": >-
292 + Javanese Gending Notation
293 + "nav_login": >-
294 + Авторизоваться
295 + "nav_login_dropdown": >-
296 + Счет
297 + "nav_logout": >-
298 + Выйти
299 + "nav_lyrics": >-
300 + Песни Текст
301 + "nav_music_database": >-
302 + Музыка
303 + "nav_new_entries": >-
304 + Новые записи
305 + "nav_signup": >-
306 + Зарегистрироваться
307 + "nav_songs": >-
308 + Песни
309 + "nav_ukulele_chords": >-
310 + Аккорды для укулеле (скоро)
311 + "need_to_logged_in": >-
312 + You need to logged in before giving this love. Don't have account yet? Just signup,
313 + it's so easy. You can use your Facebook account if you want.
314 + "need_to_logged_in_my_profile": >-
315 + You need to be logged in to perform this task.
316 + "new_user": >-
317 + New user?
318 + "or": >-
319 + Or
320 + "our_community": >-
321 + Our Community
322 + "our_guitar_chords_collection": >-
323 + Our Newest Guitar Chords Collections
324 + "our_newest_song_lyrics": >-
325 + Our Newest Song Lyrics Collection
326 + "password": >-
327 + Password
328 + "password_button_update": >-
329 + Update
330 + "password_confirmation": >-
331 + Password confirmation
332 + "password_new": >-
333 + Enter new password
334 + "password_new_confirmation": >-
335 + Confirm password
336 + "routes":
337 + "about_us": >-
338 + about-us
339 + "add_gending": >-
340 + add-gending-gamelan
341 + "add_javanese_gamelan_notation": >-
342 + add-javanese-gamelan-notation
343 + "album": >-
344 + albums
345 + "album_add": >-
346 + add-album
347 + "album_detail": >-
348 + albums
349 + "artist": >-
350 + artists
351 + "artist_add": >-
352 + add-artist
353 + "artist_detail": >-
354 + artists
355 + "audio_files": >-
356 + audio-files
357 + "bass_backing_track": >-
358 + bass-backing-track-:slug
359 + "community": >-
360 + community
361 + "contact": >-
362 + contact
363 + "drum_backing_track": >-
364 + drum-backing-track-:slug
365 + "followers": >-
366 + followers
367 + "following": >-
368 + following
369 + "guitar_backing_track": >-
370 + guitar-backing-track-:slug
371 + "guitar_chord_shape": >-
372 + guitar-chord-:slug
373 + "guitar_chord_shapes": >-
374 + all-guitar-chords
375 + "guitar_chords": >-
376 + guitar-chords
377 + "guitar_chords_add": >-
378 + add-chord
379 + "guitar_chords_detail": >-
380 + guitar-chords-:slug
381 + "guitar_chords_history": >-
382 + guitar-chords-:slug/history
383 + "guitar_chords_review": >-
384 + guitar-chords-:slug/review-chord
385 + "javanese_gamelan": >-
386 + javanese-gamelan
387 + "javanese_gamelan_notation": >-
388 + javanese-gamelan
389 + "javanese_gending": >-
390 + javanese-gending
391 + "javanese_gending_detail": >-
392 + javanese-gending-gamelan-:slug
393 + "javanese_gending_gamelan_notation": >-
394 + notation-for-javanese-gamelan
395 + "javanese_gending_gamelan_notation_detail": >-
396 + notation-for-javanese-gamelan-:slug
397 + "login": >-
398 + авторизоваться
399 + "most_loved_songs": >-
400 + most-loved-songs
401 + "privacy_policy": >-
402 + privacy-policy
403 + "signup": >-
404 + signup
405 + "song": >-
406 + songs
407 + "song_add": >-
408 + add-song
409 + "song_audio": >-
410 + song-audio
411 + "song_bass_tab": >-
412 + bass-tab-:slug
413 + "song_chords": >-
414 + song-chords
415 + "song_detail": >-
416 + songs
417 + "song_karaoke_detail": >-
418 + karaoke-song-:slug
419 + "song_lyrics": >-
420 + song-lyrics
421 + "song_lyrics_detail": >-
422 + song-lyrics-:slug
423 + "term_of_use": >-
424 + term-of-use
425 + "user_rep": >-
426 + reputation
427 + "see_album_detail": >-
428 + See album detail
429 + "see_artist_detail": >-
430 + See artist detail
431 + "see_chord_detail": >-
432 + See chord detail
433 + "see_song_detail": >-
434 + See song detail
435 + "signup": >-
436 + Signup
437 + "signup_now": >-
438 + Signup now!
439 + "signup_to_claim": >-
440 + Is this yours?
441 + "signup_with_facebook": >-
442 + Signup with Facebook
443 + "site_name": >-
444 + Musik88.com
445 + "somebody_to_love": >-
446 + Love me
447 + "song_audio_page_title": >-
448 + Audio files for %{song_name}
449 + "song_chord_page_title": >-
450 + Guitar chord %{song_name} by %{song_artist_name}
451 + "song_chord_unavailable_content": >-
452 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
453 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
454 + that's all we need.
455 + "song_chord_unavailable_meta_description": >-
456 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
457 + album.
458 + "song_chords_page_title": >-
459 + %{song_name} Chords
460 + "song_detail": >-
461 + Song detail
462 + "song_detail_page_meta_title": >-
463 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
464 + album/single.
465 + "song_detail_page_title": >-
466 + %{song_name} Song Review
467 + "song_generate_your_own_bass_backing_track": >-
468 + Generate %{song_name} Bass Backing Track
469 + "song_generate_your_own_drum_backing_track": >-
470 + Generate %{song_name} Drum Backing Track
471 + "song_generate_your_own_karaoke": >-
472 + Generate %{song_name} Karaoke File
473 + "song_generate_your_own_stem_files": >-
474 + Generate STEM Files For %{song_name}
475 + "song_karaoke_download": >-
476 + Download Karaoke Version
477 + "song_karaoke_page_title": >-
478 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
479 + Album
480 + "song_karaoke_title": >-
481 + %{filename} Karaoke Version
482 + "song_karaoke_unavailable_content": >-
483 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
484 + "song_karaoke_unavailable_meta_description": >-
485 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
486 + Album
487 + "song_lyric_detail": >-
488 + Song lyric %{title}
489 + "song_lyric_page_title": >-
490 + %{song_name} Lyric by %{song_artist_name}
491 + "song_lyric_unavailable_content": >-
492 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
493 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
494 + or %{href} to this song so we can just focus on finding the chords.
495 + "song_lyric_unavailable_content_add_lyric": >-
496 + Добавить лирику
497 + "song_lyric_unavailable_meta_description": >-
498 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
499 + album.
500 + "song_lyrics_page_title": >-
501 + Song Lyrics %{song_name}
502 + "song_menu_aside_audio": >-
503 + Audio
504 + "song_menu_aside_chords": >-
505 + Chords
506 + "song_menu_aside_info": >-
507 + Info
508 + "song_menu_aside_lyrics": >-
509 + Lyrics
510 + "song_menu_bass_backing_track": >-
511 + Bass backing track %{song_name}
512 + "song_menu_bass_tab": >-
513 + Bass tab %{song_name}
514 + "song_menu_drum_backing_track": >-
515 + Drum backing track %{song_name}
516 + "song_menu_guitar_chord": >-
517 + Guitar Chord %{song_name}
518 + "song_menu_karaoke": >-
519 + Karaoke Version %{song_name}
520 + "song_menu_lyric": >-
521 + Song Lyric %{song_name}
522 + "song_updated_at": >-
523 + Updated: %{date}
524 + "still_login": >-
525 + Запомните меня
526 + "top_10_albums": >-
527 + Top 10 Albums
528 + "top_10_artists": >-
529 + Top 10 Artists
530 + "top_10_guitar_chords": >-
531 + Top 10 Guitar Chords
532 + "top_10_song_lyrics": >-
533 + Top 10 Song Lyrics
534 + "top_10_songs": >-
535 + Top 10 Songs
536 + "user_profile": >-
537 + Profile
538 + "user_reset_password_email_heading": >-
539 + Reset password link only for you, %{user_name}!
540 + "user_reset_password_email_heading_p1": >-
541 + You are getting this email because you are trying to access your account.
542 + "user_reset_password_email_heading_p2": >-
543 + Click %{href} to reset your password.
544 + "user_reset_password_email_link": >-
545 + this one time link
546 + "user_send_reset_password_instructions_subject": >-
547 + Reset password instruction
548 + "user_signup_email_heading": >-
549 + Thanks for signing up, %{user_name}!
550 + "user_signup_email_heading_p1": >-
551 + Thanks for joining us!
552 + "user_signup_email_heading_p2": >-
553 + We are in a mission to digitalize underrepresented musics in the world. %{href}
554 + and start contributing :)
555 + "user_signup_email_subject": >-
556 + Welcome to Musik88!
557 + "welcome_user": >-
558 + Welcome, %{user_name}!
backend/musik88-web/config/locales/sv-se.yml new
+604
@@ -0,0 +1,604 @@
1 +---
2 +"sv-se":
3 + "10_most_loved_songs": >-
4 + 10 mest älskade låtar
5 + "Artist": >-
6 + Artist
7 + "JavaneseGending": >-
8 + Javanesisk Gending
9 + "Song": >-
10 + Låt
11 + "activate_solana_wallet": >-
12 + Aktivera din plånbok
13 + "active_admin":
14 + "devise":
15 + "username":
16 + "title": >-
17 + Username
18 + "activerecord":
19 + "errors":
20 + "models":
21 + "user":
22 + "attributes":
23 + "email":
24 + "blank": >-
25 + E-post får inte vara tom
26 + "invalid": >-
27 + Ogiltig e-postadress
28 + "name":
29 + "blank": >-
30 + Namnet får inte vara tomt
31 + "password":
32 + "blank": >-
33 + Lösenordet får inte vara tomt
34 + "too_short": >-
35 + Lösenordet är för kort
36 + "password_confirmation":
37 + "confirmation": >-
38 + Bekräfta att lösenordet inte är detsamma
39 + "add_chord_how_to": >-
40 + Hur man lägger till ackord
41 + "add_chord_how_to_content": >-
42 + Lägg till ditt ackord genom att skriva ner det rad för rad. Lägg till ackordsekvenserna
43 + ovanpå varje rad. Lägg till mellanrum och stycke om det behövs. Lägg också till
44 + låtsektion (ref, vers, bridge) i varje del av låten.
45 + "add_chord_label_chord": >-
46 + Ackord
47 + "add_chord_label_song": >-
48 + Bifoga till låten..
49 + "add_chord_label_title": >-
50 + .. eller lägg till titel
51 + "add_chord_placeholder_chord": >-
52 + Lägg ditt ackord här...
53 + "add_chord_placeholder_chose_song": >-
54 + Välj låt
55 + "add_chord_placeholder_title": >-
56 + Titel krävs...
57 + "add_chord_share": >-
58 + Dela med sig!
59 + "add_chord_title": >-
60 + Lägg till ackord
61 + "add_chord_validation": >-
62 + Vi behöver titeln och ackordet
63 + "add_chord_validation_success": >-
64 + Tack för att du bidrar!
65 + "add_chord_warning": >-
66 + Du har inte loggat in än. %{href_login} och gör anspråk på ditt bidrag, eller
67 + så kommer %{href} att göra anspråk på dina ackord och bidrag.
68 + "album_artists": >-
69 + Konstnärer
70 + "artist_profile_album_contribution_title": >-
71 + %{artist} visas också på
72 + "artist_profile_album_title": >-
73 + %{artist}s Albumer
74 + "artist_profile_title": >-
75 + %{artist} Profil
76 + "bach_token_discussion": >-
77 + Vad tror du? Är du intresserad?
78 + "badges": >-
79 + Märken
80 + "breadcrumb_home": >-
81 + Starsida
82 + "breadcrumb_most_loved_songs": >-
83 + Mest älskade låtar
84 + "by": >-
85 + Älskad av
86 + "cancel": >-
87 + Avbryt
88 + "change_language": >-
89 + Ändra språk
90 + "confirm_email": >-
91 + Bekräfta din email
92 + "create_account": >-
93 + Skapa konto
94 + "devise":
95 + "failure":
96 + "unconfirmed": >-
97 + Du måste bekräfta din e-postadress innan du fortsätter.
98 + "mailer":
99 + "confirmation_instructions":
100 + "user_subject": >-
101 + E-postbekräftelse Musik88.com
102 + "registrations":
103 + "signed_up_but_unconfirmed": >-
104 + Ett meddelande med en bekräftelselänk har skickats till din e-postadress.
105 + Vänligen följ länken för att aktivera ditt konto.
106 + "discussion": >-
107 + Diskussion
108 + "discussion_comment_button": >-
109 + Skicka in
110 + "discussion_comment_placeholder": >-
111 + Skriv din kommentar här
112 + "email": >-
113 + E-post
114 + "find_your_artist": >-
115 + Hitta din favoritartist
116 + "find_your_favorite_album": >-
117 + Hitta ditt nya favoritalbum
118 + "find_your_favorite_songs": >-
119 + Hitta dina favoritlåtar
120 + "find_your_friends": >-
121 + Hitta dina vänner här...
122 + "followers": >-
123 + Följare
124 + "following": >-
125 + Följande
126 + "footer": >-
127 + Skapad för skojs skull av %{profile} från hela %{instagram} &middot; &copy; 2021
128 + "footer_world": >-
129 + världen
130 + "forgot_password": >-
131 + Glömt ditt lösenord?
132 + "friends": >-
133 + Vänner
134 + "gending_add": >-
135 + Share your gending notation
136 + "gending_add_explanation": >-
137 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} kryptovaluta.
138 + "gending_description": >-
139 + Description
140 + "gending_name": >-
141 + Gending Namn
142 + "gending_notation": >-
143 + Gending Notation
144 + "gending_notation_add": >-
145 + Lägg till könsnotation
146 + "gending_notation_add_explanation": >-
147 + Finns det en musiknotation som du vill dela med dig av? %{href} och tjäna %{bach_link}
148 + kryptovaluta.
149 + "gending_notation_link": >-
150 + Notasi gending Jawa %{slug}
151 + "gending_notation_meta_desciption": >-
152 + Här är den javanesiska gending -notationen för %{title} gending
153 + "gending_notation_page_title": >-
154 + Javanesisk musiknotation %{title}
155 + "generating_solana_wallet": >-
156 + Aktiverar din plånbok...
157 + "guitar_chord_image": >-
158 + Gitarrackord ritning
159 + "guitar_chord_link": >-
160 + Gitarr akord %{slug}
161 + "guitar_chord_video": >-
162 + Gitarrackord Videon
163 + "guitar_chords_shape": >-
164 + Gitarrkord Form
165 + "h2_find_new_albums": >-
166 + Hitta nya album här!
167 + "h2_find_new_artists": >-
168 + Hitta nya artister här!
169 + "have_account": >-
170 + Har du redan ett konto?
171 + "hello": >-
172 + Valkommen till Musik 88
173 + "home_guitar_chords": >-
174 + Gitarrackord
175 + "home_guitar_chords_headline": >-
176 + Skicka in dina gitarrackord och tjäna %{bach_link} kryptovaluta för ditt bidrag.
177 + "home_headline": >-
178 + Världens största musikdatabas!
179 + "home_javanese_gending_notation": >-
180 + Javanese Gending Notation
181 + "home_javanese_gending_notation_subheadline": >-
182 + Digitalize Javanese gamelan and earn %{bach_link} kryptovaluta for your contribution.
183 + "home_lyrics": >-
184 + Låttexter
185 + "home_new_entries": >-
186 + New Entries
187 + "home_others": >-
188 + Andra
189 + "home_others_headline": >-
190 + Kolla in mina andra projekt.
191 + "home_popular": >-
192 + Popular
193 + "home_see_all": >-
194 + Se alla
195 + "home_share_gamelan_notation_now": >-
196 + Bagikan notasi gending!
197 + "home_share_now": >-
198 + Dela ditt ackord!
199 + "home_signup_now": >-
200 + Bli Medlem nu!
201 + "home_subheadline": >-
202 + Gitarrackord? Låttexter? Albumrecensioner? Javanesisk gending notation? etc! Registrera
203 + dig, bidra (lägg till artister, album, texter, etc) och tjäna %{bach_link} kryptovaluta.
204 + "home_what_is": >-
205 + Vad är Musik88?
206 + "home_youtube_karaoke": >-
207 + Youtube Karaoke
208 + "home_youtube_karaoke_headline": >-
209 + Karaokeversion av din favoritmusikvideo på YouTube som drivs av AI.
210 + "invalid_login": >-
211 + Ogiltig kombination av e-post / lösenord
212 + "join_our_chord_community": >-
213 + Gå med i vår community för att se %{song_name} ackord. Du kan också tjäna €BACH-kryptovaluta
214 + genom att aktivera din plånbok i Profil -> Inställningar.
215 + "join_our_javanese_community": >-
216 + Gå med i vår community för att se %{gending_name} notation. Du kan också tjäna
217 + €BACH-kryptovaluta genom att aktivera din plånbok i Profil -> Inställningar.
218 + "join_our_lyric_community": >-
219 + Gå med i vår community för att se %{song_name}-text från %{song_artist_name}.
220 + Du kan också tjäna €BACH-kryptovaluta genom att aktivera din plånbok i Profil
221 + -> Inställningar.
222 + "legacy_view": >-
223 + Gammal design
224 + "like_this": >-
225 + gillar det
226 + "locale_name": "\U0001F1F8\U0001F1EA Svenska"
227 + "login": >-
228 + Logga in!
229 + "login_with_facebook": >-
230 + Logga in med Facebook
231 + "lyric": >-
232 + Sångtext
233 + "lyric_cannot_be_empty": >-
234 + Sångtext kan inte vara tom
235 + "my_profile": >-
236 + Min profil
237 + "my_profile_link_href": >-
238 + denna säkra länk
239 + "my_profile_link_html": >-
240 + Detta är din offentliga profil. Följ %{href} för att redigera din profil.
241 + "my_profile_private": >-
242 + Denna profil är privat
243 + "name": >-
244 + Namn
245 + "nav_albums": >-
246 + Album
247 + "nav_artists": >-
248 + Artister
249 + "nav_buy_bach": >-
250 + Köp €BACH
251 + "nav_change_language": >-
252 + Ändra Språk:
253 + "nav_chords": >-
254 + Gitarr ackord
255 + "nav_chords_shapes": >-
256 + Gitarr Ackord Diagram
257 + "nav_community": >-
258 + Gemenskap
259 + "nav_en": >-
260 + English
261 + "nav_footer_about": >-
262 + Om Oss
263 + "nav_footer_company": >-
264 + Företag
265 + "nav_footer_contact": >-
266 + Kontakt
267 + "nav_footer_follow_us": >-
268 + Följ oss
269 + "nav_footer_privacy": >-
270 + Personuppgiftspolicy
271 + "nav_footer_term": >-
272 + Användarvillkor
273 + "nav_guitar": >-
274 + Gitarr
275 + "nav_id": >-
276 + Bahasa Indonesia
277 + "nav_javanese_gamelan": >-
278 + Javanesisk Gamelan
279 + "nav_javanese_gamelan_about": >-
280 + Om Javanesisk Gamelan
281 + "nav_javanese_gending": >-
282 + Javanesisk Gending
283 + "nav_javanese_gending_notation": >-
284 + Javanesisk Gending Notation
285 + "nav_login": >-
286 + Logga in
287 + "nav_login_dropdown": >-
288 + Konto
289 + "nav_logout": >-
290 + Logga ut
291 + "nav_lyrics": >-
292 + Låttexter
293 + "nav_music_database": >-
294 + Musik
295 + "nav_new_entries": >-
296 + Nya Poster
297 + "nav_signup": >-
298 + Bli Medlem
299 + "nav_songs": >-
300 + Låtar
301 + "nav_ukulele_chords": >-
302 + Ukulele Ackord (snart)
303 + "nav_what_is_this": >-
304 + Vad är detta?
305 + "need_to_logged_in": >-
306 + Du måste logga in innan du ger denna kärlek. Har du inget konto än? Registrera
307 + dig bara, det är så enkelt. Du kan använda ditt Facebook -konto om du vill.
308 + "need_to_logged_in_my_profile": >-
309 + You need to be logged in to perform this task.
310 + "new_user": >-
311 + Ny användare?
312 + "new_view": >-
313 + Ny design
314 + "oh_hi": >-
315 + Oh, hej!
316 + "or": >-
317 + Eller
318 + "our_community": >-
319 + Vår gemenskap
320 + "our_guitar_chords_collection": >-
321 + Våra nyaste gitarrkordsamlingar
322 + "our_newest_song_lyrics": >-
323 + Vår Nyaste Song Lyrics Collection
324 + "password": >-
325 + Lösenord
326 + "password_confirmation": >-
327 + Bekräfta lösenord
328 + "password_new_confirmation": >-
329 + Bekräfta lösenord
330 + "reputation": >-
331 + Rykte
332 + "resend_email_confirmation": >-
333 + Skicka bekräftelseinstruktioner igen
334 + "resend_email_confirmation_button": >-
335 + Skicka instruktioner igen
336 + "routes":
337 + "about_us": >-
338 + om-oss
339 + "add_gending": >-
340 + lägg-till-gending-gamelan
341 + "add_javanese_gamelan_notation": >-
342 + lägg-till-javanesisk-gamelan-notation
343 + "album": >-
344 + musikalbum
345 + "album_add": >-
346 + add-album
347 + "album_bridge": >-
348 + albums-bridge
349 + "album_detail": >-
350 + musikalbum
351 + "artist": >-
352 + artister
353 + "artist_add": >-
354 + add-artist
355 + "artist_bridge": >-
356 + artists-bridge
357 + "artist_detail": >-
358 + artister
359 + "audio_files": >-
360 + audio-files
361 + "bass_backing_track": >-
362 + bass-backing-track-:slug
363 + "community": >-
364 + gemenskap
365 + "confirm_email": >-
366 + bekräftelsemail
367 + "contact": >-
368 + kontakt
369 + "drum_backing_track": >-
370 + drum-backing-track-:slug
371 + "followers": >-
372 + följare
373 + "following": >-
374 + följande
375 + "guitar_backing_track": >-
376 + guitar-backing-track-:slug
377 + "guitar_chord_shape": >-
378 + ackord-gitarr-:slug
379 + "guitar_chord_shapes": >-
380 + alla-gitarr-ackord
381 + "guitar_chords": >-
382 + gitarr-ackord
383 + "guitar_chords_add": >-
384 + add-ackord
385 + "guitar_chords_detail": >-
386 + gitarr-ackord-:slug
387 + "guitar_chords_history": >-
388 + gitarr-ackord-:slug/historia
389 + "guitar_chords_review": >-
390 + gitarr-ackord-:slug/review-ackord
391 + "javanese_gamelan": >-
392 + javanesisk-gamelan
393 + "javanese_gamelan_notation": >-
394 + javanesisk-gamelan
395 + "javanese_gending": >-
396 + javanesisk-gending
397 + "javanese_gending_detail": >-
398 + javanesisk-gending-gamelan-:slug
399 + "javanese_gending_gamelan_notation": >-
400 + notation-av-javanesisk-gending-gamelan
401 + "javanese_gending_gamelan_notation_detail": >-
402 + notation-av-javanesisk-gending-gamelan-:slug
403 + "javanese_gending_gamelan_notation_detail_new": >-
404 + gending-notation-standard-:slug
405 + "login": >-
406 + logga-in
407 + "most_loved_songs": >-
408 + mest-älskade-låtar
409 + "privacy_policy": >-
410 + personuppgiftspolicy
411 + "signup": >-
412 + bli-medlem
413 + "song": >-
414 + låtar
415 + "song_add": >-
416 + add-song
417 + "song_audio": >-
418 + song-audio
419 + "song_bass_tab": >-
420 + bass-tab-:slug
421 + "song_bridge": >-
422 + songs-bridge
423 + "song_chords": >-
424 + låtar-ackord
425 + "song_detail": >-
426 + låtar
427 + "song_karaoke_detail": >-
428 + sjung-karaoke-låt-:slug
429 + "song_lyrics": >-
430 + låttexter
431 + "song_lyrics_detail": >-
432 + låttexter-:slug
433 + "term_of_use": >-
434 + anvandarvillkor
435 + "user_rep": >-
436 + reputation
437 + "what_is_this": >-
438 + vad-är-det
439 + "what_is_this_detail": >-
440 + vad-är-:slug
441 + "search": >-
442 + Sök
443 + "search_placeholder_title": >-
444 + Sök efter artist, låt eller javanesisk gending...
445 + "see_album_detail": >-
446 + Se detaljalbum
447 + "see_artist_detail": >-
448 + Se detaljartist
449 + "see_chord_detail": >-
450 + Se detailjkord
451 + "see_song_detail": >-
452 + Se detaljlåt
453 + "settings": >-
454 + Inställningar
455 + "signup": >-
456 + Bli Medlem
457 + "signup_now": >-
458 + Anmäl dig nu!
459 + "signup_to_claim": >-
460 + Är den här din?
461 + "signup_with_facebook": >-
462 + Skappa konto med Facebook
463 + "site_name": >-
464 + Musik88.com
465 + "somebody_to_love": >-
466 + Älska mig
467 + "song_audio_page_title": >-
468 + Fil ljud till %{song_name}
469 + "song_chord_page_title": >-
470 + Gitarr Ackord %{song_name}
471 + "song_chord_unavailable_content": >-
472 + <span class='song-lyric-unavailable'>%{song_name} gitarrkord från %{song_artist_name}</span>
473 + är ännu inte tillgänglig. Ger den här låten minst 2 kärlekar <span class = 'glyphicon
474 + glyphicon-heart'> </span>, det är allt vi behöver.
475 + "song_chord_unavailable_meta_description": >-
476 + De är %{song_name} gitarrkord av %{song_artist_name} från %{song_album_name} albumet.
477 + "song_chords_page_title": >-
478 + %{song_name} Gitarr Ackord
479 + "song_detail": >-
480 + Låtdetaljer
481 + "song_detail_page_meta_title": >-
482 + %{song_name} - %{song_artist_name} recension, tar fran albumet %{song_album_name}
483 + "song_detail_page_title": >-
484 + %{song_name} Recension
485 + "song_generate_your_own_bass_backing_track": >-
486 + Generate %{song_name} Bass Backing Track
487 + "song_generate_your_own_drum_backing_track": >-
488 + Generate %{song_name} Drum Backing Track
489 + "song_generate_your_own_karaoke": >-
490 + Skapa %{song_name} karaokefil
491 + "song_generate_your_own_stem_files": >-
492 + Generate STEM Files For %{song_name}
493 + "song_karaoke_download": >-
494 + Download Karaoke Version
495 + "song_karaoke_page_title": >-
496 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
497 + Album
498 + "song_karaoke_title": >-
499 + %{filename} Karaoke version
500 + "song_karaoke_unavailable_content": >-
501 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
502 + "song_karaoke_unavailable_meta_description": >-
503 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
504 + Album
505 + "song_lyric_detail": >-
506 + Låttext %{title}
507 + "song_lyric_page_title": >-
508 + %{song_name} Sångtext av %{song_artist_name}
509 + "song_lyric_unavailable_content": >-
510 + <span class='song-lyric-unavailable'>%{song_name} sångtexten från %{song_artist_name}</span>
511 + är ännu inte tillgänglig. Ger den här låten minst 2 kärlekar <span class = 'glyphicon
512 + glyphicon-heart'> </span> eller %{href} så vi kan bara fokusera på att hitta ackorden.
513 + "song_lyric_unavailable_content_add_lyric": >-
514 + Lägg till Sångtext
515 + "song_lyric_unavailable_meta_description": >-
516 + De är %{song_name} sångtext av %{song_artist_name} från %{song_album_name} albumet.
517 + "song_lyrics_page_title": >-
518 + Låttext %{song_name}
519 + "song_menu_aside_audio": >-
520 + Audio
521 + "song_menu_aside_chords": >-
522 + Ackord
523 + "song_menu_aside_info": >-
524 + Info
525 + "song_menu_aside_lyrics": >-
526 + Text
527 + "song_menu_bass_backing_track": >-
528 + Bass backing track %{song_name}
529 + "song_menu_bass_tab": >-
530 + Bass tab %{song_name}
531 + "song_menu_drum_backing_track": >-
532 + Drum backing track %{song_name}
533 + "song_menu_guitar_chord": >-
534 + Gitarrkord %{song_name}
535 + "song_menu_karaoke": >-
536 + Karaoke Version %{song_name}
537 + "song_menu_lyric": >-
538 + Låttext %{song_name}
539 + "song_updated_at": >-
540 + Uppdaterad: %{date}
541 + "still_login": >-
542 + Kom ihåg mig
543 + "top_10_albums": >-
544 + Topp 10 Album
545 + "top_10_artists": >-
546 + Topp 10 Artister
547 + "top_10_guitar_chords": >-
548 + Topp 10 gitarrackord
549 + "top_10_song_lyrics": >-
550 + Top 10 sångtext
551 + "top_10_songs": >-
552 + Topp 10 Låtar
553 + "user_email_confirmation_content": >-
554 + Du kan bekräfta ditt kontos e-postadress via länken nedan:
555 + "user_email_confirmation_cta": >-
556 + Bekräfta mitt konto
557 + "user_email_confirmation_header": >-
558 + Välkommen, %{email}!
559 + "user_email_with_love": >-
560 + Med ❤️ från Stockholm,
561 + "user_notifier_projects": >-
562 + Kolla in våra andra projekt
563 + "user_profile": >-
564 + Profil
565 + "user_reset_password_email_heading": >-
566 + Återställ lösenordslänk endast för dig, %{user_name}!
567 + "user_reset_password_email_heading_p1": >-
568 + Du får det här e-postmeddelandet eftersom du försöker komma åt ditt konto.
569 + "user_reset_password_email_heading_p2": >-
570 + Klicka på %{href} för att återställa ditt lösenord.
571 + "user_reset_password_email_link": >-
572 + denna engångslänk
573 + "user_send_reset_password_instructions_subject": >-
574 + Återställ lösenordsinstruktioner
575 + "user_send_wallet_activation_notification_address": >-
576 + Din plånboksadress
577 + "user_send_wallet_activation_notification_heading": >-
578 + Grattis, %{user_name}. Du har nu en ny kryptoplånbok!
579 + "user_send_wallet_activation_notification_p1": >-
580 + Din BACH kryptovaluta lagras i din plånbok. Du kan se ditt saldo i länken nedan.
581 + "user_send_wallet_activation_notification_subject": >-
582 + Du har en ny plånbok!
583 + "user_signup_email_heading": >-
584 + Tack för att du registrerade dig, %{user_name}!
585 + "user_signup_email_heading_p1": >-
586 + Tack för att du är med!
587 + "user_signup_email_heading_p2": >-
588 + Vi har ett uppdrag att digitalisera underrepresenterad musik i världen. %{href}
589 + och börja bidra :)
590 + "user_signup_email_subject": >-
591 + Välkommen till Musik88!
592 + "welcome_user": >-
593 + Valkommen, %{user_name}!
594 + "welcome_user_unconfirmed": >-
595 + Valkommen! Öppna din e-post för att bekräfta ditt konto.
596 + "your_locale": >-
597 + Språk
598 + "your_locale_explanation": >-
599 + Dina språkinställningar för denna webbplats
600 + "your_wallet_address": >-
601 + Din plånboksadress
602 + "your_wallet_address_explanation": >-
603 + Vi lagrar ditt kryptovaluta i din plånboksadress. Du kan köpa, sälja och satsa
604 + (kommer snart) ditt kryptovaluta på en öppen marknad, till exempel %{href}.
backend/musik88-web/config/locales/sv.yml new
+7
@@ -0,0 +1,7 @@
1 +---
2 +"sv":
3 + "nav_buy_bach": >-
4 + Köp €BACH
5 + "routes":
6 + "album_bridge": >-
7 + albums-bridge
backend/musik88-web/config/locales/zh-cn.yml new
+415
@@ -0,0 +1,415 @@
1 +---
2 +"zh-cn":
3 + "10_most_loved_songs": >-
4 + 10 首最受喜爱的歌曲
5 + "add_chord_validation_success": >-
6 + 感谢您的贡献!
7 + "breadcrumb_home": >-
8 + 家
9 + "breadcrumb_most_loved_songs": >-
10 + 最喜欢的歌曲
11 + "by": >-
12 + 被爱
13 + "cancel": >-
14 + 取消
15 + "confirm_email": >-
16 + 确认您的电子邮件
17 + "create_account": >-
18 + 创建账户
19 + "email": >-
20 + 电子邮件
21 + "find_your_artist": >-
22 + 找到你最喜欢的艺术家
23 + "find_your_favorite_album": >-
24 + 找到你最喜欢的新专辑
25 + "find_your_favorite_songs": >-
26 + Find Your Favorite Songs
27 + "followers": >-
28 + Follower
29 + "following": >-
30 + Following
31 + "footer": >-
32 + 由来自 %{instagram} 的 %{profile} 为乐趣而创建 &middot; &copy; 2021
33 + "footer_world": >-
34 + 世界
35 + "forgot_password": >-
36 + 忘记密码?
37 + "gending_add": >-
38 + 分享您的性别符号
39 + "gending_add_explanation": >-
40 + Help us digitalize Javanese gamelan online. %{href} and earn %{bach_link} reputation
41 + token.
42 + "gending_description": >-
43 + 描述
44 + "gending_name": >-
45 + Gending Name
46 + "gending_notation": >-
47 + Gending Notation
48 + "gending_notation_add": >-
49 + Add gending notation
50 + "gending_notation_link": >-
51 + Javanese gending notation %{slug}
52 + "gending_notation_meta_desciption": >-
53 + This is the javanese gending notation for %{title} gending.
54 + "gending_notation_page_title": >-
55 + Javanese Gending Notation %{title}
56 + "guitar_chord_image": >-
57 + 吉他和弦图像
58 + "guitar_chord_link": >-
59 + Guitar chord %{slug}
60 + "guitar_chord_video": >-
61 + 吉他和弦视频
62 + "guitar_chords_shape": >-
63 + 吉他和弦形状
64 + "have_account": >-
65 + Already have account?
66 + "hello": >-
67 + Welcome to Musik 88
68 + "home_guitar_chords": >-
69 + 吉他和弦
70 + "home_guitar_chords_headline": >-
71 + 提交您的吉他和弦并为您的贡献赚取 %{bach_link} 声誉令牌。
72 + "home_headline": >-
73 + 世界上最大的音乐数据库!
74 + "home_javanese_gending_notation": >-
75 + 爪哇语性别符号
76 + "home_javanese_gending_notation_subheadline": >-
77 + 数字化爪哇甘麦兰并为您的贡献赚取 %{bach_link} 声誉令牌。
78 + "home_new_entries": >-
79 + 新条目
80 + "home_others": >-
81 + 其他
82 + "home_others_headline": >-
83 + 看看我的其他项目。
84 + "home_popular": >-
85 + 受欢迎的
86 + "home_see_all": >-
87 + 看到所有
88 + "home_share_gamelan_notation_now": >-
89 + 分享性别符号!
90 + "home_share_now": >-
91 + 分享你的和弦!
92 + "home_signup_now": >-
93 + 立即注册!
94 + "home_subheadline": >-
95 + 吉他和弦? 歌曲歌词? 专辑评论? 爪哇性别符号? 等等! 注册、贡献(添加艺术家、专辑、歌词等),并获得 %{bach_link} 声誉令牌。
96 + "home_youtube_karaoke": >-
97 + Youtube Karaoke
98 + "home_youtube_karaoke_headline": >-
99 + 由 AI 提供支持的您最喜爱的 YouTube 音乐视频的卡拉 OK 版本。
100 + "invalid_login": >-
101 + Invalid email/password combination
102 + "locale_name": "\U0001F1E8\U0001F1F3 中国"
103 + "login": >-
104 + Login!
105 + "login_with_facebook": >-
106 + Login with Facebook
107 + "lyric": >-
108 + Song Lyric
109 + "lyric_cannot_be_empty": >-
110 + Lyric can't be empty
111 + "my_profile": >-
112 + My Profile
113 + "my_profile_link_href": >-
114 + this secure link
115 + "my_profile_link_html": >-
116 + This is your public profile. Follow %{href} to edit your profile.
117 + "my_profile_private": >-
118 + This profile is private
119 + "name": >-
120 + Name
121 + "nav_albums": >-
122 + Albums
123 + "nav_artists": >-
124 + Artists
125 + "nav_change_language": >-
126 + Change Language:
127 + "nav_chords": >-
128 + Guitar Chords
129 + "nav_chords_shapes": >-
130 + Guitar Chords Diagram
131 + "nav_community": >-
132 + Community
133 + "nav_en": >-
134 + English
135 + "nav_footer_about": >-
136 + About Us
137 + "nav_footer_company": >-
138 + Company
139 + "nav_footer_contact": >-
140 + Contact
141 + "nav_footer_follow_us": >-
142 + Follow Us
143 + "nav_footer_privacy": >-
144 + Privacy Policy
145 + "nav_footer_term": >-
146 + Term of Use
147 + "nav_guitar": >-
148 + Guitar
149 + "nav_id": >-
150 + Bahasa Indonesia
151 + "nav_javanese_gamelan": >-
152 + Javanese Gamelan
153 + "nav_javanese_gamelan_about": >-
154 + About Javanese Gamelan
155 + "nav_javanese_gending": >-
156 + Javanese Gending
157 + "nav_javanese_gending_notation": >-
158 + Javanese Gending Notation
159 + "nav_login": >-
160 + Login
161 + "nav_login_dropdown": >-
162 + Account
163 + "nav_logout": >-
164 + Log out
165 + "nav_lyrics": >-
166 + Song Lyrics
167 + "nav_music_database": >-
168 + Musics
169 + "nav_new_entries": >-
170 + New Entries
171 + "nav_songs": >-
172 + Songs
173 + "nav_ukulele_chords": >-
174 + Ukulele Chords (soon)
175 + "need_to_logged_in": >-
176 + 您需要先登录,然后才能给予此爱。 还没有账号? 只需注册,就这么简单。 如果需要,您可以使用您的 Facebook 帐户。
177 + "need_to_logged_in_my_profile": >-
178 + 您需要登录才能执行此任务。
179 + "new_user": >-
180 + New user?
181 + "or": >-
182 + Or
183 + "our_community": >-
184 + Our Community
185 + "our_guitar_chords_collection": >-
186 + Our Newest Guitar Chords Collections
187 + "our_newest_song_lyrics": >-
188 + Our Newest Song Lyrics Collection
189 + "password": >-
190 + Password
191 + "password_button_update": >-
192 + Update
193 + "password_confirmation": >-
194 + Password confirmation
195 + "password_new": >-
196 + Enter new password
197 + "password_new_confirmation": >-
198 + Confirm password
199 + "routes":
200 + "about_us": >-
201 + about-us
202 + "add_gending": >-
203 + add-gending-gamelan
204 + "add_javanese_gamelan_notation": >-
205 + add-javanese-gamelan-notation
206 + "album": >-
207 + albums
208 + "album_add": >-
209 + add-album
210 + "album_detail": >-
211 + albums
212 + "artist": >-
213 + artists
214 + "artist_add": >-
215 + add-artist
216 + "artist_detail": >-
217 + artists
218 + "audio_files": >-
219 + audio-files
220 + "bass_backing_track": >-
221 + bass-backing-track-:slug
222 + "community": >-
223 + community
224 + "contact": >-
225 + contact
226 + "drum_backing_track": >-
227 + drum-backing-track-:slug
228 + "followers": >-
229 + followers
230 + "following": >-
231 + following
232 + "guitar_backing_track": >-
233 + guitar-backing-track-:slug
234 + "guitar_chord_shape": >-
235 + guitar-chord-:slug
236 + "guitar_chord_shapes": >-
237 + all-guitar-chords
238 + "guitar_chords": >-
239 + guitar-chords
240 + "guitar_chords_add": >-
241 + add-chord
242 + "guitar_chords_detail": >-
243 + guitar-chords-:slug
244 + "guitar_chords_history": >-
245 + guitar-chords-:slug/history
246 + "guitar_chords_review": >-
247 + guitar-chords-:slug/review-chord
248 + "javanese_gamelan": >-
249 + javanese-gamelan
250 + "javanese_gamelan_notation": >-
251 + javanese-gamelan
252 + "javanese_gending": >-
253 + javanese-gending
254 + "javanese_gending_detail": >-
255 + javanese-gending-gamelan-:slug
256 + "javanese_gending_gamelan_notation": >-
257 + notation-for-javanese-gamelan
258 + "javanese_gending_gamelan_notation_detail": >-
259 + notation-for-javanese-gamelan-:slug
260 + "login": >-
261 + 登录
262 + "most_loved_songs": >-
263 + most-loved-songs
264 + "privacy_policy": >-
265 + privacy-policy
266 + "signup": >-
267 + signup
268 + "song": >-
269 + songs
270 + "song_add": >-
271 + add-song
272 + "song_audio": >-
273 + song-audio
274 + "song_bass_tab": >-
275 + bass-tab-:slug
276 + "song_chords": >-
277 + song-chords
278 + "song_detail": >-
279 + songs
280 + "song_karaoke_detail": >-
281 + karaoke-song-:slug
282 + "song_lyrics": >-
283 + song-lyrics
284 + "song_lyrics_detail": >-
285 + song-lyrics-:slug
286 + "term_of_use": >-
287 + term-of-use
288 + "user_rep": >-
289 + reputation
290 + "see_album_detail": >-
291 + See album detail
292 + "see_artist_detail": >-
293 + See artist detail
294 + "see_chord_detail": >-
295 + See chord detail
296 + "see_song_detail": >-
297 + See song detail
298 + "signup": >-
299 + Signup
300 + "signup_now": >-
301 + Signup now!
302 + "signup_to_claim": >-
303 + Is this yours?
304 + "signup_with_facebook": >-
305 + Signup with Facebook
306 + "site_name": >-
307 + Musik88.com
308 + "somebody_to_love": >-
309 + Love me
310 + "song_audio_page_title": >-
311 + Audio files for %{song_name}
312 + "song_chord_page_title": >-
313 + Guitar chord %{song_name} by %{song_artist_name}
314 + "song_chord_unavailable_content": >-
315 + The <span class='song-lyric-unavailable'>%{song_name} chord from %{song_artist_name}</span>
316 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>,
317 + that's all we need.
318 + "song_chord_unavailable_meta_description": >-
319 + Here's the %{song_name} chord from %{song_artist_name} taken from the %{song_album_name}
320 + album.
321 + "song_chords_page_title": >-
322 + %{song_name} Chords
323 + "song_detail": >-
324 + Song detail
325 + "song_detail_page_meta_title": >-
326 + %{song_name} from %{song_artist_name} song review, from the %{song_album_name}
327 + album/single.
328 + "song_detail_page_title": >-
329 + %{song_name} Song Review
330 + "song_generate_your_own_bass_backing_track": >-
331 + Generate %{song_name} Bass Backing Track
332 + "song_generate_your_own_drum_backing_track": >-
333 + Generate %{song_name} Drum Backing Track
334 + "song_generate_your_own_karaoke": >-
335 + Generate %{song_name} Karaoke File
336 + "song_generate_your_own_stem_files": >-
337 + Generate STEM Files For %{song_name}
338 + "song_karaoke_download": >-
339 + Download Karaoke Version
340 + "song_karaoke_page_title": >-
341 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
342 + Album
343 + "song_karaoke_title": >-
344 + %{filename} Karaoke Version
345 + "song_karaoke_unavailable_content": >-
346 + The %{song_name} karaoke version from %{song_artist_name} is not available yet.
347 + "song_karaoke_unavailable_meta_description": >-
348 + Download %{song_name} Karaoke Version From %{song_artist_name} From %{song_album_name}
349 + Album
350 + "song_lyric_page_title": >-
351 + %{song_name} Lyric by %{song_artist_name}
352 + "song_lyric_unavailable_content": >-
353 + The <span class='song-lyric-unavailable'>%{song_name} lyric from %{song_artist_name}</span>
354 + isn't yet avaiable. Gives this song at least 2 loves <span class='glyphicon glyphicon-heart'></span>
355 + or %{href} to this song so we can just focus on finding the chords.
356 + "song_lyric_unavailable_content_add_lyric": >-
357 + Add Lyric
358 + "song_lyric_unavailable_meta_description": >-
359 + Here's the %{song_name} lyric from %{song_artist_name} taken from the %{song_album_name}
360 + album.
361 + "song_menu_aside_audio": >-
362 + Audio
363 + "song_menu_aside_chords": >-
364 + Chords
365 + "song_menu_aside_info": >-
366 + Info
367 + "song_menu_bass_backing_track": >-
368 + Bass backing track %{song_name}
369 + "song_menu_bass_tab": >-
370 + Bass tab %{song_name}
371 + "song_menu_drum_backing_track": >-
372 + Drum backing track %{song_name}
373 + "song_menu_guitar_chord": >-
374 + Guitar Chord %{song_name}
375 + "song_menu_karaoke": >-
376 + Karaoke Version %{song_name}
377 + "song_menu_lyric": >-
378 + Song Lyric %{song_name}
379 + "song_updated_at": >-
380 + Updated: %{date}
381 + "still_login": >-
382 + 记得我
383 + "top_10_albums": >-
384 + Top 10 Albums
385 + "top_10_artists": >-
386 + Top 10 Artists
387 + "top_10_guitar_chords": >-
388 + Top 10 Guitar Chords
389 + "top_10_song_lyrics": >-
390 + Top 10 Song Lyrics
391 + "top_10_songs": >-
392 + Top 10 Songs
393 + "user_profile": >-
394 + Profile
395 + "user_reset_password_email_heading": >-
396 + Reset password link only for you, %{user_name}!
397 + "user_reset_password_email_heading_p1": >-
398 + You are getting this email because you are trying to access your account.
399 + "user_reset_password_email_heading_p2": >-
400 + Click %{href} to reset your password.
401 + "user_reset_password_email_link": >-
402 + this one time link
403 + "user_send_reset_password_instructions_subject": >-
404 + Reset password instruction
405 + "user_signup_email_heading": >-
406 + Thanks for signing up, %{user_name}!
407 + "user_signup_email_heading_p1": >-
408 + Thanks for joining us!
409 + "user_signup_email_heading_p2": >-
410 + We are in a mission to digitalize underrepresented musics in the world. %{href}
411 + and start contributing :)
412 + "user_signup_email_subject": >-
413 + Welcome to Musik88!
414 + "welcome_user": >-
415 + Welcome, %{user_name}!
backend/musik88-web/config/puma.rb new
+26
@@ -0,0 +1,26 @@
1 +workers Integer(ENV['WEB_CONCURRENCY'] || 2)
2 +threads_count = Integer(ENV['MAX_THREADS'] || 5)
3 +threads threads_count, threads_count
4 +
5 +preload_app!
6 +
7 +rackup DefaultRackup
8 +port ENV['PORT'] || 3000 # Don't change this since the nginx use it on production.
9 +environment ENV['RAILS_ENV'] || 'development'
10 +
11 +if ENV['RAILS_ENV'] == 'development'
12 + localhost_key = File.join('setup', 'certificates', 'localhost-key.pem').to_s
13 + localhost_crt = File.join('setup', 'certificates', 'localhost.pem').to_s
14 + # To be able to use rake etc
15 + ssl_bind '0.0.0.0', 3001, {
16 + key: localhost_key,
17 + cert: localhost_crt,
18 + verify_mode: 'none'
19 + }
20 +end
21 +
22 +on_worker_boot do
23 + # Worker specific setup for Rails 4.1+
24 + # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
25 + ActiveRecord::Base.establish_connection
26 +end
backend/musik88-web/config/routes.rb new
+37
@@ -0,0 +1,37 @@
1 +Rails.application.routes.draw do
2 + require 'sidekiq/web'
3 + require 'api_constraints'
4 +
5 + # TODO: Authenticated
6 + mount Tolk::Engine => '/sloth', :as => 'tolk'
7 + mount Sidekiq::Web => '/c2lkZWtpcQ=='
8 + mount ActionCable.server => '/cable'
9 + mount Split::Dashboard, at: '/hatch'
10 + Split::Dashboard.use Rack::Auth::Basic do |username, password|
11 + # Protect against timing attacks:
12 + # - Use & (do not use &&) so that it doesn't short circuit.
13 + # - Use digests to stop length information leaking
14 + ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username),
15 + ::Digest::SHA256.hexdigest(ENV['SPLIT_USERNAME'])) &
16 + ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password),
17 + ::Digest::SHA256.hexdigest(ENV['SPLIT_PASSWORD']))
18 + end
19 +
20 + # Use a single User model for both ActiveAdmin and application frontend.
21 + ActiveAdmin.routes(self)
22 +
23 + devise_for :users, controllers: {
24 + sessions: 'sessions',
25 + omniauth_callbacks: 'users/omniauth_callbacks',
26 + confirmations: 'confirmations',
27 + passwords: 'passwords'
28 + }
29 +
30 + delete 'logout', to: 'sessions#destroy'
31 + get 'sitemap', to: 'about#sitemap'
32 + match '/404', to: 'errors#not_found', via: :all
33 + match '/500', to: 'errors#internal_server_error', via: :all
34 +
35 + draw(:api)
36 + draw(:localized)
37 +end
backend/musik88-web/config/routes.rb.save new
+58
@@ -0,0 +1,58 @@
1 +Rails.application.routes.draw do
2 + # The priority is based upon order of creation: first created -> highest priority.
3 + # See how all your routes lay out with "rake routes".
4 +
5 + # You can have the root of your site routed with "root"
6 + # root 'welcome#index'
7 +
8 + # Example of regular route:
9 + # get 'products/:id' => 'catalog#view'
10 +
11 + # Example of named route that can be invoked with purchase_url(id: product.id)
12 + # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
13 +
14 + # Example resource route (maps HTTP verbs to controller actions automatically):
15 + # resources :products
16 +
17 + # Example resource route with options:
18 + # resources :products do
19 + # member do
20 + # get 'short'
21 + # post 'toggle'
22 + # end
23 + #
24 + # collection do
25 + # get 'sold'
26 + # end
27 + # end
28 +
29 + # Example resource route with sub-resources:
30 + # resources :products do
31 + # resources :comments, :sales
32 + # resource :seller
33 + # end
34 +
35 + # Example resource route with more complex sub-resources:
36 + # resources :products do
37 + # resources :comments
38 + # resources :sales do
39 + # get 'recent', on: :collection
40 + # end
41 + # end
42 +
43 + # Example resource route with concerns:
44 + # concern :toggleable do
45 + # post 'toggle'
46 + # end
47 + # resources :posts, concerns: :toggleable
48 + # resources :photos, concerns: :toggleable
49 +
50 + # Example resource route within a namespace:
51 + # namespace :admin do
52 + # # Directs /admin/products/* to Admin::ProductsController
53 + # # (app/controllers/admin/products_controller.rb)
54 + # resources :products
55 + # end
56 + root 'welcome#index'
57 + match 'contact',
58 +end
backend/musik88-web/config/routes/api.rb new
+48
@@ -0,0 +1,48 @@
1 +namespace :api do
2 + root to: 'welcome#index'
3 +
4 + namespace :v1 do
5 + root to: 'welcome#index'
6 +
7 + devise_scope :user do
8 + post '/login' => 'user#login'
9 + delete '/logout' => 'user#logout'
10 + get '/splitfire/:id' => 'audio_file#splitfire_detail'
11 + get '/profile/:username' => 'user#profile'
12 + get '/profile/:username/following' => 'user#following'
13 + get '/profile/:username/followers' => 'user#followers'
14 + end
15 +
16 + get '/splitfire', to: 'audio_file#splitfire'
17 + post '/user_files', to: 'audio_file#user_files'
18 + post '/split', to: 'audio_file#split'
19 + post '/karaoke', to: 'audio_file#karaoke'
20 + post '/guitar_backing_track', to: 'audio_file#guitar_backing_track'
21 + post '/bass_backing_track', to: 'audio_file#bass_backing_track'
22 + post '/drum_backing_track', to: 'audio_file#drum_backing_track'
23 +
24 + post '/register', to: 'user#register'
25 + get '/community/:exclude', to: 'user#community'
26 +
27 + get '/lyric_synches/:audio_file_id', to: 'karaoke#lyric_synches'
28 + post '/lyric_synches/:audio_file_id', to: 'karaoke#lyric_synches_post'
29 + put '/lyric_synches/:audio_file_id', to: 'karaoke#lyric_synches_put'
30 +
31 + # Search
32 + get '/search', to: 'search#index' # Autocomplete
33 + post '/search', to: 'search#post' # Save search history
34 + # Gamelan notation
35 + get '/gamelan/:id', to: 'gamelan#detail'
36 +
37 + # Find audio from a Spotify track
38 + get '/carousel', to: 'song_bridge#carousel'
39 + get '/top-votes', to: 'song_bridge#top_votes'
40 + get '/ready-to-play', to: 'song_bridge#ready_to_play'
41 + post '/song-bridge', to: 'song_bridge#add_song_provider'
42 + get '/song-bridge/:id', to: 'song_bridge#find_audio'
43 + get '/song-bridge/:id/detail', to: 'song_bridge#detail'
44 + post '/song-bridge/:id/vote', to: 'song_bridge#vote'
45 +
46 + get '/:platform', to: 'welcome#root'
47 + end
48 +end
backend/musik88-web/config/routes/localized.rb new
+21
@@ -0,0 +1,21 @@
1 +# frozen_string_literal: true
2 +
3 +localized do
4 + root 'welcome#index'
5 + get 'search', to: 'welcome#search', as: :search
6 +
7 + # Routes from the splitfire project
8 + draw(:localized_my)
9 + # Accounts
10 + draw(:localized_accounts)
11 +
12 + draw(:localized_tools)
13 +
14 + draw(:localized_pages)
15 +
16 + draw(:localized_contents)
17 +
18 + draw(:localized_javanese_gamelan)
19 +
20 + draw(:localized_commentable)
21 +end
backend/musik88-web/config/routes/localized_accounts.rb new
+28
@@ -0,0 +1,28 @@
1 +get '@:username', to: 'users#show', as: :profile
2 +get '@:username/user_rep', to: 'users#show_reputation', as: :show_reputation
3 +get '@:username/friends', to: 'users#friends', as: :friends
4 +get '@:username/following', to: 'users#following', as: :following
5 +get '@:username/followers', to: 'users#followers', as: :followers
6 +get '@:username/settings', to: 'users#settings', as: :settings
7 +post '@:username/settings_activate_solana', to: 'users#settings_activate_solana', as: :settings_activate_solana
8 +post '@:username/settings_locale', to: 'users#settings_locale', as: :settings_locale
9 +put '@:username/update_settings', to: 'users#update_settings', as: :update_settings
10 +resources :relationships, only: %i[create destroy]
11 +
12 +get 'users/:id', to: redirect { |_params, request| "/@#{request.params[:id]}" }
13 +
14 +get 'community', to: 'users#community', as: :community
15 +get 'login', to: 'sessions#new', as: :login
16 +post 'login', to: 'sessions#create', as: :login
17 +post 'login_pubkey', to: 'sessions#create_pubkey', as: :login_pubkey
18 +# Deprecated in favor of clicking link from login page error flash message.
19 +get 'confirm_email', to: 'sessions#confirm_email', as: :confirm_email
20 +post 'confirm_email', to: 'sessions#confirm_email', as: :confirm_email
21 +get 'signup', to: 'users#new', as: :signup
22 +post 'signup', to: 'users#create', as: :signup
23 +get 'onboarding', to: 'users#onboarding', as: :onboarding
24 +post 'onboarding_action', to: 'users#onboarding_action', as: :onboarding_action
25 +get 'username_check', to: 'users#username_check', as: :username_check
26 +post 'username', to: 'users#username', as: :username
27 +post 'onboarding_export_spotify', to: 'users#onboarding_export_spotify', as: :onboarding_export_spotify
28 +post 'onboarding_export_youtube', to: 'users#onboarding_export_youtube', as: :onboarding_export_youtube
backend/musik88-web/config/routes/localized_commentable.rb new
+30
@@ -0,0 +1,30 @@
1 +resources :lyrics do
2 + resources :comments
3 +end
4 +resources :chords do
5 + resources :comments
6 +end
7 +resources :songs do
8 + resources :comments
9 +end
10 +resources :albums do
11 + resources :comments
12 +end
13 +resources :javanese_gending_notations do
14 + resources :comments
15 +end
16 +resources :javanese_gendings do
17 + resources :comments
18 +end
19 +resources :artists do
20 + resources :comments
21 +end
22 +resources :users do
23 + resources :comments
24 +end
25 +resources :pages do
26 + resources :comments
27 +end
28 +resources :comments do
29 + resources :comments
30 +end
backend/musik88-web/config/routes/localized_contents.rb new
+58
@@ -0,0 +1,58 @@
1 +get 'artist', to: 'artists#index', as: :artist
2 +get 'artist_add', to: 'artists#add', as: :artist_add
3 +post 'artist_add', to: 'artists#add', as: :artist_add
4 +post 'artist_vote', to: 'artists#vote'
5 +delete 'artist_unvote', to: 'artists#unvote'
6 +get 'album', to: 'albums#index', as: :album
7 +get 'album_add', to: 'albums#add', as: :album_add
8 +post 'album_add', to: 'albums#add', as: :album_add
9 +post 'album_vote', to: 'albums#vote'
10 +delete 'album_unvote', to: 'albums#unvote'
11 +get 'song', to: 'songs#index', as: :song
12 +get 'song_add', to: 'songs#add', as: :song_add
13 +post 'song_add', to: 'songs#add', as: :song_add
14 +get 'song_karaoke_detail', to: 'songs#karaoke', as: :song_karaoke_detail
15 +get 'song_bass_backing_track', to: 'songs#bass_backing_track', as: :song_bass_backing_track
16 +get 'song_bass_tab', to: 'songs#bass_tab', as: :song_bass_tab
17 +
18 +post 'song_chord_pleas_loved', to: 'songs#loved'
19 +delete 'song_chord_pleas_unloved', to: 'songs#unloved'
20 +
21 +get 'most_loved_songs', to: 'songs#most_loved', as: :most_loved_songs
22 +
23 +get 'song_lyrics', to: 'lyrics#index', as: :lyrics
24 +get 'song_lyrics_detail', to: 'lyrics#lyric', as: :lyric_detail
25 +get 'guitar_chords', to: 'chords#index', as: :guitar_chords
26 +get 'guitar_chord_shapes', to: 'chords#guitar_shapes', as: :guitar_chord_shapes
27 +get 'guitar_chord_shape', to: 'chords#guitar_shape', as: :guitar_chord_shape
28 +get 'guitar_chords_detail', to: 'chords#chord', as: :guitar_chord_detail
29 +get 'guitar_chords_history', to: 'chords#history', as: :guitar_chord_history
30 +get 'guitar_chords_add', to: 'chords#add', as: :guitar_chord_add
31 +post 'guitar_chords_add', to: 'chords#add_post', as: :guitar_chord_add
32 +get 'guitar_chords_add_song_search', to: 'chords#song_search', as: :guitar_chord_add_song_search
33 +get 'guitar_chords_review', to: 'chords#review', as: :guitar_chord_review
34 +patch 'guitar_chords_review', to: 'chords#review', as: :guitar_chord_review
35 +post 'chord_vote', to: 'chords#vote'
36 +delete 'chord_unvote', to: 'chords#unvote'
37 +
38 +get 'artist_detail/:slug', to: 'artists#detail', as: :artist_detail # slug is artist name + id
39 +get 'artist_bridge/:slug', to: 'artists#bridge', as: :artist_bridge # slug is artist name + id
40 +get 'album_detail/:slug', to: 'albums#detail', as: :album_detail # slug is album name + artist name + id
41 +get 'album_bridge/:slug', to: 'albums#bridge', as: :album_bridge # slug is album name + artist name + id
42 +get 'song_detail/:slug', to: 'songs#detail', as: :song_detail # slug is song title + artist name + album + id
43 +get 'song_bridge/:slug', to: 'songs#bridge', as: :song_bridge # slug is song title + artist name + album + id
44 +get 'song_chords/:slug', to: 'songs#chords', as: :song_chords
45 +get 'song_lyrics/:slug', to: 'songs#lyrics', as: :song_lyrics
46 +get 'song_audio/:slug', to: 'songs#audio', as: :song_audio
47 +
48 +get 'audio_files', to: 'audio_files#index', as: :audio_files
49 +get 'guitar_backing_track', to: 'audio_files#guitar_backing_track', as: :guitar_backing_track
50 +post 'guitar_backing_track', to: 'audio_files#guitar_backing_track', as: :guitar_backing_track
51 +get 'bass_backing_track', to: 'audio_files#bass_backing_track', as: :bass_backing_track
52 +post 'bass_backing_track', to: 'audio_files#bass_backing_track', as: :bass_backing_track
53 +get 'drum_backing_track', to: 'audio_files#drum_backing_track', as: :drum_backing_track
54 +post 'drum_backing_track', to: 'audio_files#drum_backing_track', as: :drum_backing_track
55 +
56 +# What is?
57 +get 'what_is_this', to: 'pages#what_is_this', as: :what_is_this
58 +get 'what_is_this_detail', to: 'pages#what_is_this_detail', as: :what_is_this_detail
backend/musik88-web/config/routes/localized_javanese_gamelan.rb new
+24
@@ -0,0 +1,24 @@
1 +get 'gns' => redirect('https://github.com/smbpndk/gns')
2 +# The old index page when there was only one table.
3 +get 'javanese_gamelan', to: 'javanese_gamelan#index',
4 + as: :javanese_gamelan
5 +get 'javanese_gending', to: 'javanese_gamelan#gending',
6 + as: :javanese_gending
7 +get 'javanese_gending_detail', to: 'javanese_gamelan#gending_detail',
8 + as: :javanese_gending_detail
9 +get 'javanese_gending_gamelan_notation', to: 'javanese_gamelan#gamelan_notation',
10 + as: :javanese_gending_gamelan_notation
11 +get 'javanese_gending_gamelan_notation_detail', to: 'javanese_gamelan#notation',
12 + as: :javanese_gending_gamelan_notation_detail
13 +get 'javanese_gending_gamelan_notation_detail_new', to: 'javanese_gamelan#notation_new',
14 + as: :javanese_gending_gamelan_notation_detail_new
15 +
16 +get 'add_gending', to: 'javanese_gamelan#add_gending', as: :add_gending
17 +post 'add_gending', to: 'javanese_gamelan#add_gending', as: :add_gending
18 +post 'javanese_gending_vote', to: 'javanese_gamelan#gending_vote'
19 +delete 'javanese_gending_unvote', to: 'javanese_gamelan#gending_unvote'
20 +post 'javanese_gending_notation_vote', to: 'javanese_gamelan#gending_notation_vote'
21 +delete 'javanese_gending_notation_unvote', to: 'javanese_gamelan#gending_notation_unvote'
22 +
23 +get 'add_javanese_gamelan_notation/', to: 'javanese_gamelan#add', as: :add_gending_notation
24 +post 'add_javanese_gamelan_notation/', to: 'javanese_gamelan#add', as: :add_gending_notation
\ No newline at end of file
backend/musik88-web/config/routes/localized_my.rb new
+49
@@ -0,0 +1,49 @@
1 +# Private pages
2 +get 'my', to: redirect('my/profile')
3 +get 'my/profile', to: 'my#index', as: :my_profile
4 +get 'my/profile/edit', to: 'my#edit', as: :my_profile_edit
5 +
6 +# My Lyrics
7 +get 'my/add-lyric/:song_id', to: 'my#add_lyric', as: :add_lyric
8 +get 'my/edit-lyric/:id', to: 'my#edit_lyric', as: :edit_lyric
9 +post 'my/add-lyric/:song_id', to: 'my#add_lyric', as: :add_lyric
10 +patch 'my/edit-lyric/:id', to: 'my#edit_lyric', as: :edit_lyric
11 +get 'my/lyrics', to: 'my#lyrics', as: :my_lyrics
12 +
13 +# My Files
14 +get 'my/files', to: 'my#files', as: :my_files
15 +get 'my/files/:id', to: 'my#file_detail', as: :my_file_detail
16 +post 'my/files', to: 'my#files', as: :my_files # Add file
17 +delete 'my/files/:id', to: 'my#files', as: :my_delete_file # Delete file
18 +
19 +get 'my/admin-files', to: 'my#admin_files', as: :my_admin_files
20 +
21 +# My SplitFire
22 +get 'my/splitfire', to: 'my#splitfire', as: :my_splitfire
23 +get 'my/splitfire/:id', to: 'my#splitfire_detail', as: :my_splitfire_detail
24 +patch 'my/splitfire/:id', to: 'my#splitfire_cancel', as: :my_splitfire_cancel
25 +delete 'my/splitfire/:id', to: 'my#splitfire_delete', as: :my_splitfire_delete
26 +post 'my/splitfire/:id', to: 'my#splitfire_start', as: :my_splitfire_start
27 +patch 'my/splitfire_settings', to: 'my#splitfire_settings', as: :my_splitfire_settings
28 +
29 +# My Bass64
30 +get 'my/bass64', to: 'my#bass64', as: :my_bass64
31 +post 'my/bass64/:id', to: 'my#bass64_start', as: :my_bass64_start
32 +patch 'my/bass64/:id', to: 'my#bass64_cancel', as: :my_bass64_cancel
33 +get 'my/bass64/:id', to: 'my#bass64_detail', as: :my_bass64_detail
34 +delete 'my/bass64/:id', to: 'my#bass64_delete', as: :my_bass64_delete
35 +
36 +# My KaroKowe
37 +get 'my/karokowe', to: 'my#karokowe', as: :my_karokowe
38 +post 'my/karokowe/:id', to: 'my#karokowe_start', as: :my_karokowe_start
39 +patch 'my/karokowe/:id', to: 'my#karokowe_cancel', as: :my_karokowe_cancel
40 +get 'my/karokowe/:id', to: 'my#karokowe_detail', as: :my_karokowe_detail
41 +delete 'my/karokowe/:id', to: 'my#karokowe_delete', as: :my_karokowe_delete
42 +
43 +# My Bonzo
44 +get 'my/bonzo', to: 'my#bonzo', as: :my_bonzo
45 +post 'my/bonzo/:id', to: 'my#bonzo_start', as: :my_bonzo_start
46 +patch 'my/bonzo/:id', to: 'my#bonzo_cancel', as: :my_bonzo_cancel
47 +get 'my/bonzo/:id', to: 'my#bonzo_detail', as: :my_bonzo_detail
48 +delete 'my/bonzo/:id', to: 'my#bonzo_delete', as: :my_bonzo_delete
49 +# END Private pages
backend/musik88-web/config/routes/localized_pages.rb new
+6
@@ -0,0 +1,6 @@
1 +get 'about_us', to: 'about#index', as: :about_us
2 +get 'term_of_use', to: 'about#term', as: :term_of_use
3 +get 'privacy_policy', to: 'about#privacy', as: :privacy_policy
4 +get 'contact', to: 'about#contact', as: :contact
5 +get 'BACH', to: 'about#bach', as: :bach
6 +get 'bach', to: redirect('BACH')
backend/musik88-web/config/routes/localized_tools.rb new
+9
@@ -0,0 +1,9 @@
1 +# Tools
2 +get 'splitfire', to: 'about#splitfire', as: :splitfire
3 +post 'splitfire', to: 'about#splitfire', as: :splitfire
4 +get 'bass64', to: 'about#bass64', as: :bass64
5 +get 'bass-backing-track-generator', to: 'about#bass64_branding', as: :bass64_branding
6 +get 'karokowe', to: 'about#karokowe', as: :karokowe
7 +get 'make-karaoke-version-of-song', to: 'about#karokowe_branding', as: :karokowe_branding
8 +get 'bonzo', to: 'about#bonzo', as: :bonzo
9 +get 'drum-backing-track-generator', to: 'about#bonzo_branding', as: :bonzo_branding
backend/musik88-web/config/secrets.yml new
+22
@@ -0,0 +1,22 @@
1 +# Be sure to restart your server when you modify this file.
2 +
3 +# Your secret key is used for verifying the integrity of signed cookies.
4 +# If you change this key, all old signed cookies will become invalid!
5 +
6 +# Make sure the secret is at least 30 characters and all random,
7 +# no regular words or you'll be exposed to dictionary attacks.
8 +# You can use `rake secret` to generate a secure secret key.
9 +
10 +# Make sure the secrets in this file are kept private
11 +# if you're sharing your code publicly.
12 +
13 +development:
14 + secret_key_base: 21ab1fa7ad6d2634a04fa8165ee38f9604e661d53e45272273d04c72747eb2676a012836b6e4dfb549d66b0323990408d53e18f1499d684296094d541167046c
15 +
16 +test:
17 + secret_key_base: cda084dab2cdff92fcfd33d70ea766f2bfba51a62ef3dc764bf2ccd4c9ea9a823044beb4fe31f7512122e490dea6b0e09106c6056b29383adf77c7739ca7da81
18 +
19 +# Do not keep production secrets in the repository,
20 +# instead read values from the environment.
21 +production:
22 + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
backend/musik88-web/config/sidekiq.yml new
+10
@@ -0,0 +1,10 @@
1 +---
2 +:concurrency: 5
3 +staging:
4 + :concurrency: 10
5 +production:
6 + :concurrency: 10
7 +:queues:
8 + - critical
9 + - default
10 + - low
backend/musik88-web/config/sitemap.rb new
+60
@@ -0,0 +1,60 @@
1 +# Set the host name for URL creation
2 +SitemapGenerator::Sitemap.default_host = 'https://musik88.com'
3 +SitemapGenerator::Sitemap.adapter = SitemapGenerator::S3Adapter.new(
4 + fog_provider: 'AWS',
5 + aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
6 + aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
7 + fog_directory: ENV['S3_BUCKET'],
8 + fog_region: ENV['AWS_REGION']
9 +)
10 +SitemapGenerator::Sitemap.public_path = 'tmp/'
11 +SitemapGenerator::Sitemap.sitemaps_host = 'https://musik88.s3.amazonaws.com/'
12 +SitemapGenerator::Sitemap.sitemaps_path = 'sitemaps/'
13 +SitemapGenerator::Sitemap.ping_search_engines('https://musik88.com/sitemap')
14 +
15 +SitemapGenerator::Sitemap.create do
16 + I18n.available_locales.each do |locale|
17 + I18n.locale = locale
18 +
19 + # Index paths
20 + [javanese_gending_path, javanese_gending_gamelan_notation_path, artist_path, album_path, song_path,
21 + lyrics_path].each do |path|
22 + add path, changefreq: 'daily'
23 + end
24 +
25 + JavaneseGending.find_each do |gending|
26 + add javanese_gending_detail_path(gending.slug), changefreq: 'daily', lastmod: gending.updated_at
27 + end
28 +
29 + JavaneseGendingNotation.find_each do |notation|
30 + add javanese_gending_gamelan_notation_detail_path(notation.slug), changefreq: 'daily',
31 + lastmod: notation.updated_at
32 + end
33 +
34 + Chord.find_each do |chord|
35 + add guitar_chord_detail_path(chord.slug), changefreq: 'daily', lastmod: chord.updated_at
36 + end
37 +
38 + Song.find_each do |song|
39 + add song_detail_path(song.slug), changefreq: 'daily', lastmod: song.updated_at
40 + end
41 +
42 + SongProvider.find_each do |song|
43 + add song_bridge_path(song.slug), changefreq: 'daily', lastmod: song.updated_at
44 + end
45 +
46 + ArtistProvider.find_each do |artist|
47 + add artist_bridge_path(artist.slug), changefreq: 'daily', lastmod: artist.updated_at
48 + end
49 +
50 + AlbumProvider.find_each do |album|
51 + add album_bridge_path(album.slug), changefreq: 'daily', lastmod: album.updated_at
52 + end
53 +
54 + # PAGES
55 + [splitfire_path, bass64_path, contact_path, term_of_use_path, privacy_policy_path, login_path,
56 + signup_path].each do |path|
57 + add path, changefreq: 'monthly'
58 + end
59 + end
60 +end
backend/musik88-web/config/storage.yml new
+25
@@ -0,0 +1,25 @@
1 +local:
2 + service: Disk
3 + root: <%= Rails.root.join("../storage") %>
4 +
5 +test:
6 + service: Disk
7 + root: <%= Rails.root.join("tmp/storage") %>
8 +
9 +google:
10 + service: GCS
11 + credentials: <%= Rails.root.join("path/to/keyfile.json") %>
12 + project: ""
13 + bucket: ""
14 +amazon_dev:
15 + service: S3
16 + access_key_id: <%= Rails.application.credentials.dig(:aws, :storage_access_key_id) %>
17 + secret_access_key: <%= Rails.application.credentials.dig(:aws, :storage_secret_access_key) %>
18 + region: eu-north-1
19 + bucket: musik88-activestorage-dev
20 +amazon:
21 + service: S3
22 + access_key_id: <%= Rails.application.credentials.dig(:aws, :storage_access_key_id) %>
23 + secret_access_key: <%= Rails.application.credentials.dig(:aws, :storage_secret_access_key) %>
24 + region: eu-north-1
25 + bucket: musik88-activestorage
backend/musik88-web/config/webpack/development.js new
+5
@@ -0,0 +1,5 @@
1 +process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 +
3 +const environment = require('./environment')
4 +
5 +module.exports = environment.toWebpackConfig()
backend/musik88-web/config/webpack/environment.js new
+11
@@ -0,0 +1,11 @@
1 +const { environment } = require('@rails/webpacker')
2 +const custom = {
3 + resolve: {
4 + alias: {
5 + jquery: 'jquery/src/jquery',
6 + }
7 + }
8 +}
9 +environment.config.merge(custom)
10 +
11 +module.exports = environment
backend/musik88-web/config/webpack/production.js new
+5
@@ -0,0 +1,5 @@
1 +process.env.NODE_ENV = process.env.NODE_ENV || 'production'
2 +
3 +const environment = require('./environment')
4 +
5 +module.exports = environment.toWebpackConfig()
backend/musik88-web/config/webpack/test.js new
+5
@@ -0,0 +1,5 @@
1 +process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 +
3 +const environment = require('./environment')
4 +
5 +module.exports = environment.toWebpackConfig()
backend/musik88-web/config/webpacker.yml new
+92
@@ -0,0 +1,92 @@
1 +# Note: You must restart bin/webpack-dev-server for changes to take effect
2 +
3 +default: &default
4 + source_path: app/frontend
5 + source_entry_path: packs
6 + public_root_path: public
7 + public_output_path: packs
8 + cache_path: tmp/cache/webpacker
9 + webpack_compile_output: true
10 +
11 + # Additional paths webpack should lookup modules
12 + # ['app/assets', 'engine/foo/app/assets']
13 + additional_paths: []
14 +
15 + # Reload manifest.json on all requests so we reload latest compiled packs
16 + cache_manifest: false
17 +
18 + # Extract and emit a css file
19 + extract_css: false
20 +
21 + static_assets_extensions:
22 + - .jpg
23 + - .jpeg
24 + - .png
25 + - .gif
26 + - .tiff
27 + - .ico
28 + - .svg
29 + - .eot
30 + - .otf
31 + - .ttf
32 + - .woff
33 + - .woff2
34 +
35 + extensions:
36 + - .mjs
37 + - .js
38 + - .sass
39 + - .scss
40 + - .css
41 + - .module.sass
42 + - .module.scss
43 + - .module.css
44 + - .png
45 + - .svg
46 + - .gif
47 + - .jpeg
48 + - .jpg
49 +
50 +development:
51 + <<: *default
52 + compile: true
53 +
54 + # Reference: https://webpack.js.org/configuration/dev-server/
55 + dev_server:
56 + https: false
57 + host: localhost
58 + port: 3035
59 + public: localhost:3035
60 + hmr: false
61 + # Inline should be set to true if using HMR
62 + inline: true
63 + overlay: true
64 + compress: true
65 + disable_host_check: true
66 + use_local_ip: false
67 + quiet: false
68 + pretty: false
69 + headers:
70 + 'Access-Control-Allow-Origin': '*'
71 + watch_options:
72 + ignored: '**/node_modules/**'
73 +
74 +
75 +test:
76 + <<: *default
77 + compile: true
78 +
79 + # Compile test packs to a separate directory
80 + public_output_path: packs-test
81 +
82 +production:
83 + <<: *default
84 +
85 + # Production depends on precompilation of packs prior to booting for performance.
86 + compile: false
87 +
88 + # Extract and emit a css file
89 + extract_css: true
90 +
91 + # Cache manifest.json for performance
92 + cache_manifest: true
backend/musik88-web/db/migrate/20150611124515_create_pages.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreatePages < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :pages do |t|
4 + t.string :page_slug
5 + t.text :page_content
6 + t.string :locale
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20180523154659_create_artists.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateArtists < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :artists do |t|
4 + t.string :name
5 + t.text :description
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20180523155919_create_albums.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateAlbums < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :albums do |t|
4 + t.string :name
5 + t.text :description
6 + t.string :locale
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20180523160028_create_songs.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateSongs < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :songs do |t|
4 + t.string :name
5 + t.text :description
6 + t.string :locale
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20180523360138_create_lyrics.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateLyrics < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :lyrics do |t|
4 + t.text :lyric
5 + t.string :locale
6 + t.integer :song_id, :limit => 8
7 +
8 + t.timestamps
9 + end
10 + add_foreign_key :lyrics, :songs
11 + end
12 +end
backend/musik88-web/db/migrate/20180527164521_create_album_artists.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateAlbumArtists < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :album_artists do |t|
4 + t.boolean :primary
5 + t.references :album, foreign_key: true
6 + t.references :artist, foreign_key: true
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20180527212935_create_song_artists.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateSongArtists < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :song_artists do |t|
4 + t.boolean :primary
5 + t.references :song, foreign_key: true
6 + t.references :artist, foreign_key: true
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20180527213950_add_album_ref_to_songs.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddAlbumRefToSongs < ActiveRecord::Migration[5.2]
2 + def change
3 + add_reference :songs, :album, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20180529195705_devise_create_admin_users.rb new
+44
@@ -0,0 +1,44 @@
1 +# frozen_string_literal: true
2 +
3 +class DeviseCreateAdminUsers < ActiveRecord::Migration[5.2]
4 + def change
5 + create_table :admin_users do |t|
6 + ## Database authenticatable
7 + t.string :email, null: false, default: ""
8 + t.string :encrypted_password, null: false, default: ""
9 +
10 + ## Recoverable
11 + t.string :reset_password_token
12 + t.datetime :reset_password_sent_at
13 +
14 + ## Rememberable
15 + t.datetime :remember_created_at
16 +
17 + ## Trackable
18 + t.integer :sign_in_count, default: 0, null: false
19 + t.datetime :current_sign_in_at
20 + t.datetime :last_sign_in_at
21 + t.inet :current_sign_in_ip
22 + t.inet :last_sign_in_ip
23 +
24 + ## Confirmable
25 + # t.string :confirmation_token
26 + # t.datetime :confirmed_at
27 + # t.datetime :confirmation_sent_at
28 + # t.string :unconfirmed_email # Only if using reconfirmable
29 +
30 + ## Lockable
31 + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
32 + # t.string :unlock_token # Only if unlock strategy is :email or :both
33 + # t.datetime :locked_at
34 +
35 +
36 + t.timestamps null: false
37 + end
38 +
39 + add_index :admin_users, :email, unique: true
40 + add_index :admin_users, :reset_password_token, unique: true
41 + # add_index :admin_users, :confirmation_token, unique: true
42 + # add_index :admin_users, :unlock_token, unique: true
43 + end
44 +end
backend/musik88-web/db/migrate/20180529195707_create_active_admin_comments.rb new
+17
@@ -0,0 +1,17 @@
1 +class CreateActiveAdminComments < ActiveRecord::Migration::Current
2 + def self.up
3 + create_table :active_admin_comments do |t|
4 + t.string :namespace
5 + t.text :body
6 + t.references :resource, polymorphic: true
7 + t.references :author, polymorphic: true
8 + t.timestamps
9 + end
10 + add_index :active_admin_comments, [:namespace]
11 +
12 + end
13 +
14 + def self.down
15 + drop_table :active_admin_comments
16 + end
17 +end
backend/musik88-web/db/migrate/20180622144949_add_title_page_table.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddTitlePageTable < ActiveRecord::Migration[5.2]
2 + def change
3 + add_column :pages, :title, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20180622160911_add_views_count.rb new
+8
@@ -0,0 +1,8 @@
1 +class AddViewsCount < ActiveRecord::Migration[5.2]
2 + def change
3 + add_column :artists, :views_count, :integer, default: 0
4 + add_column :albums, :views_count, :integer, default: 0
5 + add_column :songs, :views_count, :integer, default: 0
6 + add_column :lyrics, :views_count, :integer, default: 0
7 + end
8 +end
backend/musik88-web/db/migrate/20180624070607_create_chords.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateChords < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :chords do |t|
4 + t.text :chord
5 + t.integer :song_id, :limit => 8
6 + t.integer :views_count, :default => 0
7 +
8 + t.timestamps
9 + end
10 + add_foreign_key :chords, :songs
11 + end
12 +end
backend/musik88-web/db/migrate/20180628194732_devise_create_users.rb new
+12
@@ -0,0 +1,12 @@
1 +# frozen_string_literal: true
2 +
3 +class DeviseCreateUsers < ActiveRecord::Migration[5.2]
4 + def change
5 + create_table :users do |t|
6 + t.string :name
7 + t.string :email
8 +
9 + t.timestamps
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20180705163556_add_index_to_users_email.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddIndexToUsersEmail < ActiveRecord::Migration[5.2]
2 + def change
3 + add_index :users, :email, unique: true
4 + end
5 +end
backend/musik88-web/db/migrate/20180705165703_add_password_digest_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddPasswordDigestToUsers < ActiveRecord::Migration[5.2]
2 + def change
3 + add_column :users, :password_digest, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20180711082423_create_song_chord_pleas.rb new
+8
@@ -0,0 +1,8 @@
1 +class CreateSongChordPleas < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :song_chord_pleas do |t|
4 + t.references :song, foreign_key: true
5 + t.references :user, foreign_key: true
6 + end
7 + end
8 +end
backend/musik88-web/db/migrate/20180720185743_create_javanese_gendings.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateJavaneseGendings < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :javanese_gendings do |t|
4 + t.string :name
5 + t.text :description
6 + t.text :notation
7 + t.integer :views_count, default: 0
8 +
9 + t.timestamps
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20180805175321_add_remember_digest_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddRememberDigestToUsers < ActiveRecord::Migration[5.2]
2 + def change
3 + add_column :users, :remember_digest, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20191117195747_create_chord_shapes.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateChordShapes < ActiveRecord::Migration[5.2]
2 + def change
3 + create_table :chord_shapes do |t|
4 + t.string :name
5 + t.string :youtube_link
6 + t.string :image_link
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20201023184815_create_meta_pages.rb new
+15
@@ -0,0 +1,15 @@
1 +class CreateMetaPages < ActiveRecord::Migration[6.0]
2 + def change
3 + create_table :meta_pages do |t|
4 + t.string :home
5 + t.string :title_en
6 + t.string :title_id
7 + t.string :title_sv
8 + t.text :description_en
9 + t.text :description_id
10 + t.text :description_sv
11 +
12 + t.timestamps
13 + end
14 + end
15 +end
backend/musik88-web/db/migrate/20201108132939_add_username_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUsernameToUsers < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :users, :username, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20210711042902_change_meta_table_locale_column_names.rb new
+8
@@ -0,0 +1,8 @@
1 +class ChangeMetaTableLocaleColumnNames < ActiveRecord::Migration[6.0]
2 + def change
3 + rename_column :meta_pages, :title_id, :'title_id-id'
4 + rename_column :meta_pages, :title_sv, :'title_sv-se'
5 + rename_column :meta_pages, :description_id, :'description_id-id'
6 + rename_column :meta_pages, :description_sv, :'description_sv-se'
7 + end
8 +end
backend/musik88-web/db/migrate/20210729082103_add_omniauth_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddOmniauthToUsers < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :users, :image, :text
4 + end
5 +end
backend/musik88-web/db/migrate/20210731123242_add_omniauth_provider_and_uid_to_users.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddOmniauthProviderAndUidToUsers < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :users, :provider, :string
4 + add_column :users, :uid, :string
5 + end
6 +end
backend/musik88-web/db/migrate/20210802211521_add_is_private_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddIsPrivateToUsers < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :users, :is_private, :boolean, default: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210804180132_change_defaultvalue_for_is_private.rb new
+5
@@ -0,0 +1,5 @@
1 +class ChangeDefaultvalueForIsPrivate < ActiveRecord::Migration[6.0]
2 + def change
3 + change_column_default :users, :is_private, false
4 + end
5 +end
backend/musik88-web/db/migrate/20210806093712_add_user_to_chords.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUserToChords < ActiveRecord::Migration[6.0]
2 + def change
3 + add_reference :chords, :user, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210806093801_add_user_to_lyrics.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUserToLyrics < ActiveRecord::Migration[6.0]
2 + def change
3 + add_reference :lyrics, :user, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210806093813_add_user_to_songs.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUserToSongs < ActiveRecord::Migration[6.0]
2 + def change
3 + add_reference :songs, :user, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210806134753_add_is_published_to_lyrics.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddIsPublishedToLyrics < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :lyrics, :is_published, :boolean, default: false
4 + end
5 +end
backend/musik88-web/db/migrate/20210809132815_add_is_published_to_javanese_gendings.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddIsPublishedToJavaneseGendings < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :javanese_gendings, :is_published, :boolean, default: false
4 + end
5 +end
backend/musik88-web/db/migrate/20210809185610_add_users_to_javanese_gendings.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUsersToJavaneseGendings < ActiveRecord::Migration[6.0]
2 + def change
3 + add_reference :javanese_gendings, :user, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210822214824_create_javanese_gending_notations.rb new
+14
@@ -0,0 +1,14 @@
1 +class CreateJavaneseGendingNotations < ActiveRecord::Migration[6.0]
2 + def change
3 + create_table :javanese_gending_notations do |t|
4 + t.string :name
5 + t.text :notation
6 + t.integer :views_count
7 + t.boolean :is_published
8 + t.references :user, foreign_key: true
9 + t.references :javanese_gending, foreign_key: true
10 +
11 + t.timestamps
12 + end
13 + end
14 +end
backend/musik88-web/db/migrate/20210902214242_create_karaoke_files.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateKaraokeFiles < ActiveRecord::Migration[6.0]
2 + def change
3 + create_table :karaoke_files do |t|
4 + t.string :link
5 + t.references :song, foreign_key: true
6 + t.references :user, foreign_key: true
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20210903053612_create_karaoke_file_lovers.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateKaraokeFileLovers < ActiveRecord::Migration[6.0]
2 + def change
3 + create_table :karaoke_file_lovers do |t|
4 + t.references :karaoke_file, foreign_key: true
5 + t.references :user, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20210903060414_add_clicks_count_to_karaoke_file.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddClicksCountToKaraokeFile < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :karaoke_files, :clicks_count, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/migrate/20210903060713_add_clicks_count_to_karaoke_file_lover.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddClicksCountToKaraokeFileLover < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :karaoke_file_lovers, :clicks_count, :integer
4 + end
5 +end
backend/musik88-web/db/migrate/20210906194845_create_tolk_tables.rb new
+38
@@ -0,0 +1,38 @@
1 +class CreateTolkTables < ActiveRecord::Migration[4.2]
2 + def self.up
3 + create_table :tolk_locales do |t|
4 + t.string :name
5 + t.datetime :created_at
6 + t.datetime :updated_at
7 + end
8 +
9 + add_index :tolk_locales, :name, :unique => true
10 +
11 + create_table :tolk_phrases do |t|
12 + t.text :key
13 + t.datetime :created_at
14 + t.datetime :updated_at
15 + end
16 +
17 + create_table :tolk_translations do |t|
18 + t.integer :phrase_id
19 + t.integer :locale_id
20 + t.text :text
21 + t.text :previous_text
22 + t.boolean :primary_updated, :default => false
23 + t.datetime :created_at
24 + t.datetime :updated_at
25 + end
26 +
27 + add_index :tolk_translations, [:phrase_id, :locale_id], :unique => true
28 + end
29 +
30 + def self.down
31 + remove_index :tolk_translations, :column => [:phrase_id, :locale_id]
32 + remove_index :tolk_locales, :column => :name
33 +
34 + drop_table :tolk_translations
35 + drop_table :tolk_phrases
36 + drop_table :tolk_locales
37 + end
38 +end
\ No newline at end of file
backend/musik88-web/db/migrate/20210911090652_create_active_storage_tables.active_storage.rb new
+27
@@ -0,0 +1,27 @@
1 +# This migration comes from active_storage (originally 20170806125915)
2 +class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
3 + def change
4 + create_table :active_storage_blobs do |t|
5 + t.string :key, null: false
6 + t.string :filename, null: false
7 + t.string :content_type
8 + t.text :metadata
9 + t.bigint :byte_size, null: false
10 + t.string :checksum, null: false
11 + t.datetime :created_at, null: false
12 +
13 + t.index [ :key ], unique: true
14 + end
15 +
16 + create_table :active_storage_attachments do |t|
17 + t.string :name, null: false
18 + t.references :record, null: false, polymorphic: true, index: false
19 + t.references :blob, null: false
20 +
21 + t.datetime :created_at, null: false
22 +
23 + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
24 + t.foreign_key :active_storage_blobs, column: :blob_id
25 + end
26 + end
27 +end
backend/musik88-web/db/migrate/20210911110141_create_split_audio_files.rb new
+9
@@ -0,0 +1,9 @@
1 +class CreateSplitAudioFiles < ActiveRecord::Migration[6.0]
2 + def change
3 + create_table :split_audio_files do |t|
4 + t.references :user, foreign_key: true
5 +
6 + t.timestamps
7 + end
8 + end
9 +end
backend/musik88-web/db/migrate/20210911135533_create_split_audio_file_results.rb new
+9
@@ -0,0 +1,9 @@
1 +class CreateSplitAudioFileResults < ActiveRecord::Migration[6.0]
2 + def change
3 + create_table :split_audio_file_results do |t|
4 + t.references :split_audio_file, foreign_key: true
5 +
6 + t.timestamps
7 + end
8 + end
9 +end
backend/musik88-web/db/migrate/20210913072413_add_status_to_split_audio_files.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddStatusToSplitAudioFiles < ActiveRecord::Migration[6.0]
2 + def change
3 + add_column :split_audio_files, :status, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/migrate/20210913222126_add_service_name_to_active_storage_blobs.active_storage.rb new
+18
@@ -0,0 +1,18 @@
1 +# This migration comes from active_storage (originally 20190112182829)
2 +class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
3 + def up
4 + unless column_exists?(:active_storage_blobs, :service_name)
5 + add_column :active_storage_blobs, :service_name, :string
6 +
7 + if configured_service = ActiveStorage::Blob.service.name
8 + ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
9 + end
10 +
11 + change_column :active_storage_blobs, :service_name, :string, null: false
12 + end
13 + end
14 +
15 + def down
16 + remove_column :active_storage_blobs, :service_name
17 + end
18 +end
backend/musik88-web/db/migrate/20210913222127_create_active_storage_variant_records.active_storage.rb new
+12
@@ -0,0 +1,12 @@
1 +# This migration comes from active_storage (originally 20191206030411)
2 +class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
3 + def change
4 + create_table :active_storage_variant_records do |t|
5 + t.belongs_to :blob, null: false, index: false
6 + t.string :variation_digest, null: false
7 +
8 + t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
9 + t.foreign_key :active_storage_blobs, column: :blob_id
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20210915145441_add_devise_to_users.rb new
+51
@@ -0,0 +1,51 @@
1 +# frozen_string_literal: true
2 +
3 +class AddDeviseToUsers < ActiveRecord::Migration[6.1]
4 + def self.up
5 + change_table :users do |t|
6 + ## Database authenticatable
7 + #t.string :email, null: false, default: ""
8 + t.string :encrypted_password, null: false, default: ""
9 +
10 + ## Recoverable
11 + t.string :reset_password_token
12 + t.datetime :reset_password_sent_at
13 +
14 + ## Rememberable
15 + t.datetime :remember_created_at
16 +
17 + ## Trackable
18 + # t.integer :sign_in_count, default: 0, null: false
19 + # t.datetime :current_sign_in_at
20 + # t.datetime :last_sign_in_at
21 + # t.string :current_sign_in_ip
22 + # t.string :last_sign_in_ip
23 +
24 + ## Confirmable
25 + # t.string :confirmation_token
26 + # t.datetime :confirmed_at
27 + # t.datetime :confirmation_sent_at
28 + # t.string :unconfirmed_email # Only if using reconfirmable
29 +
30 + ## Lockable
31 + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
32 + # t.string :unlock_token # Only if unlock strategy is :email or :both
33 + # t.datetime :locked_at
34 +
35 +
36 + # Uncomment below if timestamps were not included in your original model.
37 + # t.timestamps null: false
38 + end
39 +
40 + #add_index :users, :email, unique: true
41 + add_index :users, :reset_password_token, unique: true
42 + # add_index :users, :confirmation_token, unique: true
43 + # add_index :users, :unlock_token, unique: true
44 + end
45 +
46 + def self.down
47 + # By default, we don't want to make any assumption about how to roll back a migration when your
48 + # model already existed. Please edit below which fields you would like to remove in this migration.
49 + raise ActiveRecord::IrreversibleMigration
50 + end
51 +end
backend/musik88-web/db/migrate/20210915164131_change_column_name.rb new
+6
@@ -0,0 +1,6 @@
1 +class ChangeColumnName < ActiveRecord::Migration[6.1]
2 + def self.up
3 + execute "UPDATE users u SET encrypted_password = u.password_digest FROM users p WHERE u.id = p.id;"
4 + remove_column :users, :password_digest
5 + end
6 +end
backend/musik88-web/db/migrate/20210915211930_add_admin_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddAdminToUsers < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :users, :admin, :boolean
4 + end
5 +end
backend/musik88-web/db/migrate/20210916103501_create_bass_backing_tracks.rb new
+9
@@ -0,0 +1,9 @@
1 +class CreateBassBackingTracks < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :bass_backing_tracks do |t|
4 + t.boolean :is_public
5 +
6 + t.timestamps
7 + end
8 + end
9 +end
backend/musik88-web/db/migrate/20210916104942_create_audio_file_bass64_statuses.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateAudioFileBass64Statuses < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :audio_file_bass64_statuses do |t|
4 + t.references :split_audio_file, foreign_key: true
5 + t.integer :status
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20210916121153_add_split_audio_file_to_bass_backing_track.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddSplitAudioFileToBassBackingTrack < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :bass_backing_tracks, :split_audio_file, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210916201438_rename_split_audio_files_to_audio_files.rb new
+6
@@ -0,0 +1,6 @@
1 +class RenameSplitAudioFilesToAudioFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + rename_table :split_audio_files, :audio_files
4 + rename_table :split_audio_file_results, :splitfire_results
5 + end
6 +end
backend/musik88-web/db/migrate/20210916203902_rename_column_and_indexes.rb new
+7
@@ -0,0 +1,7 @@
1 +class RenameColumnAndIndexes < ActiveRecord::Migration[6.1]
2 + def change
3 + rename_column :splitfire_results, :split_audio_file_id, :audio_file_id
4 + rename_column :audio_file_bass64_statuses, :split_audio_file_id, :audio_file_id
5 + rename_column :bass_backing_tracks, :split_audio_file_id, :audio_file_id
6 + end
7 +end
backend/musik88-web/db/migrate/20210916210119_rename_active_storage_record_type.rb new
+6
@@ -0,0 +1,6 @@
1 +class RenameActiveStorageRecordType < ActiveRecord::Migration[6.1]
2 + def self.up
3 + execute "UPDATE active_storage_attachments a SET record_type = 'AudioFile' FROM active_storage_attachments b WHERE a.id = b.id AND a.record_type = 'SplitAudioFile';"
4 + execute "UPDATE active_storage_attachments a SET record_type = 'SplitfireResult' FROM active_storage_attachments b WHERE a.id = b.id AND a.record_type = 'SplitAudioFileResult';"
5 + end
6 +end
backend/musik88-web/db/migrate/20210918210038_create_audio_file_karokowe_statuses.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateAudioFileKarokoweStatuses < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :audio_file_karokowe_statuses do |t|
4 + t.references :audio_file, foreign_key: true
5 + t.integer :status
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20210919060442_modify_karaokr_file_table.rb new
+8
@@ -0,0 +1,8 @@
1 +class ModifyKaraokrFileTable < ActiveRecord::Migration[6.1]
2 + def change
3 + remove_reference :karaoke_files, :song, index: true, foreign_key: true
4 + remove_reference :karaoke_files, :user, index: true, foreign_key: true
5 + add_reference :karaoke_files, :audio_file, index: true, foreign_key: true
6 + add_column :karaoke_files, :is_public, :boolean, default: 0
7 + end
8 +end
backend/musik88-web/db/migrate/20210919080629_add_song_to_audio_files.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddSongToAudioFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :audio_files, :song, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20210919085330_create_vocal_files.rb new
+8
@@ -0,0 +1,8 @@
1 +class CreateVocalFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :vocal_files do |t|
4 + t.references :audio_file, index: true, foreign_key: true
5 + t.timestamps
6 + end
7 + end
8 +end
backend/musik88-web/db/migrate/20210919085346_create_bass_files.rb new
+8
@@ -0,0 +1,8 @@
1 +class CreateBassFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :bass_files do |t|
4 + t.references :audio_file, index: true, foreign_key: true
5 + t.timestamps
6 + end
7 + end
8 +end
backend/musik88-web/db/migrate/20210919092755_create_drum_files.rb new
+8
@@ -0,0 +1,8 @@
1 +class CreateDrumFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :drum_files do |t|
4 + t.references :audio_file, index: true, foreign_key: true
5 + t.timestamps
6 + end
7 + end
8 +end
backend/musik88-web/db/migrate/20210919092824_create_other_files.rb new
+8
@@ -0,0 +1,8 @@
1 +class CreateOtherFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :other_files do |t|
4 + t.references :audio_file, index: true, foreign_key: true
5 + t.timestamps
6 + end
7 + end
8 +end
backend/musik88-web/db/migrate/20210924220050_create_splitfire_settings.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateSplitfireSettings < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :splitfire_settings do |t|
4 + t.references :user, foreign_key: true
5 + t.integer :model
6 + t.integer :frequency
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20211009184244_create_drum_backing_tracks.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateDrumBackingTracks < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :drum_backing_tracks do |t|
4 + t.boolean :is_public
5 + t.references :audio_file, index: true, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20211009185231_create_audio_file_bonzo_statuses.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateAudioFileBonzoStatuses < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :audio_file_bonzo_statuses do |t|
4 + t.integer :status
5 + t.references :audio_file, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20211010221516_add_instagram_username_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddInstagramUsernameToUsers < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :users, :instagram, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20211012210416_create_audio_file_splitfire_statuses.rb new
+19
@@ -0,0 +1,19 @@
1 +class CreateAudioFileSplitfireStatuses < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :audio_file_splitfire_statuses do |t|
4 + t.references :audio_file, foreign_key: true
5 + t.integer :status
6 + t.integer :processing_progress, default: 0
7 +
8 + t.timestamps
9 + end
10 +
11 + remove_column :audio_files, :status, :integer
12 +
13 + add_column :audio_file_karokowe_statuses, :processing_progress, :integer, default: 0
14 +
15 + add_column :audio_file_bonzo_statuses, :processing_progress, :integer, default: 0
16 +
17 + add_column :audio_file_bass64_statuses, :processing_progress, :integer, default: 0
18 + end
19 +end
backend/musik88-web/db/migrate/20211013183531_add_is_public_to_audio_file.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddIsPublicToAudioFile < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :audio_files, :is_public, :boolean, default: false
4 + end
5 +end
backend/musik88-web/db/migrate/20211016111201_add_youtubbe_channel_id_to_users.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddYoutubbeChannelIdToUsers < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :users, :youtube_channel_id, :string, default: nil
4 + end
5 +end
backend/musik88-web/db/migrate/20211018193358_add_user_to_album.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUserToAlbum < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :albums, :user, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20211018193653_add_user_to_artist.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddUserToArtist < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :artists, :user, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20211021214947_add_youtube_video_id_to_audio_file.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddYoutubeVideoIdToAudioFile < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :audio_files, :youtube_video_url, :string, default: nil
4 + add_column :audio_files, :youtube_video_id, :string, default: nil
5 + end
6 +end
backend/musik88-web/db/migrate/20211112230051_create_relationships.rb new
+13
@@ -0,0 +1,13 @@
1 +class CreateRelationships < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :relationships do |t|
4 + t.integer :follower_id
5 + t.integer :followed_id
6 +
7 + t.timestamps
8 + end
9 + add_index :relationships, :follower_id
10 + add_index :relationships, :followed_id
11 + add_index :relationships, [:follower_id, :followed_id], unique: true
12 + end
13 +end
backend/musik88-web/db/migrate/20211130194715_drop_admin_user_table.rb new
+5
@@ -0,0 +1,5 @@
1 +class DropAdminUserTable < ActiveRecord::Migration[6.1]
2 + def change
3 + drop_table :admin_users
4 + end
5 +end
backend/musik88-web/db/migrate/20211214151340_add_title_to_chord.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddTitleToChord < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :chords, :title, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20211215225628_create_chord_votes.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateChordVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :chord_votes do |t|
4 + t.references :chord, foreign_key: true
5 + t.references :user, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20211216024558_add_about_to_user.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddAboutToUser < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :users, :about, :string, default: nil
4 + end
5 +end
backend/musik88-web/db/migrate/20211216120630_add_status_to_chord.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddStatusToChord < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :chords, :status, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/migrate/20211216131310_create_chord_histories.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateChordHistories < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :chord_histories do |t|
4 + t.references :chord, foreign_key: true
5 + t.references :user, foreign_key: true
6 + t.string :name
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20211227160313_create_guitar_backing_track_files.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateGuitarBackingTrackFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :guitar_backing_track_files do |t|
4 + t.references :audio_file, foreign_key: true
5 + t.integer :status, default: 0
6 + t.integer :status_progress, default: 0
7 + t.integer :views_count
8 +
9 + t.timestamps
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20211228114643_create_bass_backing_track_files.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateBassBackingTrackFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :bass_backing_track_files do |t|
4 + t.references :audio_file, foreign_key: true
5 + t.integer :status, default: 0
6 + t.integer :status_progress, default: 0
7 + t.integer :views_count
8 +
9 + t.timestamps
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20220107141101_create_album_images.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateAlbumImages < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :album_images do |t|
4 + t.references :album, foreign_key: true
5 + t.string :url
6 + t.integer :source
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20220107152030_create_artist_images.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateArtistImages < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :artist_images do |t|
4 + t.references :artist, foreign_key: true
5 + t.string :url
6 + t.integer :source
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20220109120902_alter_drum_backing_track.rb new
+7
@@ -0,0 +1,7 @@
1 +class AlterDrumBackingTrack < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :drum_backing_tracks, :status, :integer, default: 0
4 + add_column :drum_backing_tracks, :status_progress, :integer, default: 0
5 + add_column :drum_backing_tracks, :views_count, :integer, default: 0
6 + end
7 +end
backend/musik88-web/db/migrate/20220110213638_create_authorizations.rb new
+13
@@ -0,0 +1,13 @@
1 +class CreateAuthorizations < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :authorizations do |t|
4 + t.references :user, foreign_key: true
5 + t.string :provider
6 + t.string :uid
7 + t.string :token
8 + t.timestamp :token_expires_at, limit: 6
9 +
10 + t.timestamps
11 + end
12 + end
13 +end
backend/musik88-web/db/migrate/20220113221235_create_lyric_votes.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateLyricVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :lyric_votes do |t|
4 + t.references :lyric, foreign_key: true
5 + t.references :user, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20220114222707_create_comments.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateComments < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :comments do |t|
4 + t.text :body
5 + t.integer :commentable_id
6 + t.string :commentable_type
7 + t.references :user, foreign_key: true
8 +
9 + t.timestamps
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20220304225056_create_jwt_denylist.rb new
+9
@@ -0,0 +1,9 @@
1 +class CreateJwtDenylist < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :jwt_denylist do |t|
4 + t.string :jti, null: false
5 + t.datetime :exp, null: false
6 + end
7 + add_index :jwt_denylist, :jti
8 + end
9 +end
backend/musik88-web/db/migrate/20220515212331_create_lyric_synches.rb new
+12
@@ -0,0 +1,12 @@
1 +class CreateLyricSynches < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :lyric_synches do |t|
4 + t.references :audio_file, foreign_key: true
5 + t.references :user, foreign_key: true
6 + t.string :lyric
7 + t.string :time
8 +
9 + t.timestamps
10 + end
11 + end
12 +end
backend/musik88-web/db/migrate/20220606135634_change_default_column_lyric_synches.rb new
+6
@@ -0,0 +1,6 @@
1 +class ChangeDefaultColumnLyricSynches < ActiveRecord::Migration[6.1]
2 + def change
3 + change_column :lyric_synches, :created_at, :datetime, null: false, default: -> { 'CURRENT_TIMESTAMP' }
4 + change_column :lyric_synches, :updated_at, :datetime, null: false, default: -> { 'CURRENT_TIMESTAMP' }
5 + end
6 +end
backend/musik88-web/db/migrate/20220824220504_add_confirmable_to_devise.rb new
+19
@@ -0,0 +1,19 @@
1 +class AddConfirmableToDevise < ActiveRecord::Migration[6.1]
2 + def self.up
3 + add_column :users, :confirmation_token, :string
4 + add_column :users, :confirmed_at, :datetime
5 + add_column :users, :confirmation_sent_at, :datetime
6 + add_column :users, :unconfirmed_email, :string
7 +
8 + add_index :users, :confirmation_token, unique: true
9 + end
10 +
11 + def self.down
12 + remove_index :users, :confirmation_token
13 +
14 + remove_column :users, :unconfirmed_email
15 + remove_column :users, :confirmation_sent_at
16 + remove_column :users, :confirmed_at
17 + remove_column :users, :confirmation_token
18 + end
19 +end
backend/musik88-web/db/migrate/20220827164747_create_wallets.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateWallets < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :wallets do |t|
4 + t.references :user, foreign_key: true
5 + t.string :pubkey
6 + t.text :seed_phrase
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20220827205244_add_status_to_wallet.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddStatusToWallet < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :wallets, :status, :integer
4 + end
5 +end
backend/musik88-web/db/migrate/20220827221645_add_bach_token_account_to_wallet.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddBachTokenAccountToWallet < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :wallets, :bach_token_account, :string
4 + end
5 +end
backend/musik88-web/db/migrate/20220924083912_change_splitfire_settings_to_user_settings.rb new
+5
@@ -0,0 +1,5 @@
1 +class ChangeSplitfireSettingsToUserSettings < ActiveRecord::Migration[6.1]
2 + def change
3 + rename_table :splitfire_settings, :user_settings
4 + end
5 +end
backend/musik88-web/db/migrate/20220924084707_add_locale_to_user_settings.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddLocaleToUserSettings < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :user_settings, :locale, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/migrate/20220925162230_create_gending_votes.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateGendingVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :gending_votes do |t|
4 + t.references :user, foreign_key: true
5 + t.references :javanese_gending, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20220925162341_create_gending_notation_votes.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateGendingNotationVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :gending_notation_votes do |t|
4 + t.references :user, foreign_key: true
5 + t.references :javanese_gending_notation, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20220929192424_create_artist_votes.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateArtistVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :artist_votes do |t|
4 + t.references :user, foreign_key: true
5 + t.references :artist, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20220929200639_create_album_votes.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateAlbumVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :album_votes do |t|
4 + t.references :user, foreign_key: true
5 + t.references :album, foreign_key: true
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20221002102127_add_rich_format_to_javanese_gending_notation.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddRichFormatToJavaneseGendingNotation < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :javanese_gending_notations, :rich_format, :json
4 + end
5 +end
backend/musik88-web/db/migrate/20221009155351_create_artist_providers.rb new
+13
@@ -0,0 +1,13 @@
1 +class CreateArtistProviders < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :artist_providers do |t|
4 + t.references :user, foreign_key: true
5 + t.string :provider_id
6 + t.integer :provider_type
7 + t.string :name
8 + t.string :genres
9 +
10 + t.timestamps
11 + end
12 + end
13 +end
backend/musik88-web/db/migrate/20221009160129_add_sizes_and_provider_id_to_artist_image.rb new
+8
@@ -0,0 +1,8 @@
1 +class AddSizesAndProviderIdToArtistImage < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :artist_images, :artist_provider, foreign_key: true
4 + add_column :artist_images, :height, :integer
5 + add_column :artist_images, :width, :integer
6 + rename_column :artist_images, :source, :provider_type
7 + end
8 +end
backend/musik88-web/db/migrate/20221009170641_add_artist_to_artist_provider.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddArtistToArtistProvider < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :artist_providers, :artist, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20221030193237_fix_meta_pages.rb new
+8
@@ -0,0 +1,8 @@
1 +class FixMetaPages < ActiveRecord::Migration[6.1]
2 + def change
3 + rename_table :meta_pages, :meta
4 + add_column :meta, :title, :string
5 + add_column :meta, :description, :text
6 + add_column :meta, :locale, :string
7 + end
8 +end
backend/musik88-web/db/migrate/20221031125422_add_onboarding_status_to_authorization.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddOnboardingStatusToAuthorization < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :authorizations, :onboarding_status, :integer, default: 0
4 + add_column :authorizations, :onboarding_status_progress, :boolean, default: 0
5 + end
6 +end
backend/musik88-web/db/migrate/20221102183334_create_album_providers.rb new
+13
@@ -0,0 +1,13 @@
1 +class CreateAlbumProviders < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :album_providers do |t|
4 + t.references :user, foreign_key: true
5 + t.references :album, foreign_key: true
6 + t.string :provider_id
7 + t.integer :provider_type
8 + t.string :name
9 +
10 + t.timestamps
11 + end
12 + end
13 +end
backend/musik88-web/db/migrate/20221106115012_add_sizes_and_provider_id_to_album_image.rb new
+8
@@ -0,0 +1,8 @@
1 +class AddSizesAndProviderIdToAlbumImage < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :album_images, :album_provider, foreign_key: true
4 + add_column :album_images, :height, :integer
5 + add_column :album_images, :width, :integer
6 + rename_column :album_images, :source, :provider_type
7 + end
8 +end
backend/musik88-web/db/migrate/20221106123900_create_song_providers.rb new
+14
@@ -0,0 +1,14 @@
1 +class CreateSongProviders < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :song_providers do |t|
4 + t.references :user, foreign_key: true
5 + t.references :song, foreign_key: true
6 + t.string :provider_id
7 + t.integer :provider_type
8 + t.string :name
9 + t.string :preview_url
10 +
11 + t.timestamps
12 + end
13 + end
14 +end
backend/musik88-web/db/migrate/20221108221029_devise_invitable_add_to_users.rb new
+22
@@ -0,0 +1,22 @@
1 +class DeviseInvitableAddToUsers < ActiveRecord::Migration[6.1]
2 + def up
3 + change_table :users do |t|
4 + t.string :invitation_token
5 + t.datetime :invitation_created_at
6 + t.datetime :invitation_sent_at
7 + t.datetime :invitation_accepted_at
8 + t.integer :invitation_limit
9 + t.references :invited_by, polymorphic: true
10 + t.integer :invitations_count, default: 0
11 + t.index :invitation_token, unique: true # for invitable
12 + t.index :invited_by_id
13 + end
14 + end
15 +
16 + def down
17 + change_table :users do |t|
18 + t.remove_references :invited_by, polymorphic: true
19 + t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
20 + end
21 + end
22 +end
backend/musik88-web/db/migrate/20221110204807_add_type_to_wallets.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddTypeToWallets < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :wallets, :wallet_type, :integer, default: 1
4 + end
5 +end
backend/musik88-web/db/migrate/20221113185442_add_synch_spotify_to_user_settings.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddSynchSpotifyToUserSettings < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :user_settings, :sync_spotify, :integer, default: 1
4 + end
5 +end
backend/musik88-web/db/migrate/20221121220656_add_album_provider_to_song_provider.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddAlbumProviderToSongProvider < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :song_providers, :album_provider, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20221126123607_add_song_provider_and_artist_provider_to_song_artist.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddSongProviderAndArtistProviderToSongArtist < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :song_artists, :song_provider, foreign_key: true
4 + add_reference :song_artists, :artist_provider, foreign_key: true
5 + end
6 +end
backend/musik88-web/db/migrate/20221126150634_create_merit_actions.rb new
+17
@@ -0,0 +1,17 @@
1 +class CreateMeritActions < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :merit_actions do |t|
4 + t.integer :user_id
5 + t.string :action_method
6 + t.integer :action_value
7 + t.boolean :had_errors, default: false
8 + t.string :target_model
9 + t.integer :target_id
10 + t.text :target_data
11 + t.boolean :processed, default: false
12 + t.timestamps null: false
13 + end
14 +
15 + add_index :merit_actions, :processed
16 + end
17 +end
backend/musik88-web/db/migrate/20221126150635_create_merit_activity_logs.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateMeritActivityLogs < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :merit_activity_logs do |t|
4 + t.integer :action_id
5 + t.string :related_change_type
6 + t.integer :related_change_id
7 + t.string :description
8 + t.datetime :created_at
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20221126150636_create_sashes.rb new
+7
@@ -0,0 +1,7 @@
1 +class CreateSashes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :sashes do |t|
4 + t.timestamps null: false
5 + end
6 + end
7 +end
backend/musik88-web/db/migrate/20221126150637_create_badges_sashes.rb new
+16
@@ -0,0 +1,16 @@
1 +class CreateBadgesSashes < ActiveRecord::Migration[6.1]
2 + def self.up
3 + create_table :badges_sashes do |t|
4 + t.integer :badge_id, :sash_id
5 + t.boolean :notified_user, default: false
6 + t.datetime :created_at
7 + end
8 + add_index :badges_sashes, [:badge_id, :sash_id]
9 + add_index :badges_sashes, :badge_id
10 + add_index :badges_sashes, :sash_id
11 + end
12 +
13 + def self.down
14 + drop_table :badges_sashes
15 + end
16 +end
backend/musik88-web/db/migrate/20221126150638_create_scores_and_points.rb new
+15
@@ -0,0 +1,15 @@
1 +class CreateScoresAndPoints < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :merit_scores do |t|
4 + t.references :sash
5 + t.string :category, default: 'default'
6 + end
7 +
8 + create_table :merit_score_points do |t|
9 + t.references :score
10 + t.bigint :num_points, default: 0
11 + t.string :log
12 + t.datetime :created_at
13 + end
14 + end
15 +end
backend/musik88-web/db/migrate/20221126150716_add_merit_fields_to_users.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddMeritFieldsToUsers < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :users, :sash_id, :integer
4 + add_column :users, :level, :integer, :default => 0
5 + end
6 +end
backend/musik88-web/db/migrate/20221211142809_add_category_to_page.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddCategoryToPage < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :pages, :category, :integer
4 + end
5 +end
backend/musik88-web/db/migrate/20230107150724_add_artist_provider_to_song_provider.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddArtistProviderToSongProvider < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :song_providers, :artist_provider, foreign_key: true
4 + end
5 +end
backend/musik88-web/db/migrate/20230107165048_create_chords_song_providers_join_table.rb new
+8
@@ -0,0 +1,8 @@
1 +class CreateChordsSongProvidersJoinTable < ActiveRecord::Migration[6.1]
2 + def change
3 + create_join_table :chords, :song_providers do |t|
4 + t.index :chord_id
5 + t.index :song_provider_id
6 + end
7 + end
8 +end
backend/musik88-web/db/migrate/20230513184028_add_column_allowed_use_google_token_to_user_settings.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddColumnAllowedUseGoogleTokenToUserSettings < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :user_settings, :allowed_use_google_token, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/migrate/20230513195427_add_column_allowed_use_spotify_token_to_user_settings.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddColumnAllowedUseSpotifyTokenToUserSettings < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :user_settings, :allowed_use_spotify_token, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/migrate/20230517191439_update_audio_files.rb new
+10
@@ -0,0 +1,10 @@
1 +class UpdateAudioFiles < ActiveRecord::Migration[6.1]
2 + def change
3 + add_reference :audio_files, :song_provider, index: true
4 + add_column :audio_files, :status, :integer, default: 0
5 + remove_reference :audio_files, :song, index: true, foreign_key: true
6 + remove_column :audio_files, :is_public, :boolean
7 + remove_column :audio_files, :youtube_video_id, :string
8 + remove_column :audio_files, :youtube_video_url, :string
9 + end
10 +end
backend/musik88-web/db/migrate/20230518155843_create_song_provider_votes.rb new
+11
@@ -0,0 +1,11 @@
1 +class CreateSongProviderVotes < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :song_provider_votes do |t|
4 + t.references :song_provider, foreign_key: true
5 + t.references :user, foreign_key: true
6 + t.integer :vote_type, default: 0
7 +
8 + t.timestamps
9 + end
10 + end
11 +end
backend/musik88-web/db/migrate/20230527104320_add_refresh_token_to_authorization.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddRefreshTokenToAuthorization < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :authorizations, :refresh_token, :string
4 + remove_column :authorizations, :token_expires_at, :timestamp
5 + end
6 +end
backend/musik88-web/db/migrate/20230527184120_modify_song_providers.rb new
+11
@@ -0,0 +1,11 @@
1 +class ModifySongProviders < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :song_providers, :description, :string
4 + add_column :song_providers, :image_url, :string
5 + change_column :song_providers, :created_at, :datetime, null: false, default: -> { 'CURRENT_TIMESTAMP' }
6 + change_column :song_providers, :updated_at, :datetime, null: false, default: -> { 'CURRENT_TIMESTAMP' }
7 + remove_column :song_providers, :album_provider_id
8 + remove_column :song_providers, :artist_provider_id
9 + add_index :song_providers, %i[provider_id provider_type], unique: true
10 + end
11 +end
backend/musik88-web/db/migrate/20230528074313_drop_image_tables.rb new
+6
@@ -0,0 +1,6 @@
1 +class DropImageTables < ActiveRecord::Migration[6.1]
2 + def change
3 + drop_table :album_images
4 + drop_table :artist_images
5 + end
6 +end
backend/musik88-web/db/migrate/20230604105229_audio_file_process_update.rb new
+7
@@ -0,0 +1,7 @@
1 +class AudioFileProcessUpdate < ActiveRecord::Migration[6.1]
2 + def change
3 + rename_table :audio_file_splitfire_statuses, :audio_file_split_statuses
4 + add_column :audio_files, :progress, :integer, default: 0
5 + remove_column :audio_files, :user_id
6 + end
7 +end
backend/musik88-web/db/migrate/20230724140038_change_song_id_to_song_provider_id_for_chord.rb new
+6
@@ -0,0 +1,6 @@
1 +class ChangeSongIdToSongProviderIdForChord < ActiveRecord::Migration[6.1]
2 + def change
3 + remove_reference :chords, :song, index: true, foreign_key: true
4 + add_reference :chords, :song_provider, index: true, foreign_key: true
5 + end
6 +end
backend/musik88-web/db/migrate/20231007224050_create_search_terms.rb new
+10
@@ -0,0 +1,10 @@
1 +class CreateSearchTerms < ActiveRecord::Migration[6.1]
2 + def change
3 + create_table :search_terms do |t|
4 + t.string :term
5 + t.integer :count
6 +
7 + t.timestamps
8 + end
9 + end
10 +end
backend/musik88-web/db/migrate/20231011135933_add_length_and_onset_to_splitfire_results.rb new
+6
@@ -0,0 +1,6 @@
1 +class AddLengthAndOnsetToSplitfireResults < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :splitfire_results, :length, :float
4 + add_column :splitfire_results, :onset, :json
5 + end
6 +end
backend/musik88-web/db/migrate/20240205114851_add_status_to_song_providers.rb new
+5
@@ -0,0 +1,5 @@
1 +class AddStatusToSongProviders < ActiveRecord::Migration[6.1]
2 + def change
3 + add_column :song_providers, :status, :integer, default: 0
4 + end
5 +end
backend/musik88-web/db/schema.rb new
+745
@@ -0,0 +1,745 @@
1 +# This file is auto-generated from the current state of the database. Instead
2 +# of editing this file, please use the migrations feature of Active Record to
3 +# incrementally modify your database, and then regenerate this schema definition.
4 +#
5 +# This file is the source Rails uses to define your schema when running `bin/rails
6 +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 +# be faster and is potentially less error prone than running all of your
8 +# migrations from scratch. Old migrations may fail to apply correctly if those
9 +# migrations use external dependencies or application code.
10 +#
11 +# It's strongly recommended that you check this file into your version control system.
12 +
13 +ActiveRecord::Schema.define(version: 2024_02_05_114851) do
14 +
15 + # These are extensions that must be enabled in order to support this database
16 + enable_extension "plpgsql"
17 +
18 + create_table "active_admin_comments", force: :cascade do |t|
19 + t.string "namespace"
20 + t.text "body"
21 + t.string "resource_type"
22 + t.bigint "resource_id"
23 + t.string "author_type"
24 + t.bigint "author_id"
25 + t.datetime "created_at", null: false
26 + t.datetime "updated_at", null: false
27 + t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id"
28 + t.index ["namespace"], name: "index_active_admin_comments_on_namespace"
29 + t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id"
30 + end
31 +
32 + create_table "active_storage_attachments", force: :cascade do |t|
33 + t.string "name", null: false
34 + t.string "record_type", null: false
35 + t.bigint "record_id", null: false
36 + t.bigint "blob_id", null: false
37 + t.datetime "created_at", null: false
38 + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
39 + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
40 + end
41 +
42 + create_table "active_storage_blobs", force: :cascade do |t|
43 + t.string "key", null: false
44 + t.string "filename", null: false
45 + t.string "content_type"
46 + t.text "metadata"
47 + t.bigint "byte_size", null: false
48 + t.string "checksum", null: false
49 + t.datetime "created_at", null: false
50 + t.string "service_name", null: false
51 + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
52 + end
53 +
54 + create_table "active_storage_variant_records", force: :cascade do |t|
55 + t.bigint "blob_id", null: false
56 + t.string "variation_digest", null: false
57 + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
58 + end
59 +
60 + create_table "album_artists", force: :cascade do |t|
61 + t.boolean "primary"
62 + t.bigint "album_id"
63 + t.bigint "artist_id"
64 + t.datetime "created_at", null: false
65 + t.datetime "updated_at", null: false
66 + t.index ["album_id"], name: "index_album_artists_on_album_id"
67 + t.index ["artist_id"], name: "index_album_artists_on_artist_id"
68 + end
69 +
70 + create_table "album_providers", force: :cascade do |t|
71 + t.bigint "user_id"
72 + t.bigint "album_id"
73 + t.string "provider_id"
74 + t.integer "provider_type"
75 + t.string "name"
76 + t.datetime "created_at", precision: 6, null: false
77 + t.datetime "updated_at", precision: 6, null: false
78 + t.index ["album_id"], name: "index_album_providers_on_album_id"
79 + t.index ["user_id"], name: "index_album_providers_on_user_id"
80 + end
81 +
82 + create_table "album_votes", force: :cascade do |t|
83 + t.bigint "user_id"
84 + t.bigint "album_id"
85 + t.datetime "created_at", precision: 6, null: false
86 + t.datetime "updated_at", precision: 6, null: false
87 + t.index ["album_id"], name: "index_album_votes_on_album_id"
88 + t.index ["user_id"], name: "index_album_votes_on_user_id"
89 + end
90 +
91 + create_table "albums", force: :cascade do |t|
92 + t.string "name"
93 + t.text "description"
94 + t.string "locale"
95 + t.datetime "created_at", null: false
96 + t.datetime "updated_at", null: false
97 + t.integer "views_count", default: 0
98 + t.bigint "user_id"
99 + t.index ["user_id"], name: "index_albums_on_user_id"
100 + end
101 +
102 + create_table "artist_providers", force: :cascade do |t|
103 + t.bigint "user_id"
104 + t.string "provider_id"
105 + t.integer "provider_type"
106 + t.string "name"
107 + t.string "genres"
108 + t.datetime "created_at", precision: 6, null: false
109 + t.datetime "updated_at", precision: 6, null: false
110 + t.bigint "artist_id"
111 + t.index ["artist_id"], name: "index_artist_providers_on_artist_id"
112 + t.index ["user_id"], name: "index_artist_providers_on_user_id"
113 + end
114 +
115 + create_table "artist_votes", force: :cascade do |t|
116 + t.bigint "user_id"
117 + t.bigint "artist_id"
118 + t.datetime "created_at", precision: 6, null: false
119 + t.datetime "updated_at", precision: 6, null: false
120 + t.index ["artist_id"], name: "index_artist_votes_on_artist_id"
121 + t.index ["user_id"], name: "index_artist_votes_on_user_id"
122 + end
123 +
124 + create_table "artists", force: :cascade do |t|
125 + t.string "name"
126 + t.text "description"
127 + t.datetime "created_at", null: false
128 + t.datetime "updated_at", null: false
129 + t.integer "views_count", default: 0
130 + t.bigint "user_id"
131 + t.index ["user_id"], name: "index_artists_on_user_id"
132 + end
133 +
134 + create_table "audio_file_bass64_statuses", force: :cascade do |t|
135 + t.bigint "audio_file_id"
136 + t.integer "status"
137 + t.datetime "created_at", precision: 6, null: false
138 + t.datetime "updated_at", precision: 6, null: false
139 + t.integer "processing_progress", default: 0
140 + t.index ["audio_file_id"], name: "index_audio_file_bass64_statuses_on_audio_file_id"
141 + end
142 +
143 + create_table "audio_file_bonzo_statuses", force: :cascade do |t|
144 + t.integer "status"
145 + t.bigint "audio_file_id"
146 + t.datetime "created_at", precision: 6, null: false
147 + t.datetime "updated_at", precision: 6, null: false
148 + t.integer "processing_progress", default: 0
149 + t.index ["audio_file_id"], name: "index_audio_file_bonzo_statuses_on_audio_file_id"
150 + end
151 +
152 + create_table "audio_file_karokowe_statuses", force: :cascade do |t|
153 + t.bigint "audio_file_id"
154 + t.integer "status"
155 + t.datetime "created_at", precision: 6, null: false
156 + t.datetime "updated_at", precision: 6, null: false
157 + t.integer "processing_progress", default: 0
158 + t.index ["audio_file_id"], name: "index_audio_file_karokowe_statuses_on_audio_file_id"
159 + end
160 +
161 + create_table "audio_file_split_statuses", force: :cascade do |t|
162 + t.bigint "audio_file_id"
163 + t.integer "status"
164 + t.integer "processing_progress", default: 0
165 + t.datetime "created_at", precision: 6, null: false
166 + t.datetime "updated_at", precision: 6, null: false
167 + t.index ["audio_file_id"], name: "index_audio_file_split_statuses_on_audio_file_id"
168 + end
169 +
170 + create_table "audio_files", force: :cascade do |t|
171 + t.datetime "created_at", precision: 6, null: false
172 + t.datetime "updated_at", precision: 6, null: false
173 + t.bigint "song_provider_id"
174 + t.integer "status", default: 0
175 + t.integer "progress", default: 0
176 + t.index ["song_provider_id"], name: "index_audio_files_on_song_provider_id"
177 + end
178 +
179 + create_table "authorizations", force: :cascade do |t|
180 + t.bigint "user_id"
181 + t.string "provider"
182 + t.string "uid"
183 + t.string "token"
184 + t.datetime "created_at", precision: 6, null: false
185 + t.datetime "updated_at", precision: 6, null: false
186 + t.integer "onboarding_status", default: 0
187 + t.boolean "onboarding_status_progress", default: false
188 + t.string "refresh_token"
189 + t.index ["user_id"], name: "index_authorizations_on_user_id"
190 + end
191 +
192 + create_table "badges_sashes", force: :cascade do |t|
193 + t.integer "badge_id"
194 + t.integer "sash_id"
195 + t.boolean "notified_user", default: false
196 + t.datetime "created_at"
197 + t.index ["badge_id", "sash_id"], name: "index_badges_sashes_on_badge_id_and_sash_id"
198 + t.index ["badge_id"], name: "index_badges_sashes_on_badge_id"
199 + t.index ["sash_id"], name: "index_badges_sashes_on_sash_id"
200 + end
201 +
202 + create_table "bass_backing_track_files", force: :cascade do |t|
203 + t.bigint "audio_file_id"
204 + t.integer "status", default: 0
205 + t.integer "status_progress", default: 0
206 + t.integer "views_count"
207 + t.datetime "created_at", precision: 6, null: false
208 + t.datetime "updated_at", precision: 6, null: false
209 + t.index ["audio_file_id"], name: "index_bass_backing_track_files_on_audio_file_id"
210 + end
211 +
212 + create_table "bass_backing_tracks", force: :cascade do |t|
213 + t.boolean "is_public"
214 + t.datetime "created_at", precision: 6, null: false
215 + t.datetime "updated_at", precision: 6, null: false
216 + t.bigint "audio_file_id"
217 + t.index ["audio_file_id"], name: "index_bass_backing_tracks_on_audio_file_id"
218 + end
219 +
220 + create_table "bass_files", force: :cascade do |t|
221 + t.bigint "audio_file_id"
222 + t.datetime "created_at", precision: 6, null: false
223 + t.datetime "updated_at", precision: 6, null: false
224 + t.index ["audio_file_id"], name: "index_bass_files_on_audio_file_id"
225 + end
226 +
227 + create_table "chord_histories", force: :cascade do |t|
228 + t.bigint "chord_id"
229 + t.bigint "user_id"
230 + t.string "name"
231 + t.datetime "created_at", precision: 6, null: false
232 + t.datetime "updated_at", precision: 6, null: false
233 + t.index ["chord_id"], name: "index_chord_histories_on_chord_id"
234 + t.index ["user_id"], name: "index_chord_histories_on_user_id"
235 + end
236 +
237 + create_table "chord_shapes", force: :cascade do |t|
238 + t.string "name"
239 + t.string "youtube_link"
240 + t.string "image_link"
241 + t.datetime "created_at", null: false
242 + t.datetime "updated_at", null: false
243 + end
244 +
245 + create_table "chord_votes", force: :cascade do |t|
246 + t.bigint "chord_id"
247 + t.bigint "user_id"
248 + t.datetime "created_at", precision: 6, null: false
249 + t.datetime "updated_at", precision: 6, null: false
250 + t.index ["chord_id"], name: "index_chord_votes_on_chord_id"
251 + t.index ["user_id"], name: "index_chord_votes_on_user_id"
252 + end
253 +
254 + create_table "chords", force: :cascade do |t|
255 + t.text "chord"
256 + t.integer "views_count", default: 0
257 + t.datetime "created_at", null: false
258 + t.datetime "updated_at", null: false
259 + t.bigint "user_id"
260 + t.string "title"
261 + t.integer "status", default: 0
262 + t.bigint "song_provider_id"
263 + t.index ["song_provider_id"], name: "index_chords_on_song_provider_id"
264 + t.index ["user_id"], name: "index_chords_on_user_id"
265 + end
266 +
267 + create_table "chords_song_providers", id: false, force: :cascade do |t|
268 + t.bigint "chord_id", null: false
269 + t.bigint "song_provider_id", null: false
270 + t.index ["chord_id"], name: "index_chords_song_providers_on_chord_id"
271 + t.index ["song_provider_id"], name: "index_chords_song_providers_on_song_provider_id"
272 + end
273 +
274 + create_table "comments", force: :cascade do |t|
275 + t.text "body"
276 + t.integer "commentable_id"
277 + t.string "commentable_type"
278 + t.bigint "user_id"
279 + t.datetime "created_at", precision: 6, null: false
280 + t.datetime "updated_at", precision: 6, null: false
281 + t.index ["user_id"], name: "index_comments_on_user_id"
282 + end
283 +
284 + create_table "drum_backing_tracks", force: :cascade do |t|
285 + t.boolean "is_public"
286 + t.bigint "audio_file_id"
287 + t.datetime "created_at", precision: 6, null: false
288 + t.datetime "updated_at", precision: 6, null: false
289 + t.integer "status", default: 0
290 + t.integer "status_progress", default: 0
291 + t.integer "views_count", default: 0
292 + t.index ["audio_file_id"], name: "index_drum_backing_tracks_on_audio_file_id"
293 + end
294 +
295 + create_table "drum_files", force: :cascade do |t|
296 + t.bigint "audio_file_id"
297 + t.datetime "created_at", precision: 6, null: false
298 + t.datetime "updated_at", precision: 6, null: false
299 + t.index ["audio_file_id"], name: "index_drum_files_on_audio_file_id"
300 + end
301 +
302 + create_table "gending_notation_votes", force: :cascade do |t|
303 + t.bigint "user_id"
304 + t.bigint "javanese_gending_notation_id"
305 + t.datetime "created_at", precision: 6, null: false
306 + t.datetime "updated_at", precision: 6, null: false
307 + t.index ["javanese_gending_notation_id"], name: "index_gending_notation_votes_on_javanese_gending_notation_id"
308 + t.index ["user_id"], name: "index_gending_notation_votes_on_user_id"
309 + end
310 +
311 + create_table "gending_votes", force: :cascade do |t|
312 + t.bigint "user_id"
313 + t.bigint "javanese_gending_id"
314 + t.datetime "created_at", precision: 6, null: false
315 + t.datetime "updated_at", precision: 6, null: false
316 + t.index ["javanese_gending_id"], name: "index_gending_votes_on_javanese_gending_id"
317 + t.index ["user_id"], name: "index_gending_votes_on_user_id"
318 + end
319 +
320 + create_table "guitar_backing_track_files", force: :cascade do |t|
321 + t.bigint "audio_file_id"
322 + t.integer "status", default: 0
323 + t.integer "status_progress", default: 0
324 + t.integer "views_count"
325 + t.datetime "created_at", precision: 6, null: false
326 + t.datetime "updated_at", precision: 6, null: false
327 + t.index ["audio_file_id"], name: "index_guitar_backing_track_files_on_audio_file_id"
328 + end
329 +
330 + create_table "javanese_gending_notations", force: :cascade do |t|
331 + t.string "name"
332 + t.text "notation"
333 + t.integer "views_count"
334 + t.boolean "is_published"
335 + t.bigint "user_id"
336 + t.bigint "javanese_gending_id"
337 + t.datetime "created_at", precision: 6, null: false
338 + t.datetime "updated_at", precision: 6, null: false
339 + t.json "rich_format"
340 + t.index ["javanese_gending_id"], name: "index_javanese_gending_notations_on_javanese_gending_id"
341 + t.index ["user_id"], name: "index_javanese_gending_notations_on_user_id"
342 + end
343 +
344 + create_table "javanese_gendings", force: :cascade do |t|
345 + t.string "name"
346 + t.text "description"
347 + t.text "notation"
348 + t.integer "views_count", default: 0
349 + t.datetime "created_at", null: false
350 + t.datetime "updated_at", null: false
351 + t.boolean "is_published", default: false
352 + t.bigint "user_id"
353 + t.index ["user_id"], name: "index_javanese_gendings_on_user_id"
354 + end
355 +
356 + create_table "jwt_denylist", force: :cascade do |t|
357 + t.string "jti", null: false
358 + t.datetime "exp", null: false
359 + t.index ["jti"], name: "index_jwt_denylist_on_jti"
360 + end
361 +
362 + create_table "karaoke_file_lovers", force: :cascade do |t|
363 + t.bigint "karaoke_file_id"
364 + t.bigint "user_id"
365 + t.datetime "created_at", precision: 6, null: false
366 + t.datetime "updated_at", precision: 6, null: false
367 + t.integer "clicks_count"
368 + t.index ["karaoke_file_id"], name: "index_karaoke_file_lovers_on_karaoke_file_id"
369 + t.index ["user_id"], name: "index_karaoke_file_lovers_on_user_id"
370 + end
371 +
372 + create_table "karaoke_files", force: :cascade do |t|
373 + t.string "link"
374 + t.datetime "created_at", precision: 6, null: false
375 + t.datetime "updated_at", precision: 6, null: false
376 + t.integer "clicks_count", default: 0
377 + t.bigint "audio_file_id"
378 + t.boolean "is_public", default: false
379 + t.index ["audio_file_id"], name: "index_karaoke_files_on_audio_file_id"
380 + end
381 +
382 + create_table "lyric_synches", force: :cascade do |t|
383 + t.bigint "audio_file_id"
384 + t.bigint "user_id"
385 + t.string "lyric"
386 + t.string "time"
387 + t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
388 + t.datetime "updated_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
389 + t.index ["audio_file_id"], name: "index_lyric_synches_on_audio_file_id"
390 + t.index ["user_id"], name: "index_lyric_synches_on_user_id"
391 + end
392 +
393 + create_table "lyric_votes", force: :cascade do |t|
394 + t.bigint "lyric_id"
395 + t.bigint "user_id"
396 + t.datetime "created_at", precision: 6, null: false
397 + t.datetime "updated_at", precision: 6, null: false
398 + t.index ["lyric_id"], name: "index_lyric_votes_on_lyric_id"
399 + t.index ["user_id"], name: "index_lyric_votes_on_user_id"
400 + end
401 +
402 + create_table "lyrics", force: :cascade do |t|
403 + t.text "lyric"
404 + t.string "locale"
405 + t.bigint "song_id"
406 + t.datetime "created_at", null: false
407 + t.datetime "updated_at", null: false
408 + t.integer "views_count", default: 0
409 + t.bigint "user_id"
410 + t.boolean "is_published", default: false
411 + t.index ["user_id"], name: "index_lyrics_on_user_id"
412 + end
413 +
414 + create_table "merit_actions", force: :cascade do |t|
415 + t.integer "user_id"
416 + t.string "action_method"
417 + t.integer "action_value"
418 + t.boolean "had_errors", default: false
419 + t.string "target_model"
420 + t.integer "target_id"
421 + t.text "target_data"
422 + t.boolean "processed", default: false
423 + t.datetime "created_at", precision: 6, null: false
424 + t.datetime "updated_at", precision: 6, null: false
425 + t.index ["processed"], name: "index_merit_actions_on_processed"
426 + end
427 +
428 + create_table "merit_activity_logs", force: :cascade do |t|
429 + t.integer "action_id"
430 + t.string "related_change_type"
431 + t.integer "related_change_id"
432 + t.string "description"
433 + t.datetime "created_at"
434 + end
435 +
436 + create_table "merit_score_points", force: :cascade do |t|
437 + t.bigint "score_id"
438 + t.bigint "num_points", default: 0
439 + t.string "log"
440 + t.datetime "created_at"
441 + t.index ["score_id"], name: "index_merit_score_points_on_score_id"
442 + end
443 +
444 + create_table "merit_scores", force: :cascade do |t|
445 + t.bigint "sash_id"
446 + t.string "category", default: "default"
447 + t.index ["sash_id"], name: "index_merit_scores_on_sash_id"
448 + end
449 +
450 + create_table "meta", id: :bigint, default: -> { "nextval('meta_pages_id_seq'::regclass)" }, force: :cascade do |t|
451 + t.string "home"
452 + t.string "title"
453 + t.text "description"
454 + t.datetime "created_at", precision: 6, null: false
455 + t.datetime "updated_at", precision: 6, null: false
456 + t.string "locale"
457 + end
458 +
459 + create_table "meta_pages", id: :serial, force: :cascade do |t|
460 + end
461 +
462 + create_table "other_files", force: :cascade do |t|
463 + t.bigint "audio_file_id"
464 + t.datetime "created_at", precision: 6, null: false
465 + t.datetime "updated_at", precision: 6, null: false
466 + t.index ["audio_file_id"], name: "index_other_files_on_audio_file_id"
467 + end
468 +
469 + create_table "pages", force: :cascade do |t|
470 + t.string "page_slug"
471 + t.text "page_content"
472 + t.string "locale"
473 + t.datetime "created_at", null: false
474 + t.datetime "updated_at", null: false
475 + t.string "title"
476 + t.integer "category"
477 + end
478 +
479 + create_table "relationships", force: :cascade do |t|
480 + t.integer "follower_id"
481 + t.integer "followed_id"
482 + t.datetime "created_at", precision: 6, null: false
483 + t.datetime "updated_at", precision: 6, null: false
484 + t.index ["followed_id"], name: "index_relationships_on_followed_id"
485 + t.index ["follower_id", "followed_id"], name: "index_relationships_on_follower_id_and_followed_id", unique: true
486 + t.index ["follower_id"], name: "index_relationships_on_follower_id"
487 + end
488 +
489 + create_table "sashes", force: :cascade do |t|
490 + t.datetime "created_at", precision: 6, null: false
491 + t.datetime "updated_at", precision: 6, null: false
492 + end
493 +
494 + create_table "search_terms", force: :cascade do |t|
495 + t.string "term"
496 + t.integer "count"
497 + t.datetime "created_at", precision: 6, null: false
498 + t.datetime "updated_at", precision: 6, null: false
499 + end
500 +
501 + create_table "song_artists", force: :cascade do |t|
502 + t.boolean "primary"
503 + t.bigint "song_id"
504 + t.bigint "artist_id"
505 + t.datetime "created_at", null: false
506 + t.datetime "updated_at", null: false
507 + t.bigint "song_provider_id"
508 + t.bigint "artist_provider_id"
509 + t.index ["artist_id"], name: "index_song_artists_on_artist_id"
510 + t.index ["artist_provider_id"], name: "index_song_artists_on_artist_provider_id"
511 + t.index ["song_id"], name: "index_song_artists_on_song_id"
512 + t.index ["song_provider_id"], name: "index_song_artists_on_song_provider_id"
513 + end
514 +
515 + create_table "song_chord_pleas", force: :cascade do |t|
516 + t.bigint "song_id"
517 + t.bigint "user_id"
518 + t.index ["song_id"], name: "index_song_chord_pleas_on_song_id"
519 + t.index ["user_id"], name: "index_song_chord_pleas_on_user_id"
520 + end
521 +
522 + create_table "song_provider_chords", force: :cascade do |t|
523 + t.bigint "song_provider_id"
524 + t.bigint "chord_id"
525 + t.integer "status", default: 0
526 + t.datetime "created_at", precision: 6, null: false
527 + t.datetime "updated_at", precision: 6, null: false
528 + t.index ["chord_id"], name: "index_song_provider_chords_on_chord_id"
529 + t.index ["song_provider_id"], name: "index_song_provider_chords_on_song_provider_id"
530 + end
531 +
532 + create_table "song_provider_votes", force: :cascade do |t|
533 + t.bigint "song_provider_id"
534 + t.bigint "user_id"
535 + t.integer "vote_type", default: 0
536 + t.datetime "created_at", precision: 6, null: false
537 + t.datetime "updated_at", precision: 6, null: false
538 + t.index ["song_provider_id"], name: "index_song_provider_votes_on_song_provider_id"
539 + t.index ["user_id"], name: "index_song_provider_votes_on_user_id"
540 + end
541 +
542 + create_table "song_providers", force: :cascade do |t|
543 + t.bigint "user_id"
544 + t.bigint "song_id"
545 + t.string "provider_id"
546 + t.integer "provider_type"
547 + t.string "name"
548 + t.string "preview_url"
549 + t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
550 + t.datetime "updated_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
551 + t.string "description"
552 + t.string "image_url"
553 + t.integer "status", default: 0
554 + t.index ["provider_id", "provider_type"], name: "index_song_providers_on_provider_id_and_provider_type", unique: true
555 + t.index ["song_id"], name: "index_song_providers_on_song_id"
556 + t.index ["user_id"], name: "index_song_providers_on_user_id"
557 + end
558 +
559 + create_table "songs", force: :cascade do |t|
560 + t.string "name"
561 + t.text "description"
562 + t.string "locale"
563 + t.datetime "created_at", null: false
564 + t.datetime "updated_at", null: false
565 + t.bigint "album_id"
566 + t.integer "views_count", default: 0
567 + t.bigint "user_id"
568 + t.index ["album_id"], name: "index_songs_on_album_id"
569 + t.index ["user_id"], name: "index_songs_on_user_id"
570 + end
571 +
572 + create_table "splitfire_results", force: :cascade do |t|
573 + t.bigint "audio_file_id"
574 + t.datetime "created_at", precision: 6, null: false
575 + t.datetime "updated_at", precision: 6, null: false
576 + t.float "length"
577 + t.json "onset"
578 + t.index ["audio_file_id"], name: "index_splitfire_results_on_audio_file_id"
579 + end
580 +
581 + create_table "tolk_locales", id: :serial, force: :cascade do |t|
582 + t.string "name"
583 + t.datetime "created_at"
584 + t.datetime "updated_at"
585 + t.index ["name"], name: "index_tolk_locales_on_name", unique: true
586 + end
587 +
588 + create_table "tolk_phrases", id: :serial, force: :cascade do |t|
589 + t.text "key"
590 + t.datetime "created_at"
591 + t.datetime "updated_at"
592 + end
593 +
594 + create_table "tolk_translations", id: :serial, force: :cascade do |t|
595 + t.integer "phrase_id"
596 + t.integer "locale_id"
597 + t.text "text"
598 + t.text "previous_text"
599 + t.boolean "primary_updated", default: false
600 + t.datetime "created_at"
601 + t.datetime "updated_at"
602 + t.index ["phrase_id", "locale_id"], name: "index_tolk_translations_on_phrase_id_and_locale_id", unique: true
603 + end
604 +
605 + create_table "user_settings", force: :cascade do |t|
606 + t.bigint "user_id"
607 + t.integer "model"
608 + t.integer "frequency"
609 + t.datetime "created_at", precision: 6, null: false
610 + t.datetime "updated_at", precision: 6, null: false
611 + t.integer "locale", default: 0
612 + t.integer "sync_spotify", default: 1
613 + t.integer "allowed_use_google_token", default: 0
614 + t.integer "allowed_use_spotify_token", default: 0
615 + t.index ["user_id"], name: "index_user_settings_on_user_id"
616 + end
617 +
618 + create_table "users", force: :cascade do |t|
619 + t.string "name"
620 + t.string "email"
621 + t.datetime "created_at", null: false
622 + t.datetime "updated_at", null: false
623 + t.string "remember_digest"
624 + t.string "username"
625 + t.text "image"
626 + t.string "provider"
627 + t.string "uid"
628 + t.boolean "is_private", default: false
629 + t.string "encrypted_password", default: "", null: false
630 + t.string "reset_password_token"
631 + t.datetime "reset_password_sent_at"
632 + t.datetime "remember_created_at"
633 + t.boolean "admin"
634 + t.string "instagram"
635 + t.string "youtube_channel_id"
636 + t.string "about"
637 + t.string "confirmation_token"
638 + t.datetime "confirmed_at"
639 + t.datetime "confirmation_sent_at"
640 + t.string "unconfirmed_email"
641 + t.string "invitation_token"
642 + t.datetime "invitation_created_at"
643 + t.datetime "invitation_sent_at"
644 + t.datetime "invitation_accepted_at"
645 + t.integer "invitation_limit"
646 + t.string "invited_by_type"
647 + t.bigint "invited_by_id"
648 + t.integer "invitations_count", default: 0
649 + t.integer "sash_id"
650 + t.integer "level", default: 0
651 + t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
652 + t.index ["email"], name: "index_users_on_email", unique: true
653 + t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true
654 + t.index ["invited_by_id"], name: "index_users_on_invited_by_id"
655 + t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by"
656 + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
657 + end
658 +
659 + create_table "vocal_files", force: :cascade do |t|
660 + t.bigint "audio_file_id"
661 + t.datetime "created_at", precision: 6, null: false
662 + t.datetime "updated_at", precision: 6, null: false
663 + t.index ["audio_file_id"], name: "index_vocal_files_on_audio_file_id"
664 + end
665 +
666 + create_table "wallets", force: :cascade do |t|
667 + t.bigint "user_id"
668 + t.string "pubkey"
669 + t.text "seed_phrase"
670 + t.datetime "created_at", precision: 6, null: false
671 + t.datetime "updated_at", precision: 6, null: false
672 + t.integer "status"
673 + t.string "bach_token_account"
674 + t.integer "wallet_type", default: 1
675 + t.index ["user_id"], name: "index_wallets_on_user_id"
676 + end
677 +
678 + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
679 + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
680 + add_foreign_key "album_artists", "albums"
681 + add_foreign_key "album_artists", "artists"
682 + add_foreign_key "album_providers", "albums"
683 + add_foreign_key "album_providers", "users"
684 + add_foreign_key "album_votes", "albums"
685 + add_foreign_key "album_votes", "users"
686 + add_foreign_key "albums", "users"
687 + add_foreign_key "artist_providers", "artists"
688 + add_foreign_key "artist_providers", "users"
689 + add_foreign_key "artist_votes", "artists"
690 + add_foreign_key "artist_votes", "users"
691 + add_foreign_key "artists", "users"
692 + add_foreign_key "audio_file_bass64_statuses", "audio_files"
693 + add_foreign_key "audio_file_bonzo_statuses", "audio_files"
694 + add_foreign_key "audio_file_karokowe_statuses", "audio_files"
695 + add_foreign_key "audio_file_split_statuses", "audio_files"
696 + add_foreign_key "authorizations", "users"
697 + add_foreign_key "bass_backing_track_files", "audio_files"
698 + add_foreign_key "bass_backing_tracks", "audio_files"
699 + add_foreign_key "bass_files", "audio_files"
700 + add_foreign_key "chord_histories", "chords"
701 + add_foreign_key "chord_histories", "users"
702 + add_foreign_key "chord_votes", "chords"
703 + add_foreign_key "chord_votes", "users"
704 + add_foreign_key "chords", "song_providers"
705 + add_foreign_key "chords", "users"
706 + add_foreign_key "comments", "users"
707 + add_foreign_key "drum_backing_tracks", "audio_files"
708 + add_foreign_key "drum_files", "audio_files"
709 + add_foreign_key "gending_notation_votes", "javanese_gending_notations"
710 + add_foreign_key "gending_notation_votes", "users"
711 + add_foreign_key "gending_votes", "javanese_gendings"
712 + add_foreign_key "gending_votes", "users"
713 + add_foreign_key "guitar_backing_track_files", "audio_files"
714 + add_foreign_key "javanese_gending_notations", "javanese_gendings"
715 + add_foreign_key "javanese_gending_notations", "users"
716 + add_foreign_key "javanese_gendings", "users"
717 + add_foreign_key "karaoke_file_lovers", "karaoke_files"
718 + add_foreign_key "karaoke_file_lovers", "users"
719 + add_foreign_key "karaoke_files", "audio_files"
720 + add_foreign_key "lyric_synches", "audio_files"
721 + add_foreign_key "lyric_synches", "users"
722 + add_foreign_key "lyric_votes", "lyrics"
723 + add_foreign_key "lyric_votes", "users"
724 + add_foreign_key "lyrics", "songs"
725 + add_foreign_key "lyrics", "users"
726 + add_foreign_key "other_files", "audio_files"
727 + add_foreign_key "song_artists", "artist_providers"
728 + add_foreign_key "song_artists", "artists"
729 + add_foreign_key "song_artists", "song_providers"
730 + add_foreign_key "song_artists", "songs"
731 + add_foreign_key "song_chord_pleas", "songs"
732 + add_foreign_key "song_chord_pleas", "users"
733 + add_foreign_key "song_provider_chords", "chords"
734 + add_foreign_key "song_provider_chords", "song_providers"
735 + add_foreign_key "song_provider_votes", "song_providers"
736 + add_foreign_key "song_provider_votes", "users"
737 + add_foreign_key "song_providers", "songs"
738 + add_foreign_key "song_providers", "users"
739 + add_foreign_key "songs", "albums"
740 + add_foreign_key "songs", "users"
741 + add_foreign_key "splitfire_results", "audio_files"
742 + add_foreign_key "user_settings", "users"
743 + add_foreign_key "vocal_files", "audio_files"
744 + add_foreign_key "wallets", "users"
745 +end
backend/musik88-web/db/seeds.rb new
+7
@@ -0,0 +1,7 @@
1 +# This file should contain all the record creation needed to seed the database with its default values.
2 +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 +#
4 +# Examples:
5 +#
6 +# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 +# Mayor.create(name: 'Emanuel', city: cities.first)
backend/musik88-web/docs/ab-test.md new
+8
@@ -0,0 +1,8 @@
1 +# A/B Test
2 +
3 +Data is the truth. Don't trust your assumption. Test it.
4 +
5 +## Split
6 +
7 +1. We use https://github.com/splitrb/split to do an A/B test.
8 +2. See the dashboard at domain/hatch.
\ No newline at end of file
backend/musik88-web/docs/audio-processing.md new
+29
@@ -0,0 +1,29 @@
1 +# Audio
2 +
3 +The audio processing part is divided into two processes: youtube-to-audio and the split process.
4 +
5 +## YouTube to audio
6 +
7 +The YouTube to audio status is attached to the `audio_files` table. Homebrew is recommended installer.
8 +
9 +```shell
10 +brew install yt-dlp
11 +```
12 +
13 +
14 +## Split process
15 +
16 +The split process status is attached to the `audio_file_split_statuses` table. Uses the system `Python3` command to install `demucs` by following the official [Linux installation](https://github.com/facebookresearch/demucs/blob/main/docs/linux.md).
17 +
18 +```shell
19 +pip3 install --user -U demucs
20 +
21 +```
22 +
23 +## Background job
24 +
25 +`SideKiq` is used as the bakcground job. During development, you might need to cleanup redis now and then:
26 +
27 +```shell
28 +redis-cli flushall
29 +```
\ No newline at end of file
backend/musik88-web/docs/debian.md new
+29
@@ -0,0 +1,29 @@
1 +# Homebrew
2 +
3 +Homebrew is used to install ```pyenv```.
4 +
5 +# Pyenv
6 +
7 +~~Pyenv is used to manage ```python```.~~
8 +
9 +
10 +# Server Storage
11 +
12 +Hetzner gives a 40GB internal storage. When attaching another storage, always select manual attachment and then follow tutorial. This is an example of the tutorial:
13 +
14 +```
15 +Configure volume
16 +To configure your volume, use the following commands on your server.
17 +FORMAT VOLUME
18 + sudo mkfs.ext4 -F /dev/disk/by-id/scsi-0HC_Volume_15126575
19 +CREATE DIRECTORY
20 + mkdir /mnt/volume-hel1-1
21 +MOUNT VOLUME
22 + mount -o discard,defaults /dev/disk/by-id/scsi-0HC_Volume_15126575 /mnt/volume-hel1-1
23 +OPTIONAL: ADD VOLUME TO FSTAB
24 +?
25 + echo "/dev/disk/by-id/scsi-0HC_Volume_15126575 /mnt/volume-hel1-1 ext4 discard,nofail,defaults 0 0" >> /etc/fstab
26 +The volume is now accessible at /mnt/volume-hel1-1 for data storage. This data will persist if you detach the volume. It will be available for attachment to another server in the same location.
27 +```
28 +
29 +One will need to change the permission with: ```sudo chmod ugo+rwx /media/username/your_drive```.
\ No newline at end of file
backend/musik88-web/docs/javanese_notation.md new
+10
@@ -0,0 +1,10 @@
1 +# Javanese Gending Notation
2 +
3 +Developed based on GNS latest version.
4 +
5 +https://github.com/smbpndk/gns
6 +
7 +## Sources
8 +
9 +https://papanggamelan.files.wordpress.com/2019/02/notasi-jineman.pdf
10 +https://www.youtube.com/watch?v=x3-kVUtqIDo
\ No newline at end of file
backend/musik88-web/docs/song_artists.md new
+4
@@ -0,0 +1,4 @@
1 +# Song Artists
2 +
3 +This table is originally design for our own song database, i.e, the parent song. But, now we don't need it. So instead, we're using it to store song provider and the artist provider information.
4 +
backend/musik88-web/docs/what_is.md new
+9
@@ -0,0 +1,9 @@
1 +# What Is
2 +
3 +A knowledge portal that acts as an educational content. It will hopefully generate organic traffics to the site. We will use a 'what-is' prefix in every targetted laguage because that is the natural way of people searching information on the internet.
4 +
5 +The index and the detail page is using the same template.
6 +
7 +## Chat bot
8 +
9 +In the future, this will be replace by a bot that will act as a search engine for the content we provide.
\ No newline at end of file
backend/musik88-web/lib/api_constraints.rb new
+10
@@ -0,0 +1,10 @@
1 +class ApiConstraints
2 + def initialize(options)
3 + @version = options[:version]
4 + @default = options[:default]
5 + end
6 +
7 + def matches?(req)
8 + @default || req.headers['Accept'].include?("application/vnd.marketplace.v#{@version}")
9 + end
10 +end
backend/musik88-web/lib/assets/.keep
backend/musik88-web/lib/tasks/.keep
backend/musik88-web/log/.keep
backend/musik88-web/package.json new
+26
@@ -0,0 +1,26 @@
1 +{
2 + "dependencies": {
3 + "@activeadmin/activeadmin": "^2.13.1",
4 + "@popperjs/core": "^2.11.0",
5 + "@rails/actioncable": "^7.0.1",
6 + "@rails/activestorage": "^6.1.4-1",
7 + "@rails/ujs": "^6.1.4-1",
8 + "@rails/webpacker": "5.4.3",
9 + "activeadmin_quill_editor": "blocknotes/activeadmin_quill_editor",
10 + "bootstrap": "^5.1.3",
11 + "bootstrap-icons": "^1.7.2",
12 + "html-loader": "^3.1.0",
13 + "jquery": "^3.6.0",
14 + "less": "^3.5.0",
15 + "less-loader": "^10.2.0",
16 + "quill": "^1.3.7",
17 + "rails-ujs": "^5.2.6",
18 + "turbolinks": "^5.2.0",
19 + "webpack": "^4.46.0",
20 + "webpack-cli": "^3.3.12"
21 + },
22 + "devDependencies": {
23 + "webpack-dev-server": "^3"
24 + },
25 + "license": "UNLICENSED"
26 +}
backend/musik88-web/postcss.config.js new
+12
@@ -0,0 +1,12 @@
1 +module.exports = {
2 + plugins: [
3 + require('postcss-import'),
4 + require('postcss-flexbugs-fixes'),
5 + require('postcss-preset-env')({
6 + autoprefixer: {
7 + flexbox: 'no-2009'
8 + },
9 + stage: 3
10 + })
11 + ]
12 +}
backend/musik88-web/public/422.html new
+67
@@ -0,0 +1,67 @@
1 +<!DOCTYPE html>
2 +<html>
3 +<head>
4 + <title>The change you wanted was rejected (422)</title>
5 + <meta name="viewport" content="width=device-width,initial-scale=1">
6 + <style>
7 + body {
8 + background-color: #EFEFEF;
9 + color: #2E2F30;
10 + text-align: center;
11 + font-family: arial, sans-serif;
12 + margin: 0;
13 + }
14 +
15 + div.dialog {
16 + width: 95%;
17 + max-width: 33em;
18 + margin: 4em auto 0;
19 + }
20 +
21 + div.dialog > div {
22 + border: 1px solid #CCC;
23 + border-right-color: #999;
24 + border-left-color: #999;
25 + border-bottom-color: #BBB;
26 + border-top: #B00100 solid 4px;
27 + border-top-left-radius: 9px;
28 + border-top-right-radius: 9px;
29 + background-color: white;
30 + padding: 7px 12% 0;
31 + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32 + }
33 +
34 + h1 {
35 + font-size: 100%;
36 + color: #730E15;
37 + line-height: 1.5em;
38 + }
39 +
40 + div.dialog > p {
41 + margin: 0 0 1em;
42 + padding: 1em;
43 + background-color: #F7F7F7;
44 + border: 1px solid #CCC;
45 + border-right-color: #999;
46 + border-left-color: #999;
47 + border-bottom-color: #999;
48 + border-bottom-left-radius: 4px;
49 + border-bottom-right-radius: 4px;
50 + border-top-color: #DADADA;
51 + color: #666;
52 + box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53 + }
54 + </style>
55 +</head>
56 +
57 +<body>
58 + <!-- This file lives in public/422.html -->
59 + <div class="dialog">
60 + <div>
61 + <h1>The change you wanted was rejected.</h1>
62 + <p>Maybe you tried to change something you didn't have access to.</p>
63 + </div>
64 + <p>If you are the application owner check the logs for more information.</p>
65 + </div>
66 +</body>
67 +</html>
backend/musik88-web/public/BACH-whitepaper.md new
+15
@@ -0,0 +1,15 @@
1 +# €BACH Tokenomics
2 +
3 +- Name: BACH Token
4 +- Symbol: €BACH
5 +- Decimal place: 12
6 +- Final total supply: 59.103.435
7 +
8 +## 7 release cycles:
9 +- 1st supply, Air on the G String: €BACH 4.788
10 +- 2nd supply, Airdrop on ?? €BACH ???
11 +- 3nd supply, TBH: €BACH ???
12 +- 4nd supply, TBH: €BACH ???
13 +- 5nd supply, TBH: €BACH ???
14 +- 6nd supply, TBH: €BACH ???
15 +- 7nd supply, TBH: €BACH ???
\ No newline at end of file
backend/musik88-web/public/ads.txt new
+1
@@ -0,0 +1 @@
1 +google.com, pub-8869993014420242, DIRECT, f08c47fec0942fa0
\ No newline at end of file
backend/musik88-web/public/android-icon-144x144.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:1f60d998be830c96b39ca70bff69f4e73446aaeff6600ecb3c6943b71e72c520
3 +size 3726
backend/musik88-web/public/android-icon-192x192.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:cefcf6c27a9118ac1c976ffd5ba2908b4f7cabcf008abb818f99cff93e0e6291
3 +size 4012
backend/musik88-web/public/android-icon-36x36.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:7729c41b135223f18a7934e065a3d0bc6cb8f2a0f5c015f35491a1a9a382e900
3 +size 1228
backend/musik88-web/public/android-icon-48x48.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:92b02276a4a3728f2e15e3e0db820da3da212306a07d200f6314118275500c0b
3 +size 1397
backend/musik88-web/public/android-icon-72x72.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:30d903613b0abd4d83473a3441b8fab0f81ca138b507ca292305da1f64d2d61d
3 +size 1825
backend/musik88-web/public/android-icon-96x96.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:fb7e9a04a0f59fca332f5f6c0c5c223caef5a71b8cb3703fb2bcc362dacefd18
3 +size 2130
backend/musik88-web/public/apple-icon-114x114.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:94b2a750e90cdc791e050e6101b499c474b96c3140a178d554bf535dae788b88
3 +size 2406
backend/musik88-web/public/apple-icon-120x120.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:b8915e93c38be3a1e91e3b7902998189c6fa53a26892a468b5b1eae9038b84f8
3 +size 2501
backend/musik88-web/public/apple-icon-144x144.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:1f60d998be830c96b39ca70bff69f4e73446aaeff6600ecb3c6943b71e72c520
3 +size 3726
backend/musik88-web/public/apple-icon-152x152.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:0a3e13750d25c65339062a0896cf916e9597ad574b21e7a0248cf83997232bbf
3 +size 3972
backend/musik88-web/public/apple-icon-180x180.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:70ff90449b49d2e581ac3bca0215d2fd107a19d3dd5d55bfd777af76655f1fcc
3 +size 4700
backend/musik88-web/public/apple-icon-57x57.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:aefa6099d08dd9f7fc95fb7a5fff6d86dedca53b6af164c3c6ce21625c3ae7b2
3 +size 1567
backend/musik88-web/public/apple-icon-60x60.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:776ae6dfe46283757db9f0b084785ef45ea3e43bf2e4dd3e5301b996dd2b7ef3
3 +size 1587
backend/musik88-web/public/apple-icon-72x72.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:30d903613b0abd4d83473a3441b8fab0f81ca138b507ca292305da1f64d2d61d
3 +size 1825
backend/musik88-web/public/apple-icon-76x76.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:9a975669ba3980eb77ed68b9c810fab84e705e4a758d93b8424cfaeb1bb0b0ae
3 +size 1799
backend/musik88-web/public/apple-icon-precomposed.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:dbcdb4f01f1313af2ec56e1a5e712b1d12122e702119a662197d7519c110a9c8
3 +size 4586
backend/musik88-web/public/apple-icon.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:dbcdb4f01f1313af2ec56e1a5e712b1d12122e702119a662197d7519c110a9c8
3 +size 4586
backend/musik88-web/public/browserconfig.xml new
+2
@@ -0,0 +1,2 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
\ No newline at end of file
backend/musik88-web/public/favicon-16x16.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:95268b1075e415bafaebcbbe5e669168d1c47daedbe34f556d8b037c2b6c1c9b
3 +size 860
backend/musik88-web/public/favicon-32x32.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:609da62a6057eac9a3732a9fdd4ded1379f792b9ddcefb2f30e93f61d4f778e0
3 +size 1153
backend/musik88-web/public/favicon-96x96.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:fb7e9a04a0f59fca332f5f6c0c5c223caef5a71b8cb3703fb2bcc362dacefd18
3 +size 2130
backend/musik88-web/public/favicon.ico
Binary files /dev/null and b/backend/musik88-web/public/favicon.ico differ
backend/musik88-web/public/manifest.json new
+41
@@ -0,0 +1,41 @@
1 +{
2 + "name": "App",
3 + "icons": [
4 + {
5 + "src": "\/android-icon-36x36.png",
6 + "sizes": "36x36",
7 + "type": "image\/png",
8 + "density": "0.75"
9 + },
10 + {
11 + "src": "\/android-icon-48x48.png",
12 + "sizes": "48x48",
13 + "type": "image\/png",
14 + "density": "1.0"
15 + },
16 + {
17 + "src": "\/android-icon-72x72.png",
18 + "sizes": "72x72",
19 + "type": "image\/png",
20 + "density": "1.5"
21 + },
22 + {
23 + "src": "\/android-icon-96x96.png",
24 + "sizes": "96x96",
25 + "type": "image\/png",
26 + "density": "2.0"
27 + },
28 + {
29 + "src": "\/android-icon-144x144.png",
30 + "sizes": "144x144",
31 + "type": "image\/png",
32 + "density": "3.0"
33 + },
34 + {
35 + "src": "\/android-icon-192x192.png",
36 + "sizes": "192x192",
37 + "type": "image\/png",
38 + "density": "4.0"
39 + }
40 + ]
41 +}
\ No newline at end of file
backend/musik88-web/public/ms-icon-144x144.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:1f60d998be830c96b39ca70bff69f4e73446aaeff6600ecb3c6943b71e72c520
3 +size 3726
backend/musik88-web/public/ms-icon-150x150.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:923980fdec6396461c8eacf113a573bb6b5a74ec9313d8cb08fd6988257014cd
3 +size 3919
backend/musik88-web/public/ms-icon-310x310.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:6759f9072c7919499206c8f07e170a845eea4f1838e2fcdd09da47baff8fe3f1
3 +size 9697
backend/musik88-web/public/ms-icon-70x70.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:f2bd99112f49b0b8dbe4ebae5d5cbc567317cb58dcf2b18e28956431cc9c438a
3 +size 1754
backend/musik88-web/public/robots.txt new
+5
@@ -0,0 +1,5 @@
1 +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 +#
3 +# To ban all spiders from the entire site uncomment the next two lines:
4 +# User-agent: *
5 +# Disallow: /
backend/musik88-web/test/channels/bass64_channel_test.rb new
+8
@@ -0,0 +1,8 @@
1 +require "test_helper"
2 +
3 +class Bass64ChannelTest < ActionCable::Channel::TestCase
4 + # test "subscribes" do
5 + # subscribe
6 + # assert subscription.confirmed?
7 + # end
8 +end
backend/musik88-web/test/channels/splitfire_channel_test.rb new
+8
@@ -0,0 +1,8 @@
1 +require "test_helper"
2 +
3 +class SplitfireChannelTest < ActionCable::Channel::TestCase
4 + # test "subscribes" do
5 + # subscribe
6 + # assert subscription.confirmed?
7 + # end
8 +end
backend/musik88-web/test/controllers/.keep
backend/musik88-web/test/controllers/about_controller_test.rb new
+9
@@ -0,0 +1,9 @@
1 +require 'test_helper'
2 +
3 +class AboutControllerTest < ActionController::TestCase
4 + # test "should get index" do
5 + # get :index
6 + # assert_response :success
7 + # end
8 +
9 +end
backend/musik88-web/test/controllers/albums_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class AlbumsControllerTest < ActionDispatch::IntegrationTest
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/controllers/artists_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class ArtistsControllerTest < ActionDispatch::IntegrationTest
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/controllers/chords_controller_test.rb new
+5
@@ -0,0 +1,5 @@
1 +require 'test_helper'
2 +
3 +class ChordsControllerControllerTest < ActionDispatch::IntegrationTest
4 +
5 +end
backend/musik88-web/test/controllers/contact_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class ContactControllerTest < ActionController::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/controllers/errors_controller_test.rb new
+5
@@ -0,0 +1,5 @@
1 +require 'test_helper'
2 +
3 +class ErrorsControllerTest < ActionDispatch::IntegrationTest
4 +
5 +end
backend/musik88-web/test/controllers/javanese_gamelan_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class JavaneseGamelanControllerTest < ActionDispatch::IntegrationTest
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/controllers/lyrics_controller_test.rb new
+5
@@ -0,0 +1,5 @@
1 +require 'test_helper'
2 +
3 +class LyricsControllerTest < ActionDispatch::IntegrationTest
4 +
5 +end
backend/musik88-web/test/controllers/my_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class MyControllerTest < ActionDispatch::IntegrationTest
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/controllers/relationships_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class RelationshipsControllerTest < ActionDispatch::IntegrationTest
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/controllers/sessions_controller_test.rb new
+4
@@ -0,0 +1,4 @@
1 +require 'test_helper'
2 +
3 +class SessionsControllerTest < ActionDispatch::IntegrationTest
4 +end
backend/musik88-web/test/controllers/songs_controller_test.rb new
+9
@@ -0,0 +1,9 @@
1 +require 'test_helper'
2 +
3 +class SongsControllerTest < ActionDispatch::IntegrationTest
4 + # test "should get index" do
5 + # get songs_index_url
6 + # assert_response :success
7 + # end
8 +
9 +end
backend/musik88-web/test/controllers/users_controller_test.rb new
+14
@@ -0,0 +1,14 @@
1 +require 'test_helper'
2 +
3 +class UsersControllerTest < ActionDispatch::IntegrationTest
4 + # test "should get index" do
5 + # get users_index_url
6 + # assert_response :success
7 + # end
8 + #
9 + # test "should get new" do
10 + # get users_new_url
11 + # assert_response :success
12 + # end
13 +
14 +end
backend/musik88-web/test/controllers/welcome_controller_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class WelcomeControllerTest < ActionController::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/fixtures/.keep
backend/musik88-web/test/fixtures/album_images.yml new
+11
@@ -0,0 +1,11 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + album: one
5 + url: MyString
6 + source: 1
7 +
8 +two:
9 + album: two
10 + url: MyString
11 + source: 1
backend/musik88-web/test/fixtures/album_providers.yml new
+15
@@ -0,0 +1,15 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + album: one
6 + provider_id: MyString
7 + provider_type: 1
8 + name: MyString
9 +
10 +two:
11 + user: two
12 + album: two
13 + provider_id: MyString
14 + provider_type: 1
15 + name: MyString
backend/musik88-web/test/fixtures/album_votes.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + album: one
6 +
7 +two:
8 + user: two
9 + album: two
backend/musik88-web/test/fixtures/artist_images.yml new
+11
@@ -0,0 +1,11 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + artist: one
5 + url: MyString
6 + source: 1
7 +
8 +two:
9 + artist: two
10 + url: MyString
11 + source: 1
backend/musik88-web/test/fixtures/artist_providers.yml new
+13
@@ -0,0 +1,13 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + provider_id: MyString
5 + type: 1
6 + name: MyString
7 + genres: MyString
8 +
9 +two:
10 + provider_id: MyString
11 + type: 1
12 + name: MyString
13 + genres: MyString
backend/musik88-web/test/fixtures/artist_votes.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + artist: one
6 +
7 +two:
8 + user: two
9 + artist: two
backend/musik88-web/test/fixtures/authorizations.yml new
+15
@@ -0,0 +1,15 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + provider: MyString
6 + uid: MyString
7 + token: MyString
8 + secret: MyString
9 +
10 +two:
11 + user: two
12 + provider: MyString
13 + uid: MyString
14 + token: MyString
15 + secret: MyString
backend/musik88-web/test/fixtures/comments.yml new
+11
@@ -0,0 +1,11 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + body: MyText
5 + commentable_id: 1
6 + commentable_type: MyString
7 +
8 +two:
9 + body: MyText
10 + commentable_id: 1
11 + commentable_type: MyString
backend/musik88-web/test/fixtures/gending_notation_votes.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + javanese_gending_notation: one
6 +
7 +two:
8 + user: two
9 + javanese_gending_notation: two
backend/musik88-web/test/fixtures/gending_votes.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + javanese_gending: one
6 +
7 +two:
8 + user: two
9 + javanese_gending: two
backend/musik88-web/test/fixtures/lyric_synches.yml new
+13
@@ -0,0 +1,13 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + song: one
5 + user: one
6 + lyric: MyString
7 + time: MyString
8 +
9 +two:
10 + song: two
11 + user: two
12 + lyric: MyString
13 + time: MyString
backend/musik88-web/test/fixtures/lyric_votes.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + lyric: one
5 + user: one
6 +
7 +two:
8 + lyric: two
9 + user: two
backend/musik88-web/test/fixtures/relationships.yml new
+15
@@ -0,0 +1,15 @@
1 +one:
2 + follower: michael
3 + followed: lana
4 +
5 +two:
6 + follower: michael
7 + followed: malory
8 +
9 +three:
10 + follower: lana
11 + followed: michael
12 +
13 +four:
14 + follower: archer
15 + followed: michael
\ No newline at end of file
backend/musik88-web/test/fixtures/search_terms.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + term: MyString
5 + count: 1
6 +
7 +two:
8 + term: MyString
9 + count: 1
backend/musik88-web/test/fixtures/song_provider_votes.yml new
+9
@@ -0,0 +1,9 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + song_provider: one
5 + user: one
6 +
7 +two:
8 + song_provider: two
9 + user: two
backend/musik88-web/test/fixtures/song_providers.yml new
+17
@@ -0,0 +1,17 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + song: one
6 + provider_id: MyString
7 + provider_type: 1
8 + name: MyString
9 + preview_url: MyString
10 +
11 +two:
12 + user: two
13 + song: two
14 + provider_id: MyString
15 + provider_type: 1
16 + name: MyString
17 + preview_url: MyString
backend/musik88-web/test/fixtures/users.yml new
+17
@@ -0,0 +1,17 @@
1 +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +# This model initially had no columns defined. If you add columns to the
4 +# model remove the '{}' from the fixture names and add the columns immediately
5 +# below each fixture, per the syntax in the comments below
6 +#
7 +# one: {}
8 +# column: value
9 +#
10 +# two: {}
11 +# column: value
12 +michael:
13 + name: Michael Example
14 + email: michael@example.com
15 +archer:
16 + name: Archer Example
17 + email: archer@example.com
\ No newline at end of file
backend/musik88-web/test/fixtures/wallets.yml new
+11
@@ -0,0 +1,11 @@
1 +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 +one:
4 + user: one
5 + pubkey: MyString
6 + seed_phrase: MyText
7 +
8 +two:
9 + user: two
10 + pubkey: MyString
11 + seed_phrase: MyText
backend/musik88-web/test/helpers/.keep
backend/musik88-web/test/helpers/about_helper_test.rb new
+4
@@ -0,0 +1,4 @@
1 +require 'test_helper'
2 +
3 +class AboutHelperTest < ActionView::TestCase
4 +end
backend/musik88-web/test/helpers/contact_helper_test.rb new
+4
@@ -0,0 +1,4 @@
1 +require 'test_helper'
2 +
3 +class ContactHelperTest < ActionView::TestCase
4 +end
backend/musik88-web/test/helpers/welcome_helper_test.rb new
+4
@@ -0,0 +1,4 @@
1 +require 'test_helper'
2 +
3 +class WelcomeHelperTest < ActionView::TestCase
4 +end
backend/musik88-web/test/integration/.keep
backend/musik88-web/test/integration/following_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class FollowingTest < ActionDispatch::IntegrationTest
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/integration/users_login_test.rb new
+8
@@ -0,0 +1,8 @@
1 +require 'test_helper'
2 +
3 +class UsersLoginTest < ActionDispatch::IntegrationTest
4 + def setup
5 + @user = users(:michael)
6 + end
7 +
8 +end
backend/musik88-web/test/integration/users_signup_test.rb new
+5
@@ -0,0 +1,5 @@
1 +require 'test_helper'
2 +
3 +class UsersSignupTest < ActionDispatch::IntegrationTest
4 +
5 +end
backend/musik88-web/test/jobs/bass64_job_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class Bass64JobTest < ActiveJob::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/jobs/split_fire_job_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class SplitFireJobTest < ActiveJob::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/mailers/.keep
backend/musik88-web/test/mailers/previews/devise_mailer_preview.rb new
+9
@@ -0,0 +1,9 @@
1 +class DeviseMailerPreview < ActionMailer::Preview
2 + def confirmation_instructions
3 + DeviseMailer.confirmation_instructions(User.first, 'faketoken', {})
4 + end
5 +
6 + def reset_password_instructions
7 + DeviseMailer.reset_password_instructions(User.first, 'faketoken', {})
8 + end
9 +end
backend/musik88-web/test/mailers/previews/user_notifier_mailer_preview.rb new
+14
@@ -0,0 +1,14 @@
1 +# Preview all emails at http://localhost:3000/rails/mailers/user_notifier_mailer
2 +class UserNotifierMailerPreview < ActionMailer::Preview
3 + def send_signup_email
4 + UserNotifierMailer.send_signup_email(User.first)
5 + end
6 +
7 + def send_reset_password_instructions
8 + UserNotifierMailer.send_reset_password_instructions(User.first)
9 + end
10 +
11 + def send_wallet_activation_notification
12 + UserNotifierMailer.send_wallet_activation_notification(User.first)
13 + end
14 +end
backend/musik88-web/test/mailers/user_notifier_mailer_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class UserNotifierMailerTest < ActionMailer::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/.keep
backend/musik88-web/test/models/album_artist_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class AlbumArtistTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/album_image_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AlbumImageTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/album_provider_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AlbumProviderTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/album_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class AlbumTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/album_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AlbumVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/artist_image_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class ArtistImageTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/artist_provider_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class ArtistProviderTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/artist_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class ArtistTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/artist_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class ArtistVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/audio_file_bass64_status_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AudioFileBass64StatusTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/audio_file_bonzo_status_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AudioFileBonzoStatusTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/audio_file_karokowe_status_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AudioFileKarokoweStatusTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/audio_file_split_status_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class AudioFileSplitStatusTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/authorization_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class AuthorizationTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/bass_backing_track_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class BassBackingTrackFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/bass_backing_track_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class BassBackingTrackTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/bass_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class BassFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/chord_history_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class ChordHistoryTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/chord_shape_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class ChordShapeTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/chord_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class ChordVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/comment_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class CommentTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/drum_backing_track_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class DrumBackingTrackTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/drum_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class DrumFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/gending_notation_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class GendingNotationVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/gending_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class GendingVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/guitar_backing_track_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class GuitarBackingTrackFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/javanese_gending_notation_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class JavaneseGendingNotationTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/javanese_gending_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class JavaneseGendingTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/karaoke_file_lover_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class KaraokeFileLoverTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/karaoke_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class KaraokeFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/lyric_synch_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class LyricSynchTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/lyric_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class LyricTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/lyric_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class LyricVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/meta_page_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class MetaTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/other_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class OtherFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/page_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class PageTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/relationship_test.rb new
+4
@@ -0,0 +1,4 @@
1 +require 'test_helper'
2 +
3 +class RelationshipTest < ActiveSupport::TestCase
4 +end
backend/musik88-web/test/models/search_term_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class SearchTermTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/song_artist_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class SongArtistTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/song_chord_plea_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class SongChordPleaTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/song_provider_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class SongProviderTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/song_provider_vote_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class SongProviderVoteTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/song_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class SongTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/split_audio_file_result_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class SplitAudioFileResultTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/split_audio_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class SplitAudioFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/user_setting_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require 'test_helper'
2 +
3 +class UserSettingTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/user_test.rb new
+87
@@ -0,0 +1,87 @@
1 +require 'test_helper'
2 +
3 +class UserTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +
8 + def setup
9 + @user = User.new(name: 'Example user', email: 'user@example.com',
10 + password: 'foobar', password_confirmation: 'foobar')
11 + end
12 +
13 + test 'should be valid' do
14 + assert @user.valid?
15 + end
16 +
17 + test 'name should be present' do
18 + @user.name = ' '
19 + assert_not @user.valid?
20 + end
21 +
22 + test 'email should be present' do
23 + @user.email = ' '
24 + assert_not @user.valid?
25 + end
26 +
27 + test 'name should not be too long' do
28 + @user.name = 'a' * 51
29 + assert_not @user.valid?
30 + end
31 +
32 + test 'email should not be too long' do
33 + @user.email = 'a' * 244 + '@example.com'
34 + end
35 +
36 + test 'email validation should accept valid addresses' do
37 + valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn]
38 + valid_addresses.each do |valid_address|
39 + @user.email = valid_address
40 + assert @user.valid?, "#{valid_address.inspect} should be valid"
41 + end
42 + end
43 +
44 + test 'email validation should reject invalid addresses' do
45 + invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com
46 + foo@bar..com]
47 + invalid_addresses.each do |invalid_address|
48 + @user.email = invalid_address
49 + assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
50 + end
51 + end
52 +
53 + test 'email addresses should be unique' do
54 + duplicate_user = @user.dup
55 + duplicate_user.email = @user.email.upcase
56 + @user.save
57 + assert_not duplicate_user.valid?
58 + end
59 +
60 + test 'email address should be saved as lower case' do
61 + mixed_case_email = 'Foo@ExAMPle.CoM'
62 + @user.email = mixed_case_email
63 + @user.save
64 + assert_equal mixed_case_email.downcase, @user.reload.email
65 + end
66 +
67 + test 'password should be present (not blank)' do
68 + @user.password = @user.password_confirmation = ' ' * 6
69 + assert_not @user.valid?
70 + end
71 +
72 + test 'password should have a minimum length' do
73 + @user.password = @user.password_confirmation = 'a' * 5
74 + assert_not @user.valid?
75 + end
76 +
77 + test "should follow and unfollow a user" do
78 + michael = users(:michael)
79 + archer = users(:archer)
80 + assert_not michael.following?(archer)
81 + michael.follow(archer)
82 + assert michael.following?(archer)
83 + assert archer.followers.include?(michael)
84 + michael.unfollow(archer)
85 + assert_not michael.following?(archer)
86 + end
87 +end
backend/musik88-web/test/models/vocal_file_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class VocalFileTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/models/wallet_test.rb new
+7
@@ -0,0 +1,7 @@
1 +require "test_helper"
2 +
3 +class WalletTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 +end
backend/musik88-web/test/test_helper.rb new
+14
@@ -0,0 +1,14 @@
1 +ENV['RAILS_ENV'] ||= 'test'
2 +require File.expand_path('../../config/environment', __FILE__)
3 +require 'rails/test_help'
4 +
5 +class ActiveSupport::TestCase
6 + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 + fixtures :all
8 +
9 + # Add more helper methods to be used by all tests here...
10 + # Returns true if a test user is logged in.
11 + def is_user_signed_in?
12 + !session[:user_id].nil?
13 + end
14 +end
backend/musik88-web/vendor/assets/javascripts/.keep
backend/musik88-web/vendor/assets/stylesheets/.keep
backend/musik88-web/yarn.lock new
+7247
@@ -0,0 +1,7247 @@
1 +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 +# yarn lockfile v1
3 +
4 +
5 +"@activeadmin/activeadmin@^2.13.1":
6 + version "2.13.1"
7 + resolved "https://registry.yarnpkg.com/@activeadmin/activeadmin/-/activeadmin-2.13.1.tgz#2f33210aa0026a6937e0b08994270e8a77a9ac74"
8 + integrity sha512-o7b+zprz6M2Jp+eEWQOA/dTDxofvsDnQVr99x16z8loTUIKjE2gzC1mQ+5fNDiatZxTQdDipRVcavORJPn/mYg==
9 + dependencies:
10 + jquery "^3.4.1"
11 + jquery-ui "^1.12.1"
12 + jquery-ujs "^1.2.2"
13 +
14 +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0":
15 + version "7.16.0"
16 + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431"
17 + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==
18 + dependencies:
19 + "@babel/highlight" "^7.16.0"
20 +
21 +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4":
22 + version "7.16.4"
23 + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
24 + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==
25 +
26 +"@babel/core@^7.15.0":
27 + version "7.16.5"
28 + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c"
29 + integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==
30 + dependencies:
31 + "@babel/code-frame" "^7.16.0"
32 + "@babel/generator" "^7.16.5"
33 + "@babel/helper-compilation-targets" "^7.16.3"
34 + "@babel/helper-module-transforms" "^7.16.5"
35 + "@babel/helpers" "^7.16.5"
36 + "@babel/parser" "^7.16.5"
37 + "@babel/template" "^7.16.0"
38 + "@babel/traverse" "^7.16.5"
39 + "@babel/types" "^7.16.0"
40 + convert-source-map "^1.7.0"
41 + debug "^4.1.0"
42 + gensync "^1.0.0-beta.2"
43 + json5 "^2.1.2"
44 + semver "^6.3.0"
45 + source-map "^0.5.0"
46 +
47 +"@babel/generator@^7.16.5":
48 + version "7.16.5"
49 + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf"
50 + integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==
51 + dependencies:
52 + "@babel/types" "^7.16.0"
53 + jsesc "^2.5.1"
54 + source-map "^0.5.0"
55 +
56 +"@babel/helper-annotate-as-pure@^7.16.0":
57 + version "7.16.0"
58 + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d"
59 + integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==
60 + dependencies:
61 + "@babel/types" "^7.16.0"
62 +
63 +"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.5":
64 + version "7.16.5"
65 + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz#a8429d064dce8207194b8bf05a70a9ea828746af"
66 + integrity sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA==
67 + dependencies:
68 + "@babel/helper-explode-assignable-expression" "^7.16.0"
69 + "@babel/types" "^7.16.0"
70 +
71 +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.3":
72 + version "7.16.3"
73 + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0"
74 + integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==
75 + dependencies:
76 + "@babel/compat-data" "^7.16.0"
77 + "@babel/helper-validator-option" "^7.14.5"
78 + browserslist "^4.17.5"
79 + semver "^6.3.0"
80 +
81 +"@babel/helper-create-class-features-plugin@^7.16.5":
82 + version "7.16.5"
83 + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz#5d1bcd096792c1ebec6249eebc6358eec55d0cad"
84 + integrity sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==
85 + dependencies:
86 + "@babel/helper-annotate-as-pure" "^7.16.0"
87 + "@babel/helper-environment-visitor" "^7.16.5"
88 + "@babel/helper-function-name" "^7.16.0"
89 + "@babel/helper-member-expression-to-functions" "^7.16.5"
90 + "@babel/helper-optimise-call-expression" "^7.16.0"
91 + "@babel/helper-replace-supers" "^7.16.5"
92 + "@babel/helper-split-export-declaration" "^7.16.0"
93 +
94 +"@babel/helper-create-regexp-features-plugin@^7.16.0":
95 + version "7.16.0"
96 + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz#06b2348ce37fccc4f5e18dcd8d75053f2a7c44ff"
97 + integrity sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA==
98 + dependencies:
99 + "@babel/helper-annotate-as-pure" "^7.16.0"
100 + regexpu-core "^4.7.1"
101 +
102 +"@babel/helper-define-polyfill-provider@^0.3.0":
103 + version "0.3.0"
104 + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz#c5b10cf4b324ff840140bb07e05b8564af2ae971"
105 + integrity sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==
106 + dependencies:
107 + "@babel/helper-compilation-targets" "^7.13.0"
108 + "@babel/helper-module-imports" "^7.12.13"
109 + "@babel/helper-plugin-utils" "^7.13.0"
110 + "@babel/traverse" "^7.13.0"
111 + debug "^4.1.1"
112 + lodash.debounce "^4.0.8"
113 + resolve "^1.14.2"
114 + semver "^6.1.2"
115 +
116 +"@babel/helper-environment-visitor@^7.16.5":
117 + version "7.16.5"
118 + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8"
119 + integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==
120 + dependencies:
121 + "@babel/types" "^7.16.0"
122 +
123 +"@babel/helper-explode-assignable-expression@^7.16.0":
124 + version "7.16.0"
125 + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778"
126 + integrity sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ==
127 + dependencies:
128 + "@babel/types" "^7.16.0"
129 +
130 +"@babel/helper-function-name@^7.16.0":
131 + version "7.16.0"
132 + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481"
133 + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==
134 + dependencies:
135 + "@babel/helper-get-function-arity" "^7.16.0"
136 + "@babel/template" "^7.16.0"
137 + "@babel/types" "^7.16.0"
138 +
139 +"@babel/helper-get-function-arity@^7.16.0":
140 + version "7.16.0"
141 + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa"
142 + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==
143 + dependencies:
144 + "@babel/types" "^7.16.0"
145 +
146 +"@babel/helper-hoist-variables@^7.16.0":
147 + version "7.16.0"
148 + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a"
149 + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==
150 + dependencies:
151 + "@babel/types" "^7.16.0"
152 +
153 +"@babel/helper-member-expression-to-functions@^7.16.5":
154 + version "7.16.5"
155 + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz#1bc9f7e87354e86f8879c67b316cb03d3dc2caab"
156 + integrity sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==
157 + dependencies:
158 + "@babel/types" "^7.16.0"
159 +
160 +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0":
161 + version "7.16.0"
162 + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3"
163 + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==
164 + dependencies:
165 + "@babel/types" "^7.16.0"
166 +
167 +"@babel/helper-module-transforms@^7.16.5":
168 + version "7.16.5"
169 + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29"
170 + integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==
171 + dependencies:
172 + "@babel/helper-environment-visitor" "^7.16.5"
173 + "@babel/helper-module-imports" "^7.16.0"
174 + "@babel/helper-simple-access" "^7.16.0"
175 + "@babel/helper-split-export-declaration" "^7.16.0"
176 + "@babel/helper-validator-identifier" "^7.15.7"
177 + "@babel/template" "^7.16.0"
178 + "@babel/traverse" "^7.16.5"
179 + "@babel/types" "^7.16.0"
180 +
181 +"@babel/helper-optimise-call-expression@^7.16.0":
182 + version "7.16.0"
183 + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338"
184 + integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==
185 + dependencies:
186 + "@babel/types" "^7.16.0"
187 +
188 +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
189 + version "7.16.5"
190 + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074"
191 + integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==
192 +
193 +"@babel/helper-remap-async-to-generator@^7.16.5":
194 + version "7.16.5"
195 + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3"
196 + integrity sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw==
197 + dependencies:
198 + "@babel/helper-annotate-as-pure" "^7.16.0"
199 + "@babel/helper-wrap-function" "^7.16.5"
200 + "@babel/types" "^7.16.0"
201 +
202 +"@babel/helper-replace-supers@^7.16.5":
203 + version "7.16.5"
204 + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz#96d3988bd0ab0a2d22c88c6198c3d3234ca25326"
205 + integrity sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==
206 + dependencies:
207 + "@babel/helper-environment-visitor" "^7.16.5"
208 + "@babel/helper-member-expression-to-functions" "^7.16.5"
209 + "@babel/helper-optimise-call-expression" "^7.16.0"
210 + "@babel/traverse" "^7.16.5"
211 + "@babel/types" "^7.16.0"
212 +
213 +"@babel/helper-simple-access@^7.16.0":
214 + version "7.16.0"
215 + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517"
216 + integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==
217 + dependencies:
218 + "@babel/types" "^7.16.0"
219 +
220 +"@babel/helper-skip-transparent-expression-wrappers@^7.16.0":
221 + version "7.16.0"
222 + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09"
223 + integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==
224 + dependencies:
225 + "@babel/types" "^7.16.0"
226 +
227 +"@babel/helper-split-export-declaration@^7.16.0":
228 + version "7.16.0"
229 + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438"
230 + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==
231 + dependencies:
232 + "@babel/types" "^7.16.0"
233 +
234 +"@babel/helper-validator-identifier@^7.15.7":
235 + version "7.15.7"
236 + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
237 + integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
238 +
239 +"@babel/helper-validator-option@^7.14.5":
240 + version "7.14.5"
241 + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
242 + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
243 +
244 +"@babel/helper-wrap-function@^7.16.5":
245 + version "7.16.5"
246 + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f"
247 + integrity sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA==
248 + dependencies:
249 + "@babel/helper-function-name" "^7.16.0"
250 + "@babel/template" "^7.16.0"
251 + "@babel/traverse" "^7.16.5"
252 + "@babel/types" "^7.16.0"
253 +
254 +"@babel/helpers@^7.16.5":
255 + version "7.16.5"
256 + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd"
257 + integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==
258 + dependencies:
259 + "@babel/template" "^7.16.0"
260 + "@babel/traverse" "^7.16.5"
261 + "@babel/types" "^7.16.0"
262 +
263 +"@babel/highlight@^7.16.0":
264 + version "7.16.0"
265 + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a"
266 + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==
267 + dependencies:
268 + "@babel/helper-validator-identifier" "^7.15.7"
269 + chalk "^2.0.0"
270 + js-tokens "^4.0.0"
271 +
272 +"@babel/parser@^7.16.0", "@babel/parser@^7.16.5":
273 + version "7.16.6"
274 + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
275 + integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==
276 +
277 +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2":
278 + version "7.16.2"
279 + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183"
280 + integrity sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg==
281 + dependencies:
282 + "@babel/helper-plugin-utils" "^7.14.5"
283 +
284 +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.0":
285 + version "7.16.0"
286 + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz#358972eaab006f5eb0826183b0c93cbcaf13e1e2"
287 + integrity sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA==
288 + dependencies:
289 + "@babel/helper-plugin-utils" "^7.14.5"
290 + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
291 + "@babel/plugin-proposal-optional-chaining" "^7.16.0"
292 +
293 +"@babel/plugin-proposal-async-generator-functions@^7.16.5":
294 + version "7.16.5"
295 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz#fd3bd7e0d98404a3d4cbca15a72d533f8c9a2f67"
296 + integrity sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA==
297 + dependencies:
298 + "@babel/helper-plugin-utils" "^7.16.5"
299 + "@babel/helper-remap-async-to-generator" "^7.16.5"
300 + "@babel/plugin-syntax-async-generators" "^7.8.4"
301 +
302 +"@babel/plugin-proposal-class-properties@^7.14.5", "@babel/plugin-proposal-class-properties@^7.16.5":
303 + version "7.16.5"
304 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz#3269f44b89122110f6339806e05d43d84106468a"
305 + integrity sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A==
306 + dependencies:
307 + "@babel/helper-create-class-features-plugin" "^7.16.5"
308 + "@babel/helper-plugin-utils" "^7.16.5"
309 +
310 +"@babel/plugin-proposal-class-static-block@^7.16.5":
311 + version "7.16.5"
312 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz#df58ab015a7d3b0963aafc8f20792dcd834952a9"
313 + integrity sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ==
314 + dependencies:
315 + "@babel/helper-create-class-features-plugin" "^7.16.5"
316 + "@babel/helper-plugin-utils" "^7.16.5"
317 + "@babel/plugin-syntax-class-static-block" "^7.14.5"
318 +
319 +"@babel/plugin-proposal-dynamic-import@^7.16.5":
320 + version "7.16.5"
321 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz#2e0d19d5702db4dcb9bc846200ca02f2e9d60e9e"
322 + integrity sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ==
323 + dependencies:
324 + "@babel/helper-plugin-utils" "^7.16.5"
325 + "@babel/plugin-syntax-dynamic-import" "^7.8.3"
326 +
327 +"@babel/plugin-proposal-export-namespace-from@^7.16.5":
328 + version "7.16.5"
329 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz#3b4dd28378d1da2fea33e97b9f25d1c2f5bf1ac9"
330 + integrity sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw==
331 + dependencies:
332 + "@babel/helper-plugin-utils" "^7.16.5"
333 + "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
334 +
335 +"@babel/plugin-proposal-json-strings@^7.16.5":
336 + version "7.16.5"
337 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz#1e726930fca139caab6b084d232a9270d9d16f9c"
338 + integrity sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ==
339 + dependencies:
340 + "@babel/helper-plugin-utils" "^7.16.5"
341 + "@babel/plugin-syntax-json-strings" "^7.8.3"
342 +
343 +"@babel/plugin-proposal-logical-assignment-operators@^7.16.5":
344 + version "7.16.5"
345 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz#df1f2e4b5a0ec07abf061d2c18e53abc237d3ef5"
346 + integrity sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA==
347 + dependencies:
348 + "@babel/helper-plugin-utils" "^7.16.5"
349 + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
350 +
351 +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.5":
352 + version "7.16.5"
353 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz#652555bfeeeee2d2104058c6225dc6f75e2d0f07"
354 + integrity sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg==
355 + dependencies:
356 + "@babel/helper-plugin-utils" "^7.16.5"
357 + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
358 +
359 +"@babel/plugin-proposal-numeric-separator@^7.16.5":
360 + version "7.16.5"
361 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz#edcb6379b6cf4570be64c45965d8da7a2debf039"
362 + integrity sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw==
363 + dependencies:
364 + "@babel/helper-plugin-utils" "^7.16.5"
365 + "@babel/plugin-syntax-numeric-separator" "^7.10.4"
366 +
367 +"@babel/plugin-proposal-object-rest-spread@^7.14.7", "@babel/plugin-proposal-object-rest-spread@^7.16.5":
368 + version "7.16.5"
369 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz#f30f80dacf7bc1404bf67f99c8d9c01665e830ad"
370 + integrity sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw==
371 + dependencies:
372 + "@babel/compat-data" "^7.16.4"
373 + "@babel/helper-compilation-targets" "^7.16.3"
374 + "@babel/helper-plugin-utils" "^7.16.5"
375 + "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
376 + "@babel/plugin-transform-parameters" "^7.16.5"
377 +
378 +"@babel/plugin-proposal-optional-catch-binding@^7.16.5":
379 + version "7.16.5"
380 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz#1a5405765cf589a11a33a1fd75b2baef7d48b74e"
381 + integrity sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ==
382 + dependencies:
383 + "@babel/helper-plugin-utils" "^7.16.5"
384 + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
385 +
386 +"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.16.5":
387 + version "7.16.5"
388 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz#a5fa61056194d5059366c0009cb9a9e66ed75c1f"
389 + integrity sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A==
390 + dependencies:
391 + "@babel/helper-plugin-utils" "^7.16.5"
392 + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
393 + "@babel/plugin-syntax-optional-chaining" "^7.8.3"
394 +
395 +"@babel/plugin-proposal-private-methods@^7.16.5":
396 + version "7.16.5"
397 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz#2086f7d78c1b0c712d49b5c3fbc2d1ca21a7ee12"
398 + integrity sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA==
399 + dependencies:
400 + "@babel/helper-create-class-features-plugin" "^7.16.5"
401 + "@babel/helper-plugin-utils" "^7.16.5"
402 +
403 +"@babel/plugin-proposal-private-property-in-object@^7.16.5":
404 + version "7.16.5"
405 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz#a42d4b56005db3d405b12841309dbca647e7a21b"
406 + integrity sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA==
407 + dependencies:
408 + "@babel/helper-annotate-as-pure" "^7.16.0"
409 + "@babel/helper-create-class-features-plugin" "^7.16.5"
410 + "@babel/helper-plugin-utils" "^7.16.5"
411 + "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
412 +
413 +"@babel/plugin-proposal-unicode-property-regex@^7.16.5", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
414 + version "7.16.5"
415 + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz#35fe753afa7c572f322bd068ff3377bde0f37080"
416 + integrity sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg==
417 + dependencies:
418 + "@babel/helper-create-regexp-features-plugin" "^7.16.0"
419 + "@babel/helper-plugin-utils" "^7.16.5"
420 +
421 +"@babel/plugin-syntax-async-generators@^7.8.4":
422 + version "7.8.4"
423 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
424 + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
425 + dependencies:
426 + "@babel/helper-plugin-utils" "^7.8.0"
427 +
428 +"@babel/plugin-syntax-class-properties@^7.12.13":
429 + version "7.12.13"
430 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
431 + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
432 + dependencies:
433 + "@babel/helper-plugin-utils" "^7.12.13"
434 +
435 +"@babel/plugin-syntax-class-static-block@^7.14.5":
436 + version "7.14.5"
437 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406"
438 + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==
439 + dependencies:
440 + "@babel/helper-plugin-utils" "^7.14.5"
441 +
442 +"@babel/plugin-syntax-dynamic-import@^7.8.3":
443 + version "7.8.3"
444 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
445 + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
446 + dependencies:
447 + "@babel/helper-plugin-utils" "^7.8.0"
448 +
449 +"@babel/plugin-syntax-export-namespace-from@^7.8.3":
450 + version "7.8.3"
451 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
452 + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
453 + dependencies:
454 + "@babel/helper-plugin-utils" "^7.8.3"
455 +
456 +"@babel/plugin-syntax-json-strings@^7.8.3":
457 + version "7.8.3"
458 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
459 + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
460 + dependencies:
461 + "@babel/helper-plugin-utils" "^7.8.0"
462 +
463 +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
464 + version "7.10.4"
465 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
466 + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
467 + dependencies:
468 + "@babel/helper-plugin-utils" "^7.10.4"
469 +
470 +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
471 + version "7.8.3"
472 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
473 + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
474 + dependencies:
475 + "@babel/helper-plugin-utils" "^7.8.0"
476 +
477 +"@babel/plugin-syntax-numeric-separator@^7.10.4":
478 + version "7.10.4"
479 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
480 + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
481 + dependencies:
482 + "@babel/helper-plugin-utils" "^7.10.4"
483 +
484 +"@babel/plugin-syntax-object-rest-spread@^7.8.3":
485 + version "7.8.3"
486 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
487 + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
488 + dependencies:
489 + "@babel/helper-plugin-utils" "^7.8.0"
490 +
491 +"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
492 + version "7.8.3"
493 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
494 + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
495 + dependencies:
496 + "@babel/helper-plugin-utils" "^7.8.0"
497 +
498 +"@babel/plugin-syntax-optional-chaining@^7.8.3":
499 + version "7.8.3"
500 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
501 + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
502 + dependencies:
503 + "@babel/helper-plugin-utils" "^7.8.0"
504 +
505 +"@babel/plugin-syntax-private-property-in-object@^7.14.5":
506 + version "7.14.5"
507 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad"
508 + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==
509 + dependencies:
510 + "@babel/helper-plugin-utils" "^7.14.5"
511 +
512 +"@babel/plugin-syntax-top-level-await@^7.14.5":
513 + version "7.14.5"
514 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
515 + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
516 + dependencies:
517 + "@babel/helper-plugin-utils" "^7.14.5"
518 +
519 +"@babel/plugin-transform-arrow-functions@^7.16.5":
520 + version "7.16.5"
521 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz#04c18944dd55397b521d9d7511e791acea7acf2d"
522 + integrity sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ==
523 + dependencies:
524 + "@babel/helper-plugin-utils" "^7.16.5"
525 +
526 +"@babel/plugin-transform-async-to-generator@^7.16.5":
527 + version "7.16.5"
528 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz#89c9b501e65bb14c4579a6ce9563f859de9b34e4"
529 + integrity sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w==
530 + dependencies:
531 + "@babel/helper-module-imports" "^7.16.0"
532 + "@babel/helper-plugin-utils" "^7.16.5"
533 + "@babel/helper-remap-async-to-generator" "^7.16.5"
534 +
535 +"@babel/plugin-transform-block-scoped-functions@^7.16.5":
536 + version "7.16.5"
537 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz#af087494e1c387574260b7ee9b58cdb5a4e9b0b0"
538 + integrity sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw==
539 + dependencies:
540 + "@babel/helper-plugin-utils" "^7.16.5"
541 +
542 +"@babel/plugin-transform-block-scoping@^7.16.5":
543 + version "7.16.5"
544 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz#b91f254fe53e210eabe4dd0c40f71c0ed253c5e7"
545 + integrity sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ==
546 + dependencies:
547 + "@babel/helper-plugin-utils" "^7.16.5"
548 +
549 +"@babel/plugin-transform-classes@^7.16.5":
550 + version "7.16.5"
551 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz#6acf2ec7adb50fb2f3194dcd2909dbd056dcf216"
552 + integrity sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA==
553 + dependencies:
554 + "@babel/helper-annotate-as-pure" "^7.16.0"
555 + "@babel/helper-environment-visitor" "^7.16.5"
556 + "@babel/helper-function-name" "^7.16.0"
557 + "@babel/helper-optimise-call-expression" "^7.16.0"
558 + "@babel/helper-plugin-utils" "^7.16.5"
559 + "@babel/helper-replace-supers" "^7.16.5"
560 + "@babel/helper-split-export-declaration" "^7.16.0"
561 + globals "^11.1.0"
562 +
563 +"@babel/plugin-transform-computed-properties@^7.16.5":
564 + version "7.16.5"
565 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz#2af91ebf0cceccfcc701281ada7cfba40a9b322a"
566 + integrity sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg==
567 + dependencies:
568 + "@babel/helper-plugin-utils" "^7.16.5"
569 +
570 +"@babel/plugin-transform-destructuring@^7.14.7", "@babel/plugin-transform-destructuring@^7.16.5":
571 + version "7.16.5"
572 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz#89ebc87499ac4a81b897af53bb5d3eed261bd568"
573 + integrity sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg==
574 + dependencies:
575 + "@babel/helper-plugin-utils" "^7.16.5"
576 +
577 +"@babel/plugin-transform-dotall-regex@^7.16.5", "@babel/plugin-transform-dotall-regex@^7.4.4":
578 + version "7.16.5"
579 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz#b40739c00b6686820653536d6d143e311de67936"
580 + integrity sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw==
581 + dependencies:
582 + "@babel/helper-create-regexp-features-plugin" "^7.16.0"
583 + "@babel/helper-plugin-utils" "^7.16.5"
584 +
585 +"@babel/plugin-transform-duplicate-keys@^7.16.5":
586 + version "7.16.5"
587 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz#2450f2742325412b746d7d005227f5e8973b512a"
588 + integrity sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg==
589 + dependencies:
590 + "@babel/helper-plugin-utils" "^7.16.5"
591 +
592 +"@babel/plugin-transform-exponentiation-operator@^7.16.5":
593 + version "7.16.5"
594 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz#36e261fa1ab643cfaf30eeab38e00ed1a76081e2"
595 + integrity sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA==
596 + dependencies:
597 + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.5"
598 + "@babel/helper-plugin-utils" "^7.16.5"
599 +
600 +"@babel/plugin-transform-for-of@^7.16.5":
601 + version "7.16.5"
602 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz#9b544059c6ca11d565457c0ff1f08e13ce225261"
603 + integrity sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw==
604 + dependencies:
605 + "@babel/helper-plugin-utils" "^7.16.5"
606 +
607 +"@babel/plugin-transform-function-name@^7.16.5":
608 + version "7.16.5"
609 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz#6896ebb6a5538a75d6a4086a277752f655a7bd15"
610 + integrity sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ==
611 + dependencies:
612 + "@babel/helper-function-name" "^7.16.0"
613 + "@babel/helper-plugin-utils" "^7.16.5"
614 +
615 +"@babel/plugin-transform-literals@^7.16.5":
616 + version "7.16.5"
617 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz#af392b90e3edb2bd6dc316844cbfd6b9e009d320"
618 + integrity sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw==
619 + dependencies:
620 + "@babel/helper-plugin-utils" "^7.16.5"
621 +
622 +"@babel/plugin-transform-member-expression-literals@^7.16.5":
623 + version "7.16.5"
624 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz#4bd6ecdc11932361631097b779ca5c7570146dd5"
625 + integrity sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ==
626 + dependencies:
627 + "@babel/helper-plugin-utils" "^7.16.5"
628 +
629 +"@babel/plugin-transform-modules-amd@^7.16.5":
630 + version "7.16.5"
631 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz#92c0a3e83f642cb7e75fada9ab497c12c2616527"
632 + integrity sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ==
633 + dependencies:
634 + "@babel/helper-module-transforms" "^7.16.5"
635 + "@babel/helper-plugin-utils" "^7.16.5"
636 + babel-plugin-dynamic-import-node "^2.3.3"
637 +
638 +"@babel/plugin-transform-modules-commonjs@^7.16.5":
639 + version "7.16.5"
640 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz#4ee03b089536f076b2773196529d27c32b9d7bde"
641 + integrity sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ==
642 + dependencies:
643 + "@babel/helper-module-transforms" "^7.16.5"
644 + "@babel/helper-plugin-utils" "^7.16.5"
645 + "@babel/helper-simple-access" "^7.16.0"
646 + babel-plugin-dynamic-import-node "^2.3.3"
647 +
648 +"@babel/plugin-transform-modules-systemjs@^7.16.5":
649 + version "7.16.5"
650 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz#07078ba2e3cc94fbdd06836e355c246e98ad006b"
651 + integrity sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA==
652 + dependencies:
653 + "@babel/helper-hoist-variables" "^7.16.0"
654 + "@babel/helper-module-transforms" "^7.16.5"
655 + "@babel/helper-plugin-utils" "^7.16.5"
656 + "@babel/helper-validator-identifier" "^7.15.7"
657 + babel-plugin-dynamic-import-node "^2.3.3"
658 +
659 +"@babel/plugin-transform-modules-umd@^7.16.5":
660 + version "7.16.5"
661 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz#caa9c53d636fb4e3c99fd35a4c9ba5e5cd7e002e"
662 + integrity sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw==
663 + dependencies:
664 + "@babel/helper-module-transforms" "^7.16.5"
665 + "@babel/helper-plugin-utils" "^7.16.5"
666 +
667 +"@babel/plugin-transform-named-capturing-groups-regex@^7.16.5":
668 + version "7.16.5"
669 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz#4afd8cdee377ce3568f4e8a9ee67539b69886a3c"
670 + integrity sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA==
671 + dependencies:
672 + "@babel/helper-create-regexp-features-plugin" "^7.16.0"
673 +
674 +"@babel/plugin-transform-new-target@^7.16.5":
675 + version "7.16.5"
676 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz#759ea9d6fbbc20796056a5d89d13977626384416"
677 + integrity sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg==
678 + dependencies:
679 + "@babel/helper-plugin-utils" "^7.16.5"
680 +
681 +"@babel/plugin-transform-object-super@^7.16.5":
682 + version "7.16.5"
683 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz#8ccd9a1bcd3e7732ff8aa1702d067d8cd70ce380"
684 + integrity sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg==
685 + dependencies:
686 + "@babel/helper-plugin-utils" "^7.16.5"
687 + "@babel/helper-replace-supers" "^7.16.5"
688 +
689 +"@babel/plugin-transform-parameters@^7.16.5":
690 + version "7.16.5"
691 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz#4fc74b18a89638bd90aeec44a11793ecbe031dde"
692 + integrity sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA==
693 + dependencies:
694 + "@babel/helper-plugin-utils" "^7.16.5"
695 +
696 +"@babel/plugin-transform-property-literals@^7.16.5":
697 + version "7.16.5"
698 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz#58f1465a7202a2bb2e6b003905212dd7a79abe3f"
699 + integrity sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg==
700 + dependencies:
701 + "@babel/helper-plugin-utils" "^7.16.5"
702 +
703 +"@babel/plugin-transform-regenerator@^7.14.5", "@babel/plugin-transform-regenerator@^7.16.5":
704 + version "7.16.5"
705 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz#704cc6d8dd3dd4758267621ab7b36375238cef13"
706 + integrity sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg==
707 + dependencies:
708 + regenerator-transform "^0.14.2"
709 +
710 +"@babel/plugin-transform-reserved-words@^7.16.5":
711 + version "7.16.5"
712 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz#db95e98799675e193dc2b47d3e72a7c0651d0c30"
713 + integrity sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw==
714 + dependencies:
715 + "@babel/helper-plugin-utils" "^7.16.5"
716 +
717 +"@babel/plugin-transform-runtime@^7.15.0":
718 + version "7.16.5"
719 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz#0cc3f01d69f299d5a42cd9ec43b92ea7a777b8db"
720 + integrity sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw==
721 + dependencies:
722 + "@babel/helper-module-imports" "^7.16.0"
723 + "@babel/helper-plugin-utils" "^7.16.5"
724 + babel-plugin-polyfill-corejs2 "^0.3.0"
725 + babel-plugin-polyfill-corejs3 "^0.4.0"
726 + babel-plugin-polyfill-regenerator "^0.3.0"
727 + semver "^6.3.0"
728 +
729 +"@babel/plugin-transform-shorthand-properties@^7.16.5":
730 + version "7.16.5"
731 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz#ccb60b1a23b799f5b9a14d97c5bc81025ffd96d7"
732 + integrity sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg==
733 + dependencies:
734 + "@babel/helper-plugin-utils" "^7.16.5"
735 +
736 +"@babel/plugin-transform-spread@^7.16.5":
737 + version "7.16.5"
738 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz#912b06cff482c233025d3e69cf56d3e8fa166c29"
739 + integrity sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw==
740 + dependencies:
741 + "@babel/helper-plugin-utils" "^7.16.5"
742 + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
743 +
744 +"@babel/plugin-transform-sticky-regex@^7.16.5":
745 + version "7.16.5"
746 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz#593579bb2b5a8adfbe02cb43823275d9098f75f9"
747 + integrity sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg==
748 + dependencies:
749 + "@babel/helper-plugin-utils" "^7.16.5"
750 +
751 +"@babel/plugin-transform-template-literals@^7.16.5":
752 + version "7.16.5"
753 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz#343651385fd9923f5aa2275ca352c5d9183e1773"
754 + integrity sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ==
755 + dependencies:
756 + "@babel/helper-plugin-utils" "^7.16.5"
757 +
758 +"@babel/plugin-transform-typeof-symbol@^7.16.5":
759 + version "7.16.5"
760 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz#a1d1bf2c71573fe30965d0e4cd6a3291202e20ed"
761 + integrity sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ==
762 + dependencies:
763 + "@babel/helper-plugin-utils" "^7.16.5"
764 +
765 +"@babel/plugin-transform-unicode-escapes@^7.16.5":
766 + version "7.16.5"
767 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz#80507c225af49b4f4ee647e2a0ce53d2eeff9e85"
768 + integrity sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q==
769 + dependencies:
770 + "@babel/helper-plugin-utils" "^7.16.5"
771 +
772 +"@babel/plugin-transform-unicode-regex@^7.16.5":
773 + version "7.16.5"
774 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz#ac84d6a1def947d71ffb832426aa53b83d7ed49e"
775 + integrity sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw==
776 + dependencies:
777 + "@babel/helper-create-regexp-features-plugin" "^7.16.0"
778 + "@babel/helper-plugin-utils" "^7.16.5"
779 +
780 +"@babel/preset-env@^7.15.0":
781 + version "7.16.5"
782 + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.5.tgz#2e94d922f4a890979af04ffeb6a6b4e44ba90847"
783 + integrity sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ==
784 + dependencies:
785 + "@babel/compat-data" "^7.16.4"
786 + "@babel/helper-compilation-targets" "^7.16.3"
787 + "@babel/helper-plugin-utils" "^7.16.5"
788 + "@babel/helper-validator-option" "^7.14.5"
789 + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.2"
790 + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.0"
791 + "@babel/plugin-proposal-async-generator-functions" "^7.16.5"
792 + "@babel/plugin-proposal-class-properties" "^7.16.5"
793 + "@babel/plugin-proposal-class-static-block" "^7.16.5"
794 + "@babel/plugin-proposal-dynamic-import" "^7.16.5"
795 + "@babel/plugin-proposal-export-namespace-from" "^7.16.5"
796 + "@babel/plugin-proposal-json-strings" "^7.16.5"
797 + "@babel/plugin-proposal-logical-assignment-operators" "^7.16.5"
798 + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.5"
799 + "@babel/plugin-proposal-numeric-separator" "^7.16.5"
800 + "@babel/plugin-proposal-object-rest-spread" "^7.16.5"
801 + "@babel/plugin-proposal-optional-catch-binding" "^7.16.5"
802 + "@babel/plugin-proposal-optional-chaining" "^7.16.5"
803 + "@babel/plugin-proposal-private-methods" "^7.16.5"
804 + "@babel/plugin-proposal-private-property-in-object" "^7.16.5"
805 + "@babel/plugin-proposal-unicode-property-regex" "^7.16.5"
806 + "@babel/plugin-syntax-async-generators" "^7.8.4"
807 + "@babel/plugin-syntax-class-properties" "^7.12.13"
808 + "@babel/plugin-syntax-class-static-block" "^7.14.5"
809 + "@babel/plugin-syntax-dynamic-import" "^7.8.3"
810 + "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
811 + "@babel/plugin-syntax-json-strings" "^7.8.3"
812 + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
813 + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
814 + "@babel/plugin-syntax-numeric-separator" "^7.10.4"
815 + "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
816 + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
817 + "@babel/plugin-syntax-optional-chaining" "^7.8.3"
818 + "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
819 + "@babel/plugin-syntax-top-level-await" "^7.14.5"
820 + "@babel/plugin-transform-arrow-functions" "^7.16.5"
821 + "@babel/plugin-transform-async-to-generator" "^7.16.5"
822 + "@babel/plugin-transform-block-scoped-functions" "^7.16.5"
823 + "@babel/plugin-transform-block-scoping" "^7.16.5"
824 + "@babel/plugin-transform-classes" "^7.16.5"
825 + "@babel/plugin-transform-computed-properties" "^7.16.5"
826 + "@babel/plugin-transform-destructuring" "^7.16.5"
827 + "@babel/plugin-transform-dotall-regex" "^7.16.5"
828 + "@babel/plugin-transform-duplicate-keys" "^7.16.5"
829 + "@babel/plugin-transform-exponentiation-operator" "^7.16.5"
830 + "@babel/plugin-transform-for-of" "^7.16.5"
831 + "@babel/plugin-transform-function-name" "^7.16.5"
832 + "@babel/plugin-transform-literals" "^7.16.5"
833 + "@babel/plugin-transform-member-expression-literals" "^7.16.5"
834 + "@babel/plugin-transform-modules-amd" "^7.16.5"
835 + "@babel/plugin-transform-modules-commonjs" "^7.16.5"
836 + "@babel/plugin-transform-modules-systemjs" "^7.16.5"
837 + "@babel/plugin-transform-modules-umd" "^7.16.5"
838 + "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.5"
839 + "@babel/plugin-transform-new-target" "^7.16.5"
840 + "@babel/plugin-transform-object-super" "^7.16.5"
841 + "@babel/plugin-transform-parameters" "^7.16.5"
842 + "@babel/plugin-transform-property-literals" "^7.16.5"
843 + "@babel/plugin-transform-regenerator" "^7.16.5"
844 + "@babel/plugin-transform-reserved-words" "^7.16.5"
845 + "@babel/plugin-transform-shorthand-properties" "^7.16.5"
846 + "@babel/plugin-transform-spread" "^7.16.5"
847 + "@babel/plugin-transform-sticky-regex" "^7.16.5"
848 + "@babel/plugin-transform-template-literals" "^7.16.5"
849 + "@babel/plugin-transform-typeof-symbol" "^7.16.5"
850 + "@babel/plugin-transform-unicode-escapes" "^7.16.5"
851 + "@babel/plugin-transform-unicode-regex" "^7.16.5"
852 + "@babel/preset-modules" "^0.1.5"
853 + "@babel/types" "^7.16.0"
854 + babel-plugin-polyfill-corejs2 "^0.3.0"
855 + babel-plugin-polyfill-corejs3 "^0.4.0"
856 + babel-plugin-polyfill-regenerator "^0.3.0"
857 + core-js-compat "^3.19.1"
858 + semver "^6.3.0"
859 +
860 +"@babel/preset-modules@^0.1.5":
861 + version "0.1.5"
862 + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
863 + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==
864 + dependencies:
865 + "@babel/helper-plugin-utils" "^7.0.0"
866 + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
867 + "@babel/plugin-transform-dotall-regex" "^7.4.4"
868 + "@babel/types" "^7.4.4"
869 + esutils "^2.0.2"
870 +
871 +"@babel/runtime@^7.15.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
872 + version "7.16.5"
873 + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
874 + integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==
875 + dependencies:
876 + regenerator-runtime "^0.13.4"
877 +
878 +"@babel/template@^7.16.0":
879 + version "7.16.0"
880 + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
881 + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==
882 + dependencies:
883 + "@babel/code-frame" "^7.16.0"
884 + "@babel/parser" "^7.16.0"
885 + "@babel/types" "^7.16.0"
886 +
887 +"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.5":
888 + version "7.16.5"
889 + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3"
890 + integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==
891 + dependencies:
892 + "@babel/code-frame" "^7.16.0"
893 + "@babel/generator" "^7.16.5"
894 + "@babel/helper-environment-visitor" "^7.16.5"
895 + "@babel/helper-function-name" "^7.16.0"
896 + "@babel/helper-hoist-variables" "^7.16.0"
897 + "@babel/helper-split-export-declaration" "^7.16.0"
898 + "@babel/parser" "^7.16.5"
899 + "@babel/types" "^7.16.0"
900 + debug "^4.1.0"
901 + globals "^11.1.0"
902 +
903 +"@babel/types@^7.16.0", "@babel/types@^7.4.4":
904 + version "7.16.0"
905 + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba"
906 + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==
907 + dependencies:
908 + "@babel/helper-validator-identifier" "^7.15.7"
909 + to-fast-properties "^2.0.0"
910 +
911 +"@csstools/convert-colors@^1.4.0":
912 + version "1.4.0"
913 + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
914 + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==
915 +
916 +"@gar/promisify@^1.0.1":
917 + version "1.1.2"
918 + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210"
919 + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==
920 +
921 +"@npmcli/fs@^1.0.0":
922 + version "1.1.0"
923 + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.0.tgz#bec1d1b89c170d40e1b73ad6c943b0b75e7d2951"
924 + integrity sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA==
925 + dependencies:
926 + "@gar/promisify" "^1.0.1"
927 + semver "^7.3.5"
928 +
929 +"@npmcli/move-file@^1.0.1":
930 + version "1.1.2"
931 + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674"
932 + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==
933 + dependencies:
934 + mkdirp "^1.0.4"
935 + rimraf "^3.0.2"
936 +
937 +"@popperjs/core@^2.11.0":
938 + version "2.11.0"
939 + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
940 + integrity sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==
941 +
942 +"@rails/actioncable@^7.0.1":
943 + version "7.0.1"
944 + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.1.tgz#8f383b672e142d009f89b725d49b0832d99da74a"
945 + integrity sha512-lbGc1z2RXdiWZJE/8o2GSe2gek82EoKd2YvjRrqV//0J3/JImONUYwZ2XPmS1R9R2oth1XlIG0YidqdeTty0TA==
946 +
947 +"@rails/activestorage@^6.1.4-1":
948 + version "6.1.4"
949 + resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-6.1.4.tgz#7772f539cc846df5f4364fc57ccb48860f9e966e"
950 + integrity sha512-1Tm8uaVBhLTDEG4YaFPvqguhjbUGSPVItm0CfkRpIFZIkybWzFAxatIrk4YVOOxB8ZdXS7GdeYa1qVwjdiDkgQ==
951 + dependencies:
952 + spark-md5 "^3.0.0"
953 +
954 +"@rails/ujs@^6.1.4-1":
955 + version "6.1.4"
956 + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.4.tgz#093d5341595a02089ed309dec40f3c37da7b1b10"
957 + integrity sha512-O3lEzL5DYbxppMdsFSw36e4BHIlfz/xusynwXGv3l2lhSlvah41qviRpsoAlKXxl37nZAqK+UUF5cnGGK45Mfw==
958 +
959 +"@rails/webpacker@5.4.3":
960 + version "5.4.3"
961 + resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.4.3.tgz#cfe2d8faffe7db5001bad50a1534408b4f2efb2f"
962 + integrity sha512-tEM8tpUtfx6FxKwcuQ9+v6pzgqM5LeAdhT6IJ4Te3BPKFO1xrGrXugqeRuZ+gE8ASDZRTOK6yuQkapOpuX5JdA==
963 + dependencies:
964 + "@babel/core" "^7.15.0"
965 + "@babel/plugin-proposal-class-properties" "^7.14.5"
966 + "@babel/plugin-proposal-object-rest-spread" "^7.14.7"
967 + "@babel/plugin-syntax-dynamic-import" "^7.8.3"
968 + "@babel/plugin-transform-destructuring" "^7.14.7"
969 + "@babel/plugin-transform-regenerator" "^7.14.5"
970 + "@babel/plugin-transform-runtime" "^7.15.0"
971 + "@babel/preset-env" "^7.15.0"
972 + "@babel/runtime" "^7.15.3"
973 + babel-loader "^8.2.2"
974 + babel-plugin-dynamic-import-node "^2.3.3"
975 + babel-plugin-macros "^2.8.0"
976 + case-sensitive-paths-webpack-plugin "^2.4.0"
977 + compression-webpack-plugin "^4.0.1"
978 + core-js "^3.16.2"
979 + css-loader "^3.6.0"
980 + file-loader "^6.2.0"
981 + flatted "^3.2.2"
982 + glob "^7.1.7"
983 + js-yaml "^3.14.1"
984 + mini-css-extract-plugin "^0.9.0"
985 + optimize-css-assets-webpack-plugin "^5.0.8"
986 + path-complete-extname "^1.0.0"
987 + pnp-webpack-plugin "^1.7.0"
988 + postcss-flexbugs-fixes "^4.2.1"
989 + postcss-import "^12.0.1"
990 + postcss-loader "^3.0.0"
991 + postcss-preset-env "^6.7.0"
992 + postcss-safe-parser "^4.0.2"
993 + regenerator-runtime "^0.13.9"
994 + sass "^1.38.0"
995 + sass-loader "10.1.1"
996 + style-loader "^1.3.0"
997 + terser-webpack-plugin "^4.2.3"
998 + webpack "^4.46.0"
999 + webpack-assets-manifest "^3.1.1"
1000 + webpack-cli "^3.3.12"
1001 + webpack-sources "^1.4.3"
1002 +
1003 +"@types/glob@^7.1.1":
1004 + version "7.2.0"
1005 + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
1006 + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
1007 + dependencies:
1008 + "@types/minimatch" "*"
1009 + "@types/node" "*"
1010 +
1011 +"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
1012 + version "7.0.9"
1013 + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
1014 + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
1015 +
1016 +"@types/minimatch@*":
1017 + version "3.0.5"
1018 + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
1019 + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
1020 +
1021 +"@types/node@*":
1022 + version "17.0.0"
1023 + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.0.tgz#62797cee3b8b497f6547503b2312254d4fe3c2bb"
1024 + integrity sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==
1025 +
1026 +"@types/parse-json@^4.0.0":
1027 + version "4.0.0"
1028 + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
1029 + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
1030 +
1031 +"@types/q@^1.5.1":
1032 + version "1.5.5"
1033 + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
1034 + integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
1035 +
1036 +"@webassemblyjs/ast@1.9.0":
1037 + version "1.9.0"
1038 + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
1039 + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==
1040 + dependencies:
1041 + "@webassemblyjs/helper-module-context" "1.9.0"
1042 + "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
1043 + "@webassemblyjs/wast-parser" "1.9.0"
1044 +
1045 +"@webassemblyjs/floating-point-hex-parser@1.9.0":
1046 + version "1.9.0"
1047 + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4"
1048 + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==
1049 +
1050 +"@webassemblyjs/helper-api-error@1.9.0":
1051 + version "1.9.0"
1052 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2"
1053 + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==
1054 +
1055 +"@webassemblyjs/helper-buffer@1.9.0":
1056 + version "1.9.0"
1057 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00"
1058 + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==
1059 +
1060 +"@webassemblyjs/helper-code-frame@1.9.0":
1061 + version "1.9.0"
1062 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27"
1063 + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==
1064 + dependencies:
1065 + "@webassemblyjs/wast-printer" "1.9.0"
1066 +
1067 +"@webassemblyjs/helper-fsm@1.9.0":
1068 + version "1.9.0"
1069 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8"
1070 + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==
1071 +
1072 +"@webassemblyjs/helper-module-context@1.9.0":
1073 + version "1.9.0"
1074 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07"
1075 + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==
1076 + dependencies:
1077 + "@webassemblyjs/ast" "1.9.0"
1078 +
1079 +"@webassemblyjs/helper-wasm-bytecode@1.9.0":
1080 + version "1.9.0"
1081 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790"
1082 + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==
1083 +
1084 +"@webassemblyjs/helper-wasm-section@1.9.0":
1085 + version "1.9.0"
1086 + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346"
1087 + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==
1088 + dependencies:
1089 + "@webassemblyjs/ast" "1.9.0"
1090 + "@webassemblyjs/helper-buffer" "1.9.0"
1091 + "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
1092 + "@webassemblyjs/wasm-gen" "1.9.0"
1093 +
1094 +"@webassemblyjs/ieee754@1.9.0":
1095 + version "1.9.0"
1096 + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4"
1097 + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==
1098 + dependencies:
1099 + "@xtuc/ieee754" "^1.2.0"
1100 +
1101 +"@webassemblyjs/leb128@1.9.0":
1102 + version "1.9.0"
1103 + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95"
1104 + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==
1105 + dependencies:
1106 + "@xtuc/long" "4.2.2"
1107 +
1108 +"@webassemblyjs/utf8@1.9.0":
1109 + version "1.9.0"
1110 + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab"
1111 + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==
1112 +
1113 +"@webassemblyjs/wasm-edit@1.9.0":
1114 + version "1.9.0"
1115 + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf"
1116 + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==
1117 + dependencies:
1118 + "@webassemblyjs/ast" "1.9.0"
1119 + "@webassemblyjs/helper-buffer" "1.9.0"
1120 + "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
1121 + "@webassemblyjs/helper-wasm-section" "1.9.0"
1122 + "@webassemblyjs/wasm-gen" "1.9.0"
1123 + "@webassemblyjs/wasm-opt" "1.9.0"
1124 + "@webassemblyjs/wasm-parser" "1.9.0"
1125 + "@webassemblyjs/wast-printer" "1.9.0"
1126 +
1127 +"@webassemblyjs/wasm-gen@1.9.0":
1128 + version "1.9.0"
1129 + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c"
1130 + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==
1131 + dependencies:
1132 + "@webassemblyjs/ast" "1.9.0"
1133 + "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
1134 + "@webassemblyjs/ieee754" "1.9.0"
1135 + "@webassemblyjs/leb128" "1.9.0"
1136 + "@webassemblyjs/utf8" "1.9.0"
1137 +
1138 +"@webassemblyjs/wasm-opt@1.9.0":
1139 + version "1.9.0"
1140 + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61"
1141 + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==
1142 + dependencies:
1143 + "@webassemblyjs/ast" "1.9.0"
1144 + "@webassemblyjs/helper-buffer" "1.9.0"
1145 + "@webassemblyjs/wasm-gen" "1.9.0"
1146 + "@webassemblyjs/wasm-parser" "1.9.0"
1147 +
1148 +"@webassemblyjs/wasm-parser@1.9.0":
1149 + version "1.9.0"
1150 + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e"
1151 + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==
1152 + dependencies:
1153 + "@webassemblyjs/ast" "1.9.0"
1154 + "@webassemblyjs/helper-api-error" "1.9.0"
1155 + "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
1156 + "@webassemblyjs/ieee754" "1.9.0"
1157 + "@webassemblyjs/leb128" "1.9.0"
1158 + "@webassemblyjs/utf8" "1.9.0"
1159 +
1160 +"@webassemblyjs/wast-parser@1.9.0":
1161 + version "1.9.0"
1162 + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914"
1163 + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==
1164 + dependencies:
1165 + "@webassemblyjs/ast" "1.9.0"
1166 + "@webassemblyjs/floating-point-hex-parser" "1.9.0"
1167 + "@webassemblyjs/helper-api-error" "1.9.0"
1168 + "@webassemblyjs/helper-code-frame" "1.9.0"
1169 + "@webassemblyjs/helper-fsm" "1.9.0"
1170 + "@xtuc/long" "4.2.2"
1171 +
1172 +"@webassemblyjs/wast-printer@1.9.0":
1173 + version "1.9.0"
1174 + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899"
1175 + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==
1176 + dependencies:
1177 + "@webassemblyjs/ast" "1.9.0"
1178 + "@webassemblyjs/wast-parser" "1.9.0"
1179 + "@xtuc/long" "4.2.2"
1180 +
1181 +"@xtuc/ieee754@^1.2.0":
1182 + version "1.2.0"
1183 + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
1184 + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
1185 +
1186 +"@xtuc/long@4.2.2":
1187 + version "4.2.2"
1188 + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
1189 + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
1190 +
1191 +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
1192 + version "1.3.7"
1193 + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
1194 + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
1195 + dependencies:
1196 + mime-types "~2.1.24"
1197 + negotiator "0.6.2"
1198 +
1199 +acorn@^6.4.1:
1200 + version "6.4.2"
1201 + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
1202 + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
1203 +
1204 +activeadmin_quill_editor@blocknotes/activeadmin_quill_editor:
1205 + version "0.3.4"
1206 + resolved "https://codeload.github.com/blocknotes/activeadmin_quill_editor/tar.gz/1cad5a10c3eb890ff5645e01d4c5138b0e92eb95"
1207 +
1208 +aggregate-error@^3.0.0:
1209 + version "3.1.0"
1210 + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
1211 + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
1212 + dependencies:
1213 + clean-stack "^2.0.0"
1214 + indent-string "^4.0.0"
1215 +
1216 +ajv-errors@^1.0.0:
1217 + version "1.0.1"
1218 + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
1219 + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
1220 +
1221 +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
1222 + version "3.5.2"
1223 + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
1224 + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
1225 +
1226 +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5:
1227 + version "6.12.6"
1228 + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
1229 + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
1230 + dependencies:
1231 + fast-deep-equal "^3.1.1"
1232 + fast-json-stable-stringify "^2.0.0"
1233 + json-schema-traverse "^0.4.1"
1234 + uri-js "^4.2.2"
1235 +
1236 +alphanum-sort@^1.0.0:
1237 + version "1.0.2"
1238 + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
1239 + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
1240 +
1241 +ansi-colors@^3.0.0:
1242 + version "3.2.4"
1243 + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
1244 + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
1245 +
1246 +ansi-html-community@0.0.8:
1247 + version "0.0.8"
1248 + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
1249 + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
1250 +
1251 +ansi-regex@^2.0.0:
1252 + version "2.1.1"
1253 + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
1254 + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
1255 +
1256 +ansi-regex@^4.1.0:
1257 + version "4.1.0"
1258 + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
1259 + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
1260 +
1261 +ansi-styles@^3.2.0, ansi-styles@^3.2.1:
1262 + version "3.2.1"
1263 + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
1264 + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
1265 + dependencies:
1266 + color-convert "^1.9.0"
1267 +
1268 +anymatch@^2.0.0:
1269 + version "2.0.0"
1270 + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
1271 + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==
1272 + dependencies:
1273 + micromatch "^3.1.4"
1274 + normalize-path "^2.1.1"
1275 +
1276 +anymatch@~3.1.2:
1277 + version "3.1.2"
1278 + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
1279 + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
1280 + dependencies:
1281 + normalize-path "^3.0.0"
1282 + picomatch "^2.0.4"
1283 +
1284 +aproba@^1.1.1:
1285 + version "1.2.0"
1286 + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
1287 + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
1288 +
1289 +argparse@^1.0.7:
1290 + version "1.0.10"
1291 + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
1292 + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
1293 + dependencies:
1294 + sprintf-js "~1.0.2"
1295 +
1296 +arr-diff@^4.0.0:
1297 + version "4.0.0"
1298 + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
1299 + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
1300 +
1301 +arr-flatten@^1.1.0:
1302 + version "1.1.0"
1303 + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
1304 + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
1305 +
1306 +arr-union@^3.1.0:
1307 + version "3.1.0"
1308 + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
1309 + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
1310 +
1311 +array-flatten@1.1.1:
1312 + version "1.1.1"
1313 + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
1314 + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
1315 +
1316 +array-flatten@^2.1.0:
1317 + version "2.1.2"
1318 + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
1319 + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
1320 +
1321 +array-union@^1.0.1:
1322 + version "1.0.2"
1323 + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
1324 + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
1325 + dependencies:
1326 + array-uniq "^1.0.1"
1327 +
1328 +array-uniq@^1.0.1:
1329 + version "1.0.3"
1330 + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
1331 + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
1332 +
1333 +array-unique@^0.3.2:
1334 + version "0.3.2"
1335 + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
1336 + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
1337 +
1338 +asn1.js@^5.2.0:
1339 + version "5.4.1"
1340 + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
1341 + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
1342 + dependencies:
1343 + bn.js "^4.0.0"
1344 + inherits "^2.0.1"
1345 + minimalistic-assert "^1.0.0"
1346 + safer-buffer "^2.1.0"
1347 +
1348 +assert@^1.1.1:
1349 + version "1.5.0"
1350 + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
1351 + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==
1352 + dependencies:
1353 + object-assign "^4.1.1"
1354 + util "0.10.3"
1355 +
1356 +assign-symbols@^1.0.0:
1357 + version "1.0.0"
1358 + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
1359 + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
1360 +
1361 +async-each@^1.0.1:
1362 + version "1.0.3"
1363 + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
1364 + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
1365 +
1366 +async-limiter@~1.0.0:
1367 + version "1.0.1"
1368 + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
1369 + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
1370 +
1371 +async@^2.6.2:
1372 + version "2.6.3"
1373 + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
1374 + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
1375 + dependencies:
1376 + lodash "^4.17.14"
1377 +
1378 +atob@^2.1.2:
1379 + version "2.1.2"
1380 + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
1381 + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
1382 +
1383 +autoprefixer@^9.6.1:
1384 + version "9.8.8"
1385 + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
1386 + integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==
1387 + dependencies:
1388 + browserslist "^4.12.0"
1389 + caniuse-lite "^1.0.30001109"
1390 + normalize-range "^0.1.2"
1391 + num2fraction "^1.2.2"
1392 + picocolors "^0.2.1"
1393 + postcss "^7.0.32"
1394 + postcss-value-parser "^4.1.0"
1395 +
1396 +babel-loader@^8.2.2:
1397 + version "8.2.3"
1398 + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d"
1399 + integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==
1400 + dependencies:
1401 + find-cache-dir "^3.3.1"
1402 + loader-utils "^1.4.0"
1403 + make-dir "^3.1.0"
1404 + schema-utils "^2.6.5"
1405 +
1406 +babel-plugin-dynamic-import-node@^2.3.3:
1407 + version "2.3.3"
1408 + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
1409 + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
1410 + dependencies:
1411 + object.assign "^4.1.0"
1412 +
1413 +babel-plugin-macros@^2.8.0:
1414 + version "2.8.0"
1415 + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
1416 + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
1417 + dependencies:
1418 + "@babel/runtime" "^7.7.2"
1419 + cosmiconfig "^6.0.0"
1420 + resolve "^1.12.0"
1421 +
1422 +babel-plugin-polyfill-corejs2@^0.3.0:
1423 + version "0.3.0"
1424 + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz#407082d0d355ba565af24126fb6cb8e9115251fd"
1425 + integrity sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==
1426 + dependencies:
1427 + "@babel/compat-data" "^7.13.11"
1428 + "@babel/helper-define-polyfill-provider" "^0.3.0"
1429 + semver "^6.1.1"
1430 +
1431 +babel-plugin-polyfill-corejs3@^0.4.0:
1432 + version "0.4.0"
1433 + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz#0b571f4cf3d67f911512f5c04842a7b8e8263087"
1434 + integrity sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw==
1435 + dependencies:
1436 + "@babel/helper-define-polyfill-provider" "^0.3.0"
1437 + core-js-compat "^3.18.0"
1438 +
1439 +babel-plugin-polyfill-regenerator@^0.3.0:
1440 + version "0.3.0"
1441 + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz#9ebbcd7186e1a33e21c5e20cae4e7983949533be"
1442 + integrity sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg==
1443 + dependencies:
1444 + "@babel/helper-define-polyfill-provider" "^0.3.0"
1445 +
1446 +balanced-match@^1.0.0:
1447 + version "1.0.2"
1448 + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
1449 + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
1450 +
1451 +base64-js@^1.0.2:
1452 + version "1.5.1"
1453 + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
1454 + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
1455 +
1456 +base@^0.11.1:
1457 + version "0.11.2"
1458 + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
1459 + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==
1460 + dependencies:
1461 + cache-base "^1.0.1"
1462 + class-utils "^0.3.5"
1463 + component-emitter "^1.2.1"
1464 + define-property "^1.0.0"
1465 + isobject "^3.0.1"
1466 + mixin-deep "^1.2.0"
1467 + pascalcase "^0.1.1"
1468 +
1469 +batch@0.6.1:
1470 + version "0.6.1"
1471 + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
1472 + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=
1473 +
1474 +big.js@^5.2.2:
1475 + version "5.2.2"
1476 + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
1477 + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
1478 +
1479 +binary-extensions@^1.0.0:
1480 + version "1.13.1"
1481 + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
1482 + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
1483 +
1484 +binary-extensions@^2.0.0:
1485 + version "2.2.0"
1486 + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
1487 + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
1488 +
1489 +bindings@^1.5.0:
1490 + version "1.5.0"
1491 + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
1492 + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
1493 + dependencies:
1494 + file-uri-to-path "1.0.0"
1495 +
1496 +bluebird@^3.5.5:
1497 + version "3.7.2"
1498 + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
1499 + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
1500 +
1501 +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
1502 + version "4.12.0"
1503 + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
1504 + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
1505 +
1506 +bn.js@^5.0.0, bn.js@^5.1.1:
1507 + version "5.2.0"
1508 + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
1509 + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
1510 +
1511 +body-parser@1.19.0:
1512 + version "1.19.0"
1513 + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
1514 + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
1515 + dependencies:
1516 + bytes "3.1.0"
1517 + content-type "~1.0.4"
1518 + debug "2.6.9"
1519 + depd "~1.1.2"
1520 + http-errors "1.7.2"
1521 + iconv-lite "0.4.24"
1522 + on-finished "~2.3.0"
1523 + qs "6.7.0"
1524 + raw-body "2.4.0"
1525 + type-is "~1.6.17"
1526 +
1527 +bonjour@^3.5.0:
1528 + version "3.5.0"
1529 + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
1530 + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU=
1531 + dependencies:
1532 + array-flatten "^2.1.0"
1533 + deep-equal "^1.0.1"
1534 + dns-equal "^1.0.0"
1535 + dns-txt "^2.0.2"
1536 + multicast-dns "^6.0.1"
1537 + multicast-dns-service-types "^1.1.0"
1538 +
1539 +boolbase@^1.0.0, boolbase@~1.0.0:
1540 + version "1.0.0"
1541 + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
1542 + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
1543 +
1544 +bootstrap-icons@^1.7.2:
1545 + version "1.7.2"
1546 + resolved "https://registry.yarnpkg.com/bootstrap-icons/-/bootstrap-icons-1.7.2.tgz#4024e081e2c850602552e1fed6451e682d09322a"
1547 + integrity sha512-NiR2PqC73AQOPdVSu6GJfnk+hN2z6powcistXk1JgPnKuoV2FSdSl26w931Oz9HYbKCcKUSB6ncZTYJAYJl3QQ==
1548 +
1549 +bootstrap@^5.1.3:
1550 + version "5.1.3"
1551 + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.1.3.tgz#ba081b0c130f810fa70900acbc1c6d3c28fa8f34"
1552 + integrity sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==
1553 +
1554 +brace-expansion@^1.1.7:
1555 + version "1.1.11"
1556 + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
1557 + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
1558 + dependencies:
1559 + balanced-match "^1.0.0"
1560 + concat-map "0.0.1"
1561 +
1562 +braces@^2.3.1, braces@^2.3.2:
1563 + version "2.3.2"
1564 + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
1565 + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
1566 + dependencies:
1567 + arr-flatten "^1.1.0"
1568 + array-unique "^0.3.2"
1569 + extend-shallow "^2.0.1"
1570 + fill-range "^4.0.0"
1571 + isobject "^3.0.1"
1572 + repeat-element "^1.1.2"
1573 + snapdragon "^0.8.1"
1574 + snapdragon-node "^2.0.1"
1575 + split-string "^3.0.2"
1576 + to-regex "^3.0.1"
1577 +
1578 +braces@~3.0.2:
1579 + version "3.0.2"
1580 + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
1581 + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
1582 + dependencies:
1583 + fill-range "^7.0.1"
1584 +
1585 +brorand@^1.0.1, brorand@^1.1.0:
1586 + version "1.1.0"
1587 + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
1588 + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
1589 +
1590 +browserify-aes@^1.0.0, browserify-aes@^1.0.4:
1591 + version "1.2.0"
1592 + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
1593 + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
1594 + dependencies:
1595 + buffer-xor "^1.0.3"
1596 + cipher-base "^1.0.0"
1597 + create-hash "^1.1.0"
1598 + evp_bytestokey "^1.0.3"
1599 + inherits "^2.0.1"
1600 + safe-buffer "^5.0.1"
1601 +
1602 +browserify-cipher@^1.0.0:
1603 + version "1.0.1"
1604 + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
1605 + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==
1606 + dependencies:
1607 + browserify-aes "^1.0.4"
1608 + browserify-des "^1.0.0"
1609 + evp_bytestokey "^1.0.0"
1610 +
1611 +browserify-des@^1.0.0:
1612 + version "1.0.2"
1613 + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c"
1614 + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==
1615 + dependencies:
1616 + cipher-base "^1.0.1"
1617 + des.js "^1.0.0"
1618 + inherits "^2.0.1"
1619 + safe-buffer "^5.1.2"
1620 +
1621 +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
1622 + version "4.1.0"
1623 + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d"
1624 + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==
1625 + dependencies:
1626 + bn.js "^5.0.0"
1627 + randombytes "^2.0.1"
1628 +
1629 +browserify-sign@^4.0.0:
1630 + version "4.2.1"
1631 + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
1632 + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
1633 + dependencies:
1634 + bn.js "^5.1.1"
1635 + browserify-rsa "^4.0.1"
1636 + create-hash "^1.2.0"
1637 + create-hmac "^1.1.7"
1638 + elliptic "^6.5.3"
1639 + inherits "^2.0.4"
1640 + parse-asn1 "^5.1.5"
1641 + readable-stream "^3.6.0"
1642 + safe-buffer "^5.2.0"
1643 +
1644 +browserify-zlib@^0.2.0:
1645 + version "0.2.0"
1646 + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
1647 + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
1648 + dependencies:
1649 + pako "~1.0.5"
1650 +
1651 +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.17.5, browserslist@^4.19.1, browserslist@^4.6.4:
1652 + version "4.19.1"
1653 + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
1654 + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==
1655 + dependencies:
1656 + caniuse-lite "^1.0.30001286"
1657 + electron-to-chromium "^1.4.17"
1658 + escalade "^3.1.1"
1659 + node-releases "^2.0.1"
1660 + picocolors "^1.0.0"
1661 +
1662 +buffer-from@^1.0.0:
1663 + version "1.1.2"
1664 + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
1665 + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
1666 +
1667 +buffer-indexof@^1.0.0:
1668 + version "1.1.1"
1669 + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
1670 + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
1671 +
1672 +buffer-xor@^1.0.3:
1673 + version "1.0.3"
1674 + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
1675 + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
1676 +
1677 +buffer@^4.3.0:
1678 + version "4.9.2"
1679 + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
1680 + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
1681 + dependencies:
1682 + base64-js "^1.0.2"
1683 + ieee754 "^1.1.4"
1684 + isarray "^1.0.0"
1685 +
1686 +builtin-status-codes@^3.0.0:
1687 + version "3.0.0"
1688 + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
1689 + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
1690 +
1691 +bytes@3.0.0:
1692 + version "3.0.0"
1693 + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
1694 + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
1695 +
1696 +bytes@3.1.0:
1697 + version "3.1.0"
1698 + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
1699 + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
1700 +
1701 +cacache@^12.0.2:
1702 + version "12.0.4"
1703 + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
1704 + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==
1705 + dependencies:
1706 + bluebird "^3.5.5"
1707 + chownr "^1.1.1"
1708 + figgy-pudding "^3.5.1"
1709 + glob "^7.1.4"
1710 + graceful-fs "^4.1.15"
1711 + infer-owner "^1.0.3"
1712 + lru-cache "^5.1.1"
1713 + mississippi "^3.0.0"
1714 + mkdirp "^0.5.1"
1715 + move-concurrently "^1.0.1"
1716 + promise-inflight "^1.0.1"
1717 + rimraf "^2.6.3"
1718 + ssri "^6.0.1"
1719 + unique-filename "^1.1.1"
1720 + y18n "^4.0.0"
1721 +
1722 +cacache@^15.0.5:
1723 + version "15.3.0"
1724 + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb"
1725 + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==
1726 + dependencies:
1727 + "@npmcli/fs" "^1.0.0"
1728 + "@npmcli/move-file" "^1.0.1"
1729 + chownr "^2.0.0"
1730 + fs-minipass "^2.0.0"
1731 + glob "^7.1.4"
1732 + infer-owner "^1.0.4"
1733 + lru-cache "^6.0.0"
1734 + minipass "^3.1.1"
1735 + minipass-collect "^1.0.2"
1736 + minipass-flush "^1.0.5"
1737 + minipass-pipeline "^1.2.2"
1738 + mkdirp "^1.0.3"
1739 + p-map "^4.0.0"
1740 + promise-inflight "^1.0.1"
1741 + rimraf "^3.0.2"
1742 + ssri "^8.0.1"
1743 + tar "^6.0.2"
1744 + unique-filename "^1.1.1"
1745 +
1746 +cache-base@^1.0.1:
1747 + version "1.0.1"
1748 + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
1749 + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==
1750 + dependencies:
1751 + collection-visit "^1.0.0"
1752 + component-emitter "^1.2.1"
1753 + get-value "^2.0.6"
1754 + has-value "^1.0.0"
1755 + isobject "^3.0.1"
1756 + set-value "^2.0.0"
1757 + to-object-path "^0.3.0"
1758 + union-value "^1.0.0"
1759 + unset-value "^1.0.0"
1760 +
1761 +call-bind@^1.0.0, call-bind@^1.0.2:
1762 + version "1.0.2"
1763 + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
1764 + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
1765 + dependencies:
1766 + function-bind "^1.1.1"
1767 + get-intrinsic "^1.0.2"
1768 +
1769 +caller-callsite@^2.0.0:
1770 + version "2.0.0"
1771 + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
1772 + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
1773 + dependencies:
1774 + callsites "^2.0.0"
1775 +
1776 +caller-path@^2.0.0:
1777 + version "2.0.0"
1778 + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
1779 + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
1780 + dependencies:
1781 + caller-callsite "^2.0.0"
1782 +
1783 +callsites@^2.0.0:
1784 + version "2.0.0"
1785 + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
1786 + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
1787 +
1788 +callsites@^3.0.0:
1789 + version "3.1.0"
1790 + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
1791 + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
1792 +
1793 +camel-case@^4.1.2:
1794 + version "4.1.2"
1795 + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
1796 + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
1797 + dependencies:
1798 + pascal-case "^3.1.2"
1799 + tslib "^2.0.3"
1800 +
1801 +camelcase@^5.0.0, camelcase@^5.3.1:
1802 + version "5.3.1"
1803 + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
1804 + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
1805 +
1806 +caniuse-api@^3.0.0:
1807 + version "3.0.0"
1808 + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
1809 + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==
1810 + dependencies:
1811 + browserslist "^4.0.0"
1812 + caniuse-lite "^1.0.0"
1813 + lodash.memoize "^4.1.2"
1814 + lodash.uniq "^4.5.0"
1815 +
1816 +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286:
1817 + version "1.0.30001584"
1818 + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz"
1819 + integrity sha512-LOz7CCQ9M1G7OjJOF9/mzmqmj3jE/7VOmrfw6Mgs0E8cjOsbRXQJHsPBfmBOXDskXKrHLyyW3n7kpDW/4BsfpQ==
1820 +
1821 +case-sensitive-paths-webpack-plugin@^2.4.0:
1822 + version "2.4.0"
1823 + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
1824 + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==
1825 +
1826 +chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
1827 + version "2.4.2"
1828 + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
1829 + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
1830 + dependencies:
1831 + ansi-styles "^3.2.1"
1832 + escape-string-regexp "^1.0.5"
1833 + supports-color "^5.3.0"
1834 +
1835 +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1:
1836 + version "3.5.2"
1837 + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
1838 + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
1839 + dependencies:
1840 + anymatch "~3.1.2"
1841 + braces "~3.0.2"
1842 + glob-parent "~5.1.2"
1843 + is-binary-path "~2.1.0"
1844 + is-glob "~4.0.1"
1845 + normalize-path "~3.0.0"
1846 + readdirp "~3.6.0"
1847 + optionalDependencies:
1848 + fsevents "~2.3.2"
1849 +
1850 +chokidar@^2.1.8:
1851 + version "2.1.8"
1852 + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
1853 + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
1854 + dependencies:
1855 + anymatch "^2.0.0"
1856 + async-each "^1.0.1"
1857 + braces "^2.3.2"
1858 + glob-parent "^3.1.0"
1859 + inherits "^2.0.3"
1860 + is-binary-path "^1.0.0"
1861 + is-glob "^4.0.0"
1862 + normalize-path "^3.0.0"
1863 + path-is-absolute "^1.0.0"
1864 + readdirp "^2.2.1"
1865 + upath "^1.1.1"
1866 + optionalDependencies:
1867 + fsevents "^1.2.7"
1868 +
1869 +chownr@^1.1.1:
1870 + version "1.1.4"
1871 + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
1872 + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
1873 +
1874 +chownr@^2.0.0:
1875 + version "2.0.0"
1876 + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
1877 + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
1878 +
1879 +chrome-trace-event@^1.0.2:
1880 + version "1.0.3"
1881 + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
1882 + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
1883 +
1884 +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
1885 + version "1.0.4"
1886 + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
1887 + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
1888 + dependencies:
1889 + inherits "^2.0.1"
1890 + safe-buffer "^5.0.1"
1891 +
1892 +class-utils@^0.3.5:
1893 + version "0.3.6"
1894 + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
1895 + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==
1896 + dependencies:
1897 + arr-union "^3.1.0"
1898 + define-property "^0.2.5"
1899 + isobject "^3.0.0"
1900 + static-extend "^0.1.1"
1901 +
1902 +clean-css@^5.2.2:
1903 + version "5.2.4"
1904 + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4"
1905 + integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg==
1906 + dependencies:
1907 + source-map "~0.6.0"
1908 +
1909 +clean-stack@^2.0.0:
1910 + version "2.2.0"
1911 + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
1912 + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
1913 +
1914 +cliui@^5.0.0:
1915 + version "5.0.0"
1916 + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
1917 + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
1918 + dependencies:
1919 + string-width "^3.1.0"
1920 + strip-ansi "^5.2.0"
1921 + wrap-ansi "^5.1.0"
1922 +
1923 +clone@^2.1.1:
1924 + version "2.1.2"
1925 + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
1926 + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
1927 +
1928 +coa@^2.0.2:
1929 + version "2.0.2"
1930 + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
1931 + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
1932 + dependencies:
1933 + "@types/q" "^1.5.1"
1934 + chalk "^2.4.1"
1935 + q "^1.1.2"
1936 +
1937 +collection-visit@^1.0.0:
1938 + version "1.0.0"
1939 + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
1940 + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=
1941 + dependencies:
1942 + map-visit "^1.0.0"
1943 + object-visit "^1.0.0"
1944 +
1945 +color-convert@^1.9.0, color-convert@^1.9.3:
1946 + version "1.9.3"
1947 + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
1948 + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
1949 + dependencies:
1950 + color-name "1.1.3"
1951 +
1952 +color-name@1.1.3:
1953 + version "1.1.3"
1954 + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
1955 + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
1956 +
1957 +color-name@^1.0.0:
1958 + version "1.1.4"
1959 + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
1960 + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
1961 +
1962 +color-string@^1.6.0:
1963 + version "1.9.0"
1964 + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa"
1965 + integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==
1966 + dependencies:
1967 + color-name "^1.0.0"
1968 + simple-swizzle "^0.2.2"
1969 +
1970 +color@^3.0.0:
1971 + version "3.2.1"
1972 + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
1973 + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
1974 + dependencies:
1975 + color-convert "^1.9.3"
1976 + color-string "^1.6.0"
1977 +
1978 +commander@^2.20.0:
1979 + version "2.20.3"
1980 + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
1981 + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
1982 +
1983 +commander@^8.3.0:
1984 + version "8.3.0"
1985 + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
1986 + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
1987 +
1988 +commondir@^1.0.1:
1989 + version "1.0.1"
1990 + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
1991 + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
1992 +
1993 +component-emitter@^1.2.1:
1994 + version "1.3.0"
1995 + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
1996 + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
1997 +
1998 +compressible@~2.0.16:
1999 + version "2.0.18"
2000 + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
2001 + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
2002 + dependencies:
2003 + mime-db ">= 1.43.0 < 2"
2004 +
2005 +compression-webpack-plugin@^4.0.1:
2006 + version "4.0.1"
2007 + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-4.0.1.tgz#33eda97f1170dd38c5556771de10f34245aa0274"
2008 + integrity sha512-0mg6PgwTsUe5LEcUrOu3ob32vraDx2VdbMGAT1PARcOV+UJWDYZFdkSo6RbHoGQ061mmmkC7XpRKOlvwm/gzJQ==
2009 + dependencies:
2010 + cacache "^15.0.5"
2011 + find-cache-dir "^3.3.1"
2012 + schema-utils "^2.7.0"
2013 + serialize-javascript "^4.0.0"
2014 + webpack-sources "^1.4.3"
2015 +
2016 +compression@^1.7.4:
2017 + version "1.7.4"
2018 + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
2019 + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
2020 + dependencies:
2021 + accepts "~1.3.5"
2022 + bytes "3.0.0"
2023 + compressible "~2.0.16"
2024 + debug "2.6.9"
2025 + on-headers "~1.0.2"
2026 + safe-buffer "5.1.2"
2027 + vary "~1.1.2"
2028 +
2029 +concat-map@0.0.1:
2030 + version "0.0.1"
2031 + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
2032 + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
2033 +
2034 +concat-stream@^1.5.0:
2035 + version "1.6.2"
2036 + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
2037 + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
2038 + dependencies:
2039 + buffer-from "^1.0.0"
2040 + inherits "^2.0.3"
2041 + readable-stream "^2.2.2"
2042 + typedarray "^0.0.6"
2043 +
2044 +connect-history-api-fallback@^1.6.0:
2045 + version "1.6.0"
2046 + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
2047 + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
2048 +
2049 +console-browserify@^1.1.0:
2050 + version "1.2.0"
2051 + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
2052 + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
2053 +
2054 +constants-browserify@^1.0.0:
2055 + version "1.0.0"
2056 + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
2057 + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
2058 +
2059 +content-disposition@0.5.3:
2060 + version "0.5.3"
2061 + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
2062 + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
2063 + dependencies:
2064 + safe-buffer "5.1.2"
2065 +
2066 +content-type@~1.0.4:
2067 + version "1.0.4"
2068 + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
2069 + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
2070 +
2071 +convert-source-map@^1.7.0:
2072 + version "1.8.0"
2073 + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
2074 + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
2075 + dependencies:
2076 + safe-buffer "~5.1.1"
2077 +
2078 +cookie-signature@1.0.6:
2079 + version "1.0.6"
2080 + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
2081 + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
2082 +
2083 +cookie@0.4.0:
2084 + version "0.4.0"
2085 + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
2086 + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
2087 +
2088 +copy-anything@^2.0.1:
2089 + version "2.0.6"
2090 + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480"
2091 + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==
2092 + dependencies:
2093 + is-what "^3.14.1"
2094 +
2095 +copy-concurrently@^1.0.0:
2096 + version "1.0.5"
2097 + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
2098 + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==
2099 + dependencies:
2100 + aproba "^1.1.1"
2101 + fs-write-stream-atomic "^1.0.8"
2102 + iferr "^0.1.5"
2103 + mkdirp "^0.5.1"
2104 + rimraf "^2.5.4"
2105 + run-queue "^1.0.0"
2106 +
2107 +copy-descriptor@^0.1.0:
2108 + version "0.1.1"
2109 + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
2110 + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
2111 +
2112 +core-js-compat@^3.18.0, core-js-compat@^3.19.1:
2113 + version "3.20.0"
2114 + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.20.0.tgz#fd704640c5a213816b6d10ec0192756111e2c9d1"
2115 + integrity sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ==
2116 + dependencies:
2117 + browserslist "^4.19.1"
2118 + semver "7.0.0"
2119 +
2120 +core-js@^3.16.2:
2121 + version "3.20.0"
2122 + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.20.0.tgz#1c5ac07986b8d15473ab192e45a2e115a4a95b79"
2123 + integrity sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ==
2124 +
2125 +core-util-is@~1.0.0:
2126 + version "1.0.3"
2127 + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
2128 + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
2129 +
2130 +cosmiconfig@^5.0.0:
2131 + version "5.2.1"
2132 + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
2133 + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
2134 + dependencies:
2135 + import-fresh "^2.0.0"
2136 + is-directory "^0.3.1"
2137 + js-yaml "^3.13.1"
2138 + parse-json "^4.0.0"
2139 +
2140 +cosmiconfig@^6.0.0:
2141 + version "6.0.0"
2142 + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
2143 + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
2144 + dependencies:
2145 + "@types/parse-json" "^4.0.0"
2146 + import-fresh "^3.1.0"
2147 + parse-json "^5.0.0"
2148 + path-type "^4.0.0"
2149 + yaml "^1.7.2"
2150 +
2151 +create-ecdh@^4.0.0:
2152 + version "4.0.4"
2153 + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
2154 + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
2155 + dependencies:
2156 + bn.js "^4.1.0"
2157 + elliptic "^6.5.3"
2158 +
2159 +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
2160 + version "1.2.0"
2161 + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
2162 + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
2163 + dependencies:
2164 + cipher-base "^1.0.1"
2165 + inherits "^2.0.1"
2166 + md5.js "^1.3.4"
2167 + ripemd160 "^2.0.1"
2168 + sha.js "^2.4.0"
2169 +
2170 +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
2171 + version "1.1.7"
2172 + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
2173 + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
2174 + dependencies:
2175 + cipher-base "^1.0.3"
2176 + create-hash "^1.1.0"
2177 + inherits "^2.0.1"
2178 + ripemd160 "^2.0.0"
2179 + safe-buffer "^5.0.1"
2180 + sha.js "^2.4.8"
2181 +
2182 +cross-spawn@^6.0.0, cross-spawn@^6.0.5:
2183 + version "6.0.5"
2184 + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
2185 + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
2186 + dependencies:
2187 + nice-try "^1.0.4"
2188 + path-key "^2.0.1"
2189 + semver "^5.5.0"
2190 + shebang-command "^1.2.0"
2191 + which "^1.2.9"
2192 +
2193 +crypto-browserify@^3.11.0:
2194 + version "3.12.0"
2195 + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
2196 + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==
2197 + dependencies:
2198 + browserify-cipher "^1.0.0"
2199 + browserify-sign "^4.0.0"
2200 + create-ecdh "^4.0.0"
2201 + create-hash "^1.1.0"
2202 + create-hmac "^1.1.0"
2203 + diffie-hellman "^5.0.0"
2204 + inherits "^2.0.1"
2205 + pbkdf2 "^3.0.3"
2206 + public-encrypt "^4.0.0"
2207 + randombytes "^2.0.0"
2208 + randomfill "^1.0.3"
2209 +
2210 +css-blank-pseudo@^0.1.4:
2211 + version "0.1.4"
2212 + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
2213 + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==
2214 + dependencies:
2215 + postcss "^7.0.5"
2216 +
2217 +css-color-names@0.0.4, css-color-names@^0.0.4:
2218 + version "0.0.4"
2219 + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
2220 + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
2221 +
2222 +css-declaration-sorter@^4.0.1:
2223 + version "4.0.1"
2224 + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22"
2225 + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==
2226 + dependencies:
2227 + postcss "^7.0.1"
2228 + timsort "^0.3.0"
2229 +
2230 +css-has-pseudo@^0.10.0:
2231 + version "0.10.0"
2232 + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee"
2233 + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==
2234 + dependencies:
2235 + postcss "^7.0.6"
2236 + postcss-selector-parser "^5.0.0-rc.4"
2237 +
2238 +css-loader@^3.6.0:
2239 + version "3.6.0"
2240 + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645"
2241 + integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==
2242 + dependencies:
2243 + camelcase "^5.3.1"
2244 + cssesc "^3.0.0"
2245 + icss-utils "^4.1.1"
2246 + loader-utils "^1.2.3"
2247 + normalize-path "^3.0.0"
2248 + postcss "^7.0.32"
2249 + postcss-modules-extract-imports "^2.0.0"
2250 + postcss-modules-local-by-default "^3.0.2"
2251 + postcss-modules-scope "^2.2.0"
2252 + postcss-modules-values "^3.0.0"
2253 + postcss-value-parser "^4.1.0"
2254 + schema-utils "^2.7.0"
2255 + semver "^6.3.0"
2256 +
2257 +css-prefers-color-scheme@^3.1.1:
2258 + version "3.1.1"
2259 + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4"
2260 + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==
2261 + dependencies:
2262 + postcss "^7.0.5"
2263 +
2264 +css-select-base-adapter@^0.1.1:
2265 + version "0.1.1"
2266 + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
2267 + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
2268 +
2269 +css-select@^2.0.0:
2270 + version "2.1.0"
2271 + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef"
2272 + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==
2273 + dependencies:
2274 + boolbase "^1.0.0"
2275 + css-what "^3.2.1"
2276 + domutils "^1.7.0"
2277 + nth-check "^1.0.2"
2278 +
2279 +css-tree@1.0.0-alpha.37:
2280 + version "1.0.0-alpha.37"
2281 + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
2282 + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
2283 + dependencies:
2284 + mdn-data "2.0.4"
2285 + source-map "^0.6.1"
2286 +
2287 +css-tree@^1.1.2:
2288 + version "1.1.3"
2289 + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
2290 + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
2291 + dependencies:
2292 + mdn-data "2.0.14"
2293 + source-map "^0.6.1"
2294 +
2295 +css-what@^3.2.1:
2296 + version "3.4.2"
2297 + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
2298 + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
2299 +
2300 +cssdb@^4.4.0:
2301 + version "4.4.0"
2302 + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0"
2303 + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==
2304 +
2305 +cssesc@^2.0.0:
2306 + version "2.0.0"
2307 + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
2308 + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==
2309 +
2310 +cssesc@^3.0.0:
2311 + version "3.0.0"
2312 + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
2313 + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
2314 +
2315 +cssnano-preset-default@^4.0.8:
2316 + version "4.0.8"
2317 + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff"
2318 + integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==
2319 + dependencies:
2320 + css-declaration-sorter "^4.0.1"
2321 + cssnano-util-raw-cache "^4.0.1"
2322 + postcss "^7.0.0"
2323 + postcss-calc "^7.0.1"
2324 + postcss-colormin "^4.0.3"
2325 + postcss-convert-values "^4.0.1"
2326 + postcss-discard-comments "^4.0.2"
2327 + postcss-discard-duplicates "^4.0.2"
2328 + postcss-discard-empty "^4.0.1"
2329 + postcss-discard-overridden "^4.0.1"
2330 + postcss-merge-longhand "^4.0.11"
2331 + postcss-merge-rules "^4.0.3"
2332 + postcss-minify-font-values "^4.0.2"
2333 + postcss-minify-gradients "^4.0.2"
2334 + postcss-minify-params "^4.0.2"
2335 + postcss-minify-selectors "^4.0.2"
2336 + postcss-normalize-charset "^4.0.1"
2337 + postcss-normalize-display-values "^4.0.2"
2338 + postcss-normalize-positions "^4.0.2"
2339 + postcss-normalize-repeat-style "^4.0.2"
2340 + postcss-normalize-string "^4.0.2"
2341 + postcss-normalize-timing-functions "^4.0.2"
2342 + postcss-normalize-unicode "^4.0.1"
2343 + postcss-normalize-url "^4.0.1"
2344 + postcss-normalize-whitespace "^4.0.2"
2345 + postcss-ordered-values "^4.1.2"
2346 + postcss-reduce-initial "^4.0.3"
2347 + postcss-reduce-transforms "^4.0.2"
2348 + postcss-svgo "^4.0.3"
2349 + postcss-unique-selectors "^4.0.1"
2350 +
2351 +cssnano-util-get-arguments@^4.0.0:
2352 + version "4.0.0"
2353 + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f"
2354 + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=
2355 +
2356 +cssnano-util-get-match@^4.0.0:
2357 + version "4.0.0"
2358 + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d"
2359 + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=
2360 +
2361 +cssnano-util-raw-cache@^4.0.1:
2362 + version "4.0.1"
2363 + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282"
2364 + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==
2365 + dependencies:
2366 + postcss "^7.0.0"
2367 +
2368 +cssnano-util-same-parent@^4.0.0:
2369 + version "4.0.1"
2370 + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3"
2371 + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==
2372 +
2373 +cssnano@^4.1.10:
2374 + version "4.1.11"
2375 + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99"
2376 + integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==
2377 + dependencies:
2378 + cosmiconfig "^5.0.0"
2379 + cssnano-preset-default "^4.0.8"
2380 + is-resolvable "^1.0.0"
2381 + postcss "^7.0.0"
2382 +
2383 +csso@^4.0.2:
2384 + version "4.2.0"
2385 + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
2386 + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
2387 + dependencies:
2388 + css-tree "^1.1.2"
2389 +
2390 +cyclist@^1.0.1:
2391 + version "1.0.1"
2392 + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
2393 + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
2394 +
2395 +debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
2396 + version "2.6.9"
2397 + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
2398 + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
2399 + dependencies:
2400 + ms "2.0.0"
2401 +
2402 +debug@^3.1.1, debug@^3.2.6:
2403 + version "3.2.7"
2404 + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
2405 + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
2406 + dependencies:
2407 + ms "^2.1.1"
2408 +
2409 +debug@^4.1.0, debug@^4.1.1:
2410 + version "4.3.3"
2411 + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
2412 + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
2413 + dependencies:
2414 + ms "2.1.2"
2415 +
2416 +decamelize@^1.2.0:
2417 + version "1.2.0"
2418 + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
2419 + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
2420 +
2421 +decode-uri-component@^0.2.0:
2422 + version "0.2.0"
2423 + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
2424 + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
2425 +
2426 +deep-equal@^1.0.1:
2427 + version "1.1.1"
2428 + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
2429 + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
2430 + dependencies:
2431 + is-arguments "^1.0.4"
2432 + is-date-object "^1.0.1"
2433 + is-regex "^1.0.4"
2434 + object-is "^1.0.1"
2435 + object-keys "^1.1.1"
2436 + regexp.prototype.flags "^1.2.0"
2437 +
2438 +default-gateway@^4.2.0:
2439 + version "4.2.0"
2440 + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
2441 + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==
2442 + dependencies:
2443 + execa "^1.0.0"
2444 + ip-regex "^2.1.0"
2445 +
2446 +define-properties@^1.1.3:
2447 + version "1.1.3"
2448 + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
2449 + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
2450 + dependencies:
2451 + object-keys "^1.0.12"
2452 +
2453 +define-property@^0.2.5:
2454 + version "0.2.5"
2455 + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
2456 + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=
2457 + dependencies:
2458 + is-descriptor "^0.1.0"
2459 +
2460 +define-property@^1.0.0:
2461 + version "1.0.0"
2462 + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
2463 + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY=
2464 + dependencies:
2465 + is-descriptor "^1.0.0"
2466 +
2467 +define-property@^2.0.2:
2468 + version "2.0.2"
2469 + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
2470 + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==
2471 + dependencies:
2472 + is-descriptor "^1.0.2"
2473 + isobject "^3.0.1"
2474 +
2475 +del@^4.1.1:
2476 + version "4.1.1"
2477 + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4"
2478 + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==
2479 + dependencies:
2480 + "@types/glob" "^7.1.1"
2481 + globby "^6.1.0"
2482 + is-path-cwd "^2.0.0"
2483 + is-path-in-cwd "^2.0.0"
2484 + p-map "^2.0.0"
2485 + pify "^4.0.1"
2486 + rimraf "^2.6.3"
2487 +
2488 +depd@~1.1.2:
2489 + version "1.1.2"
2490 + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
2491 + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
2492 +
2493 +des.js@^1.0.0:
2494 + version "1.0.1"
2495 + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
2496 + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==
2497 + dependencies:
2498 + inherits "^2.0.1"
2499 + minimalistic-assert "^1.0.0"
2500 +
2501 +destroy@~1.0.4:
2502 + version "1.0.4"
2503 + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
2504 + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
2505 +
2506 +detect-file@^1.0.0:
2507 + version "1.0.0"
2508 + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
2509 + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
2510 +
2511 +detect-node@^2.0.4:
2512 + version "2.1.0"
2513 + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
2514 + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
2515 +
2516 +diffie-hellman@^5.0.0:
2517 + version "5.0.3"
2518 + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
2519 + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
2520 + dependencies:
2521 + bn.js "^4.1.0"
2522 + miller-rabin "^4.0.0"
2523 + randombytes "^2.0.0"
2524 +
2525 +dns-equal@^1.0.0:
2526 + version "1.0.0"
2527 + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
2528 + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=
2529 +
2530 +dns-packet@^1.3.1:
2531 + version "1.3.4"
2532 + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f"
2533 + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==
2534 + dependencies:
2535 + ip "^1.1.0"
2536 + safe-buffer "^5.0.1"
2537 +
2538 +dns-txt@^2.0.2:
2539 + version "2.0.2"
2540 + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
2541 + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=
2542 + dependencies:
2543 + buffer-indexof "^1.0.0"
2544 +
2545 +dom-serializer@0:
2546 + version "0.2.2"
2547 + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
2548 + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
2549 + dependencies:
2550 + domelementtype "^2.0.1"
2551 + entities "^2.0.0"
2552 +
2553 +domain-browser@^1.1.1:
2554 + version "1.2.0"
2555 + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
2556 + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
2557 +
2558 +domelementtype@1:
2559 + version "1.3.1"
2560 + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
2561 + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
2562 +
2563 +domelementtype@^2.0.1:
2564 + version "2.2.0"
2565 + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
2566 + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
2567 +
2568 +domutils@^1.7.0:
2569 + version "1.7.0"
2570 + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
2571 + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
2572 + dependencies:
2573 + dom-serializer "0"
2574 + domelementtype "1"
2575 +
2576 +dot-case@^3.0.4:
2577 + version "3.0.4"
2578 + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
2579 + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
2580 + dependencies:
2581 + no-case "^3.0.4"
2582 + tslib "^2.0.3"
2583 +
2584 +dot-prop@^5.2.0:
2585 + version "5.3.0"
2586 + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
2587 + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
2588 + dependencies:
2589 + is-obj "^2.0.0"
2590 +
2591 +duplexify@^3.4.2, duplexify@^3.6.0:
2592 + version "3.7.1"
2593 + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
2594 + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
2595 + dependencies:
2596 + end-of-stream "^1.0.0"
2597 + inherits "^2.0.1"
2598 + readable-stream "^2.0.0"
2599 + stream-shift "^1.0.0"
2600 +
2601 +ee-first@1.1.1:
2602 + version "1.1.1"
2603 + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
2604 + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
2605 +
2606 +electron-to-chromium@^1.4.17:
2607 + version "1.4.20"
2608 + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.20.tgz#8fbf9677ccac19b4249c0a6204e0943d9d66ce30"
2609 + integrity sha512-N7ZVNrdzX8NE90OXEFBMsBf3fp8P/vVDUER3WCUZjzC7OkNTXHVoF6W9qVhq8+dA8tGnbDajzUpj2ISNVVyj+Q==
2610 +
2611 +elliptic@^6.5.3:
2612 + version "6.5.4"
2613 + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
2614 + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
2615 + dependencies:
2616 + bn.js "^4.11.9"
2617 + brorand "^1.1.0"
2618 + hash.js "^1.0.0"
2619 + hmac-drbg "^1.0.1"
2620 + inherits "^2.0.4"
2621 + minimalistic-assert "^1.0.1"
2622 + minimalistic-crypto-utils "^1.0.1"
2623 +
2624 +emoji-regex@^7.0.1:
2625 + version "7.0.3"
2626 + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
2627 + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
2628 +
2629 +emojis-list@^3.0.0:
2630 + version "3.0.0"
2631 + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
2632 + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
2633 +
2634 +encodeurl@~1.0.2:
2635 + version "1.0.2"
2636 + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
2637 + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
2638 +
2639 +end-of-stream@^1.0.0, end-of-stream@^1.1.0:
2640 + version "1.4.4"
2641 + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
2642 + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
2643 + dependencies:
2644 + once "^1.4.0"
2645 +
2646 +enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0:
2647 + version "4.5.0"
2648 + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec"
2649 + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==
2650 + dependencies:
2651 + graceful-fs "^4.1.2"
2652 + memory-fs "^0.5.0"
2653 + tapable "^1.0.0"
2654 +
2655 +entities@^2.0.0:
2656 + version "2.2.0"
2657 + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
2658 + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
2659 +
2660 +errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
2661 + version "0.1.8"
2662 + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
2663 + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
2664 + dependencies:
2665 + prr "~1.0.1"
2666 +
2667 +error-ex@^1.3.1:
2668 + version "1.3.2"
2669 + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
2670 + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
2671 + dependencies:
2672 + is-arrayish "^0.2.1"
2673 +
2674 +es-abstract@^1.17.2, es-abstract@^1.19.1:
2675 + version "1.19.1"
2676 + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
2677 + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==
2678 + dependencies:
2679 + call-bind "^1.0.2"
2680 + es-to-primitive "^1.2.1"
2681 + function-bind "^1.1.1"
2682 + get-intrinsic "^1.1.1"
2683 + get-symbol-description "^1.0.0"
2684 + has "^1.0.3"
2685 + has-symbols "^1.0.2"
2686 + internal-slot "^1.0.3"
2687 + is-callable "^1.2.4"
2688 + is-negative-zero "^2.0.1"
2689 + is-regex "^1.1.4"
2690 + is-shared-array-buffer "^1.0.1"
2691 + is-string "^1.0.7"
2692 + is-weakref "^1.0.1"
2693 + object-inspect "^1.11.0"
2694 + object-keys "^1.1.1"
2695 + object.assign "^4.1.2"
2696 + string.prototype.trimend "^1.0.4"
2697 + string.prototype.trimstart "^1.0.4"
2698 + unbox-primitive "^1.0.1"
2699 +
2700 +es-to-primitive@^1.2.1:
2701 + version "1.2.1"
2702 + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
2703 + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
2704 + dependencies:
2705 + is-callable "^1.1.4"
2706 + is-date-object "^1.0.1"
2707 + is-symbol "^1.0.2"
2708 +
2709 +escalade@^3.1.1:
2710 + version "3.1.1"
2711 + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
2712 + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
2713 +
2714 +escape-html@~1.0.3:
2715 + version "1.0.3"
2716 + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
2717 + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
2718 +
2719 +escape-string-regexp@^1.0.5:
2720 + version "1.0.5"
2721 + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
2722 + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
2723 +
2724 +eslint-scope@^4.0.3:
2725 + version "4.0.3"
2726 + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
2727 + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
2728 + dependencies:
2729 + esrecurse "^4.1.0"
2730 + estraverse "^4.1.1"
2731 +
2732 +esprima@^4.0.0:
2733 + version "4.0.1"
2734 + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
2735 + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
2736 +
2737 +esrecurse@^4.1.0:
2738 + version "4.3.0"
2739 + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
2740 + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
2741 + dependencies:
2742 + estraverse "^5.2.0"
2743 +
2744 +estraverse@^4.1.1:
2745 + version "4.3.0"
2746 + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
2747 + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
2748 +
2749 +estraverse@^5.2.0:
2750 + version "5.3.0"
2751 + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
2752 + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
2753 +
2754 +esutils@^2.0.2:
2755 + version "2.0.3"
2756 + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
2757 + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
2758 +
2759 +etag@~1.8.1:
2760 + version "1.8.1"
2761 + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
2762 + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
2763 +
2764 +eventemitter3@^2.0.3:
2765 + version "2.0.3"
2766 + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
2767 + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
2768 +
2769 +eventemitter3@^4.0.0:
2770 + version "4.0.7"
2771 + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
2772 + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
2773 +
2774 +events@^3.0.0:
2775 + version "3.3.0"
2776 + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
2777 + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
2778 +
2779 +eventsource@^1.0.7:
2780 + version "1.1.0"
2781 + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf"
2782 + integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==
2783 + dependencies:
2784 + original "^1.0.0"
2785 +
2786 +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
2787 + version "1.0.3"
2788 + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
2789 + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==
2790 + dependencies:
2791 + md5.js "^1.3.4"
2792 + safe-buffer "^5.1.1"
2793 +
2794 +execa@^1.0.0:
2795 + version "1.0.0"
2796 + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
2797 + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
2798 + dependencies:
2799 + cross-spawn "^6.0.0"
2800 + get-stream "^4.0.0"
2801 + is-stream "^1.1.0"
2802 + npm-run-path "^2.0.0"
2803 + p-finally "^1.0.0"
2804 + signal-exit "^3.0.0"
2805 + strip-eof "^1.0.0"
2806 +
2807 +expand-brackets@^2.1.4:
2808 + version "2.1.4"
2809 + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
2810 + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI=
2811 + dependencies:
2812 + debug "^2.3.3"
2813 + define-property "^0.2.5"
2814 + extend-shallow "^2.0.1"
2815 + posix-character-classes "^0.1.0"
2816 + regex-not "^1.0.0"
2817 + snapdragon "^0.8.1"
2818 + to-regex "^3.0.1"
2819 +
2820 +expand-tilde@^2.0.0, expand-tilde@^2.0.2:
2821 + version "2.0.2"
2822 + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
2823 + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
2824 + dependencies:
2825 + homedir-polyfill "^1.0.1"
2826 +
2827 +express@^4.17.1:
2828 + version "4.17.1"
2829 + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
2830 + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
2831 + dependencies:
2832 + accepts "~1.3.7"
2833 + array-flatten "1.1.1"
2834 + body-parser "1.19.0"
2835 + content-disposition "0.5.3"
2836 + content-type "~1.0.4"
2837 + cookie "0.4.0"
2838 + cookie-signature "1.0.6"
2839 + debug "2.6.9"
2840 + depd "~1.1.2"
2841 + encodeurl "~1.0.2"
2842 + escape-html "~1.0.3"
2843 + etag "~1.8.1"
2844 + finalhandler "~1.1.2"
2845 + fresh "0.5.2"
2846 + merge-descriptors "1.0.1"
2847 + methods "~1.1.2"
2848 + on-finished "~2.3.0"
2849 + parseurl "~1.3.3"
2850 + path-to-regexp "0.1.7"
2851 + proxy-addr "~2.0.5"
2852 + qs "6.7.0"
2853 + range-parser "~1.2.1"
2854 + safe-buffer "5.1.2"
2855 + send "0.17.1"
2856 + serve-static "1.14.1"
2857 + setprototypeof "1.1.1"
2858 + statuses "~1.5.0"
2859 + type-is "~1.6.18"
2860 + utils-merge "1.0.1"
2861 + vary "~1.1.2"
2862 +
2863 +extend-shallow@^2.0.1:
2864 + version "2.0.1"
2865 + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
2866 + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=
2867 + dependencies:
2868 + is-extendable "^0.1.0"
2869 +
2870 +extend-shallow@^3.0.0, extend-shallow@^3.0.2:
2871 + version "3.0.2"
2872 + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
2873 + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=
2874 + dependencies:
2875 + assign-symbols "^1.0.0"
2876 + is-extendable "^1.0.1"
2877 +
2878 +extend@^3.0.2:
2879 + version "3.0.2"
2880 + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
2881 + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
2882 +
2883 +extglob@^2.0.4:
2884 + version "2.0.4"
2885 + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
2886 + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==
2887 + dependencies:
2888 + array-unique "^0.3.2"
2889 + define-property "^1.0.0"
2890 + expand-brackets "^2.1.4"
2891 + extend-shallow "^2.0.1"
2892 + fragment-cache "^0.2.1"
2893 + regex-not "^1.0.0"
2894 + snapdragon "^0.8.1"
2895 + to-regex "^3.0.1"
2896 +
2897 +fast-deep-equal@^3.1.1:
2898 + version "3.1.3"
2899 + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
2900 + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
2901 +
2902 +fast-diff@1.1.2:
2903 + version "1.1.2"
2904 + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
2905 + integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
2906 +
2907 +fast-json-stable-stringify@^2.0.0:
2908 + version "2.1.0"
2909 + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
2910 + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
2911 +
2912 +faye-websocket@^0.11.3:
2913 + version "0.11.4"
2914 + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
2915 + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
2916 + dependencies:
2917 + websocket-driver ">=0.5.1"
2918 +
2919 +figgy-pudding@^3.5.1:
2920 + version "3.5.2"
2921 + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
2922 + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
2923 +
2924 +file-loader@^6.2.0:
2925 + version "6.2.0"
2926 + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
2927 + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
2928 + dependencies:
2929 + loader-utils "^2.0.0"
2930 + schema-utils "^3.0.0"
2931 +
2932 +file-uri-to-path@1.0.0:
2933 + version "1.0.0"
2934 + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
2935 + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
2936 +
2937 +fill-range@^4.0.0:
2938 + version "4.0.0"
2939 + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
2940 + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=
2941 + dependencies:
2942 + extend-shallow "^2.0.1"
2943 + is-number "^3.0.0"
2944 + repeat-string "^1.6.1"
2945 + to-regex-range "^2.1.0"
2946 +
2947 +fill-range@^7.0.1:
2948 + version "7.0.1"
2949 + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
2950 + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
2951 + dependencies:
2952 + to-regex-range "^5.0.1"
2953 +
2954 +finalhandler@~1.1.2:
2955 + version "1.1.2"
2956 + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
2957 + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
2958 + dependencies:
2959 + debug "2.6.9"
2960 + encodeurl "~1.0.2"
2961 + escape-html "~1.0.3"
2962 + on-finished "~2.3.0"
2963 + parseurl "~1.3.3"
2964 + statuses "~1.5.0"
2965 + unpipe "~1.0.0"
2966 +
2967 +find-cache-dir@^2.1.0:
2968 + version "2.1.0"
2969 + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
2970 + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
2971 + dependencies:
2972 + commondir "^1.0.1"
2973 + make-dir "^2.0.0"
2974 + pkg-dir "^3.0.0"
2975 +
2976 +find-cache-dir@^3.3.1:
2977 + version "3.3.2"
2978 + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
2979 + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
2980 + dependencies:
2981 + commondir "^1.0.1"
2982 + make-dir "^3.0.2"
2983 + pkg-dir "^4.1.0"
2984 +
2985 +find-up@^3.0.0:
2986 + version "3.0.0"
2987 + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
2988 + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
2989 + dependencies:
2990 + locate-path "^3.0.0"
2991 +
2992 +find-up@^4.0.0:
2993 + version "4.1.0"
2994 + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
2995 + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
2996 + dependencies:
2997 + locate-path "^5.0.0"
2998 + path-exists "^4.0.0"
2999 +
3000 +findup-sync@^3.0.0:
3001 + version "3.0.0"
3002 + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
3003 + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
3004 + dependencies:
3005 + detect-file "^1.0.0"
3006 + is-glob "^4.0.0"
3007 + micromatch "^3.0.4"
3008 + resolve-dir "^1.0.1"
3009 +
3010 +flatted@^3.2.2:
3011 + version "3.2.4"
3012 + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2"
3013 + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==
3014 +
3015 +flatten@^1.0.2:
3016 + version "1.0.3"
3017 + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
3018 + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
3019 +
3020 +flush-write-stream@^1.0.0:
3021 + version "1.1.1"
3022 + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
3023 + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
3024 + dependencies:
3025 + inherits "^2.0.3"
3026 + readable-stream "^2.3.6"
3027 +
3028 +follow-redirects@^1.0.0:
3029 + version "1.14.6"
3030 + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
3031 + integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
3032 +
3033 +for-in@^1.0.2:
3034 + version "1.0.2"
3035 + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
3036 + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
3037 +
3038 +forwarded@0.2.0:
3039 + version "0.2.0"
3040 + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
3041 + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
3042 +
3043 +fragment-cache@^0.2.1:
3044 + version "0.2.1"
3045 + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
3046 + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=
3047 + dependencies:
3048 + map-cache "^0.2.2"
3049 +
3050 +fresh@0.5.2:
3051 + version "0.5.2"
3052 + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
3053 + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
3054 +
3055 +from2@^2.1.0:
3056 + version "2.3.0"
3057 + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
3058 + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
3059 + dependencies:
3060 + inherits "^2.0.1"
3061 + readable-stream "^2.0.0"
3062 +
3063 +fs-minipass@^2.0.0:
3064 + version "2.1.0"
3065 + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
3066 + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
3067 + dependencies:
3068 + minipass "^3.0.0"
3069 +
3070 +fs-write-stream-atomic@^1.0.8:
3071 + version "1.0.10"
3072 + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
3073 + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=
3074 + dependencies:
3075 + graceful-fs "^4.1.2"
3076 + iferr "^0.1.5"
3077 + imurmurhash "^0.1.4"
3078 + readable-stream "1 || 2"
3079 +
3080 +fs.realpath@^1.0.0:
3081 + version "1.0.0"
3082 + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
3083 + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
3084 +
3085 +fsevents@^1.2.7:
3086 + version "1.2.13"
3087 + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
3088 + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
3089 + dependencies:
3090 + bindings "^1.5.0"
3091 + nan "^2.12.1"
3092 +
3093 +fsevents@~2.3.2:
3094 + version "2.3.2"
3095 + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
3096 + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
3097 +
3098 +function-bind@^1.1.1:
3099 + version "1.1.1"
3100 + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
3101 + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
3102 +
3103 +gensync@^1.0.0-beta.2:
3104 + version "1.0.0-beta.2"
3105 + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
3106 + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
3107 +
3108 +get-caller-file@^2.0.1:
3109 + version "2.0.5"
3110 + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
3111 + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
3112 +
3113 +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
3114 + version "1.1.1"
3115 + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
3116 + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
3117 + dependencies:
3118 + function-bind "^1.1.1"
3119 + has "^1.0.3"
3120 + has-symbols "^1.0.1"
3121 +
3122 +get-stream@^4.0.0:
3123 + version "4.1.0"
3124 + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
3125 + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
3126 + dependencies:
3127 + pump "^3.0.0"
3128 +
3129 +get-symbol-description@^1.0.0:
3130 + version "1.0.0"
3131 + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
3132 + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
3133 + dependencies:
3134 + call-bind "^1.0.2"
3135 + get-intrinsic "^1.1.1"
3136 +
3137 +get-value@^2.0.3, get-value@^2.0.6:
3138 + version "2.0.6"
3139 + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
3140 + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
3141 +
3142 +glob-parent@^3.1.0:
3143 + version "3.1.0"
3144 + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
3145 + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
3146 + dependencies:
3147 + is-glob "^3.1.0"
3148 + path-dirname "^1.0.0"
3149 +
3150 +glob-parent@~5.1.2:
3151 + version "5.1.2"
3152 + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
3153 + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
3154 + dependencies:
3155 + is-glob "^4.0.1"
3156 +
3157 +glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7:
3158 + version "7.2.0"
3159 + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
3160 + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
3161 + dependencies:
3162 + fs.realpath "^1.0.0"
3163 + inflight "^1.0.4"
3164 + inherits "2"
3165 + minimatch "^3.0.4"
3166 + once "^1.3.0"
3167 + path-is-absolute "^1.0.0"
3168 +
3169 +global-modules@^1.0.0:
3170 + version "1.0.0"
3171 + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
3172 + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
3173 + dependencies:
3174 + global-prefix "^1.0.1"
3175 + is-windows "^1.0.1"
3176 + resolve-dir "^1.0.0"
3177 +
3178 +global-modules@^2.0.0:
3179 + version "2.0.0"
3180 + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
3181 + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
3182 + dependencies:
3183 + global-prefix "^3.0.0"
3184 +
3185 +global-prefix@^1.0.1:
3186 + version "1.0.2"
3187 + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
3188 + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
3189 + dependencies:
3190 + expand-tilde "^2.0.2"
3191 + homedir-polyfill "^1.0.1"
3192 + ini "^1.3.4"
3193 + is-windows "^1.0.1"
3194 + which "^1.2.14"
3195 +
3196 +global-prefix@^3.0.0:
3197 + version "3.0.0"
3198 + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
3199 + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
3200 + dependencies:
3201 + ini "^1.3.5"
3202 + kind-of "^6.0.2"
3203 + which "^1.3.1"
3204 +
3205 +globals@^11.1.0:
3206 + version "11.12.0"
3207 + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
3208 + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
3209 +
3210 +globby@^6.1.0:
3211 + version "6.1.0"
3212 + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
3213 + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
3214 + dependencies:
3215 + array-union "^1.0.1"
3216 + glob "^7.0.3"
3217 + object-assign "^4.0.1"
3218 + pify "^2.0.0"
3219 + pinkie-promise "^2.0.0"
3220 +
3221 +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
3222 + version "4.2.8"
3223 + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
3224 + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
3225 +
3226 +handle-thing@^2.0.0:
3227 + version "2.0.1"
3228 + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
3229 + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
3230 +
3231 +has-bigints@^1.0.1:
3232 + version "1.0.1"
3233 + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
3234 + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
3235 +
3236 +has-flag@^3.0.0:
3237 + version "3.0.0"
3238 + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
3239 + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
3240 +
3241 +has-flag@^4.0.0:
3242 + version "4.0.0"
3243 + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
3244 + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
3245 +
3246 +has-symbols@^1.0.1, has-symbols@^1.0.2:
3247 + version "1.0.2"
3248 + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
3249 + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
3250 +
3251 +has-tostringtag@^1.0.0:
3252 + version "1.0.0"
3253 + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
3254 + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
3255 + dependencies:
3256 + has-symbols "^1.0.2"
3257 +
3258 +has-value@^0.3.1:
3259 + version "0.3.1"
3260 + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
3261 + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=
3262 + dependencies:
3263 + get-value "^2.0.3"
3264 + has-values "^0.1.4"
3265 + isobject "^2.0.0"
3266 +
3267 +has-value@^1.0.0:
3268 + version "1.0.0"
3269 + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
3270 + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=
3271 + dependencies:
3272 + get-value "^2.0.6"
3273 + has-values "^1.0.0"
3274 + isobject "^3.0.0"
3275 +
3276 +has-values@^0.1.4:
3277 + version "0.1.4"
3278 + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
3279 + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E=
3280 +
3281 +has-values@^1.0.0:
3282 + version "1.0.0"
3283 + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
3284 + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=
3285 + dependencies:
3286 + is-number "^3.0.0"
3287 + kind-of "^4.0.0"
3288 +
3289 +has@^1.0.0, has@^1.0.3:
3290 + version "1.0.3"
3291 + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
3292 + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
3293 + dependencies:
3294 + function-bind "^1.1.1"
3295 +
3296 +hash-base@^3.0.0:
3297 + version "3.1.0"
3298 + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
3299 + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
3300 + dependencies:
3301 + inherits "^2.0.4"
3302 + readable-stream "^3.6.0"
3303 + safe-buffer "^5.2.0"
3304 +
3305 +hash.js@^1.0.0, hash.js@^1.0.3:
3306 + version "1.1.7"
3307 + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
3308 + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
3309 + dependencies:
3310 + inherits "^2.0.3"
3311 + minimalistic-assert "^1.0.1"
3312 +
3313 +he@^1.2.0:
3314 + version "1.2.0"
3315 + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
3316 + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
3317 +
3318 +hex-color-regex@^1.1.0:
3319 + version "1.1.0"
3320 + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
3321 + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
3322 +
3323 +hmac-drbg@^1.0.1:
3324 + version "1.0.1"
3325 + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
3326 + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
3327 + dependencies:
3328 + hash.js "^1.0.3"
3329 + minimalistic-assert "^1.0.0"
3330 + minimalistic-crypto-utils "^1.0.1"
3331 +
3332 +homedir-polyfill@^1.0.1:
3333 + version "1.0.3"
3334 + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
3335 + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
3336 + dependencies:
3337 + parse-passwd "^1.0.0"
3338 +
3339 +hpack.js@^2.1.6:
3340 + version "2.1.6"
3341 + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
3342 + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=
3343 + dependencies:
3344 + inherits "^2.0.1"
3345 + obuf "^1.0.0"
3346 + readable-stream "^2.0.1"
3347 + wbuf "^1.1.0"
3348 +
3349 +hsl-regex@^1.0.0:
3350 + version "1.0.0"
3351 + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e"
3352 + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=
3353 +
3354 +hsla-regex@^1.0.0:
3355 + version "1.0.0"
3356 + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
3357 + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
3358 +
3359 +html-entities@^1.3.1:
3360 + version "1.4.0"
3361 + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
3362 + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
3363 +
3364 +html-loader@^3.1.0:
3365 + version "3.1.0"
3366 + resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-3.1.0.tgz#e5b9c1cf81b043786b15145eb30181575a22d9c8"
3367 + integrity sha512-ycMYFRiCF7YANcLDNP72kh3Po5pTcH+bROzdDwh00iVOAY/BwvpuZ1BKPziQ35Dk9D+UD84VGX1Lu/H4HpO4fw==
3368 + dependencies:
3369 + html-minifier-terser "^6.0.2"
3370 + parse5 "^6.0.1"
3371 +
3372 +html-minifier-terser@^6.0.2:
3373 + version "6.1.0"
3374 + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
3375 + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==
3376 + dependencies:
3377 + camel-case "^4.1.2"
3378 + clean-css "^5.2.2"
3379 + commander "^8.3.0"
3380 + he "^1.2.0"
3381 + param-case "^3.0.4"
3382 + relateurl "^0.2.7"
3383 + terser "^5.10.0"
3384 +
3385 +http-deceiver@^1.2.7:
3386 + version "1.2.7"
3387 + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
3388 + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
3389 +
3390 +http-errors@1.7.2:
3391 + version "1.7.2"
3392 + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
3393 + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
3394 + dependencies:
3395 + depd "~1.1.2"
3396 + inherits "2.0.3"
3397 + setprototypeof "1.1.1"
3398 + statuses ">= 1.5.0 < 2"
3399 + toidentifier "1.0.0"
3400 +
3401 +http-errors@~1.6.2:
3402 + version "1.6.3"
3403 + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
3404 + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
3405 + dependencies:
3406 + depd "~1.1.2"
3407 + inherits "2.0.3"
3408 + setprototypeof "1.1.0"
3409 + statuses ">= 1.4.0 < 2"
3410 +
3411 +http-errors@~1.7.2:
3412 + version "1.7.3"
3413 + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
3414 + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
3415 + dependencies:
3416 + depd "~1.1.2"
3417 + inherits "2.0.4"
3418 + setprototypeof "1.1.1"
3419 + statuses ">= 1.5.0 < 2"
3420 + toidentifier "1.0.0"
3421 +
3422 +http-parser-js@>=0.5.1:
3423 + version "0.5.5"
3424 + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5"
3425 + integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==
3426 +
3427 +http-proxy-middleware@0.19.1:
3428 + version "0.19.1"
3429 + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a"
3430 + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==
3431 + dependencies:
3432 + http-proxy "^1.17.0"
3433 + is-glob "^4.0.0"
3434 + lodash "^4.17.11"
3435 + micromatch "^3.1.10"
3436 +
3437 +http-proxy@^1.17.0:
3438 + version "1.18.1"
3439 + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
3440 + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
3441 + dependencies:
3442 + eventemitter3 "^4.0.0"
3443 + follow-redirects "^1.0.0"
3444 + requires-port "^1.0.0"
3445 +
3446 +https-browserify@^1.0.0:
3447 + version "1.0.0"
3448 + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
3449 + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
3450 +
3451 +iconv-lite@0.4.24:
3452 + version "0.4.24"
3453 + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
3454 + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
3455 + dependencies:
3456 + safer-buffer ">= 2.1.2 < 3"
3457 +
3458 +icss-utils@^4.0.0, icss-utils@^4.1.1:
3459 + version "4.1.1"
3460 + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
3461 + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
3462 + dependencies:
3463 + postcss "^7.0.14"
3464 +
3465 +ieee754@^1.1.4:
3466 + version "1.2.1"
3467 + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
3468 + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
3469 +
3470 +iferr@^0.1.5:
3471 + version "0.1.5"
3472 + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
3473 + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
3474 +
3475 +image-size@~0.5.0:
3476 + version "0.5.5"
3477 + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
3478 + integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
3479 +
3480 +immutable@^4.0.0:
3481 + version "4.0.0"
3482 + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
3483 + integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
3484 +
3485 +import-cwd@^2.0.0:
3486 + version "2.1.0"
3487 + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
3488 + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=
3489 + dependencies:
3490 + import-from "^2.1.0"
3491 +
3492 +import-fresh@^2.0.0:
3493 + version "2.0.0"
3494 + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
3495 + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
3496 + dependencies:
3497 + caller-path "^2.0.0"
3498 + resolve-from "^3.0.0"
3499 +
3500 +import-fresh@^3.1.0:
3501 + version "3.3.0"
3502 + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
3503 + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
3504 + dependencies:
3505 + parent-module "^1.0.0"
3506 + resolve-from "^4.0.0"
3507 +
3508 +import-from@^2.1.0:
3509 + version "2.1.0"
3510 + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
3511 + integrity sha1-M1238qev/VOqpHHUuAId7ja387E=
3512 + dependencies:
3513 + resolve-from "^3.0.0"
3514 +
3515 +import-local@^2.0.0:
3516 + version "2.0.0"
3517 + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
3518 + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
3519 + dependencies:
3520 + pkg-dir "^3.0.0"
3521 + resolve-cwd "^2.0.0"
3522 +
3523 +imurmurhash@^0.1.4:
3524 + version "0.1.4"
3525 + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
3526 + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
3527 +
3528 +indent-string@^4.0.0:
3529 + version "4.0.0"
3530 + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
3531 + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
3532 +
3533 +indexes-of@^1.0.1:
3534 + version "1.0.1"
3535 + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
3536 + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
3537 +
3538 +infer-owner@^1.0.3, infer-owner@^1.0.4:
3539 + version "1.0.4"
3540 + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
3541 + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
3542 +
3543 +inflight@^1.0.4:
3544 + version "1.0.6"
3545 + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
3546 + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
3547 + dependencies:
3548 + once "^1.3.0"
3549 + wrappy "1"
3550 +
3551 +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
3552 + version "2.0.4"
3553 + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
3554 + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
3555 +
3556 +inherits@2.0.1:
3557 + version "2.0.1"
3558 + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
3559 + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
3560 +
3561 +inherits@2.0.3:
3562 + version "2.0.3"
3563 + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
3564 + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
3565 +
3566 +ini@^1.3.4, ini@^1.3.5:
3567 + version "1.3.8"
3568 + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
3569 + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
3570 +
3571 +internal-ip@^4.3.0:
3572 + version "4.3.0"
3573 + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
3574 + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==
3575 + dependencies:
3576 + default-gateway "^4.2.0"
3577 + ipaddr.js "^1.9.0"
3578 +
3579 +internal-slot@^1.0.3:
3580 + version "1.0.3"
3581 + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
3582 + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
3583 + dependencies:
3584 + get-intrinsic "^1.1.0"
3585 + has "^1.0.3"
3586 + side-channel "^1.0.4"
3587 +
3588 +interpret@^1.4.0:
3589 + version "1.4.0"
3590 + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
3591 + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
3592 +
3593 +ip-regex@^2.1.0:
3594 + version "2.1.0"
3595 + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
3596 + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
3597 +
3598 +ip@^1.1.0, ip@^1.1.5:
3599 + version "1.1.5"
3600 + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
3601 + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
3602 +
3603 +ipaddr.js@1.9.1, ipaddr.js@^1.9.0:
3604 + version "1.9.1"
3605 + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
3606 + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
3607 +
3608 +is-absolute-url@^2.0.0:
3609 + version "2.1.0"
3610 + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
3611 + integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=
3612 +
3613 +is-absolute-url@^3.0.3:
3614 + version "3.0.3"
3615 + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698"
3616 + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==
3617 +
3618 +is-accessor-descriptor@^0.1.6:
3619 + version "0.1.6"
3620 + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
3621 + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=
3622 + dependencies:
3623 + kind-of "^3.0.2"
3624 +
3625 +is-accessor-descriptor@^1.0.0:
3626 + version "1.0.0"
3627 + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
3628 + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==
3629 + dependencies:
3630 + kind-of "^6.0.0"
3631 +
3632 +is-arguments@^1.0.4:
3633 + version "1.1.1"
3634 + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
3635 + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
3636 + dependencies:
3637 + call-bind "^1.0.2"
3638 + has-tostringtag "^1.0.0"
3639 +
3640 +is-arrayish@^0.2.1:
3641 + version "0.2.1"
3642 + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
3643 + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
3644 +
3645 +is-arrayish@^0.3.1:
3646 + version "0.3.2"
3647 + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
3648 + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
3649 +
3650 +is-bigint@^1.0.1:
3651 + version "1.0.4"
3652 + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
3653 + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
3654 + dependencies:
3655 + has-bigints "^1.0.1"
3656 +
3657 +is-binary-path@^1.0.0:
3658 + version "1.0.1"
3659 + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
3660 + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=
3661 + dependencies:
3662 + binary-extensions "^1.0.0"
3663 +
3664 +is-binary-path@~2.1.0:
3665 + version "2.1.0"
3666 + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
3667 + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
3668 + dependencies:
3669 + binary-extensions "^2.0.0"
3670 +
3671 +is-boolean-object@^1.1.0:
3672 + version "1.1.2"
3673 + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
3674 + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
3675 + dependencies:
3676 + call-bind "^1.0.2"
3677 + has-tostringtag "^1.0.0"
3678 +
3679 +is-buffer@^1.1.5:
3680 + version "1.1.6"
3681 + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
3682 + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
3683 +
3684 +is-callable@^1.1.4, is-callable@^1.2.4:
3685 + version "1.2.4"
3686 + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
3687 + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
3688 +
3689 +is-color-stop@^1.0.0:
3690 + version "1.1.0"
3691 + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345"
3692 + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=
3693 + dependencies:
3694 + css-color-names "^0.0.4"
3695 + hex-color-regex "^1.1.0"
3696 + hsl-regex "^1.0.0"
3697 + hsla-regex "^1.0.0"
3698 + rgb-regex "^1.0.1"
3699 + rgba-regex "^1.0.0"
3700 +
3701 +is-core-module@^2.2.0:
3702 + version "2.8.0"
3703 + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548"
3704 + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==
3705 + dependencies:
3706 + has "^1.0.3"
3707 +
3708 +is-data-descriptor@^0.1.4:
3709 + version "0.1.4"
3710 + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
3711 + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=
3712 + dependencies:
3713 + kind-of "^3.0.2"
3714 +
3715 +is-data-descriptor@^1.0.0:
3716 + version "1.0.0"
3717 + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
3718 + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==
3719 + dependencies:
3720 + kind-of "^6.0.0"
3721 +
3722 +is-date-object@^1.0.1:
3723 + version "1.0.5"
3724 + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
3725 + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
3726 + dependencies:
3727 + has-tostringtag "^1.0.0"
3728 +
3729 +is-descriptor@^0.1.0:
3730 + version "0.1.6"
3731 + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
3732 + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==
3733 + dependencies:
3734 + is-accessor-descriptor "^0.1.6"
3735 + is-data-descriptor "^0.1.4"
3736 + kind-of "^5.0.0"
3737 +
3738 +is-descriptor@^1.0.0, is-descriptor@^1.0.2:
3739 + version "1.0.2"
3740 + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
3741 + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==
3742 + dependencies:
3743 + is-accessor-descriptor "^1.0.0"
3744 + is-data-descriptor "^1.0.0"
3745 + kind-of "^6.0.2"
3746 +
3747 +is-directory@^0.3.1:
3748 + version "0.3.1"
3749 + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
3750 + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
3751 +
3752 +is-extendable@^0.1.0, is-extendable@^0.1.1:
3753 + version "0.1.1"
3754 + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
3755 + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=
3756 +
3757 +is-extendable@^1.0.1:
3758 + version "1.0.1"
3759 + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
3760 + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==
3761 + dependencies:
3762 + is-plain-object "^2.0.4"
3763 +
3764 +is-extglob@^2.1.0, is-extglob@^2.1.1:
3765 + version "2.1.1"
3766 + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
3767 + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
3768 +
3769 +is-fullwidth-code-point@^2.0.0:
3770 + version "2.0.0"
3771 + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
3772 + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
3773 +
3774 +is-glob@^3.1.0:
3775 + version "3.1.0"
3776 + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
3777 + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=
3778 + dependencies:
3779 + is-extglob "^2.1.0"
3780 +
3781 +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
3782 + version "4.0.3"
3783 + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
3784 + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
3785 + dependencies:
3786 + is-extglob "^2.1.1"
3787 +
3788 +is-negative-zero@^2.0.1:
3789 + version "2.0.2"
3790 + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
3791 + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
3792 +
3793 +is-number-object@^1.0.4:
3794 + version "1.0.6"
3795 + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
3796 + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==
3797 + dependencies:
3798 + has-tostringtag "^1.0.0"
3799 +
3800 +is-number@^3.0.0:
3801 + version "3.0.0"
3802 + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
3803 + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=
3804 + dependencies:
3805 + kind-of "^3.0.2"
3806 +
3807 +is-number@^7.0.0:
3808 + version "7.0.0"
3809 + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
3810 + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
3811 +
3812 +is-obj@^2.0.0:
3813 + version "2.0.0"
3814 + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
3815 + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
3816 +
3817 +is-path-cwd@^2.0.0:
3818 + version "2.2.0"
3819 + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
3820 + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
3821 +
3822 +is-path-in-cwd@^2.0.0:
3823 + version "2.1.0"
3824 + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb"
3825 + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==
3826 + dependencies:
3827 + is-path-inside "^2.1.0"
3828 +
3829 +is-path-inside@^2.1.0:
3830 + version "2.1.0"
3831 + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2"
3832 + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==
3833 + dependencies:
3834 + path-is-inside "^1.0.2"
3835 +
3836 +is-plain-obj@^1.0.0:
3837 + version "1.1.0"
3838 + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
3839 + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
3840 +
3841 +is-plain-object@^2.0.3, is-plain-object@^2.0.4:
3842 + version "2.0.4"
3843 + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
3844 + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
3845 + dependencies:
3846 + isobject "^3.0.1"
3847 +
3848 +is-regex@^1.0.4, is-regex@^1.1.4:
3849 + version "1.1.4"
3850 + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
3851 + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
3852 + dependencies:
3853 + call-bind "^1.0.2"
3854 + has-tostringtag "^1.0.0"
3855 +
3856 +is-resolvable@^1.0.0:
3857 + version "1.1.0"
3858 + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
3859 + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
3860 +
3861 +is-shared-array-buffer@^1.0.1:
3862 + version "1.0.1"
3863 + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
3864 + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
3865 +
3866 +is-stream@^1.1.0:
3867 + version "1.1.0"
3868 + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
3869 + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
3870 +
3871 +is-string@^1.0.5, is-string@^1.0.7:
3872 + version "1.0.7"
3873 + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
3874 + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
3875 + dependencies:
3876 + has-tostringtag "^1.0.0"
3877 +
3878 +is-symbol@^1.0.2, is-symbol@^1.0.3:
3879 + version "1.0.4"
3880 + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
3881 + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
3882 + dependencies:
3883 + has-symbols "^1.0.2"
3884 +
3885 +is-weakref@^1.0.1:
3886 + version "1.0.2"
3887 + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
3888 + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
3889 + dependencies:
3890 + call-bind "^1.0.2"
3891 +
3892 +is-what@^3.14.1:
3893 + version "3.14.1"
3894 + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
3895 + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
3896 +
3897 +is-windows@^1.0.1, is-windows@^1.0.2:
3898 + version "1.0.2"
3899 + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
3900 + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
3901 +
3902 +is-wsl@^1.1.0:
3903 + version "1.1.0"
3904 + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
3905 + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
3906 +
3907 +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
3908 + version "1.0.0"
3909 + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
3910 + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
3911 +
3912 +isexe@^2.0.0:
3913 + version "2.0.0"
3914 + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
3915 + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
3916 +
3917 +isobject@^2.0.0:
3918 + version "2.1.0"
3919 + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
3920 + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
3921 + dependencies:
3922 + isarray "1.0.0"
3923 +
3924 +isobject@^3.0.0, isobject@^3.0.1:
3925 + version "3.0.1"
3926 + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
3927 + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
3928 +
3929 +jest-worker@^26.5.0:
3930 + version "26.6.2"
3931 + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
3932 + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
3933 + dependencies:
3934 + "@types/node" "*"
3935 + merge-stream "^2.0.0"
3936 + supports-color "^7.0.0"
3937 +
3938 +jquery-ui@^1.12.1:
3939 + version "1.13.0"
3940 + resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.0.tgz#ab5ac65f37ca093c51b3478c4097f55bbc008f36"
3941 + integrity sha512-Osf7ECXNTYHtKBkn9xzbIf9kifNrBhfywFEKxOeB/OVctVmLlouV9mfc2qXCp6uyO4Pn72PXKOnj09qXetopCw==
3942 + dependencies:
3943 + jquery ">=1.8.0 <4.0.0"
3944 +
3945 +jquery-ujs@^1.2.2:
3946 + version "1.2.3"
3947 + resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.3.tgz#dcac6026ab7268e5ee41faf9d31c997cd4ddd603"
3948 + integrity sha512-59wvfx5vcCTHMeQT1/OwFiAj+UffLIwjRIoXdpO7Z7BCFGepzq9T9oLVeoItjTqjoXfUrHJvV7QU6pUR+UzOoA==
3949 +
3950 +"jquery@>=1.8.0 <4.0.0", jquery@^3.4.1, jquery@^3.6.0:
3951 + version "3.6.0"
3952 + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
3953 + integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
3954 +
3955 +js-tokens@^4.0.0:
3956 + version "4.0.0"
3957 + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
3958 + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
3959 +
3960 +js-yaml@^3.13.1, js-yaml@^3.14.1:
3961 + version "3.14.1"
3962 + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
3963 + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
3964 + dependencies:
3965 + argparse "^1.0.7"
3966 + esprima "^4.0.0"
3967 +
3968 +jsesc@^2.5.1:
3969 + version "2.5.2"
3970 + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
3971 + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
3972 +
3973 +jsesc@~0.5.0:
3974 + version "0.5.0"
3975 + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
3976 + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
3977 +
3978 +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
3979 + version "1.0.2"
3980 + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
3981 + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
3982 +
3983 +json-parse-even-better-errors@^2.3.0:
3984 + version "2.3.1"
3985 + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
3986 + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
3987 +
3988 +json-schema-traverse@^0.4.1:
3989 + version "0.4.1"
3990 + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
3991 + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
3992 +
3993 +json3@^3.3.3:
3994 + version "3.3.3"
3995 + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
3996 + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
3997 +
3998 +json5@^1.0.1:
3999 + version "1.0.1"
4000 + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
4001 + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
4002 + dependencies:
4003 + minimist "^1.2.0"
4004 +
4005 +json5@^2.1.2:
4006 + version "2.2.0"
4007 + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
4008 + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
4009 + dependencies:
4010 + minimist "^1.2.5"
4011 +
4012 +killable@^1.0.1:
4013 + version "1.0.1"
4014 + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
4015 + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==
4016 +
4017 +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
4018 + version "3.2.2"
4019 + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
4020 + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
4021 + dependencies:
4022 + is-buffer "^1.1.5"
4023 +
4024 +kind-of@^4.0.0:
4025 + version "4.0.0"
4026 + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
4027 + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc=
4028 + dependencies:
4029 + is-buffer "^1.1.5"
4030 +
4031 +kind-of@^5.0.0:
4032 + version "5.1.0"
4033 + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
4034 + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
4035 +
4036 +kind-of@^6.0.0, kind-of@^6.0.2:
4037 + version "6.0.3"
4038 + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
4039 + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
4040 +
4041 +klona@^2.0.4:
4042 + version "2.0.5"
4043 + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
4044 + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
4045 +
4046 +last-call-webpack-plugin@^3.0.0:
4047 + version "3.0.0"
4048 + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555"
4049 + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==
4050 + dependencies:
4051 + lodash "^4.17.5"
4052 + webpack-sources "^1.1.0"
4053 +
4054 +less-loader@^10.2.0:
4055 + version "10.2.0"
4056 + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-10.2.0.tgz#97286d8797dc3dc05b1d16b0ecec5f968bdd4e32"
4057 + integrity sha512-AV5KHWvCezW27GT90WATaDnfXBv99llDbtaj4bshq6DvAihMdNjaPDcUMa6EXKLRF+P2opFenJp89BXg91XLYg==
4058 + dependencies:
4059 + klona "^2.0.4"
4060 +
4061 +less@^3.5.0:
4062 + version "3.13.1"
4063 + resolved "https://registry.yarnpkg.com/less/-/less-3.13.1.tgz#0ebc91d2a0e9c0c6735b83d496b0ab0583077909"
4064 + integrity sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==
4065 + dependencies:
4066 + copy-anything "^2.0.1"
4067 + tslib "^1.10.0"
4068 + optionalDependencies:
4069 + errno "^0.1.1"
4070 + graceful-fs "^4.1.2"
4071 + image-size "~0.5.0"
4072 + make-dir "^2.1.0"
4073 + mime "^1.4.1"
4074 + native-request "^1.0.5"
4075 + source-map "~0.6.0"
4076 +
4077 +lines-and-columns@^1.1.6:
4078 + version "1.2.4"
4079 + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
4080 + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
4081 +
4082 +loader-runner@^2.4.0:
4083 + version "2.4.0"
4084 + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
4085 + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
4086 +
4087 +loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
4088 + version "1.4.0"
4089 + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
4090 + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
4091 + dependencies:
4092 + big.js "^5.2.2"
4093 + emojis-list "^3.0.0"
4094 + json5 "^1.0.1"
4095 +
4096 +loader-utils@^2.0.0:
4097 + version "2.0.2"
4098 + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
4099 + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
4100 + dependencies:
4101 + big.js "^5.2.2"
4102 + emojis-list "^3.0.0"
4103 + json5 "^2.1.2"
4104 +
4105 +locate-path@^3.0.0:
4106 + version "3.0.0"
4107 + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
4108 + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
4109 + dependencies:
4110 + p-locate "^3.0.0"
4111 + path-exists "^3.0.0"
4112 +
4113 +locate-path@^5.0.0:
4114 + version "5.0.0"
4115 + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
4116 + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
4117 + dependencies:
4118 + p-locate "^4.1.0"
4119 +
4120 +lodash.debounce@^4.0.8:
4121 + version "4.0.8"
4122 + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
4123 + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
4124 +
4125 +lodash.get@^4.0:
4126 + version "4.4.2"
4127 + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
4128 + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
4129 +
4130 +lodash.has@^4.0:
4131 + version "4.5.2"
4132 + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862"
4133 + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=
4134 +
4135 +lodash.memoize@^4.1.2:
4136 + version "4.1.2"
4137 + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
4138 + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
4139 +
4140 +lodash.uniq@^4.5.0:
4141 + version "4.5.0"
4142 + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
4143 + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
4144 +
4145 +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.5:
4146 + version "4.17.21"
4147 + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
4148 + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
4149 +
4150 +loglevel@^1.6.8:
4151 + version "1.8.0"
4152 + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114"
4153 + integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==
4154 +
4155 +lower-case@^2.0.2:
4156 + version "2.0.2"
4157 + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
4158 + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
4159 + dependencies:
4160 + tslib "^2.0.3"
4161 +
4162 +lru-cache@^5.1.1:
4163 + version "5.1.1"
4164 + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
4165 + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
4166 + dependencies:
4167 + yallist "^3.0.2"
4168 +
4169 +lru-cache@^6.0.0:
4170 + version "6.0.0"
4171 + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
4172 + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
4173 + dependencies:
4174 + yallist "^4.0.0"
4175 +
4176 +make-dir@^2.0.0, make-dir@^2.1.0:
4177 + version "2.1.0"
4178 + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
4179 + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
4180 + dependencies:
4181 + pify "^4.0.1"
4182 + semver "^5.6.0"
4183 +
4184 +make-dir@^3.0.2, make-dir@^3.1.0:
4185 + version "3.1.0"
4186 + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
4187 + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
4188 + dependencies:
4189 + semver "^6.0.0"
4190 +
4191 +map-cache@^0.2.2:
4192 + version "0.2.2"
4193 + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
4194 + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
4195 +
4196 +map-visit@^1.0.0:
4197 + version "1.0.0"
4198 + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
4199 + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=
4200 + dependencies:
4201 + object-visit "^1.0.0"
4202 +
4203 +md5.js@^1.3.4:
4204 + version "1.3.5"
4205 + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
4206 + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
4207 + dependencies:
4208 + hash-base "^3.0.0"
4209 + inherits "^2.0.1"
4210 + safe-buffer "^5.1.2"
4211 +
4212 +mdn-data@2.0.14:
4213 + version "2.0.14"
4214 + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
4215 + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
4216 +
4217 +mdn-data@2.0.4:
4218 + version "2.0.4"
4219 + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
4220 + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
4221 +
4222 +media-typer@0.3.0:
4223 + version "0.3.0"
4224 + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
4225 + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
4226 +
4227 +memory-fs@^0.4.1:
4228 + version "0.4.1"
4229 + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
4230 + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
4231 + dependencies:
4232 + errno "^0.1.3"
4233 + readable-stream "^2.0.1"
4234 +
4235 +memory-fs@^0.5.0:
4236 + version "0.5.0"
4237 + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
4238 + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
4239 + dependencies:
4240 + errno "^0.1.3"
4241 + readable-stream "^2.0.1"
4242 +
4243 +merge-descriptors@1.0.1:
4244 + version "1.0.1"
4245 + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
4246 + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
4247 +
4248 +merge-stream@^2.0.0:
4249 + version "2.0.0"
4250 + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
4251 + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
4252 +
4253 +methods@~1.1.2:
4254 + version "1.1.2"
4255 + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
4256 + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
4257 +
4258 +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
4259 + version "3.1.10"
4260 + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
4261 + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
4262 + dependencies:
4263 + arr-diff "^4.0.0"
4264 + array-unique "^0.3.2"
4265 + braces "^2.3.1"
4266 + define-property "^2.0.2"
4267 + extend-shallow "^3.0.2"
4268 + extglob "^2.0.4"
4269 + fragment-cache "^0.2.1"
4270 + kind-of "^6.0.2"
4271 + nanomatch "^1.2.9"
4272 + object.pick "^1.3.0"
4273 + regex-not "^1.0.0"
4274 + snapdragon "^0.8.1"
4275 + to-regex "^3.0.2"
4276 +
4277 +miller-rabin@^4.0.0:
4278 + version "4.0.1"
4279 + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
4280 + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
4281 + dependencies:
4282 + bn.js "^4.0.0"
4283 + brorand "^1.0.1"
4284 +
4285 +mime-db@1.51.0, "mime-db@>= 1.43.0 < 2":
4286 + version "1.51.0"
4287 + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
4288 + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
4289 +
4290 +mime-types@~2.1.17, mime-types@~2.1.24:
4291 + version "2.1.34"
4292 + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
4293 + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
4294 + dependencies:
4295 + mime-db "1.51.0"
4296 +
4297 +mime@1.6.0, mime@^1.4.1:
4298 + version "1.6.0"
4299 + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
4300 + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
4301 +
4302 +mime@^2.4.4:
4303 + version "2.6.0"
4304 + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
4305 + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
4306 +
4307 +mini-css-extract-plugin@^0.9.0:
4308 + version "0.9.0"
4309 + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
4310 + integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==
4311 + dependencies:
4312 + loader-utils "^1.1.0"
4313 + normalize-url "1.9.1"
4314 + schema-utils "^1.0.0"
4315 + webpack-sources "^1.1.0"
4316 +
4317 +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
4318 + version "1.0.1"
4319 + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
4320 + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
4321 +
4322 +minimalistic-crypto-utils@^1.0.1:
4323 + version "1.0.1"
4324 + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
4325 + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
4326 +
4327 +minimatch@^3.0.4:
4328 + version "3.0.4"
4329 + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
4330 + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
4331 + dependencies:
4332 + brace-expansion "^1.1.7"
4333 +
4334 +minimist@^1.2.0, minimist@^1.2.5:
4335 + version "1.2.5"
4336 + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
4337 + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
4338 +
4339 +minipass-collect@^1.0.2:
4340 + version "1.0.2"
4341 + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
4342 + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==
4343 + dependencies:
4344 + minipass "^3.0.0"
4345 +
4346 +minipass-flush@^1.0.5:
4347 + version "1.0.5"
4348 + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373"
4349 + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==
4350 + dependencies:
4351 + minipass "^3.0.0"
4352 +
4353 +minipass-pipeline@^1.2.2:
4354 + version "1.2.4"
4355 + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c"
4356 + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==
4357 + dependencies:
4358 + minipass "^3.0.0"
4359 +
4360 +minipass@^3.0.0, minipass@^3.1.1:
4361 + version "3.1.6"
4362 + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee"
4363 + integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==
4364 + dependencies:
4365 + yallist "^4.0.0"
4366 +
4367 +minizlib@^2.1.1:
4368 + version "2.1.2"
4369 + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
4370 + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
4371 + dependencies:
4372 + minipass "^3.0.0"
4373 + yallist "^4.0.0"
4374 +
4375 +mississippi@^3.0.0:
4376 + version "3.0.0"
4377 + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
4378 + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==
4379 + dependencies:
4380 + concat-stream "^1.5.0"
4381 + duplexify "^3.4.2"
4382 + end-of-stream "^1.1.0"
4383 + flush-write-stream "^1.0.0"
4384 + from2 "^2.1.0"
4385 + parallel-transform "^1.1.0"
4386 + pump "^3.0.0"
4387 + pumpify "^1.3.3"
4388 + stream-each "^1.1.0"
4389 + through2 "^2.0.0"
4390 +
4391 +mixin-deep@^1.2.0:
4392 + version "1.3.2"
4393 + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
4394 + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
4395 + dependencies:
4396 + for-in "^1.0.2"
4397 + is-extendable "^1.0.1"
4398 +
4399 +mkdirp@^0.5, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
4400 + version "0.5.5"
4401 + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
4402 + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
4403 + dependencies:
4404 + minimist "^1.2.5"
4405 +
4406 +mkdirp@^1.0.3, mkdirp@^1.0.4:
4407 + version "1.0.4"
4408 + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
4409 + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
4410 +
4411 +move-concurrently@^1.0.1:
4412 + version "1.0.1"
4413 + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
4414 + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=
4415 + dependencies:
4416 + aproba "^1.1.1"
4417 + copy-concurrently "^1.0.0"
4418 + fs-write-stream-atomic "^1.0.8"
4419 + mkdirp "^0.5.1"
4420 + rimraf "^2.5.4"
4421 + run-queue "^1.0.3"
4422 +
4423 +ms@2.0.0:
4424 + version "2.0.0"
4425 + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
4426 + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
4427 +
4428 +ms@2.1.1:
4429 + version "2.1.1"
4430 + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
4431 + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
4432 +
4433 +ms@2.1.2:
4434 + version "2.1.2"
4435 + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
4436 + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
4437 +
4438 +ms@^2.1.1:
4439 + version "2.1.3"
4440 + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
4441 + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
4442 +
4443 +multicast-dns-service-types@^1.1.0:
4444 + version "1.1.0"
4445 + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
4446 + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=
4447 +
4448 +multicast-dns@^6.0.1:
4449 + version "6.2.3"
4450 + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229"
4451 + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==
4452 + dependencies:
4453 + dns-packet "^1.3.1"
4454 + thunky "^1.0.2"
4455 +
4456 +nan@^2.12.1:
4457 + version "2.15.0"
4458 + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
4459 + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
4460 +
4461 +nanomatch@^1.2.9:
4462 + version "1.2.13"
4463 + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
4464 + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
4465 + dependencies:
4466 + arr-diff "^4.0.0"
4467 + array-unique "^0.3.2"
4468 + define-property "^2.0.2"
4469 + extend-shallow "^3.0.2"
4470 + fragment-cache "^0.2.1"
4471 + is-windows "^1.0.2"
4472 + kind-of "^6.0.2"
4473 + object.pick "^1.3.0"
4474 + regex-not "^1.0.0"
4475 + snapdragon "^0.8.1"
4476 + to-regex "^3.0.1"
4477 +
4478 +native-request@^1.0.5:
4479 + version "1.1.0"
4480 + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.1.0.tgz#acdb30fe2eefa3e1bc8c54b3a6852e9c5c0d3cb0"
4481 + integrity sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==
4482 +
4483 +negotiator@0.6.2:
4484 + version "0.6.2"
4485 + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
4486 + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
4487 +
4488 +neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2:
4489 + version "2.6.2"
4490 + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
4491 + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
4492 +
4493 +nice-try@^1.0.4:
4494 + version "1.0.5"
4495 + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
4496 + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
4497 +
4498 +no-case@^3.0.4:
4499 + version "3.0.4"
4500 + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
4501 + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
4502 + dependencies:
4503 + lower-case "^2.0.2"
4504 + tslib "^2.0.3"
4505 +
4506 +node-forge@^0.10.0:
4507 + version "0.10.0"
4508 + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
4509 + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
4510 +
4511 +node-libs-browser@^2.2.1:
4512 + version "2.2.1"
4513 + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
4514 + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
4515 + dependencies:
4516 + assert "^1.1.1"
4517 + browserify-zlib "^0.2.0"
4518 + buffer "^4.3.0"
4519 + console-browserify "^1.1.0"
4520 + constants-browserify "^1.0.0"
4521 + crypto-browserify "^3.11.0"
4522 + domain-browser "^1.1.1"
4523 + events "^3.0.0"
4524 + https-browserify "^1.0.0"
4525 + os-browserify "^0.3.0"
4526 + path-browserify "0.0.1"
4527 + process "^0.11.10"
4528 + punycode "^1.2.4"
4529 + querystring-es3 "^0.2.0"
4530 + readable-stream "^2.3.3"
4531 + stream-browserify "^2.0.1"
4532 + stream-http "^2.7.2"
4533 + string_decoder "^1.0.0"
4534 + timers-browserify "^2.0.4"
4535 + tty-browserify "0.0.0"
4536 + url "^0.11.0"
4537 + util "^0.11.0"
4538 + vm-browserify "^1.0.1"
4539 +
4540 +node-releases@^2.0.1:
4541 + version "2.0.1"
4542 + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
4543 + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
4544 +
4545 +normalize-path@^2.1.1:
4546 + version "2.1.1"
4547 + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
4548 + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
4549 + dependencies:
4550 + remove-trailing-separator "^1.0.1"
4551 +
4552 +normalize-path@^3.0.0, normalize-path@~3.0.0:
4553 + version "3.0.0"
4554 + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
4555 + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
4556 +
4557 +normalize-range@^0.1.2:
4558 + version "0.1.2"
4559 + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
4560 + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
4561 +
4562 +normalize-url@1.9.1:
4563 + version "1.9.1"
4564 + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
4565 + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
4566 + dependencies:
4567 + object-assign "^4.0.1"
4568 + prepend-http "^1.0.0"
4569 + query-string "^4.1.0"
4570 + sort-keys "^1.0.0"
4571 +
4572 +normalize-url@^3.0.0:
4573 + version "3.3.0"
4574 + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
4575 + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
4576 +
4577 +npm-run-path@^2.0.0:
4578 + version "2.0.2"
4579 + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
4580 + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
4581 + dependencies:
4582 + path-key "^2.0.0"
4583 +
4584 +nth-check@^1.0.2:
4585 + version "1.0.2"
4586 + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
4587 + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
4588 + dependencies:
4589 + boolbase "~1.0.0"
4590 +
4591 +num2fraction@^1.2.2:
4592 + version "1.2.2"
4593 + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
4594 + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
4595 +
4596 +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
4597 + version "4.1.1"
4598 + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
4599 + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
4600 +
4601 +object-copy@^0.1.0:
4602 + version "0.1.0"
4603 + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
4604 + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw=
4605 + dependencies:
4606 + copy-descriptor "^0.1.0"
4607 + define-property "^0.2.5"
4608 + kind-of "^3.0.3"
4609 +
4610 +object-inspect@^1.11.0, object-inspect@^1.9.0:
4611 + version "1.11.1"
4612 + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.1.tgz#d4bd7d7de54b9a75599f59a00bd698c1f1c6549b"
4613 + integrity sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==
4614 +
4615 +object-is@^1.0.1:
4616 + version "1.1.5"
4617 + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
4618 + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
4619 + dependencies:
4620 + call-bind "^1.0.2"
4621 + define-properties "^1.1.3"
4622 +
4623 +object-keys@^1.0.12, object-keys@^1.1.1:
4624 + version "1.1.1"
4625 + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
4626 + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
4627 +
4628 +object-visit@^1.0.0:
4629 + version "1.0.1"
4630 + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
4631 + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=
4632 + dependencies:
4633 + isobject "^3.0.0"
4634 +
4635 +object.assign@^4.1.0, object.assign@^4.1.2:
4636 + version "4.1.2"
4637 + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
4638 + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
4639 + dependencies:
4640 + call-bind "^1.0.0"
4641 + define-properties "^1.1.3"
4642 + has-symbols "^1.0.1"
4643 + object-keys "^1.1.1"
4644 +
4645 +object.getownpropertydescriptors@^2.1.0:
4646 + version "2.1.3"
4647 + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e"
4648 + integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==
4649 + dependencies:
4650 + call-bind "^1.0.2"
4651 + define-properties "^1.1.3"
4652 + es-abstract "^1.19.1"
4653 +
4654 +object.pick@^1.3.0:
4655 + version "1.3.0"
4656 + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
4657 + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=
4658 + dependencies:
4659 + isobject "^3.0.1"
4660 +
4661 +object.values@^1.1.0:
4662 + version "1.1.5"
4663 + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac"
4664 + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==
4665 + dependencies:
4666 + call-bind "^1.0.2"
4667 + define-properties "^1.1.3"
4668 + es-abstract "^1.19.1"
4669 +
4670 +obuf@^1.0.0, obuf@^1.1.2:
4671 + version "1.1.2"
4672 + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
4673 + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
4674 +
4675 +on-finished@~2.3.0:
4676 + version "2.3.0"
4677 + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
4678 + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
4679 + dependencies:
4680 + ee-first "1.1.1"
4681 +
4682 +on-headers@~1.0.2:
4683 + version "1.0.2"
4684 + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
4685 + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
4686 +
4687 +once@^1.3.0, once@^1.3.1, once@^1.4.0:
4688 + version "1.4.0"
4689 + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
4690 + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
4691 + dependencies:
4692 + wrappy "1"
4693 +
4694 +opn@^5.5.0:
4695 + version "5.5.0"
4696 + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
4697 + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
4698 + dependencies:
4699 + is-wsl "^1.1.0"
4700 +
4701 +optimize-css-assets-webpack-plugin@^5.0.8:
4702 + version "5.0.8"
4703 + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.8.tgz#cbccdcf5a6ef61d4f8cc78cf083a67446e5f402a"
4704 + integrity sha512-mgFS1JdOtEGzD8l+EuISqL57cKO+We9GcoiQEmdCWRqqck+FGNmYJtx9qfAPzEz+lRrlThWMuGDaRkI/yWNx/Q==
4705 + dependencies:
4706 + cssnano "^4.1.10"
4707 + last-call-webpack-plugin "^3.0.0"
4708 +
4709 +original@^1.0.0:
4710 + version "1.0.2"
4711 + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
4712 + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
4713 + dependencies:
4714 + url-parse "^1.4.3"
4715 +
4716 +os-browserify@^0.3.0:
4717 + version "0.3.0"
4718 + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
4719 + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
4720 +
4721 +p-finally@^1.0.0:
4722 + version "1.0.0"
4723 + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
4724 + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
4725 +
4726 +p-limit@^2.0.0, p-limit@^2.2.0:
4727 + version "2.3.0"
4728 + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
4729 + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
4730 + dependencies:
4731 + p-try "^2.0.0"
4732 +
4733 +p-limit@^3.0.2:
4734 + version "3.1.0"
4735 + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
4736 + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
4737 + dependencies:
4738 + yocto-queue "^0.1.0"
4739 +
4740 +p-locate@^3.0.0:
4741 + version "3.0.0"
4742 + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
4743 + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
4744 + dependencies:
4745 + p-limit "^2.0.0"
4746 +
4747 +p-locate@^4.1.0:
4748 + version "4.1.0"
4749 + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
4750 + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
4751 + dependencies:
4752 + p-limit "^2.2.0"
4753 +
4754 +p-map@^2.0.0:
4755 + version "2.1.0"
4756 + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
4757 + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
4758 +
4759 +p-map@^4.0.0:
4760 + version "4.0.0"
4761 + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
4762 + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
4763 + dependencies:
4764 + aggregate-error "^3.0.0"
4765 +
4766 +p-retry@^3.0.1:
4767 + version "3.0.1"
4768 + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
4769 + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==
4770 + dependencies:
4771 + retry "^0.12.0"
4772 +
4773 +p-try@^2.0.0:
4774 + version "2.2.0"
4775 + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
4776 + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
4777 +
4778 +pako@~1.0.5:
4779 + version "1.0.11"
4780 + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
4781 + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
4782 +
4783 +parallel-transform@^1.1.0:
4784 + version "1.2.0"
4785 + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
4786 + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==
4787 + dependencies:
4788 + cyclist "^1.0.1"
4789 + inherits "^2.0.3"
4790 + readable-stream "^2.1.5"
4791 +
4792 +param-case@^3.0.4:
4793 + version "3.0.4"
4794 + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
4795 + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
4796 + dependencies:
4797 + dot-case "^3.0.4"
4798 + tslib "^2.0.3"
4799 +
4800 +parchment@^1.1.4:
4801 + version "1.1.4"
4802 + resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
4803 + integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
4804 +
4805 +parent-module@^1.0.0:
4806 + version "1.0.1"
4807 + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
4808 + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
4809 + dependencies:
4810 + callsites "^3.0.0"
4811 +
4812 +parse-asn1@^5.0.0, parse-asn1@^5.1.5:
4813 + version "5.1.6"
4814 + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
4815 + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
4816 + dependencies:
4817 + asn1.js "^5.2.0"
4818 + browserify-aes "^1.0.0"
4819 + evp_bytestokey "^1.0.0"
4820 + pbkdf2 "^3.0.3"
4821 + safe-buffer "^5.1.1"
4822 +
4823 +parse-json@^4.0.0:
4824 + version "4.0.0"
4825 + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
4826 + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
4827 + dependencies:
4828 + error-ex "^1.3.1"
4829 + json-parse-better-errors "^1.0.1"
4830 +
4831 +parse-json@^5.0.0:
4832 + version "5.2.0"
4833 + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
4834 + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
4835 + dependencies:
4836 + "@babel/code-frame" "^7.0.0"
4837 + error-ex "^1.3.1"
4838 + json-parse-even-better-errors "^2.3.0"
4839 + lines-and-columns "^1.1.6"
4840 +
4841 +parse-passwd@^1.0.0:
4842 + version "1.0.0"
4843 + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
4844 + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
4845 +
4846 +parse5@^6.0.1:
4847 + version "6.0.1"
4848 + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
4849 + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
4850 +
4851 +parseurl@~1.3.2, parseurl@~1.3.3:
4852 + version "1.3.3"
4853 + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
4854 + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
4855 +
4856 +pascal-case@^3.1.2:
4857 + version "3.1.2"
4858 + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
4859 + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
4860 + dependencies:
4861 + no-case "^3.0.4"
4862 + tslib "^2.0.3"
4863 +
4864 +pascalcase@^0.1.1:
4865 + version "0.1.1"
4866 + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
4867 + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
4868 +
4869 +path-browserify@0.0.1:
4870 + version "0.0.1"
4871 + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
4872 + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
4873 +
4874 +path-complete-extname@^1.0.0:
4875 + version "1.0.0"
4876 + resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-1.0.0.tgz#f889985dc91000c815515c0bfed06c5acda0752b"
4877 + integrity sha512-CVjiWcMRdGU8ubs08YQVzhutOR5DEfO97ipRIlOGMK5Bek5nQySknBpuxVAVJ36hseTNs+vdIcv57ZrWxH7zvg==
4878 +
4879 +path-dirname@^1.0.0:
4880 + version "1.0.2"
4881 + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
4882 + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=
4883 +
4884 +path-exists@^3.0.0:
4885 + version "3.0.0"
4886 + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
4887 + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
4888 +
4889 +path-exists@^4.0.0:
4890 + version "4.0.0"
4891 + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
4892 + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
4893 +
4894 +path-is-absolute@^1.0.0:
4895 + version "1.0.1"
4896 + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
4897 + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
4898 +
4899 +path-is-inside@^1.0.2:
4900 + version "1.0.2"
4901 + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
4902 + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
4903 +
4904 +path-key@^2.0.0, path-key@^2.0.1:
4905 + version "2.0.1"
4906 + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
4907 + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
4908 +
4909 +path-parse@^1.0.6:
4910 + version "1.0.7"
4911 + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
4912 + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
4913 +
4914 +path-to-regexp@0.1.7:
4915 + version "0.1.7"
4916 + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
4917 + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
4918 +
4919 +path-type@^4.0.0:
4920 + version "4.0.0"
4921 + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
4922 + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
4923 +
4924 +pbkdf2@^3.0.3:
4925 + version "3.1.2"
4926 + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
4927 + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==
4928 + dependencies:
4929 + create-hash "^1.1.2"
4930 + create-hmac "^1.1.4"
4931 + ripemd160 "^2.0.1"
4932 + safe-buffer "^5.0.1"
4933 + sha.js "^2.4.8"
4934 +
4935 +picocolors@^0.2.1:
4936 + version "0.2.1"
4937 + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f"
4938 + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==
4939 +
4940 +picocolors@^1.0.0:
4941 + version "1.0.0"
4942 + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
4943 + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
4944 +
4945 +picomatch@^2.0.4, picomatch@^2.2.1:
4946 + version "2.3.0"
4947 + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
4948 + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
4949 +
4950 +pify@^2.0.0, pify@^2.3.0:
4951 + version "2.3.0"
4952 + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
4953 + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
4954 +
4955 +pify@^4.0.1:
4956 + version "4.0.1"
4957 + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
4958 + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
4959 +
4960 +pinkie-promise@^2.0.0:
4961 + version "2.0.1"
4962 + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
4963 + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
4964 + dependencies:
4965 + pinkie "^2.0.0"
4966 +
4967 +pinkie@^2.0.0:
4968 + version "2.0.4"
4969 + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
4970 + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
4971 +
4972 +pkg-dir@^3.0.0:
4973 + version "3.0.0"
4974 + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
4975 + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
4976 + dependencies:
4977 + find-up "^3.0.0"
4978 +
4979 +pkg-dir@^4.1.0:
4980 + version "4.2.0"
4981 + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
4982 + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
4983 + dependencies:
4984 + find-up "^4.0.0"
4985 +
4986 +pnp-webpack-plugin@^1.7.0:
4987 + version "1.7.0"
4988 + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9"
4989 + integrity sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg==
4990 + dependencies:
4991 + ts-pnp "^1.1.6"
4992 +
4993 +portfinder@^1.0.26:
4994 + version "1.0.28"
4995 + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
4996 + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==
4997 + dependencies:
4998 + async "^2.6.2"
4999 + debug "^3.1.1"

This file is too large to show in full.

frontend/splitfire-desktop/.github/workflows/publish-desktop.yaml new
+45
@@ -0,0 +1,45 @@
1 +name: "Publish desktop"
2 +on:
3 + push:
4 + branches:
5 + - main
6 +
7 +env:
8 + REACT_APP_SPLITFIRE_API_HOST: ${{ secrets.REACT_APP_SPLITFIRE_API_HOST }}
9 + REACT_APP_MUSIK88_BASE_URL: ${{ secrets.REACT_APP_MUSIK88_BASE_URL }}
10 + REACT_APP_ACTION_CABLE_URL: ${{ secrets.REACT_APP_ACTION_CABLE_URL }}
11 +
12 +jobs:
13 + publish-desktop:
14 + permissions:
15 + contents: write
16 + strategy:
17 + fail-fast: false
18 + matrix:
19 + platform: [macos-latest, ubuntu-20.04, windows-latest]
20 +
21 + runs-on: ${{ matrix.platform }}
22 + steps:
23 + - uses: actions/checkout@v3
24 + - name: setup node
25 + uses: actions/setup-node@v3
26 + with:
27 + node-version: 16
28 + - name: Install Rust stable
29 + uses: dtolnay/rust-toolchain@stable
30 + - name: install dependencies (ubuntu only)
31 + if: matrix.platform == 'ubuntu-20.04'
32 + run: |
33 + sudo apt-get update
34 + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev
35 + - name: install frontend dependencies
36 + run: yarn install
37 + - uses: tauri-apps/tauri-action@v0
38 + env:
39 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 + with:
41 + tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
42 + releaseName: "App v__VERSION__"
43 + releaseBody: "See the assets to download this version and install."
44 + releaseDraft: true
45 + prerelease: false
frontend/splitfire-desktop/.github/workflows/publish-web.yaml new
+31
@@ -0,0 +1,31 @@
1 +name: "Publish web"
2 +on:
3 + push:
4 + branches:
5 + - main
6 +
7 +env:
8 + REACT_APP_SPLITFIRE_API_HOST: ${{ secrets.REACT_APP_SPLITFIRE_API_HOST }}
9 + REACT_APP_MUSIK88_BASE_URL: ${{ secrets.REACT_APP_MUSIK88_BASE_URL }}
10 + REACT_APP_ACTION_CABLE_URL: ${{ secrets.REACT_APP_ACTION_CABLE_URL }}
11 +
12 +jobs:
13 + publish-web:
14 + runs-on: ubuntu-latest
15 + steps:
16 + - uses: actions/checkout@v3
17 + - name: setup node
18 + uses: actions/setup-node@v3
19 + with:
20 + node-version: 16
21 + - name: Install frontend dependencies
22 + run: yarn install
23 + - name: Build
24 + run: yarn build
25 + - name: Install SSH key
26 + uses: shimataro/ssh-key-action@v2
27 + with:
28 + key: ${{ secrets.SSH_PRIVATE_KEY }}
29 + known_hosts: ${{ secrets.SSH_KNOWN_HOSTS }}
30 + - name: rsync over SSH
31 + run: rsync -r ./build/ deploy@splitfire.ai:/var/www/beta.splitfire.ai/build/
frontend/splitfire-desktop/.gitignore new
+23
@@ -0,0 +1,23 @@
1 +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 +
3 +# dependencies
4 +/node_modules
5 +/.pnp
6 +.pnp.js
7 +
8 +# testing
9 +/coverage
10 +
11 +# production
12 +/build
13 +
14 +# misc
15 +.DS_Store
16 +.env.local
17 +.env.development.local
18 +.env.test.local
19 +.env.production.local
20 +
21 +npm-debug.log*
22 +yarn-debug.log*
23 +yarn-error.log*
frontend/splitfire-desktop/.nvmrc new
+1
@@ -0,0 +1 @@
1 +v21.2.0
frontend/splitfire-desktop/.vscode/launch.json new
+32
@@ -0,0 +1,32 @@
1 +{
2 + // Use IntelliSense to learn about possible attributes.
3 + // Hover to view descriptions of existing attributes.
4 + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 + "version": "0.2.0",
6 + "configurations": [
7 + {
8 + "type": "lldb",
9 + "request": "launch",
10 + "name": "Tauri Development Debug",
11 + "cargo": {
12 + "args": [
13 + "build",
14 + "--manifest-path=./src-tauri/Cargo.toml",
15 + "--no-default-features"
16 + ]
17 + },
18 + // task for the `beforeDevCommand` if used, must be configured in `.vscode/tasks.json`
19 + "preLaunchTask": "ui:dev"
20 + },
21 + {
22 + "type": "lldb",
23 + "request": "launch",
24 + "name": "Tauri Production Debug",
25 + "cargo": {
26 + "args": ["build", "--release", "--manifest-path=./src-tauri/Cargo.toml"]
27 + },
28 + // task for the `beforeBuildCommand` if used, must be configured in `.vscode/tasks.json`
29 + "preLaunchTask": "ui:build"
30 + }
31 + ]
32 +}
\ No newline at end of file
frontend/splitfire-desktop/.vscode/tasks.json new
+33
@@ -0,0 +1,33 @@
1 +{
2 + // See https://go.microsoft.com/fwlink/?LinkId=733558
3 + // for the documentation about the tasks.json format
4 + "version": "2.0.0",
5 + "tasks": [
6 + {
7 + "label": "nvm:use",
8 + "type": "shell",
9 + "command": "nvm",
10 + "args": ["use"]
11 + },
12 + {
13 + "label": "ui:dev",
14 + "type": "shell",
15 + // `dev` keeps running in the background
16 + // ideally you should also configure a `problemMatcher`
17 + // see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
18 + "isBackground": true,
19 + // change this to your `beforeDevCommand`:
20 + "command": "bun",
21 + "args": ["start"],
22 + "dependsOn": "nvm:use"
23 + },
24 + {
25 + "label": "ui:build",
26 + "type": "shell",
27 + // change this to your `beforeBuildCommand`:
28 + "command": "bun",
29 + "args": ["build"],
30 + "dependsOn": "nvm:use"
31 + }
32 + ]
33 +}
\ No newline at end of file
frontend/splitfire-desktop/README.md new
+23
@@ -0,0 +1,23 @@
1 +# splitfire-desktop
2 +
3 +SplitFire AI is an artificial intelligence app for music enthusiasts. This repo builds desktop version of SplitFire AI.
4 +
5 +## Local development
6 +
7 +Run only the web target:
8 +1. Run `rails s` on the `musik88-web` repo.
9 +2. Set `REACT_APP_SPLITFIRE_API_HOST=localhost:3001` in your `.env` file here. Local https served from port 3001.
10 +3. Run `yarn start` on this repo.
11 +
12 +Run the tauri development:
13 +1. Run `cargo tauri dev`.
14 +2. This will run `yarn start` under the hood as the `beforeDevCommand` command.
15 +3. Check the `tauri.conf.json` in the `src-tauri` for full settings.
16 +
17 +## CI/CD
18 +
19 +The web build workflow is from [this tutorial](https://zellwk.com/blog/github-actions-deploy/).
20 +
21 +## New framework
22 +
23 +[Grid layout](https://github.com/react-grid-layout/react-grid-layout)
\ No newline at end of file
frontend/splitfire-desktop/_template/cover.css
frontend/splitfire-desktop/_template/index.html new
+79
@@ -0,0 +1,79 @@
1 +
2 +<!doctype html>
3 +<html lang="en" class="h-100">
4 + <head>
5 + <meta charset="utf-8">
6 + <meta name="viewport" content="width=device-width, initial-scale=1">
7 + <meta name="description" content="">
8 + <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
9 + <meta name="generator" content="Hugo 0.84.0">
10 + <title>Cover Template · Bootstrap v5.0</title>
11 +
12 + <link rel="canonical" href="https://getbootstrap.com/docs/5.0/examples/cover/">
13 +
14 +
15 +
16 + <!-- Bootstrap core CSS -->
17 +<link href="/docs/5.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
18 +
19 + <!-- Favicons -->
20 +<link rel="apple-touch-icon" href="/docs/5.0/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
21 +<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
22 +<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
23 +<link rel="manifest" href="/docs/5.0/assets/img/favicons/manifest.json">
24 +<link rel="mask-icon" href="/docs/5.0/assets/img/favicons/safari-pinned-tab.svg" color="#7952b3">
25 +<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon.ico">
26 +<meta name="theme-color" content="#7952b3">
27 +
28 +
29 + <style>
30 + .bd-placeholder-img {
31 + font-size: 1.125rem;
32 + text-anchor: middle;
33 + -webkit-user-select: none;
34 + -moz-user-select: none;
35 + user-select: none;
36 + }
37 +
38 + @media (min-width: 768px) {
39 + .bd-placeholder-img-lg {
40 + font-size: 3.5rem;
41 + }
42 + }
43 + </style>
44 +
45 +
46 + <!-- Custom styles for this template -->
47 + <link href="cover.css" rel="stylesheet">
48 + </head>
49 + <body class="d-flex h-100 text-center text-white bg-dark">
50 +
51 +<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
52 + <header class="mb-auto">
53 + <div>
54 + <h3 class="float-md-start mb-0">Cover</h3>
55 + <nav class="nav nav-masthead justify-content-center float-md-end">
56 + <a class="nav-link active" aria-current="page" href="#">Home</a>
57 + <a class="nav-link" href="#">Features</a>
58 + <a class="nav-link" href="#">Contact</a>
59 + </nav>
60 + </div>
61 + </header>
62 +
63 + <main class="px-3">
64 + <h1>Cover your page.</h1>
65 + <p class="lead">Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.</p>
66 + <p class="lead">
67 + <a href="#" class="btn btn-lg btn-secondary fw-bold border-white bg-white">Learn more</a>
68 + </p>
69 + </main>
70 +
71 + <footer class="mt-auto text-white-50">
72 + <p>Cover template for <a href="https://getbootstrap.com/" class="text-white">Bootstrap</a>, by <a href="https://twitter.com/mdo" class="text-white">@mdo</a>.</p>
73 + </footer>
74 +</div>
75 +
76 +
77 +
78 + </body>
79 +</html>
frontend/splitfire-desktop/bun.lockb
Binary files /dev/null and b/frontend/splitfire-desktop/bun.lockb differ
frontend/splitfire-desktop/package.json new
+71
@@ -0,0 +1,71 @@
1 +{
2 + "name": "splitfire",
3 + "license": "MIT",
4 + "version": "0.3.4",
5 + "private": true,
6 + "dependencies": {
7 + "@sentry/react": "^7.12.1",
8 + "@sentry/tracing": "^7.12.1",
9 + "@tauri-apps/api": "latest",
10 + "@testing-library/jest-dom": "^5.16.1",
11 + "@testing-library/react": "^12.1.2",
12 + "@testing-library/user-event": "^13.5.0",
13 + "@types/actioncable": "^5.2.7",
14 + "@types/jest": "^27.4.0",
15 + "@types/node": "^17.0.12",
16 + "@types/react": "^17.0.38",
17 + "@types/react-dom": "^17.0.11",
18 + "actioncable": "^5.2.6",
19 + "axios": "^0.25.0",
20 + "bootstrap": "5.1.3",
21 + "dexie": "^3.2.0",
22 + "dexie-observable": "^3.0.0-beta.11",
23 + "lodash.debounce": "^4.0.8",
24 + "react": "^17.0.2",
25 + "react-bootstrap": "^2.1.1",
26 + "react-dnd": "^16.0.1",
27 + "react-dnd-html5-backend": "^16.0.1",
28 + "react-dom": "^17.0.2",
29 + "react-icons": "^4.3.1",
30 + "react-intl": "^5.24.4",
31 + "react-router-dom": "6",
32 + "react-scripts": "5.0.0",
33 + "react-transition-group": "1.x",
34 + "react-youtube": "^7.14.0",
35 + "tone": "^14.7.77",
36 + "tslog": "^4.9.2",
37 + "typescript": "^4.5.5",
38 + "web-vitals": "^2.1.3"
39 + },
40 + "scripts": {
41 + "start": "PORT=3002 react-scripts start",
42 + "build": "react-scripts build",
43 + "test": "react-scripts test",
44 + "eject": "react-scripts eject"
45 + },
46 + "eslintConfig": {
47 + "extends": [
48 + "react-app",
49 + "react-app/jest"
50 + ]
51 + },
52 + "browserslist": {
53 + "production": [
54 + ">0.2%",
55 + "not dead",
56 + "not op_mini all"
57 + ],
58 + "development": [
59 + "last 1 chrome version",
60 + "last 1 firefox version",
61 + "last 1 safari version"
62 + ]
63 + },
64 + "resolutions": {
65 + "mini-css-extract-plugin": "2.4.5"
66 + },
67 + "devDependencies": {
68 + "@tauri-apps/cli": "^1.5.14",
69 + "@types/lodash.debounce": "^4.0.7"
70 + }
71 +}
frontend/splitfire-desktop/public/favicon.ico
Binary files /dev/null and b/frontend/splitfire-desktop/public/favicon.ico differ
frontend/splitfire-desktop/public/index.html new
+30
@@ -0,0 +1,30 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="utf-8" />
5 + <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6 + <meta name="viewport" content="width=device-width, initial-scale=1" />
7 + <meta name="theme-color" content="#000000" />
8 + <meta
9 + name="description"
10 + content="SplitFire Ai - An audio-social artificial intelligence app."
11 + />
12 + <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13 + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
14 + <title>SplitFire Ai - An audio-social artificial intelligence app.</title>
15 + </head>
16 + <body>
17 + <noscript>You need to enable JavaScript to run this app.</noscript>
18 + <div id="root" className="d-flex text-center text-white bg-dark"></div>
19 + <!--
20 + This HTML file is a template.
21 + If you open it directly in the browser, you will see an empty page.
22 +
23 + You can add webfonts, meta tags, or analytics to this file.
24 + The build step will place the bundled scripts into the <body> tag.
25 +
26 + To begin the development, run `npm start` or `yarn start`.
27 + To create a production bundle, use `npm run build` or `yarn build`.
28 + -->
29 + </body>
30 +</html>
frontend/splitfire-desktop/public/logo192.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:3c29ea5c2e0ef54ca5ab7b80ee6a9f35ba80cbe5cdfc6da0b1804a1762f10d1b
3 +size 24772
frontend/splitfire-desktop/public/manifest.json new
+25
@@ -0,0 +1,25 @@
1 +{
2 + "short_name": "React App",
3 + "name": "Create React App Sample",
4 + "icons": [
5 + {
6 + "src": "favicon.ico",
7 + "sizes": "64x64 32x32 24x24 16x16",
8 + "type": "image/x-icon"
9 + },
10 + {
11 + "src": "logo192.png",
12 + "type": "image/png",
13 + "sizes": "192x192"
14 + },
15 + {
16 + "src": "logo512.png",
17 + "type": "image/png",
18 + "sizes": "512x512"
19 + }
20 + ],
21 + "start_url": ".",
22 + "display": "standalone",
23 + "theme_color": "#000000",
24 + "background_color": "#ffffff"
25 +}
frontend/splitfire-desktop/public/robots.txt new
+3
@@ -0,0 +1,3 @@
1 +# https://www.robotstxt.org/robotstxt.html
2 +User-agent: *
3 +Disallow:
frontend/splitfire-desktop/src-tauri/.gitignore new
+3
@@ -0,0 +1,3 @@
1 +# Generated by Cargo
2 +# will have compiled files and executables
3 +/target/
frontend/splitfire-desktop/src-tauri/Cargo.lock new
+4907
@@ -0,0 +1,4907 @@
1 +# This file is automatically @generated by Cargo.
2 +# It is not intended for manual editing.
3 +version = 3
4 +
5 +[[package]]
6 +name = "addr2line"
7 +version = "0.21.0"
8 +source = "registry+https://github.com/rust-lang/crates.io-index"
9 +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
10 +dependencies = [
11 + "gimli",
12 +]
13 +
14 +[[package]]
15 +name = "adler"
16 +version = "1.0.2"
17 +source = "registry+https://github.com/rust-lang/crates.io-index"
18 +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 +
20 +[[package]]
21 +name = "ahash"
22 +version = "0.7.8"
23 +source = "registry+https://github.com/rust-lang/crates.io-index"
24 +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
25 +dependencies = [
26 + "getrandom 0.2.12",
27 + "once_cell",
28 + "version_check",
29 +]
30 +
31 +[[package]]
32 +name = "aho-corasick"
33 +version = "1.1.2"
34 +source = "registry+https://github.com/rust-lang/crates.io-index"
35 +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
36 +dependencies = [
37 + "memchr",
38 +]
39 +
40 +[[package]]
41 +name = "alloc-no-stdlib"
42 +version = "2.0.4"
43 +source = "registry+https://github.com/rust-lang/crates.io-index"
44 +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
45 +
46 +[[package]]
47 +name = "alloc-stdlib"
48 +version = "0.2.2"
49 +source = "registry+https://github.com/rust-lang/crates.io-index"
50 +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
51 +dependencies = [
52 + "alloc-no-stdlib",
53 +]
54 +
55 +[[package]]
56 +name = "alsa"
57 +version = "0.9.0"
58 +source = "registry+https://github.com/rust-lang/crates.io-index"
59 +checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce"
60 +dependencies = [
61 + "alsa-sys",
62 + "bitflags 2.4.2",
63 + "libc",
64 +]
65 +
66 +[[package]]
67 +name = "alsa-sys"
68 +version = "0.3.1"
69 +source = "registry+https://github.com/rust-lang/crates.io-index"
70 +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
71 +dependencies = [
72 + "libc",
73 + "pkg-config",
74 +]
75 +
76 +[[package]]
77 +name = "android-tzdata"
78 +version = "0.1.1"
79 +source = "registry+https://github.com/rust-lang/crates.io-index"
80 +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
81 +
82 +[[package]]
83 +name = "android_system_properties"
84 +version = "0.1.5"
85 +source = "registry+https://github.com/rust-lang/crates.io-index"
86 +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
87 +dependencies = [
88 + "libc",
89 +]
90 +
91 +[[package]]
92 +name = "anyhow"
93 +version = "1.0.81"
94 +source = "registry+https://github.com/rust-lang/crates.io-index"
95 +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
96 +
97 +[[package]]
98 +name = "app"
99 +version = "0.2.0"
100 +dependencies = [
101 + "anyhow",
102 + "chrono",
103 + "futures",
104 + "home",
105 + "hound",
106 + "log",
107 + "once_cell",
108 + "reqwest",
109 + "ringbuf",
110 + "rodio",
111 + "serde",
112 + "serde_json",
113 + "serde_repr",
114 + "tauri",
115 + "tauri-build",
116 + "tauri-plugin-log",
117 + "url-builder",
118 +]
119 +
120 +[[package]]
121 +name = "arrayvec"
122 +version = "0.7.4"
123 +source = "registry+https://github.com/rust-lang/crates.io-index"
124 +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
125 +
126 +[[package]]
127 +name = "atk"
128 +version = "0.15.1"
129 +source = "registry+https://github.com/rust-lang/crates.io-index"
130 +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd"
131 +dependencies = [
132 + "atk-sys",
133 + "bitflags 1.3.2",
134 + "glib",
135 + "libc",
136 +]
137 +
138 +[[package]]
139 +name = "atk-sys"
140 +version = "0.15.1"
141 +source = "registry+https://github.com/rust-lang/crates.io-index"
142 +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6"
143 +dependencies = [
144 + "glib-sys",
145 + "gobject-sys",
146 + "libc",
147 + "system-deps 6.2.1",
148 +]
149 +
150 +[[package]]
151 +name = "autocfg"
152 +version = "1.1.0"
153 +source = "registry+https://github.com/rust-lang/crates.io-index"
154 +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
155 +
156 +[[package]]
157 +name = "backtrace"
158 +version = "0.3.69"
159 +source = "registry+https://github.com/rust-lang/crates.io-index"
160 +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
161 +dependencies = [
162 + "addr2line",
163 + "cc",
164 + "cfg-if",
165 + "libc",
166 + "miniz_oxide",
167 + "object",
168 + "rustc-demangle",
169 +]
170 +
171 +[[package]]
172 +name = "base64"
173 +version = "0.13.1"
174 +source = "registry+https://github.com/rust-lang/crates.io-index"
175 +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
176 +
177 +[[package]]
178 +name = "base64"
179 +version = "0.21.7"
180 +source = "registry+https://github.com/rust-lang/crates.io-index"
181 +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
182 +
183 +[[package]]
184 +name = "bindgen"
185 +version = "0.69.4"
186 +source = "registry+https://github.com/rust-lang/crates.io-index"
187 +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
188 +dependencies = [
189 + "bitflags 2.4.2",
190 + "cexpr",
191 + "clang-sys",
192 + "itertools",
193 + "lazy_static",
194 + "lazycell",
195 + "proc-macro2",
196 + "quote",
197 + "regex",
198 + "rustc-hash",
199 + "shlex",
200 + "syn 2.0.53",
201 +]
202 +
203 +[[package]]
204 +name = "bitflags"
205 +version = "1.3.2"
206 +source = "registry+https://github.com/rust-lang/crates.io-index"
207 +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
208 +
209 +[[package]]
210 +name = "bitflags"
211 +version = "2.4.2"
212 +source = "registry+https://github.com/rust-lang/crates.io-index"
213 +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
214 +
215 +[[package]]
216 +name = "bitvec"
217 +version = "1.0.1"
218 +source = "registry+https://github.com/rust-lang/crates.io-index"
219 +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
220 +dependencies = [
221 + "funty",
222 + "radium",
223 + "tap",
224 + "wyz",
225 +]
226 +
227 +[[package]]
228 +name = "block"
229 +version = "0.1.6"
230 +source = "registry+https://github.com/rust-lang/crates.io-index"
231 +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
232 +
233 +[[package]]
234 +name = "block-buffer"
235 +version = "0.10.4"
236 +source = "registry+https://github.com/rust-lang/crates.io-index"
237 +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
238 +dependencies = [
239 + "generic-array",
240 +]
241 +
242 +[[package]]
243 +name = "borsh"
244 +version = "1.3.1"
245 +source = "registry+https://github.com/rust-lang/crates.io-index"
246 +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667"
247 +dependencies = [
248 + "borsh-derive",
249 + "cfg_aliases",
250 +]
251 +
252 +[[package]]
253 +name = "borsh-derive"
254 +version = "1.3.1"
255 +source = "registry+https://github.com/rust-lang/crates.io-index"
256 +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd"
257 +dependencies = [
258 + "once_cell",
259 + "proc-macro-crate 3.1.0",
260 + "proc-macro2",
261 + "quote",
262 + "syn 2.0.53",
263 + "syn_derive",
264 +]
265 +
266 +[[package]]
267 +name = "brotli"
268 +version = "3.5.0"
269 +source = "registry+https://github.com/rust-lang/crates.io-index"
270 +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
271 +dependencies = [
272 + "alloc-no-stdlib",
273 + "alloc-stdlib",
274 + "brotli-decompressor",
275 +]
276 +
277 +[[package]]
278 +name = "brotli-decompressor"
279 +version = "2.5.1"
280 +source = "registry+https://github.com/rust-lang/crates.io-index"
281 +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
282 +dependencies = [
283 + "alloc-no-stdlib",
284 + "alloc-stdlib",
285 +]
286 +
287 +[[package]]
288 +name = "bstr"
289 +version = "1.9.1"
290 +source = "registry+https://github.com/rust-lang/crates.io-index"
291 +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
292 +dependencies = [
293 + "memchr",
294 + "serde",
295 +]
296 +
297 +[[package]]
298 +name = "bumpalo"
299 +version = "3.15.4"
300 +source = "registry+https://github.com/rust-lang/crates.io-index"
301 +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
302 +
303 +[[package]]
304 +name = "byte-unit"
305 +version = "5.1.4"
306 +source = "registry+https://github.com/rust-lang/crates.io-index"
307 +checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e"
308 +dependencies = [
309 + "rust_decimal",
310 + "serde",
311 + "utf8-width",
312 +]
313 +
314 +[[package]]
315 +name = "bytecheck"
316 +version = "0.6.12"
317 +source = "registry+https://github.com/rust-lang/crates.io-index"
318 +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
319 +dependencies = [
320 + "bytecheck_derive",
321 + "ptr_meta",
322 + "simdutf8",
323 +]
324 +
325 +[[package]]
326 +name = "bytecheck_derive"
327 +version = "0.6.12"
328 +source = "registry+https://github.com/rust-lang/crates.io-index"
329 +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
330 +dependencies = [
331 + "proc-macro2",
332 + "quote",
333 + "syn 1.0.109",
334 +]
335 +
336 +[[package]]
337 +name = "bytemuck"
338 +version = "1.15.0"
339 +source = "registry+https://github.com/rust-lang/crates.io-index"
340 +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
341 +
342 +[[package]]
343 +name = "byteorder"
344 +version = "1.5.0"
345 +source = "registry+https://github.com/rust-lang/crates.io-index"
346 +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
347 +
348 +[[package]]
349 +name = "bytes"
350 +version = "1.5.0"
351 +source = "registry+https://github.com/rust-lang/crates.io-index"
352 +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
353 +
354 +[[package]]
355 +name = "cairo-rs"
356 +version = "0.15.12"
357 +source = "registry+https://github.com/rust-lang/crates.io-index"
358 +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc"
359 +dependencies = [
360 + "bitflags 1.3.2",
361 + "cairo-sys-rs",
362 + "glib",
363 + "libc",
364 + "thiserror",
365 +]
366 +
367 +[[package]]
368 +name = "cairo-sys-rs"
369 +version = "0.15.1"
370 +source = "registry+https://github.com/rust-lang/crates.io-index"
371 +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8"
372 +dependencies = [
373 + "glib-sys",
374 + "libc",
375 + "system-deps 6.2.1",
376 +]
377 +
378 +[[package]]
379 +name = "cargo_toml"
380 +version = "0.15.3"
381 +source = "registry+https://github.com/rust-lang/crates.io-index"
382 +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838"
383 +dependencies = [
384 + "serde",
385 + "toml 0.7.8",
386 +]
387 +
388 +[[package]]
389 +name = "cc"
390 +version = "1.0.90"
391 +source = "registry+https://github.com/rust-lang/crates.io-index"
392 +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
393 +dependencies = [
394 + "jobserver",
395 + "libc",
396 +]
397 +
398 +[[package]]
399 +name = "cesu8"
400 +version = "1.1.0"
401 +source = "registry+https://github.com/rust-lang/crates.io-index"
402 +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
403 +
404 +[[package]]
405 +name = "cexpr"
406 +version = "0.6.0"
407 +source = "registry+https://github.com/rust-lang/crates.io-index"
408 +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
409 +dependencies = [
410 + "nom",
411 +]
412 +
413 +[[package]]
414 +name = "cfb"
415 +version = "0.7.3"
416 +source = "registry+https://github.com/rust-lang/crates.io-index"
417 +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
418 +dependencies = [
419 + "byteorder",
420 + "fnv",
421 + "uuid",
422 +]
423 +
424 +[[package]]
425 +name = "cfg-expr"
426 +version = "0.9.1"
427 +source = "registry+https://github.com/rust-lang/crates.io-index"
428 +checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7"
429 +dependencies = [
430 + "smallvec",
431 +]
432 +
433 +[[package]]
434 +name = "cfg-expr"
435 +version = "0.15.7"
436 +source = "registry+https://github.com/rust-lang/crates.io-index"
437 +checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d"
438 +dependencies = [
439 + "smallvec",
440 + "target-lexicon",
441 +]
442 +
443 +[[package]]
444 +name = "cfg-if"
445 +version = "1.0.0"
446 +source = "registry+https://github.com/rust-lang/crates.io-index"
447 +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
448 +
449 +[[package]]
450 +name = "cfg_aliases"
451 +version = "0.1.1"
452 +source = "registry+https://github.com/rust-lang/crates.io-index"
453 +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
454 +
455 +[[package]]
456 +name = "chrono"
457 +version = "0.4.35"
458 +source = "registry+https://github.com/rust-lang/crates.io-index"
459 +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
460 +dependencies = [
461 + "android-tzdata",
462 + "iana-time-zone",
463 + "js-sys",
464 + "num-traits",
465 + "serde",
466 + "wasm-bindgen",
467 + "windows-targets 0.52.4",
468 +]
469 +
470 +[[package]]
471 +name = "clang-sys"
472 +version = "1.7.0"
473 +source = "registry+https://github.com/rust-lang/crates.io-index"
474 +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
475 +dependencies = [
476 + "glob",
477 + "libc",
478 + "libloading",
479 +]
480 +
481 +[[package]]
482 +name = "claxon"
483 +version = "0.4.3"
484 +source = "registry+https://github.com/rust-lang/crates.io-index"
485 +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688"
486 +
487 +[[package]]
488 +name = "cocoa"
489 +version = "0.24.1"
490 +source = "registry+https://github.com/rust-lang/crates.io-index"
491 +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
492 +dependencies = [
493 + "bitflags 1.3.2",
494 + "block",
495 + "cocoa-foundation",
496 + "core-foundation",
497 + "core-graphics",
498 + "foreign-types",
499 + "libc",
500 + "objc",
501 +]
502 +
503 +[[package]]
504 +name = "cocoa-foundation"
505 +version = "0.1.2"
506 +source = "registry+https://github.com/rust-lang/crates.io-index"
507 +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
508 +dependencies = [
509 + "bitflags 1.3.2",
510 + "block",
511 + "core-foundation",
512 + "core-graphics-types",
513 + "libc",
514 + "objc",
515 +]
516 +
517 +[[package]]
518 +name = "color_quant"
519 +version = "1.1.0"
520 +source = "registry+https://github.com/rust-lang/crates.io-index"
521 +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
522 +
523 +[[package]]
524 +name = "colored"
525 +version = "1.9.4"
526 +source = "registry+https://github.com/rust-lang/crates.io-index"
527 +checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355"
528 +dependencies = [
529 + "is-terminal",
530 + "lazy_static",
531 + "winapi",
532 +]
533 +
534 +[[package]]
535 +name = "combine"
536 +version = "4.6.6"
537 +source = "registry+https://github.com/rust-lang/crates.io-index"
538 +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
539 +dependencies = [
540 + "bytes",
541 + "memchr",
542 +]
543 +
544 +[[package]]
545 +name = "convert_case"
546 +version = "0.4.0"
547 +source = "registry+https://github.com/rust-lang/crates.io-index"
548 +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
549 +
550 +[[package]]
551 +name = "core-foundation"
552 +version = "0.9.4"
553 +source = "registry+https://github.com/rust-lang/crates.io-index"
554 +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
555 +dependencies = [
556 + "core-foundation-sys",
557 + "libc",
558 +]
559 +
560 +[[package]]
561 +name = "core-foundation-sys"
562 +version = "0.8.6"
563 +source = "registry+https://github.com/rust-lang/crates.io-index"
564 +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
565 +
566 +[[package]]
567 +name = "core-graphics"
568 +version = "0.22.3"
569 +source = "registry+https://github.com/rust-lang/crates.io-index"
570 +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
571 +dependencies = [
572 + "bitflags 1.3.2",
573 + "core-foundation",
574 + "core-graphics-types",
575 + "foreign-types",
576 + "libc",
577 +]
578 +
579 +[[package]]
580 +name = "core-graphics-types"
581 +version = "0.1.3"
582 +source = "registry+https://github.com/rust-lang/crates.io-index"
583 +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
584 +dependencies = [
585 + "bitflags 1.3.2",
586 + "core-foundation",
587 + "libc",
588 +]
589 +
590 +[[package]]
591 +name = "coreaudio-rs"
592 +version = "0.11.3"
593 +source = "registry+https://github.com/rust-lang/crates.io-index"
594 +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
595 +dependencies = [
596 + "bitflags 1.3.2",
597 + "core-foundation-sys",
598 + "coreaudio-sys",
599 +]
600 +
601 +[[package]]
602 +name = "coreaudio-sys"
603 +version = "0.2.15"
604 +source = "registry+https://github.com/rust-lang/crates.io-index"
605 +checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9"
606 +dependencies = [
607 + "bindgen",
608 +]
609 +
610 +[[package]]
611 +name = "cpal"
612 +version = "0.15.3"
613 +source = "registry+https://github.com/rust-lang/crates.io-index"
614 +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
615 +dependencies = [
616 + "alsa",
617 + "core-foundation-sys",
618 + "coreaudio-rs",
619 + "dasp_sample",
620 + "jni 0.21.1",
621 + "js-sys",
622 + "libc",
623 + "mach2",
624 + "ndk 0.8.0",
625 + "ndk-context",
626 + "oboe",
627 + "wasm-bindgen",
628 + "wasm-bindgen-futures",
629 + "web-sys",
630 + "windows 0.54.0",
631 +]
632 +
633 +[[package]]
634 +name = "cpufeatures"
635 +version = "0.2.12"
636 +source = "registry+https://github.com/rust-lang/crates.io-index"
637 +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
638 +dependencies = [
639 + "libc",
640 +]
641 +
642 +[[package]]
643 +name = "crc32fast"
644 +version = "1.4.0"
645 +source = "registry+https://github.com/rust-lang/crates.io-index"
646 +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
647 +dependencies = [
648 + "cfg-if",
649 +]
650 +
651 +[[package]]
652 +name = "crossbeam-channel"
653 +version = "0.5.12"
654 +source = "registry+https://github.com/rust-lang/crates.io-index"
655 +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
656 +dependencies = [
657 + "crossbeam-utils",
658 +]
659 +
660 +[[package]]
661 +name = "crossbeam-deque"
662 +version = "0.8.5"
663 +source = "registry+https://github.com/rust-lang/crates.io-index"
664 +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
665 +dependencies = [
666 + "crossbeam-epoch",
667 + "crossbeam-utils",
668 +]
669 +
670 +[[package]]
671 +name = "crossbeam-epoch"
672 +version = "0.9.18"
673 +source = "registry+https://github.com/rust-lang/crates.io-index"
674 +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
675 +dependencies = [
676 + "crossbeam-utils",
677 +]
678 +
679 +[[package]]
680 +name = "crossbeam-utils"
681 +version = "0.8.19"
682 +source = "registry+https://github.com/rust-lang/crates.io-index"
683 +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
684 +
685 +[[package]]
686 +name = "crypto-common"
687 +version = "0.1.6"
688 +source = "registry+https://github.com/rust-lang/crates.io-index"
689 +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
690 +dependencies = [
691 + "generic-array",
692 + "typenum",
693 +]
694 +
695 +[[package]]
696 +name = "cssparser"
697 +version = "0.27.2"
698 +source = "registry+https://github.com/rust-lang/crates.io-index"
699 +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
700 +dependencies = [
701 + "cssparser-macros",
702 + "dtoa-short",
703 + "itoa 0.4.8",
704 + "matches",
705 + "phf 0.8.0",
706 + "proc-macro2",
707 + "quote",
708 + "smallvec",
709 + "syn 1.0.109",
710 +]
711 +
712 +[[package]]
713 +name = "cssparser-macros"
714 +version = "0.6.1"
715 +source = "registry+https://github.com/rust-lang/crates.io-index"
716 +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
717 +dependencies = [
718 + "quote",
719 + "syn 2.0.53",
720 +]
721 +
722 +[[package]]
723 +name = "ctor"
724 +version = "0.2.7"
725 +source = "registry+https://github.com/rust-lang/crates.io-index"
726 +checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
727 +dependencies = [
728 + "quote",
729 + "syn 2.0.53",
730 +]
731 +
732 +[[package]]
733 +name = "darling"
734 +version = "0.20.8"
735 +source = "registry+https://github.com/rust-lang/crates.io-index"
736 +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
737 +dependencies = [
738 + "darling_core",
739 + "darling_macro",
740 +]
741 +
742 +[[package]]
743 +name = "darling_core"
744 +version = "0.20.8"
745 +source = "registry+https://github.com/rust-lang/crates.io-index"
746 +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
747 +dependencies = [
748 + "fnv",
749 + "ident_case",
750 + "proc-macro2",
751 + "quote",
752 + "strsim",
753 + "syn 2.0.53",
754 +]
755 +
756 +[[package]]
757 +name = "darling_macro"
758 +version = "0.20.8"
759 +source = "registry+https://github.com/rust-lang/crates.io-index"
760 +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
761 +dependencies = [
762 + "darling_core",
763 + "quote",
764 + "syn 2.0.53",
765 +]
766 +
767 +[[package]]
768 +name = "dasp_sample"
769 +version = "0.11.0"
770 +source = "registry+https://github.com/rust-lang/crates.io-index"
771 +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
772 +
773 +[[package]]
774 +name = "deranged"
775 +version = "0.3.11"
776 +source = "registry+https://github.com/rust-lang/crates.io-index"
777 +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
778 +dependencies = [
779 + "powerfmt",
780 + "serde",
781 +]
782 +
783 +[[package]]
784 +name = "derive_more"
785 +version = "0.99.17"
786 +source = "registry+https://github.com/rust-lang/crates.io-index"
787 +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
788 +dependencies = [
789 + "convert_case",
790 + "proc-macro2",
791 + "quote",
792 + "rustc_version",
793 + "syn 1.0.109",
794 +]
795 +
796 +[[package]]
797 +name = "digest"
798 +version = "0.10.7"
799 +source = "registry+https://github.com/rust-lang/crates.io-index"
800 +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
801 +dependencies = [
802 + "block-buffer",
803 + "crypto-common",
804 +]
805 +
806 +[[package]]
807 +name = "dirs-next"
808 +version = "2.0.0"
809 +source = "registry+https://github.com/rust-lang/crates.io-index"
810 +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
811 +dependencies = [
812 + "cfg-if",
813 + "dirs-sys-next",
814 +]
815 +
816 +[[package]]
817 +name = "dirs-sys-next"
818 +version = "0.1.2"
819 +source = "registry+https://github.com/rust-lang/crates.io-index"
820 +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
821 +dependencies = [
822 + "libc",
823 + "redox_users",
824 + "winapi",
825 +]
826 +
827 +[[package]]
828 +name = "dispatch"
829 +version = "0.2.0"
830 +source = "registry+https://github.com/rust-lang/crates.io-index"
831 +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
832 +
833 +[[package]]
834 +name = "dtoa"
835 +version = "1.0.9"
836 +source = "registry+https://github.com/rust-lang/crates.io-index"
837 +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
838 +
839 +[[package]]
840 +name = "dtoa-short"
841 +version = "0.3.4"
842 +source = "registry+https://github.com/rust-lang/crates.io-index"
843 +checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74"
844 +dependencies = [
845 + "dtoa",
846 +]
847 +
848 +[[package]]
849 +name = "dunce"
850 +version = "1.0.4"
851 +source = "registry+https://github.com/rust-lang/crates.io-index"
852 +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
853 +
854 +[[package]]
855 +name = "either"
856 +version = "1.10.0"
857 +source = "registry+https://github.com/rust-lang/crates.io-index"
858 +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
859 +
860 +[[package]]
861 +name = "embed-resource"
862 +version = "2.4.2"
863 +source = "registry+https://github.com/rust-lang/crates.io-index"
864 +checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d"
865 +dependencies = [
866 + "cc",
867 + "memchr",
868 + "rustc_version",
869 + "toml 0.8.11",
870 + "vswhom",
871 + "winreg 0.52.0",
872 +]
873 +
874 +[[package]]
875 +name = "embed_plist"
876 +version = "1.2.2"
877 +source = "registry+https://github.com/rust-lang/crates.io-index"
878 +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
879 +
880 +[[package]]
881 +name = "encoding_rs"
882 +version = "0.8.33"
883 +source = "registry+https://github.com/rust-lang/crates.io-index"
884 +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
885 +dependencies = [
886 + "cfg-if",
887 +]
888 +
889 +[[package]]
890 +name = "equivalent"
891 +version = "1.0.1"
892 +source = "registry+https://github.com/rust-lang/crates.io-index"
893 +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
894 +
895 +[[package]]
896 +name = "errno"
897 +version = "0.3.8"
898 +source = "registry+https://github.com/rust-lang/crates.io-index"
899 +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
900 +dependencies = [
901 + "libc",
902 + "windows-sys 0.52.0",
903 +]
904 +
905 +[[package]]
906 +name = "fastrand"
907 +version = "2.0.1"
908 +source = "registry+https://github.com/rust-lang/crates.io-index"
909 +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
910 +
911 +[[package]]
912 +name = "fdeflate"
913 +version = "0.3.4"
914 +source = "registry+https://github.com/rust-lang/crates.io-index"
915 +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
916 +dependencies = [
917 + "simd-adler32",
918 +]
919 +
920 +[[package]]
921 +name = "fern"
922 +version = "0.6.2"
923 +source = "registry+https://github.com/rust-lang/crates.io-index"
924 +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
925 +dependencies = [
926 + "colored",
927 + "log",
928 +]
929 +
930 +[[package]]
931 +name = "field-offset"
932 +version = "0.3.6"
933 +source = "registry+https://github.com/rust-lang/crates.io-index"
934 +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
935 +dependencies = [
936 + "memoffset",
937 + "rustc_version",
938 +]
939 +
940 +[[package]]
941 +name = "filetime"
942 +version = "0.2.23"
943 +source = "registry+https://github.com/rust-lang/crates.io-index"
944 +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
945 +dependencies = [
946 + "cfg-if",
947 + "libc",
948 + "redox_syscall",
949 + "windows-sys 0.52.0",
950 +]
951 +
952 +[[package]]
953 +name = "flate2"
954 +version = "1.0.28"
955 +source = "registry+https://github.com/rust-lang/crates.io-index"
956 +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
957 +dependencies = [
958 + "crc32fast",
959 + "miniz_oxide",
960 +]
961 +
962 +[[package]]
963 +name = "fnv"
964 +version = "1.0.7"
965 +source = "registry+https://github.com/rust-lang/crates.io-index"
966 +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
967 +
968 +[[package]]
969 +name = "foreign-types"
970 +version = "0.3.2"
971 +source = "registry+https://github.com/rust-lang/crates.io-index"
972 +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
973 +dependencies = [
974 + "foreign-types-shared",
975 +]
976 +
977 +[[package]]
978 +name = "foreign-types-shared"
979 +version = "0.1.1"
980 +source = "registry+https://github.com/rust-lang/crates.io-index"
981 +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
982 +
983 +[[package]]
984 +name = "form_urlencoded"
985 +version = "1.2.1"
986 +source = "registry+https://github.com/rust-lang/crates.io-index"
987 +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
988 +dependencies = [
989 + "percent-encoding",
990 +]
991 +
992 +[[package]]
993 +name = "funty"
994 +version = "2.0.0"
995 +source = "registry+https://github.com/rust-lang/crates.io-index"
996 +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
997 +
998 +[[package]]
999 +name = "futf"
1000 +version = "0.1.5"
1001 +source = "registry+https://github.com/rust-lang/crates.io-index"
1002 +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
1003 +dependencies = [
1004 + "mac",
1005 + "new_debug_unreachable",
1006 +]
1007 +
1008 +[[package]]
1009 +name = "futures"
1010 +version = "0.3.30"
1011 +source = "registry+https://github.com/rust-lang/crates.io-index"
1012 +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
1013 +dependencies = [
1014 + "futures-channel",
1015 + "futures-core",
1016 + "futures-executor",
1017 + "futures-io",
1018 + "futures-sink",
1019 + "futures-task",
1020 + "futures-util",
1021 +]
1022 +
1023 +[[package]]
1024 +name = "futures-channel"
1025 +version = "0.3.30"
1026 +source = "registry+https://github.com/rust-lang/crates.io-index"
1027 +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
1028 +dependencies = [
1029 + "futures-core",
1030 + "futures-sink",
1031 +]
1032 +
1033 +[[package]]
1034 +name = "futures-core"
1035 +version = "0.3.30"
1036 +source = "registry+https://github.com/rust-lang/crates.io-index"
1037 +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
1038 +
1039 +[[package]]
1040 +name = "futures-executor"
1041 +version = "0.3.30"
1042 +source = "registry+https://github.com/rust-lang/crates.io-index"
1043 +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
1044 +dependencies = [
1045 + "futures-core",
1046 + "futures-task",
1047 + "futures-util",
1048 +]
1049 +
1050 +[[package]]
1051 +name = "futures-io"
1052 +version = "0.3.30"
1053 +source = "registry+https://github.com/rust-lang/crates.io-index"
1054 +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
1055 +
1056 +[[package]]
1057 +name = "futures-macro"
1058 +version = "0.3.30"
1059 +source = "registry+https://github.com/rust-lang/crates.io-index"
1060 +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
1061 +dependencies = [
1062 + "proc-macro2",
1063 + "quote",
1064 + "syn 2.0.53",
1065 +]
1066 +
1067 +[[package]]
1068 +name = "futures-sink"
1069 +version = "0.3.30"
1070 +source = "registry+https://github.com/rust-lang/crates.io-index"
1071 +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
1072 +
1073 +[[package]]
1074 +name = "futures-task"
1075 +version = "0.3.30"
1076 +source = "registry+https://github.com/rust-lang/crates.io-index"
1077 +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
1078 +
1079 +[[package]]
1080 +name = "futures-util"
1081 +version = "0.3.30"
1082 +source = "registry+https://github.com/rust-lang/crates.io-index"
1083 +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
1084 +dependencies = [
1085 + "futures-channel",
1086 + "futures-core",
1087 + "futures-io",
1088 + "futures-macro",
1089 + "futures-sink",
1090 + "futures-task",
1091 + "memchr",
1092 + "pin-project-lite",
1093 + "pin-utils",
1094 + "slab",
1095 +]
1096 +
1097 +[[package]]
1098 +name = "fxhash"
1099 +version = "0.2.1"
1100 +source = "registry+https://github.com/rust-lang/crates.io-index"
1101 +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
1102 +dependencies = [
1103 + "byteorder",
1104 +]
1105 +
1106 +[[package]]
1107 +name = "gdk"
1108 +version = "0.15.4"
1109 +source = "registry+https://github.com/rust-lang/crates.io-index"
1110 +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8"
1111 +dependencies = [
1112 + "bitflags 1.3.2",
1113 + "cairo-rs",
1114 + "gdk-pixbuf",
1115 + "gdk-sys",
1116 + "gio",
1117 + "glib",
1118 + "libc",
1119 + "pango",
1120 +]
1121 +
1122 +[[package]]
1123 +name = "gdk-pixbuf"
1124 +version = "0.15.11"
1125 +source = "registry+https://github.com/rust-lang/crates.io-index"
1126 +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a"
1127 +dependencies = [
1128 + "bitflags 1.3.2",
1129 + "gdk-pixbuf-sys",
1130 + "gio",
1131 + "glib",
1132 + "libc",
1133 +]
1134 +
1135 +[[package]]
1136 +name = "gdk-pixbuf-sys"
1137 +version = "0.15.10"
1138 +source = "registry+https://github.com/rust-lang/crates.io-index"
1139 +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7"
1140 +dependencies = [
1141 + "gio-sys",
1142 + "glib-sys",
1143 + "gobject-sys",
1144 + "libc",
1145 + "system-deps 6.2.1",
1146 +]
1147 +
1148 +[[package]]
1149 +name = "gdk-sys"
1150 +version = "0.15.1"
1151 +source = "registry+https://github.com/rust-lang/crates.io-index"
1152 +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88"
1153 +dependencies = [
1154 + "cairo-sys-rs",
1155 + "gdk-pixbuf-sys",
1156 + "gio-sys",
1157 + "glib-sys",
1158 + "gobject-sys",
1159 + "libc",
1160 + "pango-sys",
1161 + "pkg-config",
1162 + "system-deps 6.2.1",
1163 +]
1164 +
1165 +[[package]]
1166 +name = "gdkwayland-sys"
1167 +version = "0.15.3"
1168 +source = "registry+https://github.com/rust-lang/crates.io-index"
1169 +checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2"
1170 +dependencies = [
1171 + "gdk-sys",
1172 + "glib-sys",
1173 + "gobject-sys",
1174 + "libc",
1175 + "pkg-config",
1176 + "system-deps 6.2.1",
1177 +]
1178 +
1179 +[[package]]
1180 +name = "gdkx11-sys"
1181 +version = "0.15.1"
1182 +source = "registry+https://github.com/rust-lang/crates.io-index"
1183 +checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178"
1184 +dependencies = [
1185 + "gdk-sys",
1186 + "glib-sys",
1187 + "libc",
1188 + "system-deps 6.2.1",
1189 + "x11",
1190 +]
1191 +
1192 +[[package]]
1193 +name = "generator"
1194 +version = "0.7.5"
1195 +source = "registry+https://github.com/rust-lang/crates.io-index"
1196 +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
1197 +dependencies = [
1198 + "cc",
1199 + "libc",
1200 + "log",
1201 + "rustversion",
1202 + "windows 0.48.0",
1203 +]
1204 +
1205 +[[package]]
1206 +name = "generic-array"
1207 +version = "0.14.7"
1208 +source = "registry+https://github.com/rust-lang/crates.io-index"
1209 +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
1210 +dependencies = [
1211 + "typenum",
1212 + "version_check",
1213 +]
1214 +
1215 +[[package]]
1216 +name = "getrandom"
1217 +version = "0.1.16"
1218 +source = "registry+https://github.com/rust-lang/crates.io-index"
1219 +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
1220 +dependencies = [
1221 + "cfg-if",
1222 + "libc",
1223 + "wasi 0.9.0+wasi-snapshot-preview1",
1224 +]
1225 +
1226 +[[package]]
1227 +name = "getrandom"
1228 +version = "0.2.12"
1229 +source = "registry+https://github.com/rust-lang/crates.io-index"
1230 +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
1231 +dependencies = [
1232 + "cfg-if",
1233 + "libc",
1234 + "wasi 0.11.0+wasi-snapshot-preview1",
1235 +]
1236 +
1237 +[[package]]
1238 +name = "gimli"
1239 +version = "0.28.1"
1240 +source = "registry+https://github.com/rust-lang/crates.io-index"
1241 +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
1242 +
1243 +[[package]]
1244 +name = "gio"
1245 +version = "0.15.12"
1246 +source = "registry+https://github.com/rust-lang/crates.io-index"
1247 +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b"
1248 +dependencies = [
1249 + "bitflags 1.3.2",
1250 + "futures-channel",
1251 + "futures-core",
1252 + "futures-io",
1253 + "gio-sys",
1254 + "glib",
1255 + "libc",
1256 + "once_cell",
1257 + "thiserror",
1258 +]
1259 +
1260 +[[package]]
1261 +name = "gio-sys"
1262 +version = "0.15.10"
1263 +source = "registry+https://github.com/rust-lang/crates.io-index"
1264 +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d"
1265 +dependencies = [
1266 + "glib-sys",
1267 + "gobject-sys",
1268 + "libc",
1269 + "system-deps 6.2.1",
1270 + "winapi",
1271 +]
1272 +
1273 +[[package]]
1274 +name = "glib"
1275 +version = "0.15.12"
1276 +source = "registry+https://github.com/rust-lang/crates.io-index"
1277 +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d"
1278 +dependencies = [
1279 + "bitflags 1.3.2",
1280 + "futures-channel",
1281 + "futures-core",
1282 + "futures-executor",
1283 + "futures-task",
1284 + "glib-macros",
1285 + "glib-sys",
1286 + "gobject-sys",
1287 + "libc",
1288 + "once_cell",
1289 + "smallvec",
1290 + "thiserror",
1291 +]
1292 +
1293 +[[package]]
1294 +name = "glib-macros"
1295 +version = "0.15.13"
1296 +source = "registry+https://github.com/rust-lang/crates.io-index"
1297 +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a"
1298 +dependencies = [
1299 + "anyhow",
1300 + "heck 0.4.1",
1301 + "proc-macro-crate 1.3.1",
1302 + "proc-macro-error",
1303 + "proc-macro2",
1304 + "quote",
1305 + "syn 1.0.109",
1306 +]
1307 +
1308 +[[package]]
1309 +name = "glib-sys"
1310 +version = "0.15.10"
1311 +source = "registry+https://github.com/rust-lang/crates.io-index"
1312 +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4"
1313 +dependencies = [
1314 + "libc",
1315 + "system-deps 6.2.1",
1316 +]
1317 +
1318 +[[package]]
1319 +name = "glob"
1320 +version = "0.3.1"
1321 +source = "registry+https://github.com/rust-lang/crates.io-index"
1322 +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
1323 +
1324 +[[package]]
1325 +name = "globset"
1326 +version = "0.4.14"
1327 +source = "registry+https://github.com/rust-lang/crates.io-index"
1328 +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1329 +dependencies = [
1330 + "aho-corasick",
1331 + "bstr",
1332 + "log",
1333 + "regex-automata 0.4.6",
1334 + "regex-syntax 0.8.2",
1335 +]
1336 +
1337 +[[package]]
1338 +name = "gobject-sys"
1339 +version = "0.15.10"
1340 +source = "registry+https://github.com/rust-lang/crates.io-index"
1341 +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a"
1342 +dependencies = [
1343 + "glib-sys",
1344 + "libc",
1345 + "system-deps 6.2.1",
1346 +]
1347 +
1348 +[[package]]
1349 +name = "gtk"
1350 +version = "0.15.5"
1351 +source = "registry+https://github.com/rust-lang/crates.io-index"
1352 +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0"
1353 +dependencies = [
1354 + "atk",
1355 + "bitflags 1.3.2",
1356 + "cairo-rs",
1357 + "field-offset",
1358 + "futures-channel",
1359 + "gdk",
1360 + "gdk-pixbuf",
1361 + "gio",
1362 + "glib",
1363 + "gtk-sys",
1364 + "gtk3-macros",
1365 + "libc",
1366 + "once_cell",
1367 + "pango",
1368 + "pkg-config",
1369 +]
1370 +
1371 +[[package]]
1372 +name = "gtk-sys"
1373 +version = "0.15.3"
1374 +source = "registry+https://github.com/rust-lang/crates.io-index"
1375 +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84"
1376 +dependencies = [
1377 + "atk-sys",
1378 + "cairo-sys-rs",
1379 + "gdk-pixbuf-sys",
1380 + "gdk-sys",
1381 + "gio-sys",
1382 + "glib-sys",
1383 + "gobject-sys",
1384 + "libc",
1385 + "pango-sys",
1386 + "system-deps 6.2.1",
1387 +]
1388 +
1389 +[[package]]
1390 +name = "gtk3-macros"
1391 +version = "0.15.6"
1392 +source = "registry+https://github.com/rust-lang/crates.io-index"
1393 +checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d"
1394 +dependencies = [
1395 + "anyhow",
1396 + "proc-macro-crate 1.3.1",
1397 + "proc-macro-error",
1398 + "proc-macro2",
1399 + "quote",
1400 + "syn 1.0.109",
1401 +]
1402 +
1403 +[[package]]
1404 +name = "h2"
1405 +version = "0.3.25"
1406 +source = "registry+https://github.com/rust-lang/crates.io-index"
1407 +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
1408 +dependencies = [
1409 + "bytes",
1410 + "fnv",
1411 + "futures-core",
1412 + "futures-sink",
1413 + "futures-util",
1414 + "http",
1415 + "indexmap 2.2.5",
1416 + "slab",
1417 + "tokio",
1418 + "tokio-util",
1419 + "tracing",
1420 +]
1421 +
1422 +[[package]]
1423 +name = "hashbrown"
1424 +version = "0.12.3"
1425 +source = "registry+https://github.com/rust-lang/crates.io-index"
1426 +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1427 +dependencies = [
1428 + "ahash",
1429 +]
1430 +
1431 +[[package]]
1432 +name = "hashbrown"
1433 +version = "0.14.3"
1434 +source = "registry+https://github.com/rust-lang/crates.io-index"
1435 +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1436 +
1437 +[[package]]
1438 +name = "heck"
1439 +version = "0.3.3"
1440 +source = "registry+https://github.com/rust-lang/crates.io-index"
1441 +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
1442 +dependencies = [
1443 + "unicode-segmentation",
1444 +]
1445 +
1446 +[[package]]
1447 +name = "heck"
1448 +version = "0.4.1"
1449 +source = "registry+https://github.com/rust-lang/crates.io-index"
1450 +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
1451 +
1452 +[[package]]
1453 +name = "heck"
1454 +version = "0.5.0"
1455 +source = "registry+https://github.com/rust-lang/crates.io-index"
1456 +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
1457 +
1458 +[[package]]
1459 +name = "hermit-abi"
1460 +version = "0.3.9"
1461 +source = "registry+https://github.com/rust-lang/crates.io-index"
1462 +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
1463 +
1464 +[[package]]
1465 +name = "hex"
1466 +version = "0.4.3"
1467 +source = "registry+https://github.com/rust-lang/crates.io-index"
1468 +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1469 +
1470 +[[package]]
1471 +name = "home"
1472 +version = "0.5.9"
1473 +source = "registry+https://github.com/rust-lang/crates.io-index"
1474 +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
1475 +dependencies = [
1476 + "windows-sys 0.52.0",
1477 +]
1478 +
1479 +[[package]]
1480 +name = "hound"
1481 +version = "3.5.1"
1482 +source = "registry+https://github.com/rust-lang/crates.io-index"
1483 +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
1484 +
1485 +[[package]]
1486 +name = "html5ever"
1487 +version = "0.26.0"
1488 +source = "registry+https://github.com/rust-lang/crates.io-index"
1489 +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
1490 +dependencies = [
1491 + "log",
1492 + "mac",
1493 + "markup5ever",
1494 + "proc-macro2",
1495 + "quote",
1496 + "syn 1.0.109",
1497 +]
1498 +
1499 +[[package]]
1500 +name = "http"
1501 +version = "0.2.12"
1502 +source = "registry+https://github.com/rust-lang/crates.io-index"
1503 +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
1504 +dependencies = [
1505 + "bytes",
1506 + "fnv",
1507 + "itoa 1.0.10",
1508 +]
1509 +
1510 +[[package]]
1511 +name = "http-body"
1512 +version = "0.4.6"
1513 +source = "registry+https://github.com/rust-lang/crates.io-index"
1514 +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
1515 +dependencies = [
1516 + "bytes",
1517 + "http",
1518 + "pin-project-lite",
1519 +]
1520 +
1521 +[[package]]
1522 +name = "http-range"
1523 +version = "0.1.5"
1524 +source = "registry+https://github.com/rust-lang/crates.io-index"
1525 +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
1526 +
1527 +[[package]]
1528 +name = "httparse"
1529 +version = "1.8.0"
1530 +source = "registry+https://github.com/rust-lang/crates.io-index"
1531 +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
1532 +
1533 +[[package]]
1534 +name = "httpdate"
1535 +version = "1.0.3"
1536 +source = "registry+https://github.com/rust-lang/crates.io-index"
1537 +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
1538 +
1539 +[[package]]
1540 +name = "hyper"
1541 +version = "0.14.28"
1542 +source = "registry+https://github.com/rust-lang/crates.io-index"
1543 +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
1544 +dependencies = [
1545 + "bytes",
1546 + "futures-channel",
1547 + "futures-core",
1548 + "futures-util",
1549 + "h2",
1550 + "http",
1551 + "http-body",
1552 + "httparse",
1553 + "httpdate",
1554 + "itoa 1.0.10",
1555 + "pin-project-lite",
1556 + "socket2",
1557 + "tokio",
1558 + "tower-service",
1559 + "tracing",
1560 + "want",
1561 +]
1562 +
1563 +[[package]]
1564 +name = "hyper-tls"
1565 +version = "0.5.0"
1566 +source = "registry+https://github.com/rust-lang/crates.io-index"
1567 +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
1568 +dependencies = [
1569 + "bytes",
1570 + "hyper",
1571 + "native-tls",
1572 + "tokio",
1573 + "tokio-native-tls",
1574 +]
1575 +
1576 +[[package]]
1577 +name = "iana-time-zone"
1578 +version = "0.1.60"
1579 +source = "registry+https://github.com/rust-lang/crates.io-index"
1580 +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
1581 +dependencies = [
1582 + "android_system_properties",
1583 + "core-foundation-sys",
1584 + "iana-time-zone-haiku",
1585 + "js-sys",
1586 + "wasm-bindgen",
1587 + "windows-core 0.52.0",
1588 +]
1589 +
1590 +[[package]]
1591 +name = "iana-time-zone-haiku"
1592 +version = "0.1.2"
1593 +source = "registry+https://github.com/rust-lang/crates.io-index"
1594 +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
1595 +dependencies = [
1596 + "cc",
1597 +]
1598 +
1599 +[[package]]
1600 +name = "ico"
1601 +version = "0.3.0"
1602 +source = "registry+https://github.com/rust-lang/crates.io-index"
1603 +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae"
1604 +dependencies = [
1605 + "byteorder",
1606 + "png",
1607 +]
1608 +
1609 +[[package]]
1610 +name = "ident_case"
1611 +version = "1.0.1"
1612 +source = "registry+https://github.com/rust-lang/crates.io-index"
1613 +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1614 +
1615 +[[package]]
1616 +name = "idna"
1617 +version = "0.5.0"
1618 +source = "registry+https://github.com/rust-lang/crates.io-index"
1619 +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1620 +dependencies = [
1621 + "unicode-bidi",
1622 + "unicode-normalization",
1623 +]
1624 +
1625 +[[package]]
1626 +name = "ignore"
1627 +version = "0.4.22"
1628 +source = "registry+https://github.com/rust-lang/crates.io-index"
1629 +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
1630 +dependencies = [
1631 + "crossbeam-deque",
1632 + "globset",
1633 + "log",
1634 + "memchr",
1635 + "regex-automata 0.4.6",
1636 + "same-file",
1637 + "walkdir",
1638 + "winapi-util",
1639 +]
1640 +
1641 +[[package]]
1642 +name = "image"
1643 +version = "0.24.9"
1644 +source = "registry+https://github.com/rust-lang/crates.io-index"
1645 +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
1646 +dependencies = [
1647 + "bytemuck",
1648 + "byteorder",
1649 + "color_quant",
1650 + "num-traits",
1651 +]
1652 +
1653 +[[package]]
1654 +name = "indexmap"
1655 +version = "1.9.3"
1656 +source = "registry+https://github.com/rust-lang/crates.io-index"
1657 +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1658 +dependencies = [
1659 + "autocfg",
1660 + "hashbrown 0.12.3",
1661 + "serde",
1662 +]
1663 +
1664 +[[package]]
1665 +name = "indexmap"
1666 +version = "2.2.5"
1667 +source = "registry+https://github.com/rust-lang/crates.io-index"
1668 +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
1669 +dependencies = [
1670 + "equivalent",
1671 + "hashbrown 0.14.3",
1672 + "serde",
1673 +]
1674 +
1675 +[[package]]
1676 +name = "infer"
1677 +version = "0.13.0"
1678 +source = "registry+https://github.com/rust-lang/crates.io-index"
1679 +checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc"
1680 +dependencies = [
1681 + "cfb",
1682 +]
1683 +
1684 +[[package]]
1685 +name = "instant"
1686 +version = "0.1.12"
1687 +source = "registry+https://github.com/rust-lang/crates.io-index"
1688 +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1689 +dependencies = [
1690 + "cfg-if",
1691 +]
1692 +
1693 +[[package]]
1694 +name = "ipnet"
1695 +version = "2.9.0"
1696 +source = "registry+https://github.com/rust-lang/crates.io-index"
1697 +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
1698 +
1699 +[[package]]
1700 +name = "is-terminal"
1701 +version = "0.4.12"
1702 +source = "registry+https://github.com/rust-lang/crates.io-index"
1703 +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
1704 +dependencies = [
1705 + "hermit-abi",
1706 + "libc",
1707 + "windows-sys 0.52.0",
1708 +]
1709 +
1710 +[[package]]
1711 +name = "itertools"
1712 +version = "0.12.1"
1713 +source = "registry+https://github.com/rust-lang/crates.io-index"
1714 +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
1715 +dependencies = [
1716 + "either",
1717 +]
1718 +
1719 +[[package]]
1720 +name = "itoa"
1721 +version = "0.4.8"
1722 +source = "registry+https://github.com/rust-lang/crates.io-index"
1723 +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
1724 +
1725 +[[package]]
1726 +name = "itoa"
1727 +version = "1.0.10"
1728 +source = "registry+https://github.com/rust-lang/crates.io-index"
1729 +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
1730 +
1731 +[[package]]
1732 +name = "javascriptcore-rs"
1733 +version = "0.16.0"
1734 +source = "registry+https://github.com/rust-lang/crates.io-index"
1735 +checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c"
1736 +dependencies = [
1737 + "bitflags 1.3.2",
1738 + "glib",
1739 + "javascriptcore-rs-sys",
1740 +]
1741 +
1742 +[[package]]
1743 +name = "javascriptcore-rs-sys"
1744 +version = "0.4.0"
1745 +source = "registry+https://github.com/rust-lang/crates.io-index"
1746 +checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c"
1747 +dependencies = [
1748 + "glib-sys",
1749 + "gobject-sys",
1750 + "libc",
1751 + "system-deps 5.0.0",
1752 +]
1753 +
1754 +[[package]]
1755 +name = "jni"
1756 +version = "0.20.0"
1757 +source = "registry+https://github.com/rust-lang/crates.io-index"
1758 +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
1759 +dependencies = [
1760 + "cesu8",
1761 + "combine",
1762 + "jni-sys",
1763 + "log",
1764 + "thiserror",
1765 + "walkdir",
1766 +]
1767 +
1768 +[[package]]
1769 +name = "jni"
1770 +version = "0.21.1"
1771 +source = "registry+https://github.com/rust-lang/crates.io-index"
1772 +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
1773 +dependencies = [
1774 + "cesu8",
1775 + "cfg-if",
1776 + "combine",
1777 + "jni-sys",
1778 + "log",
1779 + "thiserror",
1780 + "walkdir",
1781 + "windows-sys 0.45.0",
1782 +]
1783 +
1784 +[[package]]
1785 +name = "jni-sys"
1786 +version = "0.3.0"
1787 +source = "registry+https://github.com/rust-lang/crates.io-index"
1788 +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
1789 +
1790 +[[package]]
1791 +name = "jobserver"
1792 +version = "0.1.28"
1793 +source = "registry+https://github.com/rust-lang/crates.io-index"
1794 +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
1795 +dependencies = [
1796 + "libc",
1797 +]
1798 +
1799 +[[package]]
1800 +name = "js-sys"
1801 +version = "0.3.69"
1802 +source = "registry+https://github.com/rust-lang/crates.io-index"
1803 +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
1804 +dependencies = [
1805 + "wasm-bindgen",
1806 +]
1807 +
1808 +[[package]]
1809 +name = "json-patch"
1810 +version = "1.2.0"
1811 +source = "registry+https://github.com/rust-lang/crates.io-index"
1812 +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
1813 +dependencies = [
1814 + "serde",
1815 + "serde_json",
1816 + "thiserror",
1817 + "treediff",
1818 +]
1819 +
1820 +[[package]]
1821 +name = "kuchikiki"
1822 +version = "0.8.2"
1823 +source = "registry+https://github.com/rust-lang/crates.io-index"
1824 +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
1825 +dependencies = [
1826 + "cssparser",
1827 + "html5ever",
1828 + "indexmap 1.9.3",
1829 + "matches",
1830 + "selectors",
1831 +]
1832 +
1833 +[[package]]
1834 +name = "lazy_static"
1835 +version = "1.4.0"
1836 +source = "registry+https://github.com/rust-lang/crates.io-index"
1837 +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1838 +
1839 +[[package]]
1840 +name = "lazycell"
1841 +version = "1.3.0"
1842 +source = "registry+https://github.com/rust-lang/crates.io-index"
1843 +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
1844 +
1845 +[[package]]
1846 +name = "lewton"
1847 +version = "0.10.2"
1848 +source = "registry+https://github.com/rust-lang/crates.io-index"
1849 +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030"
1850 +dependencies = [
1851 + "byteorder",
1852 + "ogg",
1853 + "tinyvec",
1854 +]
1855 +
1856 +[[package]]
1857 +name = "libc"
1858 +version = "0.2.153"
1859 +source = "registry+https://github.com/rust-lang/crates.io-index"
1860 +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
1861 +
1862 +[[package]]
1863 +name = "libloading"
1864 +version = "0.8.3"
1865 +source = "registry+https://github.com/rust-lang/crates.io-index"
1866 +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
1867 +dependencies = [
1868 + "cfg-if",
1869 + "windows-targets 0.52.4",
1870 +]
1871 +
1872 +[[package]]
1873 +name = "libredox"
1874 +version = "0.0.1"
1875 +source = "registry+https://github.com/rust-lang/crates.io-index"
1876 +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
1877 +dependencies = [
1878 + "bitflags 2.4.2",
1879 + "libc",
1880 + "redox_syscall",
1881 +]
1882 +
1883 +[[package]]
1884 +name = "line-wrap"
1885 +version = "0.1.1"
1886 +source = "registry+https://github.com/rust-lang/crates.io-index"
1887 +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
1888 +dependencies = [
1889 + "safemem",
1890 +]
1891 +
1892 +[[package]]
1893 +name = "linux-raw-sys"
1894 +version = "0.4.13"
1895 +source = "registry+https://github.com/rust-lang/crates.io-index"
1896 +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
1897 +
1898 +[[package]]
1899 +name = "lock_api"
1900 +version = "0.4.11"
1901 +source = "registry+https://github.com/rust-lang/crates.io-index"
1902 +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
1903 +dependencies = [
1904 + "autocfg",
1905 + "scopeguard",
1906 +]
1907 +
1908 +[[package]]
1909 +name = "log"
1910 +version = "0.4.21"
1911 +source = "registry+https://github.com/rust-lang/crates.io-index"
1912 +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
1913 +dependencies = [
1914 + "value-bag",
1915 +]
1916 +
1917 +[[package]]
1918 +name = "loom"
1919 +version = "0.5.6"
1920 +source = "registry+https://github.com/rust-lang/crates.io-index"
1921 +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
1922 +dependencies = [
1923 + "cfg-if",
1924 + "generator",
1925 + "scoped-tls",
1926 + "serde",
1927 + "serde_json",
1928 + "tracing",
1929 + "tracing-subscriber",
1930 +]
1931 +
1932 +[[package]]
1933 +name = "mac"
1934 +version = "0.1.1"
1935 +source = "registry+https://github.com/rust-lang/crates.io-index"
1936 +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
1937 +
1938 +[[package]]
1939 +name = "mach2"
1940 +version = "0.4.2"
1941 +source = "registry+https://github.com/rust-lang/crates.io-index"
1942 +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
1943 +dependencies = [
1944 + "libc",
1945 +]
1946 +
1947 +[[package]]
1948 +name = "malloc_buf"
1949 +version = "0.0.6"
1950 +source = "registry+https://github.com/rust-lang/crates.io-index"
1951 +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
1952 +dependencies = [
1953 + "libc",
1954 +]
1955 +
1956 +[[package]]
1957 +name = "markup5ever"
1958 +version = "0.11.0"
1959 +source = "registry+https://github.com/rust-lang/crates.io-index"
1960 +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
1961 +dependencies = [
1962 + "log",
1963 + "phf 0.10.1",
1964 + "phf_codegen 0.10.0",
1965 + "string_cache",
1966 + "string_cache_codegen",
1967 + "tendril",
1968 +]
1969 +
1970 +[[package]]
1971 +name = "matchers"
1972 +version = "0.1.0"
1973 +source = "registry+https://github.com/rust-lang/crates.io-index"
1974 +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
1975 +dependencies = [
1976 + "regex-automata 0.1.10",
1977 +]
1978 +
1979 +[[package]]
1980 +name = "matches"
1981 +version = "0.1.10"
1982 +source = "registry+https://github.com/rust-lang/crates.io-index"
1983 +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
1984 +
1985 +[[package]]
1986 +name = "memchr"
1987 +version = "2.7.1"
1988 +source = "registry+https://github.com/rust-lang/crates.io-index"
1989 +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
1990 +
1991 +[[package]]
1992 +name = "memoffset"
1993 +version = "0.9.0"
1994 +source = "registry+https://github.com/rust-lang/crates.io-index"
1995 +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1996 +dependencies = [
1997 + "autocfg",
1998 +]
1999 +
2000 +[[package]]
2001 +name = "mime"
2002 +version = "0.3.17"
2003 +source = "registry+https://github.com/rust-lang/crates.io-index"
2004 +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
2005 +
2006 +[[package]]
2007 +name = "minimal-lexical"
2008 +version = "0.2.1"
2009 +source = "registry+https://github.com/rust-lang/crates.io-index"
2010 +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2011 +
2012 +[[package]]
2013 +name = "miniz_oxide"
2014 +version = "0.7.2"
2015 +source = "registry+https://github.com/rust-lang/crates.io-index"
2016 +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
2017 +dependencies = [
2018 + "adler",
2019 + "simd-adler32",
2020 +]
2021 +
2022 +[[package]]
2023 +name = "mio"
2024 +version = "0.8.11"
2025 +source = "registry+https://github.com/rust-lang/crates.io-index"
2026 +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
2027 +dependencies = [
2028 + "libc",
2029 + "wasi 0.11.0+wasi-snapshot-preview1",
2030 + "windows-sys 0.48.0",
2031 +]
2032 +
2033 +[[package]]
2034 +name = "native-tls"
2035 +version = "0.2.11"
2036 +source = "registry+https://github.com/rust-lang/crates.io-index"
2037 +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
2038 +dependencies = [
2039 + "lazy_static",
2040 + "libc",
2041 + "log",
2042 + "openssl",
2043 + "openssl-probe",
2044 + "openssl-sys",
2045 + "schannel",
2046 + "security-framework",
2047 + "security-framework-sys",
2048 + "tempfile",
2049 +]
2050 +
2051 +[[package]]
2052 +name = "ndk"
2053 +version = "0.6.0"
2054 +source = "registry+https://github.com/rust-lang/crates.io-index"
2055 +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
2056 +dependencies = [
2057 + "bitflags 1.3.2",
2058 + "jni-sys",
2059 + "ndk-sys 0.3.0",
2060 + "num_enum 0.5.11",
2061 + "thiserror",
2062 +]
2063 +
2064 +[[package]]
2065 +name = "ndk"
2066 +version = "0.8.0"
2067 +source = "registry+https://github.com/rust-lang/crates.io-index"
2068 +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
2069 +dependencies = [
2070 + "bitflags 2.4.2",
2071 + "jni-sys",
2072 + "log",
2073 + "ndk-sys 0.5.0+25.2.9519653",
2074 + "num_enum 0.7.2",
2075 + "thiserror",
2076 +]
2077 +
2078 +[[package]]
2079 +name = "ndk-context"
2080 +version = "0.1.1"
2081 +source = "registry+https://github.com/rust-lang/crates.io-index"
2082 +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
2083 +
2084 +[[package]]
2085 +name = "ndk-sys"
2086 +version = "0.3.0"
2087 +source = "registry+https://github.com/rust-lang/crates.io-index"
2088 +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
2089 +dependencies = [
2090 + "jni-sys",
2091 +]
2092 +
2093 +[[package]]
2094 +name = "ndk-sys"
2095 +version = "0.5.0+25.2.9519653"
2096 +source = "registry+https://github.com/rust-lang/crates.io-index"
2097 +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
2098 +dependencies = [
2099 + "jni-sys",
2100 +]
2101 +
2102 +[[package]]
2103 +name = "new_debug_unreachable"
2104 +version = "1.0.6"
2105 +source = "registry+https://github.com/rust-lang/crates.io-index"
2106 +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
2107 +
2108 +[[package]]
2109 +name = "nodrop"
2110 +version = "0.1.14"
2111 +source = "registry+https://github.com/rust-lang/crates.io-index"
2112 +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
2113 +
2114 +[[package]]
2115 +name = "nom"
2116 +version = "7.1.3"
2117 +source = "registry+https://github.com/rust-lang/crates.io-index"
2118 +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
2119 +dependencies = [
2120 + "memchr",
2121 + "minimal-lexical",
2122 +]
2123 +
2124 +[[package]]
2125 +name = "nu-ansi-term"
2126 +version = "0.46.0"
2127 +source = "registry+https://github.com/rust-lang/crates.io-index"
2128 +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
2129 +dependencies = [
2130 + "overload",
2131 + "winapi",
2132 +]
2133 +
2134 +[[package]]
2135 +name = "num-conv"
2136 +version = "0.1.0"
2137 +source = "registry+https://github.com/rust-lang/crates.io-index"
2138 +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
2139 +
2140 +[[package]]
2141 +name = "num-derive"
2142 +version = "0.4.2"
2143 +source = "registry+https://github.com/rust-lang/crates.io-index"
2144 +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
2145 +dependencies = [
2146 + "proc-macro2",
2147 + "quote",
2148 + "syn 2.0.53",
2149 +]
2150 +
2151 +[[package]]
2152 +name = "num-traits"
2153 +version = "0.2.18"
2154 +source = "registry+https://github.com/rust-lang/crates.io-index"
2155 +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
2156 +dependencies = [
2157 + "autocfg",
2158 +]
2159 +
2160 +[[package]]
2161 +name = "num_cpus"
2162 +version = "1.16.0"
2163 +source = "registry+https://github.com/rust-lang/crates.io-index"
2164 +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
2165 +dependencies = [
2166 + "hermit-abi",
2167 + "libc",
2168 +]
2169 +
2170 +[[package]]
2171 +name = "num_enum"
2172 +version = "0.5.11"
2173 +source = "registry+https://github.com/rust-lang/crates.io-index"
2174 +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
2175 +dependencies = [
2176 + "num_enum_derive 0.5.11",
2177 +]
2178 +
2179 +[[package]]
2180 +name = "num_enum"
2181 +version = "0.7.2"
2182 +source = "registry+https://github.com/rust-lang/crates.io-index"
2183 +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
2184 +dependencies = [
2185 + "num_enum_derive 0.7.2",
2186 +]
2187 +
2188 +[[package]]
2189 +name = "num_enum_derive"
2190 +version = "0.5.11"
2191 +source = "registry+https://github.com/rust-lang/crates.io-index"
2192 +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
2193 +dependencies = [
2194 + "proc-macro-crate 1.3.1",
2195 + "proc-macro2",
2196 + "quote",
2197 + "syn 1.0.109",
2198 +]
2199 +
2200 +[[package]]
2201 +name = "num_enum_derive"
2202 +version = "0.7.2"
2203 +source = "registry+https://github.com/rust-lang/crates.io-index"
2204 +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
2205 +dependencies = [
2206 + "proc-macro-crate 3.1.0",
2207 + "proc-macro2",
2208 + "quote",
2209 + "syn 2.0.53",
2210 +]
2211 +
2212 +[[package]]
2213 +name = "num_threads"
2214 +version = "0.1.7"
2215 +source = "registry+https://github.com/rust-lang/crates.io-index"
2216 +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
2217 +dependencies = [
2218 + "libc",
2219 +]
2220 +
2221 +[[package]]
2222 +name = "objc"
2223 +version = "0.2.7"
2224 +source = "registry+https://github.com/rust-lang/crates.io-index"
2225 +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
2226 +dependencies = [
2227 + "malloc_buf",
2228 + "objc_exception",
2229 +]
2230 +
2231 +[[package]]
2232 +name = "objc_exception"
2233 +version = "0.1.2"
2234 +source = "registry+https://github.com/rust-lang/crates.io-index"
2235 +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
2236 +dependencies = [
2237 + "cc",
2238 +]
2239 +
2240 +[[package]]
2241 +name = "objc_id"
2242 +version = "0.1.1"
2243 +source = "registry+https://github.com/rust-lang/crates.io-index"
2244 +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
2245 +dependencies = [
2246 + "objc",
2247 +]
2248 +
2249 +[[package]]
2250 +name = "object"
2251 +version = "0.32.2"
2252 +source = "registry+https://github.com/rust-lang/crates.io-index"
2253 +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
2254 +dependencies = [
2255 + "memchr",
2256 +]
2257 +
2258 +[[package]]
2259 +name = "oboe"
2260 +version = "0.6.1"
2261 +source = "registry+https://github.com/rust-lang/crates.io-index"
2262 +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
2263 +dependencies = [
2264 + "jni 0.21.1",
2265 + "ndk 0.8.0",
2266 + "ndk-context",
2267 + "num-derive",
2268 + "num-traits",
2269 + "oboe-sys",
2270 +]
2271 +
2272 +[[package]]
2273 +name = "oboe-sys"
2274 +version = "0.6.1"
2275 +source = "registry+https://github.com/rust-lang/crates.io-index"
2276 +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
2277 +dependencies = [
2278 + "cc",
2279 +]
2280 +
2281 +[[package]]
2282 +name = "ogg"
2283 +version = "0.8.0"
2284 +source = "registry+https://github.com/rust-lang/crates.io-index"
2285 +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e"
2286 +dependencies = [
2287 + "byteorder",
2288 +]
2289 +
2290 +[[package]]
2291 +name = "once_cell"
2292 +version = "1.19.0"
2293 +source = "registry+https://github.com/rust-lang/crates.io-index"
2294 +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
2295 +
2296 +[[package]]
2297 +name = "openssl"
2298 +version = "0.10.64"
2299 +source = "registry+https://github.com/rust-lang/crates.io-index"
2300 +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
2301 +dependencies = [
2302 + "bitflags 2.4.2",
2303 + "cfg-if",
2304 + "foreign-types",
2305 + "libc",
2306 + "once_cell",
2307 + "openssl-macros",
2308 + "openssl-sys",
2309 +]
2310 +
2311 +[[package]]
2312 +name = "openssl-macros"
2313 +version = "0.1.1"
2314 +source = "registry+https://github.com/rust-lang/crates.io-index"
2315 +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
2316 +dependencies = [
2317 + "proc-macro2",
2318 + "quote",
2319 + "syn 2.0.53",
2320 +]
2321 +
2322 +[[package]]
2323 +name = "openssl-probe"
2324 +version = "0.1.5"
2325 +source = "registry+https://github.com/rust-lang/crates.io-index"
2326 +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
2327 +
2328 +[[package]]
2329 +name = "openssl-sys"
2330 +version = "0.9.101"
2331 +source = "registry+https://github.com/rust-lang/crates.io-index"
2332 +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
2333 +dependencies = [
2334 + "cc",
2335 + "libc",
2336 + "pkg-config",
2337 + "vcpkg",
2338 +]
2339 +
2340 +[[package]]
2341 +name = "overload"
2342 +version = "0.1.1"
2343 +source = "registry+https://github.com/rust-lang/crates.io-index"
2344 +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
2345 +
2346 +[[package]]
2347 +name = "pango"
2348 +version = "0.15.10"
2349 +source = "registry+https://github.com/rust-lang/crates.io-index"
2350 +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f"
2351 +dependencies = [
2352 + "bitflags 1.3.2",
2353 + "glib",
2354 + "libc",
2355 + "once_cell",
2356 + "pango-sys",
2357 +]
2358 +
2359 +[[package]]
2360 +name = "pango-sys"
2361 +version = "0.15.10"
2362 +source = "registry+https://github.com/rust-lang/crates.io-index"
2363 +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa"
2364 +dependencies = [
2365 + "glib-sys",
2366 + "gobject-sys",
2367 + "libc",
2368 + "system-deps 6.2.1",
2369 +]
2370 +
2371 +[[package]]
2372 +name = "parking_lot"
2373 +version = "0.12.1"
2374 +source = "registry+https://github.com/rust-lang/crates.io-index"
2375 +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
2376 +dependencies = [
2377 + "lock_api",
2378 + "parking_lot_core",
2379 +]
2380 +
2381 +[[package]]
2382 +name = "parking_lot_core"
2383 +version = "0.9.9"
2384 +source = "registry+https://github.com/rust-lang/crates.io-index"
2385 +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
2386 +dependencies = [
2387 + "cfg-if",
2388 + "libc",
2389 + "redox_syscall",
2390 + "smallvec",
2391 + "windows-targets 0.48.5",
2392 +]
2393 +
2394 +[[package]]
2395 +name = "percent-encoding"
2396 +version = "2.3.1"
2397 +source = "registry+https://github.com/rust-lang/crates.io-index"
2398 +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
2399 +
2400 +[[package]]
2401 +name = "phf"
2402 +version = "0.8.0"
2403 +source = "registry+https://github.com/rust-lang/crates.io-index"
2404 +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
2405 +dependencies = [
2406 + "phf_macros 0.8.0",
2407 + "phf_shared 0.8.0",
2408 + "proc-macro-hack",
2409 +]
2410 +
2411 +[[package]]
2412 +name = "phf"
2413 +version = "0.10.1"
2414 +source = "registry+https://github.com/rust-lang/crates.io-index"
2415 +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
2416 +dependencies = [
2417 + "phf_shared 0.10.0",
2418 +]
2419 +
2420 +[[package]]
2421 +name = "phf"
2422 +version = "0.11.2"
2423 +source = "registry+https://github.com/rust-lang/crates.io-index"
2424 +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
2425 +dependencies = [
2426 + "phf_macros 0.11.2",
2427 + "phf_shared 0.11.2",
2428 +]
2429 +
2430 +[[package]]
2431 +name = "phf_codegen"
2432 +version = "0.8.0"
2433 +source = "registry+https://github.com/rust-lang/crates.io-index"
2434 +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
2435 +dependencies = [
2436 + "phf_generator 0.8.0",
2437 + "phf_shared 0.8.0",
2438 +]
2439 +
2440 +[[package]]
2441 +name = "phf_codegen"
2442 +version = "0.10.0"
2443 +source = "registry+https://github.com/rust-lang/crates.io-index"
2444 +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
2445 +dependencies = [
2446 + "phf_generator 0.10.0",
2447 + "phf_shared 0.10.0",
2448 +]
2449 +
2450 +[[package]]
2451 +name = "phf_generator"
2452 +version = "0.8.0"
2453 +source = "registry+https://github.com/rust-lang/crates.io-index"
2454 +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
2455 +dependencies = [
2456 + "phf_shared 0.8.0",
2457 + "rand 0.7.3",
2458 +]
2459 +
2460 +[[package]]
2461 +name = "phf_generator"
2462 +version = "0.10.0"
2463 +source = "registry+https://github.com/rust-lang/crates.io-index"
2464 +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
2465 +dependencies = [
2466 + "phf_shared 0.10.0",
2467 + "rand 0.8.5",
2468 +]
2469 +
2470 +[[package]]
2471 +name = "phf_generator"
2472 +version = "0.11.2"
2473 +source = "registry+https://github.com/rust-lang/crates.io-index"
2474 +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
2475 +dependencies = [
2476 + "phf_shared 0.11.2",
2477 + "rand 0.8.5",
2478 +]
2479 +
2480 +[[package]]
2481 +name = "phf_macros"
2482 +version = "0.8.0"
2483 +source = "registry+https://github.com/rust-lang/crates.io-index"
2484 +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
2485 +dependencies = [
2486 + "phf_generator 0.8.0",
2487 + "phf_shared 0.8.0",
2488 + "proc-macro-hack",
2489 + "proc-macro2",
2490 + "quote",
2491 + "syn 1.0.109",
2492 +]
2493 +
2494 +[[package]]
2495 +name = "phf_macros"
2496 +version = "0.11.2"
2497 +source = "registry+https://github.com/rust-lang/crates.io-index"
2498 +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
2499 +dependencies = [
2500 + "phf_generator 0.11.2",
2501 + "phf_shared 0.11.2",
2502 + "proc-macro2",
2503 + "quote",
2504 + "syn 2.0.53",
2505 +]
2506 +
2507 +[[package]]
2508 +name = "phf_shared"
2509 +version = "0.8.0"
2510 +source = "registry+https://github.com/rust-lang/crates.io-index"
2511 +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
2512 +dependencies = [
2513 + "siphasher",
2514 +]
2515 +
2516 +[[package]]
2517 +name = "phf_shared"
2518 +version = "0.10.0"
2519 +source = "registry+https://github.com/rust-lang/crates.io-index"
2520 +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
2521 +dependencies = [
2522 + "siphasher",
2523 +]
2524 +
2525 +[[package]]
2526 +name = "phf_shared"
2527 +version = "0.11.2"
2528 +source = "registry+https://github.com/rust-lang/crates.io-index"
2529 +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
2530 +dependencies = [
2531 + "siphasher",
2532 +]
2533 +
2534 +[[package]]
2535 +name = "pin-project-lite"
2536 +version = "0.2.13"
2537 +source = "registry+https://github.com/rust-lang/crates.io-index"
2538 +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
2539 +
2540 +[[package]]
2541 +name = "pin-utils"
2542 +version = "0.1.0"
2543 +source = "registry+https://github.com/rust-lang/crates.io-index"
2544 +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
2545 +
2546 +[[package]]
2547 +name = "pkg-config"
2548 +version = "0.3.30"
2549 +source = "registry+https://github.com/rust-lang/crates.io-index"
2550 +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
2551 +
2552 +[[package]]
2553 +name = "plist"
2554 +version = "1.6.0"
2555 +source = "registry+https://github.com/rust-lang/crates.io-index"
2556 +checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef"
2557 +dependencies = [
2558 + "base64 0.21.7",
2559 + "indexmap 2.2.5",
2560 + "line-wrap",
2561 + "quick-xml",
2562 + "serde",
2563 + "time",
2564 +]
2565 +
2566 +[[package]]
2567 +name = "png"
2568 +version = "0.17.13"
2569 +source = "registry+https://github.com/rust-lang/crates.io-index"
2570 +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1"
2571 +dependencies = [
2572 + "bitflags 1.3.2",
2573 + "crc32fast",
2574 + "fdeflate",
2575 + "flate2",
2576 + "miniz_oxide",
2577 +]
2578 +
2579 +[[package]]
2580 +name = "powerfmt"
2581 +version = "0.2.0"
2582 +source = "registry+https://github.com/rust-lang/crates.io-index"
2583 +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
2584 +
2585 +[[package]]
2586 +name = "ppv-lite86"
2587 +version = "0.2.17"
2588 +source = "registry+https://github.com/rust-lang/crates.io-index"
2589 +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
2590 +
2591 +[[package]]
2592 +name = "precomputed-hash"
2593 +version = "0.1.1"
2594 +source = "registry+https://github.com/rust-lang/crates.io-index"
2595 +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
2596 +
2597 +[[package]]
2598 +name = "proc-macro-crate"
2599 +version = "1.3.1"
2600 +source = "registry+https://github.com/rust-lang/crates.io-index"
2601 +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
2602 +dependencies = [
2603 + "once_cell",
2604 + "toml_edit 0.19.15",
2605 +]
2606 +
2607 +[[package]]
2608 +name = "proc-macro-crate"
2609 +version = "3.1.0"
2610 +source = "registry+https://github.com/rust-lang/crates.io-index"
2611 +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
2612 +dependencies = [
2613 + "toml_edit 0.21.1",
2614 +]
2615 +
2616 +[[package]]
2617 +name = "proc-macro-error"
2618 +version = "1.0.4"
2619 +source = "registry+https://github.com/rust-lang/crates.io-index"
2620 +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2621 +dependencies = [
2622 + "proc-macro-error-attr",
2623 + "proc-macro2",
2624 + "quote",
2625 + "syn 1.0.109",
2626 + "version_check",
2627 +]
2628 +
2629 +[[package]]
2630 +name = "proc-macro-error-attr"
2631 +version = "1.0.4"
2632 +source = "registry+https://github.com/rust-lang/crates.io-index"
2633 +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2634 +dependencies = [
2635 + "proc-macro2",
2636 + "quote",
2637 + "version_check",
2638 +]
2639 +
2640 +[[package]]
2641 +name = "proc-macro-hack"
2642 +version = "0.5.20+deprecated"
2643 +source = "registry+https://github.com/rust-lang/crates.io-index"
2644 +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
2645 +
2646 +[[package]]
2647 +name = "proc-macro2"
2648 +version = "1.0.79"
2649 +source = "registry+https://github.com/rust-lang/crates.io-index"
2650 +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
2651 +dependencies = [
2652 + "unicode-ident",
2653 +]
2654 +
2655 +[[package]]
2656 +name = "ptr_meta"
2657 +version = "0.1.4"
2658 +source = "registry+https://github.com/rust-lang/crates.io-index"
2659 +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
2660 +dependencies = [
2661 + "ptr_meta_derive",
2662 +]
2663 +
2664 +[[package]]
2665 +name = "ptr_meta_derive"
2666 +version = "0.1.4"
2667 +source = "registry+https://github.com/rust-lang/crates.io-index"
2668 +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
2669 +dependencies = [
2670 + "proc-macro2",
2671 + "quote",
2672 + "syn 1.0.109",
2673 +]
2674 +
2675 +[[package]]
2676 +name = "quick-xml"
2677 +version = "0.31.0"
2678 +source = "registry+https://github.com/rust-lang/crates.io-index"
2679 +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
2680 +dependencies = [
2681 + "memchr",
2682 +]
2683 +
2684 +[[package]]
2685 +name = "quote"
2686 +version = "1.0.35"
2687 +source = "registry+https://github.com/rust-lang/crates.io-index"
2688 +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
2689 +dependencies = [
2690 + "proc-macro2",
2691 +]
2692 +
2693 +[[package]]
2694 +name = "radium"
2695 +version = "0.7.0"
2696 +source = "registry+https://github.com/rust-lang/crates.io-index"
2697 +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
2698 +
2699 +[[package]]
2700 +name = "rand"
2701 +version = "0.7.3"
2702 +source = "registry+https://github.com/rust-lang/crates.io-index"
2703 +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
2704 +dependencies = [
2705 + "getrandom 0.1.16",
2706 + "libc",
2707 + "rand_chacha 0.2.2",
2708 + "rand_core 0.5.1",
2709 + "rand_hc",
2710 + "rand_pcg",
2711 +]
2712 +
2713 +[[package]]
2714 +name = "rand"
2715 +version = "0.8.5"
2716 +source = "registry+https://github.com/rust-lang/crates.io-index"
2717 +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
2718 +dependencies = [
2719 + "libc",
2720 + "rand_chacha 0.3.1",
2721 + "rand_core 0.6.4",
2722 +]
2723 +
2724 +[[package]]
2725 +name = "rand_chacha"
2726 +version = "0.2.2"
2727 +source = "registry+https://github.com/rust-lang/crates.io-index"
2728 +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
2729 +dependencies = [
2730 + "ppv-lite86",
2731 + "rand_core 0.5.1",
2732 +]
2733 +
2734 +[[package]]
2735 +name = "rand_chacha"
2736 +version = "0.3.1"
2737 +source = "registry+https://github.com/rust-lang/crates.io-index"
2738 +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
2739 +dependencies = [
2740 + "ppv-lite86",
2741 + "rand_core 0.6.4",
2742 +]
2743 +
2744 +[[package]]
2745 +name = "rand_core"
2746 +version = "0.5.1"
2747 +source = "registry+https://github.com/rust-lang/crates.io-index"
2748 +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
2749 +dependencies = [
2750 + "getrandom 0.1.16",
2751 +]
2752 +
2753 +[[package]]
2754 +name = "rand_core"
2755 +version = "0.6.4"
2756 +source = "registry+https://github.com/rust-lang/crates.io-index"
2757 +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
2758 +dependencies = [
2759 + "getrandom 0.2.12",
2760 +]
2761 +
2762 +[[package]]
2763 +name = "rand_hc"
2764 +version = "0.2.0"
2765 +source = "registry+https://github.com/rust-lang/crates.io-index"
2766 +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
2767 +dependencies = [
2768 + "rand_core 0.5.1",
2769 +]
2770 +
2771 +[[package]]
2772 +name = "rand_pcg"
2773 +version = "0.2.1"
2774 +source = "registry+https://github.com/rust-lang/crates.io-index"
2775 +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
2776 +dependencies = [
2777 + "rand_core 0.5.1",
2778 +]
2779 +
2780 +[[package]]
2781 +name = "raw-window-handle"
2782 +version = "0.5.2"
2783 +source = "registry+https://github.com/rust-lang/crates.io-index"
2784 +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
2785 +
2786 +[[package]]
2787 +name = "redox_syscall"
2788 +version = "0.4.1"
2789 +source = "registry+https://github.com/rust-lang/crates.io-index"
2790 +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
2791 +dependencies = [
2792 + "bitflags 1.3.2",
2793 +]
2794 +
2795 +[[package]]
2796 +name = "redox_users"
2797 +version = "0.4.4"
2798 +source = "registry+https://github.com/rust-lang/crates.io-index"
2799 +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
2800 +dependencies = [
2801 + "getrandom 0.2.12",
2802 + "libredox",
2803 + "thiserror",
2804 +]
2805 +
2806 +[[package]]
2807 +name = "regex"
2808 +version = "1.10.3"
2809 +source = "registry+https://github.com/rust-lang/crates.io-index"
2810 +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
2811 +dependencies = [
2812 + "aho-corasick",
2813 + "memchr",
2814 + "regex-automata 0.4.6",
2815 + "regex-syntax 0.8.2",
2816 +]
2817 +
2818 +[[package]]
2819 +name = "regex-automata"
2820 +version = "0.1.10"
2821 +source = "registry+https://github.com/rust-lang/crates.io-index"
2822 +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
2823 +dependencies = [
2824 + "regex-syntax 0.6.29",
2825 +]
2826 +
2827 +[[package]]
2828 +name = "regex-automata"
2829 +version = "0.4.6"
2830 +source = "registry+https://github.com/rust-lang/crates.io-index"
2831 +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
2832 +dependencies = [
2833 + "aho-corasick",
2834 + "memchr",
2835 + "regex-syntax 0.8.2",
2836 +]
2837 +
2838 +[[package]]
2839 +name = "regex-syntax"
2840 +version = "0.6.29"
2841 +source = "registry+https://github.com/rust-lang/crates.io-index"
2842 +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
2843 +
2844 +[[package]]
2845 +name = "regex-syntax"
2846 +version = "0.8.2"
2847 +source = "registry+https://github.com/rust-lang/crates.io-index"
2848 +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
2849 +
2850 +[[package]]
2851 +name = "rend"
2852 +version = "0.4.2"
2853 +source = "registry+https://github.com/rust-lang/crates.io-index"
2854 +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
2855 +dependencies = [
2856 + "bytecheck",
2857 +]
2858 +
2859 +[[package]]
2860 +name = "reqwest"
2861 +version = "0.11.26"
2862 +source = "registry+https://github.com/rust-lang/crates.io-index"
2863 +checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2"
2864 +dependencies = [
2865 + "base64 0.21.7",
2866 + "bytes",
2867 + "encoding_rs",
2868 + "futures-core",
2869 + "futures-util",
2870 + "h2",
2871 + "http",
2872 + "http-body",
2873 + "hyper",
2874 + "hyper-tls",
2875 + "ipnet",
2876 + "js-sys",
2877 + "log",
2878 + "mime",
2879 + "native-tls",
2880 + "once_cell",
2881 + "percent-encoding",
2882 + "pin-project-lite",
2883 + "rustls-pemfile",
2884 + "serde",
2885 + "serde_json",
2886 + "serde_urlencoded",
2887 + "sync_wrapper",
2888 + "system-configuration",
2889 + "tokio",
2890 + "tokio-native-tls",
2891 + "tower-service",
2892 + "url",
2893 + "wasm-bindgen",
2894 + "wasm-bindgen-futures",
2895 + "web-sys",
2896 + "winreg 0.50.0",
2897 +]
2898 +
2899 +[[package]]
2900 +name = "ringbuf"
2901 +version = "0.3.3"
2902 +source = "registry+https://github.com/rust-lang/crates.io-index"
2903 +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a"
2904 +dependencies = [
2905 + "crossbeam-utils",
2906 +]
2907 +
2908 +[[package]]
2909 +name = "rkyv"
2910 +version = "0.7.44"
2911 +source = "registry+https://github.com/rust-lang/crates.io-index"
2912 +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0"
2913 +dependencies = [
2914 + "bitvec",
2915 + "bytecheck",
2916 + "bytes",
2917 + "hashbrown 0.12.3",
2918 + "ptr_meta",
2919 + "rend",
2920 + "rkyv_derive",
2921 + "seahash",
2922 + "tinyvec",
2923 + "uuid",
2924 +]
2925 +
2926 +[[package]]
2927 +name = "rkyv_derive"
2928 +version = "0.7.44"
2929 +source = "registry+https://github.com/rust-lang/crates.io-index"
2930 +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65"
2931 +dependencies = [
2932 + "proc-macro2",
2933 + "quote",
2934 + "syn 1.0.109",
2935 +]
2936 +
2937 +[[package]]
2938 +name = "rodio"
2939 +version = "0.17.3"
2940 +source = "registry+https://github.com/rust-lang/crates.io-index"
2941 +checksum = "3b1bb7b48ee48471f55da122c0044fcc7600cfcc85db88240b89cb832935e611"
2942 +dependencies = [
2943 + "claxon",
2944 + "cpal",
2945 + "hound",
2946 + "lewton",
2947 + "symphonia",
2948 +]
2949 +
2950 +[[package]]
2951 +name = "rust_decimal"
2952 +version = "1.34.3"
2953 +source = "registry+https://github.com/rust-lang/crates.io-index"
2954 +checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df"
2955 +dependencies = [
2956 + "arrayvec",
2957 + "borsh",
2958 + "bytes",
2959 + "num-traits",
2960 + "rand 0.8.5",
2961 + "rkyv",
2962 + "serde",
2963 + "serde_json",
2964 +]
2965 +
2966 +[[package]]
2967 +name = "rustc-demangle"
2968 +version = "0.1.23"
2969 +source = "registry+https://github.com/rust-lang/crates.io-index"
2970 +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
2971 +
2972 +[[package]]
2973 +name = "rustc-hash"
2974 +version = "1.1.0"
2975 +source = "registry+https://github.com/rust-lang/crates.io-index"
2976 +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
2977 +
2978 +[[package]]
2979 +name = "rustc_version"
2980 +version = "0.4.0"
2981 +source = "registry+https://github.com/rust-lang/crates.io-index"
2982 +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
2983 +dependencies = [
2984 + "semver",
2985 +]
2986 +
2987 +[[package]]
2988 +name = "rustix"
2989 +version = "0.38.31"
2990 +source = "registry+https://github.com/rust-lang/crates.io-index"
2991 +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
2992 +dependencies = [
2993 + "bitflags 2.4.2",
2994 + "errno",
2995 + "libc",
2996 + "linux-raw-sys",
2997 + "windows-sys 0.52.0",
2998 +]
2999 +
3000 +[[package]]
3001 +name = "rustls-pemfile"
3002 +version = "1.0.4"
3003 +source = "registry+https://github.com/rust-lang/crates.io-index"
3004 +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
3005 +dependencies = [
3006 + "base64 0.21.7",
3007 +]
3008 +
3009 +[[package]]
3010 +name = "rustversion"
3011 +version = "1.0.14"
3012 +source = "registry+https://github.com/rust-lang/crates.io-index"
3013 +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
3014 +
3015 +[[package]]
3016 +name = "ryu"
3017 +version = "1.0.17"
3018 +source = "registry+https://github.com/rust-lang/crates.io-index"
3019 +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
3020 +
3021 +[[package]]
3022 +name = "safemem"
3023 +version = "0.3.3"
3024 +source = "registry+https://github.com/rust-lang/crates.io-index"
3025 +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
3026 +
3027 +[[package]]
3028 +name = "same-file"
3029 +version = "1.0.6"
3030 +source = "registry+https://github.com/rust-lang/crates.io-index"
3031 +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
3032 +dependencies = [
3033 + "winapi-util",
3034 +]
3035 +
3036 +[[package]]
3037 +name = "schannel"
3038 +version = "0.1.23"
3039 +source = "registry+https://github.com/rust-lang/crates.io-index"
3040 +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
3041 +dependencies = [
3042 + "windows-sys 0.52.0",
3043 +]
3044 +
3045 +[[package]]
3046 +name = "scoped-tls"
3047 +version = "1.0.1"
3048 +source = "registry+https://github.com/rust-lang/crates.io-index"
3049 +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
3050 +
3051 +[[package]]
3052 +name = "scopeguard"
3053 +version = "1.2.0"
3054 +source = "registry+https://github.com/rust-lang/crates.io-index"
3055 +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3056 +
3057 +[[package]]
3058 +name = "seahash"
3059 +version = "4.1.0"
3060 +source = "registry+https://github.com/rust-lang/crates.io-index"
3061 +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
3062 +
3063 +[[package]]
3064 +name = "security-framework"
3065 +version = "2.9.2"
3066 +source = "registry+https://github.com/rust-lang/crates.io-index"
3067 +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
3068 +dependencies = [
3069 + "bitflags 1.3.2",
3070 + "core-foundation",
3071 + "core-foundation-sys",
3072 + "libc",
3073 + "security-framework-sys",
3074 +]
3075 +
3076 +[[package]]
3077 +name = "security-framework-sys"
3078 +version = "2.9.1"
3079 +source = "registry+https://github.com/rust-lang/crates.io-index"
3080 +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
3081 +dependencies = [
3082 + "core-foundation-sys",
3083 + "libc",
3084 +]
3085 +
3086 +[[package]]
3087 +name = "selectors"
3088 +version = "0.22.0"
3089 +source = "registry+https://github.com/rust-lang/crates.io-index"
3090 +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe"
3091 +dependencies = [
3092 + "bitflags 1.3.2",
3093 + "cssparser",
3094 + "derive_more",
3095 + "fxhash",
3096 + "log",
3097 + "matches",
3098 + "phf 0.8.0",
3099 + "phf_codegen 0.8.0",
3100 + "precomputed-hash",
3101 + "servo_arc",
3102 + "smallvec",
3103 + "thin-slice",
3104 +]
3105 +
3106 +[[package]]
3107 +name = "semver"
3108 +version = "1.0.22"
3109 +source = "registry+https://github.com/rust-lang/crates.io-index"
3110 +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
3111 +dependencies = [
3112 + "serde",
3113 +]
3114 +
3115 +[[package]]
3116 +name = "serde"
3117 +version = "1.0.197"
3118 +source = "registry+https://github.com/rust-lang/crates.io-index"
3119 +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
3120 +dependencies = [
3121 + "serde_derive",
3122 +]
3123 +
3124 +[[package]]
3125 +name = "serde_derive"
3126 +version = "1.0.197"
3127 +source = "registry+https://github.com/rust-lang/crates.io-index"
3128 +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
3129 +dependencies = [
3130 + "proc-macro2",
3131 + "quote",
3132 + "syn 2.0.53",
3133 +]
3134 +
3135 +[[package]]
3136 +name = "serde_json"
3137 +version = "1.0.114"
3138 +source = "registry+https://github.com/rust-lang/crates.io-index"
3139 +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
3140 +dependencies = [
3141 + "indexmap 2.2.5",
3142 + "itoa 1.0.10",
3143 + "ryu",
3144 + "serde",
3145 +]
3146 +
3147 +[[package]]
3148 +name = "serde_repr"
3149 +version = "0.1.18"
3150 +source = "registry+https://github.com/rust-lang/crates.io-index"
3151 +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
3152 +dependencies = [
3153 + "proc-macro2",
3154 + "quote",
3155 + "syn 2.0.53",
3156 +]
3157 +
3158 +[[package]]
3159 +name = "serde_spanned"
3160 +version = "0.6.5"
3161 +source = "registry+https://github.com/rust-lang/crates.io-index"
3162 +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
3163 +dependencies = [
3164 + "serde",
3165 +]
3166 +
3167 +[[package]]
3168 +name = "serde_urlencoded"
3169 +version = "0.7.1"
3170 +source = "registry+https://github.com/rust-lang/crates.io-index"
3171 +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
3172 +dependencies = [
3173 + "form_urlencoded",
3174 + "itoa 1.0.10",
3175 + "ryu",
3176 + "serde",
3177 +]
3178 +
3179 +[[package]]
3180 +name = "serde_with"
3181 +version = "3.7.0"
3182 +source = "registry+https://github.com/rust-lang/crates.io-index"
3183 +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
3184 +dependencies = [
3185 + "base64 0.21.7",
3186 + "chrono",
3187 + "hex",
3188 + "indexmap 1.9.3",
3189 + "indexmap 2.2.5",
3190 + "serde",
3191 + "serde_derive",
3192 + "serde_json",
3193 + "serde_with_macros",
3194 + "time",
3195 +]
3196 +
3197 +[[package]]
3198 +name = "serde_with_macros"
3199 +version = "3.7.0"
3200 +source = "registry+https://github.com/rust-lang/crates.io-index"
3201 +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
3202 +dependencies = [
3203 + "darling",
3204 + "proc-macro2",
3205 + "quote",
3206 + "syn 2.0.53",
3207 +]
3208 +
3209 +[[package]]
3210 +name = "serialize-to-javascript"
3211 +version = "0.1.1"
3212 +source = "registry+https://github.com/rust-lang/crates.io-index"
3213 +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
3214 +dependencies = [
3215 + "serde",
3216 + "serde_json",
3217 + "serialize-to-javascript-impl",
3218 +]
3219 +
3220 +[[package]]
3221 +name = "serialize-to-javascript-impl"
3222 +version = "0.1.1"
3223 +source = "registry+https://github.com/rust-lang/crates.io-index"
3224 +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
3225 +dependencies = [
3226 + "proc-macro2",
3227 + "quote",
3228 + "syn 1.0.109",
3229 +]
3230 +
3231 +[[package]]
3232 +name = "servo_arc"
3233 +version = "0.1.1"
3234 +source = "registry+https://github.com/rust-lang/crates.io-index"
3235 +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
3236 +dependencies = [
3237 + "nodrop",
3238 + "stable_deref_trait",
3239 +]
3240 +
3241 +[[package]]
3242 +name = "sha2"
3243 +version = "0.10.8"
3244 +source = "registry+https://github.com/rust-lang/crates.io-index"
3245 +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
3246 +dependencies = [
3247 + "cfg-if",
3248 + "cpufeatures",
3249 + "digest",
3250 +]
3251 +
3252 +[[package]]
3253 +name = "sharded-slab"
3254 +version = "0.1.7"
3255 +source = "registry+https://github.com/rust-lang/crates.io-index"
3256 +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
3257 +dependencies = [
3258 + "lazy_static",
3259 +]
3260 +
3261 +[[package]]
3262 +name = "shlex"
3263 +version = "1.3.0"
3264 +source = "registry+https://github.com/rust-lang/crates.io-index"
3265 +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
3266 +
3267 +[[package]]
3268 +name = "simd-adler32"
3269 +version = "0.3.7"
3270 +source = "registry+https://github.com/rust-lang/crates.io-index"
3271 +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
3272 +
3273 +[[package]]
3274 +name = "simdutf8"
3275 +version = "0.1.4"
3276 +source = "registry+https://github.com/rust-lang/crates.io-index"
3277 +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
3278 +
3279 +[[package]]
3280 +name = "siphasher"
3281 +version = "0.3.11"
3282 +source = "registry+https://github.com/rust-lang/crates.io-index"
3283 +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
3284 +
3285 +[[package]]
3286 +name = "slab"
3287 +version = "0.4.9"
3288 +source = "registry+https://github.com/rust-lang/crates.io-index"
3289 +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
3290 +dependencies = [
3291 + "autocfg",
3292 +]
3293 +
3294 +[[package]]
3295 +name = "smallvec"
3296 +version = "1.13.1"
3297 +source = "registry+https://github.com/rust-lang/crates.io-index"
3298 +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
3299 +
3300 +[[package]]
3301 +name = "socket2"
3302 +version = "0.5.6"
3303 +source = "registry+https://github.com/rust-lang/crates.io-index"
3304 +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
3305 +dependencies = [
3306 + "libc",
3307 + "windows-sys 0.52.0",
3308 +]
3309 +
3310 +[[package]]
3311 +name = "soup2"
3312 +version = "0.2.1"
3313 +source = "registry+https://github.com/rust-lang/crates.io-index"
3314 +checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0"
3315 +dependencies = [
3316 + "bitflags 1.3.2",
3317 + "gio",
3318 + "glib",
3319 + "libc",
3320 + "once_cell",
3321 + "soup2-sys",
3322 +]
3323 +
3324 +[[package]]
3325 +name = "soup2-sys"
3326 +version = "0.2.0"
3327 +source = "registry+https://github.com/rust-lang/crates.io-index"
3328 +checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf"
3329 +dependencies = [
3330 + "bitflags 1.3.2",
3331 + "gio-sys",
3332 + "glib-sys",
3333 + "gobject-sys",
3334 + "libc",
3335 + "system-deps 5.0.0",
3336 +]
3337 +
3338 +[[package]]
3339 +name = "stable_deref_trait"
3340 +version = "1.2.0"
3341 +source = "registry+https://github.com/rust-lang/crates.io-index"
3342 +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
3343 +
3344 +[[package]]
3345 +name = "state"
3346 +version = "0.5.3"
3347 +source = "registry+https://github.com/rust-lang/crates.io-index"
3348 +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b"
3349 +dependencies = [
3350 + "loom",
3351 +]
3352 +
3353 +[[package]]
3354 +name = "string_cache"
3355 +version = "0.8.7"
3356 +source = "registry+https://github.com/rust-lang/crates.io-index"
3357 +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
3358 +dependencies = [
3359 + "new_debug_unreachable",
3360 + "once_cell",
3361 + "parking_lot",
3362 + "phf_shared 0.10.0",
3363 + "precomputed-hash",
3364 + "serde",
3365 +]
3366 +
3367 +[[package]]
3368 +name = "string_cache_codegen"
3369 +version = "0.5.2"
3370 +source = "registry+https://github.com/rust-lang/crates.io-index"
3371 +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
3372 +dependencies = [
3373 + "phf_generator 0.10.0",
3374 + "phf_shared 0.10.0",
3375 + "proc-macro2",
3376 + "quote",
3377 +]
3378 +
3379 +[[package]]
3380 +name = "strsim"
3381 +version = "0.10.0"
3382 +source = "registry+https://github.com/rust-lang/crates.io-index"
3383 +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
3384 +
3385 +[[package]]
3386 +name = "symphonia"
3387 +version = "0.5.4"
3388 +source = "registry+https://github.com/rust-lang/crates.io-index"
3389 +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
3390 +dependencies = [
3391 + "lazy_static",
3392 + "symphonia-bundle-mp3",
3393 + "symphonia-core",
3394 + "symphonia-metadata",
3395 +]
3396 +
3397 +[[package]]
3398 +name = "symphonia-bundle-mp3"
3399 +version = "0.5.4"
3400 +source = "registry+https://github.com/rust-lang/crates.io-index"
3401 +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
3402 +dependencies = [
3403 + "lazy_static",
3404 + "log",
3405 + "symphonia-core",
3406 + "symphonia-metadata",
3407 +]
3408 +
3409 +[[package]]
3410 +name = "symphonia-core"
3411 +version = "0.5.4"
3412 +source = "registry+https://github.com/rust-lang/crates.io-index"
3413 +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
3414 +dependencies = [
3415 + "arrayvec",
3416 + "bitflags 1.3.2",
3417 + "bytemuck",
3418 + "lazy_static",
3419 + "log",
3420 +]
3421 +
3422 +[[package]]
3423 +name = "symphonia-metadata"
3424 +version = "0.5.4"
3425 +source = "registry+https://github.com/rust-lang/crates.io-index"
3426 +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
3427 +dependencies = [
3428 + "encoding_rs",
3429 + "lazy_static",
3430 + "log",
3431 + "symphonia-core",
3432 +]
3433 +
3434 +[[package]]
3435 +name = "syn"
3436 +version = "1.0.109"
3437 +source = "registry+https://github.com/rust-lang/crates.io-index"
3438 +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
3439 +dependencies = [
3440 + "proc-macro2",
3441 + "quote",
3442 + "unicode-ident",
3443 +]
3444 +
3445 +[[package]]
3446 +name = "syn"
3447 +version = "2.0.53"
3448 +source = "registry+https://github.com/rust-lang/crates.io-index"
3449 +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
3450 +dependencies = [
3451 + "proc-macro2",
3452 + "quote",
3453 + "unicode-ident",
3454 +]
3455 +
3456 +[[package]]
3457 +name = "syn_derive"
3458 +version = "0.1.8"
3459 +source = "registry+https://github.com/rust-lang/crates.io-index"
3460 +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b"
3461 +dependencies = [
3462 + "proc-macro-error",
3463 + "proc-macro2",
3464 + "quote",
3465 + "syn 2.0.53",
3466 +]
3467 +
3468 +[[package]]
3469 +name = "sync_wrapper"
3470 +version = "0.1.2"
3471 +source = "registry+https://github.com/rust-lang/crates.io-index"
3472 +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
3473 +
3474 +[[package]]
3475 +name = "system-configuration"
3476 +version = "0.5.1"
3477 +source = "registry+https://github.com/rust-lang/crates.io-index"
3478 +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
3479 +dependencies = [
3480 + "bitflags 1.3.2",
3481 + "core-foundation",
3482 + "system-configuration-sys",
3483 +]
3484 +
3485 +[[package]]
3486 +name = "system-configuration-sys"
3487 +version = "0.5.0"
3488 +source = "registry+https://github.com/rust-lang/crates.io-index"
3489 +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
3490 +dependencies = [
3491 + "core-foundation-sys",
3492 + "libc",
3493 +]
3494 +
3495 +[[package]]
3496 +name = "system-deps"
3497 +version = "5.0.0"
3498 +source = "registry+https://github.com/rust-lang/crates.io-index"
3499 +checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e"
3500 +dependencies = [
3501 + "cfg-expr 0.9.1",
3502 + "heck 0.3.3",
3503 + "pkg-config",
3504 + "toml 0.5.11",
3505 + "version-compare 0.0.11",
3506 +]
3507 +
3508 +[[package]]
3509 +name = "system-deps"
3510 +version = "6.2.1"
3511 +source = "registry+https://github.com/rust-lang/crates.io-index"
3512 +checksum = "e8e9199467bcbc77c6a13cc6e32a6af21721ab8c96aa0261856c4fda5a4433f0"
3513 +dependencies = [
3514 + "cfg-expr 0.15.7",
3515 + "heck 0.4.1",
3516 + "pkg-config",
3517 + "toml 0.8.11",
3518 + "version-compare 0.1.1",
3519 +]
3520 +
3521 +[[package]]
3522 +name = "tao"
3523 +version = "0.16.7"
3524 +source = "registry+https://github.com/rust-lang/crates.io-index"
3525 +checksum = "d22205b267a679ca1c590b9f178488d50981fc3e48a1b91641ae31593db875ce"
3526 +dependencies = [
3527 + "bitflags 1.3.2",
3528 + "cairo-rs",
3529 + "cc",
3530 + "cocoa",
3531 + "core-foundation",
3532 + "core-graphics",
3533 + "crossbeam-channel",
3534 + "dispatch",
3535 + "gdk",
3536 + "gdk-pixbuf",
3537 + "gdk-sys",
3538 + "gdkwayland-sys",
3539 + "gdkx11-sys",
3540 + "gio",
3541 + "glib",
3542 + "glib-sys",
3543 + "gtk",
3544 + "image",
3545 + "instant",
3546 + "jni 0.20.0",
3547 + "lazy_static",
3548 + "libc",
3549 + "log",
3550 + "ndk 0.6.0",
3551 + "ndk-context",
3552 + "ndk-sys 0.3.0",
3553 + "objc",
3554 + "once_cell",
3555 + "parking_lot",
3556 + "png",
3557 + "raw-window-handle",
3558 + "scopeguard",
3559 + "serde",
3560 + "tao-macros",
3561 + "unicode-segmentation",
3562 + "uuid",
3563 + "windows 0.39.0",
3564 + "windows-implement",
3565 + "x11-dl",
3566 +]
3567 +
3568 +[[package]]
3569 +name = "tao-macros"
3570 +version = "0.1.2"
3571 +source = "registry+https://github.com/rust-lang/crates.io-index"
3572 +checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2"
3573 +dependencies = [
3574 + "proc-macro2",
3575 + "quote",
3576 + "syn 1.0.109",
3577 +]
3578 +
3579 +[[package]]
3580 +name = "tap"
3581 +version = "1.0.1"
3582 +source = "registry+https://github.com/rust-lang/crates.io-index"
3583 +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
3584 +
3585 +[[package]]
3586 +name = "tar"
3587 +version = "0.4.40"
3588 +source = "registry+https://github.com/rust-lang/crates.io-index"
3589 +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
3590 +dependencies = [
3591 + "filetime",
3592 + "libc",
3593 + "xattr",
3594 +]
3595 +
3596 +[[package]]
3597 +name = "target-lexicon"
3598 +version = "0.12.14"
3599 +source = "registry+https://github.com/rust-lang/crates.io-index"
3600 +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
3601 +
3602 +[[package]]
3603 +name = "tauri"
3604 +version = "1.7.1"
3605 +source = "registry+https://github.com/rust-lang/crates.io-index"
3606 +checksum = "336bc661a3f3250853fa83c6e5245449ed1c26dce5dcb28bdee7efedf6278806"
3607 +dependencies = [
3608 + "anyhow",
3609 + "cocoa",
3610 + "dirs-next",
3611 + "dunce",
3612 + "embed_plist",
3613 + "encoding_rs",
3614 + "flate2",
3615 + "futures-util",
3616 + "getrandom 0.2.12",
3617 + "glib",
3618 + "glob",
3619 + "gtk",
3620 + "heck 0.5.0",
3621 + "http",
3622 + "ignore",
3623 + "objc",
3624 + "once_cell",
3625 + "percent-encoding",
3626 + "rand 0.8.5",
3627 + "raw-window-handle",
3628 + "semver",
3629 + "serde",
3630 + "serde_json",
3631 + "serde_repr",
3632 + "serialize-to-javascript",
3633 + "state",
3634 + "tar",
3635 + "tauri-macros",
3636 + "tauri-runtime",
3637 + "tauri-runtime-wry",
3638 + "tauri-utils",
3639 + "tempfile",
3640 + "thiserror",
3641 + "tokio",
3642 + "url",
3643 + "uuid",
3644 + "webkit2gtk",
3645 + "webview2-com",
3646 + "windows 0.39.0",
3647 +]
3648 +
3649 +[[package]]
3650 +name = "tauri-build"
3651 +version = "1.5.1"
3652 +source = "registry+https://github.com/rust-lang/crates.io-index"
3653 +checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532"
3654 +dependencies = [
3655 + "anyhow",
3656 + "cargo_toml",
3657 + "dirs-next",
3658 + "heck 0.4.1",
3659 + "json-patch",
3660 + "semver",
3661 + "serde",
3662 + "serde_json",
3663 + "tauri-utils",
3664 + "tauri-winres",
3665 + "walkdir",
3666 +]
3667 +
3668 +[[package]]
3669 +name = "tauri-codegen"
3670 +version = "1.4.4"
3671 +source = "registry+https://github.com/rust-lang/crates.io-index"
3672 +checksum = "c1aed706708ff1200ec12de9cfbf2582b5d8ec05f6a7293911091effbd22036b"
3673 +dependencies = [
3674 + "base64 0.21.7",
3675 + "brotli",
3676 + "ico",
3677 + "json-patch",
3678 + "plist",
3679 + "png",
3680 + "proc-macro2",
3681 + "quote",
3682 + "semver",
3683 + "serde",
3684 + "serde_json",
3685 + "sha2",
3686 + "tauri-utils",
3687 + "thiserror",
3688 + "time",
3689 + "uuid",
3690 + "walkdir",
3691 +]
3692 +
3693 +[[package]]
3694 +name = "tauri-macros"
3695 +version = "1.4.5"
3696 +source = "registry+https://github.com/rust-lang/crates.io-index"
3697 +checksum = "b88f831d2973ae4f81a706a0004e67dac87f2e4439973bbe98efbd73825d8ede"
3698 +dependencies = [
3699 + "heck 0.5.0",
3700 + "proc-macro2",
3701 + "quote",
3702 + "syn 1.0.109",
3703 + "tauri-codegen",
3704 + "tauri-utils",
3705 +]
3706 +
3707 +[[package]]
3708 +name = "tauri-plugin-log"
3709 +version = "0.0.0"
3710 +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6a6c9daeb261a29707c9d011346c8b7746ae6f54"
3711 +dependencies = [
3712 + "byte-unit",
3713 + "fern",
3714 + "log",
3715 + "serde",
3716 + "serde_json",
3717 + "serde_repr",
3718 + "tauri",
3719 + "time",
3720 +]
3721 +
3722 +[[package]]
3723 +name = "tauri-runtime"
3724 +version = "0.14.4"
3725 +source = "registry+https://github.com/rust-lang/crates.io-index"
3726 +checksum = "3068ed62b63dedc705558f4248c7ecbd5561f0f8050949859ea0db2326f26012"
3727 +dependencies = [
3728 + "gtk",
3729 + "http",
3730 + "http-range",
3731 + "rand 0.8.5",
3732 + "raw-window-handle",
3733 + "serde",
3734 + "serde_json",
3735 + "tauri-utils",
3736 + "thiserror",
3737 + "url",
3738 + "uuid",
3739 + "webview2-com",
3740 + "windows 0.39.0",
3741 +]
3742 +
3743 +[[package]]
3744 +name = "tauri-runtime-wry"
3745 +version = "0.14.9"
3746 +source = "registry+https://github.com/rust-lang/crates.io-index"
3747 +checksum = "d4c3db170233096aa30330feadcd895bf9317be97e624458560a20e814db7955"
3748 +dependencies = [
3749 + "cocoa",
3750 + "gtk",
3751 + "percent-encoding",
3752 + "rand 0.8.5",
3753 + "raw-window-handle",
3754 + "tauri-runtime",
3755 + "tauri-utils",
3756 + "uuid",
3757 + "webkit2gtk",
3758 + "webview2-com",
3759 + "windows 0.39.0",
3760 + "wry",
3761 +]
3762 +
3763 +[[package]]
3764 +name = "tauri-utils"
3765 +version = "1.6.0"
3766 +source = "registry+https://github.com/rust-lang/crates.io-index"
3767 +checksum = "2826db448309d382dac14d520f0c0a40839b87b57b977e59cf5f296b3ace6a93"
3768 +dependencies = [
3769 + "brotli",
3770 + "ctor",
3771 + "dunce",
3772 + "glob",
3773 + "heck 0.5.0",
3774 + "html5ever",
3775 + "infer",
3776 + "json-patch",
3777 + "kuchikiki",
3778 + "log",
3779 + "memchr",
3780 + "phf 0.11.2",
3781 + "proc-macro2",
3782 + "quote",
3783 + "semver",
3784 + "serde",
3785 + "serde_json",
3786 + "serde_with",
3787 + "thiserror",
3788 + "url",
3789 + "walkdir",
3790 + "windows-version",
3791 +]
3792 +
3793 +[[package]]
3794 +name = "tauri-winres"
3795 +version = "0.1.1"
3796 +source = "registry+https://github.com/rust-lang/crates.io-index"
3797 +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb"
3798 +dependencies = [
3799 + "embed-resource",
3800 + "toml 0.7.8",
3801 +]
3802 +
3803 +[[package]]
3804 +name = "tempfile"
3805 +version = "3.10.1"
3806 +source = "registry+https://github.com/rust-lang/crates.io-index"
3807 +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
3808 +dependencies = [
3809 + "cfg-if",
3810 + "fastrand",
3811 + "rustix",
3812 + "windows-sys 0.52.0",
3813 +]
3814 +
3815 +[[package]]
3816 +name = "tendril"
3817 +version = "0.4.3"
3818 +source = "registry+https://github.com/rust-lang/crates.io-index"
3819 +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
3820 +dependencies = [
3821 + "futf",
3822 + "mac",
3823 + "utf-8",
3824 +]
3825 +
3826 +[[package]]
3827 +name = "thin-slice"
3828 +version = "0.1.1"
3829 +source = "registry+https://github.com/rust-lang/crates.io-index"
3830 +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
3831 +
3832 +[[package]]
3833 +name = "thiserror"
3834 +version = "1.0.58"
3835 +source = "registry+https://github.com/rust-lang/crates.io-index"
3836 +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
3837 +dependencies = [
3838 + "thiserror-impl",
3839 +]
3840 +
3841 +[[package]]
3842 +name = "thiserror-impl"
3843 +version = "1.0.58"
3844 +source = "registry+https://github.com/rust-lang/crates.io-index"
3845 +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
3846 +dependencies = [
3847 + "proc-macro2",
3848 + "quote",
3849 + "syn 2.0.53",
3850 +]
3851 +
3852 +[[package]]
3853 +name = "thread_local"
3854 +version = "1.1.8"
3855 +source = "registry+https://github.com/rust-lang/crates.io-index"
3856 +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
3857 +dependencies = [
3858 + "cfg-if",
3859 + "once_cell",
3860 +]
3861 +
3862 +[[package]]
3863 +name = "time"
3864 +version = "0.3.34"
3865 +source = "registry+https://github.com/rust-lang/crates.io-index"
3866 +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
3867 +dependencies = [
3868 + "deranged",
3869 + "itoa 1.0.10",
3870 + "libc",
3871 + "num-conv",
3872 + "num_threads",
3873 + "powerfmt",
3874 + "serde",
3875 + "time-core",
3876 + "time-macros",
3877 +]
3878 +
3879 +[[package]]
3880 +name = "time-core"
3881 +version = "0.1.2"
3882 +source = "registry+https://github.com/rust-lang/crates.io-index"
3883 +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
3884 +
3885 +[[package]]
3886 +name = "time-macros"
3887 +version = "0.2.17"
3888 +source = "registry+https://github.com/rust-lang/crates.io-index"
3889 +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
3890 +dependencies = [
3891 + "num-conv",
3892 + "time-core",
3893 +]
3894 +
3895 +[[package]]
3896 +name = "tinyvec"
3897 +version = "1.6.0"
3898 +source = "registry+https://github.com/rust-lang/crates.io-index"
3899 +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
3900 +dependencies = [
3901 + "tinyvec_macros",
3902 +]
3903 +
3904 +[[package]]
3905 +name = "tinyvec_macros"
3906 +version = "0.1.1"
3907 +source = "registry+https://github.com/rust-lang/crates.io-index"
3908 +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
3909 +
3910 +[[package]]
3911 +name = "tokio"
3912 +version = "1.36.0"
3913 +source = "registry+https://github.com/rust-lang/crates.io-index"
3914 +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
3915 +dependencies = [
3916 + "backtrace",
3917 + "bytes",
3918 + "libc",
3919 + "mio",
3920 + "num_cpus",
3921 + "pin-project-lite",
3922 + "socket2",
3923 + "windows-sys 0.48.0",
3924 +]
3925 +
3926 +[[package]]
3927 +name = "tokio-native-tls"
3928 +version = "0.3.1"
3929 +source = "registry+https://github.com/rust-lang/crates.io-index"
3930 +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
3931 +dependencies = [
3932 + "native-tls",
3933 + "tokio",
3934 +]
3935 +
3936 +[[package]]
3937 +name = "tokio-util"
3938 +version = "0.7.10"
3939 +source = "registry+https://github.com/rust-lang/crates.io-index"
3940 +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
3941 +dependencies = [
3942 + "bytes",
3943 + "futures-core",
3944 + "futures-sink",
3945 + "pin-project-lite",
3946 + "tokio",
3947 + "tracing",
3948 +]
3949 +
3950 +[[package]]
3951 +name = "toml"
3952 +version = "0.5.11"
3953 +source = "registry+https://github.com/rust-lang/crates.io-index"
3954 +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
3955 +dependencies = [
3956 + "serde",
3957 +]
3958 +
3959 +[[package]]
3960 +name = "toml"
3961 +version = "0.7.8"
3962 +source = "registry+https://github.com/rust-lang/crates.io-index"
3963 +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
3964 +dependencies = [
3965 + "serde",
3966 + "serde_spanned",
3967 + "toml_datetime",
3968 + "toml_edit 0.19.15",
3969 +]
3970 +
3971 +[[package]]
3972 +name = "toml"
3973 +version = "0.8.11"
3974 +source = "registry+https://github.com/rust-lang/crates.io-index"
3975 +checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e"
3976 +dependencies = [
3977 + "serde",
3978 + "serde_spanned",
3979 + "toml_datetime",
3980 + "toml_edit 0.22.7",
3981 +]
3982 +
3983 +[[package]]
3984 +name = "toml_datetime"
3985 +version = "0.6.5"
3986 +source = "registry+https://github.com/rust-lang/crates.io-index"
3987 +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
3988 +dependencies = [
3989 + "serde",
3990 +]
3991 +
3992 +[[package]]
3993 +name = "toml_edit"
3994 +version = "0.19.15"
3995 +source = "registry+https://github.com/rust-lang/crates.io-index"
3996 +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
3997 +dependencies = [
3998 + "indexmap 2.2.5",
3999 + "serde",
4000 + "serde_spanned",
4001 + "toml_datetime",
4002 + "winnow 0.5.40",
4003 +]
4004 +
4005 +[[package]]
4006 +name = "toml_edit"
4007 +version = "0.21.1"
4008 +source = "registry+https://github.com/rust-lang/crates.io-index"
4009 +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
4010 +dependencies = [
4011 + "indexmap 2.2.5",
4012 + "toml_datetime",
4013 + "winnow 0.5.40",
4014 +]
4015 +
4016 +[[package]]
4017 +name = "toml_edit"
4018 +version = "0.22.7"
4019 +source = "registry+https://github.com/rust-lang/crates.io-index"
4020 +checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992"
4021 +dependencies = [
4022 + "indexmap 2.2.5",
4023 + "serde",
4024 + "serde_spanned",
4025 + "toml_datetime",
4026 + "winnow 0.6.5",
4027 +]
4028 +
4029 +[[package]]
4030 +name = "tower-service"
4031 +version = "0.3.2"
4032 +source = "registry+https://github.com/rust-lang/crates.io-index"
4033 +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
4034 +
4035 +[[package]]
4036 +name = "tracing"
4037 +version = "0.1.40"
4038 +source = "registry+https://github.com/rust-lang/crates.io-index"
4039 +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
4040 +dependencies = [
4041 + "pin-project-lite",
4042 + "tracing-attributes",
4043 + "tracing-core",
4044 +]
4045 +
4046 +[[package]]
4047 +name = "tracing-attributes"
4048 +version = "0.1.27"
4049 +source = "registry+https://github.com/rust-lang/crates.io-index"
4050 +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
4051 +dependencies = [
4052 + "proc-macro2",
4053 + "quote",
4054 + "syn 2.0.53",
4055 +]
4056 +
4057 +[[package]]
4058 +name = "tracing-core"
4059 +version = "0.1.32"
4060 +source = "registry+https://github.com/rust-lang/crates.io-index"
4061 +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
4062 +dependencies = [
4063 + "once_cell",
4064 + "valuable",
4065 +]
4066 +
4067 +[[package]]
4068 +name = "tracing-log"
4069 +version = "0.2.0"
4070 +source = "registry+https://github.com/rust-lang/crates.io-index"
4071 +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
4072 +dependencies = [
4073 + "log",
4074 + "once_cell",
4075 + "tracing-core",
4076 +]
4077 +
4078 +[[package]]
4079 +name = "tracing-subscriber"
4080 +version = "0.3.18"
4081 +source = "registry+https://github.com/rust-lang/crates.io-index"
4082 +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
4083 +dependencies = [
4084 + "matchers",
4085 + "nu-ansi-term",
4086 + "once_cell",
4087 + "regex",
4088 + "sharded-slab",
4089 + "smallvec",
4090 + "thread_local",
4091 + "tracing",
4092 + "tracing-core",
4093 + "tracing-log",
4094 +]
4095 +
4096 +[[package]]
4097 +name = "treediff"
4098 +version = "4.0.3"
4099 +source = "registry+https://github.com/rust-lang/crates.io-index"
4100 +checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5"
4101 +dependencies = [
4102 + "serde_json",
4103 +]
4104 +
4105 +[[package]]
4106 +name = "try-lock"
4107 +version = "0.2.5"
4108 +source = "registry+https://github.com/rust-lang/crates.io-index"
4109 +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
4110 +
4111 +[[package]]
4112 +name = "typenum"
4113 +version = "1.17.0"
4114 +source = "registry+https://github.com/rust-lang/crates.io-index"
4115 +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
4116 +
4117 +[[package]]
4118 +name = "unicode-bidi"
4119 +version = "0.3.15"
4120 +source = "registry+https://github.com/rust-lang/crates.io-index"
4121 +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
4122 +
4123 +[[package]]
4124 +name = "unicode-ident"
4125 +version = "1.0.12"
4126 +source = "registry+https://github.com/rust-lang/crates.io-index"
4127 +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
4128 +
4129 +[[package]]
4130 +name = "unicode-normalization"
4131 +version = "0.1.23"
4132 +source = "registry+https://github.com/rust-lang/crates.io-index"
4133 +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
4134 +dependencies = [
4135 + "tinyvec",
4136 +]
4137 +
4138 +[[package]]
4139 +name = "unicode-segmentation"
4140 +version = "1.11.0"
4141 +source = "registry+https://github.com/rust-lang/crates.io-index"
4142 +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
4143 +
4144 +[[package]]
4145 +name = "url"
4146 +version = "2.5.0"
4147 +source = "registry+https://github.com/rust-lang/crates.io-index"
4148 +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
4149 +dependencies = [
4150 + "form_urlencoded",
4151 + "idna",
4152 + "percent-encoding",
4153 + "serde",
4154 +]
4155 +
4156 +[[package]]
4157 +name = "url-builder"
4158 +version = "0.1.1"
4159 +source = "registry+https://github.com/rust-lang/crates.io-index"
4160 +checksum = "6c560d19163937c53680c515b82a9311c5a796691c67890b8ad579423123e194"
4161 +
4162 +[[package]]
4163 +name = "utf-8"
4164 +version = "0.7.6"
4165 +source = "registry+https://github.com/rust-lang/crates.io-index"
4166 +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
4167 +
4168 +[[package]]
4169 +name = "utf8-width"
4170 +version = "0.1.7"
4171 +source = "registry+https://github.com/rust-lang/crates.io-index"
4172 +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
4173 +
4174 +[[package]]
4175 +name = "uuid"
4176 +version = "1.7.0"
4177 +source = "registry+https://github.com/rust-lang/crates.io-index"
4178 +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
4179 +dependencies = [
4180 + "getrandom 0.2.12",
4181 +]
4182 +
4183 +[[package]]
4184 +name = "valuable"
4185 +version = "0.1.0"
4186 +source = "registry+https://github.com/rust-lang/crates.io-index"
4187 +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
4188 +
4189 +[[package]]
4190 +name = "value-bag"
4191 +version = "1.8.0"
4192 +source = "registry+https://github.com/rust-lang/crates.io-index"
4193 +checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d"
4194 +
4195 +[[package]]
4196 +name = "vcpkg"
4197 +version = "0.2.15"
4198 +source = "registry+https://github.com/rust-lang/crates.io-index"
4199 +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
4200 +
4201 +[[package]]
4202 +name = "version-compare"
4203 +version = "0.0.11"
4204 +source = "registry+https://github.com/rust-lang/crates.io-index"
4205 +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b"
4206 +
4207 +[[package]]
4208 +name = "version-compare"
4209 +version = "0.1.1"
4210 +source = "registry+https://github.com/rust-lang/crates.io-index"
4211 +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
4212 +
4213 +[[package]]
4214 +name = "version_check"
4215 +version = "0.9.4"
4216 +source = "registry+https://github.com/rust-lang/crates.io-index"
4217 +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
4218 +
4219 +[[package]]
4220 +name = "vswhom"
4221 +version = "0.1.0"
4222 +source = "registry+https://github.com/rust-lang/crates.io-index"
4223 +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
4224 +dependencies = [
4225 + "libc",
4226 + "vswhom-sys",
4227 +]
4228 +
4229 +[[package]]
4230 +name = "vswhom-sys"
4231 +version = "0.1.2"
4232 +source = "registry+https://github.com/rust-lang/crates.io-index"
4233 +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18"
4234 +dependencies = [
4235 + "cc",
4236 + "libc",
4237 +]
4238 +
4239 +[[package]]
4240 +name = "walkdir"
4241 +version = "2.5.0"
4242 +source = "registry+https://github.com/rust-lang/crates.io-index"
4243 +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
4244 +dependencies = [
4245 + "same-file",
4246 + "winapi-util",
4247 +]
4248 +
4249 +[[package]]
4250 +name = "want"
4251 +version = "0.3.1"
4252 +source = "registry+https://github.com/rust-lang/crates.io-index"
4253 +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
4254 +dependencies = [
4255 + "try-lock",
4256 +]
4257 +
4258 +[[package]]
4259 +name = "wasi"
4260 +version = "0.9.0+wasi-snapshot-preview1"
4261 +source = "registry+https://github.com/rust-lang/crates.io-index"
4262 +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
4263 +
4264 +[[package]]
4265 +name = "wasi"
4266 +version = "0.11.0+wasi-snapshot-preview1"
4267 +source = "registry+https://github.com/rust-lang/crates.io-index"
4268 +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
4269 +
4270 +[[package]]
4271 +name = "wasm-bindgen"
4272 +version = "0.2.92"
4273 +source = "registry+https://github.com/rust-lang/crates.io-index"
4274 +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
4275 +dependencies = [
4276 + "cfg-if",
4277 + "wasm-bindgen-macro",
4278 +]
4279 +
4280 +[[package]]
4281 +name = "wasm-bindgen-backend"
4282 +version = "0.2.92"
4283 +source = "registry+https://github.com/rust-lang/crates.io-index"
4284 +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
4285 +dependencies = [
4286 + "bumpalo",
4287 + "log",
4288 + "once_cell",
4289 + "proc-macro2",
4290 + "quote",
4291 + "syn 2.0.53",
4292 + "wasm-bindgen-shared",
4293 +]
4294 +
4295 +[[package]]
4296 +name = "wasm-bindgen-futures"
4297 +version = "0.4.42"
4298 +source = "registry+https://github.com/rust-lang/crates.io-index"
4299 +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
4300 +dependencies = [
4301 + "cfg-if",
4302 + "js-sys",
4303 + "wasm-bindgen",
4304 + "web-sys",
4305 +]
4306 +
4307 +[[package]]
4308 +name = "wasm-bindgen-macro"
4309 +version = "0.2.92"
4310 +source = "registry+https://github.com/rust-lang/crates.io-index"
4311 +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
4312 +dependencies = [
4313 + "quote",
4314 + "wasm-bindgen-macro-support",
4315 +]
4316 +
4317 +[[package]]
4318 +name = "wasm-bindgen-macro-support"
4319 +version = "0.2.92"
4320 +source = "registry+https://github.com/rust-lang/crates.io-index"
4321 +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
4322 +dependencies = [
4323 + "proc-macro2",
4324 + "quote",
4325 + "syn 2.0.53",
4326 + "wasm-bindgen-backend",
4327 + "wasm-bindgen-shared",
4328 +]
4329 +
4330 +[[package]]
4331 +name = "wasm-bindgen-shared"
4332 +version = "0.2.92"
4333 +source = "registry+https://github.com/rust-lang/crates.io-index"
4334 +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
4335 +
4336 +[[package]]
4337 +name = "web-sys"
4338 +version = "0.3.69"
4339 +source = "registry+https://github.com/rust-lang/crates.io-index"
4340 +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
4341 +dependencies = [
4342 + "js-sys",
4343 + "wasm-bindgen",
4344 +]
4345 +
4346 +[[package]]
4347 +name = "webkit2gtk"
4348 +version = "0.18.2"
4349 +source = "registry+https://github.com/rust-lang/crates.io-index"
4350 +checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
4351 +dependencies = [
4352 + "bitflags 1.3.2",
4353 + "cairo-rs",
4354 + "gdk",
4355 + "gdk-sys",
4356 + "gio",
4357 + "gio-sys",
4358 + "glib",
4359 + "glib-sys",
4360 + "gobject-sys",
4361 + "gtk",
4362 + "gtk-sys",
4363 + "javascriptcore-rs",
4364 + "libc",
4365 + "once_cell",
4366 + "soup2",
4367 + "webkit2gtk-sys",
4368 +]
4369 +
4370 +[[package]]
4371 +name = "webkit2gtk-sys"
4372 +version = "0.18.0"
4373 +source = "registry+https://github.com/rust-lang/crates.io-index"
4374 +checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3"
4375 +dependencies = [
4376 + "atk-sys",
4377 + "bitflags 1.3.2",
4378 + "cairo-sys-rs",
4379 + "gdk-pixbuf-sys",
4380 + "gdk-sys",
4381 + "gio-sys",
4382 + "glib-sys",
4383 + "gobject-sys",
4384 + "gtk-sys",
4385 + "javascriptcore-rs-sys",
4386 + "libc",
4387 + "pango-sys",
4388 + "pkg-config",
4389 + "soup2-sys",
4390 + "system-deps 6.2.1",
4391 +]
4392 +
4393 +[[package]]
4394 +name = "webview2-com"
4395 +version = "0.19.1"
4396 +source = "registry+https://github.com/rust-lang/crates.io-index"
4397 +checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178"
4398 +dependencies = [
4399 + "webview2-com-macros",
4400 + "webview2-com-sys",
4401 + "windows 0.39.0",
4402 + "windows-implement",
4403 +]
4404 +
4405 +[[package]]
4406 +name = "webview2-com-macros"
4407 +version = "0.6.0"
4408 +source = "registry+https://github.com/rust-lang/crates.io-index"
4409 +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac"
4410 +dependencies = [
4411 + "proc-macro2",
4412 + "quote",
4413 + "syn 1.0.109",
4414 +]
4415 +
4416 +[[package]]
4417 +name = "webview2-com-sys"
4418 +version = "0.19.0"
4419 +source = "registry+https://github.com/rust-lang/crates.io-index"
4420 +checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7"
4421 +dependencies = [
4422 + "regex",
4423 + "serde",
4424 + "serde_json",
4425 + "thiserror",
4426 + "windows 0.39.0",
4427 + "windows-bindgen",
4428 + "windows-metadata",
4429 +]
4430 +
4431 +[[package]]
4432 +name = "winapi"
4433 +version = "0.3.9"
4434 +source = "registry+https://github.com/rust-lang/crates.io-index"
4435 +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
4436 +dependencies = [
4437 + "winapi-i686-pc-windows-gnu",
4438 + "winapi-x86_64-pc-windows-gnu",
4439 +]
4440 +
4441 +[[package]]
4442 +name = "winapi-i686-pc-windows-gnu"
4443 +version = "0.4.0"
4444 +source = "registry+https://github.com/rust-lang/crates.io-index"
4445 +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
4446 +
4447 +[[package]]
4448 +name = "winapi-util"
4449 +version = "0.1.6"
4450 +source = "registry+https://github.com/rust-lang/crates.io-index"
4451 +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
4452 +dependencies = [
4453 + "winapi",
4454 +]
4455 +
4456 +[[package]]
4457 +name = "winapi-x86_64-pc-windows-gnu"
4458 +version = "0.4.0"
4459 +source = "registry+https://github.com/rust-lang/crates.io-index"
4460 +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
4461 +
4462 +[[package]]
4463 +name = "windows"
4464 +version = "0.39.0"
4465 +source = "registry+https://github.com/rust-lang/crates.io-index"
4466 +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
4467 +dependencies = [
4468 + "windows-implement",
4469 + "windows_aarch64_msvc 0.39.0",
4470 + "windows_i686_gnu 0.39.0",
4471 + "windows_i686_msvc 0.39.0",
4472 + "windows_x86_64_gnu 0.39.0",
4473 + "windows_x86_64_msvc 0.39.0",
4474 +]
4475 +
4476 +[[package]]
4477 +name = "windows"
4478 +version = "0.48.0"
4479 +source = "registry+https://github.com/rust-lang/crates.io-index"
4480 +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
4481 +dependencies = [
4482 + "windows-targets 0.48.5",
4483 +]
4484 +
4485 +[[package]]
4486 +name = "windows"
4487 +version = "0.54.0"
4488 +source = "registry+https://github.com/rust-lang/crates.io-index"
4489 +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
4490 +dependencies = [
4491 + "windows-core 0.54.0",
4492 + "windows-targets 0.52.4",
4493 +]
4494 +
4495 +[[package]]
4496 +name = "windows-bindgen"
4497 +version = "0.39.0"
4498 +source = "registry+https://github.com/rust-lang/crates.io-index"
4499 +checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41"
4500 +dependencies = [
4501 + "windows-metadata",
4502 + "windows-tokens",
4503 +]
4504 +
4505 +[[package]]
4506 +name = "windows-core"
4507 +version = "0.52.0"
4508 +source = "registry+https://github.com/rust-lang/crates.io-index"
4509 +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
4510 +dependencies = [
4511 + "windows-targets 0.52.4",
4512 +]
4513 +
4514 +[[package]]
4515 +name = "windows-core"
4516 +version = "0.54.0"
4517 +source = "registry+https://github.com/rust-lang/crates.io-index"
4518 +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
4519 +dependencies = [
4520 + "windows-result",
4521 + "windows-targets 0.52.4",
4522 +]
4523 +
4524 +[[package]]
4525 +name = "windows-implement"
4526 +version = "0.39.0"
4527 +source = "registry+https://github.com/rust-lang/crates.io-index"
4528 +checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7"
4529 +dependencies = [
4530 + "syn 1.0.109",
4531 + "windows-tokens",
4532 +]
4533 +
4534 +[[package]]
4535 +name = "windows-metadata"
4536 +version = "0.39.0"
4537 +source = "registry+https://github.com/rust-lang/crates.io-index"
4538 +checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278"
4539 +
4540 +[[package]]
4541 +name = "windows-result"
4542 +version = "0.1.0"
4543 +source = "registry+https://github.com/rust-lang/crates.io-index"
4544 +checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64"
4545 +dependencies = [
4546 + "windows-targets 0.52.4",
4547 +]
4548 +
4549 +[[package]]
4550 +name = "windows-sys"
4551 +version = "0.45.0"
4552 +source = "registry+https://github.com/rust-lang/crates.io-index"
4553 +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
4554 +dependencies = [
4555 + "windows-targets 0.42.2",
4556 +]
4557 +
4558 +[[package]]
4559 +name = "windows-sys"
4560 +version = "0.48.0"
4561 +source = "registry+https://github.com/rust-lang/crates.io-index"
4562 +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
4563 +dependencies = [
4564 + "windows-targets 0.48.5",
4565 +]
4566 +
4567 +[[package]]
4568 +name = "windows-sys"
4569 +version = "0.52.0"
4570 +source = "registry+https://github.com/rust-lang/crates.io-index"
4571 +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
4572 +dependencies = [
4573 + "windows-targets 0.52.4",
4574 +]
4575 +
4576 +[[package]]
4577 +name = "windows-targets"
4578 +version = "0.42.2"
4579 +source = "registry+https://github.com/rust-lang/crates.io-index"
4580 +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
4581 +dependencies = [
4582 + "windows_aarch64_gnullvm 0.42.2",
4583 + "windows_aarch64_msvc 0.42.2",
4584 + "windows_i686_gnu 0.42.2",
4585 + "windows_i686_msvc 0.42.2",
4586 + "windows_x86_64_gnu 0.42.2",
4587 + "windows_x86_64_gnullvm 0.42.2",
4588 + "windows_x86_64_msvc 0.42.2",
4589 +]
4590 +
4591 +[[package]]
4592 +name = "windows-targets"
4593 +version = "0.48.5"
4594 +source = "registry+https://github.com/rust-lang/crates.io-index"
4595 +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
4596 +dependencies = [
4597 + "windows_aarch64_gnullvm 0.48.5",
4598 + "windows_aarch64_msvc 0.48.5",
4599 + "windows_i686_gnu 0.48.5",
4600 + "windows_i686_msvc 0.48.5",
4601 + "windows_x86_64_gnu 0.48.5",
4602 + "windows_x86_64_gnullvm 0.48.5",
4603 + "windows_x86_64_msvc 0.48.5",
4604 +]
4605 +
4606 +[[package]]
4607 +name = "windows-targets"
4608 +version = "0.52.4"
4609 +source = "registry+https://github.com/rust-lang/crates.io-index"
4610 +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
4611 +dependencies = [
4612 + "windows_aarch64_gnullvm 0.52.4",
4613 + "windows_aarch64_msvc 0.52.4",
4614 + "windows_i686_gnu 0.52.4",
4615 + "windows_i686_msvc 0.52.4",
4616 + "windows_x86_64_gnu 0.52.4",
4617 + "windows_x86_64_gnullvm 0.52.4",
4618 + "windows_x86_64_msvc 0.52.4",
4619 +]
4620 +
4621 +[[package]]
4622 +name = "windows-tokens"
4623 +version = "0.39.0"
4624 +source = "registry+https://github.com/rust-lang/crates.io-index"
4625 +checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
4626 +
4627 +[[package]]
4628 +name = "windows-version"
4629 +version = "0.1.0"
4630 +source = "registry+https://github.com/rust-lang/crates.io-index"
4631 +checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4"
4632 +dependencies = [
4633 + "windows-targets 0.52.4",
4634 +]
4635 +
4636 +[[package]]
4637 +name = "windows_aarch64_gnullvm"
4638 +version = "0.42.2"
4639 +source = "registry+https://github.com/rust-lang/crates.io-index"
4640 +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
4641 +
4642 +[[package]]
4643 +name = "windows_aarch64_gnullvm"
4644 +version = "0.48.5"
4645 +source = "registry+https://github.com/rust-lang/crates.io-index"
4646 +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
4647 +
4648 +[[package]]
4649 +name = "windows_aarch64_gnullvm"
4650 +version = "0.52.4"
4651 +source = "registry+https://github.com/rust-lang/crates.io-index"
4652 +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
4653 +
4654 +[[package]]
4655 +name = "windows_aarch64_msvc"
4656 +version = "0.39.0"
4657 +source = "registry+https://github.com/rust-lang/crates.io-index"
4658 +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
4659 +
4660 +[[package]]
4661 +name = "windows_aarch64_msvc"
4662 +version = "0.42.2"
4663 +source = "registry+https://github.com/rust-lang/crates.io-index"
4664 +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
4665 +
4666 +[[package]]
4667 +name = "windows_aarch64_msvc"
4668 +version = "0.48.5"
4669 +source = "registry+https://github.com/rust-lang/crates.io-index"
4670 +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
4671 +
4672 +[[package]]
4673 +name = "windows_aarch64_msvc"
4674 +version = "0.52.4"
4675 +source = "registry+https://github.com/rust-lang/crates.io-index"
4676 +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
4677 +
4678 +[[package]]
4679 +name = "windows_i686_gnu"
4680 +version = "0.39.0"
4681 +source = "registry+https://github.com/rust-lang/crates.io-index"
4682 +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
4683 +
4684 +[[package]]
4685 +name = "windows_i686_gnu"
4686 +version = "0.42.2"
4687 +source = "registry+https://github.com/rust-lang/crates.io-index"
4688 +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
4689 +
4690 +[[package]]
4691 +name = "windows_i686_gnu"
4692 +version = "0.48.5"
4693 +source = "registry+https://github.com/rust-lang/crates.io-index"
4694 +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
4695 +
4696 +[[package]]
4697 +name = "windows_i686_gnu"
4698 +version = "0.52.4"
4699 +source = "registry+https://github.com/rust-lang/crates.io-index"
4700 +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
4701 +
4702 +[[package]]
4703 +name = "windows_i686_msvc"
4704 +version = "0.39.0"
4705 +source = "registry+https://github.com/rust-lang/crates.io-index"
4706 +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
4707 +
4708 +[[package]]
4709 +name = "windows_i686_msvc"
4710 +version = "0.42.2"
4711 +source = "registry+https://github.com/rust-lang/crates.io-index"
4712 +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
4713 +
4714 +[[package]]
4715 +name = "windows_i686_msvc"
4716 +version = "0.48.5"
4717 +source = "registry+https://github.com/rust-lang/crates.io-index"
4718 +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
4719 +
4720 +[[package]]
4721 +name = "windows_i686_msvc"
4722 +version = "0.52.4"
4723 +source = "registry+https://github.com/rust-lang/crates.io-index"
4724 +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
4725 +
4726 +[[package]]
4727 +name = "windows_x86_64_gnu"
4728 +version = "0.39.0"
4729 +source = "registry+https://github.com/rust-lang/crates.io-index"
4730 +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
4731 +
4732 +[[package]]
4733 +name = "windows_x86_64_gnu"
4734 +version = "0.42.2"
4735 +source = "registry+https://github.com/rust-lang/crates.io-index"
4736 +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
4737 +
4738 +[[package]]
4739 +name = "windows_x86_64_gnu"
4740 +version = "0.48.5"
4741 +source = "registry+https://github.com/rust-lang/crates.io-index"
4742 +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
4743 +
4744 +[[package]]
4745 +name = "windows_x86_64_gnu"
4746 +version = "0.52.4"
4747 +source = "registry+https://github.com/rust-lang/crates.io-index"
4748 +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
4749 +
4750 +[[package]]
4751 +name = "windows_x86_64_gnullvm"
4752 +version = "0.42.2"
4753 +source = "registry+https://github.com/rust-lang/crates.io-index"
4754 +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
4755 +
4756 +[[package]]
4757 +name = "windows_x86_64_gnullvm"
4758 +version = "0.48.5"
4759 +source = "registry+https://github.com/rust-lang/crates.io-index"
4760 +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
4761 +
4762 +[[package]]
4763 +name = "windows_x86_64_gnullvm"
4764 +version = "0.52.4"
4765 +source = "registry+https://github.com/rust-lang/crates.io-index"
4766 +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
4767 +
4768 +[[package]]
4769 +name = "windows_x86_64_msvc"
4770 +version = "0.39.0"
4771 +source = "registry+https://github.com/rust-lang/crates.io-index"
4772 +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
4773 +
4774 +[[package]]
4775 +name = "windows_x86_64_msvc"
4776 +version = "0.42.2"
4777 +source = "registry+https://github.com/rust-lang/crates.io-index"
4778 +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
4779 +
4780 +[[package]]
4781 +name = "windows_x86_64_msvc"
4782 +version = "0.48.5"
4783 +source = "registry+https://github.com/rust-lang/crates.io-index"
4784 +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
4785 +
4786 +[[package]]
4787 +name = "windows_x86_64_msvc"
4788 +version = "0.52.4"
4789 +source = "registry+https://github.com/rust-lang/crates.io-index"
4790 +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
4791 +
4792 +[[package]]
4793 +name = "winnow"
4794 +version = "0.5.40"
4795 +source = "registry+https://github.com/rust-lang/crates.io-index"
4796 +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
4797 +dependencies = [
4798 + "memchr",
4799 +]
4800 +
4801 +[[package]]
4802 +name = "winnow"
4803 +version = "0.6.5"
4804 +source = "registry+https://github.com/rust-lang/crates.io-index"
4805 +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
4806 +dependencies = [
4807 + "memchr",
4808 +]
4809 +
4810 +[[package]]
4811 +name = "winreg"
4812 +version = "0.50.0"
4813 +source = "registry+https://github.com/rust-lang/crates.io-index"
4814 +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
4815 +dependencies = [
4816 + "cfg-if",
4817 + "windows-sys 0.48.0",
4818 +]
4819 +
4820 +[[package]]
4821 +name = "winreg"
4822 +version = "0.52.0"
4823 +source = "registry+https://github.com/rust-lang/crates.io-index"
4824 +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
4825 +dependencies = [
4826 + "cfg-if",
4827 + "windows-sys 0.48.0",
4828 +]
4829 +
4830 +[[package]]
4831 +name = "wry"
4832 +version = "0.24.10"
4833 +source = "registry+https://github.com/rust-lang/crates.io-index"
4834 +checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45"
4835 +dependencies = [
4836 + "base64 0.13.1",
4837 + "block",
4838 + "cocoa",
4839 + "core-graphics",
4840 + "crossbeam-channel",
4841 + "dunce",
4842 + "gdk",
4843 + "gio",
4844 + "glib",
4845 + "gtk",
4846 + "html5ever",
4847 + "http",
4848 + "kuchikiki",
4849 + "libc",
4850 + "log",
4851 + "objc",
4852 + "objc_id",
4853 + "once_cell",
4854 + "serde",
4855 + "serde_json",
4856 + "sha2",
4857 + "soup2",
4858 + "tao",
4859 + "thiserror",
4860 + "url",
4861 + "webkit2gtk",
4862 + "webkit2gtk-sys",
4863 + "webview2-com",
4864 + "windows 0.39.0",
4865 + "windows-implement",
4866 +]
4867 +
4868 +[[package]]
4869 +name = "wyz"
4870 +version = "0.5.1"
4871 +source = "registry+https://github.com/rust-lang/crates.io-index"
4872 +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
4873 +dependencies = [
4874 + "tap",
4875 +]
4876 +
4877 +[[package]]
4878 +name = "x11"
4879 +version = "2.21.0"
4880 +source = "registry+https://github.com/rust-lang/crates.io-index"
4881 +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
4882 +dependencies = [
4883 + "libc",
4884 + "pkg-config",
4885 +]
4886 +
4887 +[[package]]
4888 +name = "x11-dl"
4889 +version = "2.21.0"
4890 +source = "registry+https://github.com/rust-lang/crates.io-index"
4891 +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
4892 +dependencies = [
4893 + "libc",
4894 + "once_cell",
4895 + "pkg-config",
4896 +]
4897 +
4898 +[[package]]
4899 +name = "xattr"
4900 +version = "1.3.1"
4901 +source = "registry+https://github.com/rust-lang/crates.io-index"
4902 +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
4903 +dependencies = [
4904 + "libc",
4905 + "linux-raw-sys",
4906 + "rustix",
4907 +]
frontend/splitfire-desktop/src-tauri/Cargo.toml new
+40
@@ -0,0 +1,40 @@
1 +[package]
2 +name = "app"
3 +version = "0.2.0"
4 +description = "SplitFire AI"
5 +authors = ["setoelkahfi@gmail.com"]
6 +license = "MIT"
7 +repository = "https://github.com/setoelkahfi/splitfire-desktop"
8 +default-run = "app"
9 +edition = "2021"
10 +rust-version = "1.77"
11 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 +
13 +[build-dependencies]
14 +tauri-build = { version = "1.5", features = [] }
15 +
16 +[dependencies]
17 +anyhow = "1"
18 +chrono = "0.4.31"
19 +futures = "0.3"
20 +home = "0.5.4"
21 +hound = "3"
22 +log = "^0.4"
23 +once_cell = "1.18.0"
24 +reqwest = { version = "0.11", features = ["json"] }
25 +ringbuf = "0.3"
26 +rodio = "0.17"
27 +serde = { version = "1.0", features = ["derive"] }
28 +serde_json = "1"
29 +serde_repr = "0.1"
30 +tauri = { version = "1.7.1", features = [] }
31 +tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1", features = ["colored"] }
32 +url-builder = "0.1.1"
33 +
34 +[features]
35 +# by default Tauri runs in production mode
36 +# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
37 +default = ["custom-protocol"]
38 +# this feature is used for production builds where `devPath` points to the filesystem
39 +# DO NOT remove this
40 +custom-protocol = ["tauri/custom-protocol"]
frontend/splitfire-desktop/src-tauri/app-icon.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:c084ca20c0a6e66c6138daf2ffa54bbaaea3c69ee699979d0fcbc87ecb4663f8
3 +size 97387
frontend/splitfire-desktop/src-tauri/build.rs new
+3
@@ -0,0 +1,3 @@
1 +fn main() {
2 + tauri_build::build()
3 +}
frontend/splitfire-desktop/src-tauri/icons/128x128.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:577f960525033a3e600c6af5c238ba90bb6464b23350db586fa1e11ff1027a2f
3 +size 9492
frontend/splitfire-desktop/src-tauri/icons/128x128@2x.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:2f725dc1ade537732060561cd5f371cf7f1b1f8446541bb6fd8883613b9a71f9
3 +size 20309
frontend/splitfire-desktop/src-tauri/icons/32x32.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:90f4b029a7aafd1944c85c9076ada3ebdec691b8ea983b8f34245d0ab7deb75e
3 +size 1933
frontend/splitfire-desktop/src-tauri/icons/SplitFire-1024x1024.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:0d172e7ae367cdade8d4216ec98f4e11099952a0a524cd34ba6dc05dc607d482
3 +size 333102
frontend/splitfire-desktop/src-tauri/icons/SplitFire-152x152.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:2bed23a1230ab85e0fe07c4796efe1cacdf9026475aa9225c1b6f940f3191059
3 +size 24226
frontend/splitfire-desktop/src-tauri/icons/SplitFire-180x180.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:3b1cc7a59e745d2c292c1d4e801cd49956f8f7b33cdf76026edd9fff29741376
3 +size 27001
frontend/splitfire-desktop/src-tauri/icons/SplitFire167x167.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:3c29ea5c2e0ef54ca5ab7b80ee6a9f35ba80cbe5cdfc6da0b1804a1762f10d1b
3 +size 24772
frontend/splitfire-desktop/src-tauri/icons/Square107x107Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:e40d4800bdedf53de836896d31920adc7aa22c0a7b1386f1eeabafb15cfceb94
3 +size 7617
frontend/splitfire-desktop/src-tauri/icons/Square142x142Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:68ee0b76a859c7673b1b8a0b13394fe536b1329160a119e987ad6547534d70b6
3 +size 10482
frontend/splitfire-desktop/src-tauri/icons/Square150x150Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:772c4d583992fd0231c540c77467379af2383b22d2d5e6d9bd09e1de92ec2504
3 +size 11255
frontend/splitfire-desktop/src-tauri/icons/Square284x284Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:e6007cdee6634a3fd041afe1982bb772b01b4b82c72777bbee534551b49ee8bc
3 +size 22877
frontend/splitfire-desktop/src-tauri/icons/Square30x30Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:90e150d2ea18412310484f180862a098ac9fe8c452ffb86e00aa1e80e6602062
3 +size 1821
frontend/splitfire-desktop/src-tauri/icons/Square310x310Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:8ab7d8964b093a3019f315d4ba9cf60f53ac165d7c13d44513659ecad5eddcf8
3 +size 25128
frontend/splitfire-desktop/src-tauri/icons/Square44x44Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:48a5d99ddaa7f054e24325da04eb98c4d3e8af8f62c16ffaad1368dec1791464
3 +size 2839
frontend/splitfire-desktop/src-tauri/icons/Square71x71Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:0dfb7fecb17bcfc8503c3ffd6bc57e62a3816bf666afed4a55ad0012f8410044
3 +size 4937
frontend/splitfire-desktop/src-tauri/icons/Square89x89Logo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:92a041bf133e3873388ae744c9b97fe3b2d57446e47abe441822ca78043bd23e
3 +size 6344
frontend/splitfire-desktop/src-tauri/icons/StoreLogo.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:b44057cb4d604bf995ef9b3aa63ec52aefc3564f986d7cc7616bb8b705f232c7
3 +size 3225
frontend/splitfire-desktop/src-tauri/icons/icon.icns
Binary files /dev/null and b/frontend/splitfire-desktop/src-tauri/icons/icon.icns differ
frontend/splitfire-desktop/src-tauri/icons/icon.ico
Binary files /dev/null and b/frontend/splitfire-desktop/src-tauri/icons/icon.ico differ
frontend/splitfire-desktop/src-tauri/icons/icon.png new
+3
@@ -0,0 +1,3 @@
1 +version https://git-lfs.github.com/spec/v1
2 +oid sha256:b76437e5747e37185a495d826433b67ac58f080057e44229b2ddef988ed77e8b
3 +size 46041
frontend/splitfire-desktop/src-tauri/src/command/constants.rs new
+30
@@ -0,0 +1,30 @@
1 +use url_builder::URLBuilder;
2 +
3 +// Client ID and Client Secret
4 +#[cfg(debug_assertions)]
5 +pub const SF_CLIENT_ID: &str = "cli";
6 +#[cfg(not(debug_assertions))]
7 +pub const SF_CLIENT_ID: &str = "cli";
8 +#[cfg(debug_assertions)]
9 +pub const SF_CLIENT_SECRET: &str = "secretttttttt";
10 +#[cfg(not(debug_assertions))]
11 +pub const SF_CLIENT_SECRET: &str = "secretttttttt";
12 +
13 +// BASE_URL
14 +#[cfg(debug_assertions)]
15 +pub const API_HOST: &str = "localhost:3001";
16 +#[cfg(not(debug_assertions))]
17 +pub const API_HOST: &str = "api.musik88.com";
18 +
19 +// Paths
20 +pub const PATH_AUDIO: &str = "api/v1/splitfire";
21 +
22 +pub fn base_url_builder() -> URLBuilder {
23 + let mut url_builder = URLBuilder::new();
24 + url_builder
25 + .set_protocol("https")
26 + .set_host(API_HOST)
27 + .add_param("client_id", SF_CLIENT_ID)
28 + .add_param("client_secret", SF_CLIENT_SECRET);
29 + url_builder
30 +}
frontend/splitfire-desktop/src-tauri/src/command/download_result_files.rs new
+269
@@ -0,0 +1,269 @@
1 +use crate::{
2 + audio_dir_path,
3 + command::player_prepare::get_mode_from_filename,
4 + players::{BASS_FILE, DRUMS_FILE, OTHER_FILE, VOCALS_FILE},
5 +};
6 +use anyhow::anyhow;
7 +use futures::future;
8 +use log::{debug, error, info, warn};
9 +use reqwest::{Client, Error, Response};
10 +use std::{
11 + fmt::Display,
12 + fs::{create_dir_all, File, OpenOptions},
13 + io::Write,
14 + path::{Path, PathBuf},
15 +};
16 +
17 +use super::player_set_volume::Mode;
18 +
19 +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
20 +pub struct ResultFile {
21 + id: i32,
22 + source_file: String,
23 + filename: String,
24 + pub length: Option<f64>,
25 + onset: Option<Vec<f64>>,
26 +}
27 +
28 +impl Display for ResultFile {
29 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 + write!(
31 + f,
32 + "{{ id: {}, source_file: {}, filename: {}, length: {:?}, onset: {:?} }}",
33 + self.id, self.source_file, self.filename, self.length, self.onset
34 + )
35 + }
36 +}
37 +
38 +pub async fn save_result_files(audio_file_id: &String, result_files: &[ResultFile]) {
39 + // Check if we have ~/.sfai/audio/{provider_id} folder and create it if not
40 + let audio_id_path = match get_provider_id_dir_path(audio_file_id).await {
41 + Ok(path) => path,
42 + Err(e) => {
43 + error!("Failed to get audio directory: {:?}", e);
44 + return;
45 + }
46 + };
47 +
48 + // Write length and onset to result file's filename.json
49 + let x = result_files
50 + .iter()
51 + .map(|result_file| {
52 + persist_result_file(result_file.clone());
53 + let filename = format!("{}.json", result_file.filename);
54 + let file = get_result_file(&audio_id_path, &filename, true);
55 + async move {
56 + match file {
57 + Ok(mut file) => {
58 + let _ =
59 + file.write_all(serde_json::to_string(&result_file).unwrap().as_bytes());
60 + }
61 + Err(e) => {
62 + error!("Failed to get file: {:?}", e);
63 + }
64 + }
65 + }
66 + })
67 + .collect::<Vec<_>>();
68 +
69 + let _ = future::join_all(x).await;
70 + info!("Saved audio analysis.");
71 +}
72 +
73 +fn persist_result_file(result_file: ResultFile) {
74 + debug!("Persisting result file: {}", result_file);
75 + let mode = get_mode_from_filename(&result_file.filename);
76 + match mode {
77 + Mode::Vocals => {
78 + let _ = unsafe { VOCALS_FILE.set(result_file) };
79 + }
80 + Mode::Bass => {
81 + let _ = unsafe { BASS_FILE.set(result_file) };
82 + }
83 + Mode::Other => {
84 + let _ = unsafe { OTHER_FILE.set(result_file) };
85 + }
86 + Mode::Drums => {
87 + let _ = unsafe { DRUMS_FILE.set(result_file) };
88 + }
89 + Mode::Unknown => {
90 + error!("Unknown mode.");
91 + }
92 + }
93 +}
94 +
95 +pub async fn download_result_files(provider_id: String, result_files: Vec<ResultFile>) {
96 + // Check if we have ~/.sfai/audio/{provider_id} folder and create it if not
97 + let provider_id_path = match get_provider_id_dir_path(&provider_id).await {
98 + Ok(path) => path,
99 + Err(e) => {
100 + error!("Failed to get audio directory: {:?}", e);
101 + return;
102 + }
103 + };
104 +
105 + batch_download(result_files, provider_id_path).await;
106 +}
107 +
108 +async fn batch_download(result_files: Vec<ResultFile>, provider_id_path: PathBuf) {
109 + // Download files in parallel and store them in the ~/.sfai/audio/{provider_id} folder
110 + info!("Batch downloading {:?} result files.", result_files.len());
111 +
112 + let download_file = |result_file: ResultFile| {
113 + info!("Downloading file: {:?}", result_file.source_file);
114 + let task = async {
115 + let response = Client::new().get(result_file.source_file).send().await;
116 +
117 + match response {
118 + Ok(response) => {
119 + info!("Got response: {:?}", response);
120 + Ok(response)
121 + }
122 + Err(e) => {
123 + error!("Failed to get response: {:?}", e);
124 + Err(e)
125 + }
126 + }
127 + };
128 +
129 + // Create file in ~/.sfai/audio/{provider_id} folder
130 + let file = get_result_file(&provider_id_path, &result_file.filename, true);
131 +
132 + (task, file)
133 + };
134 +
135 + let (tasks, files): (Vec<_>, Vec<_>) = result_files
136 + .into_iter()
137 + .filter(|x| {
138 + debug!("Length: {:?}", x.length);
139 + debug!("Onset: {:?}", x.onset);
140 + should_download_file(&provider_id_path, x.filename.clone())
141 + })
142 + .map(download_file)
143 + .unzip();
144 +
145 + let results = future::join_all(tasks).await;
146 +
147 + let store_result = |x: (Result<Response, Error>, Result<File, std::io::Error>)| async {
148 + //info!("Storing result: {:?}, {:?}", x.0, x.1);
149 +
150 + // make sure both are Oks
151 + let (result, file) = match (x.0, x.1) {
152 + (Ok(result), Ok(file)) => {
153 + info!("Got result and file.");
154 + info!("Result: {:?}", result.url());
155 + info!("File: {:?}", file);
156 + (result, file)
157 + }
158 + _ => {
159 + error!("Failed to get result and file.");
160 + return;
161 + }
162 + };
163 + // Write bytes to file
164 + let _ = write_bytes_to_file(file, result).await;
165 + };
166 +
167 + // Map result and file to store_result
168 + let store_results = results
169 + .into_iter()
170 + .zip(files)
171 + .map(|x| async {
172 + debug!("Storing result: {:?}, {:?}", x.0, x.1);
173 + let _ = store_result(x).await;
174 + })
175 + .collect::<Vec<_>>();
176 +
177 + let _ = future::join_all(store_results).await;
178 +
179 + info!("Batch download done.");
180 +}
181 +
182 +// Check if we should download the file.
183 +// If we have the ~/.sfai/audio/{provider_id} folder, then we should not download the result files again.
184 +pub async fn get_provider_id_dir_path(provider_id: &String) -> anyhow::Result<PathBuf> {
185 + if let Some(audio_dir_path) = audio_dir_path() {
186 + let provider_dir_path = audio_dir_path.join(provider_id.clone());
187 + if provider_dir_path.exists() && provider_dir_path.is_dir() {
188 + return Ok(provider_dir_path);
189 + }
190 + // CREATE DIRECTORY
191 + let _ = create_dir_all(audio_dir_path.join(provider_id));
192 + Ok(provider_dir_path)
193 + } else {
194 + error!("Failed to get audio directory.");
195 + Err(anyhow!("Failed to get audio directory."))
196 + }
197 +}
198 +
199 +fn get_result_file(
200 + provider_id_path: &Path,
201 + filename: &String,
202 + should_create_file: bool,
203 +) -> Result<File, std::io::Error> {
204 + OpenOptions::new()
205 + .create(should_create_file)
206 + .write(true)
207 + .read(true)
208 + .open(provider_id_path.join(filename))
209 +}
210 +
211 +fn should_download_file(provider_id_path: &Path, filename: String) -> bool {
212 + match get_result_file(provider_id_path, &filename, false) {
213 + Ok(file) => {
214 + warn!(
215 + "File already exists: {:?}",
216 + &provider_id_path.join(filename)
217 + );
218 + // Check if file is not empty
219 + let metadata = file.metadata();
220 + match metadata {
221 + Ok(metadata) => {
222 + if metadata.len() > 0 {
223 + warn!("File is not empty. We should not download it.");
224 + return false;
225 + }
226 + debug!("File is empty. We should download it.");
227 + true
228 + }
229 + Err(e) => {
230 + error!("Failed to get metadata: {:?}", e);
231 + true
232 + }
233 + }
234 + }
235 + Err(e) => {
236 + debug!(
237 + "Could not find a result file {:?}: {:?}",
238 + &provider_id_path.join(filename),
239 + e.to_string()
240 + );
241 + true
242 + }
243 + }
244 +}
245 +
246 +// Write reqwest::Response bytes to file.
247 +async fn write_bytes_to_file(mut file: File, response: Response) -> Result<(), std::io::Error> {
248 + info!("Writing bytes to: {:?}", file);
249 +
250 + match response.bytes().await {
251 + Ok(bytes) => match file.write_all(&bytes) {
252 + Ok(_) => {
253 + info!("Wrote bytes to file.");
254 + Ok(())
255 + }
256 + Err(e) => {
257 + error!("Failed to write bytes to file: {:?}", e);
258 + Err(e)
259 + }
260 + },
261 + Err(e) => {
262 + error!("Failed to get bytes: {:?}", e);
263 + Err(std::io::Error::new(
264 + std::io::ErrorKind::Other,
265 + "Failed to get bytes.",
266 + ))
267 + }
268 + }
269 +}
frontend/splitfire-desktop/src-tauri/src/command/mod.rs new
+8
@@ -0,0 +1,8 @@
1 +pub mod constants;
2 +pub mod download_result_files;
3 +pub mod pages;
4 +pub mod player_controls;
5 +pub mod player_prepare;
6 +pub mod player_record;
7 +pub mod player_set_volume;
8 +pub mod player_unmount;
frontend/splitfire-desktop/src-tauri/src/command/pages.rs new
+28
@@ -0,0 +1,28 @@
1 +use tauri::{AppHandle, WindowBuilder, WindowUrl};
2 +
3 +#[tauri::command]
4 +pub async fn open_lyrics_editor(handle: AppHandle, path: String) {
5 + let url = format!("http://localhost:3002{}", path);
6 + let docs_window = WindowBuilder::new(
7 + &handle,
8 + "lyrics_editor", /* the unique window label */
9 + WindowUrl::App(url.parse().unwrap()),
10 + )
11 + .build()
12 + .unwrap();
13 + let _ = docs_window.set_title("Lyrics Editor");
14 +}
15 +
16 +#[tauri::command]
17 +pub async fn open_player(handle: AppHandle, path: String) {
18 + print!("{path}");
19 + let url = format!("http://localhost:3002{}", path);
20 + let docs_window = WindowBuilder::new(
21 + &handle,
22 + "page_search", /* the unique window label */
23 + WindowUrl::App(url.parse().unwrap()),
24 + )
25 + .build()
26 + .unwrap();
27 + let _ = docs_window.set_title("Search");
28 +}
frontend/splitfire-desktop/src-tauri/src/command/player_controls.rs new
+102
@@ -0,0 +1,102 @@
1 +use super::player_set_volume::Mode;
2 +use crate::{
3 + command::player_prepare::prepare_players,
4 + players::{BASS_SINK, DRUMS_SINK, OTHER_SINK, VOCALS_SINK},
5 +};
6 +use log::info;
7 +use serde::{Deserialize, Serialize};
8 +use std::fmt::Display;
9 +
10 +#[derive(Deserialize, Serialize, Debug, PartialEq)]
11 +pub struct PlayerVolume {
12 + mode: Mode,
13 + volume: String,
14 +}
15 +
16 +impl Display for PlayerVolume {
17 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 + write!(f, "{:?} {}", self.mode, self.volume)
19 + }
20 +}
21 +
22 +#[tauri::command]
23 +pub async fn player_paused() {
24 + info!("Player paused.");
25 +
26 + // Stop all sinks
27 + if let Some(sink) = unsafe { VOCALS_SINK.get() } {
28 + sink.pause();
29 + }
30 + if let Some(sink) = unsafe { BASS_SINK.get() } {
31 + sink.pause();
32 + }
33 + if let Some(sink) = unsafe { OTHER_SINK.get() } {
34 + sink.pause();
35 + }
36 + if let Some(sink) = unsafe { DRUMS_SINK.get() } {
37 + sink.pause();
38 + }
39 +}
40 +
41 +#[tauri::command]
42 +pub async fn player_play(player_volumes: Vec<PlayerVolume>) {
43 + info!("Player resumed. {:?}", player_volumes);
44 +
45 + // Stop all sinks
46 + if let Some(sink) = unsafe { VOCALS_SINK.get() } {
47 + player_volumes.iter().for_each(|player_volume| {
48 + if player_volume.mode == Mode::Vocals {
49 + let volume = player_volume.volume.parse::<f32>().unwrap() / 100.0;
50 + sink.set_volume(volume);
51 + }
52 + });
53 + sink.play();
54 + }
55 + if let Some(sink) = unsafe { BASS_SINK.get() } {
56 + player_volumes.iter().for_each(|player_volume| {
57 + if player_volume.mode == Mode::Bass {
58 + let volume = player_volume.volume.parse::<f32>().unwrap() / 100.0;
59 + sink.set_volume(volume);
60 + }
61 + });
62 + sink.play();
63 + }
64 + if let Some(sink) = unsafe { OTHER_SINK.get() } {
65 + player_volumes.iter().for_each(|player_volume| {
66 + if player_volume.mode == Mode::Other {
67 + let volume = player_volume.volume.parse::<f32>().unwrap() / 100.0;
68 + sink.set_volume(volume);
69 + }
70 + });
71 + sink.play();
72 + }
73 + if let Some(sink) = unsafe { DRUMS_SINK.get() } {
74 + player_volumes.iter().for_each(|player_volume| {
75 + if player_volume.mode == Mode::Drums {
76 + let volume = player_volume.volume.parse::<f32>().unwrap() / 100.0;
77 + sink.set_volume(volume);
78 + }
79 + });
80 + sink.play();
81 + }
82 +}
83 +
84 +#[tauri::command]
85 +pub async fn player_stop(audio_id: String) {
86 + info!("Stopping player.");
87 +
88 + // Stop all sinks
89 + if let Some(sink) = unsafe { VOCALS_SINK.take() } {
90 + sink.stop();
91 + }
92 + if let Some(sink) = unsafe { BASS_SINK.take() } {
93 + sink.stop();
94 + }
95 + if let Some(sink) = unsafe { OTHER_SINK.take() } {
96 + sink.stop();
97 + }
98 + if let Some(sink) = unsafe { DRUMS_SINK.take() } {
99 + sink.stop()
100 + }
101 + prepare_players(audio_id).await;
102 +}
frontend/splitfire-desktop/src-tauri/src/command/player_prepare.rs new
+277
@@ -0,0 +1,277 @@
1 +use super::{
2 + constants::{base_url_builder, PATH_AUDIO},
3 + download_result_files::{save_result_files, ResultFile},
4 +};
5 +use crate::{
6 + command::{
7 + download_result_files::{download_result_files, get_provider_id_dir_path},
8 + player_set_volume::Mode,
9 + },
10 + get_soundcard,
11 + players::{BASS_SINK, DRUMS_SINK, OTHER_SINK, VOCALS_SINK},
12 + record::record_instrument::AudioDevice,
13 +};
14 +use log::{debug, error, info};
15 +use reqwest::Client;
16 +use rodio::{Decoder, DeviceTrait, OutputStream, Sink};
17 +use serde::{Deserialize, Serialize};
18 +use serde_repr::*;
19 +use std::{
20 + fs::{self, DirEntry, File},
21 + io::BufReader,
22 + path::{Path, PathBuf},
23 +};
24 +use tauri::async_runtime::spawn;
25 +
26 +#[derive(Serialize, Deserialize)]
27 +enum Status {
28 + #[serde(rename = "downloading")]
29 + Downloading,
30 + #[serde(rename = "splitting")]
31 + Split,
32 + #[serde(rename = "done")]
33 + Done,
34 +}
35 +
36 +#[derive(Serialize, Deserialize)]
37 +struct AudioFile {
38 + id: i32,
39 + name: String,
40 + status: Status,
41 + results: Vec<ResultFile>,
42 +}
43 +
44 +#[derive(Serialize, Deserialize)]
45 +struct AudioFileResponse {
46 + code: i32,
47 + message: String,
48 + audio_file: AudioFile,
49 +}
50 +
51 +#[derive(Serialize_repr)]
52 +#[repr(u8)]
53 +enum TauriResponse {
54 + Error = 0,
55 + Success = 1,
56 +}
57 +
58 +#[derive(Serialize)]
59 +pub struct PreparePlayerResponse {
60 + status: TauriResponse,
61 + message: String,
62 + audio_file_name: Option<String>,
63 +}
64 +
65 +#[tauri::command]
66 +pub async fn player_prepare(provider_id: String) -> PreparePlayerResponse {
67 + debug!("Preparing player for provider_id {}.", provider_id);
68 +
69 + // Get audio file response
70 + let response = Client::new()
71 + .get(build_audio_detail_url(provider_id))
72 + .send()
73 + .await;
74 +
75 + match response {
76 + Ok(response) => {
77 + debug!("Got response: {:?}", response);
78 + let res: AudioFileResponse = match response.json().await {
79 + Ok(audio_file) => audio_file,
80 + Err(e) => {
81 + error!("Failed to parse response: {:?}", e);
82 + return PreparePlayerResponse {
83 + status: TauriResponse::Error,
84 + message: e.to_string(),
85 + audio_file_name: None,
86 + };
87 + }
88 + };
89 +
90 + prepare_result_files(res.audio_file).await
91 + }
92 + Err(e) => {
93 + error!("Failed to get response: {:?}", e);
94 + PreparePlayerResponse {
95 + status: TauriResponse::Error,
96 + message: e.to_string(),
97 + audio_file_name: None,
98 + }
99 + }
100 + }
101 +}
102 +
103 +async fn prepare_result_files(audio_file: AudioFile) -> PreparePlayerResponse {
104 + save_result_files(&audio_file.id.to_string(), &audio_file.results).await;
105 + download_result_files(audio_file.id.to_string(), audio_file.results).await;
106 + prepare_players(audio_file.id.to_string()).await;
107 + PreparePlayerResponse {
108 + status: TauriResponse::Success,
109 + message: "Success".to_string(),
110 + audio_file_name: Some(audio_file.name),
111 + }
112 +}
113 +
114 +fn build_audio_detail_url(id: String) -> String {
115 + let mut url_builder = base_url_builder();
116 + url_builder.add_route(PATH_AUDIO);
117 + url_builder.add_route(&id);
118 + url_builder.build()
119 +}
120 +
121 +pub async fn prepare_players(audio_id: String) {
122 + info!("Preparing player audio files: {}", audio_id);
123 +
124 + // Check if we have the ~/.sfai/audio/{audio_id} folder
125 + let audio_dir_path: PathBuf = match get_provider_id_dir_path(&audio_id).await {
126 + Ok(path) => path,
127 + Err(e) => {
128 + info!("Failed to get audio directory: {:?}", e);
129 + return;
130 + }
131 + };
132 +
133 + // get the files in the directory
134 + let dir_entries = if let Ok(paths) = fs::read_dir(audio_dir_path) {
135 + paths
136 + } else {
137 + info!("Failed to read audio directory.");
138 + return;
139 + };
140 +
141 + let get_path = |dir_entry: Result<DirEntry, std::io::Error>| -> Option<PathBuf> {
142 + debug!("Getting path to audio file.");
143 + let dir_entry = match dir_entry {
144 + Ok(dir_entry) => dir_entry,
145 + Err(e) => {
146 + info!("Failed to get dir entry: {:?}", e);
147 + return None;
148 + }
149 + };
150 + Some(dir_entry.path())
151 + };
152 +
153 + let sink = |path: &Path| -> Option<(Mode, Sink)> {
154 + debug!("Playing: {:?}", path);
155 + let device = if let Ok(device) = get_soundcard(AudioDevice::Output, true) {
156 + device
157 + } else {
158 + info!("Failed to get soundcard output.");
159 + return None;
160 + };
161 +
162 + if let Ok(name) = device.name() {
163 + info!("Output device is: {}", name);
164 + }
165 +
166 + let (_stream, handle) = if let Ok(x) = OutputStream::try_from_device(&device) {
167 + x
168 + } else {
169 + info!("Failed to get output stream.");
170 + return None;
171 + };
172 +
173 + let sink = if let Ok(z) = Sink::try_new(&handle) {
174 + z
175 + } else {
176 + info!("Failed to get sink.");
177 + return None;
178 + };
179 +
180 + let file = if let Ok(f) = File::open(path) {
181 + f
182 + } else {
183 + info!("Failed to open file.");
184 + return None;
185 + };
186 +
187 + match Decoder::new(BufReader::new(file)) {
188 + Ok(decoder) => {
189 + sink.append(decoder);
190 + sink.pause();
191 + Some((get_mode(path), sink))
192 + }
193 + Err(e) => {
194 + info!("Failed to create decoder: {:?}", e);
195 + None
196 + }
197 + }
198 + };
199 +
200 + let file_paths = dir_entries
201 + .into_iter()
202 + .flat_map(get_path)
203 + .collect::<Vec<_>>();
204 +
205 + file_paths
206 + .into_iter()
207 + .filter_map(|x| {
208 + // Only play .wav
209 + if let Some("wav") = x.extension().and_then(|x| x.to_str()) {
210 + Some(x)
211 + } else {
212 + None
213 + }
214 + })
215 + .map(|x| {
216 + spawn(async move {
217 + let sink = sink(&x);
218 + persist_sinks(sink)
219 + })
220 + })
221 + .for_each(drop);
222 +}
223 +
224 +fn persist_sinks(sink: Option<(Mode, Sink)>) {
225 + // Return early if we don't have a sink
226 + let (mode, sink) = match sink {
227 + Some(x) => x,
228 + None => return,
229 + };
230 + match mode {
231 + Mode::Vocals => {
232 + info!("Persisting vocals sink.");
233 + let _ = unsafe { VOCALS_SINK.set(sink) };
234 + }
235 + Mode::Bass => {
236 + info!("Persisting bass sink.");
237 + let _ = unsafe { BASS_SINK.set(sink) };
238 + }
239 + Mode::Other => {
240 + info!("Persisting other sink.");
241 + let _ = unsafe { OTHER_SINK.set(sink) };
242 + }
243 + Mode::Drums => {
244 + info!("Persisting drums sink.");
245 + let _ = unsafe { DRUMS_SINK.set(sink) };
246 + }
247 + Mode::Unknown => {
248 + error!("Got an unkown mode.");
249 + panic!("Should not be here.")
250 + }
251 + }
252 +}
253 +
254 +fn get_mode(path: &Path) -> Mode {
255 + if let Some(file_name) = path.file_name() {
256 + if let Some(file_name) = file_name.to_str() {
257 + return get_mode_from_filename(file_name);
258 + } else {
259 + return Mode::Unknown;
260 + }
261 + }
262 + Mode::Unknown
263 +}
264 +
265 +pub fn get_mode_from_filename(filename: &str) -> Mode {
266 + if filename.contains("vocal") {
267 + Mode::Vocals
268 + } else if filename.contains("drums") {
269 + Mode::Drums
270 + } else if filename.contains("bass") {
271 + Mode::Bass
272 + } else if filename.contains("other") {
273 + Mode::Other
274 + } else {
275 + Mode::Unknown
276 + }
277 +}
frontend/splitfire-desktop/src-tauri/src/command/player_record.rs new
+38
@@ -0,0 +1,38 @@
1 +use crate::{
2 + command::player_controls::player_play,
3 + players::BASS_FILE,
4 + record::record_instrument::{record_instrument, send_signal_to_stop, DEFAULT_LENGTH, DEFAULT_LENGTH_TOLERANCE},
5 +};
6 +use log::info;
7 +
8 +use super::player_controls::PlayerVolume;
9 +
10 +#[tauri::command]
11 +pub async fn player_record(audio_id: String, user_id: i32, player_volumes: Vec<PlayerVolume>) {
12 + // Get audio file response
13 + player_play(player_volumes).await;
14 +
15 + // Do the recording
16 + record_instrument(audio_id, user_id).await;
17 +}
18 +
19 +#[tauri::command]
20 +pub async fn player_record_stop() {
21 + info!("Stopping recording.");
22 + send_signal_to_stop();
23 +}
24 +
25 +#[tauri::command]
26 +pub async fn player_recording_length() -> f64 {
27 + info!("Get bass file length.");
28 + if let Some(result_file) = unsafe { BASS_FILE.get() } {
29 + if let Some(length) = result_file.length {
30 + // Round upwards to the nearest second
31 + let rounded_length = length.ceil();
32 + return rounded_length + DEFAULT_LENGTH_TOLERANCE;
33 + }
34 + DEFAULT_LENGTH
35 + } else {
36 + DEFAULT_LENGTH
37 + }
38 +}
frontend/splitfire-desktop/src-tauri/src/command/player_set_volume.rs new
+63
@@ -0,0 +1,63 @@
1 +use std::fmt::Display;
2 +
3 +use crate::players::{BASS_SINK, DRUMS_SINK, OTHER_SINK, VOCALS_SINK};
4 +use log::debug;
5 +use serde::{Deserialize, Serialize};
6 +
7 +#[derive(Deserialize, Serialize, Debug, PartialEq)]
8 +pub enum Mode {
9 + #[serde(rename = "vocals")]
10 + Vocals,
11 + #[serde(rename = "bass")]
12 + Bass,
13 + #[serde(rename = "other")]
14 + Other,
15 + #[serde(rename = "drums")]
16 + Drums,
17 + Unknown,
18 +}
19 +
20 +impl Display for Mode {
21 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 + let mode = match self {
23 + Mode::Vocals => "vocals",
24 + Mode::Bass => "bass",
25 + Mode::Other => "other",
26 + Mode::Drums => "drums",
27 + Mode::Unknown => "unknown",
28 + };
29 + write!(f, "{}", mode)
30 + }
31 +}
32 +
33 +#[tauri::command]
34 +pub fn player_set_volume(mode: Mode, volume: String) {
35 + // Calcluate volume from 0.0 to 1.0
36 + let volume = volume.parse::<f32>().unwrap() / 100.0;
37 + debug!("Setting volume for {:?} to {}.", mode, volume);
38 + match mode {
39 + Mode::Vocals => {
40 + if let Some(sink) = unsafe { VOCALS_SINK.get() } {
41 + sink.set_volume(volume)
42 + }
43 + }
44 + Mode::Bass => {
45 + if let Some(sink) = unsafe { BASS_SINK.get() } {
46 + sink.set_volume(volume)
47 + }
48 + }
49 + Mode::Other => {
50 + if let Some(sink) = unsafe { OTHER_SINK.get() } {
51 + sink.set_volume(volume)
52 + }
53 + }
54 + Mode::Drums => {
55 + if let Some(sink) = unsafe { DRUMS_SINK.get() } {
56 + sink.set_volume(volume)
57 + }
58 + }
59 + Mode::Unknown => {
60 + panic!("Should not be here.")
61 + }
62 + }
63 +}
frontend/splitfire-desktop/src-tauri/src/command/player_unmount.rs new
+33
@@ -0,0 +1,33 @@
1 +use crate::players::{
2 + BASS_FILE, BASS_SINK, DRUMS_FILE, DRUMS_SINK, OTHER_FILE, OTHER_SINK, VOCALS_FILE, VOCALS_SINK,
3 +};
4 +use log::info;
5 +
6 +#[tauri::command]
7 +pub async fn player_unmount() {
8 + info!("Unmounting player.");
9 +
10 + // Stop all sinks
11 + if let Some(sink) = unsafe { VOCALS_SINK.take() } {
12 + sink.stop();
13 + }
14 + if let Some(sink) = unsafe { BASS_SINK.take() } {
15 + sink.stop();
16 + }
17 + if let Some(sink) = unsafe { OTHER_SINK.take() } {
18 + sink.stop();
19 + }
20 + if let Some(sink) = unsafe { DRUMS_SINK.take() } {
21 + sink.stop();
22 + }
23 +
24 + // Dereference result files
25 + if let (Some(_), Some(_), Some(_), Some(_)) = (
26 + unsafe { VOCALS_FILE.take() },
27 + unsafe { DRUMS_FILE.take() },
28 + unsafe { BASS_FILE.take() },
29 + unsafe { OTHER_FILE.take() },
30 + ) {
31 + info!("Unmounting result files done.");
32 + }
33 +}
frontend/splitfire-desktop/src-tauri/src/lib.rs new
+96
@@ -0,0 +1,96 @@
1 +use log::{debug, error, info};
2 +use record::record_instrument::AudioDevice;
3 +use rodio::{
4 + cpal::{default_host, traits::HostTrait},
5 + Device, DeviceTrait,
6 +};
7 +use std::{fs::create_dir_all, path::PathBuf};
8 +
9 +pub mod command;
10 +pub mod players;
11 +pub mod record;
12 +
13 +#[cfg(debug_assertions)]
14 +const HOME_DIR: &str = ".sfai-dev";
15 +#[cfg(not(debug_assertions))]
16 +const HOME_DIR: &str = ".sfai";
17 +
18 +const AUDIO_DIR: &str = "audio";
19 +
20 +// Get ~/.sfai directory
21 +pub fn sfai_home_dir_path() -> Option<PathBuf> {
22 + match home::home_dir() {
23 + Some(path) => {
24 + info!("Home directory: {}", path.to_str().unwrap());
25 + let home_dir_path = path.join(HOME_DIR);
26 + if home_dir_path.exists() && home_dir_path.is_file() {
27 + return Some(home_dir_path);
28 + }
29 + // CREATE DIRECTORY
30 + //create_dir_all(path.join(".smb"))?;
31 + let _ = create_dir_all(path.join(HOME_DIR));
32 + Some(home_dir_path)
33 + }
34 + None => {
35 + error!("Failed to get home directory. So strange.");
36 + None
37 + }
38 + }
39 +}
40 +
41 +// Create audio directory if it doesn't exist
42 +pub fn audio_dir_path() -> Option<PathBuf> {
43 + if let Some(home_dir_path) = sfai_home_dir_path() {
44 + let audio_dir_path = home_dir_path.join(AUDIO_DIR);
45 + if audio_dir_path.exists() && audio_dir_path.is_file() {
46 + return Some(audio_dir_path);
47 + }
48 + // CREATE DIRECTORY
49 + let _ = create_dir_all(home_dir_path.join(AUDIO_DIR));
50 + Some(audio_dir_path)
51 + } else {
52 + None
53 + }
54 +}
55 +
56 +// Get the soundcard device or the default device if fallback is true.
57 +pub fn get_soundcard(
58 + device_type: AudioDevice,
59 + fallback_to_default: bool,
60 +) -> Result<Device, anyhow::Error> {
61 + let host = default_host();
62 + // Get list of all input devices including soundcards, microphones, etc.
63 + let devices = match device_type {
64 + AudioDevice::Input => host.input_devices()?,
65 + AudioDevice::Output => host.output_devices()?,
66 + };
67 + // Select output device scarlett 2i2
68 + let device = match devices.into_iter().find(|x| match x.name() {
69 + Ok(n) => {
70 + info!("{} name: {}", device_type, n);
71 + n.contains("Scarlett 2i2")
72 + }
73 + Err(e) => {
74 + error!("Failed to get device name: {}", e);
75 + false
76 + }
77 + }) {
78 + Some(d) => d,
79 + None => {
80 + if fallback_to_default {
81 + let devices = match device_type {
82 + AudioDevice::Input => host.default_input_device(),
83 + AudioDevice::Output => host.default_output_device(),
84 + };
85 +
86 + if let Some(device) = devices {
87 + debug!("Fallback to default {}.", device_type);
88 + return Ok(device);
89 + }
90 + }
91 + let message = format!("Failed to find {}.", device_type);
92 + return Err(anyhow::Error::msg(message));
93 + }
94 + };
95 + Ok(device)
96 +}
frontend/splitfire-desktop/src-tauri/src/main.rs new
+54
@@ -0,0 +1,54 @@
1 +#![cfg_attr(
2 + all(not(debug_assertions), target_os = "windows"),
3 + windows_subsystem = "windows"
4 +)]
5 +
6 +use app::{
7 + command::{
8 + pages::{__cmd__open_lyrics_editor, __cmd__open_player, open_lyrics_editor, open_player},
9 + player_controls::{
10 + __cmd__player_paused, __cmd__player_play, __cmd__player_stop, player_paused,
11 + player_play, player_stop,
12 + },
13 + player_prepare::{__cmd__player_prepare, player_prepare},
14 + player_record::{
15 + __cmd__player_record, __cmd__player_record_stop, __cmd__player_recording_length,
16 + player_record, player_record_stop, player_recording_length,
17 + },
18 + player_set_volume::{__cmd__player_set_volume, player_set_volume},
19 + player_unmount::{__cmd__player_unmount, player_unmount},
20 + },
21 + sfai_home_dir_path,
22 +};
23 +use tauri_plugin_log::fern::colors::{Color, ColoredLevelConfig};
24 +
25 +fn main() {
26 + // Create ~/.sfai directory if it doesn't exist
27 + sfai_home_dir_path();
28 +
29 + tauri::Builder::default()
30 + .plugin(
31 + tauri_plugin_log::Builder::default()
32 + .with_colors(
33 + ColoredLevelConfig::default()
34 + .debug(Color::Green)
35 + .info(Color::Cyan),
36 + )
37 + .build(),
38 + )
39 + .invoke_handler(tauri::generate_handler![
40 + open_lyrics_editor,
41 + open_player,
42 + player_record,
43 + player_record_stop,
44 + player_prepare,
45 + player_set_volume,
46 + player_unmount,
47 + player_paused,
48 + player_play,
49 + player_stop,
50 + player_recording_length
51 + ])
52 + .run(tauri::generate_context!())
53 + .expect("Error while running tauri application.");
54 +}
frontend/splitfire-desktop/src-tauri/src/players.rs new
+23
@@ -0,0 +1,23 @@
1 +use crate::command::download_result_files::ResultFile;
2 +use hound::WavWriter;
3 +use once_cell::sync::OnceCell;
4 +use rodio::Sink;
5 +use std::{
6 + fs::File,
7 + io::BufWriter,
8 + sync::{mpsc::Sender, Arc, Mutex},
9 +};
10 +
11 +pub static mut VOCALS_SINK: OnceCell<Sink> = OnceCell::new();
12 +pub static mut DRUMS_SINK: OnceCell<Sink> = OnceCell::new();
13 +pub static mut BASS_SINK: OnceCell<Sink> = OnceCell::new();
14 +pub static mut OTHER_SINK: OnceCell<Sink> = OnceCell::new();
15 +
16 +pub static mut VOCALS_FILE: OnceCell<ResultFile> = OnceCell::new();
17 +pub static mut DRUMS_FILE: OnceCell<ResultFile> = OnceCell::new();
18 +pub static mut BASS_FILE: OnceCell<ResultFile> = OnceCell::new();
19 +pub static mut OTHER_FILE: OnceCell<ResultFile> = OnceCell::new();
20 +
21 +pub type RecordingWriter = Arc<Mutex<Option<WavWriter<BufWriter<File>>>>>;
22 +pub static mut RECORDING_WRITER: OnceCell<RecordingWriter> = OnceCell::new();
23 +pub static mut RECORDING_SENDER: OnceCell<Sender<()>> = OnceCell::new();
frontend/splitfire-desktop/src-tauri/src/record/mod.rs new
+1
@@ -0,0 +1 @@
1 +pub mod record_instrument;
frontend/splitfire-desktop/src-tauri/src/record/record_instrument.rs new
+257
@@ -0,0 +1,257 @@
1 +use crate::{
2 + command::{download_result_files::get_provider_id_dir_path, player_set_volume::Mode},
3 + get_soundcard,
4 + players::{BASS_FILE, RECORDING_SENDER, RECORDING_WRITER},
5 +};
6 +use chrono::Utc;
7 +use cpal::{
8 + traits::{DeviceTrait, StreamTrait},
9 + FromSample, Sample, SampleFormat, SupportedStreamConfig,
10 +};
11 +use hound::WavWriter;
12 +use log::{debug, error, info};
13 +use rodio::cpal;
14 +use std::thread::spawn;
15 +use std::{
16 + fmt::Display,
17 + fs::{create_dir_all, File},
18 + io::BufWriter,
19 + sync::{mpsc::channel, Arc, Mutex},
20 + thread::sleep,
21 + time::Duration,
22 +};
23 +
24 +pub enum AudioDevice {
25 + Input,
26 + Output,
27 +}
28 +
29 +impl Display for AudioDevice {
30 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 + match self {
32 + AudioDevice::Input => write!(f, "Input device"),
33 + AudioDevice::Output => write!(f, "Output device"),
34 + }
35 + }
36 +}
37 +
38 +fn sample_format(format: SampleFormat) -> hound::SampleFormat {
39 + if format.is_float() {
40 + hound::SampleFormat::Float
41 + } else {
42 + hound::SampleFormat::Int
43 + }
44 +}
45 +
46 +fn wav_spec_from_config(config: &SupportedStreamConfig) -> hound::WavSpec {
47 + hound::WavSpec {
48 + channels: config.channels() as _,
49 + sample_rate: config.sample_rate().0 as _,
50 + bits_per_sample: (config.sample_format().sample_size() * 8) as _,
51 + sample_format: sample_format(config.sample_format()),
52 + }
53 +}
54 +
55 +pub type WavWriterHandle = Arc<Mutex<Option<WavWriter<BufWriter<File>>>>>;
56 +
57 +fn write_input_data<T, U>(input: &[T], writer: &WavWriterHandle)
58 +where
59 + T: Sample,
60 + U: Sample + hound::Sample + FromSample<T>,
61 +{
62 + if let Ok(mut guard) = writer.try_lock() {
63 + if let Some(writer) = guard.as_mut() {
64 + for &sample in input.iter() {
65 + let sample: U = U::from_sample(sample);
66 + writer.write_sample(sample).ok();
67 + }
68 + }
69 + }
70 +}
71 +
72 +pub const DEFAULT_LENGTH: f64 = 599.0;
73 +// Is this needed?
74 +pub const DEFAULT_LENGTH_TOLERANCE: f64 = 1.0;
75 +
76 +pub async fn record_instrument(audio_id: String, user_id: i32) {
77 + info!("Start recording: audio_id:{} user_id:{}", audio_id, user_id);
78 + // Find devices.
79 + let input_device = if let Ok(device) = get_soundcard(AudioDevice::Input, true) {
80 + device
81 + } else {
82 + info!("Failed to get soundcard input.");
83 + return;
84 + };
85 +
86 + if let Ok(name) = input_device.name() {
87 + info!("Input device is: {}", name);
88 + }
89 +
90 + let input_config = match input_device.default_input_config() {
91 + Ok(c) => c,
92 + Err(e) => {
93 + error!("failed to find input config: {}", e);
94 + return;
95 + }
96 + };
97 +
98 + // The WAV file we're recording to.
99 + let recording_path = match get_recording_path(audio_id, user_id.to_string(), Mode::Bass).await {
100 + Ok(path) => path,
101 + Err(e) => {
102 + error!("failed to get audio directory: {}", e);
103 + return;
104 + }
105 + };
106 + debug!("Recording path: {}", recording_path);
107 + let spec = wav_spec_from_config(&input_config);
108 + let writer = match WavWriter::create(&recording_path, spec) {
109 + Ok(w) => w,
110 + Err(e) => {
111 + error!("Failed to create wav writer: {}", e);
112 + return;
113 + }
114 + };
115 + let writer = Arc::new(Mutex::new(Some(writer)));
116 +
117 + // Run the input stream on a separate thread.
118 + let input_stream_writer = writer.clone();
119 +
120 + let err_fn = move |err| {
121 + error!("an error occurred on stream: {}", err);
122 + };
123 +
124 + let stream = match input_config.sample_format() {
125 + SampleFormat::I8 => input_device.build_input_stream(
126 + &input_config.into(),
127 + move |data, _: &_| write_input_data::<i8, i8>(data, &input_stream_writer),
128 + err_fn,
129 + None,
130 + ),
131 +
132 + SampleFormat::I16 => input_device.build_input_stream(
133 + &input_config.into(),
134 + move |data, _: &_| write_input_data::<i16, i16>(data, &input_stream_writer),
135 + err_fn,
136 + None,
137 + ),
138 + SampleFormat::I32 => input_device.build_input_stream(
139 + &input_config.into(),
140 + move |data, _: &_| write_input_data::<i32, i32>(data, &input_stream_writer),
141 + err_fn,
142 + None,
143 + ),
144 + SampleFormat::F32 => input_device.build_input_stream(
145 + &input_config.into(),
146 + move |data, _: &_| write_input_data::<f32, f32>(data, &input_stream_writer),
147 + err_fn,
148 + None,
149 + ),
150 + _ => {
151 + error!("unsupported sample format");
152 + return;
153 + }
154 + };
155 +
156 + let stream = match stream {
157 + Ok(s) => s,
158 + Err(e) => {
159 + error!("failed to build input stream: {}", e);
160 + return;
161 + }
162 + };
163 +
164 + let _ = stream.play();
165 +
166 + // Take the audio length in seconds
167 + // Get result file bass' length
168 +
169 + let length_with_tolerance = if let Some(bass_file) = unsafe { BASS_FILE.get() } {
170 + if let Some(length) = bass_file.length {
171 + length + DEFAULT_LENGTH_TOLERANCE
172 + } else {
173 + DEFAULT_LENGTH
174 + }
175 + } else {
176 + DEFAULT_LENGTH
177 + };
178 + debug!("Recording for {} seconds", length_with_tolerance);
179 +
180 + // Create channel to stop recording
181 + let (send, recv) = channel::<()>();
182 +
183 + // Persist writer and sender
184 + unsafe {
185 + let _ = RECORDING_WRITER.set(writer);
186 + };
187 + unsafe {
188 + let _ = RECORDING_SENDER.set(send);
189 + };
190 +
191 + // Wait for recording to finish
192 + spawn(move || {
193 + // Sleep for length of recording
194 + // Add 5 seconds from original recording length so that the original thread can stop the recording
195 + if recv
196 + .recv_timeout(std::time::Duration::from_secs_f64(length_with_tolerance))
197 + .is_ok()
198 + {
199 + info!("Recording stopped abruptly.");
200 + stop_recording();
201 + return;
202 + }
203 + let _ = recv.recv();
204 + });
205 +
206 + // See if we can actually do this
207 + sleep(Duration::from_secs_f64(length_with_tolerance));
208 + info!("Recording stopped from the original thread.");
209 + send_signal_to_stop();
210 +}
211 +
212 +pub fn send_signal_to_stop() {
213 + if let Some(sender) = unsafe { RECORDING_SENDER.get() } {
214 + let _ = sender.send(());
215 + }
216 +}
217 +
218 +fn stop_recording() {
219 + info!("Stopping recording.");
220 + // Get writer from oncecell
221 + if let Ok(mut guard) = unsafe { RECORDING_WRITER.get().unwrap().lock() } {
222 + if let Some(writer) = guard.take() {
223 + writer.finalize().ok();
224 + info!("Recording complete!");
225 + }
226 + } else {
227 + info!("Failed to finalize writer.");
228 + };
229 +}
230 +
231 +// Get string path to audio directory
232 +async fn get_recording_path(
233 + audio_id: String,
234 + user_id: String,
235 + mode: Mode,
236 +) -> anyhow::Result<String> {
237 + let audio_dir_path = if let Ok(path) = get_provider_id_dir_path(&audio_id).await {
238 + path
239 + } else {
240 + return Err(anyhow::Error::msg("failed to get audio directory"));
241 + };
242 +
243 + // Check is user directory exists
244 + let user_dir_path = audio_dir_path.join(&user_id);
245 + if !user_dir_path.exists() {
246 + // CREATE DIRECTORY
247 + let _ = create_dir_all(&user_dir_path);
248 + }
249 + let now = Utc::now().timestamp();
250 + // UCG formated filename
251 + let filename = format!("{}-{}-{}-{}.wav", mode, audio_id, user_id, now);
252 + let userfilepath = user_dir_path.join(filename);
253 + match userfilepath.to_str() {
254 + Some(userfile) => Ok(userfile.to_string()),
255 + None => Err(anyhow::Error::msg("failed to get audio directory")),
256 + }
257 +}
frontend/splitfire-desktop/src-tauri/tauri.conf.json new
+66
@@ -0,0 +1,66 @@
1 +{
2 + "build": {
3 + "beforeBuildCommand": "bun build",
4 + "beforeDevCommand": "bun start",
5 + "devPath": "http://localhost:3002",
6 + "distDir": "../build"
7 + },
8 + "package": {
9 + "productName": "SplitFire",
10 + "version": "0.3.4"
11 + },
12 + "tauri": {
13 + "allowlist": {
14 + "all": false
15 + },
16 + "bundle": {
17 + "active": true,
18 + "category": "DeveloperTool",
19 + "copyright": "",
20 + "deb": {
21 + "depends": []
22 + },
23 + "externalBin": [],
24 + "icon": [
25 + "icons/SplitFire-152x152.png",
26 + "icons/SplitFire-180x180.png",
27 + "icons/SplitFire-1024x1024.png",
28 + "icons/SplitFire167x167.png",
29 + "icons/icon.icns",
30 + "icons/icon.ico"
31 + ],
32 + "identifier": "ai.splitfire.desktop",
33 + "longDescription": "",
34 + "macOS": {
35 + "entitlements": null,
36 + "exceptionDomain": "",
37 + "frameworks": [],
38 + "providerShortName": null,
39 + "signingIdentity": null
40 + },
41 + "resources": [],
42 + "shortDescription": "",
43 + "targets": "all",
44 + "windows": {
45 + "certificateThumbprint": null,
46 + "digestAlgorithm": "sha256",
47 + "timestampUrl": ""
48 + }
49 + },
50 + "security": {
51 + "csp": null
52 + },
53 + "updater": {
54 + "active": false
55 + },
56 + "windows": [
57 + {
58 + "maximized": true,
59 + "height": 600,
60 + "resizable": true,
61 + "title": "SplitFire",
62 + "width": 800
63 + }
64 + ]
65 + }
66 +}
frontend/splitfire-desktop/src/App.css new
+111
@@ -0,0 +1,111 @@
1 +html, body {
2 + overflow: hidden;
3 +}
4 +html,
5 +body,
6 +#root {
7 + height: 100%;
8 +}
9 +
10 +.main-container {
11 + position: absolute;
12 + top: 0;
13 + bottom: 0;
14 + left: 0;
15 + right: 0;
16 + overflow: auto;
17 +}
18 +
19 +.bd-placeholder-img {
20 + font-size: 1.125rem;
21 + text-anchor: middle;
22 + -webkit-user-select: none;
23 + -moz-user-select: none;
24 + user-select: none;
25 +}
26 +
27 +@media (min-width: 768px) {
28 + .bd-placeholder-img-lg {
29 + font-size: 3.5rem;
30 + }
31 +}
32 +
33 +/*
34 + * Globals
35 + */
36 +
37 +.title {
38 + font-size: calc(1.375rem + 1.0vw);
39 + font-weight: 300;
40 + line-height: 1.2;
41 +}
42 +
43 +/* Custom default button */
44 +.btn-secondary,
45 +.btn-secondary:hover,
46 +.btn-secondary:focus {
47 + color: #333;
48 + text-shadow: none;
49 + /* Prevent inheritance from `body` */
50 +}
51 +
52 +
53 +/*
54 + * Base structure
55 + */
56 +
57 +body {
58 + text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
59 + box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
60 +}
61 +
62 +.cover-container {
63 + max-width: 45em;
64 +}
65 +
66 +.flex-wrapper {
67 + display: flex;
68 + min-height: 82vh;
69 + flex-direction: column;
70 + justify-content: space-between;
71 +}
72 +
73 +/*
74 + * Header
75 + */
76 +
77 +.nav-masthead .nav-link {
78 + padding: .25rem 0;
79 + font-weight: 700;
80 + color: rgba(255, 255, 255, .5);
81 + background-color: transparent;
82 + border-bottom: .25rem solid transparent;
83 +}
84 +
85 +.nav-masthead .nav-link:hover,
86 +.nav-masthead .nav-link:focus {
87 + border-bottom-color: rgba(255, 255, 255, .25);
88 +}
89 +
90 +.nav-masthead .nav-link+.nav-link {
91 + margin-left: 1rem;
92 +}
93 +
94 +.nav-masthead .active {
95 + color: #fff;
96 + border-bottom-color: #fff;
97 +}
98 +
99 +/* Votes */
100 +.votes {
101 + font-size: calc(5.375rem + 1.5vw);
102 +}
103 +
104 +input[type=range]{
105 + writing-mode: bt-lr;
106 + appearance: slider-vertical;
107 + -webkit-appearance: slider-vertical;
108 + width: 10px;
109 + height: 150px;
110 + padding: 0 0px;
111 +}
\ No newline at end of file
frontend/splitfire-desktop/src/App.test.js new
+8
@@ -0,0 +1,8 @@
1 +import { render, screen } from '@testing-library/react';
2 +import App from './App';
3 +
4 +test('renders learn react link', () => {
5 + render(<App />);
6 + const linkElement = screen.getByText(/learn react/i);
7 + expect(linkElement).toBeInTheDocument();
8 +});
frontend/splitfire-desktop/src/App.tsx new
+129
@@ -0,0 +1,129 @@
1 +import { IntlProvider } from 'react-intl'
2 +import { BrowserRouter, Route, Routes } from 'react-router-dom'
3 +import 'bootstrap/dist/css/bootstrap.min.css'
4 +import messagesEn from './translations/en.json'
5 +//import messagesId from './translations/id.json'
6 +import './App.css';
7 +import Main from './components/templates/Main';
8 +import axios from 'axios'
9 +import About from './components/pages/About'
10 +import Home from './components/pages/Home'
11 +import Login from './components/profiles/Login'
12 +import Logout from './components/profiles/Logout'
13 +import { ProfileRootView } from './components/profiles/Profile'
14 +import { ProfileFollowersRootView } from './components/profiles/ProfileFollowers'
15 +import { ProfileFollowingRootView } from './components/profiles/ProfileFollowing'
16 +import Song from './components/player/Song'
17 +import { CurrentUser, CurrentUserType, db } from './lib/db'
18 +import { ProfileNetworksRootView } from './components/profiles/ProfileNetworks'
19 +import Contact from './components/pages/Contact'
20 +import KaraokeView from './components/karaoke/KaraokeView'
21 +import { KaraokePlayerView } from './components/karaoke/KaraokePlayerView'
22 +import KaraokeLyricsView from './components/karaoke/KaraokeLyricsView'
23 +import Search from './components/pages/Search';
24 +import { SongView as SongDetailView } from './components/pages/song/SongView';
25 +import { Split } from './components/pages/esef/Split';
26 +import { SplitDetailView } from './components/pages/esef/SplitFireView';
27 +import { useEffect, useState } from 'react';
28 +import { Container, Spinner } from 'react-bootstrap';
29 +import { UserContext } from './lib/CurrentUserContext';
30 +import { PlayerAudio } from './components/player/Player';
31 +import { PlayerView } from './components/player/PlayerView';
32 +import { useLogger } from './lib/logger';
33 +import { LoadingView } from './components/templates/LoadingView';
34 +
35 +let i18nConfig = {
36 + language: 'en',
37 + messages: messagesEn
38 +};
39 +
40 +let host = process.env.REACT_APP_SPLITFIRE_API_HOST;
41 +
42 +axios.defaults.baseURL = `https://${host}/api/v1/`
43 +
44 +enum State {
45 + LOADING,
46 + LOADED,
47 + ERROR
48 +}
49 +
50 +export default function App() {
51 +
52 + const log = useLogger('App')
53 + const [state, setState] = useState(State.LOADING)
54 + const [user, setUser] = useState<CurrentUser | null>(null)
55 +
56 + const getCurrentUser = async () => {
57 + setState(State.LOADING)
58 + try {
59 + const result = await db.currentUser.where({ type: CurrentUserType.MAIN }).first()
60 + log.debug('Result: ', result)
61 + if (result) {
62 + setUser(result)
63 + }
64 + setState(State.LOADED)
65 + } catch (error) {
66 + log.error(error)
67 + setState(State.ERROR)
68 + }
69 + }
70 +
71 + useEffect(() => {
72 + getCurrentUser()
73 + }, [])
74 +
75 + const onUpdateUser = (user: CurrentUser | null) => {
76 + setUser(user)
77 + }
78 +
79 + const didLogout = () => {
80 + db.currentUser.clear().then(res => {
81 + window.location.href = '/'
82 + })
83 + }
84 +
85 + if (state === State.LOADING) {
86 + return <Container className='d-flex justify-content-center align-items-center' style={{ height: '100vh' }}>
87 + <LoadingView />
88 + </Container>
89 + }
90 +
91 + return (
92 + <IntlProvider locale={i18nConfig.language} messages={i18nConfig.messages}>
93 + <UserContext.Provider value={{ user, updateUser: onUpdateUser }}>
94 + <BrowserRouter>
95 + <Routes>
96 + <Route path='/' element={<Main />} >
97 + <Route index element={<Home />} />
98 + <Route path='about' element={<About />} />
99 + <Route path='contact' element={<Contact />} />
100 + <Route path='login' element={<Login />} />
101 + <Route path='logout' element={<Logout didLogout={didLogout} />} />
102 + <Route path='song' element={<Song />} >
103 + <Route path=':audioId' element={<SongDetailView />} />
104 + </Route>
105 + <Route path='split' element={<Split />} >
106 + <Route path=':providerId' element={<SplitDetailView />} />
107 + </Route>
108 + <Route path='karaoke' element={<KaraokeView />} >
109 + <Route path=':audioFileId' element={<KaraokePlayerView />} />
110 + <Route path=':audioFileId/lyrics' element={<KaraokeLyricsView />} />
111 + </Route>
112 + <Route path='player' element={<PlayerView />} >
113 + <Route path=':providerId' element={<PlayerAudio />} />
114 + </Route>
115 + <Route path='search' element={<Search />} />
116 + </Route>
117 + <Route path='/:usernameOrId' element={<Main />} >
118 + <Route index element={<ProfileRootView />} />
119 + <Route element={<ProfileNetworksRootView />}>
120 + <Route path='followers' element={<ProfileFollowersRootView />} />
121 + <Route path='following' element={<ProfileFollowingRootView />} />
122 + </Route>
123 + </Route>
124 + </Routes>
125 + </BrowserRouter>
126 + </UserContext.Provider>
127 + </IntlProvider >
128 + );
129 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/karaoke/KaraokeLyricsView.tsx new
+220
@@ -0,0 +1,220 @@
1 +import axios from "axios";
2 +import { Component } from "react";
3 +import { Alert, Button, Col, Container, Row } from "react-bootstrap";
4 +import { BsPlusCircle } from "react-icons/bs";
5 +import { Navigate, useParams } from "react-router-dom";
6 +import { CurrentUser } from "../../lib/db";
7 +import LyricSynch, { compare } from "../../models/lyricSynches";
8 +import { useCurrentUser } from "./KaraokeView";
9 +import LyricSynchRow from "./LyricSynchRow";
10 +
11 +export default function KaraokeLyricsView() {
12 + const { audioFileId } = useParams()
13 + const { currentUser } = useCurrentUser()
14 +
15 + if (!audioFileId) {
16 + return <p>Something wrong...</p>
17 + }
18 + return <KaraokeLyrics audioFileId={audioFileId} currentUser={currentUser} />
19 +}
20 +
21 +interface KaraokeLyricsProps {
22 + audioFileId: string
23 + currentUser: CurrentUser
24 +}
25 +
26 +interface KaraokeLyricsState {
27 + lyrics: LyricSynch[]
28 + errorMessage: string
29 + processing: boolean
30 + validationMessage: string
31 + shouldNavigateToPlayer: boolean
32 +}
33 +
34 +export class KaraokeLyrics extends Component<KaraokeLyricsProps, KaraokeLyricsState> {
35 +
36 + constructor(props: KaraokeLyricsProps) {
37 + super(props)
38 + this.state = {
39 + lyrics: [],
40 + errorMessage: '',
41 + processing: false,
42 + validationMessage: '',
43 + shouldNavigateToPlayer: false
44 + }
45 + }
46 +
47 + componentDidMount() {
48 + axios
49 + .get(`/lyric_synches/${this.props.audioFileId}`, {
50 + headers: {
51 + 'Authorization': this.props.currentUser.accessToken
52 + }
53 + })
54 + .then(res => {
55 + const lyricSynches: LyricSynch[] = res.data.lyric_synches
56 + //console.log(lyricSynches)
57 + this.setState({
58 + lyrics: lyricSynches
59 + })
60 + })
61 + .catch(error => {
62 + //console.log(error)
63 + this.setState({ errorMessage: 'Something error with this file.' })
64 + });
65 + }
66 +
67 + addLine() {
68 + const { lyrics } = this.state
69 + lyrics.push({ id: -1, lyric: '', time: '00:00' })
70 + this.setState({ lyrics })
71 + ////console.log(this.state)
72 + }
73 +
74 + removeLine(index: number) {
75 + const { lyrics } = this.state
76 + delete lyrics[index]
77 + this.setState({ lyrics })
78 + //console.log(this.state)
79 + }
80 +
81 + updateTime(index: number, time: string) {
82 + const { lyrics } = this.state
83 + lyrics[index].time = time
84 + this.setState({ lyrics })
85 + //console.log('updateTime', this.state)
86 + }
87 +
88 + updateLyric(index: number, lyric: string) {
89 + const { lyrics } = this.state
90 + lyrics[index].lyric = lyric
91 + this.setState({ lyrics })
92 + //console.log('updateLyric', this.state)
93 + }
94 +
95 + submit() {
96 + //console.log(this.state)
97 + this.setState({ processing: true })
98 + const isEmpty = (element: LyricSynch) => element.lyric.length === 0
99 + if (this.state.lyrics.some(isEmpty)) {
100 + this.setState({
101 + processing: false,
102 + validationMessage: 'Lyrics cannot be empty.'
103 + })
104 + return
105 + }
106 + const isNewLyric = (element: LyricSynch) => element.id < 0
107 + const isExistingLyric = (element: LyricSynch) => element.id > 0
108 + const newLyrics = this.state.lyrics.filter(isNewLyric)
109 + const existingLyrics = this.state.lyrics.filter(isExistingLyric)
110 +
111 + let requests = []
112 + // Find lyrics to update
113 + if (existingLyrics.length > 0) {
114 + requests.push(
115 + axios
116 + .put(`/lyric_synches/${this.props.audioFileId}`, {
117 + payload: existingLyrics
118 + }, {
119 + headers: {
120 + 'Authorization': this.props.currentUser.accessToken
121 + }
122 + })
123 + )
124 + }
125 +
126 + // Find lyrics to add
127 + if (newLyrics.length > 0) {
128 + requests.push(
129 + axios
130 + .post(`/lyric_synches/${this.props.audioFileId}`, {
131 + payload: newLyrics
132 + }, {
133 + headers: {
134 + 'Authorization': this.props.currentUser.accessToken
135 + }
136 + })
137 + )
138 + }
139 +
140 + axios
141 + .all(requests)
142 + .then(axios.spread((data1, data2) => {
143 + // output of req.
144 + //console.log('data1', data1, 'data2', data2)
145 + if ((data1 && data1.data.code === 200) || (data2 && data2.data.code === 200)) {
146 + this.setState({ shouldNavigateToPlayer: true })
147 + } else {
148 + this.setState({
149 + processing: false,
150 + validationMessage: 'Request error.'
151 + })
152 + }
153 + }))
154 + .catch(error => {
155 + //console.log(error)
156 + this.setState({
157 + processing: false,
158 + validationMessage: 'Request error.'
159 + })
160 + })
161 + }
162 +
163 + buttonTitle(): string {
164 + return this.state.processing ? 'Processing...' : 'Submit'
165 + }
166 +
167 + render() {
168 +
169 + const { lyrics, processing, errorMessage, validationMessage, shouldNavigateToPlayer } = this.state
170 +
171 + if (shouldNavigateToPlayer) {
172 + return <Navigate to={`/karaoke/${this.props.audioFileId}`} />
173 + }
174 +
175 + if (errorMessage.length > 0) {
176 + return <p>Cannot load file.</p>
177 + }
178 +
179 + let validationMessageView
180 + if (validationMessage.length > 0) {
181 + validationMessageView = <Alert variant="danger" onClose={() => this.setState({ validationMessage: ''})} dismissible><p>{validationMessage}</p></Alert>
182 + }
183 +
184 + return <Container>
185 + {validationMessageView}
186 + <Row>
187 + <Col>
188 + {lyrics.sort(compare).map((lyric, index) => (
189 + <LyricSynchRow
190 + lyricSynch={lyric}
191 + key={index}
192 + index={index}
193 + onRemove={ () => { this.removeLine(index) }}
194 + onTimeChange={ (time: string) => { this.updateTime(index, time) } }
195 + onLyricChange={ (lyric: string) => { this.updateLyric(index, lyric) } }
196 + processing={processing}
197 + />
198 + ))}
199 + </Col>
200 + </Row>
201 + <Row className="mb-4">
202 + <Col>
203 + <Button onClick={() => this.addLine()} >
204 + <BsPlusCircle color={`white`} />
205 + </Button>
206 + </Col>
207 + </Row>
208 + <Row>
209 + <Col>
210 + <Button
211 + variant="primary"
212 + onClick={() => this.submit() }
213 + disabled={processing} >
214 + {this.buttonTitle()}
215 + </Button>
216 + </Col>
217 + </Row>
218 + </Container>
219 + }
220 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/karaoke/KaraokePlayer.tsx new
+473
@@ -0,0 +1,473 @@
1 +import axios from "axios";
2 +import { Component } from "react";
3 +import { Spinner, Col, Row, Button } from "react-bootstrap";
4 +import { IconContext } from "react-icons";
5 +import { GiPauseButton, GiPlayButton, GiMicrophone } from "react-icons/gi";
6 +import { FormattedMessage } from "react-intl";
7 +import YouTube, { Options } from "react-youtube";
8 +import { Player, Channel } from "tone";
9 +import { AudioFiles, CurrentUser, db } from "../../lib/db";
10 +import { AudioFile, Status } from "../player/models/AudioFile";
11 +import { Mode } from "../player/models/Mode";
12 +import { Result } from "../player/models/Result";
13 +import { YouTubeEventTarget } from "../player/models/YouTubeEventTarget";
14 +import { YouTubePlayerState } from "../player/models/YouTubePlayerState";
15 +import ActionCable from "actioncable";
16 +import LyricSynch, { compare } from "../../models/lyricSynches";
17 +import { LyricRow } from "./LyricSynchRow";
18 +import { Link } from "react-router-dom";
19 +import { BsChatLeftTextFill } from "react-icons/bs";
20 +import { invoke } from '@tauri-apps/api'
21 +import { LoadingView } from "../templates/LoadingView";
22 +import isTauri from "../../lib/isTauri";
23 +
24 +interface KaraokePlayerProps {
25 + audioFileId: string
26 + currentUser: CurrentUser
27 +}
28 +
29 +interface KaraokePlayerState {
30 + errorMessage: string
31 + audioFile: AudioFile | null
32 + playerTime: number
33 + isPlayerReady: boolean
34 + isYoutubePlayerReady: boolean
35 + isPlaying: boolean
36 + vocalAudioOn: boolean
37 + guitarAudioOn: boolean
38 + bassAudioOn: boolean
39 + drumsAudioOn: boolean
40 + pianoAudioOn: boolean
41 + lyricSynches: LyricSynch[]
42 +}
43 +
44 +class KaraokePlayer extends Component<KaraokePlayerProps, KaraokePlayerState> {
45 +
46 +
47 + vocalPlayer: Player | null = null;
48 + otherPlayer: Player | null = null;
49 + bassPlayer: Player | null = null;
50 + drumsPlayer: Player | null = null;
51 + pianoPlayer: Player | null = null;
52 +
53 + youTubeEventTarget: YouTubeEventTarget | null = null
54 + updateYouTubePlayerStatusIntervalId: any | null = null
55 +
56 + debugChannel: any
57 + cableApp = ActionCable.createConsumer(`wss://${process.env.REACT_APP_SPLITFIRE_API_HOST}/cable`)
58 +
59 + constructor(props: KaraokePlayerProps) {
60 + super(props);
61 + this.state = {
62 + errorMessage: '',
63 + audioFile: null,
64 + playerTime: 0,
65 + isPlayerReady: false,
66 + isYoutubePlayerReady: false,
67 + isPlaying: false,
68 + vocalAudioOn: true,
69 + guitarAudioOn: true,
70 + bassAudioOn: true,
71 + drumsAudioOn: true,
72 + pianoAudioOn: true,
73 + lyricSynches: []
74 + }
75 +
76 + const received = (data: AudioFile) => {
77 + this._updateProgress(data)
78 + }
79 +
80 + const conected = () => { }
81 +
82 + this.debugChannel = this.cableApp.subscriptions.create({
83 + channel: 'FileProcessingChannel',
84 + audioFileId: this.state.audioFile?.id
85 + }, {
86 + conected,
87 + received
88 + })
89 + }
90 +
91 + componentDidMount() {
92 + const audioFileId = this.props.audioFileId;
93 + axios.get(`/splitfire/${audioFileId}`, {
94 + headers: {
95 + 'Authorization': this.props.currentUser.accessToken
96 + }
97 + })
98 + .then(res => {
99 + //console.log("Get results", res);
100 + const audioFile = res.data.audio_file;
101 + this.setState({ audioFile });
102 + // console.log(this.state);
103 + this._downloadAudioFilesIfNeeded();
104 + })
105 + .catch(error => {
106 + //console.log(error)
107 + this.setState({ errorMessage: 'Something error with this file.' })
108 + });
109 +
110 + axios.get(`/lyric_synches/${audioFileId}`, {
111 + headers: {
112 + 'Authorization': this.props.currentUser.accessToken
113 + }
114 + })
115 + .then(res => {
116 + const lyricSynches = res.data.lyric_synches
117 + this.setState({ lyricSynches })
118 + })
119 + .catch(error => {
120 + //console.log(error)
121 + this.setState({ errorMessage: 'Something error with this file.' })
122 + });
123 + }
124 +
125 + componentWillUnmount() {
126 + this.setState({ isPlaying: false })
127 + this.vocalPlayer?.stop()
128 + this.bassPlayer?.stop()
129 + this.drumsPlayer?.stop()
130 + this.pianoPlayer?.stop()
131 + this.otherPlayer?.stop()
132 +
133 + this.cableApp.disconnect()
134 + }
135 +
136 + _openLyricsEditor(audioFileId: number) {
137 + invoke('open_lyrics_editor', { path: `/karaoke/${audioFileId}/lyrics` })
138 + .then((response) => console.log(response))
139 + .catch((error) => console.error(error))
140 + }
141 +
142 + _updateProgress(data: AudioFile) {
143 + //console.log(data)
144 +
145 + this.setState({ audioFile: data })
146 +
147 + if (this.state.audioFile?.status === Status.DONE)
148 + window.location.reload();
149 + }
150 +
151 + _downloadAudioFilesIfNeeded() {
152 +
153 + if (this.state.audioFile?.status !== Status.DONE)
154 + return
155 +
156 + this._prepareAudio(Mode.Vocal);
157 + this._prepareAudio(Mode.Bass);
158 + this._prepareAudio(Mode.Drum);
159 + this._prepareAudio(Mode.Guitar);
160 + this._prepareAudio(Mode.Keyboard);
161 + }
162 +
163 + async _downloadFile(type: Mode) {
164 + // //console.log('_downloadFile', type);
165 + let audioFile: Result | undefined;
166 + switch (type) {
167 + case Mode.Vocal:
168 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('vocals') });
169 + break;
170 + case Mode.Bass:
171 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
172 + break;
173 + case Mode.Drum:
174 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
175 + break;
176 + case Mode.Guitar:
177 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
178 + break;
179 + case Mode.Keyboard:
180 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('piano') });
181 + break;
182 + }
183 +
184 + const audioFileId = this.props.audioFileId;
185 + // console.log("Download url", audioFile?.source_file);
186 + axios({
187 + url: audioFile?.source_file,
188 + method: 'GET',
189 + responseType: 'blob',
190 + }).then((response) => {
191 + const file = new Blob([response.data], { type: 'audio/mp3' });
192 + const item: AudioFiles = {
193 + audioFileId, type: type, file
194 + }
195 + db.audioFiles.add(item);
196 + // console.log('File Downloaded', file);
197 + this._setAudioSrc(type, file);
198 + }).catch(error => {
199 + // console.log(error);
200 + });
201 + }
202 +
203 + async _prepareAudio(type: Mode) {
204 + const audioFileId = this.props.audioFileId;
205 + db.audioFiles
206 + .get({ audioFileId: audioFileId, type: type })
207 + .then((record: AudioFiles | undefined) => {
208 + if (record?.file === undefined) {
209 + this._downloadFile(type);
210 + return
211 + }
212 + this._setAudioSrc(type, record.file)
213 + })
214 + .catch((onrejected: any) => {
215 + // console.log("Rejected", onrejected);
216 + });
217 + }
218 +
219 + _setAudioSrc(type: Mode, file: Blob) {
220 + // Get window.URL object
221 + const URL = window.URL || window.webkitURL;
222 + // Create and revoke ObjectURL
223 + const audioFileURL = URL.createObjectURL(file);
224 + const channel = new Channel().toDestination();
225 +
226 + switch (type) {
227 + case Mode.Vocal:
228 + this.vocalPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
229 + this.vocalPlayer.connect(channel);
230 + break;
231 + case Mode.Bass:
232 + this.bassPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
233 + this.bassPlayer.connect(channel);
234 + break;
235 + case Mode.Drum:
236 + this.drumsPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
237 + this.drumsPlayer.connect(channel);
238 + break;
239 + case Mode.Guitar:
240 + this.otherPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
241 + this.otherPlayer.connect(channel);
242 + break;
243 + case Mode.Keyboard:
244 + this.pianoPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
245 + this.pianoPlayer.connect(channel);
246 + break;
247 + }
248 + }
249 +
250 + _updatePlayerStatus: (() => void) = () => {
251 + if (
252 + this.vocalPlayer?.loaded &&
253 + this.drumsPlayer?.loaded &&
254 + this.otherPlayer?.loaded &&
255 + this.bassPlayer?.loaded &&
256 + this.pianoPlayer?.loaded
257 + ) {
258 + this.setState({ isPlayerReady: true });
259 + }
260 + }
261 +
262 + togglePlayAudio() {
263 + this.setState({ isPlaying: !this.state.isPlaying })
264 + if (this.state.isPlaying) {
265 + this.vocalPlayer?.stop()
266 + this.bassPlayer?.stop()
267 + this.drumsPlayer?.stop()
268 + this.pianoPlayer?.stop()
269 + this.otherPlayer?.stop()
270 + this.youTubeEventTarget?.pauseVideo()
271 + } else {
272 + this.youTubeEventTarget?.seekTo(0)
273 + this.youTubeEventTarget?.playVideo()
274 + this.vocalPlayer?.start();
275 + this.bassPlayer?.start();
276 + this.drumsPlayer?.start();
277 + this.pianoPlayer?.start();
278 + this.otherPlayer?.start();
279 + }
280 + }
281 +
282 + setToggleInstrument(mode: Mode) {
283 + switch (mode) {
284 + case Mode.Vocal:
285 + if (!this.vocalPlayer) return;
286 + this.setState({ vocalAudioOn: !this.state.vocalAudioOn });
287 + this.vocalPlayer.mute = this.state.vocalAudioOn;
288 + break;
289 + case Mode.Bass:
290 + if (!this.bassPlayer) return;
291 + this.setState({ bassAudioOn: !this.state.bassAudioOn });
292 + this.bassPlayer.mute = this.state.bassAudioOn;
293 + break;
294 + case Mode.Guitar:
295 + if (!this.otherPlayer) return;
296 + this.setState({ guitarAudioOn: !this.state.guitarAudioOn });
297 + this.otherPlayer.mute = this.state.guitarAudioOn;
298 + break;
299 + case Mode.Keyboard:
300 + if (!this.pianoPlayer) return;
301 + this.setState({ pianoAudioOn: !this.state.pianoAudioOn });
302 + this.pianoPlayer.mute = this.state.pianoAudioOn;;
303 + break;
304 + case Mode.Drum:
305 + if (!this.drumsPlayer) return;
306 + this.setState({ drumsAudioOn: !this.state.drumsAudioOn });
307 + this.drumsPlayer.mute = this.state.drumsAudioOn;
308 + break;
309 + }
310 + }
311 +
312 + render() {
313 +
314 + const { audioFile, errorMessage, isPlayerReady, isYoutubePlayerReady, isPlaying, lyricSynches } = this.state
315 +
316 + if (errorMessage.length > 0) {
317 + return (
318 + <p>Something wrong with this file.</p>
319 + )
320 + }
321 +
322 + if (audioFile?.status === Status.DOWNLOADING || audioFile?.status === Status.SPLITTING) {
323 + return <p>Processing file: {audioFile.progress}% </p>
324 + }
325 +
326 + if (isPlayerReady && audioFile) {
327 + const opts: Options = {
328 + width: '100%',
329 + playerVars: {
330 + // https://developers.google.com/youtube/player_parameters
331 + autoplay: 0,
332 + mute: 1,
333 + controls: 0,
334 + rel: 0,
335 + showinfo: 0
336 + },
337 + }
338 +
339 + let togglePlayButton = <div>
340 + <p>
341 + <FormattedMessage id="player.loadingYoutube"
342 + defaultMessage="Generating video..."
343 + description="Loading message" />
344 + </p>
345 + <Spinner animation='grow' variant="danger"></Spinner>
346 + </div>
347 +
348 + let lyricsView = <p>Loading lyrics...</p>
349 + let buttonViews = <Col><p>Loading mixer...</p></Col>
350 +
351 + if (isYoutubePlayerReady) {
352 + let button
353 + if (isPlaying) {
354 + button = <Col>
355 + <GiPauseButton
356 + color={isPlaying ? `white` : `red`}
357 + onClick={() => this.togglePlayAudio()} />
358 + </Col>
359 + } else {
360 + button = <Col>
361 + <GiPlayButton
362 + color={isPlaying ? `white` : `red`}
363 + onClick={() => this.togglePlayAudio()} />
364 + </Col>
365 + }
366 +
367 + togglePlayButton = <Col>{button}</Col>
368 + let buttonLyric
369 + if (isTauri()) {
370 + buttonLyric = <Button variant="secondary">
371 + <BsChatLeftTextFill color="white" onClick={() => this._openLyricsEditor(audioFile.id)} />
372 + </Button>
373 +
374 + } else {
375 + buttonLyric = <Link to={`/karaoke/${audioFile.id}/lyrics`} title="Add or edit lyrics" target="_blank" rel="noopener noreferrer" >
376 + <BsChatLeftTextFill color="white" />
377 + </Link>
378 + }
379 + buttonViews = <>
380 + <Col xs={6}>
381 + <GiMicrophone
382 + color={this.state.vocalAudioOn ? `white` : `red`}
383 + onClick={() => this.setToggleInstrument(Mode.Vocal)} />
384 + </Col>
385 + <Col xs={6}>
386 + {buttonLyric}
387 + </Col>
388 + </>
389 +
390 + if (lyricSynches.length > 0) {
391 + lyricsView = <>
392 + {lyricSynches.sort(compare).map((lyric, index) => (
393 + <Row key={index} >
394 + <Col>
395 + <LyricRow lyric={lyric.lyric} />
396 + </Col>
397 + </Row>
398 + ))}
399 + </>
400 + } else {
401 + lyricsView = <p>No lyrics available yet. Click above icon to add.</p>
402 + }
403 + }
404 +
405 + return (
406 + <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
407 + <Row className="mb-3 mt-3">
408 + <Row>
409 + <Col>
410 + <h1>{this.state.audioFile?.name}</h1>
411 + </Col>
412 + </Row>
413 + </Row>
414 + <Row className="mb-3 mt-3">
415 + {togglePlayButton}
416 + </Row>
417 + <Row className="mb-3 mt-3">
418 + {buttonViews}
419 + </Row>
420 + {lyricsView}
421 + <Row className="mt-3">
422 + <Col style={{ display: isYoutubePlayerReady ? 'block' : 'none', pointerEvents: 'none' }}>
423 + <YouTube
424 + videoId={this.state.audioFile?.youtube_video_id}
425 + opts={opts}
426 + onReady={this._onReady.bind(this)}
427 + onStateChange={this._onStateChange.bind(this)}
428 + />
429 + </Col>
430 + </Row>
431 + </IconContext.Provider>
432 + )
433 + } else {
434 + return <LoadingView />
435 + }
436 + }
437 +
438 + _onReady(event: any) {
439 + this.youTubeEventTarget = event.target
440 + this.youTubeEventTarget?.playVideo()
441 + this.updateYouTubePlayerStatusIntervalId = setInterval(() => {
442 + if (!this.state.isYoutubePlayerReady) {
443 + this._updateYouTubePlayerStatusIfNeeded()
444 + }
445 + }, 2000)
446 + }
447 +
448 + _onStateChange(event: any) {
449 + if (!this.youTubeEventTarget) return
450 +
451 + if (this.youTubeEventTarget.getPlayerState() === YouTubePlayerState.ended) {
452 + this.setState({ isPlaying: false })
453 + }
454 + }
455 +
456 + _updateYouTubePlayerStatusIfNeeded() {
457 +
458 + if (this.state.isYoutubePlayerReady) return
459 + if (!this.youTubeEventTarget) return
460 +
461 + // Let's say 5 seconds buffer is enough.
462 + if (this.youTubeEventTarget?.getCurrentTime() > 5) {
463 + this.setState({ isYoutubePlayerReady: true })
464 + this.youTubeEventTarget?.pauseVideo()
465 +
466 + if (this.updateYouTubePlayerStatusIntervalId)
467 + clearInterval(this.updateYouTubePlayerStatusIntervalId)
468 + }
469 + }
470 +
471 +}
472 +
473 +export default KaraokePlayer
\ No newline at end of file
frontend/splitfire-desktop/src/components/karaoke/KaraokePlayerView.tsx new
+15
@@ -0,0 +1,15 @@
1 +import { useParams } from "react-router-dom"
2 +import KaraokePlayer from "./KaraokePlayer"
3 +import { useCurrentUser } from "./KaraokeView"
4 +
5 +export function KaraokePlayerView() {
6 +
7 + const { audioFileId } = useParams()
8 + const { currentUser } = useCurrentUser()
9 +
10 + if (currentUser && audioFileId) {
11 + return <KaraokePlayer audioFileId={audioFileId} currentUser={currentUser} />
12 + }
13 +
14 + return <p>Something wrong</p>
15 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/karaoke/KaraokeView.tsx new
+55
@@ -0,0 +1,55 @@
1 +import { useState, useEffect } from 'react'
2 +import { Spinner } from 'react-bootstrap'
3 +import { FormattedMessage } from 'react-intl'
4 +import { Navigate, Outlet, useOutletContext, useParams } from 'react-router-dom'
5 +import { db, CurrentUserType, CurrentUser } from '../../lib/db'
6 +
7 +type ContextType = { currentUser: CurrentUser };
8 +
9 +export function useCurrentUser() {
10 + return useOutletContext<ContextType>()
11 +}
12 +
13 +export default function KaraokeView() {
14 +
15 + const { audioFileId } = useParams()
16 + const [currentUser, setCurrentUser] = useState<CurrentUser | undefined>(undefined)
17 + const [isRequesting, setisRequesting] = useState(true)
18 +
19 +
20 + useEffect(() => {
21 + db.currentUser.get({ type: CurrentUserType.MAIN }).then((res: CurrentUser | undefined ) => {
22 + if (res) {
23 + setCurrentUser(res)
24 + } else {
25 + setCurrentUser(undefined)
26 + }
27 + setisRequesting(false)
28 + }).catch( error => {
29 + //console.log(error)
30 + setCurrentUser(undefined)
31 + setisRequesting(false)
32 + })
33 + })
34 +
35 + if (isRequesting) {
36 + return <div>
37 + <p>
38 + <FormattedMessage id="home.loading"
39 + defaultMessage="Loading SplitFire AI..."
40 + description="Loading message" />
41 + </p>
42 + <Spinner animation='grow' variant="danger"></Spinner>
43 + </div>
44 + }
45 +
46 + if (!currentUser) {
47 + return <Navigate to={'/login'} />
48 + }
49 +
50 + if (currentUser && audioFileId) {
51 + return <Outlet context={{ currentUser }}/>
52 + }
53 +
54 + return <p>Something wrong</p>
55 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/karaoke/LyricSynchRow.tsx new
+66
@@ -0,0 +1,66 @@
1 +import { useState } from "react";
2 +import { Button, Col, Form, Row } from "react-bootstrap";
3 +import { BsFillTrashFill } from "react-icons/bs";
4 +import LyricSynch from "../../models/lyricSynches";
5 +
6 +interface LyricSynchRowProps {
7 + lyricSynch: LyricSynch
8 + index: number
9 + onLyricChange: (lyric: string) => void
10 + onTimeChange: (time: string) => void
11 + onRemove: () => void
12 + processing: boolean
13 +}
14 +
15 +export default function LyricSynchRow(props: LyricSynchRowProps) {
16 +
17 + const [lyric, setLyric] = useState(props.lyricSynch.lyric)
18 + const [time, setTime] = useState(props.lyricSynch.time)
19 +
20 + let removeButton
21 + if (props.lyricSynch.id < 0) {
22 + removeButton = <Button onClick={() => props.onRemove() } disabled={props.processing} >
23 + <BsFillTrashFill color="white" />
24 + </Button>
25 + }
26 +
27 + return <Row className="mb-3">
28 + <Col xs={3}>
29 + <Form.Control
30 + type="text"
31 + value={time}
32 + onChange={(e) => {
33 + setTime(e.currentTarget.value)
34 + props.onTimeChange(e.currentTarget.value)
35 + }}
36 + disabled={props.processing}
37 + />
38 + </Col>
39 + <Col xs={8}>
40 + <Form.Control
41 + as="textarea"
42 + rows={2}
43 + value={lyric}
44 + onChange={(e) => {
45 + setLyric(e.currentTarget.value)
46 + props.onLyricChange(e.currentTarget.value)
47 + }}
48 + disabled={props.processing}
49 + />
50 + </Col>
51 + <Col xs={1}>
52 + {removeButton}
53 + </Col>
54 + </Row>
55 +}
56 +interface LyricRowProps {
57 + lyric: String
58 +}
59 +export function LyricRow(props: LyricRowProps) {
60 +
61 + return <Row>
62 + <Col>
63 + {props.lyric}
64 + </Col>
65 + </Row>
66 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/About.tsx new
+15
@@ -0,0 +1,15 @@
1 +import { Col } from "react-bootstrap";
2 +import { FormattedMessage } from "react-intl";
3 +
4 +export default function About() {
5 + return (
6 + <Col>
7 + <h1>SplitFire Ai</h1>
8 + <p>
9 + <FormattedMessage id="splitfire.whatIs"
10 + defaultMessage="I'm an artificial intelligence software who will split your favorite music to its separate components."
11 + description="Explanation message" />
12 + </p>
13 + </Col>
14 + )
15 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/AutoCompleteView.tsx new
+22
@@ -0,0 +1,22 @@
1 +import { Container, Row, Button } from "react-bootstrap"
2 +import { Link } from "react-router-dom"
3 +import { SearchAutocompleteResult } from "./Home"
4 +
5 +interface Prop {
6 + results: SearchAutocompleteResult[]
7 +}
8 +
9 +export default function AutoCompleteView(props: Prop) {
10 + if (props.results.length === 0) {
11 + return <></>
12 + }
13 + return (
14 + <Container className="text-start">{props.results.map((result, index) =>
15 + <Row key={index}>
16 + <Link to={{ pathname: `/song/${result.id}` }}>
17 + <Button className="mb-2">{result.name}</Button>
18 + </Link>
19 + </Row>
20 + )}</Container>
21 + )
22 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/Contact.tsx new
+33
@@ -0,0 +1,33 @@
1 +import { Col, Container, Row } from 'react-bootstrap'
2 +import { BsFacebook, BsInstagram, BsTwitter, BsYoutube } from 'react-icons/bs'
3 +import { FormattedMessage } from 'react-intl'
4 +
5 +export default function Contact() {
6 + return (
7 + <Container>
8 + <Row>
9 + <Col>
10 + <p>
11 + <FormattedMessage id="splitfire.whatIs"
12 + defaultMessage="Connect with us."
13 + description="Explanation message" />
14 + </p>
15 + <ul className='list-unstyled'>
16 + <li>
17 + <BsInstagram /> <a href='https://www.instagram.com/splitfireAI/'>SplitfireAI</a>
18 + </li>
19 + <li>
20 + <BsTwitter /> <a href='https://www.twitter.com/splitfireAI/' >SplitfireAI</a>
21 + </li>
22 + <li>
23 + <BsYoutube /> <a href='https://www.youtube.com/c/SplitFireAI/' >SplitfireAI</a>
24 + </li>
25 + <li>
26 + <BsFacebook /> <a href='https://www.facebook.com/SplitfireAI'>SplitfireAI</a>
27 + </li>
28 + </ul>
29 + </Col>
30 + </Row>
31 + </Container>
32 + )
33 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/Home.tsx new
+51
@@ -0,0 +1,51 @@
1 +import { Component, ReactNode } from "react"
2 +import { Container, Row } from "react-bootstrap";
3 +import RandomView from "./home/Random";
4 +import SearchView from "./home/SearchFormView";
5 +import TopVotesView from "./home/TopVotesView";
6 +import ReadyToPlayView from "./home/ReadyToPlay";
7 +
8 +type HomeProps = {}
9 +
10 +type HomeState = {}
11 +
12 +export interface SearchAutocompleteResult {
13 + name: string
14 + id: string
15 + title: string
16 +}
17 +
18 +export interface SearchResponse {
19 + code: number
20 + message: string
21 + results: SearchAutocompleteResult[]
22 +}
23 +
24 +class Home extends Component<HomeProps, HomeState> {
25 +
26 + constructor(props: HomeProps) {
27 + super(props)
28 + this.state = {}
29 + }
30 +
31 + render(): ReactNode {
32 + return (
33 + <Container className="mb-5">
34 + <Row className="mb-3">
35 + <SearchView />
36 + </Row>
37 + <Row className="mb-3">
38 + <ReadyToPlayView />
39 + </Row>
40 + <Row className="mb-3">
41 + <RandomView />
42 + </Row>
43 + <Row className="mb-3">
44 + <TopVotesView />
45 + </Row>
46 + </Container >
47 + )
48 + }
49 +}
50 +
51 +export default Home
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/Search.tsx new
+74
@@ -0,0 +1,74 @@
1 +import axios from "axios";
2 +import { useEffect, useState } from "react";
3 +import { Col, Container, Row, Spinner } from "react-bootstrap";
4 +import { useSearchParams } from "react-router-dom";
5 +import { SearchResponse, SearchAutocompleteResult } from "./Home";
6 +import { SearchResultView } from "./SearchResultView";
7 +import { useLogger } from "../../lib/logger";
8 +
9 +enum State {
10 + IDLE,
11 + LOADED,
12 + ERROR
13 +}
14 +
15 +export default function Search() {
16 +
17 + const log = useLogger('Search')
18 + const [searchParams] = useSearchParams();
19 + const [state, setState] = useState(State.IDLE)
20 + const [searchResults, setSearchResults] = useState<SearchAutocompleteResult[]>([])
21 +
22 + const q = searchParams.get('q')
23 +
24 + useEffect(() => {
25 + axios
26 + .get(`/search?q=${q}&ac=false&client=sf-desktop`)
27 + .then(res => {
28 + log.debug(res)
29 + let response: SearchResponse = res.data
30 + if (response.code === 200) {
31 + setState(State.LOADED)
32 + setSearchResults(response.results)
33 + } else {
34 + setState(State.ERROR)
35 + }
36 + })
37 + .catch(error => {
38 + log.error(error)
39 + setState(State.ERROR)
40 + })
41 +
42 + })
43 +
44 + let resultView: JSX.Element = <div>
45 + <Spinner animation='grow' variant="danger"></Spinner>
46 + </div>
47 + let title = `Searching '${q}'...`
48 +
49 + if (state === State.ERROR) {
50 + resultView = <div>
51 + <p>Something went wrong.</p>
52 + </div>
53 + }
54 +
55 + if (state === State.LOADED) {
56 + resultView = <SearchResultView results={searchResults} />
57 + title = `Results for '${q}'.`
58 + }
59 +
60 + return (
61 + <Container className="mb-5">
62 + <Row className="mb-3">
63 + <Col>
64 + <h1>{title}</h1>
65 + </Col>
66 + </Row>
67 + <Row className="mb-3">
68 + <Col>
69 + {resultView}
70 + </Col>
71 + </Row>
72 + </Container>
73 + )
74 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/SearchResultView.tsx new
+31
@@ -0,0 +1,31 @@
1 +import { Row, Col, Container } from "react-bootstrap"
2 +import { SearchAutocompleteResult } from "./Home"
3 +
4 +interface Props {
5 + results: SearchAutocompleteResult[]
6 +}
7 +export function SearchResultView(props: Props) {
8 +
9 + let resultView
10 + if (props.results.length === 0) {
11 + resultView = <Row>
12 + <Col>No results...</Col>
13 + </Row>
14 + } else {
15 + resultView = <div>
16 + {
17 + props.results.map(result => (
18 + <Row>
19 + <Col>{result.title}</Col>
20 + </Row>
21 + ))
22 + }
23 + </div>
24 + }
25 +
26 + return (
27 + <Container>
28 + {resultView}
29 + </Container>
30 + )
31 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/esef/Split.tsx new
+12
@@ -0,0 +1,12 @@
1 +import { Col, Container, Row } from "react-bootstrap";
2 +import { Outlet } from "react-router-dom";
3 +
4 +export function Split() {
5 + return <Container>
6 + <Row>
7 + <Col>
8 + <Outlet />
9 + </Col>
10 + </Row>
11 + </Container>
12 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/esef/SplitFireView.tsx new
+81
@@ -0,0 +1,81 @@
1 +import axios from "axios"
2 +import { useState, useEffect } from "react"
3 +import { Spinner } from "react-bootstrap"
4 +import { useParams } from "react-router-dom"
5 +import { SongProvider } from "../../../models/SongResponse"
6 +import { SongProviderVote } from "../../../models/SongVotesDetailResponse"
7 +import SongVotesDetailView from "../song/SongVotesDetailView"
8 +
9 +enum State {
10 + LOADING,
11 + LOADED,
12 + ERROR
13 +}
14 +
15 +enum SFErrorCode {
16 + NOT_FOUND = 404,
17 + INTERNAL_SERVER_ERROR = 500
18 +}
19 +
20 +interface SFError {
21 + code: SFErrorCode,
22 + message: string
23 +}
24 +
25 +export enum HTTPStatusCode {
26 + OK = 200,
27 + NOT_FOUND = 404,
28 + INTERNAL_SERVER_ERROR = 500
29 +}
30 +
31 +export interface SongBridgeResponse {
32 + code: HTTPStatusCode,
33 + message: string,
34 + error: SFError | null,
35 + song_provider: SongProvider
36 + votes: SongProviderVote[]
37 +}
38 +
39 +export function SplitDetailView() {
40 +
41 + const { providerId } = useParams()
42 + const [state, setState] = useState(State.LOADING)
43 + const [songProvider, setSongProvider] = useState<SongProvider | null>(null)
44 + const [votes, setVotes] = useState<SongProviderVote[]>([])
45 +
46 + useEffect(() => {
47 + setState(State.LOADING)
48 + axios.get(`/song-bridge/${providerId}/detail`)
49 + .then(res => {
50 + const response: SongBridgeResponse = res.data
51 + if (response.code === HTTPStatusCode.OK) {
52 +
53 + console.log(response)
54 + setSongProvider(response.song_provider)
55 + setVotes(response.votes)
56 + setState(State.LOADED)
57 + } else {
58 + console.log('error')
59 + setState(State.ERROR)
60 + }
61 + })
62 + .catch(error => {
63 + console.log(error)
64 + setState(State.ERROR)
65 + })
66 + }, [providerId])
67 +
68 + if (state === State.LOADED && songProvider) {
69 + return <SongVotesDetailView songProvider={songProvider} votes={votes} />
70 + }
71 +
72 + if (state === State.LOADING) {
73 + return <div>
74 + <Spinner animation="border" role="status">
75 + <span className="visually-hidden">Loading...</span>
76 + </Spinner>
77 + </div>
78 + }
79 +
80 + return <p>Error</p>
81 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/home/Random.tsx new
+83
@@ -0,0 +1,83 @@
1 +import axios from "axios";
2 +import { useEffect, useState } from "react";
3 +import { Button, Carousel, Col, Row, Spinner } from "react-bootstrap";
4 +import { FormattedMessage } from "react-intl";
5 +import { Link } from "react-router-dom";
6 +import { SongProvider } from "../../../models/SongResponse";
7 +import { useLogger } from "../../../lib/logger";
8 +import ErrorView from "../../templates/Error";
9 +
10 +enum State {
11 + LOADING,
12 + LOADED,
13 + ERROR
14 +}
15 +
16 +export default function RandomView() {
17 +
18 + const log = useLogger('CarouselView')
19 + const [state, setState] = useState(State.LOADING)
20 + const [files, setFiles] = useState<SongProvider[]>([])
21 +
22 + useEffect(() => {
23 + setState(State.LOADING)
24 + axios.get(`/carousel`)
25 + .then(res => {
26 + const files = res.data.audio_files;
27 + log.debug(files)
28 + setFiles(files)
29 + setState(State.LOADED)
30 + })
31 + .catch(error => {
32 + log.error(error)
33 + setState(State.ERROR)
34 + })
35 + }, [])
36 +
37 + if (state === State.LOADING) {
38 + return <div>
39 + <p>
40 + <FormattedMessage id="home.loading"
41 + defaultMessage="Loading SplitFire AI..."
42 + description="Loading message" />
43 + </p>
44 + <Spinner animation='grow' variant="danger"></Spinner>
45 + </div>
46 + }
47 +
48 + if (state === State.ERROR) {
49 + return <ErrorView errorMessage="Something went wrong..." />
50 + }
51 +
52 + if (files.length === 0) {
53 + return <div>
54 + No content found
55 + </div>
56 + }
57 +
58 + return (
59 + <div>
60 + <Row>
61 + <Col xs={4}>
62 + <p className="h3">Random</p>
63 + </Col>
64 + </Row>
65 + <Carousel>
66 + {files.slice(0, 5).map(item => (
67 + <Carousel.Item key={item.id} style={{ padding: '20px' }}>
68 + <img
69 + className="d-block w-100"
70 + src={item.image_url}
71 + alt="First slide"
72 + />
73 + <Carousel.Caption>
74 + <Link to={{ pathname: `/split/${item.id}` }}>
75 + <Button variant="dark">{item.name}</Button>
76 + </Link>
77 + </Carousel.Caption>
78 + </Carousel.Item>
79 + ))}
80 + </Carousel>
81 + </div>
82 + )
83 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/home/ReadyToPlay.tsx new
+77
@@ -0,0 +1,77 @@
1 +import { useEffect, useState } from "react"
2 +import { SongProvider } from "../../../models/SongResponse"
3 +import axios from "axios"
4 +import { FormattedMessage } from "react-intl"
5 +import { Button, Carousel, Col, Row, Spinner } from "react-bootstrap"
6 +import { Link } from "react-router-dom"
7 +
8 +enum State {
9 + LOADING,
10 + LOADED,
11 + ERROR
12 +}
13 +
14 +export default function ReadyToPlayView() {
15 +
16 + const [state, setState] = useState(State.LOADING)
17 + const [files, setFiles] = useState<SongProvider[]>([])
18 +
19 + useEffect(() => {
20 + setState(State.LOADING)
21 + axios.get(`/ready-to-play`)
22 + .then(res => {
23 + const files = res.data.audio_files;
24 + console.log(files)
25 + setFiles(files)
26 + setState(State.LOADED)
27 + })
28 + .catch(error => {
29 + console.log(error)
30 + setState(State.ERROR)
31 + })
32 + }, [])
33 +
34 + if (state === State.LOADING) {
35 + return <div>
36 + <p>
37 + <FormattedMessage id="home.loading"
38 + defaultMessage="Loading top votes..."
39 + description="Loading top votes message" />
40 + </p>
41 + <Spinner animation='grow' variant="danger"></Spinner>
42 + </div>
43 + }
44 +
45 + if (state === State.ERROR) {
46 + return <></>
47 + }
48 +
49 + return (
50 + <div>
51 + <Row>
52 + <Col xs={4}>
53 + <p className="h3">Ready to Play!</p>
54 + </Col>
55 + </Row>
56 + <Row>
57 + <Carousel>
58 + {files.map((file) => (
59 + <Carousel.Item key={file.id} style={{ padding: '20px' }}>
60 + <img
61 + className="d-block w-100"
62 + src={file.image_url}
63 + alt="First slide"
64 + />
65 + <Carousel.Caption>
66 + <Link to={{ pathname: `/split/${file.id}` }}>
67 + <Button variant="dark">{file.name}</Button>
68 + </Link>
69 + </Carousel.Caption>
70 + </Carousel.Item>
71 + ))}
72 + </Carousel>
73 + </Row>
74 + </div>
75 + )
76 +
77 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/home/SearchFormView.tsx new
+102
@@ -0,0 +1,102 @@
1 +import debounce from "lodash.debounce";
2 +import { Form, Row, Col, Button, Alert, Spinner } from "react-bootstrap";
3 +import { SearchAutocompleteResult, SearchResponse } from "../Home";
4 +import { useState } from "react";
5 +import axios from "axios";
6 +import AutoCompleteView from "../AutoCompleteView";
7 +import { Navigate } from "react-router-dom";
8 +
9 +enum State {
10 + IDLE,
11 + TO_SEARCH,
12 + LOADING,
13 + LOADED,
14 + ERROR
15 +}
16 +
17 +export default function SearchView() {
18 +
19 + const [state, setState] = useState(State.IDLE)
20 + const [query, setQuery] = useState('')
21 + const [searchResults, setSearchResults] = useState<SearchAutocompleteResult[]>([])
22 + const [errorMessage, setErrorMessage] = useState('')
23 + const [validQ, setValidQ] = useState(false)
24 +
25 + const debouncedSearch = debounce(async (query: string) => {
26 + setState(State.LOADING)
27 + axios
28 + .get(`/search?q=${query}&ac=true&client=sf-desktop`)
29 + .then(res => {
30 + //console.log(res)
31 + let response: SearchResponse = res.data
32 +
33 + console.log(response)
34 + if (res.status === 200) {
35 + setSearchResults(response.results)
36 + setState(State.LOADED)
37 + } else {
38 + setState(State.ERROR)
39 + }
40 + })
41 + .catch(error => {
42 + console.log(error)
43 + setState(State.ERROR)
44 + })
45 + }, 300); // 300ms is the debounce delay
46 +
47 + const onChange = (query: string) => {
48 + if (query.length === 0) {
49 + setErrorMessage('Query is required')
50 + setValidQ(false)
51 + return
52 + }
53 + if (query.length < 3) {
54 + setErrorMessage('Query must be at least 3 characters')
55 + setValidQ(false)
56 + return
57 + }
58 + setValidQ(true)
59 + setQuery(query)
60 + debouncedSearch(query)
61 + }
62 +
63 + const onSubmit = () => {
64 + console.log('onSubmit')
65 + setState(State.TO_SEARCH)
66 + }
67 +
68 + if (state === State.TO_SEARCH) {
69 + return <Navigate to={`/search?q=${query}`} />
70 + }
71 +
72 + let validationText = <></>
73 + if (state === State.ERROR) {
74 + validationText = <Alert variant="danger" onClose={() => setErrorMessage('')} dismissible><p>{errorMessage}</p></Alert>
75 + }
76 +
77 + let autoCompleteView = <></>
78 + if (state === State.LOADING) {
79 + autoCompleteView = <Spinner animation='grow' variant="info"></Spinner>
80 + } else if (state === State.LOADED) {
81 + autoCompleteView = <AutoCompleteView results={searchResults} />
82 + }
83 +
84 + return (
85 + <Form onSubmit={(e) => e.preventDefault()}>
86 + <Row>
87 + <Col xs={8}>
88 + <Form.Control
89 + placeholder="Search song..."
90 + className="mb-3"
91 + onChange={(e) => onChange(e.target.value)}
92 + />
93 + {validationText}
94 + {autoCompleteView}
95 + </Col>
96 + <Col xs={{ span: 3, offset: 1 }}>
97 + <Button variant="primary" onClick={() => onSubmit()} disabled={!validQ}>Search</Button>
98 + </Col >
99 + </Row>
100 + </Form >
101 + )
102 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/home/TopVotesView.tsx new
+77
@@ -0,0 +1,77 @@
1 +import { useEffect, useState } from "react"
2 +import { SongProvider } from "../../../models/SongResponse"
3 +import axios from "axios"
4 +import { FormattedMessage } from "react-intl"
5 +import { Button, Carousel, Col, Row, Spinner } from "react-bootstrap"
6 +import { Link } from "react-router-dom"
7 +
8 +enum State {
9 + LOADING,
10 + LOADED,
11 + ERROR
12 +}
13 +
14 +export default function TopVotesView() {
15 +
16 + const [state, setState] = useState(State.LOADING)
17 + const [files, setFiles] = useState<SongProvider[]>([])
18 +
19 + useEffect(() => {
20 + setState(State.LOADING)
21 + axios.get(`/top-votes`)
22 + .then(res => {
23 + const files = res.data.audio_files;
24 + console.log(files)
25 + setFiles(files)
26 + setState(State.LOADED)
27 + })
28 + .catch(error => {
29 + console.log(error)
30 + setState(State.ERROR)
31 + })
32 + }, [])
33 +
34 + if (state === State.LOADING) {
35 + return <div>
36 + <p>
37 + <FormattedMessage id="home.loading"
38 + defaultMessage="Loading top votes..."
39 + description="Loading top votes message" />
40 + </p>
41 + <Spinner animation='grow' variant="danger"></Spinner>
42 + </div>
43 + }
44 +
45 + if (state === State.ERROR) {
46 + return <></>
47 + }
48 +
49 + return (
50 + <div>
51 + <Row>
52 + <Col xs={4}>
53 + <p className="h3">Top Votes</p>
54 + </Col>
55 + </Row>
56 + <Row>
57 + <Carousel>
58 + {files.map((file) => (
59 + <Carousel.Item key={file.id} style={{ padding: '20px' }}>
60 + <img
61 + className="d-block w-100"
62 + src={file.image_url}
63 + alt="First slide"
64 + />
65 + <Carousel.Caption>
66 + <Link to={{ pathname: `/split/${file.id}` }}>
67 + <Button variant="dark">{file.name}</Button>
68 + </Link>
69 + </Carousel.Caption>
70 + </Carousel.Item>
71 + ))}
72 + </Carousel>
73 + </Row>
74 + </div>
75 + )
76 +
77 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/SongView.tsx new
+125
@@ -0,0 +1,125 @@
1 +import axios from "axios";
2 +import { useCallback, useEffect, useState } from "react"
3 +import { Col, Container, Image, Row, Spinner } from "react-bootstrap";
4 +import { FormattedMessage } from "react-intl";
5 +import { useParams } from "react-router-dom";
6 +import { SongResponse } from "../../../models/SongResponse";
7 +import SongViewItems from "./SongViewItems";
8 +import SongViewConnectProvider from "./SongViewConnectProvider";
9 +import { useLogger } from "../../../lib/logger";
10 +
11 +export interface Payload {
12 + reference_id: string
13 + provider_id: string
14 + name: string
15 + provider: string
16 +}
17 +
18 +enum State {
19 + LOADING,
20 + LOADED,
21 + ERROR,
22 + NEED_UPDATE
23 +}
24 +
25 +export function SongView() {
26 +
27 + const log = useLogger('SongView')
28 + // Reference ID
29 + const { audioId } = useParams() as { audioId: string }
30 + const [state, setState] = useState(State.LOADING)
31 + const [songResponse, setSongResponse] = useState<SongResponse | null>(null)
32 +
33 + const fetchSong = useCallback(() => {
34 + setState(State.LOADING)
35 + axios.get(`/song-bridge/${audioId}`)
36 + .then(res => {
37 + log.debug(res)
38 + const response: SongResponse = res.data;
39 + log.debug(response)
40 + setSongResponse(response)
41 + setState(State.LOADED)
42 + })
43 + .catch(error => {
44 + log.error(error)
45 + setState(State.ERROR)
46 + })
47 +
48 + }, [audioId])
49 +
50 + useEffect(() => {
51 + fetchSong()
52 + }, [audioId, fetchSong])
53 +
54 + const onAddedProvider = (payload: Payload) => {
55 + fetchSong()
56 + }
57 +
58 + let mainView = <></>
59 + if (state === State.LOADING) {
60 + mainView = <div>
61 + <p>
62 + <FormattedMessage id="song.loading"
63 + defaultMessage="Searching for songs..."
64 + description="Loading message" />
65 + </p>
66 + <Spinner animation='grow' variant="warning"></Spinner>
67 + </div>
68 + }
69 +
70 + if (state === State.ERROR) {
71 + mainView = <div>
72 + <p>
73 + <FormattedMessage id="song.error"
74 + defaultMessage="Something wrong..."
75 + description="Loading message" />
76 + </p>
77 + </div>
78 + }
79 + if (state === State.LOADED && songResponse) {
80 + const { name, album_name, images, items } = songResponse
81 + log.debug(items)
82 + let topView = <></>
83 + if (songResponse?.items.length > 0) {
84 + topView = <SongViewItems items={items} referenceId={audioId} />
85 + } else {
86 + topView = <Row className="mb-3">
87 + <Col>
88 + <p>
89 + <FormattedMessage id="song.no_results"
90 + defaultMessage="No results found."
91 + description="No results message" />
92 + </p>
93 + </Col>
94 + </Row>
95 + }
96 + let imageView = <></>
97 + if (images.length > 0) {
98 + imageView = <Image
99 + className="w-100"
100 + src={images[0].url}
101 + alt="Image of the song"
102 + />
103 + }
104 +
105 + mainView = <>
106 + <Row className="mb-3">
107 + <Col xs={4}>
108 + {imageView}
109 + </Col>
110 + <Col xs={8}>
111 + <h1>{name}</h1>
112 + <h2>{album_name}</h2>
113 + </Col>
114 + </Row>
115 + {topView}
116 + <SongViewConnectProvider name={name} referenceId={audioId} onAddedProvider={onAddedProvider} />
117 + </>
118 + }
119 +
120 + return (
121 + <Container className="mb-5">
122 + {mainView}
123 + </Container>
124 + )
125 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/SongViewConnectProvider.tsx new
+139
@@ -0,0 +1,139 @@
1 +import { useEffect, useState } from "react";
2 +import { Form, Row, Col, Button, Alert } from "react-bootstrap";
3 +import { FormattedMessage } from "react-intl";
4 +import { CurrentUserType, db } from "../../../lib/db";
5 +import { Navigate } from "react-router-dom";
6 +import { Payload } from "./SongView";
7 +import axios from "axios";
8 +
9 +interface Props {
10 + referenceId: string
11 + name: string
12 + onAddedProvider: (payload: Payload) => void
13 +}
14 +
15 +enum State {
16 + IDLE,
17 + LOADING,
18 + LOADED,
19 + ERROR
20 +}
21 +
22 +export default function SongViewConnectProvider(props: Props) {
23 +
24 + const [validUrl, setValidUrl] = useState(false)
25 + const [url, setUrl] = useState("")
26 + const [errorMessage, setErrorMessage] = useState("")
27 + const [accessToken, setAccessToken] = useState<string | null>(null)
28 + const [redirectToLogin, setRedirectToLogin] = useState(false)
29 + const [, setState] = useState(State.IDLE)
30 +
31 + useEffect(() => {
32 + db
33 + .currentUser
34 + .where({ type: CurrentUserType.MAIN })
35 + .first()
36 + .then(res => {
37 + console.log(res)
38 + if (res?.accessToken) {
39 + setAccessToken(res?.accessToken)
40 + }
41 + }).catch(error => {
42 + console.log(error)
43 + })
44 + }, [])
45 +
46 + const validateYoutubeUrl = (url: string) => {
47 + if (url.length === 0) {
48 + setErrorMessage('URL is required')
49 + setValidUrl(false)
50 + }
51 + if (url.startsWith("https://www.youtube.com/watch?v=")) {
52 + setValidUrl(true)
53 + setUrl(url)
54 + setErrorMessage('')
55 + } else {
56 + setErrorMessage('Invalid YouTube URL')
57 + setValidUrl(false)
58 + }
59 + }
60 +
61 + const onAddUrl = () => {
62 + if (!accessToken) {
63 + setRedirectToLogin(true)
64 + return
65 + }
66 + if (validUrl) {
67 + const videoId = url.replace("https://www.youtube.com/watch?v=", "")
68 + console.log(videoId)
69 +
70 + addSongProvider({
71 + reference_id: props.referenceId,
72 + provider_id: videoId,
73 + name: props.name,
74 + provider: 'youtube'
75 + }, accessToken)
76 + }
77 + }
78 +
79 + const addSongProvider = (payload: Payload, accessToken: string) => {
80 + console.log(payload)
81 + setState(State.LOADING)
82 + axios
83 + .post(`/song-bridge`, payload, {
84 + headers: {
85 + 'Content-Type': 'application/json',
86 + 'Authorization': accessToken,
87 + }
88 + })
89 + .then(res => {
90 + console.log("Get results", res);
91 + setState(State.LOADED)
92 + setUrl('')
93 + props.onAddedProvider(payload)
94 + })
95 + .catch(error => {
96 + //console.log(error)
97 + setState(State.ERROR)
98 + setErrorMessage('Something went wrong')
99 + })
100 + }
101 +
102 + if (redirectToLogin) {
103 + return <Navigate to={'/login'} />
104 + }
105 +
106 + let validationText: any = ''
107 + if (errorMessage.length > 0) {
108 + validationText = <Alert variant="danger" onClose={() => setErrorMessage('')} dismissible><p>{errorMessage}</p></Alert>
109 + }
110 +
111 + return (
112 + <Row>
113 + <Col xs={3}>
114 + <h4>
115 + <FormattedMessage id="song.connect-provider"
116 + defaultMessage="Add more"
117 + description="Connect provider" />
118 + </h4>
119 + </Col>
120 + <Col xs={9}>
121 + <Form onSubmit={(e) => e.preventDefault()}>
122 + <Row>
123 + <Col xs={8}>
124 + <Form.Control
125 + placeholder="Put YouTube URL here..."
126 + className="mb-3"
127 + onChange={(e) => validateYoutubeUrl(e.target.value)}
128 + />
129 + {validationText}
130 + </Col>
131 + <Col xs={{ span: 3, offset: 1 }}>
132 + <Button variant="primary" onClick={() => onAddUrl()} disabled={!validUrl}>Add</Button>
133 + </Col >
134 + </Row>
135 + </Form >
136 + </Col>
137 + </Row>
138 + )
139 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/SongViewItems.tsx new
+152
@@ -0,0 +1,152 @@
1 +import { Row, Col, Container, Image } from "react-bootstrap";
2 +import { GiPlayButton, GiTimeBomb } from "react-icons/gi";
3 +import { MdCallSplit } from "react-icons/md";
4 +import { Payload } from "./SongView";
5 +import { useEffect, useState } from "react";
6 +import { db, CurrentUserType } from "../../../lib/db";
7 +import { Link, Navigate } from "react-router-dom";
8 +import axios from "axios";
9 +import { SongProvider } from "../../../models/SongResponse";
10 +import { Status } from "../../player/models/AudioFile";
11 +
12 +interface SongViewItemsProps {
13 + referenceId: string
14 + items: SongProvider[]
15 +}
16 +
17 +enum State {
18 + IDLE,
19 + LOADING,
20 + LOADED,
21 + ERROR
22 +}
23 +
24 +export default function SongViewItems(props: SongViewItemsProps) {
25 + return (
26 + <>
27 + <Row className="mb-3">
28 + <Col>
29 + <Container className="mb-5">
30 + <Row className="mb-3">
31 + <Col>
32 + <h3>Play this song!</h3>
33 + </Col>
34 + </Row>
35 + {props.items.map((video, index) =>
36 + <SongViewItem key={index} provider={video} referenceId={props.referenceId} />
37 + )}
38 + </Container>
39 + </Col>
40 + </Row>
41 + </>
42 + )
43 +}
44 +
45 +interface SongViewItemProps {
46 + referenceId: string
47 + provider: SongProvider
48 +}
49 +
50 +function SongViewItem(props: SongViewItemProps) {
51 +
52 + useEffect(() => {
53 + db
54 + .currentUser
55 + .where({ type: CurrentUserType.MAIN })
56 + .first()
57 + .then(res => {
58 + console.log(res)
59 + if (res?.accessToken) {
60 + setAccessToken(res?.accessToken)
61 + }
62 + }).catch(error => {
63 + console.log(error)
64 + })
65 + }, [])
66 +
67 + const [state, setstate] = useState(State.IDLE)
68 + const [accessToken, setAccessToken] = useState<string | null>(null)
69 + const [redirectToLogin, setRedirectToLogin] = useState(false)
70 +
71 + const onRequestSplit = (videoId: string) => {
72 + if (!accessToken) {
73 + setRedirectToLogin(true)
74 + return
75 + }
76 +
77 + addSongProvider({
78 + reference_id: props.referenceId,
79 + provider_id: videoId,
80 + name: props.provider.name,
81 + provider: 'youtube'
82 + }, accessToken)
83 + }
84 +
85 + const addSongProvider = (payload: Payload, accessToken: string) => {
86 + console.log(payload)
87 + setstate(State.LOADING)
88 + axios
89 + .post(`/split`, { id: props.provider.id, provider_id: payload.provider_id, provider: payload.provider }, {
90 + headers: {
91 + 'Content-Type': 'application/json',
92 + 'Authorization': accessToken,
93 + }
94 + })
95 + .then(res => {
96 + console.log("Get results", res);
97 + setstate(State.IDLE)
98 + // Refresh the list to update the split button
99 + })
100 + .catch(error => {
101 + console.log(error)
102 + setstate(State.ERROR)
103 + })
104 + }
105 +
106 + if (redirectToLogin) {
107 + return <Navigate to={'/login'} />
108 + }
109 + let splitRequestButton = <></>
110 + console.log(props.provider)
111 + if (state === State.IDLE && !props.provider.audio_file) {
112 + splitRequestButton = <MdCallSplit
113 + onClick={() => onRequestSplit(props.provider.provider_id)}
114 + size={50}
115 + style={{ cursor: 'pointer' }}
116 + title="Request split"
117 + />
118 + } else if (
119 + state === State.IDLE &&
120 + props.provider.audio_file &&
121 + props.provider.audio_file.status === Status.DONE
122 + ) {
123 + splitRequestButton = <Link to={`/splitfire/${props.provider.id}`} aria-current="page">
124 + <GiPlayButton size={50} style={{ cursor: 'pointer' }} title="Play" />
125 + </Link>
126 + } else if (
127 + state === State.IDLE &&
128 + props.provider.audio_file &&
129 + (props.provider.audio_file.status === Status.SPLITTING || props.provider.audio_file.status === Status.DOWNLOADING)
130 + ) {
131 + splitRequestButton = <Link to={`/song/votes/${props.provider.id}`} aria-current="page">
132 + <GiTimeBomb size={50} style={{ cursor: 'pointer' }} title="Waiting for split" />
133 + </Link>
134 + }
135 +
136 +
137 + if (state === State.LOADING) {
138 + splitRequestButton = <div>
139 + <p>Loading...</p>
140 + </div>
141 + }
142 +
143 + return (
144 + <Row className="mb-3 align-items-center">
145 + <Col xs={9} className="text-start">
146 + <h4>{props.provider.name}</h4>
147 + <Image src={`https://img.youtube.com/vi/${props.provider.provider_id}/mqdefault.jpg`} thumbnail />
148 + </Col>
149 + <Col xs={3}>{splitRequestButton}</Col>
150 + </Row >
151 + )
152 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/SongVotes.tsx new
+7
@@ -0,0 +1,7 @@
1 +import { Outlet } from "react-router-dom";
2 +
3 +export default function SongVotes() {
4 + return (
5 + <Outlet />
6 + )
7 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/SongVotesDetailView.tsx new
+21
@@ -0,0 +1,21 @@
1 +import { Container } from "react-bootstrap"
2 +import { SongProvider } from "../../../models/SongResponse"
3 +import { SongProviderVote } from "../../../models/SongVotesDetailResponse"
4 +import { YoutubeView } from "./components/YoutubeView"
5 +import { TitleView } from "./components/TitleView"
6 +import { UpDownVotesView } from "./components/UpDownVotes"
7 +
8 +interface Props {
9 + songProvider: SongProvider,
10 + votes: SongProviderVote[]
11 +}
12 +
13 +export default function SongVotesDetailView(props: Props) {
14 + return (
15 + <Container className="mb-5">
16 + <TitleView name={props.songProvider.name} />
17 + <UpDownVotesView votes={props.votes} providerId={props.songProvider.id} audioFile={props.songProvider.audio_file} />
18 + <YoutubeView provider_id={props.songProvider.provider_id} />
19 + </Container>
20 + )
21 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/SongVotesView.tsx new
+7
@@ -0,0 +1,7 @@
1 +export default function SongVotesView() {
2 + return (
3 + <div>
4 + <h1>19</h1>
5 + </div>
6 + )
7 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/components/ButtonSplit.tsx new
+99
@@ -0,0 +1,99 @@
1 +import { useContext, useState } from "react"
2 +import { AudioFile, Status } from "../../../player/models/AudioFile"
3 +import { UserContext } from "../../../../lib/CurrentUserContext"
4 +import requestSplitService, { SplitResponse } from "../../../../lib/requestSplitService"
5 +import { Col, Spinner } from "react-bootstrap"
6 +import { GiSandsOfTime, GiSpaceShuttle } from "react-icons/gi"
7 +import { MdNotificationImportant } from "react-icons/md"
8 +import { Navigate } from "react-router-dom"
9 +import { HTTPStatusCode } from "../../esef/SplitFireView"
10 +import { BsPlayCircle } from "react-icons/bs"
11 +
12 +enum State {
13 + LOADING,
14 + LOADED,
15 + ERROR,
16 +}
17 +
18 +export function ButtonSplit(props: { providerId: string, audioFile: AudioFile | null, aggregateVotes: number }) {
19 +
20 + const [state, setState] = useState(State.LOADED)
21 + const { user } = useContext(UserContext)
22 + const [goToLogin, setGoToLogin] = useState(false)
23 + const [audioFile, setAudioFile] = useState<AudioFile | null>(null)
24 +
25 + useState(() => {
26 + setAudioFile(props.audioFile)
27 + })
28 +
29 + const splitRequest = () => {
30 + if (!user || !user.accessToken) {
31 + setGoToLogin(true)
32 + return
33 + }
34 +
35 + setState(State.LOADING)
36 + requestSplitService(props.providerId, user.accessToken)
37 + .then(res => {
38 + console.log(res)
39 + const response: SplitResponse = res.data
40 + if (response.code === HTTPStatusCode.OK) {
41 + setAudioFile(response.audio_file)
42 + setState(State.LOADED)
43 + } else {
44 + setState(State.ERROR)
45 + }
46 + })
47 + .catch(error => {
48 + console.log(error)
49 + setState(State.ERROR)
50 + })
51 + }
52 +
53 + // Default to production value.
54 + const splitTreshold = process.env.REACT_APP_SPLIT_THRESHOLD ? parseInt(process.env.REACT_APP_SPLIT_THRESHOLD) : 5
55 +
56 + const isDoneSplitting = audioFile && audioFile.status === Status.DONE
57 + const isCurrentlySplitting = audioFile && (audioFile.status === Status.SPLITTING || audioFile.status === Status.DOWNLOADING)
58 + const isReadyToSplit = props.aggregateVotes > splitTreshold
59 +
60 + if (goToLogin) {
61 + return <Navigate to={'/login'} />
62 + }
63 +
64 + if (state === State.LOADING) {
65 + return <Col className="align-self-center" xs={12}>
66 + <Spinner animation="border" role="status">
67 + <span className="visually-hidden">Loading...</span>
68 + </Spinner>
69 + </Col>
70 + }
71 + // Check if we have processed the split request.
72 + if (isDoneSplitting) {
73 + return <Col className="align-self-center mb-3 mt-3" xs={12}>
74 + <h1>Let's Play!</h1>
75 + </Col>
76 + } else if (isCurrentlySplitting) {
77 + return <Col className="align-self-center mb-3 mt-3" xs={12}>
78 + <GiSandsOfTime
79 + size={40}
80 + className="mb-3"
81 + color="green"
82 + title="Split request in progress..." />
83 + </Col>
84 + } else if (isReadyToSplit) {
85 + return <Col className="align-self-center mb-3 mt-3" xs={12}>
86 + <GiSpaceShuttle
87 + size={40}
88 + className="mb-3"
89 + onClick={splitRequest}
90 + style={{ cursor: "pointer" }}
91 + color="green"
92 + title="Split request..." />
93 + </Col>
94 + }
95 +
96 + return <Col className="align-self-center mb-3 mt-3" xs={12}>
97 + <MdNotificationImportant size={40} className="mb-3" color="green" title="Not enough votes to split..." />
98 + </Col>
99 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/components/TitleView.tsx new
+11
@@ -0,0 +1,11 @@
1 +import { Col, Row } from "react-bootstrap";
2 +
3 +export function TitleView(props: { name: string }) {
4 + return (
5 + <Row className="mb-5">
6 + <Col>
7 + <h1 className="title text-center">{`Upvotes "${props.name}" so we can jam together!`}</h1>
8 + </Col>
9 + </Row>
10 + )
11 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/components/UpDownVotes.tsx new
+213
@@ -0,0 +1,213 @@
1 +import axios from "axios"
2 +import { CSSProperties, useState, useContext } from "react"
3 +import { Row, Col, Spinner } from "react-bootstrap"
4 +import { BsArrowUpCircleFill, BsArrowDownCircleFill, BsPlayBtnFill } from "react-icons/bs"
5 +import { Navigate } from "react-router-dom"
6 +import { UserContext } from "../../../../lib/CurrentUserContext"
7 +import { CurrentUser } from "../../../../lib/db"
8 +import { SongProviderVote } from "../../../../models/SongVotesDetailResponse"
9 +import { AudioFile, Status } from "../../../player/models/AudioFile"
10 +import { HTTPStatusCode, SongBridgeResponse } from "../../esef/SplitFireView"
11 +import { ButtonSplit } from "./ButtonSplit"
12 +import { VoterGravatarsViews } from "./VoterGravatarsViews"
13 +
14 +enum State {
15 + LOADING,
16 + LOADED,
17 + ERROR,
18 +}
19 +
20 +interface UpDownVotesViewProps {
21 + providerId: string
22 + votes: SongProviderVote[]
23 + audioFile: AudioFile | null
24 +}
25 +
26 +export enum VoteType {
27 + UP = 'up',
28 + DOWN = 'down'
29 +}
30 +
31 +interface VoteState {
32 + aggregate: number
33 + buttonUpStyle: CSSProperties
34 + buttonDownStyle: CSSProperties
35 + upVotes: SongProviderVote[]
36 + downVotes: SongProviderVote[]
37 +}
38 +
39 +export function UpDownVotesView(props: UpDownVotesViewProps) {
40 +
41 + const [state, setState] = useState(State.LOADED)
42 + const [goToLogin, setGoToLogin] = useState(false)
43 + const [goToPlayer, setGoToPlayer] = useState(false)
44 + const { user } = useContext(UserContext)
45 +
46 + const buttonStyle = (votes: SongProviderVote[], type: VoteType): CSSProperties => {
47 + if (!user) {
48 + return { cursor: "pointer" }
49 + }
50 +
51 + if (type === VoteType.UP) {
52 + if (shouldDisableButton(votes, VoteType.UP, user)) {
53 + return { pointerEvents: "none", opacity: "0.4" }
54 + } else {
55 + return { cursor: "pointer" }
56 + }
57 + } else {
58 + if (shouldDisableButton(votes, VoteType.DOWN, user)) {
59 + return { pointerEvents: "none", opacity: "0.4" }
60 + } else {
61 + return { cursor: "pointer" }
62 + }
63 + }
64 + }
65 +
66 + const calculateVotes = (votes: SongProviderVote[]): number => {
67 + if (votes.length === 0) {
68 + return 0
69 + }
70 +
71 + const up = votes.filter((x) => x.vote_type === VoteType.UP).length
72 + const down = votes.filter((x) => x.vote_type === VoteType.DOWN).length
73 + //console.log('upvotes', up)
74 + //console.log('downvotes', down)
75 + const votesAggregate = up - down
76 + return votesAggregate
77 + }
78 +
79 + const shouldDisableButton = (votes: SongProviderVote[], type: VoteType, user: CurrentUser) => {
80 + if (!user) {
81 + return false
82 + }
83 + const x = votes.filter((y) => {
84 + if (y.user_id === user.user.id && y.vote_type === type)
85 + return true
86 + return false
87 + })
88 + return x.length > 0
89 + }
90 +
91 + const [voteState, setVoteState] = useState<VoteState>({
92 + aggregate: calculateVotes(props.votes),
93 + buttonUpStyle: buttonStyle(props.votes, VoteType.UP),
94 + buttonDownStyle: buttonStyle(props.votes, VoteType.DOWN),
95 + upVotes: props.votes.filter((x) => x.vote_type === VoteType.UP),
96 + downVotes: props.votes.filter((x) => x.vote_type === VoteType.DOWN)
97 + })
98 +
99 + const vote = (type: VoteType) => {
100 +
101 + if (!user || !user.accessToken || user.accessToken.length <= 1) {
102 + setGoToLogin(true)
103 + return
104 + }
105 +
106 + setState(State.LOADING)
107 + axios
108 + .post(`/song-bridge/${props.providerId}/vote`, { vote_type: type, provider_id: props.providerId },
109 + {
110 + headers: {
111 + 'Content-Type': 'application/json',
112 + 'Authorization': user.accessToken,
113 + }
114 + })
115 + .then(res => {
116 + const response: SongBridgeResponse = res.data
117 + //console.log(response.votes)
118 + if (response.code === HTTPStatusCode.OK) {
119 + setVoteState({
120 + aggregate: calculateVotes(response.votes),
121 + buttonUpStyle: buttonStyle(response.votes, VoteType.UP),
122 + buttonDownStyle: buttonStyle(response.votes, VoteType.DOWN),
123 + upVotes: response.votes.filter((x) => x.vote_type === VoteType.UP),
124 + downVotes: response.votes.filter((x) => x.vote_type === VoteType.DOWN)
125 + })
126 + setState(State.LOADED)
127 + } else {
128 + console.log('error')
129 + setState(State.ERROR)
130 + }
131 + })
132 + .catch(error => {
133 + console.log(error)
134 + setState(State.ERROR)
135 + })
136 + }
137 +
138 + const isDoneSplitting = props.audioFile && props.audioFile.status === Status.DONE
139 +
140 + if (goToLogin) {
141 + return <Navigate to={'/login'} />
142 + }
143 +
144 + if (goToPlayer) {
145 + return <Navigate to={`/player/${props.audioFile?.id}`} />
146 +}
147 +
148 + if (state === State.LOADING) {
149 + return <Row className="mb-3" style={{ minHeight: 250 }}>
150 + <Col className="align-self-center">
151 + <Spinner animation="border" role="status">
152 + <span className="visually-hidden">Loading...</span>
153 + </Spinner>
154 + </Col>
155 + </Row>
156 + }
157 +
158 + const mainView = isDoneSplitting ? <LetsPlayView onClick={() => setGoToPlayer(true)}/>
159 + : <MainVotesView voteState={voteState} vote={vote} />
160 +
161 + return (
162 + <Row className="mb-3 mt-3" style={{ minHeight: 250 }}>
163 + <ButtonSplit providerId={props.providerId} audioFile={props.audioFile} aggregateVotes={voteState.aggregate} />
164 + {mainView}
165 + </Row>
166 + )
167 +}
168 +
169 +function LetsPlayView(props: { onClick: () => void }) {
170 +
171 + const { onClick } = props
172 +
173 + return <Col className="align-self-center" xs={12}>
174 + <BsPlayBtnFill onClick={onClick} size={50} cursor={'pointer'} color="green"/>
175 + </Col>
176 +}
177 +
178 +function MainVotesView(props: { voteState: VoteState, vote: (type: VoteType) => void }) {
179 +
180 + const { voteState, vote } = props
181 +
182 + return <>
183 + <Col xs={5}>
184 + <VoterGravatarsViews
185 + voters={voteState.upVotes}
186 + type={VoteType.UP}
187 + />
188 + </Col>
189 + <Col xs={2}>
190 + <BsArrowUpCircleFill
191 + size={40}
192 + className="mb-3"
193 + style={voteState.buttonUpStyle}
194 + onClick={() => vote(VoteType.UP)}
195 + color="green"
196 + />
197 + <h1 className="votes">{voteState.aggregate}</h1>
198 + <BsArrowDownCircleFill
199 + size={40}
200 + className="mt-3"
201 + style={voteState.buttonDownStyle}
202 + onClick={() => vote(VoteType.DOWN)}
203 + color="green"
204 + />
205 + </Col>
206 + <Col xs={5} className="align-self-end">
207 + <VoterGravatarsViews
208 + voters={voteState.downVotes}
209 + type={VoteType.DOWN}
210 + />
211 + </Col>
212 +</>
213 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/components/VoterGravatarsViews.tsx new
+21
@@ -0,0 +1,21 @@
1 +import { Col, Container, Row, Image } from "react-bootstrap"
2 +import { Link } from "react-router-dom"
3 +import { SongProviderVote } from "../../../../models/SongVotesDetailResponse"
4 +import { VoteType } from "./UpDownVotes"
5 +
6 +export function VoterGravatarsViews(props: { voters: SongProviderVote[], type: VoteType }) {
7 + const className = props.type === VoteType.DOWN ? "justify-content-end" : "justify-content-start"
8 + return (
9 + <Container>
10 + <Row className={className}>
11 + {props.voters.map((x, i) => {
12 + return <Col xs={2} key={i} >
13 + <Link to={`/@${x.voter_username_or_id}`}>
14 + <Image roundedCircle src={x.voter_gravatar} width={24} />
15 + </Link>
16 + </Col>
17 + })}
18 + </Row>
19 + </Container>
20 + )
21 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/pages/song/components/YoutubeView.tsx new
+26
@@ -0,0 +1,26 @@
1 +import { Col, Row } from "react-bootstrap"
2 +import YouTube, { Options } from "react-youtube"
3 +
4 +export function YoutubeView(props: { provider_id: string }) {
5 + const opts: Options = {
6 + width: '100%',
7 + playerVars: {
8 + // https://developers.google.com/youtube/player_parameters
9 + autoplay: 0,
10 + mute: 0,
11 + controls: 0,
12 + rel: 0,
13 + showinfo: 0
14 + },
15 + }
16 + return (
17 + <Row className="mt-5">
18 + <Col>
19 + <YouTube
20 + videoId={props.provider_id}
21 + opts={opts}
22 + />
23 + </Col>
24 + </Row>
25 + )
26 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/AudioFile.tsx new
+7
@@ -0,0 +1,7 @@
1 +export default function AudioFile() {
2 + return (
3 + <div>
4 + AudioFile
5 + </div>
6 + )
7 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/AudioPlayer.tsx new
+442
@@ -0,0 +1,442 @@
1 +/* eslint-disable no-script-url */
2 +/* eslint-disable jsx-a11y/anchor-is-valid */
3 +import axios from 'axios';
4 +import { CSSProperties } from 'react';
5 +import { Component } from 'react';
6 +import { Col, Row, Spinner } from 'react-bootstrap';
7 +import YouTube, { Options } from 'react-youtube';
8 +import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys, GiPlayButton, GiPauseButton } from 'react-icons/gi';
9 +import { IconContext } from 'react-icons';
10 +import { db, AudioFiles } from '../../lib/db';
11 +import { FormattedMessage } from 'react-intl';
12 +import { Channel, Player } from 'tone';
13 +import ActionCable from 'actioncable';
14 +import { Result } from './models/Result';
15 +import { AudioFile, Status } from './models/AudioFile';
16 +import { YouTubeEventTarget } from './models/YouTubeEventTarget';
17 +import { YouTubePlayerState } from './models/YouTubePlayerState';
18 +import { Mode } from './models/Mode';
19 +import { LoadingView } from '../templates/LoadingView';
20 +
21 +const buttonStyle: CSSProperties = {
22 +
23 +}
24 +
25 +type AudioProps = {
26 + audioId: string
27 +}
28 +
29 +type AudioState = {
30 + errorMessage: string
31 + audioFile: AudioFile | null
32 + playerTime: number
33 + isPlayerReady: boolean
34 + isYoutubePlayerReady: boolean
35 + isPlaying: boolean
36 + vocalAudioOn: boolean
37 + guitarAudioOn: boolean
38 + bassAudioOn: boolean
39 + drumsAudioOn: boolean
40 + pianoAudioOn: boolean
41 +}
42 +
43 +const actionCableHost = process.env.REACT_APP_ACTION_CABLE_URL || 'ws://localhost:3000/cable'
44 +
45 +class AudioPlayer extends Component<AudioProps, AudioState> {
46 +
47 + vocalPlayer: Player | null = null;
48 + otherPlayer: Player | null = null;
49 + bassPlayer: Player | null = null;
50 + drumsPlayer: Player | null = null;
51 + pianoPlayer: Player | null = null;
52 +
53 + youTubeEventTarget: YouTubeEventTarget | null = null
54 + updateYouTubePlayerStatusIntervalId: any | null = null
55 +
56 + debugChannel: any
57 + cableApp = ActionCable.createConsumer(actionCableHost)
58 +
59 + constructor(props: AudioProps) {
60 + super(props);
61 + this.state = {
62 + errorMessage: '',
63 + audioFile: null,
64 + playerTime: 0,
65 + isPlayerReady: false,
66 + isYoutubePlayerReady: false,
67 + isPlaying: false,
68 + vocalAudioOn: true,
69 + guitarAudioOn: true,
70 + bassAudioOn: true,
71 + drumsAudioOn: true,
72 + pianoAudioOn: true,
73 + }
74 +
75 + const received = (data: AudioFile) => {
76 + this._updateProgress(data)
77 + }
78 +
79 + const conected = () => { }
80 +
81 + this.debugChannel = this.cableApp.subscriptions.create({
82 + channel: 'FileProcessingChannel',
83 + audioFileId: this.state.audioFile?.id
84 + }, {
85 + conected,
86 + received
87 + })
88 + }
89 +
90 + componentDidMount() {
91 + const audioFileId = this.props.audioId;
92 + axios.get(`/splitfire/${audioFileId}`)
93 + .then(res => {
94 + // console.log("Get results", res);
95 + const audioFile = res.data.audio_file;
96 + this.setState({ audioFile });
97 + // console.log(this.state);
98 + this._downloadAudioFilesIfNeeded();
99 + })
100 + .catch(error => {
101 + //console.log(error)
102 + this.setState({ errorMessage: 'Something error with this file.' })
103 + });
104 + }
105 +
106 + componentWillUnmount() {
107 + this.setState({ isPlaying: false })
108 + this.vocalPlayer?.stop()
109 + this.bassPlayer?.stop()
110 + this.drumsPlayer?.stop()
111 + this.pianoPlayer?.stop()
112 + this.otherPlayer?.stop()
113 +
114 + this.cableApp.disconnect()
115 + }
116 +
117 + _updateProgress(data: AudioFile) {
118 + //console.log(data)
119 +
120 + this.setState({ audioFile: data })
121 +
122 + if (this.state.audioFile?.status === Status.DONE)
123 + window.location.reload();
124 + }
125 +
126 + _downloadAudioFilesIfNeeded() {
127 +
128 + if (this.state.audioFile?.status !== Status.DONE)
129 + return
130 +
131 + this._prepareAudio(Mode.Vocal);
132 + this._prepareAudio(Mode.Bass);
133 + this._prepareAudio(Mode.Drum);
134 + this._prepareAudio(Mode.Guitar);
135 + this._prepareAudio(Mode.Keyboard);
136 + }
137 +
138 + async _downloadFile(type: Mode) {
139 + // //console.log('_downloadFile', type);
140 + let audioFile: Result | undefined;
141 + switch (type) {
142 + case Mode.Vocal:
143 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('vocals') });
144 + break;
145 + case Mode.Bass:
146 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
147 + break;
148 + case Mode.Drum:
149 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
150 + break;
151 + case Mode.Guitar:
152 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
153 + break;
154 + case Mode.Keyboard:
155 + audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('piano') });
156 + break;
157 + }
158 +
159 + const audioFileId = this.props.audioId;
160 + // console.log("Download url", audioFile?.source_file);
161 + axios({
162 + url: audioFile?.source_file,
163 + method: 'GET',
164 + responseType: 'blob',
165 + }).then((response) => {
166 + const file = new Blob([response.data], { type: 'audio/mp3' });
167 + const item: AudioFiles = {
168 + audioFileId, type: type, file
169 + }
170 + db.audioFiles.add(item);
171 + // console.log('File Downloaded', file);
172 + this._setAudioSrc(type, file);
173 + }).catch(error => {
174 + // console.log(error);
175 + });
176 + }
177 +
178 + async _prepareAudio(type: Mode) {
179 + const audioFileId = this.props.audioId;
180 + db.audioFiles
181 + .get({ audioFileId: audioFileId, type: type })
182 + .then((record: AudioFiles | undefined) => {
183 + if (record?.file === undefined) {
184 + this._downloadFile(type);
185 + return
186 + }
187 + this._setAudioSrc(type, record.file)
188 + })
189 + .catch((onrejected: any) => {
190 + // console.log("Rejected", onrejected);
191 + });
192 + }
193 +
194 + _setAudioSrc(type: Mode, file: Blob) {
195 + // Get window.URL object
196 + const URL = window.URL || window.webkitURL;
197 + // Create and revoke ObjectURL
198 + const audioFileURL = URL.createObjectURL(file);
199 + const channel = new Channel().toDestination();
200 +
201 + switch (type) {
202 + case Mode.Vocal:
203 + this.vocalPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
204 + this.vocalPlayer.connect(channel);
205 + break;
206 + case Mode.Bass:
207 + this.bassPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
208 + this.bassPlayer.connect(channel);
209 + break;
210 + case Mode.Drum:
211 + this.drumsPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
212 + this.drumsPlayer.connect(channel);
213 + break;
214 + case Mode.Guitar:
215 + this.otherPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
216 + this.otherPlayer.connect(channel);
217 + break;
218 + case Mode.Keyboard:
219 + this.pianoPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
220 + this.pianoPlayer.connect(channel);
221 + break;
222 + }
223 + }
224 +
225 + _updatePlayerStatus: (() => void) = () => {
226 + if (
227 + this.vocalPlayer?.loaded &&
228 + this.drumsPlayer?.loaded &&
229 + this.otherPlayer?.loaded &&
230 + this.bassPlayer?.loaded &&
231 + this.pianoPlayer?.loaded
232 + ) {
233 + this.setState({ isPlayerReady: true });
234 + }
235 + }
236 +
237 + togglePlayAudio() {
238 + this.setState({ isPlaying: !this.state.isPlaying })
239 + if (this.state.isPlaying) {
240 + this.vocalPlayer?.stop()
241 + this.bassPlayer?.stop()
242 + this.drumsPlayer?.stop()
243 + this.pianoPlayer?.stop()
244 + this.otherPlayer?.stop()
245 + this.youTubeEventTarget?.pauseVideo()
246 + } else {
247 + this.youTubeEventTarget?.seekTo(0)
248 + this.youTubeEventTarget?.playVideo()
249 + this.vocalPlayer?.start();
250 + this.bassPlayer?.start();
251 + this.drumsPlayer?.start();
252 + this.pianoPlayer?.start();
253 + this.otherPlayer?.start();
254 + }
255 + }
256 +
257 + setToggleInstrument(mode: Mode) {
258 + switch (mode) {
259 + case Mode.Vocal:
260 + if (!this.vocalPlayer) return;
261 + this.setState({ vocalAudioOn: !this.state.vocalAudioOn });
262 + this.vocalPlayer.mute = this.state.vocalAudioOn;
263 + break;
264 + case Mode.Bass:
265 + if (!this.bassPlayer) return;
266 + this.setState({ bassAudioOn: !this.state.bassAudioOn });
267 + this.bassPlayer.mute = this.state.bassAudioOn;
268 + break;
269 + case Mode.Guitar:
270 + if (!this.otherPlayer) return;
271 + this.setState({ guitarAudioOn: !this.state.guitarAudioOn });
272 + this.otherPlayer.mute = this.state.guitarAudioOn;
273 + break;
274 + case Mode.Keyboard:
275 + if (!this.pianoPlayer) return;
276 + this.setState({ pianoAudioOn: !this.state.pianoAudioOn });
277 + this.pianoPlayer.mute = this.state.pianoAudioOn;;
278 + break;
279 + case Mode.Drum:
280 + if (!this.drumsPlayer) return;
281 + this.setState({ drumsAudioOn: !this.state.drumsAudioOn });
282 + this.drumsPlayer.mute = this.state.drumsAudioOn;
283 + break;
284 + }
285 + }
286 +
287 + render() {
288 +
289 + const { audioFile, errorMessage, isPlayerReady, isYoutubePlayerReady, isPlaying } = this.state
290 +
291 + if (errorMessage.length > 0) {
292 + return (
293 + <p>Something wrong with this file.</p>
294 + )
295 + }
296 +
297 + if (audioFile?.status === Status.DOWNLOADING || audioFile?.status === Status.SPLITTING) {
298 + return <p>Processing file: {audioFile.progress}% </p>
299 + }
300 +
301 + if (isPlayerReady) {
302 + const opts: Options = {
303 + width: '100%',
304 + playerVars: {
305 + // https://developers.google.com/youtube/player_parameters
306 + autoplay: 0,
307 + mute: 1,
308 + controls: 0,
309 + rel: 0,
310 + showinfo: 0
311 + },
312 + };
313 + let togglePlayButton = <div>
314 + <p>
315 + <FormattedMessage id="player.loadingYoutube"
316 + defaultMessage="Generating video..."
317 + description="Loading message" />
318 + </p>
319 + <Spinner animation='grow' variant="danger"></Spinner>
320 + </div>
321 + if (isYoutubePlayerReady) {
322 + let button
323 + if (isPlaying) {
324 + button = <Col>
325 + <GiPauseButton
326 + color={isPlaying ? `white` : `red`}
327 + onClick={() => this.togglePlayAudio()}
328 + style={buttonStyle} />
329 + </Col>
330 + } else {
331 + button = <Col>
332 + <GiPlayButton
333 + color={isPlaying ? `white` : `red`}
334 + onClick={() => this.togglePlayAudio()}
335 + style={buttonStyle} />
336 + </Col>
337 + }
338 + togglePlayButton = <Col>
339 + <p>
340 + <FormattedMessage id="player.instruction"
341 + defaultMessage="Click isntrument icon to mute part of the song."
342 + description="Instruction message" />
343 + </p>
344 + {button}
345 + </Col>
346 + }
347 +
348 + return (
349 + <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
350 + <Row className="mb-3 mt-3">
351 + <Col>
352 + <h1>{this.state.audioFile?.name}</h1>
353 + </Col>
354 + </Row>
355 + <Row className="mb-3 mt-3">
356 + {togglePlayButton}
357 + </Row>
358 + <Row className="mb-3 mt-3">
359 + <Col>
360 + <GiMicrophone
361 + color={this.state.vocalAudioOn ? `white` : `red`}
362 + onClick={() => this.setToggleInstrument(Mode.Vocal)}
363 + style={buttonStyle} />
364 + </Col>
365 + <Col>
366 + <GiDrumKit
367 + color={this.state.drumsAudioOn ? `white` : `red`}
368 + onClick={() => this.setToggleInstrument(Mode.Drum)}
369 + style={buttonStyle} />
370 + </Col>
371 + <Col>
372 + <GiGuitarBassHead
373 + color={this.state.bassAudioOn ? `white` : `red`}
374 + onClick={() => this.setToggleInstrument(Mode.Bass)}
375 + style={buttonStyle} />
376 + </Col>
377 + <Col>
378 + <GiGuitarHead
379 + color={this.state.guitarAudioOn ? `white` : `red`}
380 + onClick={() => this.setToggleInstrument(Mode.Guitar)}
381 + style={buttonStyle} />
382 + </Col>
383 + <Col>
384 + <GiPianoKeys
385 + color={this.state.pianoAudioOn ? `white` : `red`}
386 + onClick={() => this.setToggleInstrument(Mode.Keyboard)}
387 + style={buttonStyle} />
388 + </Col>
389 + </Row>
390 + <Row>
391 + <Col style={{ display: isYoutubePlayerReady ? 'block' : 'none', pointerEvents: 'none' }}>
392 + <YouTube
393 + videoId={this.state.audioFile?.youtube_video_id}
394 + opts={opts}
395 + onReady={this._onReady.bind(this)}
396 + onStateChange={this._onStateChange.bind(this)}
397 + />
398 + </Col>
399 + </Row>
400 + </IconContext.Provider>
401 + )
402 + } else {
403 + return <LoadingView />
404 + }
405 + }
406 +
407 + _onReady(event: any) {
408 + this.youTubeEventTarget = event.target
409 + this.youTubeEventTarget?.playVideo()
410 + this.updateYouTubePlayerStatusIntervalId = setInterval(() => {
411 + if (!this.state.isYoutubePlayerReady) {
412 + this._updateYouTubePlayerStatusIfNeeded()
413 + }
414 + }, 2000)
415 + }
416 +
417 + _onStateChange(event: any) {
418 + if (!this.youTubeEventTarget) return
419 +
420 + if (this.youTubeEventTarget.getPlayerState() === YouTubePlayerState.ended) {
421 + this.setState({ isPlaying: false })
422 + }
423 + }
424 +
425 + _updateYouTubePlayerStatusIfNeeded() {
426 +
427 + if (this.state.isYoutubePlayerReady) return
428 + if (!this.youTubeEventTarget) return
429 +
430 + // Let's say 5 seconds buffer is enough.
431 + if (this.youTubeEventTarget?.getCurrentTime() > 5) {
432 + this.setState({ isYoutubePlayerReady: true })
433 + this.youTubeEventTarget?.pauseVideo()
434 +
435 + if (this.updateYouTubePlayerStatusIntervalId)
436 + clearInterval(this.updateYouTubePlayerStatusIntervalId)
437 + }
438 + }
439 +
440 +}
441 +
442 +export default AudioPlayer;
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/AudioPlayerView.tsx new
+20
@@ -0,0 +1,20 @@
1 +import { useContext } from "react";
2 +import { Navigate, useParams } from "react-router-dom";
3 +import AudioPlayer from "./AudioPlayer";
4 +import { UserContext } from "../../lib/CurrentUserContext";
5 +
6 +export function AudioPlayerView() {
7 +
8 + const { audioId } = useParams()
9 + const { user } = useContext(UserContext)
10 +
11 + if (!user) {
12 + return <Navigate to={'/login'} />
13 + }
14 +
15 + if (user && audioId) {
16 + return <AudioPlayer audioId={audioId} />
17 + }
18 +
19 + return <p>Something wrong</p>
20 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/ControlButtons.tsx new
+43
@@ -0,0 +1,43 @@
1 +import { Col, Row } from "react-bootstrap"
2 +import { BsPlayBtnFill, BsStopBtnFill, BsFillRecordBtnFill, BsPauseBtnFill } from "react-icons/bs"
3 +
4 +export function ControlButtonsView(props: { isPlaying: boolean, isRecording: boolean, onClick: () => void, onStop: () => void, onRecord: () => void }) {
5 +
6 + const { isPlaying, isRecording, onClick, onStop, onRecord } = props
7 +
8 + const _onPlayOrPause = () => {
9 + if (isRecording) return
10 +
11 + onClick()
12 + }
13 +
14 + return <Row>
15 + <Col xs={{span: 1, offset: 4}} className="mb-3 mt-3" color="red" >
16 + { isPlaying ? <BsPauseBtnFill onClick={_onPlayOrPause} size={32} style={ { cursor: isRecording ? undefined : "pointer"}} title="Pause" color={ isRecording ? "grey" : "white"} />
17 + : <BsPlayBtnFill onClick={_onPlayOrPause} size={32} style={ { cursor: isRecording ? undefined : "pointer"}} title="Play" color={ isRecording ? "grey" : "white" } />
18 + }
19 + </Col>
20 + <Col xs={{span: 1}} className="mb-3 mt-3">
21 + <BsStopBtnFill
22 + onClick={() => {
23 + if (isRecording) return
24 +
25 + onStop()
26 + }}
27 + size={32}
28 + style={{ cursor: isRecording ? undefined : "pointer"}}
29 + title="Stop"
30 + color={ isRecording ? "grey" : "white" }
31 + />
32 + </Col>
33 + <Col xs={{span: 1}} className="mb-3 mt-3">
34 + <BsFillRecordBtnFill
35 + onClick={onRecord}
36 + size={32}
37 + style={{ cursor: "pointer"}}
38 + title={ isRecording ? "Stop" : "Record"}
39 + color={ isRecording ? "green" : "red" }
40 + />
41 + </Col>
42 + </Row>
43 +}
frontend/splitfire-desktop/src/components/player/CountDownView.tsx new
+24
@@ -0,0 +1,24 @@
1 +import { useEffect } from "react"
2 +import { useCountDown } from "../../lib/useCountDown"
3 +import { Col, Row } from "react-bootstrap"
4 +
5 +export function CountDownView(props: { seconds: number, type: string, onCountDownEnd?: () => void}) {
6 + const { secondsLeft, startTimer } = useCountDown()
7 +
8 + useEffect(() => {
9 + startTimer(props.seconds)
10 + }, [])
11 +
12 + if (secondsLeft === 0 && props.onCountDownEnd) {
13 + console.log('CountDownView: onCountDownEnd')
14 + props.onCountDownEnd()
15 + }
16 +
17 + const displayText = secondsLeft === 0 ? props.type : secondsLeft
18 +
19 + return <Row>
20 + <Col className="align-self-center" xs={12}>
21 + <h1>{displayText}</h1>
22 + </Col>
23 + </Row>
24 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/HideShowToggle.tsx new
+12
@@ -0,0 +1,12 @@
1 +import { MdArrowLeft, MdArrowDropDown } from "react-icons/md"
2 +
3 +export function HideShowToggleView(props: { hideVolumeSliders: boolean, setHideVolumeSliders: (hideVolumeSliders: boolean) => void }) {
4 +
5 + const { hideVolumeSliders, setHideVolumeSliders } = props
6 +
7 + if (hideVolumeSliders) {
8 + return <MdArrowLeft onClick={() => setHideVolumeSliders(!hideVolumeSliders)} size={30} style={{cursor: "pointer"}} />
9 + }
10 +
11 + return <MdArrowDropDown onClick={() => setHideVolumeSliders(!hideVolumeSliders)} size={30} style={{cursor: "pointer"}} />
12 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/Player.tsx new
+278
@@ -0,0 +1,278 @@
1 +import { useCallback, useContext, useEffect, useState } from "react"
2 +import { Col, Container, Row } from "react-bootstrap"
3 +import { useParams } from "react-router-dom"
4 +import { ModeDemucs } from "./models/Mode"
5 +import { useLogger } from "../../lib/logger"
6 +import { CountDownView } from "./CountDownView"
7 +import ErrorView from "../templates/Error"
8 +import { LoadingView } from "../templates/LoadingView"
9 +import { invoke } from '@tauri-apps/api'
10 +import { UserContext } from "../../lib/CurrentUserContext"
11 +import { PlayerPrepareResponse, TAURI_PLAYER_RECORD, TAURI_PLAYER_SET_VOLUME, TAURI_PLAYER_PAUSED, TAURI_PLAYER_UNMOUNT, TAURI_PLAYER_PREPARE, TauriResponse, TAURI_PLAYER_STOP, TAURI_PLAYER_RESUMED, TAURI_PLAYER_PLAY, TAURI_PLAYER_RECORDING_LENGTH, TAURI_PLAYER_RECORD_STOP } from "../../lib/tauriHandler"
12 +import { ControlButtonsView } from "./ControlButtons"
13 +import { VolumeSliderView } from "./VolumeSliderView"
14 +import { HideShowToggleView } from "./HideShowToggle"
15 +import { RecordingView } from "./RecordingView"
16 +
17 +enum State {
18 + LOADING,
19 + LOADED,
20 + ERROR,
21 +}
22 +
23 +enum PlayerState {
24 + STOPPED,
25 + PLAYING,
26 + PAUSED,
27 + RECORDING
28 +}
29 +
30 +interface PlayerVolume {
31 + mode: ModeDemucs,
32 + volume: string
33 +}
34 +
35 +export function PlayerAudio() {
36 +
37 + const log = useLogger('PlayerAudio')
38 + const { providerId } = useParams()
39 + const { user } = useContext(UserContext)
40 +
41 + const [state, setState] = useState(State.LOADING)
42 + const [title, setTitle] = useState<string>('Loading audio ...')
43 + const [isCountingCountdown, setIsCountingCountdown] = useState(false)
44 + const [recordingLength, setRecordingLength] = useState<number>(0)
45 + const [hideVolumeSliders, setHideVolumeSliders] = useState(false)
46 + const [playerState, setPlayerState] = useState<PlayerState>(PlayerState.STOPPED)
47 +
48 + // Volumes
49 + const [vocalsVolume, setVocalsVolume] = useState<string>('100')
50 + const [drumsVolume, setDrumsVolume] = useState<string>('100')
51 + const [bassVolume, setBassVolume] = useState<string>('100')
52 + const [otherVolume, setOtherVolume] = useState<string>('100')
53 +
54 + const COUNTING_DOWN_NUMBER = 3
55 +
56 + const _onVolumeChange = (mode: ModeDemucs, value: string) => {
57 + log.debug('_onVolumeChange', mode, value)
58 + switch (mode) {
59 + case ModeDemucs.Vocals:
60 + setVocalsVolume(value)
61 + break;
62 + case ModeDemucs.Drums:
63 + setDrumsVolume(value)
64 + break;
65 + case ModeDemucs.Bass:
66 + setBassVolume(value)
67 + break;
68 + case ModeDemucs.Other:
69 + setOtherVolume(value)
70 + break;
71 + }
72 + invoke(TAURI_PLAYER_SET_VOLUME, { mode: mode, volume: value })
73 + }
74 +
75 + const _playerVolumes = (): PlayerVolume[] => {
76 + return [
77 + { mode: ModeDemucs.Vocals, volume: vocalsVolume },
78 + { mode: ModeDemucs.Drums, volume: drumsVolume },
79 + { mode: ModeDemucs.Bass, volume: bassVolume },
80 + { mode: ModeDemucs.Other, volume: otherVolume },
81 + ]
82 + }
83 +
84 + const _toggleRecording = async () => {
85 + try {
86 + // If we are playing playbacks, stop it before recording.
87 + if (playerState === PlayerState.PLAYING) {
88 + setPlayerState(PlayerState.STOPPED)
89 + const result = await invoke(TAURI_PLAYER_STOP, { audioId: providerId, userId: user?.user.id })
90 + log.debug(TAURI_PLAYER_STOP, result)
91 + }
92 +
93 + // If we're already recording, we want to stop it and return.
94 + if (playerState === PlayerState.RECORDING) {
95 + _stopRecording()
96 + return
97 + }
98 +
99 + // Otherwise, we're good to go.
100 + // Get the length of the audio file.
101 +
102 + let length: number = await invoke(TAURI_PLAYER_RECORDING_LENGTH, { audioId: providerId, userId: user?.user.id })
103 + log.debug(TAURI_PLAYER_RECORDING_LENGTH, length)
104 + setRecordingLength(length)
105 + setIsCountingCountdown(true)
106 +
107 + setTimeout(async () => {
108 + log.debug(TAURI_PLAYER_RECORD, 'Start recording')
109 + setPlayerState(PlayerState.RECORDING)
110 + setIsCountingCountdown(false)
111 + const result = await invoke(TAURI_PLAYER_RECORD, { audioId: providerId, userId: user?.user.id, playerVolumes: _playerVolumes() })
112 + log.debug(TAURI_PLAYER_RECORD, result)
113 + }, COUNTING_DOWN_NUMBER * 1000)
114 + } catch (error) {
115 + log.error(error)
116 + }
117 + }
118 +
119 + const _stopRecording = async () => {
120 + setRecordingLength(0)
121 + setPlayerState(PlayerState.STOPPED)
122 + const stop_recording_result = await invoke(TAURI_PLAYER_RECORD_STOP, { audioId: providerId, userId: user?.user.id })
123 + const result = await invoke(TAURI_PLAYER_STOP, { audioId: providerId, userId: user?.user.id })
124 + log.debug(TAURI_PLAYER_STOP, result)
125 + log.debug(TAURI_PLAYER_RECORD_STOP, stop_recording_result)
126 + }
127 +
128 + const _stopPlayer = async () => {
129 + try {
130 + setPlayerState(PlayerState.STOPPED)
131 + setRecordingLength(0)
132 + const result = await invoke(TAURI_PLAYER_STOP, { audioId: providerId, userId: user?.user.id })
133 + log.debug(TAURI_PLAYER_STOP, result)
134 + } catch (error) {
135 + log.error(error)
136 + }
137 + }
138 +
139 + const _togglePlayAudio = async () => {
140 + // Should disabled when recording
141 + if (playerState === PlayerState.RECORDING) {
142 + return
143 + }
144 + try {
145 + switch (playerState) {
146 + case PlayerState.STOPPED:
147 + log.debug(TAURI_PLAYER_PLAY, 'Start playing')
148 + setIsCountingCountdown(true)
149 + setTimeout(async () => {
150 + setIsCountingCountdown(false)
151 + setPlayerState(PlayerState.PLAYING)
152 + await invoke(TAURI_PLAYER_PLAY, { playerVolumes: _playerVolumes() })
153 + }, COUNTING_DOWN_NUMBER * 1000)
154 + break;
155 + case PlayerState.PLAYING:
156 + log.debug(TAURI_PLAYER_PAUSED, 'Paused')
157 + setPlayerState(PlayerState.PAUSED)
158 + await invoke(TAURI_PLAYER_PAUSED)
159 + break;
160 + case PlayerState.PAUSED:
161 + log.debug(TAURI_PLAYER_RESUMED, 'Resumed')
162 + setPlayerState(PlayerState.PLAYING)
163 + await invoke(TAURI_PLAYER_PLAY, { playerVolumes: _playerVolumes() })
164 + }
165 + } catch (error) {
166 + log.error(error)
167 + }
168 + }
169 +
170 + const preparePlayer = useCallback(async () => {
171 + if (!providerId) {
172 + log.error('providerId is null')
173 + setState(State.ERROR)
174 + return
175 + }
176 + log.debug('preparePlayer', providerId)
177 + try {
178 + const result: PlayerPrepareResponse = await invoke(TAURI_PLAYER_PREPARE, { providerId: providerId })
179 + log.debug('PreparePlayerResponse', result)
180 + switch (result.status) {
181 + case TauriResponse.SUCCESS:
182 + if (result.audio_file_name)
183 + setTitle(result.audio_file_name)
184 +
185 + setState(State.LOADED)
186 + break;
187 + case TauriResponse.ERROR:
188 + setState(State.ERROR)
189 + break;
190 + }
191 + } catch (error) {
192 + log.error(error)
193 + setState(State.ERROR)
194 + }
195 + }, [])
196 +
197 + const onFinishedRecording = () => {
198 + log.debug('Recording finished')
199 + //_toggleRecording()
200 + }
201 +
202 + const _willUnmount = () => {
203 + try {
204 + const result = invoke(TAURI_PLAYER_UNMOUNT, { audioId: providerId, userId: user?.user.id })
205 + log.debug(TAURI_PLAYER_UNMOUNT, result)
206 + }
207 + catch (error) {
208 + log.error(error)
209 + }
210 + }
211 +
212 + useEffect(() => {
213 + preparePlayer()
214 +
215 + return () => {
216 + _willUnmount()
217 + }
218 + }, [])
219 +
220 + if (state === State.LOADING) {
221 + return <LoadingView />
222 + }
223 +
224 + if (state === State.ERROR) {
225 + return <ErrorView errorMessage="Something went wrong ..." />
226 + }
227 +
228 + let buttonOrCounting = <></>
229 + if (isCountingCountdown) {
230 + buttonOrCounting = <CountDownView seconds={COUNTING_DOWN_NUMBER} type="" />
231 + } else {
232 + buttonOrCounting = <ControlButtonsView
233 + isPlaying={playerState === PlayerState.PLAYING}
234 + isRecording={playerState === PlayerState.RECORDING}
235 + onClick={_togglePlayAudio}
236 + onStop={_stopPlayer}
237 + onRecord={_toggleRecording}
238 + />
239 + }
240 +
241 + let audioWaveform = <></>
242 + if (playerState === PlayerState.PLAYING || playerState === PlayerState.PAUSED) {
243 + audioWaveform = <>
244 + <CountDownView seconds={recordingLength} type="Practicing!!!" />
245 + </>
246 + } else if (playerState === PlayerState.RECORDING) {
247 + audioWaveform = <RecordingView length={recordingLength} onRecordingEnd={onFinishedRecording} />
248 + }
249 +
250 + return <Container>
251 + <Row>
252 + <Col xs={12} className="mb-3 mt-3">
253 + <h1>{title}</h1>
254 + </Col>
255 + </Row>
256 + {buttonOrCounting}
257 + <Row className="h-100 d-inline-block">
258 + <Col xs={12} className="mb-3 mt-3">
259 + <Container style={{ height: 300 }}>
260 + <div className="d-flex align-items-center justify-content-center h-100">
261 + <div className="d-flex flex-column">
262 + {audioWaveform}
263 + </div>
264 + </div>
265 + </Container>
266 + </Col>
267 + </Row>
268 + <Row className="border border-light">
269 + <Col xs={2} className="mb-3 mt-3">
270 + <p color="red">Volume</p>
271 + </Col>
272 + <Col xs={{ span: 2, offset: 8 }} className="mb-3 mt-3">
273 + <HideShowToggleView hideVolumeSliders={hideVolumeSliders} setHideVolumeSliders={setHideVolumeSliders} />
274 + </Col>
275 + <VolumeSliderView _onVolumeChange={_onVolumeChange} isHidden={hideVolumeSliders} />
276 + </Row>
277 + </Container>
278 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/PlayerView.tsx new
+28
@@ -0,0 +1,28 @@
1 +import { useContext } from "react";
2 +import { Navigate, Outlet, useParams } from "react-router-dom";
3 +import { UserContext } from "../../lib/CurrentUserContext";
4 +import { Col, Container, Row } from "react-bootstrap";
5 +import ErrorView from "../templates/Error";
6 +
7 +export function PlayerView() {
8 +
9 + const { providerId } = useParams()
10 + const { user } = useContext(UserContext)
11 +
12 + if (!user) {
13 + return <Navigate to={'/login'} />
14 + }
15 +
16 + if (user && providerId) {
17 + return <Container>
18 + <Row>
19 + <Col xs={12} className="mb-3 mt-3">
20 + <h1>Let's play!</h1>
21 + </Col>
22 + </Row>
23 + <Outlet context={{ user }} />
24 + </Container>
25 + }
26 +
27 + return <ErrorView errorMessage="Something went wrong ..." />
28 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/RecordingView.tsx new
+9
@@ -0,0 +1,9 @@
1 +import { CountDownView } from "./CountDownView"
2 +
3 +export function RecordingView(props: { length: number, onRecordingEnd: () => void }) {
4 + const { length, onRecordingEnd } = props
5 + return <div>
6 + <h1>Recording...</h1>
7 + <CountDownView seconds={length} type="" onCountDownEnd={onRecordingEnd} />
8 + </div>
9 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/Song.tsx new
+7
@@ -0,0 +1,7 @@
1 +import { Outlet } from "react-router-dom";
2 +
3 +export default function Song() {
4 + return (
5 + <Outlet />
6 + )
7 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/VolumeSliderView.tsx new
+28
@@ -0,0 +1,28 @@
1 +import { Container, Row, Col, Form } from "react-bootstrap"
2 +import { ModeDemucs } from "./models/Mode"
3 +
4 +export function VolumeSliderView(props: { _onVolumeChange: (mode: ModeDemucs, value: string) => void, isHidden: boolean }) {
5 +
6 + const { _onVolumeChange, isHidden } = props
7 +
8 + return <Container hidden={isHidden} className="mb-3">
9 + <Row>
10 + <Col xs={3}>
11 + <p>Vocals</p>
12 + <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Vocals, e.target.value) } defaultValue={100}/>
13 + </Col>
14 + <Col xs={3}>
15 + <p>Drums</p>
16 + <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Drums, e.target.value) } defaultValue={100} />
17 + </Col>
18 + <Col xs={3}>
19 + <p>Bass</p>
20 + <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Bass, e.target.value) } defaultValue={100} />
21 + </Col>
22 + <Col xs={3}>
23 + <p>Other</p>
24 + <Form.Range onChange={e => _onVolumeChange(ModeDemucs.Other, e.target.value) } defaultValue={100} />
25 + </Col>
26 + </Row>
27 + </Container>
28 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/models/AudioFile.tsx new
+17
@@ -0,0 +1,17 @@
1 +import { Result } from "./Result";
2 +
3 +export enum Status {
4 + DOWNLOADING = "downloading", // Requested to process file.
5 + SPLITTING = "splitting", // Fresly uploaded file.
6 + DONE = "done", // Done processing file.
7 +}
8 +
9 +export interface AudioFile {
10 + id: number,
11 + name: string,
12 + youtube_video_id: string,
13 + youtube_thumbnail: string,
14 + status: Status,
15 + progress: number,
16 + results: Result[]
17 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/models/Mode.tsx new
+14
@@ -0,0 +1,14 @@
1 +export enum Mode {
2 + Vocal = 'Vocal',
3 + Bass = 'Bass',
4 + Guitar = 'Guitar',
5 + Drum = 'Drum',
6 + Keyboard = 'Keyboard'
7 +}
8 +
9 +export enum ModeDemucs {
10 + Vocals = 'vocals',
11 + Bass = 'bass',
12 + Other = 'other',
13 + Drums = 'drums',
14 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/models/Result.tsx new
+5
@@ -0,0 +1,5 @@
1 +export interface Result {
2 + id: number,
3 + source_file: string,
4 + filename: string
5 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/models/YouTubeEventTarget.tsx new
+10
@@ -0,0 +1,10 @@
1 +import { YouTubePlayerState } from "./YouTubePlayerState";
2 +
3 +export interface YouTubeEventTarget {
4 + pauseVideo: () => void,
5 + playVideo: () => void,
6 + seekTo: (arg0: number) => void,
7 + getPlayerState: () => YouTubePlayerState,
8 + getCurrentTime: () => number
9 +
10 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/player/models/YouTubePlayerState.tsx new
+4
@@ -0,0 +1,4 @@
1 +// https://developers.google.com/youtube/iframe_api_reference#Playback_status
2 +export enum YouTubePlayerState {
3 + unstarted = -1, ended, playing, paused, buffering, videoCued
4 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/BackView.tsx new
+24
@@ -0,0 +1,24 @@
1 +import { Row, Col } from "react-bootstrap"
2 +import { BsArrowLeft } from "react-icons/bs"
3 +import { Link } from "react-router-dom"
4 +import User, { usernameOrId } from "../../models/user"
5 +
6 +interface BackViewProps {
7 + user: User
8 +}
9 +
10 +export default function BackView(props: BackViewProps) {
11 + return (
12 + <Row className="mb-2">
13 + <Col xs={1} className="text-start">
14 + <Link to={`/${usernameOrId(props.user)}`}>
15 + <BsArrowLeft></BsArrowLeft>
16 + </Link>
17 + </Col>
18 + <Col xs={11} className="text-start">
19 + <div className="">{`${props.user.name}`}</div>
20 + <div className="">{`${usernameOrId(props.user)}`}</div>
21 + </Col>
22 + </Row>
23 + )
24 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/Login.tsx new
+140
@@ -0,0 +1,140 @@
1 +import axios from "axios"
2 +import { Component } from "react"
3 +import { Alert, Button, Col, Container, Form, Row } from "react-bootstrap"
4 +import { Navigate } from "react-router-dom"
5 +import { CurrentUser, CurrentUserType, db } from "../../lib/db"
6 +import User, { ProfileResponse, usernameOrId } from '../../models/user'
7 +import { Mode } from "../player/models/Mode"
8 +import { UserContext } from "../../lib/CurrentUserContext"
9 +
10 +interface LoginProps { }
11 +
12 +interface LoginState {
13 + user: User | null
14 + usernameOrEmail: string
15 + password: string
16 + errorMessage: string
17 + processing: boolean
18 +}
19 +
20 +export default class Login extends Component<LoginProps, LoginState> {
21 +
22 + static contextType = UserContext
23 +
24 + constructor(props: LoginProps) {
25 + super(props)
26 + this.state = {
27 + user: null,
28 + usernameOrEmail: '',
29 + password: '',
30 + errorMessage: '',
31 + processing: false
32 + }
33 + }
34 +
35 + componentDidMount() {
36 + const user = this.context.user
37 + if (user) {
38 + this.setState({ user: user })
39 + }
40 + }
41 +
42 + login(update: (user: CurrentUser) => void) {
43 + //console.log(this.state)
44 + this.setState({ processing: true })
45 + axios
46 + .post(`/login`, {
47 + username: this.state.usernameOrEmail,
48 + password: this.state.password
49 + })
50 + .then(res => {
51 + const response: ProfileResponse = res.data
52 + if (response.code === 200) {
53 + let currentUser: CurrentUser = {
54 + accessToken: res.headers.authorization,
55 + type: CurrentUserType.MAIN,
56 + user: response.user,
57 + mode: Mode.Vocal
58 + }
59 + db.currentUser.add(currentUser)
60 + this.setState({
61 + user: response.user
62 + })
63 + update(currentUser)
64 + //this.props.didLogin(response.user)
65 + } else {
66 + //console.log(res)
67 + this.setState({
68 + processing: false,
69 + errorMessage: response.message
70 + })
71 + }
72 + })
73 + .catch(error => {
74 + //console.log(error)
75 + this.setState({
76 + processing: false,
77 + errorMessage: 'Unkown error.'
78 + })
79 + })
80 + }
81 +
82 + _buttonTitle(): string {
83 + if (this.state.processing) {
84 + return 'Logging in...'
85 + }
86 + return 'Submit'
87 + }
88 +
89 + render() {
90 + const { user, errorMessage } = this.state
91 +
92 + if (user) {
93 + return <Navigate to={`/${usernameOrId(user)}`} />
94 + }
95 +
96 + let validationText: any = ''
97 + if (errorMessage.length > 0) {
98 + validationText = <Alert variant="danger" onClose={() => this.setState({ errorMessage: '' })} dismissible>
99 + <p>{errorMessage}</p>
100 + <p><a href={`${process.env.REACT_APP_MUSIK88_BASE_URL}/users/password/new`} target="__blank">Forget password?</a></p>
101 + </Alert >
102 + }
103 +
104 + return (
105 + <UserContext.Consumer>
106 + {({ user, updateUser: update }) => (
107 + <Row className="justify-content-md-center">
108 + <Col>
109 + <Container>
110 + <Form>
111 + {validationText}
112 + <Form.Group className="mb-3" controlId="formBasicEmail">
113 + <Form.Label>Email address</Form.Label>
114 + <Form.Control type="email" placeholder="Enter email" value={this.state.usernameOrEmail} onChange={(e) => this.setState({ usernameOrEmail: e.currentTarget.value })} />
115 + </Form.Group>
116 + <Form.Group className="mb-3" controlId="formBasicPassword">
117 + <Form.Label>Password</Form.Label>
118 + <Form.Control type="password" placeholder="Password" value={this.state.password} onChange={(e) => this.setState({ password: e.currentTarget.value })} />
119 + </Form.Group>
120 + <Button variant="primary" onClick={() => this.login(update)} disabled={this.state.processing}>
121 + {this._buttonTitle()}
122 + </Button>
123 + <p>or</p>
124 + <Form.Group className="mb-3">
125 + <a href={`${process.env.REACT_APP_MUSIK88_BASE_URL}/signup`} target={`__blank`}>
126 + <Button variant="success">
127 + Register
128 + </Button>
129 + </a>
130 + <p>Account managed by Musik88.</p>
131 + </Form.Group>
132 + </Form>
133 + </Container>
134 + </Col>
135 + </Row>
136 + )}
137 + </UserContext.Consumer>
138 + )
139 + }
140 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/Logout.tsx new
+39
@@ -0,0 +1,39 @@
1 +import { useContext, useEffect } from "react"
2 +import { Spinner } from "react-bootstrap"
3 +import axios from "../../lib/axios"
4 +import { UserContext } from "../../lib/CurrentUserContext"
5 +
6 +interface LogoutProps {
7 + didLogout: () => void
8 +}
9 +
10 +export default function Logout(props: LogoutProps) {
11 +
12 + const currentUser = useContext(UserContext)
13 +
14 + useEffect(() => {
15 + console.log(currentUser)
16 + if (currentUser.user?.accessToken) {
17 + axios
18 + .delete(`/logout`, {
19 + headers: {
20 + 'Authorization': currentUser.user.accessToken
21 + }
22 + })
23 + .then(res => {
24 + props.didLogout()
25 + })
26 + .catch(error => {
27 + console.log(error)
28 + })
29 + }
30 + })
31 +
32 + return (
33 + <div>
34 + <Spinner animation="border" role="status">
35 + <span className="visually-hidden">Loading...</span>
36 + </Spinner>
37 + </div>
38 + )
39 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/Profile.tsx new
+137
@@ -0,0 +1,137 @@
1 +import axios from "axios"
2 +import { Component } from "react"
3 +import { Col, Container, Row, Spinner } from "react-bootstrap"
4 +import { useParams } from "react-router-dom"
5 +import { db, CurrentUserType } from "../../lib/db"
6 +import User, { ProfileResponse } from '../../models/user'
7 +import { Mode } from "../player/models/Mode"
8 +import ProfileView from "./ProfileView"
9 +import { UserContext } from "../../lib/CurrentUserContext"
10 +import { Logger } from "tslog"
11 +import { MdBugReport } from "react-icons/md"
12 +import ErrorView from "../templates/Error"
13 +
14 +export function ProfileRootView() {
15 + const { usernameOrId } = useParams()
16 +
17 + if (!usernameOrId) {
18 + return (
19 + <p>Something is wrong...</p>
20 + )
21 + }
22 +
23 + return (
24 + <Profile usernameOrId={usernameOrId} />
25 + )
26 +}
27 +
28 +interface ProfileProps {
29 + usernameOrId: string
30 +}
31 +
32 +interface ProfileState {
33 + user: User | null
34 + isCurrentUser: boolean
35 + errorMessage: string
36 + mode: Mode
37 + isUpdatingMode: boolean
38 +}
39 +
40 +class Profile extends Component<ProfileProps, ProfileState> {
41 +
42 + static contextType = UserContext
43 + log = new Logger({ name: 'Profile', type: 'pretty' })
44 +
45 + constructor(props: ProfileProps) {
46 + super(props)
47 + this.state = {
48 + user: null,
49 + isCurrentUser: false,
50 + errorMessage: '',
51 + mode: Mode.Vocal,
52 + isUpdatingMode: false
53 + }
54 + }
55 +
56 + componentDidUpdate(prevProps: ProfileProps) {
57 + if (this.props.usernameOrId !== prevProps.usernameOrId) {
58 + this._fetchUserDetail()
59 + }
60 + }
61 +
62 + onChangeMode(mode: Mode): Promise<Mode> {
63 + this.log.debug(mode)
64 + this.setState({ isUpdatingMode: true })
65 + return new Promise<Mode>((resolve, reject) => {
66 + db
67 + .currentUser
68 + .where({ type: CurrentUserType.MAIN })
69 + .modify({ mode })
70 + .finally(() => {
71 + this.setState({ isUpdatingMode: false })
72 + resolve(mode)
73 + })
74 + })
75 + }
76 +
77 + componentDidMount() {
78 + this._fetchUserDetail()
79 + }
80 +
81 + _fetchUserDetail() {
82 + axios.get(`/profile/${this.props.usernameOrId}`)
83 + .then(res => {
84 + this.log.debug(res.data)
85 + const response: ProfileResponse = res.data
86 + if (response.code === 200) {
87 + this.setState({ user: response.user })
88 + this._updateCurrentUserState()
89 + } else {
90 + this.setState({ errorMessage: response.message })
91 + }
92 + })
93 + .catch(error => {
94 + this.log.error(error)
95 + this.setState({ errorMessage: 'Something went wrong...' })
96 + })
97 + }
98 +
99 + _updateCurrentUserState() {
100 + const user = this.context.user
101 + const isCurrentUser = user.user.id === this.state.user?.id
102 + this.setState({ isCurrentUser, mode: user?.mode })
103 + }
104 +
105 + render() {
106 +
107 + const { user, isCurrentUser, errorMessage, mode, isUpdatingMode } = this.state
108 + this.log.debug(mode)
109 + let content = <Row>
110 + <Col>
111 + <Spinner animation='grow' variant='danger'></Spinner>
112 + </Col>
113 + </Row>
114 +
115 + if (user) {
116 + content = <ProfileView
117 + user={user}
118 + isCurrentUser={isCurrentUser}
119 + mode={mode}
120 + onChangeMode={this.onChangeMode.bind(this)}
121 + isUpdatingMode={isUpdatingMode}
122 + />
123 + }
124 +
125 + if (errorMessage.length > 0) {
126 + return <ErrorView errorMessage={errorMessage} />
127 + }
128 +
129 + return (
130 + <Container>
131 + {content}
132 + </Container>
133 + )
134 + }
135 +}
136 +
137 +export default Profile
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/ProfileFollowers.tsx new
+75
@@ -0,0 +1,75 @@
1 +import axios from "axios"
2 +import { Component, ReactNode } from "react"
3 +import { Col, Row, Spinner } from "react-bootstrap"
4 +import { useParams } from "react-router-dom"
5 +import User, { FollowersResponse } from "../../models/user"
6 +import ProfileFollowingView from "./ProfileListView"
7 +import TabMenu, { Tab } from "./Tabs"
8 +
9 +export function ProfileFollowersRootView() {
10 + const { usernameOrId } = useParams()
11 +
12 + if (!usernameOrId) {
13 + return (
14 + <p>Something is wrong...</p>
15 + )
16 + }
17 +
18 + return (
19 + <ProfileFollowers usernameOrId={usernameOrId} />
20 + )
21 +}
22 +
23 +interface ProfileFollowersProps {
24 + usernameOrId: string
25 +}
26 +
27 +interface ProfileFollowersState {
28 + user: User | null
29 + followers: User[] | null
30 +}
31 +
32 +class ProfileFollowers extends Component<ProfileFollowersProps, ProfileFollowersState> {
33 +
34 + constructor(props: ProfileFollowersProps) {
35 + super(props)
36 + this.state = {
37 + user: null,
38 + followers: null
39 + }
40 + }
41 +
42 + componentDidMount() {
43 + axios.get(`/profile/${this.props.usernameOrId}/followers`)
44 + .then(res => {
45 + //console.log(res.data)
46 +
47 + const response: FollowersResponse = res.data
48 + if (response.code === 200) {
49 + this.setState({ user: response.user, followers: response.followers })
50 + } else {
51 + //console.log("Error")
52 + }
53 + })
54 + .catch(error => {
55 + //console.log(error)
56 + })
57 + }
58 +
59 + render(): ReactNode {
60 + let content = <Row>
61 + <Col>
62 + <Spinner animation='grow' variant='danger'></Spinner>
63 + </Col>
64 + </Row>
65 +
66 + if (this.state.user && this.state.followers) {
67 + content = <ProfileFollowingView following={this.state.followers} />
68 + }
69 +
70 + return <div>
71 + <TabMenu active={Tab.Followers} usernameOrId={this.props.usernameOrId} />
72 + {content}
73 + </div>
74 + }
75 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/ProfileFollowing.tsx new
+77
@@ -0,0 +1,77 @@
1 +import axios from "axios"
2 +import { Component, ReactNode } from "react"
3 +import { Col, Row, Spinner } from "react-bootstrap"
4 +import { useParams } from "react-router-dom"
5 +import User, { FollowingResponse } from '../../models/user'
6 +import ProfileFollowingView from "./ProfileListView"
7 +import TabMenu, { Tab } from "./Tabs"
8 +
9 +export function ProfileFollowingRootView() {
10 + const { usernameOrId } = useParams()
11 +
12 + if (!usernameOrId) {
13 + return (
14 + <p>Something is wrong...</p>
15 + )
16 + }
17 +
18 + return (
19 + <ProfileFollowing usernameOrId={usernameOrId} />
20 + )
21 +}
22 +
23 +interface ProfileFollowingProps {
24 + usernameOrId: string
25 +}
26 +
27 +interface ProfileFollowingState {
28 + user: User | null
29 + following: User[] | null
30 +}
31 +
32 +class ProfileFollowing extends Component<ProfileFollowingProps, ProfileFollowingState> {
33 +
34 + constructor(props: ProfileFollowingProps) {
35 + super(props)
36 + this.state = {
37 + user: null,
38 + following: null
39 + }
40 + }
41 +
42 + componentDidMount() {
43 + axios.get(`/profile/${this.props.usernameOrId}/following`)
44 + .then(res => {
45 + //console.log(res.data)
46 +
47 + const response: FollowingResponse = res.data
48 + if (response.code === 200) {
49 + this.setState({ user: response.user, following: response.following })
50 + } else {
51 + //console.log('Error')
52 + }
53 + })
54 + .catch(error => {
55 + //console.log(error)
56 + })
57 + }
58 +
59 + render(): ReactNode {
60 + let content = <Row>
61 + <Col>
62 + <Spinner animation='grow' variant='danger'></Spinner>
63 + </Col>
64 + </Row>
65 +
66 + if (this.state.user && this.state.following) {
67 + content = <ProfileFollowingView following={this.state.following} />
68 + }
69 +
70 + return <div>
71 + <TabMenu active={Tab.Following} usernameOrId={this.props.usernameOrId} />
72 + {content}
73 + </div>
74 + }
75 +}
76 +
77 +export default ProfileFollowing
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/ProfileListView.tsx new
+63
@@ -0,0 +1,63 @@
1 +import { Row, Col, Container, Image } from "react-bootstrap"
2 +import { Link } from "react-router-dom"
3 +import User, { usernameOrId } from "../../models/user"
4 +
5 +interface ProfileListViewProps {
6 + key: number
7 + user: User
8 +}
9 +
10 +interface ProfileFollowingViewProps {
11 + following: User[]
12 +}
13 +
14 +export default function ProfileFollowingView(props: ProfileFollowingViewProps) {
15 + let content = <p>Nothing here :(</p>
16 + if (props.following.length > 0) {
17 + content = <div>
18 + {props.following.map( user =>
19 + (<ProfileListView user={user} key={user.id} />)
20 + )}
21 + </div>
22 + }
23 + return (
24 + content
25 + )
26 +}
27 +
28 +function ProfileListView(props: ProfileListViewProps) {
29 + return (
30 + <Row className="m-3">
31 + <Col xs={2}>
32 + <Image roundedCircle thumbnail src={props.user.gravatar_url} sizes={'24x24'}/>
33 + </Col>
34 + <Col xs={10} className="text-start">
35 + <Container>
36 + <Row>
37 + <Col xs={8} className="mt-3 font-weight-bold">
38 + <div>
39 + <Link to={`/${usernameOrId(props.user)}`}>
40 + {props.user.name}
41 + </Link>
42 + </div>
43 + <div>
44 + {`${usernameOrId(props.user)}`}
45 + </div>
46 + </Col>
47 + <Col xs={4} className="mt-3 font-weight-light float-end">
48 + <span className="badge rounded-pill bg-primary">{`${props.user.followers_count}`} Followers</span>
49 + <span className="badge rounded-pill bg-primary">{`${props.user.following_count}`} Following</span>
50 + </Col>
51 + </Row>
52 + </Container>
53 + <Container className="blockquote-footer">
54 + <Row>
55 + <Col>
56 + {props.user.about}
57 + </Col>
58 + </Row>
59 + </Container>
60 + </Col>
61 + </Row>
62 + )
63 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/ProfileNetworks.tsx new
+79
@@ -0,0 +1,79 @@
1 +import axios from "axios"
2 +import { Component, ReactNode } from "react"
3 +import { Row, Col, Spinner, Container } from "react-bootstrap"
4 +import { useParams, Outlet } from "react-router-dom"
5 +import User, { ProfileResponse } from "../../models/user"
6 +import BackView from "./BackView"
7 +import { Tab } from "./Tabs"
8 +
9 +export function ProfileNetworksRootView() {
10 + const { usernameOrId } = useParams()
11 +
12 + if (!usernameOrId) {
13 + return (
14 + <p>Something is wrong...</p>
15 + )
16 + }
17 + return (
18 + <ProfileNetworksView usernameOrId={usernameOrId} />
19 + )
20 +}
21 +
22 +interface ProfileNetworksProps {
23 + usernameOrId: string
24 +}
25 +
26 +interface ProfileNetworksState {
27 + user: User | null
28 + tab: Tab | null
29 + isError: boolean
30 +}
31 +
32 +class ProfileNetworksView extends Component<ProfileNetworksProps, ProfileNetworksState> {
33 +
34 + constructor(props: ProfileNetworksProps) {
35 + super(props)
36 + this.state = {
37 + user: null,
38 + tab: null,
39 + isError: false
40 + }
41 + }
42 +
43 + componentDidMount() {
44 + axios.get(`/profile/${this.props.usernameOrId}`)
45 + .then(res => {
46 + //console.log(res.data)
47 + const response: ProfileResponse = res.data
48 + if (response.code === 200) {
49 + this.setState({ user: response.user })
50 + } else {
51 + //console.log("Error")
52 + this.setState({ isError: true })
53 + }
54 + })
55 + .catch(error => {
56 + //console.log(error)
57 + this.setState({ isError: true })
58 + })
59 + }
60 +
61 + render(): ReactNode {
62 + const { user } = this.state
63 +
64 + let content = <Row>
65 + <Col>
66 + <Spinner animation='grow' variant='danger'></Spinner>
67 + </Col>
68 + </Row>
69 +
70 + if (user) {
71 + content = <div>
72 + <BackView user={user} />
73 + <Outlet />
74 + </div>
75 + }
76 +
77 + return <Container>{content}</Container>
78 + }
79 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/ProfileView.tsx new
+99
@@ -0,0 +1,99 @@
1 +import { useState } from "react"
2 +import User, { usernameOrId } from "../../models/user"
3 +import { Mode } from "../player/models/Mode"
4 +import { Button, Col, Form, Row, Spinner, Image } from "react-bootstrap"
5 +import { Link } from "react-router-dom"
6 +import { useLogger } from "../../lib/logger"
7 +
8 +
9 +interface ProfileViewProps {
10 + user: User
11 + isCurrentUser: boolean
12 + isUpdatingMode: boolean
13 + mode: Mode
14 + onChangeMode: (mode: Mode) => Promise<Mode>
15 +}
16 +
17 +export default function ProfileView(props: ProfileViewProps) {
18 +
19 + const log = useLogger('ProfileView')
20 + const [updatingMode, setUpdatingMode] = useState(props.isUpdatingMode)
21 + const [currentMode, setCurrentMode] = useState(props.mode)
22 +
23 + log.debug(props.mode)
24 + log.debug(currentMode)
25 +
26 + const modeChangeHander = (stringMode: string) => {
27 + setUpdatingMode(true)
28 + props
29 + .onChangeMode(stringMode as Mode)
30 + .then(mode => {
31 + setUpdatingMode(false)
32 + setCurrentMode(mode)
33 + })
34 + }
35 +
36 + let logoutButton
37 +
38 + if (props.isCurrentUser) {
39 + logoutButton = <Col xs={12} className="mt-3 font-weight-bold">
40 + <Link to={'/logout'} ><Button variant="danger">Logout</Button></Link>
41 + </Col>
42 + }
43 + let modeView
44 + if (!updatingMode && props.isCurrentUser) {
45 + let options = []
46 + for (const value in Mode) {
47 + if (typeof value !== "string") {
48 + continue;
49 + }
50 + if (value === 'Keyboard') {
51 + continue;
52 + }
53 + options.push(<option value={value} key={value}> {value}</option >)
54 + }
55 + modeView = <Col xs={{ span: 4, offset: 4 }} className="mt-3">
56 + <Form.Select onChange={(e) => modeChangeHander(e.currentTarget.value)} value={currentMode}>
57 + {options}
58 + </Form.Select>
59 + </Col>
60 + }
61 + if (updatingMode) {
62 + modeView = <Col xs={{ span: 4, offset: 4 }} className="mt-3">
63 + <Spinner animation='grow' variant='danger'></Spinner>
64 + </Col>
65 + }
66 +
67 + return (
68 + <div className="mt-3">
69 + <Row>
70 + <Col xs={12}>
71 + <Image roundedCircle thumbnail src={props.user.gravatar_url} />
72 + </Col>
73 + <Col xs={12} className="mt-3 font-weight-bold">
74 + {props.user.name}
75 + </Col>
76 + <Col xs={12} className="mt-3 font-weight-light">
77 + {usernameOrId(props.user)}
78 + </Col>
79 + {modeView}
80 + <Col xs={12} className="mt-3 blockquote-footer">
81 + {props.user.about}
82 + </Col>
83 + {logoutButton}
84 + </Row>
85 + <Row className="mt-3">
86 + <Col xs={6}>
87 + <Link to={`/${usernameOrId(props.user)}/followers`} className="text-white" aria-current="page">
88 + {props.user.followers_count} Followers
89 + </Link>
90 + </Col>
91 + <Col xs={6}>
92 + <Link to={`/${usernameOrId(props.user)}/following`} className="text-white" aria-current="page">
93 + {props.user.following_count} Following
94 + </Link>
95 + </Col>
96 + </Row>
97 + </div>
98 + )
99 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/profiles/Tabs.tsx new
+34
@@ -0,0 +1,34 @@
1 +import { Link } from "react-router-dom"
2 +
3 +export enum Tab {
4 + Following = 0,
5 + Followers
6 +}
7 +
8 +interface TabMenuProps {
9 + active: Tab
10 + usernameOrId: string
11 +}
12 +
13 +export default function TabMenu(props: TabMenuProps) {
14 + return (
15 + <ul className={'nav nav-tabs nav-justified'}>
16 + <li className={'nav-item'}>
17 + <Link
18 + to={`/@${props.usernameOrId}/followers`}
19 + className={`nav-link${props.active === Tab.Followers ? ' active' : ''}`}
20 + aria-current="page">
21 + Followers
22 + </Link>
23 + </li>
24 + <li className={'nav-item'}>
25 + <Link
26 + to={`/@${props.usernameOrId}/following`}
27 + className={`nav-link${props.active === Tab.Following ? ' active' : ''}`}
28 + aria-current="page">
29 + Following
30 + </Link>
31 + </li>
32 + </ul>
33 + )
34 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/templates/Error.tsx new
+15
@@ -0,0 +1,15 @@
1 +import { Container, Row, Col } from "react-bootstrap";
2 +import { MdBugReport } from "react-icons/md";
3 +
4 +export default function ErrorView(props: { errorMessage: string }) {
5 + return (
6 + <Container>
7 + <Row>
8 + <Col>
9 + <MdBugReport size={50} color="red" />
10 + <p>{props.errorMessage}</p>
11 + </Col>
12 + </Row>
13 + </Container>
14 + )
15 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/templates/Footer.tsx new
+27
@@ -0,0 +1,27 @@
1 +import React from 'react'
2 +import { Col, Row } from 'react-bootstrap'
3 +import { Link } from 'react-router-dom'
4 +
5 +const year = (new Date()).getFullYear();
6 +
7 +export default function Footer() {
8 + return (
9 + <footer className="mt-5 text-white-50">
10 + <Row>
11 + <Col xs={6}>
12 + <p className='float-start'>© {year}</p>
13 + </Col>
14 + <Col xs={6}>
15 + <ul className="list-inline float-end">
16 + <li className='list-inline-item'>
17 + <Link to='/about' aria-current="page">About</Link>
18 + </li>
19 + <li className='list-inline-item'>
20 + <Link to='/contact' aria-current="page">Contact</Link>
21 + </li>
22 + </ul>
23 + </Col>
24 + </Row>
25 + </footer>
26 + )
27 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/templates/Header.tsx new
+53
@@ -0,0 +1,53 @@
1 +import React from 'react'
2 +import { Component } from 'react'
3 +import { Link, Location } from 'react-router-dom'
4 +import 'dexie-observable'
5 +import { usernameOrId } from '../../models/user'
6 +import { Badge, Row } from 'react-bootstrap'
7 +import { UserContext } from '../../lib/CurrentUserContext'
8 +
9 +interface HeaderProps {
10 + location: Location
11 +}
12 +
13 +interface HeaderState { }
14 +
15 +export default class Header extends Component<HeaderProps, HeaderState> {
16 +
17 + constructor(props: HeaderProps) {
18 + super(props)
19 + this.state = {}
20 + }
21 +
22 + _isActive(path: string): string {
23 + if (this.props.location.pathname === path)
24 + return ' active'
25 + return ''
26 + }
27 +
28 + render(): React.ReactNode {
29 + return (
30 + <UserContext.Consumer>
31 + {({ user }) => (
32 + <header className="mb-5">
33 + <Row className="border border-light">
34 + <h3 className="float-md-start mb-0">
35 + <Link to='/' className={`text-white nav-link${this._isActive('/about')}`} aria-current="page">
36 + <span className=''>SplitFire AI </span>
37 + <Badge bg="warning" text="dark" className='fst-italic fw-light'>beta</Badge>
38 + </Link>
39 + </h3>
40 + <nav className="nav nav-masthead justify-content-center float-md-end mb-3">
41 + <Link to='/' className={`nav-link${this._isActive('/')}`} aria-current="page">Home</Link>
42 + {
43 + user?.user ? <Link to={`/${usernameOrId(user?.user)}`} className={`nav-link`} aria-current="page">@{usernameOrId(user.user)}</Link>
44 + : <Link to='/login' className={`nav-link${this._isActive('/login')}`} aria-current="page">Login</Link>
45 + }
46 + </nav>
47 + </Row>
48 + </header>
49 + )}
50 + </UserContext.Consumer>
51 + )
52 + }
53 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/templates/LoadingView.tsx new
+12
@@ -0,0 +1,12 @@
1 +import { Col, Container, Spinner } from "react-bootstrap";
2 +
3 +export function LoadingView() {
4 +
5 + return <Container>
6 + <Col className="align-self-center" xs={12}>
7 + <Spinner animation="grow" role="status" variant="danger">
8 + <span className="visually-hidden">Loading...</span>
9 + </Spinner>
10 + </Col>
11 + </Container>
12 +}
\ No newline at end of file
frontend/splitfire-desktop/src/components/templates/Main.tsx new
+22
@@ -0,0 +1,22 @@
1 +import { Outlet, useLocation } from 'react-router-dom'
2 +import Header from './Header'
3 +import Footer from './Footer'
4 +
5 +export default function Main() {
6 +
7 + const location = useLocation()
8 +
9 + return (
10 + <div className="main-container d-flex text-center text-white bg-dark">
11 + <div className="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
12 + <main className="px-3 h-100">
13 + <Header location={location} />
14 + <div className="flex-wrapper">
15 + <Outlet />
16 + <Footer />
17 + </div>
18 + </main>
19 + </div>
20 + </div>
21 + )
22 +}
\ No newline at end of file
frontend/splitfire-desktop/src/index.css new
+13
@@ -0,0 +1,13 @@
1 +body {
2 + margin: 0;
3 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 + sans-serif;
6 + -webkit-font-smoothing: antialiased;
7 + -moz-osx-font-smoothing: grayscale;
8 +}
9 +
10 +code {
11 + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 + monospace;
13 +}
frontend/splitfire-desktop/src/index.tsx new
+23
@@ -0,0 +1,23 @@
1 +import { render } from 'react-dom';
2 +import './index.css';
3 +import * as Sentry from "@sentry/react";
4 +import { BrowserTracing } from "@sentry/tracing";
5 +import App from './App';
6 +import reportWebVitals from './reportWebVitals';
7 +
8 +Sentry.init({
9 + dsn: "https://35835e5db0a1497daa344e7cbff13641@o933906.ingest.sentry.io/6685071",
10 + integrations: [new BrowserTracing()],
11 +
12 + // Set tracesSampleRate to 1.0 to capture 100%
13 + // of transactions for performance monitoring.
14 + // We recommend adjusting this value in production
15 + tracesSampleRate: process.env.NODE_ENV === "production" ? 1.0 : 0,
16 +});
17 +
18 +render(<App />, document.getElementById('root'));
19 +
20 +// If you want to start measuring performance in your app, pass a function
21 +// to log results (for example: reportWebVitals(console.log))
22 +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
23 +reportWebVitals();
frontend/splitfire-desktop/src/lib/CurrentUserContext.ts new
+9
@@ -0,0 +1,9 @@
1 +import { createContext } from "react";
2 +import { CurrentUser } from "./db";
3 +
4 +interface CurrentUserContext {
5 + user: CurrentUser | null
6 + updateUser: (user: CurrentUser) => void
7 +}
8 +
9 +export const UserContext = createContext<CurrentUserContext>({ user: null, updateUser: () => { } });
\ No newline at end of file
frontend/splitfire-desktop/src/lib/axios.ts new
+12
@@ -0,0 +1,12 @@
1 +import axios from "axios";
2 +
3 +let host = process.env.REACT_APP_SPLITFIRE_API_HOST;
4 +
5 +const client = axios.create({
6 + baseURL: `https://${host}/api/v1/`,
7 + headers: {
8 + "Content-Type": "application/json",
9 + },
10 +});
11 +
12 +export default client;
frontend/splitfire-desktop/src/lib/db.ts new
+142
@@ -0,0 +1,142 @@
1 +import Dexie, { Table } from 'dexie';
2 +import { Mode, ModeDemucs } from '../components/player/models/Mode';
3 +import User from '../models/user';
4 +import { AudioFile } from '../components/player/models/AudioFile';
5 +import axios from './axios';
6 +import { Result } from '../components/player/models/Result';
7 +import { Logger } from 'tslog';
8 +import isTauri from './isTauri';
9 +
10 +// Legacy Spleeter result file
11 +export interface AudioFiles {
12 + id?: number;
13 + audioFileId: string,
14 + type: Mode,
15 + file: Blob
16 +}
17 +
18 +export enum CurrentUserType {
19 + MAIN, GUEST
20 +}
21 +
22 +export interface CurrentUser {
23 + accessToken: string
24 + type: CurrentUserType
25 + mode: Mode
26 + user: User
27 +}
28 +
29 +// Splitfire result file
30 +interface ResultFile {
31 + id?: number;
32 + audioFileId: number,
33 + type: ModeDemucs,
34 + buffer: ArrayBuffer,
35 + blobType: string
36 +}
37 +
38 +export interface BlobFile {
39 + file: Blob
40 + mode: ModeDemucs
41 +}
42 +
43 +export class SplitfireDB extends Dexie {
44 +
45 + audioFiles!: Table<AudioFiles>
46 + resultFiles!: Table<ResultFile>
47 + currentUser!: Table<CurrentUser>
48 + log = new Logger({ name: 'SplitfireDB', type: isTauri() ? 'json' : 'pretty' });
49 +
50 + constructor() {
51 + super('splitfireDB');
52 + this.version(9).stores({
53 + audioFiles: '++id, [audioFileId+type]', // This is when I was using Spleeter
54 + resultFiles: '++id, [audioFileId+type]', // This is when I was using Demucs
55 + currentUser: '++id, [userId+type], type'
56 + });
57 + }
58 +
59 + // The public interface to get the blob result files.
60 + // It takes the audio file and then compute the modes from the results field.
61 + async getResultFiles(audioFile: AudioFile): Promise<BlobFile[]> {
62 + return new Promise(async (resolve, reject) => {
63 + try {
64 + // Check modes of results
65 + const modes: ModeDemucs[] = audioFile.results.map(result => result.filename.split('-').shift() as ModeDemucs)
66 +
67 + this.log.error('modes', modes);
68 + let resultFiles: BlobFile[] = [];
69 + // Check if we have result file in the db
70 + for (const mode of modes) {
71 + const result = await this._getResultFile(audioFile, mode);
72 + resultFiles.push(result)
73 + }
74 + this.log.debug('resultFiles', resultFiles);
75 + resolve(resultFiles)
76 + } catch (error) {
77 + this.log.error(error);
78 + reject(error)
79 + }
80 + })
81 + }
82 +
83 + async _getResultFile(audioFile: AudioFile, mode: ModeDemucs): Promise<BlobFile> {
84 + return new Promise(async (resolve, reject) => {
85 + try {
86 + // Check if we have result file in the db
87 + const result = await db.resultFiles.get({ audioFileId: audioFile.id, type: mode })
88 + // if we do, then return it
89 + this.log.debug('_getResultFile', result);
90 + if (result?.buffer) {
91 + const blob = new Blob([result.buffer], { type: result.blobType })
92 + resolve({file: blob, mode})
93 + } else {
94 + // otherwise, download the file and return it
95 + const file = await this._downloadFile(audioFile, mode);
96 + resolve({file, mode})
97 + }
98 + } catch (error) {
99 + reject(error)
100 + }
101 + });
102 + }
103 +
104 + async _downloadFile(audioFile: AudioFile, type: ModeDemucs): Promise<Blob> {
105 + this.log.debug('_downloadFile', type);
106 + let result: Result | undefined;
107 + switch (type) {
108 + case ModeDemucs.Vocals:
109 + result = audioFile.results.find(obj => { return obj.filename.startsWith('vocals') });
110 + break;
111 + case ModeDemucs.Bass:
112 + result = audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
113 + break;
114 + case ModeDemucs.Drums:
115 + result = audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
116 + break;
117 + case ModeDemucs.Other:
118 + result = audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
119 + break;
120 + }
121 +
122 + this.log.debug("Download url", result?.source_file);
123 +
124 + return new Promise(async (resolve, reject) => {
125 + try {
126 + const response = await axios({ url: result?.source_file, method: 'GET', responseType: 'blob' })
127 + const blob = new Blob([response.data], { type: 'audio/mp3' })
128 + const buffer = await blob.arrayBuffer()
129 + const item: ResultFile = {
130 + audioFileId: audioFile.id, type: type, buffer: buffer, blobType: blob.type
131 + }
132 + db.resultFiles.add(item);
133 + resolve(blob);
134 + } catch (error) {
135 + this.log.error(error);
136 + reject(error);
137 + }
138 + });
139 + }
140 +}
141 +
142 +export const db = new SplitfireDB();
\ No newline at end of file
frontend/splitfire-desktop/src/lib/isTauri.ts new
+11
@@ -0,0 +1,11 @@
1 +declare const window: Window & { __TAURI__: any }
2 +
3 +const isTauri = () => {
4 + return Boolean(
5 + typeof window !== 'undefined' &&
6 + window !== undefined &&
7 + window.__TAURI__ !== undefined
8 + )
9 +}
10 +
11 +export default isTauri
\ No newline at end of file
frontend/splitfire-desktop/src/lib/logger.ts new
+10
@@ -0,0 +1,10 @@
1 +import { Logger } from "tslog"
2 +import isTauri from "./isTauri"
3 +
4 +export const useLogger = (name: string) => {
5 + return new Logger({
6 + name: name,
7 + type: isTauri() ? 'json' : 'pretty',
8 + minLevel: process.env.NODE_ENV === "production" ? 6: 0,
9 + })
10 +}
\ No newline at end of file
frontend/splitfire-desktop/src/lib/requestAudioFiles.ts new
+5
@@ -0,0 +1,5 @@
1 +import axios from "./axios"
2 +
3 +export default function requestAudioFiles(audioFileId: string): Promise<any> {
4 + return axios.get(`/splitfire/${audioFileId}`)
5 +}
\ No newline at end of file
frontend/splitfire-desktop/src/lib/requestSplitService.ts new
+18
@@ -0,0 +1,18 @@
1 +import { AudioFile } from "../components/player/models/AudioFile"
2 +import axios from "./axios"
3 +
4 +export default function requestSplitService(providerId: string, accessToken: string): Promise<any> {
5 + return axios.post(`/split`, { provider_id: providerId },
6 + {
7 + headers: {
8 + 'Content-Type': 'application/json',
9 + 'Authorization': accessToken,
10 + }
11 + })
12 +}
13 +
14 +export interface SplitResponse {
15 + code: number
16 + audio_file: AudioFile
17 + message: string
18 +}
\ No newline at end of file
frontend/splitfire-desktop/src/lib/tauriHandler.ts new
+25
@@ -0,0 +1,25 @@
1 +export const TAURI_DOWNLOAD_RESULT_FILES = 'download_result_files'
2 +
3 +// Player commands
4 +export const TAURI_PLAYER_PREPARE = 'player_prepare'
5 +export const TAURI_PLAYER_SET_VOLUME = 'player_set_volume'
6 +export const TAURI_PLAYER_UNMOUNT = 'player_unmount'
7 +export const TAURI_PLAYER_RECORD = 'player_record'
8 +export const TAURI_PLAYER_RECORD_STOP = 'player_record_stop'
9 +export const TAURI_PLAYER_PLAY = 'player_play'
10 +export const TAURI_PLAYER_PAUSED = 'player_paused'
11 +export const TAURI_PLAYER_RESUMED = 'player_resumed'
12 +export const TAURI_PLAYER_STOP = 'player_stop'
13 +export const TAURI_PLAYER_RECORDING_LENGTH = 'player_recording_length' // in seconds
14 +
15 +export enum TauriResponse {
16 + ERROR = 0,
17 + SUCCESS = 1
18 +}
19 +
20 +// Response from the Tauri API
21 +export interface PlayerPrepareResponse {
22 + status: TauriResponse,
23 + message: string,
24 + audio_file_name?: string,
25 +}
\ No newline at end of file
frontend/splitfire-desktop/src/lib/useCountDown.ts new
+22
@@ -0,0 +1,22 @@
1 +import { useState, useEffect } from 'react';
2 +
3 +export function useCountDown() {
4 + const [secondsLeft, setSecondsLeft] = useState(0);
5 +
6 + useEffect(() => {
7 + if (secondsLeft <= 0) return
8 +
9 + const timerId = setTimeout(() => setSecondsLeft(secondsLeft - 1), 1000);
10 + return () => clearTimeout(timerId);
11 + }
12 + , [secondsLeft]);
13 +
14 + function startTimer(seconds: number) {
15 + setSecondsLeft(seconds);
16 + }
17 +
18 + return {
19 + secondsLeft,
20 + startTimer,
21 + };
22 +}
\ No newline at end of file
frontend/splitfire-desktop/src/logo.svg new
+1
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
frontend/splitfire-desktop/src/models/SongResponse.tsx new
+74
@@ -0,0 +1,74 @@
1 +import { AudioFile } from "../components/player/models/AudioFile"
2 +
3 +export interface SongResponse {
4 + code: number,
5 + message: string,
6 + name: string,
7 + album_name: string,
8 + images: ProviderImage[],
9 + items: SongProvider[]
10 +}
11 +/*
12 +{
13 + "id": 1571,
14 + "user_id": 1,
15 + "song_id": 641,
16 + "provider_id": "EYACy7jELw4",
17 + "provider_type": "youtube",
18 + "name": "Arjuna",
19 + "preview_url": null,
20 + "created_at": "2022-11-23T12:56:56.623Z",
21 + "updated_at": "2022-11-23T12:56:56.623Z",
22 + "album_provider_id": 1,
23 + "artist_provider_id": null,
24 + "path": "/songs-bridge/arjuna-1571",
25 + "type": "Song bridge",
26 + "created": "6 months ago"
27 +}
28 +*/
29 +export interface SongProvider {
30 + id: string,
31 + name: string,
32 + provider_id: string,
33 + provider_type: ProviderType,
34 + image_url: string,
35 + audio_file: AudioFile | null
36 +}
37 +
38 +enum ProviderType {
39 + YOUTUBE = 'youtube',
40 + SPOTIFY = 'spotify'
41 +}
42 +
43 +export interface ProviderImage {
44 + height: number,
45 + url: string,
46 + width: number
47 +}
48 +
49 +// Type definitions for Youtube API v3
50 +export interface YoutubeVideo {
51 + etag: string,
52 + snippet: YoutubeVideoSnippet
53 +}
54 +export interface YoutubeVideoSnippet {
55 + videoId: string,
56 + publishedAt: string,
57 + channelId: string,
58 + title: string,
59 + description: string,
60 + thumbnails: YoutubeVideoThumbnails,
61 + channelTitle: string,
62 + liveBroadcastContent: string,
63 + publishTime: string
64 +}
65 +export interface YoutubeVideoThumbnails {
66 + default: YoutubeVideoThumbnail,
67 + medium: YoutubeVideoThumbnail,
68 + high: YoutubeVideoThumbnail
69 +}
70 +export interface YoutubeVideoThumbnail {
71 + url: string,
72 + width: number,
73 + height: number
74 +}
\ No newline at end of file
frontend/splitfire-desktop/src/models/SongVotesDetailResponse.tsx new
+20
@@ -0,0 +1,20 @@
1 +import { VoteType } from "../components/pages/song/components/UpDownVotes"
2 +import { SongProvider } from "./SongResponse"
3 +
4 +export default interface SongVotesDetailResponse {
5 + code: string
6 + message: string
7 + song_provider: SongProvider
8 + votes: SongProviderVote[]
9 +}
10 +
11 +export interface SongProviderVote {
12 + id: number
13 + user_id: number
14 + song_provider_id: number
15 + vote_type: VoteType
16 + voter_username_or_id: string
17 + voter_gravatar: string
18 + created_at: string
19 + updated_at: string
20 +}
\ No newline at end of file
frontend/splitfire-desktop/src/models/lyricSynches.tsx new
+15
@@ -0,0 +1,15 @@
1 +export default interface LyricSynch {
2 + id: number,
3 + lyric: string,
4 + time: string
5 +}
6 +
7 +export function compare(a: LyricSynch, b: LyricSynch) {
8 + if (a.id < 0) {
9 + return 0
10 + }
11 + if (a.id < b.id) {
12 + return -1
13 + }
14 + return 0
15 +}
\ No newline at end of file
frontend/splitfire-desktop/src/models/user.tsx new
+35
@@ -0,0 +1,35 @@
1 +export default interface User {
2 + id: number,
3 + email: string,
4 + username: string,
5 + name: string,
6 + gravatar_url: string,
7 + followers_count: number,
8 + following_count: number,
9 + about: string
10 +}
11 +
12 +export function usernameOrId(user: User): string {
13 + const usernameOrId = user.username ? user.username : `${user.id}`
14 + return `${usernameOrId}`
15 +}
16 +
17 +export interface ProfileResponse {
18 + code: number,
19 + message: string,
20 + user: User
21 +}
22 +
23 +export interface FollowingResponse {
24 + code: number,
25 + message: string,
26 + user: User,
27 + following: User[]
28 +}
29 +
30 +export interface FollowersResponse {
31 + code: number,
32 + message: string,
33 + user: User,
34 + followers: User[]
35 +}
\ No newline at end of file
frontend/splitfire-desktop/src/reportWebVitals.js new
+13
@@ -0,0 +1,13 @@
1 +const reportWebVitals = onPerfEntry => {
2 + if (onPerfEntry && onPerfEntry instanceof Function) {
3 + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 + getCLS(onPerfEntry);
5 + getFID(onPerfEntry);
6 + getFCP(onPerfEntry);
7 + getLCP(onPerfEntry);
8 + getTTFB(onPerfEntry);
9 + });
10 + }
11 +};
12 +
13 +export default reportWebVitals;
frontend/splitfire-desktop/src/setupTests.js new
+5
@@ -0,0 +1,5 @@
1 +// jest-dom adds custom jest matchers for asserting on DOM nodes.
2 +// allows you to do things like:
3 +// expect(element).toHaveTextContent(/react/i)
4 +// learn more: https://github.com/testing-library/jest-dom
5 +import '@testing-library/jest-dom';
frontend/splitfire-desktop/src/translations/de.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "header.home": "Heim",
3 + "header.about": "Etwa",
4 + "header.cv": "CV",
5 + "header.contact": "Kontakt",
6 + "header.fun": "Lustige Projekte",
7 + "home.title": "Hallo, mein Name ist {name}.",
8 + "home.iam": "Ich bin ",
9 + "about.title": "Etwa",
10 + "about.skills": "Fähigkeiten",
11 + "cv.title": "CV",
12 + "contact.title": "Kontakt",
13 + "player.instruction": "Klicken Sie auf das Instrumentensymbol, um es aus dem Song zu entfernen.",
14 + "home.loading": "SplitFire AI wird geladen...",
15 + "player.loadingYoutube": "Warten auf YouTube...",
16 + "footer.toTop": "Zurück nach oben",
17 + "splitfire.whatIs": "Ich bin eine Software für künstliche Intelligenz, die Ihre Lieblingsmusik in ihre einzelnen Komponenten aufteilt.",
18 + "player.loading": "🤖 Ich mache mein Geschäft..."
19 +}
frontend/splitfire-desktop/src/translations/en.json new
+20
@@ -0,0 +1,20 @@
1 +{
2 + "header.home": "Home",
3 + "header.about": "About",
4 + "header.cv": "CV",
5 + "header.contact": "Contact",
6 + "header.fun": "Fun projects",
7 + "home.title": "Hello, my name is {name}.",
8 + "home.iam": "I'm ",
9 + "about.title": "About",
10 + "about.skills": "Skills",
11 + "cv.title": "CV",
12 + "contact.title": "Contact",
13 + "player.instruction": "Click the instrument icon to remove it from the song.",
14 + "home.loading": "Loading SplitFire AI...",
15 + "player.loadingYoutube": "Generating video...",
16 + "footer.toTop": "Back to top",
17 + "splitfire.whatIs": "I'm an artificial intelligence software who will split your favorite music to its separate components.",
18 + "player.loading": "🤖 I'm doing my job...",
19 + "song.loading": "🤖 Searching for song..."
20 +}
\ No newline at end of file
frontend/splitfire-desktop/src/translations/es.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "header.home": "Casa",
3 + "header.about": "Sobre",
4 + "header.cv": "CV",
5 + "header.contact": "Contacto",
6 + "header.fun": "Proyectos divertidos",
7 + "home.title": "Hola, mi nombre es {name}.",
8 + "home.iam": "Estoy ",
9 + "about.title": "Acerca de",
10 + "about.skills": "Habilidades",
11 + "cv.title": "CV",
12 + "contact.title": "Contacto",
13 + "player.instruction": "Haz clic en el ícono del instrumento para eliminarlo de la canción.",
14 + "home.loading": "Cargando SplitFire IA...",
15 + "player.loadingYoutube": "Esperando YouTube...",
16 + "footer.toTop": "Volver al principio",
17 + "splitfire.whatIs": "Soy un software de inteligencia artificial que dividirá tu música favorita en sus componentes separados",
18 + "player.loading": "🤖 Estoy haciendo mi trabajo..."
19 +}
frontend/splitfire-desktop/src/translations/fr.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "header.home": "Accueil",
3 + "header.about": "Sur",
4 + "header.cv": "CV",
5 + "header.contact": "Contact",
6 + "header.fun": "Projets amusants",
7 + "home.title": "Bonjour, je m'appelle {name}.",
8 + "home.iam": "Je suis ",
9 + "about.title": "Sur",
10 + "about.skills": "Compétences",
11 + "cv.title": "CV",
12 + "contact.title": "Contact",
13 + "player.instruction": "Cliquez sur l'icône de l'instrument pour le supprimer du morceau.",
14 + "home.loading": "Chargement de SplitFire AI...",
15 + "player.loadingYoutube": "En attente de YouTube...",
16 + "footer.toTop": "Retour en haut",
17 + "splitfire.whatIs": "Je suis un logiciel d'intelligence artificielle qui divisera votre musique préférée en ses composants séparés.",
18 + "player.loading": "🤖 Je fais mes affaires..."
19 +}
frontend/splitfire-desktop/src/translations/id.json new
+19
@@ -0,0 +1,19 @@
1 + {
2 + "header.home": "Beranda",
3 + "header.about": "Tentang",
4 + "header.cv": "CV",
5 + "header.contact": "Kontak",
6 + "header.fun": "Iseng-iseng berhadiah",
7 + "home.title": "Halo, nama saya {name}.",
8 + "home.iam": "Saya ",
9 + "about.title": "Tentang",
10 + "about.skills": "Keterampilan",
11 + "cv.title": "CV",
12 + "contact.title": "Kontak",
13 + "player.instruction": "Klik ikon instrumen untuk menghapusnya dari lagu.",
14 + "home.loading": "Memuat SplitFire AI...",
15 + "player.loadingYoutube": "Menunggu YouTube...",
16 + "footer.toTop": "Kembali ke atas",
17 + "splitfire.whatIs": "Saya adalah perangkat lunak kecerdasan buatan yang akan membagi musik favorit Anda ke komponen terpisah.",
18 + "player.loading": "🤖 Saya sedang melakukan pekerjaan saya..."
19 + }
frontend/splitfire-desktop/src/translations/ja.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "header.home": "ホーム",
3 + "header.about": "約",
4 + "header.cv": "CV",
5 + "header.contact": "連絡先",
6 + "header.fun": "楽しいプロジェクト",
7 + "home.title": "こんにちは,私の名前は{name}です。",
8 + "home.iam": "私は",
9 + "about.title": "約",
10 + "about.skills": "スキル",
11 + "cv.title": "CV",
12 + "contact.title": "連絡先",
13 + "player.instruction": "楽器のアイコンをクリックして,曲から削除します。",
14 + "home.loading": "SplitFire AIを読み込んでいます...",
15 + "player.loadingYoutube": "YouTubeを待っています...",
16 + "footer.toTop": "トップに戻る",
17 + "splitfire.whatIs": "私は,お気に入りの音楽を個別のコンポーネントに分割する人工知能ソフトウェアです。",
18 + "player.loading": "🤖私は自分の仕事をしています..."
19 +}
frontend/splitfire-desktop/src/translations/sv.json new
+19
@@ -0,0 +1,19 @@
1 + {
2 + "header.home": "Hem",
3 + "header.about": "Om",
4 + "header.cv": "CV",
5 + "header.contact": "Kontakt",
6 + "header.fun": "Roliga projekt",
7 + "home.title": "Tjena, jag heter {name}.",
8 + "home.iam": "Jag ",
9 + "about.title": "Om",
10 + "about.skills": "Kompetens",
11 + "cv.title": "CV",
12 + "contact.title": "Kontakt",
13 + "player.instruction": "Klicka på instrumentikonen för att ta bort den från låten.",
14 + "home.loading": "Laddar SplitFire AI...",
15 + "player.loadingYoutube": "Väntar på YouTube...",
16 + "footer.toTop": "Tillbaka till toppen",
17 + "splitfire.whatIs": "Jag är en programvara för artificiell intelligens som delar upp din favoritmusik till dess separata komponenter.",
18 + "player.loading": "🤖 Jag gör min job..."
19 + }
\ No newline at end of file
frontend/splitfire-desktop/src/translations/zh.json new
+19
@@ -0,0 +1,19 @@
1 +{
2 + "header.home": "家",
3 + "header.about": "关于",
4 + "header.cv": "CV",
5 + "header.contact": "接触",
6 + "header.fun": "有趣的项目",
7 + "home.title": "你好我的名字是 {name}.",
8 + "home.iam": "我是",
9 + "about.title": "关于",
10 + "about.skills": "技能",
11 + "cv.title": "CV",
12 + "contact.title": "接触",
13 + "player.instruction": "点击乐器图标将其从歌曲中移除。",
14 + "home.loading": "正在加载 SplitFire AI...",
15 + "player.loadingYoutube": "正在等待 YouTube...",
16 + "footer.toTop": "回到顶部",
17 + "splitfire.whatIs": "我是一个人工智能软件,可以将你最喜欢的音乐拆分成不同的组件.",
18 + "player.loading": "🤖 我在做我的事......"
19 +}
frontend/splitfire-desktop/tsconfig.json new
+34
@@ -0,0 +1,34 @@
1 +{
2 + "compilerOptions": {
3 + "target": "es5",
4 + "lib": [
5 + "dom",
6 + "dom.iterable",
7 + "esnext"
8 + ],
9 + "allowJs": true,
10 + "skipLibCheck": true,
11 + "esModuleInterop": true,
12 + "allowSyntheticDefaultImports": true,
13 + "strict": true,
14 + "forceConsistentCasingInFileNames": true,
15 + "module": "esnext",
16 + "moduleResolution": "node",
17 + "resolveJsonModule": true,
18 + "isolatedModules": true,
19 + "noEmit": true,
20 + "jsx": "react-jsx",
21 + "typeRoots": [
22 + "./types",
23 + "./node_modules/@types"
24 + ],
25 + "noFallthroughCasesInSwitch": true
26 + },
27 + "include": [
28 + "src"
29 + ],
30 + "exclude": [
31 + "node_modules",
32 + "types"
33 + ]
34 +}
frontend/splitfire-desktop/types/react-intl/index.d.ts new
+3
@@ -0,0 +1,3 @@
1 +declare module 'react-intl'
2 +declare module 'react-intl/locale-data/en'
3 +declare module 'react-intl/locale-data/id'
\ No newline at end of file