Merge pull request 'rebase' (#1) from RemixDev/deemix-webui:main into main
Reviewed-on: https://codeberg.org/wolfwork/deemix-webui/pulls/1
This commit is contained in:
commit
d886194d6f
@ -34,12 +34,14 @@ By simply running
|
||||
$ npm run dev
|
||||
```
|
||||
|
||||
P.S.: You need to be inside the [deemix-pyweb](https://codeberg.org/RemixDev/deemix-pyweb) repo to have the server working correctly.
|
||||
|
||||
you will have 3 tasks running at the same time:
|
||||
- the [Python](https://www.python.org/) server
|
||||
- the server
|
||||
- the [rollup](https://rollupjs.org/guide/en/) watcher pointing to the configured `.js` file and ready to re-bundle
|
||||
- the [SASS](https://sass-lang.com/) compiler watching for `.scss` files changes
|
||||
|
||||
Note that in development mode 2 more files, `bundle.js.map` and `style.css.map`, will be created in the public folder. These files will be deleted when running the build command, so you don't need to worry about them.
|
||||
Note that in development mode 2 more files (`bundle.js.map` and `style.css.map`) will be created in the public folder. These files will be deleted when running the build command, so you don't need to worry about them.
|
||||
|
||||
**You can now go to http://127.0.0.1:6595 and see the app running.**
|
||||
|
||||
@ -51,7 +53,7 @@ However, if you need to edit the `public/index.html` file you'll have to kill th
|
||||
|
||||
### Adding files
|
||||
|
||||
If you want to add new super-awesome `.js` files, just add them. Deemix uses ES6 synthax, so you'll probably need to export some functions or variables from your new file. Files that will export and import nothing will be ignored by the bundler (rollup).
|
||||
If you want to add new super-awesome `.js` or `.vue` files, just add them. Deemix uses ES6 synthax, so you'll probably need to export some functions or variables from your new file. Files that will export and import nothing will be ignored by the bundler (rollup).
|
||||
|
||||
If you want to add new mega-awesome `.scss` (style) files, you need to import them in the main `style.scss` file. The `.scss` files **must** all start with an underscore _, except for the `style.scss` file.
|
||||
|
||||
|
39
README.md
39
README.md
@ -1,38 +1,15 @@
|
||||
# deemix-webui
|
||||
|
||||
This is just the WebUI for deemix, it should be used with deemix-pyweb or something like that
|
||||
This is just the WebUI for deemix, it should be used with deemix-pyweb or something like that.
|
||||
If you are a web developer and want to contribute to this project, please read the [COMPILE-UI](COMPILE-UI.md) file.
|
||||
|
||||
## What's left to do?
|
||||
## Related projects
|
||||
|
||||
- [ ] Use Vue app-wide
|
||||
- [X] First step: rewrite the app in Single File Components way
|
||||
- [ ] Second step: Implement routing for the whole app using Vue Router ⚒
|
||||
- [ ] Third step: Remove jQuery
|
||||
- [ ] Implement custom contextmenu ⚒
|
||||
- [X] Copy and paste functions
|
||||
- [X] Copy Link where possible
|
||||
- [X] Download Quality
|
||||
- [X] Copy Image URL where possible
|
||||
- [ ] Resolve cut/copy/paste compatibility issues
|
||||
- [ ] Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html)
|
||||
- Use ES2020 async imports, if possible
|
||||
- [ ] Make the UI look coherent
|
||||
- [ ] Style buttons
|
||||
- [ ] Style text inputs
|
||||
- [ ] Style checkboxes
|
||||
- [ ] Search tab
|
||||
- [ ] Better placeholer before search
|
||||
- [ ] Link Analyzer
|
||||
- [ ] Better placeholer before analyzing and error feedback
|
||||
- [ ] Settings tab
|
||||
- [ ] Variable selector near template inputs
|
||||
- Maybe tabbing the section for easy navigation
|
||||
- Could use a carousel, but it's not worth adding a new dep
|
||||
- [ ] Block selection where it's not needed (keep only titles artists albums labels and useful data)
|
||||
- There's a SASS mixin for this. Need to use it in the proper classes
|
||||
- [ ] Better feedback for socket.io possible errors
|
||||
- [X] Remove images size limit and add warning if > 1200
|
||||
- ?
|
||||
You can find more informations about deemix at https://deemix.app/
|
||||
|
||||
- [deemix](https://codeberg.org/RemixDev/deemix)
|
||||
- [deemix-pyweb](https://codeberg.org/RemixDev/deemix-pyweb)
|
||||
- [deemix-tools](https://codeberg.org/RemixDev/deemix-tools)
|
||||
|
||||
# License
|
||||
|
||||
|
26
package-lock.json
generated
26
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "deemix",
|
||||
"version": "1.0.16",
|
||||
"name": "deemix-webui",
|
||||
"version": "0.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -1241,11 +1241,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
||||
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.2.tgz",
|
||||
@ -2795,9 +2790,9 @@
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
|
||||
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
|
||||
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
|
||||
},
|
||||
"vue-i18n": {
|
||||
"version": "8.18.2",
|
||||
@ -2810,9 +2805,9 @@
|
||||
"integrity": "sha512-SdKRBeoXUjaZ9R/8AyxsdTqkOfMcI5tWxPZOUX5Ie1BTL5rPSZ0O++pbiZCeYeythiZIdLEfkDiQPKIaWk5hDg=="
|
||||
},
|
||||
"vue-template-compiler": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
|
||||
"integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==",
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz",
|
||||
"integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"de-indent": "^1.0.2",
|
||||
@ -2825,6 +2820,11 @@
|
||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
|
||||
"integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw=="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
|
14
package.json
14
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "deemix",
|
||||
"version": "1.0.16",
|
||||
"name": "deemix-webui",
|
||||
"version": "0.2.0",
|
||||
"homepage": "https://codeberg.org/RemixDev/deemix-webui/src/master/README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -13,17 +13,19 @@
|
||||
"build:js": "rollup -c",
|
||||
"watch:js": "rollup -c -w",
|
||||
"serve": "python ../server.py",
|
||||
"serve:gui": "python ../deemix-pyweb.py --dev",
|
||||
"dev": "npm-run-all --parallel serve watch:js watch:styles",
|
||||
"dev:gui": "npm-run-all --parallel serve:gui watch:js watch:styles",
|
||||
"build": "npm-run-all --sequential clean build:js build:styles"
|
||||
},
|
||||
"dependencies": {
|
||||
"jquery": "^3.5.1",
|
||||
"lodash-es": "^4.17.15",
|
||||
"svg-country-flags": "^1.2.7",
|
||||
"toastify-js": "^1.8.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue": "^2.6.12",
|
||||
"vue-i18n": "^8.18.2",
|
||||
"vue-router": "^3.3.4"
|
||||
"vue-router": "^3.3.4",
|
||||
"vuex": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^3.1.0",
|
||||
@ -39,6 +41,6 @@
|
||||
"rollup-plugin-terser": "^6.1.0",
|
||||
"rollup-plugin-vue": "^4.2.0",
|
||||
"sass": "^1.26.8",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
140
public/css/vendor/OpenSans.css
vendored
140
public/css/vendor/OpenSans.css
vendored
@ -4,7 +4,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hmIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -13,7 +13,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hvIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -22,7 +22,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hnIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -31,7 +31,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hoIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -40,7 +40,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hkIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -49,7 +49,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hlIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -58,7 +58,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWyV9hrIqM.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -67,7 +67,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Udc1UAw.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -76,7 +76,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0ddc1UAw.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -85,7 +85,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Vdc1UAw.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -94,7 +94,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0adc1UAw.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -103,7 +103,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Wdc1UAw.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -112,7 +112,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Xdc1UAw.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -121,7 +121,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Zdc0.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem6YaGs126MiZpBA-UFUK0Zdc0.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -130,7 +130,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhmIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -139,7 +139,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhvIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -148,7 +148,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhnIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -157,7 +157,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhoIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -166,7 +166,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhkIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -175,7 +175,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhlIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -184,7 +184,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKXGUdhrIqM.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -193,7 +193,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhmIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhmIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -202,7 +202,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhvIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhvIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -211,7 +211,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhnIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhnIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -220,7 +220,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhoIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhoIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -229,7 +229,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhkIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhkIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -238,7 +238,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhlIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhlIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -247,7 +247,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhrIqM.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKWiUNhrIqM.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -256,7 +256,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hmIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hmIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -265,7 +265,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hvIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hvIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -274,7 +274,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hnIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hnIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -283,7 +283,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hoIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hoIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -292,7 +292,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hkIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hkIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -301,7 +301,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hlIqOjjg.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hlIqOjjg.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -310,7 +310,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hrIqM.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/memnYaGs126MiZpBA-UFUKW-U9hrIqM.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -319,7 +319,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OX-hpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -328,7 +328,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OVuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -337,7 +337,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OXuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -346,7 +346,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OUehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -355,7 +355,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OXehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -364,7 +364,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OXOhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -373,7 +373,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OUuhp.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN_r8OUuhp.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -382,7 +382,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFWJ0bbck.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFWJ0bbck.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -391,7 +391,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFUZ0bbck.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFUZ0bbck.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -400,7 +400,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFWZ0bbck.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFWZ0bbck.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -409,7 +409,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFVp0bbck.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFVp0bbck.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -418,7 +418,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFWp0bbck.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFWp0bbck.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -427,7 +427,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFW50bbck.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFW50bbck.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -436,7 +436,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem8YaGs126MiZpBA-UFVZ0b.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem8YaGs126MiZpBA-UFVZ0b.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -445,7 +445,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOX-hpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -454,7 +454,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOVuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -463,7 +463,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOXuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -472,7 +472,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOUehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -481,7 +481,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOXehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -490,7 +490,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOXOhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -499,7 +499,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOUuhp.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UNirkOUuhp.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -508,7 +508,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOX-hpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOX-hpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -517,7 +517,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOVuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOVuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -526,7 +526,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOXuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOXuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -535,7 +535,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOUehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOUehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -544,7 +544,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOXehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOXehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -553,7 +553,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOXOhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOXOhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -562,7 +562,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOUuhp.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN7rgOUuhp.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@ -571,7 +571,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOX-hpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOX-hpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@ -580,7 +580,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOVuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOVuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@ -589,7 +589,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOXuhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOXuhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@ -598,7 +598,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOUehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOUehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@ -607,7 +607,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOXehpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOXehpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@ -616,7 +616,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOXOhpOqc.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOXOhpOqc.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@ -625,6 +625,6 @@
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('/public/fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOUuhp.woff2') format('woff2');
|
||||
src: url('../../fonts/OpenSans/mem5YaGs126MiZpBA-UN8rsOUuhp.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
@ -4,12 +4,12 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>deemix</title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/vendor/animate.css">
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/vendor/material-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/vendor/OpenSans.css">
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/vendor/toastify.css">
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/style.css">
|
||||
<link rel="shortcut icon" href="/public/favicon.ico">
|
||||
<link rel="stylesheet" type="text/css" href="/css/vendor/animate.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/vendor/material-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/vendor/OpenSans.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/vendor/toastify.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/style.css">
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=0">
|
||||
<script>
|
||||
@ -23,8 +23,8 @@
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript" src="/public/js/vendor/socket.io.js"></script>
|
||||
<script type="text/javascript" src="/js/vendor/socket.io.js"></script>
|
||||
|
||||
<script src="/public/js/bundle.js"></script>
|
||||
<script src="/js/bundle.js"></script>
|
||||
|
||||
</html>
|
File diff suppressed because one or more lines are too long
100
src/app.js
100
src/app.js
@ -7,9 +7,9 @@ window.vol = {
|
||||
|
||||
import App from '@components/App.vue'
|
||||
import i18n from '@/plugins/i18n'
|
||||
// import router from '@/plugins/router'
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
|
||||
import $ from 'jquery'
|
||||
import { socket } from '@/utils/socket'
|
||||
import { toast } from '@/utils/toasts'
|
||||
import { init as initTabs } from '@js/tabs.js'
|
||||
@ -27,17 +27,15 @@ function startApp() {
|
||||
|
||||
function mountApp() {
|
||||
new Vue({
|
||||
// router,
|
||||
store,
|
||||
router,
|
||||
i18n,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
}
|
||||
|
||||
function initClient() {
|
||||
window.clientMode = true
|
||||
document.querySelector(`#open_downloads_folder`).classList.remove('hide')
|
||||
document.querySelector(`#select_downloads_folder`).classList.remove('hide')
|
||||
document.querySelector(`#settings_btn_applogin`).classList.remove('hide')
|
||||
store.dispatch('setClientMode', true)
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', startApp)
|
||||
@ -45,25 +43,18 @@ window.addEventListener('pywebviewready', initClient)
|
||||
|
||||
/* ===== Global shortcuts ===== */
|
||||
|
||||
document.addEventListener('keyup', keyEvent => {
|
||||
if (keyEvent.key == "Backspace" && keyEvent.ctrlKey){
|
||||
let searchbar = document.querySelector('#searchbar')
|
||||
searchbar.value = ""
|
||||
searchbar.focus()
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('paste', pasteEvent => {
|
||||
pasteText = pasteEvent.clipboardData.getData('Text')
|
||||
if (pasteEvent.target.localName != "input"){
|
||||
if (isValidURL(pasteText)){
|
||||
if (main_selected === 'analyzer_tab') {
|
||||
let pasteText = pasteEvent.clipboardData.getData('Text')
|
||||
|
||||
if (pasteEvent.target.localName != 'input') {
|
||||
if (isValidURL(pasteText)) {
|
||||
if (window.main_selected === 'analyzer_tab') {
|
||||
EventBus.$emit('linkAnalyzerTab:reset')
|
||||
socket.emit('analyzeLink', pasteText)
|
||||
} else {
|
||||
Downloads.sendAddToQueue(pasteText)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
let searchbar = document.querySelector('#searchbar')
|
||||
searchbar.select()
|
||||
searchbar.setSelectionRange(0, 99999)
|
||||
@ -85,8 +76,10 @@ socket.on('logging_in', function() {
|
||||
socket.on('init_autologin', function() {
|
||||
let arl = localStorage.getItem('arl')
|
||||
let accountNum = localStorage.getItem('accountNum')
|
||||
|
||||
if (arl) {
|
||||
arl = arl.trim()
|
||||
|
||||
if (accountNum != 0) {
|
||||
socket.emit('login', arl, true, accountNum)
|
||||
} else {
|
||||
@ -96,62 +89,42 @@ socket.on('init_autologin', function() {
|
||||
})
|
||||
|
||||
socket.on('logged_in', function(data) {
|
||||
switch (data.status) {
|
||||
const { status, user } = data
|
||||
|
||||
switch (status) {
|
||||
case 1:
|
||||
case 3:
|
||||
// Login ok
|
||||
toast(i18n.t('toasts.loggedIn'), 'done', true, 'login-toast')
|
||||
if (data.arl) {
|
||||
localStorage.setItem('arl', data.arl)
|
||||
$('#login_input_arl').val(data.arl)
|
||||
}
|
||||
$('#open_login_prompt').hide()
|
||||
if (data.user) {
|
||||
$('#settings_username').text(data.user.name)
|
||||
$('#settings_picture').attr(
|
||||
'src',
|
||||
`https://e-cdns-images.dzcdn.net/images/user/${data.user.picture}/125x125-000000-80-0-0.jpg`
|
||||
)
|
||||
// $('#logged_in_info').show()
|
||||
document.getElementById('logged_in_info').classList.remove('hide')
|
||||
}
|
||||
document.getElementById('home_not_logged_in').classList.add('hide')
|
||||
|
||||
store.dispatch('login', data)
|
||||
break
|
||||
case 2:
|
||||
// Already logged in
|
||||
toast(i18n.t('toasts.alreadyLogged'), 'done', true, 'login-toast')
|
||||
if (data.user) {
|
||||
$('#settings_username').text(data.user.name)
|
||||
$('#settings_picture').attr(
|
||||
'src',
|
||||
`https://e-cdns-images.dzcdn.net/images/user/${data.user.picture}/125x125-000000-80-0-0.jpg`
|
||||
)
|
||||
// $('#logged_in_info').show()
|
||||
document.getElementById('logged_in_info').classList.remove('hide')
|
||||
}
|
||||
document.getElementById('home_not_logged_in').classList.add('hide')
|
||||
|
||||
store.dispatch('setUser', user)
|
||||
break
|
||||
case 0:
|
||||
// Login failed
|
||||
toast(i18n.t('toasts.loginFailed'), 'close', true, 'login-toast')
|
||||
localStorage.removeItem('arl')
|
||||
$('#login_input_arl').val('')
|
||||
|
||||
store.dispatch('removeARL')
|
||||
break
|
||||
case -1:
|
||||
toast(i18n.t('toasts.deezerNotAvailable'), 'close', true, 'login-toast')
|
||||
$('#open_login_prompt').show()
|
||||
document.getElementById('logged_in_info').classList.add('hide')
|
||||
// $('#logged_in_info').hide()
|
||||
$('#settings_username').text('Not Logged')
|
||||
$('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
|
||||
document.getElementById('home_not_logged_in').classList.remove('hide')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('logged_out', function() {
|
||||
toast(i18n.t('toasts.loggedOut'), 'done', true, 'login-toast')
|
||||
localStorage.removeItem('arl')
|
||||
$('#login_input_arl').val('')
|
||||
$('#open_login_prompt').show()
|
||||
document.getElementById('logged_in_info').classList.add('hide')
|
||||
$('#settings_username').text('Not Logged')
|
||||
$('#settings_picture').attr('src', `https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg`)
|
||||
document.getElementById('home_not_logged_in').classList.remove('hide')
|
||||
|
||||
store.dispatch('logout')
|
||||
})
|
||||
|
||||
socket.on('restoringQueue', function() {
|
||||
@ -167,11 +140,11 @@ socket.on('currentItemCancelled', function(uuid) {
|
||||
})
|
||||
|
||||
socket.on('startAddingArtist', function(data) {
|
||||
toast(i18n.t('toasts.startAddingArtist', {artist: data.name}), 'loading', false, 'artist_' + data.id)
|
||||
toast(i18n.t('toasts.startAddingArtist', { artist: data.name }), 'loading', false, 'artist_' + data.id)
|
||||
})
|
||||
|
||||
socket.on('finishAddingArtist', function(data) {
|
||||
toast(i18n.t('toasts.finishAddingArtist', {artist: data.name}), 'done', true, 'artist_' + data.id)
|
||||
toast(i18n.t('toasts.finishAddingArtist', { artist: data.name }), 'done', true, 'artist_' + data.id)
|
||||
})
|
||||
|
||||
socket.on('startConvertingSpotifyPlaylist', function(id) {
|
||||
@ -187,12 +160,15 @@ socket.on('errorMessage', function(error) {
|
||||
})
|
||||
|
||||
socket.on('queueError', function(queueItem) {
|
||||
if (queueItem.errid) toast(i18n.t(`errors.ids.${queueItem.errid}`), 'error')
|
||||
else toast(queueItem.error, 'error')
|
||||
if (queueItem.errid) {
|
||||
toast(i18n.t(`errors.ids.${queueItem.errid}`), 'error')
|
||||
} else {
|
||||
toast(queueItem.error, 'error')
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('alreadyInQueue', function(data) {
|
||||
toast(i18n.t('toasts.alreadyInQueue', {item: data.title}), 'playlist_add_check')
|
||||
toast(i18n.t('toasts.alreadyInQueue', { item: data.title }), 'playlist_add_check')
|
||||
})
|
||||
|
||||
socket.on('loginNeededToDownload', function(data) {
|
||||
|
@ -1,9 +1,16 @@
|
||||
<template>
|
||||
<div style="height: inherit;">
|
||||
<BaseLoadingPlaceholder id="start_app_placeholder" text="Connecting to the server..." />
|
||||
|
||||
<div>
|
||||
<TheSidebar />
|
||||
<TheMainContent />
|
||||
|
||||
<div class="app-container">
|
||||
<div class="content-container">
|
||||
<TheSearchBar />
|
||||
<TheMiddleSection />
|
||||
</div>
|
||||
<TheDownloadTab class="downlaods" />
|
||||
</div>
|
||||
|
||||
<BaseLoadingPlaceholder id="start_app_placeholder" text="Connecting to the server..." />
|
||||
|
||||
<TheTrackPreview />
|
||||
<TheQualityModal />
|
||||
@ -12,18 +19,39 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.app-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 48px;
|
||||
}
|
||||
|
||||
.downlaods {
|
||||
flex-basis: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import TheSidebar from '@components/TheSidebar.vue'
|
||||
import TheMainContent from '@components/TheMainContent.vue'
|
||||
import TheMiddleSection from '@components/TheMiddleSection.vue'
|
||||
import TheTrackPreview from '@components/TheTrackPreview.vue'
|
||||
import TheQualityModal from '@components/TheQualityModal.vue'
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
import TheContextMenu from '@components/TheContextMenu.vue'
|
||||
import TheDownloadTab from '@components/TheDownloadTab.vue'
|
||||
import TheSearchBar from '@components/TheSearchBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheSidebar,
|
||||
TheMainContent,
|
||||
TheSearchBar,
|
||||
TheMiddleSection,
|
||||
TheDownloadTab,
|
||||
TheTrackPreview,
|
||||
TheQualityModal,
|
||||
BaseLoadingPlaceholder,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="artist_tab" class="main_tabcontent fixed_footer image_header">
|
||||
<div id="artist_tab" class="main_tabcontent fixed_footer image_header" ref="root">
|
||||
<header
|
||||
class="inline-flex"
|
||||
:style="{
|
||||
@ -51,29 +51,25 @@
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="release.cover_small"
|
||||
style="margin-right: 16px; width: 56px; height: 56px;"
|
||||
style="margin-right: 16px; width: 56px; height: 56px"
|
||||
/>
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon"> explicit </i>
|
||||
{{ release.title }}
|
||||
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color: #ff7300;">
|
||||
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color: #ff7300">
|
||||
fiber_new
|
||||
</i>
|
||||
</td>
|
||||
<td>{{ release.release_date }}</td>
|
||||
<td>{{ release.nb_song }}</td>
|
||||
<td @click.stop="addToQueue" :data-link="release.link" class="clickable">
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">
|
||||
file_download
|
||||
</i>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')"> file_download </i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<footer>
|
||||
<button class="back-button">{{ $t('globals.back') }}</button>
|
||||
<button class="back-button" @click="backTab">{{ $t('globals.back') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@ -82,7 +78,7 @@
|
||||
import { isEmpty, orderBy } from 'lodash-es'
|
||||
import { socket } from '@/utils/socket'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import { showView } from '@js/tabs'
|
||||
import { showView, backTab } from '@js/tabs'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
@ -101,6 +97,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
backTab,
|
||||
albumView: showView.bind(null, 'album'),
|
||||
reset() {
|
||||
this.title = 'Loading...'
|
||||
@ -143,6 +140,8 @@ export default {
|
||||
return g1.getTime() <= g2.getTime()
|
||||
},
|
||||
showArtist(data) {
|
||||
this.reset()
|
||||
|
||||
const { name, picture_xl, id, releases } = data
|
||||
|
||||
this.title = name
|
||||
@ -183,7 +182,6 @@ export default {
|
||||
mounted() {
|
||||
socket.on('show_artist', this.showArtist)
|
||||
|
||||
EventBus.$on('artistTab:reset', this.reset)
|
||||
EventBus.$on('artistTab:updateSelected', this.updateSelected)
|
||||
EventBus.$on('artistTab:changeTab', this.changeTab)
|
||||
}
|
||||
|
@ -1,6 +1,15 @@
|
||||
<template>
|
||||
<div id="about_tab" class="main_tabcontent">
|
||||
<div id="about_tab" class="main_tabcontent" ref="root">
|
||||
<h2 class="page_heading">{{ $t('sidebar.about') }}</h2>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('about.updates.currentVersion') }}:
|
||||
<span>{{ current || $t('about.updates.versionNotAvailable') }}</span>
|
||||
</li>
|
||||
<li>{{ $t('about.updates.deemixVersion') }}: {{ deemixVersion }}</li>
|
||||
<li v-if="updateAvailable && latest">{{ $t('about.updates.updateAvailable', { version: latest }) }}</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li v-html="$t('about.usesLibrary')"></li>
|
||||
<li v-html="$t('about.thanks')"></li>
|
||||
@ -93,7 +102,7 @@
|
||||
<a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">
|
||||
<img
|
||||
alt="GNU General Public License"
|
||||
style="border-width: 0;"
|
||||
style="border-width: 0"
|
||||
src="https://www.gnu.org/graphics/gplv3-127x51.png"
|
||||
/>
|
||||
</a>
|
||||
@ -101,6 +110,7 @@
|
||||
<p v-html="$t('about.lincensedUnder')"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
li,
|
||||
p,
|
||||
@ -190,14 +200,36 @@ ul {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/utils/socket'
|
||||
import paypal from '@/assets/paypal.svg'
|
||||
import ethereum from '@/assets/ethereum.svg'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
paypal,
|
||||
ethereum
|
||||
})
|
||||
ethereum,
|
||||
current: null,
|
||||
latest: null,
|
||||
updateAvailable: false,
|
||||
deemixVersion: null
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['getAboutInfo'])
|
||||
},
|
||||
methods: {
|
||||
initUpdate(data) {
|
||||
const { currentCommit, latestCommit, updateAvailable, deemixVersion } = data
|
||||
this.current = currentCommit
|
||||
this.latest = latestCommit
|
||||
this.updateAvailable = updateAvailable
|
||||
this.deemixVersion = deemixVersion
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initUpdate(this.getAboutInfo)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="charts_tab" class="main_tabcontent">
|
||||
<div id="charts_tab" class="main_tabcontent" ref="root">
|
||||
<h2 class="page_heading">{{ $t('charts.title') }}</h2>
|
||||
<div v-if="country === ''" id="charts_selection">
|
||||
<div class="release_grid charts_grid">
|
||||
@ -35,7 +35,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else id="charts_table">
|
||||
<button @click="changeCountry">{{ $t('charts.changeCountry') }}</button>
|
||||
<button @click="onChangeCountry">{{ $t('charts.changeCountry') }}</button>
|
||||
<button @click.stop="addToQueue" :data-link="'https://www.deezer.com/playlist/' + id">
|
||||
{{ $t('charts.download') }}
|
||||
</button>
|
||||
@ -105,15 +105,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView } from '@js/tabs.js'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import Utils from '@/utils/utils'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
|
||||
import { getChartsData } from '@/data/charts'
|
||||
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'the-charts-tab',
|
||||
data() {
|
||||
return {
|
||||
country: '',
|
||||
@ -122,7 +124,18 @@ export default {
|
||||
chart: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
socket.on('setChartTracks', this.setTracklist)
|
||||
this.$on('hook:destroyed', () => {
|
||||
socket.off('setChartTracks')
|
||||
})
|
||||
|
||||
const chartsData = await getChartsData()
|
||||
|
||||
this.initCharts(chartsData)
|
||||
},
|
||||
methods: {
|
||||
convertDuration,
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playPausePreview(e) {
|
||||
@ -134,10 +147,9 @@ export default {
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
addToQueue(e) {
|
||||
e.stopPropagation()
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
getTrackList(event) {
|
||||
document.getElementById('content').scrollTo(0, 0)
|
||||
@ -159,12 +171,12 @@ export default {
|
||||
setTracklist(data) {
|
||||
this.chart = data
|
||||
},
|
||||
changeCountry() {
|
||||
onChangeCountry() {
|
||||
this.country = ''
|
||||
this.id = 0
|
||||
},
|
||||
initCharts(data) {
|
||||
this.countries = data
|
||||
initCharts(chartsData) {
|
||||
this.countries = chartsData
|
||||
this.country = localStorage.getItem('chart') || ''
|
||||
|
||||
if (!this.country) return
|
||||
@ -182,10 +194,6 @@ export default {
|
||||
localStorage.setItem('chart', this.country)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
socket.on('init_charts', this.initCharts)
|
||||
socket.on('setChartTracks', this.setTracklist)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,72 +1,87 @@
|
||||
<template>
|
||||
<section id="content" @scroll="handleContentScroll" ref="content">
|
||||
<section id="content" @scroll="$route.name === 'Search' ? handleContentScroll($event) : null" ref="content">
|
||||
<div id="container">
|
||||
<ArtistTab />
|
||||
<TheChartsTab />
|
||||
<TheFavoritesTab />
|
||||
<TheErrorsTab />
|
||||
<TheHomeTab />
|
||||
<TheLinkAnalyzerTab />
|
||||
<TheAboutTab />
|
||||
<TheSettingsTab />
|
||||
<TheMainSearch :scrolled-search-type="newScrolled" />
|
||||
<TracklistTab />
|
||||
<BaseLoadingPlaceholder id="search_placeholder" text="Searching..." :hidden="!loading" />
|
||||
|
||||
<keep-alive>
|
||||
<router-view
|
||||
v-if="!$route.meta.notKeepAlive"
|
||||
v-show="!loading"
|
||||
:key="$route.fullPath"
|
||||
:perform-scrolled-search="performScrolledSearch"
|
||||
exclude=""
|
||||
></router-view>
|
||||
</keep-alive>
|
||||
|
||||
<router-view
|
||||
v-if="$route.meta.notKeepAlive"
|
||||
v-show="!loading"
|
||||
:key="$route.fullPath"
|
||||
:perform-scrolled-search="performScrolledSearch"
|
||||
exclude=""
|
||||
></router-view>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
max-width: 1280px;
|
||||
width: var(--container-width);
|
||||
}
|
||||
|
||||
#content {
|
||||
background-color: var(--main-background);
|
||||
width: 100%;
|
||||
height: calc(100vh - 93px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--main-background);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--main-scroll);
|
||||
border-radius: 4px;
|
||||
width: 6px;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import ArtistTab from '@components/ArtistTab.vue'
|
||||
import TracklistTab from '@components/TracklistTab.vue'
|
||||
|
||||
import TheChartsTab from '@components/TheChartsTab.vue'
|
||||
import TheFavoritesTab from '@components/TheFavoritesTab.vue'
|
||||
import TheErrorsTab from '@components/TheErrorsTab.vue'
|
||||
import TheHomeTab from '@components/TheHomeTab.vue'
|
||||
import TheLinkAnalyzerTab from '@components/TheLinkAnalyzerTab.vue'
|
||||
import TheAboutTab from '@components/TheAboutTab.vue'
|
||||
import TheSettingsTab from '@components/TheSettingsTab.vue'
|
||||
import TheMainSearch from '@components/TheMainSearch.vue'
|
||||
|
||||
import { debounce } from '@/utils/utils'
|
||||
import EventBus from '@/utils/EventBus.js'
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ArtistTab,
|
||||
TheChartsTab,
|
||||
TheFavoritesTab,
|
||||
TheErrorsTab,
|
||||
TheHomeTab,
|
||||
TheLinkAnalyzerTab,
|
||||
TheAboutTab,
|
||||
TheSettingsTab,
|
||||
TheMainSearch,
|
||||
TracklistTab
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
data: () => ({
|
||||
newScrolled: null
|
||||
performScrolledSearch: false,
|
||||
loading: false
|
||||
}),
|
||||
mounted() {
|
||||
this.$root.$on('updateSearchLoadingState', loading => {
|
||||
this.loading = loading
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleContentScroll: debounce(async function() {
|
||||
handleContentScroll: debounce(async function () {
|
||||
if (this.$refs.content.scrollTop + this.$refs.content.clientHeight < this.$refs.content.scrollHeight) return
|
||||
|
||||
if (
|
||||
main_selected !== 'search_tab' ||
|
||||
['track_search', 'album_search', 'artist_search', 'playlist_search'].indexOf(window.search_selected) === -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.newScrolled = window.search_selected.split('_')[0]
|
||||
this.performScrolledSearch = true
|
||||
|
||||
await this.$nextTick()
|
||||
|
||||
this.newScrolled = null
|
||||
this.performScrolledSearch = false
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -15,16 +15,54 @@
|
||||
:title="$t('globals.toggle_download_tab_hint')"
|
||||
></i>
|
||||
<div id="queue_buttons">
|
||||
<i id="open_downloads_folder" class="material-icons download_bar_icon hide" :title="$t('globals.open_downloads_folder')" @click="openDownloadsFolder">folder_open</i>
|
||||
<i id="clean_queue" class="material-icons download_bar_icon" @click="cleanQueue" :title="$t('globals.clean_queue_hint')">clear_all</i>
|
||||
<i id="cancel_queue" class="material-icons download_bar_icon" @click="cancelQueue" :title="$t('globals.cancel_queue_hint')">delete_sweep</i>
|
||||
<i
|
||||
id="open_downloads_folder"
|
||||
v-if="clientMode"
|
||||
class="material-icons download_bar_icon"
|
||||
:title="$t('globals.open_downloads_folder')"
|
||||
@click="openDownloadsFolder"
|
||||
>
|
||||
folder_open
|
||||
</i>
|
||||
<i
|
||||
id="clean_queue"
|
||||
class="material-icons download_bar_icon"
|
||||
@click="cleanQueue"
|
||||
:title="$t('globals.clean_queue_hint')"
|
||||
>
|
||||
clear_all
|
||||
</i>
|
||||
<i
|
||||
id="cancel_queue"
|
||||
class="material-icons download_bar_icon"
|
||||
@click="cancelQueue"
|
||||
:title="$t('globals.cancel_queue_hint')"
|
||||
>
|
||||
delete_sweep
|
||||
</i>
|
||||
</div>
|
||||
<div id="download_list" ref="list">
|
||||
<QueueItem
|
||||
v-for="item in queueList"
|
||||
:queue-item="item"
|
||||
:key="item.uuid"
|
||||
@show-errors="showErrorsTab"
|
||||
@remove-item="onRemoveItem"
|
||||
/>
|
||||
</div>
|
||||
<div id="download_list" @click="handleListClick" ref="list"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#download_tab_container {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import QueueItem from '@components/downloads/QueueItem.vue'
|
||||
|
||||
import { socket } from '@/utils/socket'
|
||||
import { toast } from '@/utils/toasts'
|
||||
|
||||
@ -32,12 +70,23 @@ const tabMinWidth = 250
|
||||
const tabMaxWidth = 500
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
cachedTabWidth: parseInt(localStorage.getItem('downloadTabWidth')) || 300,
|
||||
queue: [],
|
||||
queueList: {},
|
||||
queueComplete: []
|
||||
}),
|
||||
components: {
|
||||
QueueItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cachedTabWidth: parseInt(localStorage.getItem('downloadTabWidth')) || 300,
|
||||
queue: [],
|
||||
queueList: {},
|
||||
queueComplete: []
|
||||
// clientMode: window.clientMode
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
clientMode: 'getClientMode'
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
socket.on('startDownload', this.startDownload)
|
||||
socket.on('startConversion', this.startConversion)
|
||||
@ -69,6 +118,10 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setErrors']),
|
||||
onRemoveItem(uuid) {
|
||||
socket.emit('removeFromQueue', uuid)
|
||||
},
|
||||
setTabWidth(newWidth) {
|
||||
if (undefined === newWidth) {
|
||||
this.$refs.container.style.width = ''
|
||||
@ -78,25 +131,14 @@ export default {
|
||||
this.$refs.list.style.width = newWidth + 'px'
|
||||
}
|
||||
},
|
||||
handleListClick(event) {
|
||||
const { target } = event
|
||||
|
||||
if (!target.matches('.queue_icon[data-uuid]')) {
|
||||
return
|
||||
}
|
||||
|
||||
let icon = target.innerText
|
||||
let uuid = $(target).data('uuid')
|
||||
|
||||
switch (icon) {
|
||||
case 'remove':
|
||||
socket.emit('removeFromQueue', uuid)
|
||||
break
|
||||
default:
|
||||
}
|
||||
},
|
||||
initQueue(data) {
|
||||
const { queue: initQueue, queueComplete: initQueueComplete, currentItem, queueList: initQueueList, restored } = data
|
||||
const {
|
||||
queue: initQueue,
|
||||
queueComplete: initQueueComplete,
|
||||
currentItem,
|
||||
queueList: initQueueList,
|
||||
restored
|
||||
} = data
|
||||
|
||||
if (initQueueComplete.length) {
|
||||
initQueueComplete.forEach(item => {
|
||||
@ -115,97 +157,57 @@ export default {
|
||||
this.addToQueue(initQueueList[item])
|
||||
})
|
||||
|
||||
if (restored){
|
||||
if (restored) {
|
||||
toast(this.$t('toasts.queueRestored'), 'done', true, 'restoring_queue')
|
||||
socket.emit('queueRestored')
|
||||
}
|
||||
},
|
||||
addToQueue(queueItem, current = false) {
|
||||
if (Array.isArray(queueItem)){
|
||||
if (queueItem.length > 1){
|
||||
if (Array.isArray(queueItem)) {
|
||||
if (queueItem.length > 1) {
|
||||
queueItem.forEach((item, i) => {
|
||||
item.silent = true
|
||||
this.addToQueue(item)
|
||||
});
|
||||
toast(this.$t('toasts.addedMoreToQueue', {n: queueItem.length}), 'playlist_add_check')
|
||||
})
|
||||
toast(this.$t('toasts.addedMoreToQueue', { n: queueItem.length }), 'playlist_add_check')
|
||||
return
|
||||
}else{
|
||||
} else {
|
||||
queueItem = queueItem[0]
|
||||
}
|
||||
}
|
||||
this.queueList[queueItem.uuid] = queueItem
|
||||
|
||||
if (queueItem.downloaded + queueItem.failed == queueItem.size) {
|
||||
if (this.queueComplete.indexOf(queueItem.uuid) == -1) {
|
||||
// * Here we have only queueItem objects
|
||||
this.$set(queueItem, 'current', current)
|
||||
this.$set(this.queueList, queueItem.uuid, queueItem)
|
||||
|
||||
// * Used when opening the app in another tab
|
||||
const itemIsAlreadyDownloaded = queueItem.downloaded + queueItem.failed == queueItem.size
|
||||
|
||||
if (itemIsAlreadyDownloaded) {
|
||||
const itemIsNotInCompletedQueue = this.queueComplete.indexOf(queueItem.uuid) == -1
|
||||
|
||||
this.$set(this.queueList[queueItem.uuid], 'status', 'download finished')
|
||||
|
||||
if (itemIsNotInCompletedQueue) {
|
||||
// * Add it
|
||||
this.queueComplete.push(queueItem.uuid)
|
||||
}
|
||||
} else {
|
||||
if (this.queue.indexOf(queueItem.uuid) == -1) {
|
||||
const itemIsNotInQueue = this.queue.indexOf(queueItem.uuid) == -1
|
||||
|
||||
if (itemIsNotInQueue) {
|
||||
this.queue.push(queueItem.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
let queueDOM = document.getElementById('download_' + queueItem.uuid)
|
||||
const needToStartDownload = (queueItem.progress > 0 && queueItem.progress < 100) || current
|
||||
|
||||
if (typeof queueDOM == 'undefined' || queueDOM == null) {
|
||||
$(this.$refs.list).append(
|
||||
`<div class="download_object" id="download_${queueItem.uuid}" data-deezerid="${queueItem.id}">
|
||||
<div class="download_info">
|
||||
<img width="75px" class="rounded coverart" src="${queueItem.cover}" alt="Cover ${queueItem.title}"/>
|
||||
<div class="download_info_data">
|
||||
<span class="download_line">${queueItem.title}</span> <span class="download_slim_separator"> - </span>
|
||||
<span class="secondary-text">${queueItem.artist}</span>
|
||||
</div>
|
||||
<div class="download_info_status">
|
||||
<span class="download_line"><span class="queue_downloaded">${queueItem.downloaded + queueItem.failed}</span>/${
|
||||
queueItem.size
|
||||
}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="download_bar">
|
||||
<div class="progress"><div id="bar_${queueItem.uuid}" class="indeterminate"></div></div>
|
||||
<i class="material-icons queue_icon" data-uuid="${queueItem.uuid}">remove</i>
|
||||
</div>
|
||||
</div>`
|
||||
)
|
||||
}
|
||||
|
||||
if (queueItem.progress > 0 || current) {
|
||||
if (needToStartDownload) {
|
||||
this.startDownload(queueItem.uuid)
|
||||
}
|
||||
|
||||
$('#bar_' + queueItem.uuid).css('width', queueItem.progress + '%')
|
||||
|
||||
if (queueItem.failed >= 1 && $('#download_' + queueItem.uuid + ' .queue_failed').length == 0) {
|
||||
$('#download_' + queueItem.uuid + ' .download_info_status').append(
|
||||
`<span class="secondary-text inline-flex"><span class="download_slim_separator">(</span><span class="queue_failed_button inline-flex"><span class="queue_failed">${queueItem.failed}</span><i class="material-icons">error_outline</i></span><span class="download_slim_separator">)</span></span>`
|
||||
)
|
||||
}
|
||||
|
||||
if (queueItem.downloaded + queueItem.failed == queueItem.size) {
|
||||
let resultIcon = $('#download_' + queueItem.uuid).find('.queue_icon')
|
||||
|
||||
if (queueItem.failed == 0) {
|
||||
resultIcon.text('done')
|
||||
} else {
|
||||
let failedButton = $('#download_' + queueItem.uuid).find('.queue_failed_button')
|
||||
|
||||
resultIcon.addClass('clickable')
|
||||
failedButton.addClass('clickable')
|
||||
|
||||
resultIcon.bind('click', { item: queueItem }, this.showErrorsTab)
|
||||
failedButton.bind('click', { item: queueItem }, this.showErrorsTab)
|
||||
|
||||
if (queueItem.failed >= queueItem.size) {
|
||||
resultIcon.text('error')
|
||||
} else {
|
||||
resultIcon.text('warning')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!queueItem.silent) {
|
||||
toast(this.$t('toasts.addedToQueue', {item: queueItem.title}), 'playlist_add_check')
|
||||
toast(this.$t('toasts.addedToQueue', { item: queueItem.title }), 'playlist_add_check')
|
||||
}
|
||||
},
|
||||
updateQueue(update) {
|
||||
@ -215,34 +217,19 @@ export default {
|
||||
if (uuid && this.queue.indexOf(uuid) > -1) {
|
||||
if (downloaded) {
|
||||
this.queueList[uuid].downloaded++
|
||||
$('#download_' + uuid + ' .queue_downloaded').text(
|
||||
this.queueList[uuid].downloaded + this.queueList[uuid].failed
|
||||
)
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
this.queueList[uuid].failed++
|
||||
$('#download_' + uuid + ' .queue_downloaded').text(
|
||||
this.queueList[uuid].downloaded + this.queueList[uuid].failed
|
||||
)
|
||||
if (this.queueList[uuid].failed == 1 && $('#download_' + uuid + ' .queue_failed').length == 0) {
|
||||
$('#download_' + uuid + ' .download_info_status').append(
|
||||
`<span class="secondary-text inline-flex"><span class="download_slim_separator">(</span><span class="queue_failed_button inline-flex"><span class="queue_failed">1</span> <i class="material-icons">error_outline</i></span><span class="download_slim_separator">)</span></span>`
|
||||
)
|
||||
} else {
|
||||
$('#download_' + uuid + ' .queue_failed').text(this.queueList[uuid].failed)
|
||||
}
|
||||
|
||||
this.queueList[uuid].errors.push({ message: error, data: data, errid: errid })
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
this.queueList[uuid].progress = progress
|
||||
$('#bar_' + uuid).css('width', progress + '%')
|
||||
}
|
||||
|
||||
if (conversion) {
|
||||
$('#bar_' + uuid).css('width', (100-conversion) + '%')
|
||||
this.queueList[uuid].conversion = conversion
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -250,32 +237,28 @@ export default {
|
||||
let index = this.queue.indexOf(uuid)
|
||||
|
||||
if (index > -1) {
|
||||
this.queue.splice(index, 1)
|
||||
$(`#download_${this.queueList[uuid].uuid}`).remove()
|
||||
delete this.queueList[uuid]
|
||||
this.$delete(this.queue, index)
|
||||
this.$delete(this.queueList, uuid)
|
||||
}
|
||||
},
|
||||
removeAllDownloads(currentItem) {
|
||||
this.queueComplete = []
|
||||
|
||||
if (currentItem == '') {
|
||||
if (!currentItem) {
|
||||
this.queue = []
|
||||
this.queueList = {}
|
||||
$(listEl).html('')
|
||||
} else {
|
||||
this.queue = [currentItem]
|
||||
|
||||
let tempQueueItem = this.queueList[currentItem]
|
||||
|
||||
this.queueList = {}
|
||||
this.queueList[currentItem] = tempQueueItem
|
||||
|
||||
$('.download_object').each(function(index) {
|
||||
if ($(this).attr('id') != 'download_' + currentItem) $(this).remove()
|
||||
})
|
||||
}
|
||||
},
|
||||
removedFinishedDownloads() {
|
||||
this.queueComplete.forEach(item => {
|
||||
$('#download_' + item).remove()
|
||||
this.queueComplete.forEach(uuid => {
|
||||
this.$delete(this.queueList, uuid)
|
||||
})
|
||||
|
||||
this.queueComplete = []
|
||||
@ -300,47 +283,10 @@ export default {
|
||||
cancelQueue() {
|
||||
socket.emit('cancelAllDownloads')
|
||||
},
|
||||
finishDownload(uuid) {
|
||||
if (this.queue.indexOf(uuid) > -1) {
|
||||
toast(this.$t('toasts.finishDownload', {item: this.queueList[uuid].title}), 'done')
|
||||
|
||||
$('#bar_' + uuid).css('width', '100%')
|
||||
|
||||
let resultIcon = $('#download_' + uuid).find('.queue_icon')
|
||||
|
||||
if (this.queueList[uuid].failed == 0) {
|
||||
resultIcon.text('done')
|
||||
} else {
|
||||
let failedButton = $('#download_' + uuid).find('.queue_failed_button')
|
||||
|
||||
resultIcon.addClass('clickable')
|
||||
failedButton.addClass('clickable')
|
||||
|
||||
resultIcon.bind('click', { item: this.queueList[uuid] }, this.showErrorsTab)
|
||||
failedButton.bind('click', { item: this.queueList[uuid] }, this.showErrorsTab)
|
||||
|
||||
if (this.queueList[uuid].failed >= this.queueList[uuid].size) {
|
||||
resultIcon.text('error')
|
||||
} else {
|
||||
resultIcon.text('warning')
|
||||
}
|
||||
}
|
||||
|
||||
let index = this.queue.indexOf(uuid)
|
||||
if (index > -1) {
|
||||
this.queue.splice(index, 1)
|
||||
this.queueComplete.push(uuid)
|
||||
}
|
||||
|
||||
if (this.queue.length <= 0) {
|
||||
toast(this.$t('toasts.allDownloaded'), 'done_all')
|
||||
}
|
||||
}
|
||||
},
|
||||
openDownloadsFolder() {
|
||||
if (window.clientMode) {
|
||||
socket.emit('openDownloadsFolder')
|
||||
}
|
||||
// if (this.clientMode) {
|
||||
socket.emit('openDownloadsFolder')
|
||||
// }
|
||||
},
|
||||
handleDrag(event) {
|
||||
let newWidth = window.innerWidth - event.pageX + 2
|
||||
@ -358,24 +304,37 @@ export default {
|
||||
document.addEventListener('mousemove', this.handleDrag)
|
||||
},
|
||||
startDownload(uuid) {
|
||||
$('#bar_' + uuid)
|
||||
.removeClass('converting')
|
||||
.removeClass('indeterminate')
|
||||
.addClass('determinate')
|
||||
this.$set(this.queueList[uuid], 'status', 'downloading')
|
||||
},
|
||||
finishDownload(uuid) {
|
||||
let isInQueue = this.queue.indexOf(uuid) > -1
|
||||
|
||||
if (!isInQueue) return
|
||||
|
||||
this.$set(this.queueList[uuid], 'status', 'download finished')
|
||||
toast(this.$t('toasts.finishDownload', { item: this.queueList[uuid].title }), 'done')
|
||||
|
||||
let index = this.queue.indexOf(uuid)
|
||||
|
||||
if (index > -1) {
|
||||
this.queue.splice(index, 1)
|
||||
this.queueComplete.push(uuid)
|
||||
}
|
||||
|
||||
if (this.queue.length <= 0) {
|
||||
toast(this.$t('toasts.allDownloaded'), 'done_all')
|
||||
}
|
||||
},
|
||||
startConversion(uuid) {
|
||||
$('#bar_' + uuid)
|
||||
.addClass('converting')
|
||||
.removeClass('indeterminate')
|
||||
.addClass('determinate')
|
||||
.css('width', '100%')
|
||||
this.$set(this.queueList[uuid], 'status', 'converting')
|
||||
this.$set(this.queueList[uuid], 'conversion', 0)
|
||||
},
|
||||
showErrorsTab(clickEvent) {
|
||||
this.$root.$emit('showTabErrors', clickEvent.data.item, clickEvent.target)
|
||||
async showErrorsTab(item) {
|
||||
await this.setErrors(item)
|
||||
|
||||
this.$router.push({ name: 'Errors' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div id="errors_tab" class="main_tabcontent">
|
||||
<h1>{{ $t('errors.title', {name: title}) }}</h1>
|
||||
<h1>{{ $t('errors.title', { name: title }) }}</h1>
|
||||
|
||||
<table class="table table--tracklist">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
@ -19,31 +20,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { changeTab } from '@js/tabs.js'
|
||||
|
||||
import EventBus from '@/utils/EventBus'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'the-errors-tab',
|
||||
data: () => ({
|
||||
title: '',
|
||||
errors: []
|
||||
}),
|
||||
methods: {
|
||||
reset() {
|
||||
this.title = ''
|
||||
this.errors = []
|
||||
computed: {
|
||||
...mapGetters(['getErrors']),
|
||||
title() {
|
||||
return `${this.getErrors.artist} - ${this.getErrors.title}`
|
||||
},
|
||||
showErrors(data, eventTarget) {
|
||||
this.title = data.artist + ' - ' + data.title
|
||||
this.errors = data.errors
|
||||
|
||||
changeTab(eventTarget, 'main', 'errors_tab')
|
||||
errors() {
|
||||
return this.getErrors.errors
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('showTabErrors', this.showErrors)
|
||||
this.$root.$on('showTabErrors', this.showErrors)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="favorites_tab" class="main_tabcontent" @click="handleFavoritesTabClick">
|
||||
<div id="favorites_tab" class="main_tabcontent">
|
||||
<h2 class="page_heading">
|
||||
{{ $t('favorites.title') }}
|
||||
<div
|
||||
@ -13,21 +13,18 @@
|
||||
</div>
|
||||
</h2>
|
||||
<div class="section-tabs">
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_playlist_tab">
|
||||
{{ $tc('globals.listTabs.playlist', 2) }}
|
||||
</div>
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_album_tab">
|
||||
{{ $tc('globals.listTabs.album', 2) }}
|
||||
</div>
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_artist_tab">
|
||||
{{ $tc('globals.listTabs.artist', 2) }}
|
||||
</div>
|
||||
<div class="section-tabs__tab favorites_tablinks" id="favorites_track_tab">
|
||||
{{ $tc('globals.listTabs.track', 2) }}
|
||||
<div
|
||||
class="section-tabs__tab favorites_tablinks"
|
||||
:class="{ active: activeTab === tab }"
|
||||
@click="activeTab = tab"
|
||||
v-for="tab in tabs"
|
||||
:key="tab"
|
||||
>
|
||||
{{ $tc(`globals.listTabs.${tab}`, 2) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="playlist_favorites" class="favorites_tabcontent">
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'playlist' }">
|
||||
<div v-if="playlists.length == 0">
|
||||
<h1>{{ $t('favorites.noPlaylists') }}</h1>
|
||||
</div>
|
||||
@ -47,7 +44,12 @@
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{ `${$t('globals.by', {artist: release.creator.name})} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.creator.name })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
release.nb_tracks
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
@ -70,12 +72,18 @@
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{ `${$t('globals.by', {artist: release.creator.name})} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.creator.name })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
release.nb_tracks
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="album_favorites" class="favorites_tabcontent">
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'album' }">
|
||||
<div v-if="albums.length == 0">
|
||||
<h1>{{ $t('favorites.noAlbums') }}</h1>
|
||||
</div>
|
||||
@ -94,11 +102,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ `${$t('globals.by', {artist: release.artist.name})}` }}</p>
|
||||
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="artist_favorites" class="favorites_tabcontent">
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'artist' }">
|
||||
<div v-if="artists.length == 0">
|
||||
<h1>{{ $t('favorites.noArtists') }}</h1>
|
||||
</div>
|
||||
@ -120,7 +129,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="track_favorites" class="favorites_tabcontent">
|
||||
|
||||
<div class="favorites_tabcontent" :class="{ 'favorites_tabcontent--active': activeTab === 'track' }">
|
||||
<div v-if="tracks.length == 0">
|
||||
<h1>{{ $t('favorites.noTracks') }}</h1>
|
||||
</div>
|
||||
@ -189,24 +199,63 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.favorites_tabcontent {
|
||||
display: none;
|
||||
|
||||
&--active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { showView } from '@js/tabs'
|
||||
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView, changeTab } from '@js/tabs.js'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import Utils from '@/utils/utils'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
import { toast } from '@/utils/toasts'
|
||||
|
||||
import { getFavoritesData } from '@/data/favorites'
|
||||
|
||||
export default {
|
||||
name: 'the-favorites-tab',
|
||||
data() {
|
||||
return {
|
||||
tracks: [],
|
||||
albums: [],
|
||||
artists: [],
|
||||
playlists: [],
|
||||
spotifyPlaylists: []
|
||||
spotifyPlaylists: [],
|
||||
activeTab: 'playlist',
|
||||
tabs: ['playlist', 'album', 'artist', 'track']
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const favoritesData = await getFavoritesData()
|
||||
|
||||
// TODO Change with isLoggedIn vuex getter
|
||||
if (Object.entries(favoritesData).length === 0) return
|
||||
|
||||
this.setFavorites(favoritesData)
|
||||
},
|
||||
mounted() {
|
||||
socket.on('updated_userFavorites', this.updated_userFavorites)
|
||||
socket.on('updated_userSpotifyPlaylists', this.updated_userSpotifyPlaylists)
|
||||
socket.on('updated_userPlaylists', this.updated_userPlaylists)
|
||||
socket.on('updated_userAlbums', this.updated_userAlbums)
|
||||
socket.on('updated_userArtist', this.updated_userArtist)
|
||||
socket.on('updated_userTracks', this.updated_userTracks)
|
||||
|
||||
this.$on('hook:destroyed', () => {
|
||||
socket.off('updated_userFavorites')
|
||||
socket.off('updated_userSpotifyPlaylists')
|
||||
socket.off('updated_userPlaylists')
|
||||
socket.off('updated_userAlbums')
|
||||
socket.off('updated_userArtist')
|
||||
socket.off('updated_userTracks')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
@ -221,39 +270,9 @@ export default {
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
handleFavoritesTabClick(event) {
|
||||
const {
|
||||
target,
|
||||
target: { id }
|
||||
} = event
|
||||
let selectedTab = null
|
||||
|
||||
switch (id) {
|
||||
case 'favorites_playlist_tab':
|
||||
selectedTab = 'playlist_favorites'
|
||||
break
|
||||
case 'favorites_album_tab':
|
||||
selectedTab = 'album_favorites'
|
||||
break
|
||||
case 'favorites_artist_tab':
|
||||
selectedTab = 'artist_favorites'
|
||||
break
|
||||
case 'favorites_track_tab':
|
||||
selectedTab = 'track_favorites'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!selectedTab) return
|
||||
|
||||
changeTab(target, 'favorites', selectedTab)
|
||||
},
|
||||
convertDuration,
|
||||
addToQueue(e) {
|
||||
e.stopPropagation()
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
updated_userSpotifyPlaylists(data) {
|
||||
this.spotifyPlaylists = data
|
||||
@ -272,16 +291,15 @@ export default {
|
||||
},
|
||||
reloadTabs() {
|
||||
this.$refs.reloadButton.classList.add('spin')
|
||||
|
||||
socket.emit('update_userFavorites')
|
||||
if (localStorage.getItem('spotifyUser'))
|
||||
|
||||
if (localStorage.getItem('spotifyUser')) {
|
||||
socket.emit('update_userSpotifyPlaylists', localStorage.getItem('spotifyUser'))
|
||||
}
|
||||
},
|
||||
updated_userFavorites(data) {
|
||||
const { tracks, albums, artists, playlists } = data
|
||||
this.tracks = tracks
|
||||
this.albums = albums
|
||||
this.artists = artists
|
||||
this.playlists = playlists
|
||||
this.setFavorites(data)
|
||||
|
||||
// Removing animation class only when the animation has completed an iteration
|
||||
// Prevents animation ugly stutter
|
||||
@ -294,22 +312,14 @@ export default {
|
||||
{ once: true }
|
||||
)
|
||||
},
|
||||
initFavorites(data) {
|
||||
this.updated_userFavorites(data)
|
||||
document.getElementById('favorites_playlist_tab').click()
|
||||
setFavorites(data) {
|
||||
const { tracks, albums, artists, playlists } = data
|
||||
|
||||
this.tracks = tracks
|
||||
this.albums = albums
|
||||
this.artists = artists
|
||||
this.playlists = playlists
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
socket.on('init_favorites', this.initFavorites)
|
||||
socket.on('updated_userFavorites', this.updated_userFavorites)
|
||||
socket.on('updated_userSpotifyPlaylists', this.updated_userSpotifyPlaylists)
|
||||
socket.on('updated_userPlaylists', this.updated_userPlaylists)
|
||||
socket.on('updated_userAlbums', this.updated_userAlbums)
|
||||
socket.on('updated_userArtist', this.updated_userArtist)
|
||||
socket.on('updated_userTracks', this.updated_userTracks)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
@ -1,10 +1,15 @@
|
||||
<template>
|
||||
<div id="home_tab" class="main_tabcontent">
|
||||
<div id="home_tab" class="main_tabcontent" ref="root">
|
||||
<h2 class="page_heading">{{ $t('globals.welcome') }}</h2>
|
||||
<section id="home_not_logged_in" class="home_section" ref="notLogged">
|
||||
|
||||
<section class="home_section" ref="notLogged" v-if="!isLoggedIn">
|
||||
<p id="home_not_logged_text">{{ $t('home.needTologin') }}</p>
|
||||
<button type="button" name="button" @click="openSettings">{{ $t('home.openSettings') }}</button>
|
||||
<!-- <button type="button" name="button" @click="openSettings">{{ $t('home.openSettings') }}</button> -->
|
||||
<router-link tag="button" name="button" :to="{ name: 'Settings' }">
|
||||
{{ $t('home.openSettings') }}
|
||||
</router-link>
|
||||
</section>
|
||||
|
||||
<section v-if="playlists.length" class="home_section">
|
||||
<h3 class="section_heading">{{ $t('home.sections.popularPlaylists') }}</h3>
|
||||
<div class="release_grid">
|
||||
@ -29,11 +34,17 @@
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{ `${$t('globals.by', {artist: release.user.name})} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.user.name })} - ${$tc(
|
||||
'globals.listTabs.trackN',
|
||||
release.nb_tracks
|
||||
)}`
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="albums.length" class="home_section">
|
||||
<h3 class="section_heading">{{ $t('home.sections.popularAlbums') }}</h3>
|
||||
<div class="release_grid">
|
||||
@ -57,7 +68,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">{{ `${$t('globals.by', {artist: release.artist.name})}` }}</p>
|
||||
<p class="secondary-text">{{ `${$t('globals.by', { artist: release.artist.name })}` }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -65,27 +76,36 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView } from '@js/tabs.js'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { showView } from '@js/tabs'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { getHomeData } from '@/data/home'
|
||||
|
||||
export default {
|
||||
name: 'the-home-tab',
|
||||
data() {
|
||||
return {
|
||||
playlists: [],
|
||||
albums: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const homeData = await getHomeData()
|
||||
|
||||
this.initHome(homeData)
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isLoggedIn']),
|
||||
needToWait() {
|
||||
return this.getHomeData.albums.data.length === 0 && this.getHomeData.playlists.data.length === 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playlistView: showView.bind(null, 'playlist'),
|
||||
openSettings() {
|
||||
document.getElementById('main_settings_tablink').click()
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
initHome(data) {
|
||||
const {
|
||||
@ -96,13 +116,6 @@ export default {
|
||||
this.playlists = playlistData
|
||||
this.albums = albumData
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (localStorage.getItem('arl')) {
|
||||
this.$refs.notLogged.classList.add('hide')
|
||||
}
|
||||
|
||||
socket.on('init_home', this.initHome)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div id="analyzer_tab" class="main_tabcontent image_header">
|
||||
<div id="analyzer_tab" class="main_tabcontent image_header" ref="root">
|
||||
<h2 class="page_heading page_heading--capitalize">{{ $t('sidebar.linkAnalyzer') }}</h2>
|
||||
<div v-if="link == ''">
|
||||
|
||||
<div v-if="link === ''">
|
||||
<p>
|
||||
{{ $t('linkAnalyzer.info') }}
|
||||
</p>
|
||||
@ -9,10 +10,11 @@
|
||||
{{ $t('linkAnalyzer.useful') }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="link == 'error'">
|
||||
<div v-else-if="link === 'error'">
|
||||
<h2>{{ $t('linkAnalyzer.linkNotSupported') }}</h2>
|
||||
<p>{{ $t('linkAnalyzer.linkNotSupportedYet') }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<header
|
||||
class="inline-flex"
|
||||
@ -115,21 +117,21 @@
|
||||
|
||||
<script>
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView } from '@js/tabs.js'
|
||||
import Utils from '@/utils/utils'
|
||||
import { showView } from '@js/tabs'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
import { COUNTRIES } from '@/utils/countries'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
|
||||
export default {
|
||||
name: 'the-link-analyzer-tab',
|
||||
data() {
|
||||
return {
|
||||
link: '',
|
||||
title: '',
|
||||
subtitle: '',
|
||||
image: '',
|
||||
data: {},
|
||||
type: '',
|
||||
link: '',
|
||||
id: '0',
|
||||
countries: []
|
||||
}
|
||||
@ -137,7 +139,7 @@ export default {
|
||||
methods: {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
convertDuration: Utils.convertDuration,
|
||||
convertDuration,
|
||||
reset() {
|
||||
this.title = 'Loading...'
|
||||
this.subtitle = ''
|
||||
@ -148,6 +150,7 @@ export default {
|
||||
this.countries = []
|
||||
},
|
||||
showTrack(data) {
|
||||
this.reset()
|
||||
const {
|
||||
title,
|
||||
title_version,
|
||||
@ -167,13 +170,14 @@ export default {
|
||||
let temp = []
|
||||
let chars = [...cc].map(c => c.charCodeAt() + 127397)
|
||||
temp.push(String.fromCodePoint(...chars))
|
||||
temp.push(Utils.COUNTRIES[cc])
|
||||
temp.push(COUNTRIES[cc])
|
||||
this.countries.push(temp)
|
||||
})
|
||||
|
||||
this.data = data
|
||||
},
|
||||
showAlbum(data) {
|
||||
this.reset()
|
||||
const { title, cover_xl, link, id } = data
|
||||
|
||||
this.title = title
|
||||
@ -187,7 +191,7 @@ export default {
|
||||
this.link = 'error'
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<main id="main_content">
|
||||
<!-- <router-link to="/tracklist/132">Go to Foo</router-link> -->
|
||||
<!-- <router-view></router-view> -->
|
||||
<TheMiddleSection />
|
||||
<TheDownloadTab />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TheMiddleSection from '@components/TheMiddleSection.vue'
|
||||
import TheDownloadTab from '@components/TheDownloadTab.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheMiddleSection,
|
||||
TheDownloadTab
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
@ -1,455 +1,92 @@
|
||||
<template>
|
||||
<div id="search_tab" class="main_tabcontent" @click="handleSearchTabClick">
|
||||
<div :class="{ hide: results.query != '' }">
|
||||
<div id="search_tab" class="main_tabcontent" ref="root">
|
||||
<div v-show="!showSearchTab">
|
||||
<h2>{{ $t('search.startSearching') }}</h2>
|
||||
<p>{{ $t('search.description') }}</p>
|
||||
</div>
|
||||
<div v-show="results.query !== ''">
|
||||
|
||||
<div v-show="showSearchTab">
|
||||
<ul class="section-tabs">
|
||||
<li class="section-tabs__tab search_tablinks" id="search_all_tab">{{ $t('globals.listTabs.all') }}</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_track_tab">{{ $tc('globals.listTabs.track', 2) }}</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_album_tab">{{ $tc('globals.listTabs.album', 2) }}</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_artist_tab">
|
||||
{{ $tc('globals.listTabs.artist', 2) }}
|
||||
</li>
|
||||
<li class="section-tabs__tab search_tablinks" id="search_playlist_tab">
|
||||
{{ $tc('globals.listTabs.playlist', 2) }}
|
||||
<li
|
||||
class="section-tabs__tab"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.name"
|
||||
@click="currentTab = tab"
|
||||
:class="{ active: currentTab.name === tab.name }"
|
||||
>
|
||||
{{ tab.name }}
|
||||
</li>
|
||||
</ul>
|
||||
<div id="search_tab_content">
|
||||
<!-- ### Main Search Tab ### -->
|
||||
<div id="main_search" class="search_tabcontent">
|
||||
<template v-for="section in results.allTab.ORDER">
|
||||
<section
|
||||
v-if="
|
||||
(section != 'TOP_RESULT' && results.allTab[section].data.length > 0) ||
|
||||
results.allTab[section].length > 0
|
||||
"
|
||||
class="search_section"
|
||||
>
|
||||
<h2
|
||||
@click="changeSearchTab(section)"
|
||||
class="search_header"
|
||||
:class="{ top_result_header: section === 'TOP_RESULT' }"
|
||||
>
|
||||
{{ $tc(`globals.listTabs.${section.toLowerCase()}`, 2) }}
|
||||
</h2>
|
||||
<!-- Top result -->
|
||||
<div
|
||||
v-if="section == 'TOP_RESULT'"
|
||||
class="top_result clickable"
|
||||
@click="handleClickTopResult"
|
||||
:data-id="results.allTab.TOP_RESULT[0].id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
:src="results.allTab.TOP_RESULT[0].picture"
|
||||
:class="(results.allTab.TOP_RESULT[0].type == 'artist' ? 'circle' : 'rounded') + ' coverart'"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="results.allTab.TOP_RESULT[0].link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info_box">
|
||||
<p class="primary-text">{{ results.allTab.TOP_RESULT[0].title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
results.allTab.TOP_RESULT[0].type == 'artist'
|
||||
? $t('search.fans', {n: $n(results.allTab.TOP_RESULT[0].nb_fan)})
|
||||
: $t('globals.by', {artist: results.allTab.TOP_RESULT[0].artist}) +
|
||||
' - ' +
|
||||
$tc('globals.listTabs.trackN', results.allTab.TOP_RESULT[0].nb_song)
|
||||
}}
|
||||
</p>
|
||||
<span class="tag">{{ $tc(`globals.listTabs.${results.allTab.TOP_RESULT[0].type}`, 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'TRACK'">
|
||||
<table class="table table--tracks">
|
||||
<tbody>
|
||||
<tr v-for="track in results.allTab.TRACK.data.slice(0, 6)">
|
||||
<td class="table__icon" aria-hidden="true">
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' +
|
||||
track.ALB_PICTURE +
|
||||
'/32x32-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.EXPLICIT_LYRICS == 1" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
{{ track.SNG_TITLE + (track.VERSION ? ' ' + track.VERSION : '') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="table__cell table__cell--medium table__cell--center breakline">
|
||||
<span
|
||||
class="clickable"
|
||||
@click="artistView"
|
||||
:data-id="artist.ART_ID"
|
||||
v-for="artist in track.ARTISTS"
|
||||
>{{ artist.ART_NAME }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.ALB_ID"
|
||||
>
|
||||
{{ track.ALB_TITLE }}
|
||||
</td>
|
||||
<td class="table__cell table__cell--center">
|
||||
{{ convertDuration(track.DURATION) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://www.deezer.com/track/' + track.SNG_ID"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">
|
||||
get_app
|
||||
</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="section == 'ARTIST'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.ARTIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click="artistView"
|
||||
:data-id="release.ART_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="circle coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/artist/' +
|
||||
release.ART_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://deezer.com/artist/' + release.ART_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.ART_NAME }}</p>
|
||||
<p class="secondary-text">{{ $t('search.fans', {n: $n(release.NB_FAN)}) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'ALBUM'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.ALBUM.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click="albumView"
|
||||
:data-id="release.ALB_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' +
|
||||
release.ALB_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://deezer.com/album/' + release.ALB_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i
|
||||
v-if="[1, 4].indexOf(release.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS) != -1"
|
||||
class="material-icons explicit_icon"
|
||||
>explicit</i
|
||||
>
|
||||
{{ release.ALB_TITLE }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{ release.ART_NAME + ' - ' + $tc('globals.listTabs.trackN', release.NUMBER_TRACK) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.PLAYLIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click="playlistView"
|
||||
:data-id="release.PLAYLIST_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/' +
|
||||
release.PICTURE_TYPE +
|
||||
'/' +
|
||||
release.PLAYLIST_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="'https://deezer.com/playlist/' + release.PLAYLIST_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.TITLE }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.trackN', release.NB_SONG) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<div
|
||||
v-if="
|
||||
results.allTab.ORDER.every(section =>
|
||||
section == 'TOP_RESULT' ? results.allTab[section].length == 0 : results.allTab[section].data.length == 0
|
||||
)
|
||||
"
|
||||
>
|
||||
<h1>{{ $t('search.noResults') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ### Track Search Tab ### -->
|
||||
<div id="track_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.trackTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.trackTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsTrack') }}</h1>
|
||||
</div>
|
||||
<table class="table table--tracks" v-if="results.trackTab.data.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ $tc('globals.listTabs.title', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.artist', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.album', 1) }}</th>
|
||||
<th>
|
||||
<i class="material-icons">
|
||||
timer
|
||||
</i>
|
||||
</th>
|
||||
<th style="width: 56px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="track in results.trackTab.data">
|
||||
<td class="table__icon table__icon--big">
|
||||
<a
|
||||
href="#"
|
||||
@click="playPausePreview"
|
||||
:class="'rounded' + (track.preview ? ' single-cover' : '')"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
<i
|
||||
@mouseenter="previewMouseEnter"
|
||||
@mouseleave="previewMouseLeave"
|
||||
v-if="track.preview"
|
||||
class="material-icons preview_controls"
|
||||
:title="$t('globals.play_hint')"
|
||||
>
|
||||
play_arrow
|
||||
</i>
|
||||
<img class="rounded coverart" :src="track.album.cover_small" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
||||
? ' ' + track.title_version
|
||||
: '')
|
||||
}}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
@click="artistView"
|
||||
:data-id="track.artist.id"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
@click="albumView"
|
||||
:data-id="track.album.id"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</td>
|
||||
<td class="table__cell table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">
|
||||
get_app
|
||||
</i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- ### Album Search Tab ### -->
|
||||
<div id="album_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.albumTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.albumTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsAlbum') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.albumTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.albumTab.data"
|
||||
class="release clickable"
|
||||
@click="albumView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
|
||||
{{ release.title }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
$t('globals.by', {artist: release.artist.name}) + ' - ' + $tc('globals.listTabs.trackN', release.nb_tracks)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ### Artist Search Tab ### -->
|
||||
<div id="artist_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.artistTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.artistTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsArtist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.artistTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.artistTab.data"
|
||||
class="release clickable"
|
||||
@click="artistView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.name }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.releaseN', release.nb_album) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ### Playlist Search Tab ### -->
|
||||
<div id="playlist_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.playlistTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.playlistTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsPlaylist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.playlistTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.playlistTab.data"
|
||||
class="release clickable"
|
||||
@click="playlistView"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="addToQueue"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{ `${$t('globals.by', {artist: release.user.name})} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="currentTab.component"
|
||||
:results="results"
|
||||
@add-to-queue="addToQueue"
|
||||
@artist-view="artistView"
|
||||
@album-view="albumView"
|
||||
@playlist-view="playlistView"
|
||||
@change-search-tab="changeSearchTab"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView } from '@js/tabs.js'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import Utils from '@/utils/utils'
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
import ResultsAll from '@components/search/ResultsAll.vue'
|
||||
import ResultsAlbums from '@components/search/ResultsAlbums.vue'
|
||||
import ResultsArtists from '@components/search/ResultsArtists.vue'
|
||||
import ResultsPlaylists from '@components/search/ResultsPlaylists.vue'
|
||||
import ResultsTracks from '@components/search/ResultsTracks.vue'
|
||||
|
||||
import { changeTab } from '@js/tabs.js'
|
||||
import EventBus from '@/utils/EventBus.js'
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView } from '@js/tabs'
|
||||
import { sendAddToQueue } from '@/utils/downloads'
|
||||
import { numberWithDots, convertDuration } from '@/utils/utils'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
export default {
|
||||
name: 'the-main-search-tab',
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
data() {
|
||||
const $t = this.$t.bind(this)
|
||||
const $tc = this.$tc.bind(this)
|
||||
|
||||
return {
|
||||
currentTab: {
|
||||
name: '',
|
||||
component: {}
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
name: $t('globals.listTabs.all'),
|
||||
searchType: 'all',
|
||||
component: ResultsAll
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.track', 2),
|
||||
searchType: 'track',
|
||||
component: ResultsTracks
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.album', 2),
|
||||
searchType: 'album',
|
||||
component: ResultsAlbums
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.artist', 2),
|
||||
searchType: 'artist',
|
||||
component: ResultsArtists
|
||||
},
|
||||
{
|
||||
name: $tc('globals.listTabs.playlist', 2),
|
||||
searchType: 'playlist',
|
||||
component: ResultsPlaylists
|
||||
}
|
||||
],
|
||||
results: {
|
||||
query: '',
|
||||
allTab: {
|
||||
@ -487,16 +124,39 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showSearchTab() {
|
||||
return this.results.query !== ''
|
||||
},
|
||||
loadedTabs() {
|
||||
const loaded = []
|
||||
|
||||
for (const resultKey in this.results) {
|
||||
if (this.results.hasOwnProperty(resultKey)) {
|
||||
const result = this.results[resultKey]
|
||||
|
||||
if (result.loaded) {
|
||||
loaded.push(resultKey.replace(/Tab/g, ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loaded
|
||||
}
|
||||
},
|
||||
props: {
|
||||
scrolledSearchType: {
|
||||
type: String,
|
||||
performScrolledSearch: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.currentTab = this.tabs[0]
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('mainSearch:checkLoadMoreContent', this.checkLoadMoreContent)
|
||||
this.$root.$on('mainSearch:showNewResults', this.checkIfShowNewResults)
|
||||
|
||||
this.$root.$on('mainSearch:showNewResults', this.showNewResults)
|
||||
socket.on('mainSearch', this.handleMainSearch)
|
||||
socket.on('search', this.handleSearch)
|
||||
},
|
||||
@ -504,138 +164,65 @@ export default {
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playlistView: showView.bind(null, 'playlist'),
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
},
|
||||
handleSearchTabClick(event) {
|
||||
const {
|
||||
target,
|
||||
target: { id }
|
||||
} = event
|
||||
let selectedTab = null
|
||||
changeSearchTab(sectionName) {
|
||||
sectionName = sectionName.toLowerCase()
|
||||
|
||||
switch (id) {
|
||||
case 'search_all_tab':
|
||||
selectedTab = 'main_search'
|
||||
break
|
||||
case 'search_track_tab':
|
||||
selectedTab = 'track_search'
|
||||
break
|
||||
case 'search_album_tab':
|
||||
selectedTab = 'album_search'
|
||||
break
|
||||
case 'search_artist_tab':
|
||||
selectedTab = 'artist_search'
|
||||
break
|
||||
case 'search_playlist_tab':
|
||||
selectedTab = 'playlist_search'
|
||||
break
|
||||
let newTab = this.tabs.find(tab => {
|
||||
return tab.searchType === sectionName
|
||||
})
|
||||
|
||||
default:
|
||||
break
|
||||
if (!newTab) {
|
||||
console.error(`No tab ${sectionName} found`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedTab) return
|
||||
|
||||
changeTab(target, 'search', selectedTab)
|
||||
window.scrollTo(0, 0)
|
||||
this.currentTab = newTab
|
||||
},
|
||||
handleClickTopResult(event) {
|
||||
let topResultType = this.results.allTab.TOP_RESULT[0].type
|
||||
checkIfShowNewResults(term, mainSelected) {
|
||||
let needToPerformNewSearch = term !== this.results.query || mainSelected == 'search_tab'
|
||||
|
||||
switch (topResultType) {
|
||||
case 'artist':
|
||||
this.artistView(event)
|
||||
break
|
||||
case 'album':
|
||||
this.albumView(event)
|
||||
break
|
||||
case 'playlist':
|
||||
this.playlistView(event)
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
if (needToPerformNewSearch) {
|
||||
this.showNewResults(term)
|
||||
}
|
||||
},
|
||||
showNewResults(term, mainSelected) {
|
||||
if (term !== this.results.query || mainSelected == 'search_tab') {
|
||||
document.getElementById('search_tab_content').style.display = 'none'
|
||||
socket.emit('mainSearch', { term })
|
||||
showNewResults(term) {
|
||||
socket.emit('mainSearch', { term })
|
||||
|
||||
// Showing loading placeholder
|
||||
document.getElementById('content').style.display = 'none'
|
||||
document.getElementById('search_placeholder').classList.toggle('loading_placeholder--hidden')
|
||||
} else {
|
||||
document.getElementById('search_tab_content').style.display = 'block'
|
||||
document.getElementById('main_search_tablink').click()
|
||||
}
|
||||
// Showing loading placeholder
|
||||
this.$root.$emit('updateSearchLoadingState', true)
|
||||
this.currentTab = this.tabs[0]
|
||||
},
|
||||
checkLoadMoreContent(searchSelected) {
|
||||
if (this.results[searchSelected.split('_')[0] + 'Tab'].data.length !== 0) return
|
||||
|
||||
this.search(searchSelected.split('_')[0])
|
||||
},
|
||||
changeSearchTab(section) {
|
||||
if (section === 'TOP_RESULT') return
|
||||
|
||||
let tabID
|
||||
|
||||
// Using the switch beacuse it's tricky to find refernces of the belo IDs
|
||||
switch (section) {
|
||||
case 'TRACK':
|
||||
tabID = 'search_track_tab'
|
||||
break
|
||||
case 'ALBUM':
|
||||
tabID = 'search_album_tab'
|
||||
break
|
||||
case 'ARTIST':
|
||||
tabID = 'search_artist_tab'
|
||||
break
|
||||
case 'PLAYLIST':
|
||||
tabID = 'search_playlist_tab'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
document.getElementById(tabID).click()
|
||||
},
|
||||
addToQueue(e) {
|
||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||
sendAddToQueue(e.currentTarget.dataset.link)
|
||||
},
|
||||
numberWithDots: Utils.numberWithDots,
|
||||
convertDuration: Utils.convertDuration,
|
||||
numberWithDots,
|
||||
convertDuration,
|
||||
search(type) {
|
||||
socket.emit('search', {
|
||||
term: this.results.query,
|
||||
type: type,
|
||||
start: this.results[type + 'Tab'].next,
|
||||
type,
|
||||
start: this.results[`${type}Tab`].next,
|
||||
nb: 30
|
||||
})
|
||||
},
|
||||
scrolledSearch(type) {
|
||||
let currentTab = type + 'Tab'
|
||||
scrolledSearch() {
|
||||
if (this.currentTab.searchType === 'all') return
|
||||
|
||||
let currentTab = `${this.currentTab.searchType}Tab`
|
||||
|
||||
if (this.results[currentTab].next < this.results[currentTab].total) {
|
||||
socket.emit('search', {
|
||||
term: this.results.query,
|
||||
type: type,
|
||||
start: this.results[currentTab].next,
|
||||
nb: 30
|
||||
})
|
||||
this.search(this.currentTab.searchType)
|
||||
}
|
||||
},
|
||||
handleMainSearch(result) {
|
||||
// Hiding loading placeholder
|
||||
document.getElementById('content').style.display = ''
|
||||
document.getElementById('search_placeholder').classList.toggle('loading_placeholder--hidden')
|
||||
this.$root.$emit('updateSearchLoadingState', false)
|
||||
|
||||
let resetObj = { data: [], next: 0, total: 0, loaded: false }
|
||||
|
||||
@ -644,12 +231,7 @@ export default {
|
||||
this.results.albumTab = { ...resetObj }
|
||||
this.results.artistTab = { ...resetObj }
|
||||
this.results.playlistTab = { ...resetObj }
|
||||
|
||||
if (this.results.query == '') document.getElementById('search_all_tab').click()
|
||||
|
||||
this.results.query = result.QUERY
|
||||
document.getElementById('search_tab_content').style.display = 'block'
|
||||
document.getElementById('main_search_tablink').click()
|
||||
},
|
||||
handleSearch(result) {
|
||||
const { next: nextResult, total, type, data } = result
|
||||
@ -673,13 +255,21 @@ export default {
|
||||
}
|
||||
|
||||
this.results[currentTab].loaded = true
|
||||
},
|
||||
isTabLoaded(tab) {
|
||||
return this.loadedTabs.indexOf(tab.searchType) !== -1 || tab.searchType === 'all'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrolledSearchType(newType) {
|
||||
if (!newType) return
|
||||
performScrolledSearch(needToSearch) {
|
||||
if (!needToSearch) return
|
||||
|
||||
this.scrolledSearch(newType)
|
||||
this.scrolledSearch(needToSearch)
|
||||
},
|
||||
currentTab(newTab) {
|
||||
if (this.isTabLoaded(newTab)) return
|
||||
|
||||
this.search(newTab.searchType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,29 @@
|
||||
<template>
|
||||
<div id="middle_section">
|
||||
<TheSearchBar />
|
||||
<main id="main_content">
|
||||
<TheContent />
|
||||
<BaseLoadingPlaceholder id="search_placeholder" text="Searching..." :hidden="true" />
|
||||
</div>
|
||||
<!-- <BaseLoadingPlaceholder id="search_placeholder" text="Searching..." :hidden="true" /> -->
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#main_content {
|
||||
background-color: var(--main-background);
|
||||
min-width: 10px;
|
||||
// margin-left: 48px; // $sidebar-width
|
||||
// width: calc(100% - #{$sidebar-width});
|
||||
// flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import TheContent from '@components/TheContent.vue'
|
||||
import TheSearchBar from '@components/TheSearchBar.vue'
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheContent,
|
||||
TheSearchBar,
|
||||
BaseLoadingPlaceholder
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
ref="searchbar"
|
||||
@keyup="handleSearchBarKeyup($event)"
|
||||
/>
|
||||
<!-- @keyup.enter.exact="onEnter"
|
||||
@keyup.ctrl.enter="onCTRLEnter" -->
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@ -25,28 +27,68 @@ import EventBus from '@/utils/EventBus.js'
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lastTextSearch: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keyup', keyEvent => {
|
||||
if (!(keyEvent.key == 'Backspace' && keyEvent.ctrlKey)) return
|
||||
|
||||
this.$refs.searchbar.value = ''
|
||||
this.$refs.searchbar.focus()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleSearchBarKeyup(keyEvent) {
|
||||
// Enter key
|
||||
if (keyEvent.keyCode !== 13) return
|
||||
test() {
|
||||
console.log('test passato')
|
||||
},
|
||||
async handleSearchBarKeyup(keyEvent) {
|
||||
let isEnterPressed = keyEvent.keyCode === 13
|
||||
|
||||
// If not enter do nothing
|
||||
if (!isEnterPressed) return
|
||||
|
||||
let term = this.$refs.searchbar.value
|
||||
let isEmptySearch = term === ''
|
||||
|
||||
if (isValidURL(term)) {
|
||||
if (keyEvent.ctrlKey) {
|
||||
// If empty do nothing
|
||||
if (isEmptySearch) return
|
||||
|
||||
let isSearchingURL = isValidURL(term)
|
||||
let isCtrlPressed = keyEvent.ctrlKey
|
||||
let isShowingAnalyzer = this.$route.name === 'Link Analyzer'
|
||||
let isShowingSearch = this.$route.name === 'Search'
|
||||
let sameAsLastSearch = term === this.lastTextSearch
|
||||
|
||||
if (isSearchingURL) {
|
||||
if (isCtrlPressed) {
|
||||
this.$root.$emit('QualityModal:open', term)
|
||||
} else {
|
||||
if (main_selected === 'analyzer_tab') {
|
||||
EventBus.$emit('linkAnalyzerTab:reset')
|
||||
if (isShowingAnalyzer) {
|
||||
// EventBus.$emit('linkAnalyzerTab:reset')
|
||||
socket.emit('analyzeLink', term)
|
||||
} else {
|
||||
// ? Open downloads tab ?
|
||||
Downloads.sendAddToQueue(term)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (term === '') return
|
||||
if (isShowingSearch && sameAsLastSearch) return
|
||||
|
||||
this.$root.$emit('mainSearch:showNewResults', term, main_selected)
|
||||
if (!isShowingSearch) {
|
||||
await this.$router.push({
|
||||
name: 'Search'
|
||||
})
|
||||
}
|
||||
|
||||
if (!sameAsLastSearch) {
|
||||
this.$root.$emit('updateSearchLoadingState', true)
|
||||
this.lastTextSearch = term
|
||||
}
|
||||
|
||||
this.$root.$emit('mainSearch:showNewResults', term, window.main_selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
<template>
|
||||
<div id="settings_tab" class="main_tabcontent fixed_footer">
|
||||
<div id="settings_tab" class="main_tabcontent fixed_footer" ref="root">
|
||||
<h2 class="page_heading">{{ $t('settings.title') }}</h2>
|
||||
|
||||
<div id="logged_in_info" ref="loggedInInfo">
|
||||
<img id="settings_picture" src="" alt="Profile Picture" ref="userpicture" class="circle" />
|
||||
<div id="logged_in_info" v-if="isLoggedIn" ref="loggedInInfo">
|
||||
<img id="settings_picture" :src="pictureHref" alt="Profile Picture" ref="userpicture" class="circle" />
|
||||
|
||||
<i18n path="settings.login.loggedIn" tag="p">
|
||||
<strong place="username" id="settings_username" ref="username"></strong>
|
||||
<strong place="username" id="settings_username" ref="username">{{ user.name || 'not logged' }}</strong>
|
||||
</i18n>
|
||||
|
||||
<button id="settings_btn_logout" @click="logout">{{ $t('settings.login.logout') }}</button>
|
||||
|
||||
<select v-if="accounts.length" id="family_account" v-model="accountNum" @change="changeAccount">
|
||||
<option v-for="(account, i) in accounts" :value="i.toString()">{{ account.BLOG_NAME }}</option>
|
||||
<option v-for="(account, i) in accounts" :key="account" :value="i.toString()">{{ account.BLOG_NAME }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -18,7 +21,14 @@
|
||||
<i class="material-icons">person</i>{{ $t('settings.login.title') }}
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<input autocomplete="off" type="password" id="login_input_arl" ref="loginInput" placeholder="ARL" />
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
:value="arl"
|
||||
id="login_input_arl"
|
||||
ref="loginInput"
|
||||
placeholder="ARL"
|
||||
/>
|
||||
<button id="settings_btn_copyArl" class="only_icon" @click="copyARLtoClipboard">
|
||||
<i class="material-icons">assignment</i>
|
||||
</button>
|
||||
@ -26,10 +36,10 @@
|
||||
<a href="https://codeberg.org/RemixDev/deemix/wiki/Getting-your-own-ARL" target="_blank">
|
||||
{{ $t('settings.login.arl.question') }}
|
||||
</a>
|
||||
<a id="settings_btn_applogin" class="hide" href="#" @click="applogin">
|
||||
Automated login
|
||||
<a id="settings_btn_applogin" v-if="clientMode" href="#" @click="appLogin">
|
||||
{{ $t('settings.login.login') }}
|
||||
</a>
|
||||
<button id="settings_btn_updateArl" @click="login" style="width: 100%;">
|
||||
<button id="settings_btn_updateArl" @click="login" style="width: 100%">
|
||||
{{ $t('settings.login.arl.update') }}
|
||||
</button>
|
||||
</div>
|
||||
@ -67,7 +77,7 @@
|
||||
</h3>
|
||||
<div class="inline-flex">
|
||||
<input autocomplete="off" type="text" v-model="settings.downloadLocation" />
|
||||
<button id="select_downloads_folder" class="only_icon hide" @click="selectDownloadFolder">
|
||||
<button id="select_downloads_folder" v-if="clientMode" class="only_icon" @click="selectDownloadFolder">
|
||||
<i class="material-icons">folder</i>
|
||||
</button>
|
||||
</div>
|
||||
@ -275,13 +285,17 @@
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.localArtworkSize') }}</p>
|
||||
<input type="number" min="100" max="10000" step="100" v-model.number="settings.localArtworkSize" />
|
||||
<p v-if="settings.localArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22;">⚠️ {{ $t('settings.covers.imageSizeWarning') }}</p>
|
||||
<p v-if="settings.localArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22">
|
||||
⚠️ {{ $t('settings.covers.imageSizeWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.embeddedArtworkSize') }}</p>
|
||||
<input type="number" min="100" max="10000" step="100" v-model.number="settings.embeddedArtworkSize" />
|
||||
<p v-if="settings.embeddedArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22;">⚠️ {{ $t('settings.covers.imageSizeWarning') }}</p>
|
||||
<p v-if="settings.embeddedArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22">
|
||||
⚠️ {{ $t('settings.covers.imageSizeWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="input_group">
|
||||
@ -297,7 +311,14 @@
|
||||
<input type="checkbox" v-model="settings.embeddedArtworkPNG" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.embeddedArtworkPNG') }}</span>
|
||||
</label>
|
||||
<p v-if="settings.embeddedArtworkPNG" style="opacity: 0.75; color: #ffcc22;">⚠️ {{ $t('settings.covers.embeddedPNGWarning') }}</p>
|
||||
<p v-if="settings.embeddedArtworkPNG" style="opacity: 0.75; color: #ffcc22">
|
||||
⚠️ {{ $t('settings.covers.embeddedPNGWarning') }}
|
||||
</p>
|
||||
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.coverDescriptionUTF8" />
|
||||
<span class="checkbox_text">{{ $t('settings.covers.coverDescriptionUTF8') }}</span>
|
||||
</label>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.covers.jpegImageQuality') }}</p>
|
||||
@ -307,7 +328,7 @@
|
||||
|
||||
<div class="settings-group">
|
||||
<h3 class="settings-group__header settings-group__header--with-icon">
|
||||
<i class="material-icons" style="width: 1em; height: 1em;">bookmarks</i>{{ $t('settings.tags.head') }}
|
||||
<i class="material-icons" style="width: 1em; height: 1em">bookmarks</i>{{ $t('settings.tags.head') }}
|
||||
</h3>
|
||||
|
||||
<div class="settings-container">
|
||||
@ -395,6 +416,10 @@
|
||||
<input type="checkbox" v-model="settings.tags.lyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.lyrics') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.syncedLyrics" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.syncedLyrics') }}</span>
|
||||
</label>
|
||||
<label class="with_checkbox">
|
||||
<input type="checkbox" v-model="settings.tags.copyright" />
|
||||
<span class="checkbox_text">{{ $t('settings.tags.copyright') }}</span>
|
||||
@ -437,13 +462,13 @@
|
||||
<option value="nothing">{{ $t('settings.other.multiArtistSeparator.nothing') }}</option>
|
||||
<option value="default">{{ $t('settings.other.multiArtistSeparator.default') }}</option>
|
||||
<option value="andFeat">{{ $t('settings.other.multiArtistSeparator.andFeat') }}</option>
|
||||
<option value=" & ">{{ $t('settings.other.multiArtistSeparator.using', {separator: ' & '}) }}</option>
|
||||
<option value=",">{{ $t('settings.other.multiArtistSeparator.using', {separator: ','}) }}</option>
|
||||
<option value=", ">{{ $t('settings.other.multiArtistSeparator.using', {separator: ', '}) }}</option>
|
||||
<option value="/">{{ $t('settings.other.multiArtistSeparator.using', {separator: '/'}) }}</option>
|
||||
<option value=" / ">{{ $t('settings.other.multiArtistSeparator.using', {separator: ' / '}) }}</option>
|
||||
<option value=";">{{ $t('settings.other.multiArtistSeparator.using', {separator: ';'}) }}</option>
|
||||
<option value="; ">{{ $t('settings.other.multiArtistSeparator.using', {separator: '; '}) }}</option>
|
||||
<option value=" & ">{{ $t('settings.other.multiArtistSeparator.using', { separator: ' & ' }) }}</option>
|
||||
<option value=",">{{ $t('settings.other.multiArtistSeparator.using', { separator: ',' }) }}</option>
|
||||
<option value=", ">{{ $t('settings.other.multiArtistSeparator.using', { separator: ', ' }) }}</option>
|
||||
<option value="/">{{ $t('settings.other.multiArtistSeparator.using', { separator: '/' }) }}</option>
|
||||
<option value=" / ">{{ $t('settings.other.multiArtistSeparator.using', { separator: ' / ' }) }}</option>
|
||||
<option value=";">{{ $t('settings.other.multiArtistSeparator.using', { separator: ';' }) }}</option>
|
||||
<option value="; ">{{ $t('settings.other.multiArtistSeparator.using', { separator: '; ' }) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -470,26 +495,34 @@
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.other.dateFormat.title') }}</p>
|
||||
<select v-model="settings.dateFormat">
|
||||
<option value="Y-M-D">{{
|
||||
`${$t('settings.other.dateFormat.year')}-${$t('settings.other.dateFormat.month')}-${$t(
|
||||
'settings.other.dateFormat.day'
|
||||
)}`
|
||||
}}</option>
|
||||
<option value="Y-D-M">{{
|
||||
`${$t('settings.other.dateFormat.year')}-${$t('settings.other.dateFormat.day')}-${$t(
|
||||
'settings.other.dateFormat.month'
|
||||
)}`
|
||||
}}</option>
|
||||
<option value="D-M-Y">{{
|
||||
`${$t('settings.other.dateFormat.day')}-${$t('settings.other.dateFormat.month')}-${$t(
|
||||
'settings.other.dateFormat.year'
|
||||
)}`
|
||||
}}</option>
|
||||
<option value="M-D-Y">{{
|
||||
`${$t('settings.other.dateFormat.month')}-${$t('settings.other.dateFormat.day')}-${$t(
|
||||
'settings.other.dateFormat.year'
|
||||
)}`
|
||||
}}</option>
|
||||
<option value="Y-M-D">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.year')}-${$t('settings.other.dateFormat.month')}-${$t(
|
||||
'settings.other.dateFormat.day'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="Y-D-M">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.year')}-${$t('settings.other.dateFormat.day')}-${$t(
|
||||
'settings.other.dateFormat.month'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="D-M-Y">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.day')}-${$t('settings.other.dateFormat.month')}-${$t(
|
||||
'settings.other.dateFormat.year'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="M-D-Y">
|
||||
{{
|
||||
`${$t('settings.other.dateFormat.month')}-${$t('settings.other.dateFormat.day')}-${$t(
|
||||
'settings.other.dateFormat.year'
|
||||
)}`
|
||||
}}
|
||||
</option>
|
||||
<option value="Y">{{ $t('settings.other.dateFormat.year') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -556,6 +589,9 @@
|
||||
</svg>
|
||||
{{ $t('settings.spotify.title') }}
|
||||
</h3>
|
||||
<a href="https://codeberg.org/RemixDev/deemix/wiki/Enabling-Spotify-Features" target="_blank">
|
||||
{{ $t('settings.spotify.question') }}
|
||||
</a>
|
||||
|
||||
<div class="input_group">
|
||||
<p class="input_group_text">{{ $t('settings.spotify.clientID') }}</p>
|
||||
@ -579,7 +615,16 @@
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#logged_in_info {
|
||||
height: 250px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.locale-flag {
|
||||
width: 60px;
|
||||
display: inline-flex;
|
||||
@ -606,30 +651,47 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
import { toast } from '@/utils/toasts'
|
||||
import { socket } from '@/utils/socket'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
import flags from '@/utils/flags'
|
||||
|
||||
import { getSettingsData } from '@/data/settings'
|
||||
|
||||
export default {
|
||||
name: 'the-settings-tab',
|
||||
data: () => ({
|
||||
flags,
|
||||
currentLocale: 'en',
|
||||
locales: [],
|
||||
settings: { tags: {} },
|
||||
lastSettings: {},
|
||||
spotifyFeatures: {},
|
||||
lastCredentials: {},
|
||||
defaultSettings: {},
|
||||
lastUser: '',
|
||||
spotifyUser: '',
|
||||
slimDownloads: false,
|
||||
previewVolume: window.vol,
|
||||
accountNum: 0,
|
||||
accounts: []
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
flags,
|
||||
currentLocale: 'en',
|
||||
locales: [],
|
||||
settings: {
|
||||
tags: {}
|
||||
},
|
||||
lastSettings: {},
|
||||
spotifyFeatures: {},
|
||||
lastCredentials: {},
|
||||
defaultSettings: {},
|
||||
lastUser: '',
|
||||
spotifyUser: '',
|
||||
slimDownloads: false,
|
||||
previewVolume: window.vol,
|
||||
accountNum: 0,
|
||||
accounts: []
|
||||
// clientMode: window.clientMode
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
arl: 'getARL',
|
||||
user: 'getUser',
|
||||
isLoggedIn: 'isLoggedIn',
|
||||
clientMode: 'getClientMode'
|
||||
}),
|
||||
needToWait() {
|
||||
return Object.keys(this.getSettings).length === 0
|
||||
},
|
||||
changeSlimDownloads: {
|
||||
get() {
|
||||
return this.slimDownloads
|
||||
@ -639,15 +701,22 @@ export default {
|
||||
document.getElementById('download_list').classList.toggle('slim', wantSlimDownloads)
|
||||
localStorage.setItem('slimDownloads', wantSlimDownloads)
|
||||
}
|
||||
},
|
||||
pictureHref() {
|
||||
// Default image: https://e-cdns-images.dzcdn.net/images/user/125x125-000000-80-0-0.jpg
|
||||
return `https://e-cdns-images.dzcdn.net/images/user/${this.user.picture}/125x125-000000-80-0-0.jpg`
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
async mounted() {
|
||||
this.locales = this.$i18n.availableLocales
|
||||
|
||||
EventBus.$on('settingsTab:revertSettings', this.revertSettings)
|
||||
EventBus.$on('settingsTab:revertCredentials', this.revertCredentials)
|
||||
const { settingsData, defaultSettingsData, spotifyCredentials } = await getSettingsData()
|
||||
|
||||
this.$refs.loggedInInfo.classList.add('hide')
|
||||
this.defaultSettings = defaultSettingsData
|
||||
this.initSettings(settingsData, spotifyCredentials)
|
||||
|
||||
// this.revertSettings()
|
||||
// this.revertCredentials()
|
||||
|
||||
let storedLocale = localStorage.getItem('locale')
|
||||
|
||||
@ -656,12 +725,6 @@ export default {
|
||||
this.currentLocale = storedLocale
|
||||
}
|
||||
|
||||
let storedArl = localStorage.getItem('arl')
|
||||
|
||||
if (storedArl) {
|
||||
this.$refs.loginInput.value = storedArl.trim()
|
||||
}
|
||||
|
||||
let storedAccountNum = localStorage.getItem('accountNum')
|
||||
|
||||
if (storedAccountNum) {
|
||||
@ -679,25 +742,37 @@ export default {
|
||||
this.changeSlimDownloads = 'true' === localStorage.getItem('slimDownloads')
|
||||
|
||||
let volume = parseInt(localStorage.getItem('previewVolume'))
|
||||
|
||||
if (isNaN(volume)) {
|
||||
volume = 80
|
||||
localStorage.setItem('previewVolume', volume)
|
||||
}
|
||||
|
||||
window.vol.preview_max_volume = volume
|
||||
|
||||
socket.on('init_settings', this.initSettings)
|
||||
socket.on('updateSettings', this.updateSettings)
|
||||
socket.on('accountChanged', this.accountChanged)
|
||||
socket.on('familyAccounts', this.initAccounts)
|
||||
socket.on('downloadFolderSelected', this.downloadFolderSelected)
|
||||
socket.on('applogin_arl', this.setArl)
|
||||
socket.on('applogin_arl', this.loggedInViaDeezer)
|
||||
|
||||
this.$on('hook:destroyed', () => {
|
||||
socket.off('updateSettings')
|
||||
socket.off('accountChanged')
|
||||
socket.off('familyAccounts')
|
||||
socket.off('downloadFolderSelected')
|
||||
socket.off('applogin_arl')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
dispatchARL: 'setARL'
|
||||
}),
|
||||
revertSettings() {
|
||||
this.settings = { ...this.lastSettings }
|
||||
this.settings = JSON.parse(JSON.stringify(this.lastSettings))
|
||||
},
|
||||
revertCredentials() {
|
||||
this.spotifyCredentials = { ...this.lastCredentials }
|
||||
this.spotifyCredentials = JSON.parse(JSON.stringify(this.lastCredentials))
|
||||
this.spotifyUser = (' ' + this.lastUser).slice(1)
|
||||
},
|
||||
copyARLtoClipboard() {
|
||||
@ -720,9 +795,11 @@ export default {
|
||||
localStorage.setItem('previewVolume', this.previewVolume.preview_max_volume)
|
||||
},
|
||||
saveSettings() {
|
||||
this.lastSettings = { ...this.settings }
|
||||
this.lastCredentials = { ...this.spotifyFeatures }
|
||||
this.lastSettings = JSON.parse(JSON.stringify(this.settings))
|
||||
this.lastCredentials = JSON.parse(JSON.stringify(this.spotifyFeatures))
|
||||
|
||||
let changed = false
|
||||
|
||||
if (this.lastUser != this.spotifyUser) {
|
||||
// force cloning without linking
|
||||
this.lastUser = (' ' + this.spotifyUser).slice(1)
|
||||
@ -733,34 +810,31 @@ export default {
|
||||
socket.emit('saveSettings', this.lastSettings, this.lastCredentials, changed ? this.lastUser : false)
|
||||
},
|
||||
selectDownloadFolder() {
|
||||
if (window.clientMode) socket.emit('selectDownloadFolder')
|
||||
socket.emit('selectDownloadFolder')
|
||||
},
|
||||
downloadFolderSelected(folder){
|
||||
console.log(folder)
|
||||
this.settings.downloadLocation = folder
|
||||
downloadFolderSelected(folder) {
|
||||
this.$set(this.settings, 'downloadLocation', folder)
|
||||
},
|
||||
loadSettings(settings, spotifyCredentials, defaults = null) {
|
||||
if (defaults) {
|
||||
this.defaultSettings = { ...defaults }
|
||||
}
|
||||
|
||||
this.lastSettings = { ...settings }
|
||||
this.lastCredentials = { ...spotifyCredentials }
|
||||
this.settings = settings
|
||||
this.spotifyFeatures = spotifyCredentials
|
||||
loadSettings(data) {
|
||||
this.lastSettings = JSON.parse(JSON.stringify(data))
|
||||
this.settings = JSON.parse(JSON.stringify(data))
|
||||
},
|
||||
loadCredentials(credentials) {
|
||||
this.lastCredentials = JSON.parse(JSON.stringify(credentials))
|
||||
this.spotifyFeatures = JSON.parse(JSON.stringify(credentials))
|
||||
},
|
||||
login() {
|
||||
let arl = this.$refs.loginInput.value.trim()
|
||||
if (arl != '' && arl != localStorage.getItem('arl')) {
|
||||
socket.emit('login', arl, true, this.accountNum)
|
||||
let newArl = this.$refs.loginInput.value.trim()
|
||||
|
||||
if (newArl && newArl !== this.arl) {
|
||||
socket.emit('login', newArl, true, this.accountNum)
|
||||
}
|
||||
},
|
||||
applogin(e) {
|
||||
e.preventDefault()
|
||||
if (window.clientMode) socket.emit('applogin')
|
||||
appLogin(e) {
|
||||
socket.emit('applogin')
|
||||
},
|
||||
setArl(arl) {
|
||||
this.$refs.loginInput.value = arl
|
||||
loggedInViaDeezer(arl) {
|
||||
this.dispatchARL({ arl })
|
||||
this.login()
|
||||
},
|
||||
changeAccount() {
|
||||
@ -770,6 +844,7 @@ export default {
|
||||
this.$refs.username.innerText = user.name
|
||||
this.$refs.userpicture.src = `https://e-cdns-images.dzcdn.net/images/user/${user.picture}/125x125-000000-80-0-0.jpg`
|
||||
this.accountNum = accountNum
|
||||
|
||||
localStorage.setItem('accountNum', this.accountNum)
|
||||
},
|
||||
initAccounts(accounts) {
|
||||
@ -778,16 +853,21 @@ export default {
|
||||
logout() {
|
||||
socket.emit('logout')
|
||||
},
|
||||
initSettings(settings, credentials, defaults) {
|
||||
this.loadSettings(settings, credentials, defaults)
|
||||
initSettings(settings, credentials) {
|
||||
// this.loadDefaultSettings()
|
||||
this.loadSettings(settings)
|
||||
this.loadCredentials(credentials)
|
||||
|
||||
toast(this.$t('settings.toasts.init'), 'settings')
|
||||
},
|
||||
updateSettings(settings, credentials) {
|
||||
this.loadSettings(settings, credentials)
|
||||
updateSettings(newSettings, newCredentials) {
|
||||
this.loadSettings(newSettings)
|
||||
this.loadCredentials(newCredentials)
|
||||
|
||||
toast(this.$t('settings.toasts.update'), 'settings')
|
||||
},
|
||||
resetSettings() {
|
||||
this.settings = { ...this.defaultSettings }
|
||||
this.settings = JSON.parse(JSON.stringify(this.defaultSettings))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,28 @@
|
||||
<template>
|
||||
<aside id="sidebar" role="navigation" @click="handleSidebarClick">
|
||||
<span id="main_home_tablink" class="main_tablinks" role="link" aria-label="home">
|
||||
<i class="material-icons side_icon">home</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.home') }}</span>
|
||||
</span>
|
||||
<span id="main_search_tablink" class="main_tablinks" role="link" aria-label="search">
|
||||
<i class="material-icons side_icon">search</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.search') }}</span>
|
||||
</span>
|
||||
<span id="main_charts_tablink" class="main_tablinks" role="link" aria-label="charts">
|
||||
<i class="material-icons side_icon">show_chart</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.charts') }}</span>
|
||||
</span>
|
||||
<span id="main_favorites_tablink" class="main_tablinks" role="link" aria-label="favorites">
|
||||
<i class="material-icons side_icon">star</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.favorites') }}</span>
|
||||
</span>
|
||||
<span id="main_analyzer_tablink" class="main_tablinks" role="link" aria-label="link analyzer">
|
||||
<i class="material-icons side_icon">link</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.linkAnalyzer') }}</span>
|
||||
</span>
|
||||
<span id="main_settings_tablink" class="main_tablinks" role="link" aria-label="settings">
|
||||
<i class="material-icons side_icon">settings</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.settings') }}</span>
|
||||
</span>
|
||||
<span id="main_about_tablink" class="main_tablinks" role="link" aria-label="info">
|
||||
<i class="material-icons side_icon">info</i>
|
||||
<span class="main_tablinks_text">{{ $t('sidebar.about') }}</span>
|
||||
</span>
|
||||
<aside id="sidebar" role="navigation">
|
||||
<router-link
|
||||
v-for="link in links"
|
||||
:key="link.id"
|
||||
tag="span"
|
||||
class="main_tablinks"
|
||||
role="link"
|
||||
:id="link.id"
|
||||
:class="{ active: activeTablink === link.name }"
|
||||
:aria-label="link.ariaLabel"
|
||||
:to="{ name: link.routerName }"
|
||||
@click.native="activeTablink = link.name"
|
||||
>
|
||||
<i class="material-icons side_icon">{{ link.icon }}</i>
|
||||
<span class="main_tablinks_text">{{ link.label }}</span>
|
||||
</router-link>
|
||||
|
||||
<span id="theme_selector" class="main_tablinks" role="link" aria-label="theme selector">
|
||||
<i class="material-icons side_icon side_icon--theme">palette</i>
|
||||
<div id="theme_togglers">
|
||||
<div
|
||||
v-for="theme of themes"
|
||||
:key="theme"
|
||||
class="theme_toggler "
|
||||
class="theme_toggler"
|
||||
:class="[{ 'theme_toggler--active': activeTheme === theme }, `theme_toggler--${theme}`]"
|
||||
@click="changeTheme(theme)"
|
||||
></div>
|
||||
@ -77,15 +65,76 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { changeTab } from '@js/tabs.js'
|
||||
|
||||
export default {
|
||||
name: 'the-sidebar',
|
||||
data: () => ({
|
||||
appOnline: null,
|
||||
activeTheme: 'light',
|
||||
themes: ['purple', 'dark', 'light']
|
||||
}),
|
||||
data() {
|
||||
const $t = this.$t.bind(this)
|
||||
const $tc = this.$tc.bind(this)
|
||||
|
||||
return {
|
||||
appOnline: null,
|
||||
activeTheme: 'light',
|
||||
themes: ['purple', 'dark', 'light'],
|
||||
activeTablink: 'home',
|
||||
links: [
|
||||
{
|
||||
id: 'main_home_tablink',
|
||||
name: 'home',
|
||||
ariaLabel: 'home',
|
||||
routerName: 'Home',
|
||||
icon: 'home',
|
||||
label: $t('sidebar.home')
|
||||
},
|
||||
{
|
||||
id: 'main_search_tablink',
|
||||
name: 'search',
|
||||
ariaLabel: 'search',
|
||||
routerName: 'Search',
|
||||
icon: 'search',
|
||||
label: $t('sidebar.search')
|
||||
},
|
||||
{
|
||||
id: 'main_charts_tablink',
|
||||
name: 'charts',
|
||||
ariaLabel: 'charts',
|
||||
routerName: 'Charts',
|
||||
icon: 'show_chart',
|
||||
label: $t('sidebar.charts')
|
||||
},
|
||||
{
|
||||
id: 'main_favorites_tablink',
|
||||
name: 'favorites',
|
||||
ariaLabel: 'favorites',
|
||||
routerName: 'Favorites',
|
||||
icon: 'star',
|
||||
label: $t('sidebar.favorites')
|
||||
},
|
||||
{
|
||||
id: 'main_analyzer_tablink',
|
||||
name: 'analyzer',
|
||||
ariaLabel: 'link analyzer',
|
||||
routerName: 'Link Analyzer',
|
||||
icon: 'link',
|
||||
label: $t('sidebar.linkAnalyzer')
|
||||
},
|
||||
{
|
||||
id: 'main_settings_tablink',
|
||||
name: 'settings',
|
||||
ariaLabel: 'settings',
|
||||
routerName: 'Settings',
|
||||
icon: 'settings',
|
||||
label: $t('sidebar.settings')
|
||||
},
|
||||
{
|
||||
id: 'main_about_tablink',
|
||||
name: 'about',
|
||||
ariaLabel: 'info',
|
||||
routerName: 'About',
|
||||
icon: 'info',
|
||||
label: $t('sidebar.about')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
/* === Online status handling === */
|
||||
this.appOnline = navigator.onLine
|
||||
@ -100,6 +149,14 @@ export default {
|
||||
|
||||
/* === Current theme handling === */
|
||||
this.activeTheme = localStorage.getItem('selectedTheme') || 'light'
|
||||
|
||||
this.$router.afterEach((to, from) => {
|
||||
const linkInSidebar = this.links.find(link => link.routerName === to.name)
|
||||
|
||||
if (!linkInSidebar) return
|
||||
|
||||
this.activeTablink = linkInSidebar.name
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
changeTheme(newTheme) {
|
||||
@ -110,65 +167,19 @@ export default {
|
||||
localStorage.setItem('selectedTheme', newTheme)
|
||||
|
||||
// Animating everything to have a smoother theme switch
|
||||
document.querySelectorAll('*').forEach(el => {
|
||||
el.style.transition = 'all 200ms ease-in-out'
|
||||
const allElements = document.querySelectorAll('*')
|
||||
|
||||
allElements.forEach(el => {
|
||||
el.classList.add('changing-theme')
|
||||
})
|
||||
|
||||
document.documentElement.addEventListener('transitionend', function transitionHandler() {
|
||||
document.querySelectorAll('*').forEach(el => {
|
||||
el.style.transition = ''
|
||||
allElements.forEach(el => {
|
||||
el.classList.remove('changing-theme')
|
||||
})
|
||||
|
||||
document.documentElement.removeEventListener('transitionend', transitionHandler)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Handles click Event on the sidebar and changes tab
|
||||
* according to clicked icon.
|
||||
* Uses event delegation
|
||||
* @param {Event} event
|
||||
*/
|
||||
handleSidebarClick(event) {
|
||||
const { target } = event
|
||||
|
||||
const wantToChangeTab = target.matches('.main_tablinks') || target.parentElement.matches('.main_tablinks')
|
||||
|
||||
if (!wantToChangeTab) return
|
||||
|
||||
let sidebarEl = target.matches('.main_tablinks') ? target : target.parentElement
|
||||
let targetID = sidebarEl.id
|
||||
let selectedTab = null
|
||||
|
||||
switch (targetID) {
|
||||
case 'main_search_tablink':
|
||||
selectedTab = 'search_tab'
|
||||
break
|
||||
case 'main_home_tablink':
|
||||
selectedTab = 'home_tab'
|
||||
break
|
||||
case 'main_charts_tablink':
|
||||
selectedTab = 'charts_tab'
|
||||
break
|
||||
case 'main_favorites_tablink':
|
||||
selectedTab = 'favorites_tab'
|
||||
break
|
||||
case 'main_analyzer_tablink':
|
||||
selectedTab = 'analyzer_tab'
|
||||
break
|
||||
case 'main_settings_tablink':
|
||||
selectedTab = 'settings_tab'
|
||||
break
|
||||
case 'main_about_tablink':
|
||||
selectedTab = 'about_tab'
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (!selectedTab) return
|
||||
|
||||
changeTab(sidebarEl, 'main', selectedTab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
// import $ from 'jquery'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
import { adjustVolume } from '@/utils/adjust-volume'
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
previewStopped: false
|
||||
@ -25,98 +27,113 @@ export default {
|
||||
await this.$refs.preview.play()
|
||||
|
||||
this.previewStopped = false
|
||||
$(this.$refs.preview).animate({ volume: vol.preview_max_volume / 100 }, 500)
|
||||
|
||||
await adjustVolume(this.$refs.preview, window.vol.preview_max_volume / 100, { duration: 500 })
|
||||
},
|
||||
onTimeUpdate() {
|
||||
async onTimeUpdate() {
|
||||
// Prevents first time entering in this function
|
||||
if (isNaN(this.$refs.preview.duration)) return
|
||||
|
||||
let duration = this.$refs.preview.duration
|
||||
if (!isFinite(duration)) duration = 30
|
||||
|
||||
if (!isFinite(duration)) {
|
||||
duration = 30
|
||||
}
|
||||
|
||||
if (duration - this.$refs.preview.currentTime >= 1) return
|
||||
if (this.previewStopped) return
|
||||
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 800)
|
||||
await adjustVolume(this.$refs.preview, 0, { duration: 800 })
|
||||
|
||||
this.previewStopped = true
|
||||
|
||||
$('a[playing] > .preview_controls').css({ opacity: 0 })
|
||||
$('*').removeAttr('playing')
|
||||
$('.preview_controls').text('play_arrow')
|
||||
$('.preview_playlist_controls').text('play_arrow')
|
||||
document.querySelectorAll('a[playing] > .preview_controls').forEach(control => {
|
||||
control.style.opacity = 0
|
||||
})
|
||||
|
||||
document.querySelectorAll('*').forEach(el => {
|
||||
el.removeAttribute('playing')
|
||||
})
|
||||
|
||||
document.querySelectorAll('.preview_controls, .preview_playlist_controls').forEach(el => {
|
||||
el.textContent = 'play_arrow'
|
||||
})
|
||||
},
|
||||
playPausePreview(e) {
|
||||
async playPausePreview(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const { currentTarget: obj } = event
|
||||
|
||||
var $icon = obj.tagName == 'I' ? $(obj) : $(obj).children('i')
|
||||
var icon = obj.tagName == 'I' ? obj : obj.querySelector('i')
|
||||
|
||||
if ($(obj).attr('playing')) {
|
||||
if (obj.hasAttribute('playing')) {
|
||||
if (this.$refs.preview.paused) {
|
||||
this.$refs.preview.play()
|
||||
this.previewStopped = false
|
||||
|
||||
$icon.text('pause')
|
||||
icon.innerText = 'pause'
|
||||
|
||||
$(this.$refs.preview).animate({ volume: vol.preview_max_volume / 100 }, 500)
|
||||
await adjustVolume(this.$refs.preview, window.vol.preview_max_volume / 100, { duration: 500 })
|
||||
} else {
|
||||
this.previewStopped = true
|
||||
|
||||
$icon.text('play_arrow')
|
||||
icon.innerText = 'play_arrow'
|
||||
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 250, 'swing', () => {
|
||||
this.$refs.preview.pause()
|
||||
})
|
||||
await adjustVolume(this.$refs.preview, 0, { duration: 250 })
|
||||
|
||||
this.$refs.preview.pause()
|
||||
}
|
||||
} else {
|
||||
$('*').removeAttr('playing')
|
||||
$(obj).attr('playing', true)
|
||||
document.querySelectorAll('*').forEach(el => {
|
||||
el.removeAttribute('playing')
|
||||
})
|
||||
obj.setAttribute('playing', true)
|
||||
|
||||
$('.preview_controls').text('play_arrow')
|
||||
$('.preview_playlist_controls').text('play_arrow')
|
||||
$('.preview_controls').css({ opacity: 0 })
|
||||
document.querySelectorAll('.preview_controls, .preview_playlist_controls').forEach(el => {
|
||||
el.textContent = 'play_arrow'
|
||||
})
|
||||
|
||||
$icon.text('pause')
|
||||
$icon.css({ opacity: 1 })
|
||||
document.querySelectorAll('.preview_controls').forEach(el => {
|
||||
el.style.opacity = 0
|
||||
})
|
||||
|
||||
icon.innerText = 'pause'
|
||||
icon.style.opacity = 1
|
||||
|
||||
this.previewStopped = false
|
||||
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 250, 'swing', () => {
|
||||
this.$refs.preview.pause()
|
||||
$('#preview-track_source').prop('src', $(obj).data('preview'))
|
||||
this.$refs.preview.load()
|
||||
})
|
||||
await adjustVolume(this.$refs.preview, 0, { duration: 250 })
|
||||
this.$refs.preview.pause()
|
||||
|
||||
document.getElementById('preview-track_source').src = obj.getAttribute('data-preview')
|
||||
|
||||
this.$refs.preview.load()
|
||||
}
|
||||
},
|
||||
stopStackedTabsPreview() {
|
||||
if (
|
||||
$('.preview_playlist_controls').filter(function() {
|
||||
return $(this).attr('playing')
|
||||
}).length > 0
|
||||
) {
|
||||
$(this.$refs.preview).animate({ volume: 0 }, 800)
|
||||
this.previewStopped = true
|
||||
$('.preview_playlist_controls').removeAttr('playing')
|
||||
$('.preview_playlist_controls').text('play_arrow')
|
||||
}
|
||||
async stopStackedTabsPreview() {
|
||||
let controls = Array.prototype.slice.call(document.querySelectorAll('.preview_playlist_controls[playing]'))
|
||||
|
||||
if (controls.length === 0) return
|
||||
|
||||
await adjustVolume(this.$refs.preview, 0, { duration: 800 })
|
||||
|
||||
this.previewStopped = true
|
||||
|
||||
controls.forEach(control => {
|
||||
control.removeAttribute('playing')
|
||||
control.innerText = 'play_arrow'
|
||||
})
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
$(e.currentTarget).css({ opacity: 1 })
|
||||
e.currentTarget.style.opacity = 1
|
||||
},
|
||||
previewMouseLeave(event) {
|
||||
const { currentTarget: obj } = event
|
||||
const parentIsPlaying = obj.parentElement.hasAttribute('playing')
|
||||
|
||||
if (
|
||||
($(obj)
|
||||
.parent()
|
||||
.attr('playing') &&
|
||||
this.previewStopped) ||
|
||||
!$(obj)
|
||||
.parent()
|
||||
.attr('playing')
|
||||
) {
|
||||
$(obj).css({ opacity: 0 }, 200)
|
||||
if ((parentIsPlaying && this.previewStopped) || !parentIsPlaying) {
|
||||
obj.style.opacity = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="tracklist_tab" class="main_tabcontent fixed_footer image_header">
|
||||
<div id="tracklist_tab" class="main_tabcontent fixed_footer image_header" ref="root">
|
||||
<header
|
||||
:style="{
|
||||
'background-image':
|
||||
@ -55,9 +55,7 @@
|
||||
</td>
|
||||
<td class="table__cell--large table__cell--with-icon">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon">
|
||||
explicit
|
||||
</i>
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon"> explicit </i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
||||
@ -91,9 +89,9 @@
|
||||
<input class="clickable" type="checkbox" v-model="track.selected" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else-if="track.type == 'disc_separator'" class="table__row-no-highlight" style="opacity: 0.54;">
|
||||
<tr v-else-if="track.type == 'disc_separator'" class="table__row-no-highlight" style="opacity: 0.54">
|
||||
<td>
|
||||
<div class="table__cell-content table__cell-content--vertical-center" style="opacity: 0.54;">
|
||||
<div class="table__cell-content table__cell-content--vertical-center" style="opacity: 0.54">
|
||||
<i class="material-icons">album</i>
|
||||
</div>
|
||||
</td>
|
||||
@ -131,15 +129,15 @@
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<span v-if="label" style="opacity: 0.4; margin-top: 8px; display: inline-block; font-size: 13px;">{{ label }}</span>
|
||||
<span v-if="label" style="opacity: 0.4; margin-top: 8px; display: inline-block; font-size: 13px">{{ label }}</span>
|
||||
<footer>
|
||||
<button @click.stop="addToQueue" :data-link="link">
|
||||
{{ `${$t('globals.download', {thing: $tc(`globals.listTabs.${type}`, 1)})}` }}
|
||||
{{ `${$t('globals.download', { thing: $tc(`globals.listTabs.${type}`, 1) })}` }}
|
||||
</button>
|
||||
<button class="with_icon" @click.stop="addToQueue" :data-link="selectedLinks()">
|
||||
{{ $t('tracklist.downloadSelection') }}<i class="material-icons">file_download</i>
|
||||
</button>
|
||||
<button class="back-button">{{ $t('globals.back') }}</button>
|
||||
<button class="back-button" @click="backTab">{{ $t('globals.back') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@ -147,7 +145,7 @@
|
||||
<script>
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { socket } from '@/utils/socket'
|
||||
import { showView } from '@js/tabs.js'
|
||||
import { showView, backTab } from '@js/tabs.js'
|
||||
import Downloads from '@/utils/downloads'
|
||||
import Utils from '@/utils/utils'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
@ -166,6 +164,7 @@ export default {
|
||||
body: []
|
||||
}),
|
||||
methods: {
|
||||
backTab,
|
||||
artistView: showView.bind(null, 'artist'),
|
||||
albumView: showView.bind(null, 'album'),
|
||||
playPausePreview(e) {
|
||||
@ -203,6 +202,8 @@ export default {
|
||||
},
|
||||
convertDuration: Utils.convertDuration,
|
||||
showAlbum(data) {
|
||||
this.reset()
|
||||
|
||||
const {
|
||||
id: albumID,
|
||||
title: albumTitle,
|
||||
@ -231,6 +232,8 @@ export default {
|
||||
}
|
||||
},
|
||||
showPlaylist(data) {
|
||||
this.reset()
|
||||
|
||||
const {
|
||||
id: playlistID,
|
||||
title: playlistTitle,
|
||||
@ -246,7 +249,10 @@ export default {
|
||||
this.title = playlistTitle
|
||||
this.image = playlistCover
|
||||
this.release_date = creation_date.substring(0, 10)
|
||||
this.metadata = `${this.$t('globals.by', {artist: creatorName})} • ${this.$tc('globals.listTabs.trackN', numberOfTracks)}`
|
||||
this.metadata = `${this.$t('globals.by', { artist: creatorName })} • ${this.$tc(
|
||||
'globals.listTabs.trackN',
|
||||
numberOfTracks
|
||||
)}`
|
||||
|
||||
if (isEmpty(playlistTracks)) {
|
||||
this.body = null
|
||||
@ -255,6 +261,8 @@ export default {
|
||||
}
|
||||
},
|
||||
showSpotifyPlaylist(data) {
|
||||
this.reset()
|
||||
|
||||
const {
|
||||
uri: playlistURI,
|
||||
name: playlistName,
|
||||
@ -272,7 +280,10 @@ export default {
|
||||
? images[0].url
|
||||
: 'https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/1000x1000-000000-80-0-0.jpg'
|
||||
this.release_date = ''
|
||||
this.metadata = `${this.$t('globals.by', {artist: ownerName})} • ${this.$tc('globals.listTabs.trackN', numberOfTracks)}`
|
||||
this.metadata = `${this.$t('globals.by', { artist: ownerName })} • ${this.$tc(
|
||||
'globals.listTabs.trackN',
|
||||
numberOfTracks
|
||||
)}`
|
||||
|
||||
if (isEmpty(playlistTracks)) {
|
||||
this.body = null
|
||||
@ -285,7 +296,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
EventBus.$on('tracklistTab:reset', this.reset)
|
||||
EventBus.$on('tracklistTab:selectRow', this.selectRow)
|
||||
|
||||
socket.on('show_album', this.showAlbum)
|
||||
|
117
src/components/downloads/QueueItem.vue
Normal file
117
src/components/downloads/QueueItem.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="download_object" :id="`download_${queueItem.uuid}`" :data-deezerid="queueItem.id">
|
||||
<div class="download_info">
|
||||
<img width="75px" class="rounded coverart" :src="queueItem.cover" :alt="`Cover ${queueItem.title}`" />
|
||||
<div class="download_info_data">
|
||||
<span class="download_line">{{ queueItem.title }}</span> <span class="download_slim_separator"> - </span>
|
||||
<span class="secondary-text">{{ queueItem.artist }}</span>
|
||||
</div>
|
||||
<div class="download_info_status">
|
||||
<span class="download_line">
|
||||
<span class="queue_downloaded">{{ queueItem.downloaded + queueItem.failed }}</span
|
||||
>/{{ queueItem.size }}
|
||||
</span>
|
||||
<span class="secondary-text inline-flex" v-if="queueItem.failed >= 1">
|
||||
<span class="download_slim_separator">(</span>
|
||||
<span
|
||||
class="queue_failed_button inline-flex"
|
||||
:class="{ clickable: finishedWithFails }"
|
||||
@click="finishedWithFails ? $emit('show-errors', queueItem) : null"
|
||||
>
|
||||
<span class="queue_failed">{{ queueItem.failed }}</span>
|
||||
<i class="material-icons">error_outline</i>
|
||||
</span>
|
||||
<span class="download_slim_separator">)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="download_bar">
|
||||
<div class="progress">
|
||||
<div :id="`bar_${queueItem.uuid}`" :class="barClass" :style="barStyle"></div>
|
||||
</div>
|
||||
<i
|
||||
class="material-icons queue_icon"
|
||||
:data-uuid="queueItem.uuid"
|
||||
:class="{ clickable: finishedWithFails }"
|
||||
@click="onResultIconClick"
|
||||
v-if="!isLoading"
|
||||
>
|
||||
{{ resultIconText }}
|
||||
</i>
|
||||
<div v-else class="circle-loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
queueItem: Object
|
||||
},
|
||||
computed: {
|
||||
finishedWithFails() {
|
||||
return this.queueItem.status === 'download finished' && this.queueItem.failed >= 1
|
||||
},
|
||||
barClass() {
|
||||
return {
|
||||
converting: this.queueItem.status === 'converting',
|
||||
indeterminate: ['converting', 'downloading', 'download finished'].indexOf(this.queueItem.status) === -1,
|
||||
determinate: ['converting', 'downloading', 'download finished'].indexOf(this.queueItem.status) !== -1
|
||||
}
|
||||
},
|
||||
barStyle() {
|
||||
let width = 0
|
||||
|
||||
if (this.queueItem.status === 'download finished') {
|
||||
width = 100
|
||||
}
|
||||
|
||||
if (this.queueItem.status === 'downloading') {
|
||||
width = this.queueItem.progress
|
||||
}
|
||||
|
||||
if (this.queueItem.status === 'converting') {
|
||||
width = 100 - this.queueItem.conversion
|
||||
}
|
||||
|
||||
return {
|
||||
width: `${width}%`
|
||||
}
|
||||
},
|
||||
resultIconText() {
|
||||
let text = 'remove'
|
||||
|
||||
if (this.queueItem.status === 'download finished') {
|
||||
if (this.queueItem.failed == 0) {
|
||||
text = 'done'
|
||||
} else {
|
||||
if (this.queueItem.failed >= this.queueItem.size) {
|
||||
text = 'error'
|
||||
} else {
|
||||
text = 'warning'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onResultIconClick() {
|
||||
if (this.finishedWithFails) {
|
||||
this.$emit('show-errors', this.queueItem)
|
||||
}
|
||||
|
||||
if (this.queueItem.status === 'downloading') {
|
||||
this.isLoading = true
|
||||
this.$emit('remove-item', this.queueItem.uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
51
src/components/search/ResultsAlbums.vue
Normal file
51
src/components/search/ResultsAlbums.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div id="album_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.albumTab.loaded" />
|
||||
<div v-else-if="results.albumTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsAlbum') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.albumTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.albumTab.data"
|
||||
class="release clickable"
|
||||
@click.stop="$emit('album-view', $event)"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.cover_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
|
||||
{{ release.title }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
$t('globals.by', { artist: release.artist.name }) +
|
||||
' - ' +
|
||||
$tc('globals.listTabs.trackN', release.nb_tracks)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
}
|
||||
}
|
||||
</script>
|
234
src/components/search/ResultsAll.vue
Normal file
234
src/components/search/ResultsAll.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div id="main_search" class="search_tabcontent">
|
||||
<template v-for="section in results.allTab.ORDER">
|
||||
<section
|
||||
v-if="
|
||||
(section != 'TOP_RESULT' && results.allTab[section].data.length > 0) || results.allTab[section].length > 0
|
||||
"
|
||||
class="search_section"
|
||||
>
|
||||
<h2
|
||||
@click="$emit('change-search-tab', section)"
|
||||
class="search_header"
|
||||
:class="{ top_result_header: section === 'TOP_RESULT' }"
|
||||
>
|
||||
{{ $tc(`globals.listTabs.${section.toLowerCase()}`, 2) }}
|
||||
</h2>
|
||||
<!-- Top result -->
|
||||
<div
|
||||
v-if="section == 'TOP_RESULT'"
|
||||
class="top_result clickable"
|
||||
@click.stop="$emit(`${topResultType}-view`, $event)"
|
||||
:data-id="results.allTab.TOP_RESULT[0].id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
:src="results.allTab.TOP_RESULT[0].picture"
|
||||
:class="(results.allTab.TOP_RESULT[0].type == 'artist' ? 'circle' : 'rounded') + ' coverart'"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="results.allTab.TOP_RESULT[0].link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info_box">
|
||||
<p class="primary-text">{{ results.allTab.TOP_RESULT[0].title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
results.allTab.TOP_RESULT[0].type == 'artist'
|
||||
? $t('search.fans', { n: $n(results.allTab.TOP_RESULT[0].nb_fan) })
|
||||
: $t('globals.by', { artist: results.allTab.TOP_RESULT[0].artist }) +
|
||||
' - ' +
|
||||
$tc('globals.listTabs.trackN', results.allTab.TOP_RESULT[0].nb_song)
|
||||
}}
|
||||
</p>
|
||||
<span class="tag">{{ $tc(`globals.listTabs.${results.allTab.TOP_RESULT[0].type}`, 1) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'TRACK'">
|
||||
<table class="table table--tracks">
|
||||
<tbody>
|
||||
<tr v-for="track in results.allTab.TRACK.data.slice(0, 6)">
|
||||
<td class="table__icon" aria-hidden="true">
|
||||
<img
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' + track.ALB_PICTURE + '/32x32-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.EXPLICIT_LYRICS == 1" class="material-icons explicit_icon"> explicit </i>
|
||||
{{ track.SNG_TITLE + (track.VERSION ? ' ' + track.VERSION : '') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="table__cell table__cell--medium table__cell--center breakline">
|
||||
<span
|
||||
class="clickable"
|
||||
@click.stop="$emit('artist-view', $event)"
|
||||
:data-id="artist.ART_ID"
|
||||
v-for="artist in track.ARTISTS"
|
||||
:key="artist.ART_ID"
|
||||
>
|
||||
{{ artist.ART_NAME }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--medium table__cell--center breakline clickable"
|
||||
@click.stop="$emit('album-view', $event)"
|
||||
:data-id="track.ALB_ID"
|
||||
>
|
||||
{{ track.ALB_TITLE }}
|
||||
</td>
|
||||
<td class="table__cell table__cell--center">
|
||||
{{ convertDuration(track.DURATION) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://www.deezer.com/track/' + track.SNG_ID"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')"> get_app </i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="section == 'ARTIST'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.ARTIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click.stop="$emit('artist-view', $event)"
|
||||
:data-id="release.ART_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="circle coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/artist/' + release.ART_PICTURE + '/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://deezer.com/artist/' + release.ART_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.ART_NAME }}</p>
|
||||
<p class="secondary-text">{{ $t('search.fans', { n: $n(release.NB_FAN) }) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'ALBUM'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.ALBUM.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click.stop="$emit('album-view', $event)"
|
||||
:data-id="release.ALB_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/cover/' + release.ALB_PICTURE + '/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://deezer.com/album/' + release.ALB_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text inline-flex">
|
||||
<i
|
||||
v-if="[1, 4].indexOf(release.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS) != -1"
|
||||
class="material-icons explicit_icon"
|
||||
>explicit</i
|
||||
>
|
||||
{{ release.ALB_TITLE }}
|
||||
</p>
|
||||
<p class="secondary-text">
|
||||
{{ release.ART_NAME + ' - ' + $tc('globals.listTabs.trackN', release.NUMBER_TRACK) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
|
||||
<div
|
||||
v-for="release in results.allTab.PLAYLIST.data.slice(0, 10)"
|
||||
class="release clickable"
|
||||
@click.stop="$emit('playlist-view', $event)"
|
||||
:data-id="release.PLAYLIST_ID"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img
|
||||
aria-hidden="true"
|
||||
class="rounded coverart"
|
||||
:src="
|
||||
'https://e-cdns-images.dzcdn.net/images/' +
|
||||
release.PICTURE_TYPE +
|
||||
'/' +
|
||||
release.PLAYLIST_PICTURE +
|
||||
'/156x156-000000-80-0-0.jpg'
|
||||
"
|
||||
/>
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="'https://deezer.com/playlist/' + release.PLAYLIST_ID"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.TITLE }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.trackN', release.NB_SONG) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<div v-if="noResults">
|
||||
<h1>{{ $t('search.noResults') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
computed: {
|
||||
topResultType() {
|
||||
return this.results.allTab.TOP_RESULT[0].type
|
||||
},
|
||||
noResults() {
|
||||
return this.results.allTab.ORDER.every(section =>
|
||||
section == 'TOP_RESULT'
|
||||
? this.results.allTab[section].length == 0
|
||||
: this.results.allTab[section].data.length == 0
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
convertDuration
|
||||
}
|
||||
}
|
||||
</script>
|
42
src/components/search/ResultsArtists.vue
Normal file
42
src/components/search/ResultsArtists.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div id="artist_search" class="search_tabcontent">
|
||||
<base-loading-placeholder v-if="!results.artistTab.loaded"></base-loading-placeholder>
|
||||
<div v-else-if="results.artistTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsArtist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.artistTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.artistTab.data"
|
||||
class="release clickable"
|
||||
@click.stop="$emit('artist-view', $event)"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="circle coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.name }}</p>
|
||||
<p class="secondary-text">{{ $tc('globals.listTabs.releaseN', release.nb_album) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
}
|
||||
}
|
||||
</script>
|
46
src/components/search/ResultsPlaylists.vue
Normal file
46
src/components/search/ResultsPlaylists.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div id="playlist_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.playlistTab.loaded" />
|
||||
<div v-else-if="results.playlistTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsPlaylist') }}</h1>
|
||||
</div>
|
||||
<div class="release_grid" v-if="results.playlistTab.data.length > 0">
|
||||
<div
|
||||
v-for="release in results.playlistTab.data"
|
||||
class="release clickable"
|
||||
@click.stop="$emit('playlist-view', $event)"
|
||||
:data-id="release.id"
|
||||
>
|
||||
<div class="cover_container">
|
||||
<img aria-hidden="true" class="rounded coverart" :src="release.picture_medium" />
|
||||
<div
|
||||
role="button"
|
||||
aria-label="download"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="release.link"
|
||||
class="download_overlay"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="primary-text">{{ release.title }}</p>
|
||||
<p class="secondary-text">
|
||||
{{
|
||||
`${$t('globals.by', { artist: release.user.name })} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}`
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
}
|
||||
}
|
||||
</script>
|
114
src/components/search/ResultsTracks.vue
Normal file
114
src/components/search/ResultsTracks.vue
Normal file
@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div id="track_search" class="search_tabcontent">
|
||||
<BaseLoadingPlaceholder v-if="!results.trackTab.loaded" />
|
||||
<div v-else-if="results.trackTab.data.length == 0">
|
||||
<h1>{{ $t('search.noResultsTrack') }}</h1>
|
||||
</div>
|
||||
<table class="table table--tracks" v-if="results.trackTab.data.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ $tc('globals.listTabs.title', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.artist', 1) }}</th>
|
||||
<th>{{ $tc('globals.listTabs.album', 1) }}</th>
|
||||
<th>
|
||||
<i class="material-icons"> timer </i>
|
||||
</th>
|
||||
<th style="width: 56px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="track in results.trackTab.data">
|
||||
<td class="table__icon table__icon--big">
|
||||
<a
|
||||
href="#"
|
||||
@click="playPausePreview"
|
||||
:class="'rounded' + (track.preview ? ' single-cover' : '')"
|
||||
:data-preview="track.preview"
|
||||
>
|
||||
<i
|
||||
@mouseenter="previewMouseEnter"
|
||||
@mouseleave="previewMouseLeave"
|
||||
v-if="track.preview"
|
||||
class="material-icons preview_controls"
|
||||
:title="$t('globals.play_hint')"
|
||||
>
|
||||
play_arrow
|
||||
</i>
|
||||
<img class="rounded coverart" :src="track.album.cover_small" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="table__cell table__cell--large breakline">
|
||||
<div class="table__cell-content table__cell-content--vertical-center">
|
||||
<i v-if="track.explicit_lyrics" class="material-icons explicit_icon"> explicit </i>
|
||||
{{
|
||||
track.title +
|
||||
(track.title_version && track.title.indexOf(track.title_version) == -1 ? ' ' + track.title_version : '')
|
||||
}}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
@click.stop="artistView"
|
||||
:data-id="track.artist.id"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell table__cell--medium table__cell--center breakline clickable"
|
||||
@click.stop="albumView"
|
||||
:data-id="track.album.id"
|
||||
>
|
||||
{{ track.album.title }}
|
||||
</td>
|
||||
<td class="table__cell table__cell--small table__cell--center">
|
||||
{{ convertDuration(track.duration) }}
|
||||
</td>
|
||||
<td
|
||||
class="table__cell--download table__cell--center clickable"
|
||||
@click.stop="$emit('add-to-queue', $event)"
|
||||
:data-link="track.link"
|
||||
role="button"
|
||||
aria-label="download"
|
||||
>
|
||||
<i class="material-icons" :title="$t('globals.download_hint')"> get_app </i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseLoadingPlaceholder from '@components/BaseLoadingPlaceholder.vue'
|
||||
|
||||
import EventBus from '@/utils/EventBus.js'
|
||||
import { convertDuration } from '@/utils/utils'
|
||||
|
||||
export default {
|
||||
props: ['results'],
|
||||
components: {
|
||||
BaseLoadingPlaceholder
|
||||
},
|
||||
methods: {
|
||||
convertDuration,
|
||||
artistView(event) {
|
||||
this.$emit('artist-view', event)
|
||||
},
|
||||
albumView(event) {
|
||||
this.$emit('album-view', event)
|
||||
},
|
||||
playlistView(event) {
|
||||
this.$emit('playlist-view', event)
|
||||
},
|
||||
playPausePreview(e) {
|
||||
EventBus.$emit('trackPreview:playPausePreview', e)
|
||||
},
|
||||
previewMouseEnter(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseEnter', e)
|
||||
},
|
||||
previewMouseLeave(e) {
|
||||
EventBus.$emit('trackPreview:previewMouseLeave', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
22
src/data/charts.js
Normal file
22
src/data/charts.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
let chartsData = {}
|
||||
let cached = false
|
||||
|
||||
export function getChartsData() {
|
||||
if (cached) {
|
||||
return chartsData
|
||||
} else {
|
||||
socket.emit('get_charts_data')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.on('init_charts', data => {
|
||||
chartsData = data
|
||||
cached = true
|
||||
|
||||
socket.off('init_charts')
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
22
src/data/favorites.js
Normal file
22
src/data/favorites.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
let favoritesData = {}
|
||||
let cached = false
|
||||
|
||||
export function getFavoritesData() {
|
||||
if (cached) {
|
||||
return favoritesData
|
||||
} else {
|
||||
socket.emit('get_favorites_data')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.on('init_favorites', data => {
|
||||
favoritesData = data
|
||||
cached = true
|
||||
|
||||
socket.off('init_favorites')
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
22
src/data/home.js
Normal file
22
src/data/home.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
let homeData = {}
|
||||
let cached = false
|
||||
|
||||
export function getHomeData() {
|
||||
if (cached) {
|
||||
return homeData
|
||||
} else {
|
||||
socket.emit('get_home_data')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.on('init_home', data => {
|
||||
homeData = data
|
||||
cached = true
|
||||
|
||||
socket.off('init_home')
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
27
src/data/settings.js
Normal file
27
src/data/settings.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
let settingsData = {}
|
||||
let defaultSettingsData = {}
|
||||
let spotifyCredentials = {}
|
||||
|
||||
let cached = false
|
||||
|
||||
export function getSettingsData() {
|
||||
if (cached) {
|
||||
return { settingsData, defaultSettingsData, spotifyCredentials }
|
||||
} else {
|
||||
socket.emit('get_settings_data')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.on('init_settings', (settings, credentials, defaults) => {
|
||||
settingsData = settings
|
||||
defaultSettingsData = defaults
|
||||
spotifyCredentials = credentials
|
||||
// cached = true
|
||||
|
||||
socket.off('init_settings')
|
||||
resolve({ settingsData, defaultSettingsData, spotifyCredentials })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
172
src/js/tabs.js
172
src/js/tabs.js
@ -1,5 +1,5 @@
|
||||
import { socket } from '@/utils/socket'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
import router from '@/router'
|
||||
|
||||
/* ===== Globals ====== */
|
||||
window.search_selected = ''
|
||||
@ -7,78 +7,35 @@ window.main_selected = ''
|
||||
window.windows_stack = []
|
||||
window.currentStack = {}
|
||||
|
||||
// Exporting this function out of the default export
|
||||
// because it's used in components that are needed
|
||||
// in this file too
|
||||
export function showView(viewType, event) {
|
||||
// console.error('SHOW VIEW')
|
||||
const {
|
||||
currentTarget: {
|
||||
dataset: { id }
|
||||
}
|
||||
} = event
|
||||
|
||||
switch (viewType) {
|
||||
case 'artist':
|
||||
EventBus.$emit('artistTab:reset')
|
||||
break
|
||||
case 'album':
|
||||
case 'playlist':
|
||||
case 'spotifyplaylist':
|
||||
EventBus.$emit('tracklistTab:reset')
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
socket.emit('getTracklist', { type: viewType, id })
|
||||
showTab(viewType, id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the tab to the wanted one
|
||||
* Need to understand the difference from showTab
|
||||
*
|
||||
* Needs EventBus
|
||||
*/
|
||||
// Used only in errors tab
|
||||
export function changeTab(sidebarEl, section, tabName) {
|
||||
// console.error('CHANGE TAB')
|
||||
window.windows_stack = []
|
||||
window.currentStack = {}
|
||||
|
||||
// * The visualized content of the tab
|
||||
// ! Can be more than one per tab, happens in MainSearch and Favorites tab
|
||||
// ! because they have more tablinks (see below)
|
||||
const tabContent = document.getElementsByClassName(`${section}_tabcontent`)
|
||||
// * Only in section search
|
||||
updateTabLink(section)
|
||||
|
||||
for (let i = 0; i < tabContent.length; i++) {
|
||||
tabContent[i].style.display = 'none'
|
||||
}
|
||||
// * Only when clicking the settings icon in the sidebar
|
||||
// resetSettings(tabName)
|
||||
|
||||
// * Tabs inside the actual tab (like albums, tracks, playlists...)
|
||||
const tabLinks = document.getElementsByClassName(`${section}_tablinks`)
|
||||
// * Only in section search
|
||||
setSelectedTab(section, tabName)
|
||||
|
||||
for (let i = 0; i < tabLinks.length; i++) {
|
||||
tabLinks[i].classList.remove('active')
|
||||
}
|
||||
|
||||
if (tabName === 'settings_tab' && window.main_selected !== 'settings_tab') {
|
||||
EventBus.$emit('settingsTab:revertSettings')
|
||||
EventBus.$emit('settingsTab:revertCredentials')
|
||||
}
|
||||
|
||||
document.getElementById(tabName).style.display = 'block'
|
||||
// * Only if window.main_selected === 'search_tab'
|
||||
checkNeedToLoadMoreContent()
|
||||
}
|
||||
|
||||
function setSelectedTab(section, tabName) {
|
||||
if (section === 'main') {
|
||||
window.main_selected = tabName
|
||||
} else if ('search' === section) {
|
||||
} else if (section === 'search') {
|
||||
window.search_selected = tabName
|
||||
}
|
||||
}
|
||||
|
||||
sidebarEl.classList.add('active')
|
||||
|
||||
// Check if you need to load more content in the search tab
|
||||
function checkNeedToLoadMoreContent() {
|
||||
// * Check if you need to load more content in the search tab
|
||||
// * Happens when the user changes the tab in the main search
|
||||
if (
|
||||
window.main_selected === 'search_tab' &&
|
||||
['track_search', 'album_search', 'artist_search', 'playlist_search'].indexOf(window.search_selected) !== -1
|
||||
@ -87,77 +44,50 @@ export function changeTab(sidebarEl, section, tabName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the passed tab, keeping track of the one that the user is coming from.
|
||||
*
|
||||
* Needs EventBus
|
||||
*/
|
||||
function showTab(type, id, back = false) {
|
||||
if (window.windows_stack.length === 0) {
|
||||
window.windows_stack.push({ tab: window.main_selected })
|
||||
} else if (!back) {
|
||||
if (window.currentStack.type === 'artist') {
|
||||
EventBus.$emit('artistTab:updateSelected')
|
||||
function resetSettings(tabName) {
|
||||
if (tabName === 'settings_tab' && window.main_selected !== 'settings_tab') {
|
||||
EventBus.$emit('settingsTab:revertSettings')
|
||||
EventBus.$emit('settingsTab:revertCredentials')
|
||||
}
|
||||
}
|
||||
|
||||
function updateTabLink(section) {
|
||||
// * Tabs inside the actual tab (like albums, tracks, playlists...)
|
||||
// * or sidebar links
|
||||
if (section == 'main') return
|
||||
|
||||
const tabLinks = document.getElementsByClassName(`${section}_tablinks`)
|
||||
|
||||
for (let i = 0; i < tabLinks.length; i++) {
|
||||
tabLinks[i].classList.remove('active')
|
||||
}
|
||||
}
|
||||
|
||||
export function showView(viewType, event) {
|
||||
const {
|
||||
currentTarget: {
|
||||
dataset: { id }
|
||||
}
|
||||
} = event
|
||||
const isArtist = viewType === 'artist'
|
||||
const name = isArtist ? 'Artist' : 'Tracklist'
|
||||
const params = isArtist ? { id } : { type: viewType, id }
|
||||
|
||||
window.windows_stack.push(window.currentStack)
|
||||
}
|
||||
|
||||
window.tab = type === 'artist' ? 'artist_tab' : 'tracklist_tab'
|
||||
|
||||
window.currentStack = { type, id }
|
||||
let tabcontent = document.getElementsByClassName('main_tabcontent')
|
||||
|
||||
for (let i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = 'none'
|
||||
}
|
||||
|
||||
document.getElementById(window.tab).style.display = 'block'
|
||||
|
||||
EventBus.$emit('trackPreview:stopStackedTabsPreview')
|
||||
router.push({
|
||||
name,
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back to the previous tab according to the global window stack.
|
||||
*
|
||||
* Needs EventBus and socket
|
||||
*/
|
||||
function backTab() {
|
||||
if (window.windows_stack.length == 1) {
|
||||
document.getElementById(`main_${window.main_selected}link`).click()
|
||||
} else {
|
||||
// Retrieving tab type and tab id
|
||||
let data = window.windows_stack.pop()
|
||||
let { type, id, selected } = data
|
||||
|
||||
if (type === 'artist') {
|
||||
EventBus.$emit('artistTab:reset')
|
||||
|
||||
if (selected) {
|
||||
EventBus.$emit('artistTab:changeTab', selected)
|
||||
}
|
||||
} else {
|
||||
EventBus.$emit('tracklistTab:reset')
|
||||
}
|
||||
|
||||
socket.emit('getTracklist', { type, id })
|
||||
showTab(type, id, true)
|
||||
}
|
||||
|
||||
EventBus.$emit('trackPreview:stopStackedTabsPreview')
|
||||
}
|
||||
|
||||
function _linkListeners() {
|
||||
const backButtons = Array.prototype.slice.call(document.getElementsByClassName('back-button'))
|
||||
|
||||
backButtons.forEach(button => {
|
||||
button.addEventListener('click', backTab)
|
||||
})
|
||||
export function backTab() {
|
||||
// ! Need to implement the memory of the opened artist tab
|
||||
router.back()
|
||||
}
|
||||
|
||||
export function init() {
|
||||
// Open default tab
|
||||
changeTab(document.getElementById('main_home_tablink'), 'main', 'home_tab')
|
||||
|
||||
_linkListeners()
|
||||
}
|
||||
|
@ -8,5 +8,8 @@
|
||||
"@components/*": ["./components/*"]
|
||||
}
|
||||
},
|
||||
"typeAcquisition": {
|
||||
"include": ["socket.io-client"]
|
||||
},
|
||||
"exclude": ["assets/**/*", "styles/**/*"]
|
||||
}
|
||||
|
@ -41,6 +41,12 @@ const en = {
|
||||
}
|
||||
},
|
||||
about: {
|
||||
updates: {
|
||||
currentVersion: 'Current Version',
|
||||
versionNotAvailable: 'N/A',
|
||||
updateAvailable: `You're not running the latest version available: {version}`,
|
||||
deemixVersion: 'deemix lib version'
|
||||
},
|
||||
titles: {
|
||||
usefulLinks: 'Useful Links',
|
||||
bugReports: 'Bug Reports',
|
||||
@ -98,7 +104,8 @@ const en = {
|
||||
wrongBitrateNoAlternative: 'Track not found at desired bitrate and no alternative found!',
|
||||
no360RA: 'Track is not available in Reality Audio 360.',
|
||||
notAvailable: "Track not available on Deezer's servers!",
|
||||
notAvailableNoAlternative: "Track not available on Deezer's servers and no alternative found!"
|
||||
notAvailableNoAlternative: "Track not available on Deezer's servers and no alternative found!",
|
||||
noSpaceLeft: "No space left on the device!"
|
||||
}
|
||||
},
|
||||
favorites: {
|
||||
@ -168,7 +175,8 @@ const en = {
|
||||
finishAddingArtist: 'Added {artist} albums to queue',
|
||||
startConvertingSpotifyPlaylist: 'Converting spotify tracks to Deezer tracks',
|
||||
finishConvertingSpotifyPlaylist: 'Spotify playlist converted',
|
||||
loginNeededToDownload: 'You need to log in to download tracks!'
|
||||
loginNeededToDownload: 'You need to log in to download tracks!',
|
||||
deezerNotAvailable: 'Deezer is not available in your country. You should use a VPN.'
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
@ -180,7 +188,8 @@ const en = {
|
||||
question: 'How do I get my own ARL?',
|
||||
update: 'Update ARL'
|
||||
},
|
||||
logout: 'Logout'
|
||||
logout: 'Logout',
|
||||
login: 'Login via deezer.com'
|
||||
},
|
||||
appearance: {
|
||||
title: 'Appearance',
|
||||
@ -256,7 +265,8 @@ const en = {
|
||||
jpegImageQuality: 'JPEG image quality',
|
||||
embeddedArtworkPNG: 'Save embedded artwork as PNG',
|
||||
embeddedPNGWarning: 'PNGs are not officialy supported by Deezer and can be buggy',
|
||||
imageSizeWarning: 'Anything above x1200 is not officialy used by Deezer, you may encounter issues'
|
||||
imageSizeWarning: 'Anything above x1200 is not officialy used by Deezer, you may encounter issues',
|
||||
coverDescriptionUTF8: 'Save cover description using UTF8 (iTunes Cover Fix)'
|
||||
},
|
||||
tags: {
|
||||
head: 'Which tags to save',
|
||||
@ -280,6 +290,7 @@ const en = {
|
||||
replayGain: 'Replay Gain',
|
||||
label: 'Album Label',
|
||||
lyrics: 'Unsynchronized Lyrics',
|
||||
syncedLyrics: 'Synchronized Lyrics',
|
||||
copyright: 'Copyright',
|
||||
composer: 'Composer',
|
||||
involvedPeople: 'Involved People'
|
||||
@ -332,7 +343,8 @@ const en = {
|
||||
title: 'Spotify Features',
|
||||
clientID: 'Spotify ClientID',
|
||||
clientSecret: 'Spotify Client Secret',
|
||||
username: 'Spotify Username'
|
||||
username: 'Spotify Username',
|
||||
question: 'How do I enable Spotify Features?'
|
||||
},
|
||||
reset: 'Reset to Default',
|
||||
save: 'Save',
|
||||
|
@ -40,6 +40,12 @@ const es = {
|
||||
}
|
||||
},
|
||||
about: {
|
||||
updates: {
|
||||
currentVersion: 'Versión actual',
|
||||
versionNotAvailable: 'N/D',
|
||||
updateAvailable: `No estás ejecutando la última versión disponible: {versión}`,
|
||||
deemixVersion: 'versión de la biblioteca deemix'
|
||||
},
|
||||
titles: {
|
||||
usefulLinks: 'Enlaces útiles',
|
||||
bugReports: 'Reportar fallos',
|
||||
@ -96,7 +102,8 @@ const es = {
|
||||
wrongBitrateNoAlternative: '¡Pista no encontrada a la tasa de bits deseada y no se ha encontrado ninguna alternativa!',
|
||||
no360RA: 'La pista no está disponible en Reality Audio 360.',
|
||||
notAvailable: '¡La pista no está disponible en los servidores de Deezer!',
|
||||
notAvailableNoAlternative: '¡La pista no está disponible en los servidores de Deezer y no se ha encontrado ninguna alternativa!'
|
||||
notAvailableNoAlternative: '¡La pista no está disponible en los servidores de Deezer y no se ha encontrado ninguna alternativa!',
|
||||
noSpaceLeft: '¡No queda espacio en el dispositivo!'
|
||||
}
|
||||
},
|
||||
favorites: {
|
||||
@ -166,7 +173,8 @@ const es = {
|
||||
finishAddingArtist: 'Añadido {artist} álbumes a la cola',
|
||||
startConvertingSpotifyPlaylist: 'Convertir las pistas de Spotify en pistas de Deezer',
|
||||
finishConvertingSpotifyPlaylist: 'Lista de reproducción de Spotify convertida',
|
||||
loginNeededToDownload: '¡Necesitas iniciar sesión para descargar títulos!'
|
||||
loginNeededToDownload: '¡Necesitas iniciar sesión para descargar títulos!',
|
||||
deezerNotAvailable: 'Deezer no está disponible en su país. Deberías usar una VPN.'
|
||||
},
|
||||
settings: {
|
||||
title: 'Configuración',
|
||||
@ -178,7 +186,8 @@ const es = {
|
||||
question: '¿Cómo consigo mi propio ARL?',
|
||||
update: 'Actualiza la ARL'
|
||||
},
|
||||
logout: 'Cerrar sesión'
|
||||
logout: 'Cerrar sesión',
|
||||
login: 'Ingresa a través de deezer.com'
|
||||
},
|
||||
appearance: {
|
||||
title: 'Apariencia',
|
||||
@ -225,7 +234,8 @@ const es = {
|
||||
y: 'Sí, sobrescribir el archivo',
|
||||
n: 'No, no sobrescribir el archivo',
|
||||
t: 'Sobrescribir sólo las etiquetas',
|
||||
b: 'No, mantener los dos archivos y agrega un número al archivo duplicado'
|
||||
b: 'No, mantener los dos archivos y agrega un número al archivo duplicado',
|
||||
e: 'No, y no mirar las extensiones'
|
||||
},
|
||||
fallbackBitrate: 'La solución alternativa de bitrate',
|
||||
fallbackSearch: 'Búsqueda de la segunda opción',
|
||||
@ -253,7 +263,8 @@ const es = {
|
||||
jpegImageQuality: 'Calidad de la imagen JPEG',
|
||||
embeddedArtworkPNG: 'Guardar las imágenes incrustadas como PNG',
|
||||
embeddedPNGWarning: 'Las PNG no están oficialmente soportadas por Deezer y puede encontrar errores.',
|
||||
imageSizeWarning: 'Nada por encima de x1200 no es usado oficialmente por Deezer, tú podrías encontrar inconvenientes'
|
||||
imageSizeWarning: 'Nada por encima de x1200 no es usado oficialmente por Deezer, tú podrías encontrar inconvenientes',
|
||||
coverDescriptionUTF8: 'Guardar la descripción de la portada usando UTF8 (arregla la portada de iTunes)'
|
||||
},
|
||||
tags: {
|
||||
head: '¿Qué etiquetas guardar?',
|
||||
@ -277,6 +288,7 @@ const es = {
|
||||
replayGain: 'Ganancia de la reproducción',
|
||||
label: 'Etiqueta del álbum',
|
||||
lyrics: 'Letras no sincronizadas',
|
||||
syncedLyrics: 'Letra sincronizada',
|
||||
copyright: 'Derechos de autor',
|
||||
composer: 'Compositor',
|
||||
involvedPeople: 'Personas involucradas'
|
||||
|
@ -32,14 +32,21 @@ const fr = {
|
||||
playlist: 'playlist | playlists',
|
||||
compile: 'compilation | compilations',
|
||||
ep: 'ep | eps',
|
||||
bundle: 'bundle | bundles',
|
||||
more: "Plus d'albums",
|
||||
featured: 'En vedette dans',
|
||||
featured: 'Apparaît dans',
|
||||
spotifyPlaylist: 'playlist spotify | playlists spotify',
|
||||
releaseDate: 'date de sortie',
|
||||
error: 'erreur'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
updates: {
|
||||
currentVersion: 'Version Actuelle',
|
||||
versionNotAvailable: 'N/A',
|
||||
updateAvailable: "Vous n'utilisez pas la dernière version disponible : {version}",
|
||||
deemixVersion: 'Version de la bibliothèque deemix'
|
||||
},
|
||||
titles: {
|
||||
usefulLinks: 'Liens Utiles',
|
||||
bugReports: 'Rapports De Bug',
|
||||
@ -61,7 +68,7 @@ const fr = {
|
||||
officialSubreddit: 'Subreddit Officiel',
|
||||
newsChannel: "Canal d'Informations",
|
||||
questions: `Si vous avez des questions ou des problèmes avec l'application, cherchez d'abord une solution dans le <a href="https://www.reddit.com/r/deemix" target="_blank">subreddit</a>. Ensuite, si la solution ne s'y trouve pas, vous pouvez publier un message dans le subreddit en décrivant votre problème.`,
|
||||
beforeReporting: "Avant de signaler un bug, assurez-vous que vous utilisez la version la plus récente de l'application. Vérifiez que vous souhaitez nous rapporter un bug et non quelque chose qui ne fonctionne pas de votre côté.",
|
||||
beforeReporting: "Avant de signaler un bug, assurez-vous que vous utilisez la version la plus récente de l'application. Vérifiez que vous souhaitez nous rapporter un bug et non quelque chose qui ne fonctionne pas uniquement de votre côté.",
|
||||
beSure: "Assurez-vous que le bug soit reproductible sur d'autres appareils mais aussi de <strong>NE PAS</strong> signaler un bug si celui-ci a déjà été recensé.",
|
||||
duplicateReports: "Les rapports de bug répétitifs seront supprimés, merci d'en prendre bonne note.",
|
||||
dontOpenIssues: "<strong>NE PAS</strong> rapporter de problème s'il ne s'agit que de simples questions. Un subreddit existe pour ces questions.",
|
||||
@ -97,7 +104,8 @@ const fr = {
|
||||
wrongBitrateNoAlternative: "La piste est introuvable au débit souhaité et aucune alternative n'a été trouvée !",
|
||||
no360RA: 'La piste est indisponible au format Reality Audio 360.',
|
||||
notAvailable: 'La piste est indisponible sur les serveurs de Deezer !',
|
||||
notAvailableNoAlternative: "La piste est indisponible sur les serveurs de Deezer et aucune alternative n'a été trouvée !"
|
||||
notAvailableNoAlternative: "La piste est indisponible sur les serveurs de Deezer et aucune alternative n'a été trouvée !",
|
||||
noSpaceLeft: "L'espace disponible sur cet appareil est insuffisant !"
|
||||
}
|
||||
},
|
||||
favorites: {
|
||||
@ -111,7 +119,7 @@ const fr = {
|
||||
needTologin: 'Vous devez vous connecter à votre compte Deezer avant de pouvoir démarrer un téléchargement.',
|
||||
openSettings: 'Ouvrir Les Paramètres',
|
||||
sections: {
|
||||
popularPlaylists: 'Playlists populaires',
|
||||
popularPlaylists: 'Playlists les plus écoutées',
|
||||
popularAlbums: 'Albums les plus écoutés'
|
||||
}
|
||||
},
|
||||
@ -136,8 +144,8 @@ const fr = {
|
||||
}
|
||||
},
|
||||
search: {
|
||||
startSearching: 'Démarrer la recherche !',
|
||||
description: 'Vous pouvez rechercher une piste, un album entier, un artiste, une playlist... tout ce que vous voulez ! Vous pouvez également copier-coller un lien Deezer',
|
||||
startSearching: 'Démarrer une recherche !',
|
||||
description: 'Vous pouvez rechercher une piste, un album entier, un artiste, une playlist... tout ce que vous voulez ! Vous pouvez également coller un lien Deezer.',
|
||||
fans: '{n} fans',
|
||||
noResults: 'Aucun résultat',
|
||||
noResultsTrack: "Aucune piste n'a été trouvée",
|
||||
@ -163,11 +171,12 @@ const fr = {
|
||||
loggedOut: 'Déconnecté',
|
||||
cancellingCurrentItem: "Annulation de l'élément actuel.",
|
||||
currentItemCancelled: 'Élément actuel annulé.',
|
||||
startAddingArtist: "Ajout de {artist} albums à la file d'attente",
|
||||
finishAddingArtist: "{artist} albums ajoutés à la file d'attente",
|
||||
startAddingArtist: "Ajout des albums de {artist} à la file d'attente",
|
||||
finishAddingArtist: "Les albums de {artist} ont été ajoutés à la file d'attente",
|
||||
startConvertingSpotifyPlaylist: 'Conversion de pistes Spotify en équivalents Deezer',
|
||||
finishConvertingSpotifyPlaylist: 'Playlist Spotify convertie',
|
||||
loginNeededToDownload: 'Vous devez vous connecter pour pouvoir télécharger des pistes !'
|
||||
loginNeededToDownload: 'Vous devez vous connecter pour pouvoir télécharger des pistes !',
|
||||
deezerNotAvailable: "Deezer est indisponible dans votre pays. Vous devez utiliser un VPN."
|
||||
},
|
||||
settings: {
|
||||
title: 'Paramètres',
|
||||
@ -179,7 +188,8 @@ const fr = {
|
||||
question: 'Comment obtenir mon ARL personnel ?',
|
||||
update: "Mettre à jour l'ARL"
|
||||
},
|
||||
logout: 'Déconnexion'
|
||||
logout: 'Déconnexion',
|
||||
login: 'Connexion via deezer.com'
|
||||
},
|
||||
appearance: {
|
||||
title: 'Apparence',
|
||||
@ -210,7 +220,7 @@ const fr = {
|
||||
title: 'Titres des pistes',
|
||||
padTracks: "Longueur uniforme des numéros de piste (ajoute automatiquement des zéros devant le numéro initial de la piste)",
|
||||
paddingSize: 'Nombre de zéros à ajouter en permanence devant le numéro initial de la piste',
|
||||
illegalCharacterReplacer: 'Remplacement de caractère inapproprié'
|
||||
illegalCharacterReplacer: "Substitut aux caractères non autorisés (dans les noms de fichiers et de dossiers)"
|
||||
},
|
||||
downloads: {
|
||||
title: 'Téléchargements',
|
||||
@ -222,7 +232,7 @@ const fr = {
|
||||
1: 'MP3 128kbps'
|
||||
},
|
||||
overwriteFile: {
|
||||
title: 'Faut-il écraser les fichiers ?',
|
||||
title: 'Les fichiers doivent-ils être écrasés ?',
|
||||
y: 'Oui, écraser le fichier',
|
||||
n: 'Non, ne pas écraser le fichier',
|
||||
t: 'Écraser uniquement les métadonnées',
|
||||
@ -254,8 +264,9 @@ const fr = {
|
||||
},
|
||||
jpegImageQuality: "Qualité de l'image JPEG",
|
||||
embeddedArtworkPNG: "Enregistrer l'illustration incorporée aux fichiers audio en tant que PNG",
|
||||
embeddedPNGWarning: 'Les images PNG ne sont pas officiellement utilisées par Deezer et pourraient causer des problèmes',
|
||||
imageSizeWarning: "Toute valeur supérieure à x1200 n'est pas officiellement supportée par Deezer, vous pourriez donc rencontrer des problèmes"
|
||||
embeddedPNGWarning: 'Les images PNG ne sont pas officiellement utilisées par Deezer et pourraient causer des problèmes.',
|
||||
imageSizeWarning: "Toute valeur supérieure à x1200 n'est pas officiellement supportée par Deezer, vous pourriez donc rencontrer des problèmes.",
|
||||
coverDescriptionUTF8: 'Enregistrer la description de la pochette au format UTF8 (iTunes Cover Fix)'
|
||||
},
|
||||
tags: {
|
||||
head: 'Métadonnées à sauvegarder',
|
||||
@ -279,6 +290,7 @@ const fr = {
|
||||
replayGain: 'Gain En Relecture (Replay Gain)',
|
||||
label: "Label De l'Album",
|
||||
lyrics: 'Paroles Non-Synchronisées',
|
||||
syncedLyrics: 'Paroles Synchronisées',
|
||||
copyright: "Droits d'Auteur (Copyright)",
|
||||
composer: 'Compositeur',
|
||||
involvedPeople: 'Personnes Impliquées'
|
||||
@ -306,7 +318,7 @@ const fr = {
|
||||
day: 'JJ'
|
||||
},
|
||||
featuredToTitle: {
|
||||
title: 'Que faut-il faire avec les artistes participants (featuring) ?',
|
||||
title: 'Que faire des artistes participants (featuring) ?',
|
||||
0: 'Ne rien faire',
|
||||
1: 'Les retirer du titre de la piste',
|
||||
3: "Les supprimer du titre de la piste et du titre de l'album",
|
||||
@ -331,7 +343,8 @@ const fr = {
|
||||
title: 'Fonctionnalités Spotify',
|
||||
clientID: 'clientID Spotify',
|
||||
clientSecret: 'Client Secret Spotify',
|
||||
username: "Nom d'utilisateur Spotify"
|
||||
username: "Nom d'utilisateur Spotify",
|
||||
question: 'Comment activer les Fonctionnalités Spotify ?'
|
||||
},
|
||||
reset: 'Rétablir les valeurs par défaut',
|
||||
save: 'Sauvegarder',
|
||||
@ -346,7 +359,7 @@ const fr = {
|
||||
search: 'recherche',
|
||||
charts: 'classements',
|
||||
favorites: 'favoris',
|
||||
linkAnalyzer: 'analyseur de liens',
|
||||
linkAnalyzer: 'analyseur de lien',
|
||||
settings: 'paramètres',
|
||||
about: 'à propos'
|
||||
},
|
||||
|
@ -41,6 +41,12 @@ const it = {
|
||||
}
|
||||
},
|
||||
about: {
|
||||
updates: {
|
||||
currentVersion: 'Versione corrente',
|
||||
versionNotAvailable: 'N/A',
|
||||
updateAvailable: `Non stai usando l'ultima versione disponibile: {version}`,
|
||||
deemixVersion: 'Versione libreria deemix'
|
||||
},
|
||||
titles: {
|
||||
usefulLinks: 'Link Utili',
|
||||
bugReports: 'Segnalazione di bug',
|
||||
@ -101,7 +107,8 @@ const it = {
|
||||
wrongBitrateNoAlternative: 'Brano non trovato con il bitrate specificato e nessuna alternativa trovata!',
|
||||
no360RA: 'Brano non disponibile in Reality Audio 360.',
|
||||
notAvailable: 'Brano non presente sui server di Deezer!',
|
||||
notAvailableNoAlternative: 'Brano non presente sui server di Deezer e nessuna alternativa trovata!'
|
||||
notAvailableNoAlternative: 'Brano non presente sui server di Deezer e nessuna alternativa trovata!',
|
||||
noSpaceLeft: "Spazio su disco esaurito!"
|
||||
}
|
||||
},
|
||||
favorites: {
|
||||
@ -174,7 +181,8 @@ const it = {
|
||||
finishAddingArtist: 'Aggiunto gli album di {artist} alla coda',
|
||||
startConvertingSpotifyPlaylist: 'Convertendo i brani da spotify a deezer',
|
||||
finishConvertingSpotifyPlaylist: 'Playlist di spotify convertita',
|
||||
loginNeededToDownload: 'Devi accedere prima di poter scaricare brani!'
|
||||
loginNeededToDownload: 'Devi accedere prima di poter scaricare brani!',
|
||||
deezerNotAvailable: 'Deezer non è disponibile nel tuo paese. Dovresti usare una VPN.'
|
||||
},
|
||||
settings: {
|
||||
title: 'Impostazioni',
|
||||
@ -186,7 +194,8 @@ const it = {
|
||||
question: 'Come ottengo il mio ARL?',
|
||||
update: 'Aggiorna ARL'
|
||||
},
|
||||
logout: 'Disconnettiti'
|
||||
logout: 'Disconnettiti',
|
||||
login: 'Accedi tramite deezer.com'
|
||||
},
|
||||
appearance: {
|
||||
title: 'Aspetto',
|
||||
@ -262,7 +271,8 @@ const it = {
|
||||
jpegImageQuality: 'Qualità immagine JPEG',
|
||||
embeddedArtworkPNG: 'Salva copertina incorporata come PNG',
|
||||
embeddedPNGWarning: 'Le immagini PNG non sono usate ufficialmente da Deezer e potrebbero dare problemi',
|
||||
imageSizeWarning: 'Dimensioni maggiori di x1200 non sono usate ufficialmente da Deezer, potresti incontrare problemi'
|
||||
imageSizeWarning: 'Dimensioni maggiori di x1200 non sono usate ufficialmente da Deezer, potresti incontrare problemi',
|
||||
coverDescriptionUTF8: 'Salva la descrizione della copertina in UTF8 (iTunes Cover Fix)'
|
||||
},
|
||||
tags: {
|
||||
head: 'Quali tag salvare',
|
||||
@ -286,6 +296,7 @@ const it = {
|
||||
replayGain: 'Replay gain',
|
||||
label: 'Casa Discografica',
|
||||
lyrics: 'Testo non Sincronizzato',
|
||||
syncedLyrics: 'Testo Sincronizzato',
|
||||
copyright: 'Copyright',
|
||||
composer: 'Compositori',
|
||||
involvedPeople: 'Persone Coinvolte'
|
||||
@ -338,7 +349,8 @@ const it = {
|
||||
title: 'Spotify Features',
|
||||
clientID: 'Spotify clientID',
|
||||
clientSecret: 'Spotify Client Secret',
|
||||
username: 'Spotify username'
|
||||
username: 'Spotify username',
|
||||
question: 'Come attivo le Spotify Features?'
|
||||
},
|
||||
reset: 'Reimposta Default',
|
||||
save: 'Salva',
|
||||
|
@ -32,6 +32,7 @@ const ru = {
|
||||
playlist: 'плейлист | плейлисты | плейлисты',
|
||||
compile: 'сплит | сплиты | сплиты',
|
||||
ep: 'ep',
|
||||
bundle: 'бандл | бандлы | бандлы',
|
||||
more: 'Больше альбомов',
|
||||
featured: 'Представлено в',
|
||||
spotifyPlaylist: 'плейлист spotify | плейлисты spotify | плейлисты spotify',
|
||||
@ -40,6 +41,12 @@ const ru = {
|
||||
}
|
||||
},
|
||||
about: {
|
||||
updates: {
|
||||
currentVersion: 'Текущая версия',
|
||||
versionNotAvailable: 'Н/Д',
|
||||
updateAvailable: `Вы используете не последнюю доступную версию: {version}`,
|
||||
deemixVersion: 'Версия библиотеки deemix'
|
||||
},
|
||||
titles: {
|
||||
usefulLinks: 'Полезные ссылки',
|
||||
bugReports: 'Отчёты об ошибках',
|
||||
@ -97,7 +104,8 @@ const ru = {
|
||||
wrongBitrateNoAlternative: 'Данного трека нет в нужном битрейте. Альтернатив не найдено!',
|
||||
no360RA: 'Трек недоступен в формате Reality Audio 360.',
|
||||
notAvailable: "Трек недоступен на серверах Deezer!",
|
||||
notAvailableNoAlternative: "Трек недоступен на серверах Deezer. Альтернатив не найдено!"
|
||||
notAvailableNoAlternative: "Трек недоступен на серверах Deezer. Альтернатив не найдено!",
|
||||
noSpaceLeft: "На устройстве не осталось свободного места!"
|
||||
}
|
||||
},
|
||||
favorites: {
|
||||
@ -179,7 +187,8 @@ const ru = {
|
||||
question: 'Как узнать свой ARL?',
|
||||
update: 'Обновить ARL'
|
||||
},
|
||||
logout: 'Выйти'
|
||||
logout: 'Выйти',
|
||||
login: 'Войти через deezer.com'
|
||||
},
|
||||
appearance: {
|
||||
title: 'Внешний вид',
|
||||
@ -279,6 +288,7 @@ const ru = {
|
||||
replayGain: 'Replay Gain',
|
||||
label: 'Издатель',
|
||||
lyrics: 'Текст песни',
|
||||
syncedLyrics: 'Синхрон. текст песни',
|
||||
copyright: 'Права (копирайт)',
|
||||
composer: 'Композитор',
|
||||
involvedPeople: 'Вовлечённые люди'
|
||||
@ -331,7 +341,8 @@ const ru = {
|
||||
title: 'Настройки Spotify',
|
||||
clientID: 'Spotify clientID',
|
||||
clientSecret: 'Spotify Client Secret',
|
||||
username: 'Spotify username'
|
||||
username: 'Spotify username',
|
||||
question: 'Как включить функции Spotify?'
|
||||
},
|
||||
reset: 'По умолчанию',
|
||||
save: 'Сохранить',
|
||||
|
@ -1,33 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
|
||||
import TracklistTab from '@components/TracklistTab.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/tracklist/:id',
|
||||
component: TracklistTab
|
||||
},
|
||||
// 404
|
||||
{
|
||||
path: '*',
|
||||
component: TracklistTab
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
// linkActiveClass: 'open',
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
126
src/router.js
Normal file
126
src/router.js
Normal file
@ -0,0 +1,126 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import { socket } from '@/utils/socket'
|
||||
import EventBus from '@/utils/EventBus'
|
||||
|
||||
import ArtistTab from '@components/ArtistTab.vue'
|
||||
import TracklistTab from '@components/TracklistTab.vue'
|
||||
|
||||
import TheHomeTab from '@components/TheHomeTab.vue'
|
||||
import TheChartsTab from '@components/TheChartsTab.vue'
|
||||
import TheFavoritesTab from '@components/TheFavoritesTab.vue'
|
||||
import TheErrorsTab from '@components/TheErrorsTab.vue'
|
||||
import TheLinkAnalyzerTab from '@components/TheLinkAnalyzerTab.vue'
|
||||
import TheAboutTab from '@components/TheAboutTab.vue'
|
||||
import TheSettingsTab from '@components/TheSettingsTab.vue'
|
||||
import TheMainSearch from '@components/TheMainSearch.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: TheHomeTab,
|
||||
meta: {
|
||||
notKeepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/tracklist/:type/:id',
|
||||
name: 'Tracklist',
|
||||
component: TracklistTab
|
||||
},
|
||||
{
|
||||
path: '/artist/:id',
|
||||
name: 'Artist',
|
||||
component: ArtistTab
|
||||
},
|
||||
{
|
||||
path: '/charts',
|
||||
name: 'Charts',
|
||||
component: TheChartsTab,
|
||||
meta: {
|
||||
notKeepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/favorites',
|
||||
name: 'Favorites',
|
||||
component: TheFavoritesTab,
|
||||
meta: {
|
||||
notKeepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/errors',
|
||||
name: 'Errors',
|
||||
component: TheErrorsTab
|
||||
},
|
||||
{
|
||||
path: '/link-analyzer',
|
||||
name: 'Link Analyzer',
|
||||
component: TheLinkAnalyzerTab
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: TheAboutTab
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
component: TheSettingsTab
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
name: 'Search',
|
||||
component: TheMainSearch
|
||||
},
|
||||
// 404 client side
|
||||
{
|
||||
path: '*',
|
||||
component: TheHomeTab
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
// linkActiveClass: 'open',
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
let getTracklistParams = null
|
||||
|
||||
switch (to.name) {
|
||||
case 'Artist':
|
||||
getTracklistParams = {
|
||||
type: 'artist',
|
||||
id: to.params.id
|
||||
}
|
||||
break
|
||||
case 'Tracklist':
|
||||
getTracklistParams = {
|
||||
type: to.params.type,
|
||||
id: to.params.id
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (getTracklistParams) {
|
||||
socket.emit('getTracklist', getTracklistParams)
|
||||
}
|
||||
|
||||
EventBus.$emit('trackPreview:stopStackedTabsPreview')
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
19
src/store/index.js
Normal file
19
src/store/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import Vuex from 'vuex'
|
||||
import Vue from 'vue'
|
||||
|
||||
import about from '@/store/modules/about'
|
||||
import login from '@/store/modules/login'
|
||||
import errors from '@/store/modules/errors'
|
||||
|
||||
// Load Vuex
|
||||
Vue.use(Vuex)
|
||||
|
||||
// Create store
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
about,
|
||||
login,
|
||||
errors
|
||||
},
|
||||
strict: process.env.NODE_ENV !== 'production'
|
||||
})
|
41
src/store/modules/about.js
Normal file
41
src/store/modules/about.js
Normal file
@ -0,0 +1,41 @@
|
||||
const state = {
|
||||
currentCommit: null,
|
||||
latestCommit: null,
|
||||
updateAvailable: false,
|
||||
deemixVersion: null
|
||||
}
|
||||
|
||||
const actions = {
|
||||
setAboutInfo({ commit }, payload) {
|
||||
commit('SET_CURRENT_COMMIT', payload.currentCommit)
|
||||
commit('SET_LATEST_COMMIT', payload.latestCommit)
|
||||
commit('SET_UPDATE_AVAILABLE', payload.updateAvailable)
|
||||
commit('SET_DEEMIX_VERSION', payload.deemixVersion)
|
||||
}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getAboutInfo: state => state
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_CURRENT_COMMIT: (state, payload) => {
|
||||
state.currentCommit = payload
|
||||
},
|
||||
SET_LATEST_COMMIT: (state, payload) => {
|
||||
state.latestCommit = payload
|
||||
},
|
||||
SET_UPDATE_AVAILABLE: (state, payload) => {
|
||||
state.updateAvailable = payload
|
||||
},
|
||||
SET_DEEMIX_VERSION: (state, payload) => {
|
||||
state.deemixVersion = payload
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
31
src/store/modules/defaultSettings.js
Normal file
31
src/store/modules/defaultSettings.js
Normal file
@ -0,0 +1,31 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
const state = {}
|
||||
|
||||
const actions = {
|
||||
setDefaultSettings({ commit }, payload) {
|
||||
for (const settingName in payload) {
|
||||
if (!payload.hasOwnProperty(settingName)) return
|
||||
|
||||
const settingValue = payload[settingName]
|
||||
commit('SET_UNKNOWN_DEFAULT_SETTING', { settingName, settingValue })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getDefaultSettings: state => state
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_UNKNOWN_DEFAULT_SETTING(state, payload) {
|
||||
Vue.set(state, payload.settingName, payload.settingValue)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
actions,
|
||||
getters,
|
||||
mutations
|
||||
}
|
45
src/store/modules/errors.js
Normal file
45
src/store/modules/errors.js
Normal file
@ -0,0 +1,45 @@
|
||||
const state = {
|
||||
artist: '',
|
||||
bitrate: '',
|
||||
cover: '',
|
||||
downloaded: 0,
|
||||
errors: [],
|
||||
failed: 0,
|
||||
id: '',
|
||||
progress: 0,
|
||||
silent: true,
|
||||
size: 0,
|
||||
title: '',
|
||||
type: '',
|
||||
uuid: ''
|
||||
}
|
||||
|
||||
const actions = {
|
||||
setErrors({ commit }, payload) {
|
||||
commit('SET_ERRORS', payload)
|
||||
}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getErrors: state => state
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_ERRORS(state, payload) {
|
||||
// The payload has useless data for the GUI, so only the needed data is saved in the store
|
||||
for (const errorName in state) {
|
||||
if (state.hasOwnProperty(errorName)) {
|
||||
const error = payload[errorName]
|
||||
|
||||
state[errorName] = error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
84
src/store/modules/login.js
Normal file
84
src/store/modules/login.js
Normal file
@ -0,0 +1,84 @@
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
arl: localStorage.getItem('arl') || '',
|
||||
status: null,
|
||||
user: {
|
||||
id: null,
|
||||
name: '',
|
||||
picture: ''
|
||||
},
|
||||
clientMode: false
|
||||
}
|
||||
}
|
||||
|
||||
const state = getDefaultState()
|
||||
|
||||
const actions = {
|
||||
login({ commit, dispatch }, payload) {
|
||||
const { arl, user, status } = payload
|
||||
|
||||
dispatch('setARL', { arl })
|
||||
commit('SET_USER', user)
|
||||
commit('SET_STATUS', status)
|
||||
},
|
||||
logout({ commit }) {
|
||||
localStorage.removeItem('arl')
|
||||
|
||||
commit('RESET_LOGIN')
|
||||
},
|
||||
setARL({ commit }, payload) {
|
||||
let { arl, saveOnLocalStorage } = payload
|
||||
|
||||
saveOnLocalStorage = typeof saveOnLocalStorage === 'undefined' ? true : saveOnLocalStorage
|
||||
|
||||
commit('SET_ARL', arl)
|
||||
|
||||
if (saveOnLocalStorage) {
|
||||
localStorage.setItem('arl', arl)
|
||||
}
|
||||
},
|
||||
removeARL({ commit }) {
|
||||
commit('SET_ARL', '')
|
||||
|
||||
localStorage.removeItem('arl')
|
||||
},
|
||||
setUser({ commit }, payload) {
|
||||
commit('SET_USER', payload)
|
||||
},
|
||||
setClientMode({ commit }, payload) {
|
||||
commit('SET_CLIENT_MODE', payload)
|
||||
}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getARL: state => state.arl,
|
||||
getUser: state => state.user,
|
||||
getClientMode: state => state.clientMode,
|
||||
isLoggedIn: state => !!state.arl
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_ARL(state, payload) {
|
||||
state.arl = payload
|
||||
},
|
||||
SET_STATUS(state, payload) {
|
||||
state.status = payload
|
||||
},
|
||||
SET_USER(state, payload) {
|
||||
state.user = payload
|
||||
},
|
||||
SET_CLIENT_MODE(state, payload) {
|
||||
state.clientMode = payload
|
||||
},
|
||||
RESET_LOGIN(state) {
|
||||
// Needed for reactivity
|
||||
Object.assign(state, getDefaultState())
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
31
src/store/modules/spotifyCredentials.js
Normal file
31
src/store/modules/spotifyCredentials.js
Normal file
@ -0,0 +1,31 @@
|
||||
const state = {
|
||||
clientId: '',
|
||||
clientSecret: ''
|
||||
}
|
||||
|
||||
const actions = {
|
||||
setCredentials({ commit }, payload) {
|
||||
commit('SET_CLIENT_ID', payload.clientId)
|
||||
commit('SET_CLIENT_SECRET', payload.clientSecret)
|
||||
}
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getCredentials: state => state
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_CLIENT_ID(state, payload) {
|
||||
state.clientId = payload
|
||||
},
|
||||
SET_CLIENT_SECRET(state, payload) {
|
||||
state.clientSecret = payload
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
@ -1,11 +1,3 @@
|
||||
/* Middle section */
|
||||
#middle_section {
|
||||
background-color: var(--main-background);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 10px;
|
||||
}
|
||||
|
||||
/* Center section */
|
||||
$icon-dimension: 2rem;
|
||||
$searchbar-height: calc(2rem + 1em);
|
||||
@ -63,31 +55,6 @@ $searchbar-height: calc(2rem + 1em);
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
background-color: var(--main-background);
|
||||
// width: calc(100% - 10px);
|
||||
width: 100%;
|
||||
height: calc(100% - 93px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
// padding-left: 10px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--main-background);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--main-scroll);
|
||||
border-radius: 4px;
|
||||
width: 6px;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
--container-width: 95%;
|
||||
|
||||
@ -100,12 +67,6 @@ $searchbar-height: calc(2rem + 1em);
|
||||
}
|
||||
}
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
max-width: 1280px;
|
||||
width: var(--container-width);
|
||||
}
|
||||
|
||||
/* Modal Content */
|
||||
.smallmodal-content {
|
||||
--modal-content-width: 95%;
|
||||
|
@ -316,13 +316,6 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
#main_content {
|
||||
margin-left: $sidebar-width;
|
||||
width: calc(100% - #{$sidebar-width});
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
.inline-flex {
|
||||
display: flex;
|
||||
@ -341,3 +334,11 @@ a {
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.changing-theme {
|
||||
transition: all 200ms ease-in-out;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
cursor: pointer;
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 25px;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:not(.top_result_header) {
|
||||
transition: color 200ms ease-in-out;
|
||||
|
@ -3,14 +3,6 @@
|
||||
height: 125px;
|
||||
}
|
||||
|
||||
#logged_in_info {
|
||||
height: 250px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#log_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,8 +1,9 @@
|
||||
.search_tabcontent,
|
||||
.main_tabcontent,
|
||||
.favorites_tabcontent {
|
||||
display: none;
|
||||
}
|
||||
// .search_tabcontent
|
||||
// .main_tabcontent,
|
||||
// .favorites_tabcontent
|
||||
// {
|
||||
// display: none;
|
||||
// }
|
||||
|
||||
.main_tabcontent {
|
||||
h1 {
|
||||
|
28
src/utils/adjust-volume.js
Normal file
28
src/utils/adjust-volume.js
Normal file
@ -0,0 +1,28 @@
|
||||
// https://stackoverflow.com/questions/7451508/html5-audio-playback-with-fade-in-and-fade-out#answer-13149848
|
||||
export async function adjustVolume(element, newVolume, { duration = 1000, easing = swing, interval = 13 } = {}) {
|
||||
const originalVolume = element.volume
|
||||
const delta = newVolume - originalVolume
|
||||
|
||||
if (!delta || !duration || !easing || !interval) {
|
||||
element.volume = newVolume
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const ticks = Math.floor(duration / interval)
|
||||
let tick = 1
|
||||
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
element.volume = originalVolume + easing(tick / ticks) * delta
|
||||
// console.log(element.volume)
|
||||
if (++tick === ticks) {
|
||||
clearInterval(timer)
|
||||
resolve()
|
||||
}
|
||||
}, interval)
|
||||
})
|
||||
}
|
||||
|
||||
export function swing(p) {
|
||||
return 0.5 - Math.cos(p * Math.PI) / 2
|
||||
}
|
251
src/utils/countries.js
Normal file
251
src/utils/countries.js
Normal file
@ -0,0 +1,251 @@
|
||||
export const COUNTRIES = {
|
||||
AF: 'Afghanistan',
|
||||
AX: '\u00c5land Islands',
|
||||
AL: 'Albania',
|
||||
DZ: 'Algeria',
|
||||
AS: 'American Samoa',
|
||||
AD: 'Andorra',
|
||||
AO: 'Angola',
|
||||
AI: 'Anguilla',
|
||||
AQ: 'Antarctica',
|
||||
AG: 'Antigua and Barbuda',
|
||||
AR: 'Argentina',
|
||||
AM: 'Armenia',
|
||||
AW: 'Aruba',
|
||||
AU: 'Australia',
|
||||
AT: 'Austria',
|
||||
AZ: 'Azerbaijan',
|
||||
BS: 'Bahamas',
|
||||
BH: 'Bahrain',
|
||||
BD: 'Bangladesh',
|
||||
BB: 'Barbados',
|
||||
BY: 'Belarus',
|
||||
BE: 'Belgium',
|
||||
BZ: 'Belize',
|
||||
BJ: 'Benin',
|
||||
BM: 'Bermuda',
|
||||
BT: 'Bhutan',
|
||||
BO: 'Bolivia, Plurinational State of',
|
||||
BQ: 'Bonaire, Sint Eustatius and Saba',
|
||||
BA: 'Bosnia and Herzegovina',
|
||||
BW: 'Botswana',
|
||||
BV: 'Bouvet Island',
|
||||
BR: 'Brazil',
|
||||
IO: 'British Indian Ocean Territory',
|
||||
BN: 'Brunei Darussalam',
|
||||
BG: 'Bulgaria',
|
||||
BF: 'Burkina Faso',
|
||||
BI: 'Burundi',
|
||||
KH: 'Cambodia',
|
||||
CM: 'Cameroon',
|
||||
CA: 'Canada',
|
||||
CV: 'Cape Verde',
|
||||
KY: 'Cayman Islands',
|
||||
CF: 'Central African Republic',
|
||||
TD: 'Chad',
|
||||
CL: 'Chile',
|
||||
CN: 'China',
|
||||
CX: 'Christmas Island',
|
||||
CC: 'Cocos (Keeling) Islands',
|
||||
CO: 'Colombia',
|
||||
KM: 'Comoros',
|
||||
CG: 'Congo',
|
||||
CD: 'Congo, the Democratic Republic of the',
|
||||
CK: 'Cook Islands',
|
||||
CR: 'Costa Rica',
|
||||
CI: "C\u00f4te d'Ivoire",
|
||||
HR: 'Croatia',
|
||||
CU: 'Cuba',
|
||||
CW: 'Cura\u00e7ao',
|
||||
CY: 'Cyprus',
|
||||
CZ: 'Czech Republic',
|
||||
DK: 'Denmark',
|
||||
DJ: 'Djibouti',
|
||||
DM: 'Dominica',
|
||||
DO: 'Dominican Republic',
|
||||
EC: 'Ecuador',
|
||||
EG: 'Egypt',
|
||||
SV: 'El Salvador',
|
||||
GQ: 'Equatorial Guinea',
|
||||
ER: 'Eritrea',
|
||||
EE: 'Estonia',
|
||||
ET: 'Ethiopia',
|
||||
FK: 'Falkland Islands (Malvinas)',
|
||||
FO: 'Faroe Islands',
|
||||
FJ: 'Fiji',
|
||||
FI: 'Finland',
|
||||
FR: 'France',
|
||||
GF: 'French Guiana',
|
||||
PF: 'French Polynesia',
|
||||
TF: 'French Southern Territories',
|
||||
GA: 'Gabon',
|
||||
GM: 'Gambia',
|
||||
GE: 'Georgia',
|
||||
DE: 'Germany',
|
||||
GH: 'Ghana',
|
||||
GI: 'Gibraltar',
|
||||
GR: 'Greece',
|
||||
GL: 'Greenland',
|
||||
GD: 'Grenada',
|
||||
GP: 'Guadeloupe',
|
||||
GU: 'Guam',
|
||||
GT: 'Guatemala',
|
||||
GG: 'Guernsey',
|
||||
GN: 'Guinea',
|
||||
GW: 'Guinea-Bissau',
|
||||
GY: 'Guyana',
|
||||
HT: 'Haiti',
|
||||
HM: 'Heard Island and McDonald Islands',
|
||||
VA: 'Holy See (Vatican City State)',
|
||||
HN: 'Honduras',
|
||||
HK: 'Hong Kong',
|
||||
HU: 'Hungary',
|
||||
IS: 'Iceland',
|
||||
IN: 'India',
|
||||
ID: 'Indonesia',
|
||||
IR: 'Iran, Islamic Republic of',
|
||||
IQ: 'Iraq',
|
||||
IE: 'Ireland',
|
||||
IM: 'Isle of Man',
|
||||
IL: 'Israel',
|
||||
IT: 'Italy',
|
||||
JM: 'Jamaica',
|
||||
JP: 'Japan',
|
||||
JE: 'Jersey',
|
||||
JO: 'Jordan',
|
||||
KZ: 'Kazakhstan',
|
||||
KE: 'Kenya',
|
||||
KI: 'Kiribati',
|
||||
KP: "Korea, Democratic People's Republic of",
|
||||
KR: 'Korea, Republic of',
|
||||
KW: 'Kuwait',
|
||||
KG: 'Kyrgyzstan',
|
||||
LA: "Lao People's Democratic Republic",
|
||||
LV: 'Latvia',
|
||||
LB: 'Lebanon',
|
||||
LS: 'Lesotho',
|
||||
LR: 'Liberia',
|
||||
LY: 'Libya',
|
||||
LI: 'Liechtenstein',
|
||||
LT: 'Lithuania',
|
||||
LU: 'Luxembourg',
|
||||
MO: 'Macao',
|
||||
MK: 'Macedonia, the Former Yugoslav Republic of',
|
||||
MG: 'Madagascar',
|
||||
MW: 'Malawi',
|
||||
MY: 'Malaysia',
|
||||
MV: 'Maldives',
|
||||
ML: 'Mali',
|
||||
MT: 'Malta',
|
||||
MH: 'Marshall Islands',
|
||||
MQ: 'Martinique',
|
||||
MR: 'Mauritania',
|
||||
MU: 'Mauritius',
|
||||
YT: 'Mayotte',
|
||||
MX: 'Mexico',
|
||||
FM: 'Micronesia, Federated States of',
|
||||
MD: 'Moldova, Republic of',
|
||||
MC: 'Monaco',
|
||||
MN: 'Mongolia',
|
||||
ME: 'Montenegro',
|
||||
MS: 'Montserrat',
|
||||
MA: 'Morocco',
|
||||
MZ: 'Mozambique',
|
||||
MM: 'Myanmar',
|
||||
NA: 'Namibia',
|
||||
NR: 'Nauru',
|
||||
NP: 'Nepal',
|
||||
NL: 'Netherlands',
|
||||
NC: 'New Caledonia',
|
||||
NZ: 'New Zealand',
|
||||
NI: 'Nicaragua',
|
||||
NE: 'Niger',
|
||||
NG: 'Nigeria',
|
||||
NU: 'Niue',
|
||||
NF: 'Norfolk Island',
|
||||
MP: 'Northern Mariana Islands',
|
||||
NO: 'Norway',
|
||||
OM: 'Oman',
|
||||
PK: 'Pakistan',
|
||||
PW: 'Palau',
|
||||
PS: 'Palestine, State of',
|
||||
PA: 'Panama',
|
||||
PG: 'Papua New Guinea',
|
||||
PY: 'Paraguay',
|
||||
PE: 'Peru',
|
||||
PH: 'Philippines',
|
||||
PN: 'Pitcairn',
|
||||
PL: 'Poland',
|
||||
PT: 'Portugal',
|
||||
PR: 'Puerto Rico',
|
||||
QA: 'Qatar',
|
||||
RE: 'R\u00e9union',
|
||||
RO: 'Romania',
|
||||
RU: 'Russian Federation',
|
||||
RW: 'Rwanda',
|
||||
BL: 'Saint Barth\u00e9lemy',
|
||||
SH: 'Saint Helena, Ascension and Tristan da Cunha',
|
||||
KN: 'Saint Kitts and Nevis',
|
||||
LC: 'Saint Lucia',
|
||||
MF: 'Saint Martin (French part)',
|
||||
PM: 'Saint Pierre and Miquelon',
|
||||
VC: 'Saint Vincent and the Grenadines',
|
||||
WS: 'Samoa',
|
||||
SM: 'San Marino',
|
||||
ST: 'Sao Tome and Principe',
|
||||
SA: 'Saudi Arabia',
|
||||
SN: 'Senegal',
|
||||
RS: 'Serbia',
|
||||
SC: 'Seychelles',
|
||||
SL: 'Sierra Leone',
|
||||
SG: 'Singapore',
|
||||
SX: 'Sint Maarten (Dutch part)',
|
||||
SK: 'Slovakia',
|
||||
SI: 'Slovenia',
|
||||
SB: 'Solomon Islands',
|
||||
SO: 'Somalia',
|
||||
ZA: 'South Africa',
|
||||
GS: 'South Georgia and the South Sandwich Islands',
|
||||
SS: 'South Sudan',
|
||||
ES: 'Spain',
|
||||
LK: 'Sri Lanka',
|
||||
SD: 'Sudan',
|
||||
SR: 'Suriname',
|
||||
SJ: 'Svalbard and Jan Mayen',
|
||||
SZ: 'Swaziland',
|
||||
SE: 'Sweden',
|
||||
CH: 'Switzerland',
|
||||
SY: 'Syrian Arab Republic',
|
||||
TW: 'Taiwan, Province of China',
|
||||
TJ: 'Tajikistan',
|
||||
TZ: 'Tanzania, United Republic of',
|
||||
TH: 'Thailand',
|
||||
TL: 'Timor-Leste',
|
||||
TG: 'Togo',
|
||||
TK: 'Tokelau',
|
||||
TO: 'Tonga',
|
||||
TT: 'Trinidad and Tobago',
|
||||
TN: 'Tunisia',
|
||||
TR: 'Turkey',
|
||||
TM: 'Turkmenistan',
|
||||
TC: 'Turks and Caicos Islands',
|
||||
TV: 'Tuvalu',
|
||||
UG: 'Uganda',
|
||||
UA: 'Ukraine',
|
||||
AE: 'United Arab Emirates',
|
||||
GB: 'United Kingdom',
|
||||
US: 'United States',
|
||||
UM: 'United States Minor Outlying Islands',
|
||||
UY: 'Uruguay',
|
||||
UZ: 'Uzbekistan',
|
||||
VU: 'Vanuatu',
|
||||
VE: 'Venezuela, Bolivarian Republic of',
|
||||
VN: 'Viet Nam',
|
||||
VG: 'Virgin Islands, British',
|
||||
VI: 'Virgin Islands, U.S.',
|
||||
WF: 'Wallis and Futuna',
|
||||
EH: 'Western Sahara',
|
||||
YE: 'Yemen',
|
||||
ZM: 'Zambia',
|
||||
ZW: 'Zimbabwe'
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
function sendAddToQueue(url, bitrate = null) {
|
||||
export function sendAddToQueue(url, bitrate = null) {
|
||||
if (!url) return
|
||||
|
||||
socket.emit('addToQueue', { url, bitrate }, () => {})
|
||||
|
@ -1,5 +1,11 @@
|
||||
import store from '@/store'
|
||||
|
||||
export const socket = io.connect(window.location.href)
|
||||
|
||||
socket.on('connect', () => {
|
||||
document.getElementById('start_app_placeholder').classList.add('loading_placeholder--hidden')
|
||||
})
|
||||
|
||||
socket.on('init_update', data => {
|
||||
store.dispatch('setAboutInfo', data)
|
||||
})
|
||||
|
@ -1,5 +1,4 @@
|
||||
import Toastify from 'toastify-js'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { socket } from '@/utils/socket'
|
||||
|
||||
@ -8,49 +7,83 @@ let toastsWithId = {}
|
||||
export const toast = function(msg, icon = null, dismiss = true, id = null) {
|
||||
if (toastsWithId[id]) {
|
||||
let toastObj = toastsWithId[id]
|
||||
let toastDOM = $(`div.toastify[toast_id=${id}]`)
|
||||
|
||||
let toastElement = document.querySelectorAll(`div.toastify[toast_id=${id}]`)
|
||||
|
||||
if (msg) {
|
||||
toastDOM.find('.toast-message').html(msg)
|
||||
toastElement.forEach(toast => {
|
||||
const messages = toast.querySelectorAll('.toast-message')
|
||||
|
||||
messages.forEach(message => {
|
||||
message.innerHTML = msg
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
if (icon == 'loading') icon = `<div class="circle-loader"></div>`
|
||||
else icon = `<i class="material-icons">${icon}</i>`
|
||||
toastDOM.find('.toast-icon').html(icon)
|
||||
if (icon == 'loading') {
|
||||
icon = `<div class="circle-loader"></div>`
|
||||
} else {
|
||||
icon = `<i class="material-icons">${icon}</i>`
|
||||
}
|
||||
|
||||
toastElement.forEach(toast => {
|
||||
const icons = toast.querySelectorAll('.toast-icon')
|
||||
|
||||
icons.forEach(toastIcon => {
|
||||
toastIcon.innerHTML = icon
|
||||
})
|
||||
})
|
||||
}
|
||||
if (dismiss !== null && dismiss) {
|
||||
toastDOM.addClass('dismissable')
|
||||
setTimeout(function() {
|
||||
toastElement.forEach(toast => {
|
||||
toast.classList.add('dismissable')
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
toastObj.hideToast()
|
||||
|
||||
delete toastsWithId[id]
|
||||
}, 3000)
|
||||
}
|
||||
} else {
|
||||
if (icon == null) icon = ''
|
||||
else if (icon == 'loading') icon = `<div class="circle-loader"></div>`
|
||||
else icon = `<i class="material-icons">${icon}</i>`
|
||||
if (icon == null) {
|
||||
icon = ''
|
||||
} else if (icon == 'loading') {
|
||||
icon = `<div class="circle-loader"></div>`
|
||||
} else {
|
||||
icon = `<i class="material-icons">${icon}</i>`
|
||||
}
|
||||
|
||||
let toastObj = Toastify({
|
||||
text: `<span class="toast-icon">${icon}</span><span class="toast-message">${msg}</toast>`,
|
||||
duration: dismiss ? 3000 : 0,
|
||||
gravity: 'bottom',
|
||||
position: 'left',
|
||||
className: dismiss ? 'dismissable' : '',
|
||||
onClick: function(){
|
||||
onClick: function() {
|
||||
let dismissable = true
|
||||
if (id){
|
||||
|
||||
if (id) {
|
||||
let toastClasses = document.querySelector(`div.toastify[toast_id=${id}]`).classList
|
||||
if (toastClasses){
|
||||
dismissable = Array.from(toastClasses).indexOf('dismissable') != -1
|
||||
|
||||
if (toastClasses) {
|
||||
dismissable = Array.prototype.slice.call(toastClasses).indexOf('dismissable') != -1
|
||||
}
|
||||
}
|
||||
if (toastObj && dismissable) {
|
||||
toastObj.hideToast()
|
||||
if (id) delete toastsWithId[id]
|
||||
|
||||
if (id) {
|
||||
delete toastsWithId[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
}).showToast()
|
||||
if (id) {
|
||||
toastsWithId[id] = toastObj
|
||||
$(toastObj.toastElement).attr('toast_id', id)
|
||||
|
||||
toastObj.toastElement.setAttribute('toast_id', id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,11 @@ export function isValidURL(text) {
|
||||
let lowerCaseText = text.toLowerCase()
|
||||
|
||||
if (lowerCaseText.startsWith('http')) {
|
||||
if (lowerCaseText.indexOf('deezer.com') >= 0 || lowerCaseText.indexOf('open.spotify.com') >= 0) {
|
||||
if (
|
||||
lowerCaseText.indexOf('deezer.com') >= 0 ||
|
||||
lowerCaseText.indexOf('deezer.page.link') >= 0 ||
|
||||
lowerCaseText.indexOf('open.spotify.com') >= 0
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} else if (lowerCaseText.startsWith('spotify:')) {
|
||||
@ -91,263 +95,10 @@ export function copyToClipboard(text) {
|
||||
ghostInput.remove()
|
||||
}
|
||||
|
||||
export const COUNTRIES = {
|
||||
AF: 'Afghanistan',
|
||||
AX: '\u00c5land Islands',
|
||||
AL: 'Albania',
|
||||
DZ: 'Algeria',
|
||||
AS: 'American Samoa',
|
||||
AD: 'Andorra',
|
||||
AO: 'Angola',
|
||||
AI: 'Anguilla',
|
||||
AQ: 'Antarctica',
|
||||
AG: 'Antigua and Barbuda',
|
||||
AR: 'Argentina',
|
||||
AM: 'Armenia',
|
||||
AW: 'Aruba',
|
||||
AU: 'Australia',
|
||||
AT: 'Austria',
|
||||
AZ: 'Azerbaijan',
|
||||
BS: 'Bahamas',
|
||||
BH: 'Bahrain',
|
||||
BD: 'Bangladesh',
|
||||
BB: 'Barbados',
|
||||
BY: 'Belarus',
|
||||
BE: 'Belgium',
|
||||
BZ: 'Belize',
|
||||
BJ: 'Benin',
|
||||
BM: 'Bermuda',
|
||||
BT: 'Bhutan',
|
||||
BO: 'Bolivia, Plurinational State of',
|
||||
BQ: 'Bonaire, Sint Eustatius and Saba',
|
||||
BA: 'Bosnia and Herzegovina',
|
||||
BW: 'Botswana',
|
||||
BV: 'Bouvet Island',
|
||||
BR: 'Brazil',
|
||||
IO: 'British Indian Ocean Territory',
|
||||
BN: 'Brunei Darussalam',
|
||||
BG: 'Bulgaria',
|
||||
BF: 'Burkina Faso',
|
||||
BI: 'Burundi',
|
||||
KH: 'Cambodia',
|
||||
CM: 'Cameroon',
|
||||
CA: 'Canada',
|
||||
CV: 'Cape Verde',
|
||||
KY: 'Cayman Islands',
|
||||
CF: 'Central African Republic',
|
||||
TD: 'Chad',
|
||||
CL: 'Chile',
|
||||
CN: 'China',
|
||||
CX: 'Christmas Island',
|
||||
CC: 'Cocos (Keeling) Islands',
|
||||
CO: 'Colombia',
|
||||
KM: 'Comoros',
|
||||
CG: 'Congo',
|
||||
CD: 'Congo, the Democratic Republic of the',
|
||||
CK: 'Cook Islands',
|
||||
CR: 'Costa Rica',
|
||||
CI: "C\u00f4te d'Ivoire",
|
||||
HR: 'Croatia',
|
||||
CU: 'Cuba',
|
||||
CW: 'Cura\u00e7ao',
|
||||
CY: 'Cyprus',
|
||||
CZ: 'Czech Republic',
|
||||
DK: 'Denmark',
|
||||
DJ: 'Djibouti',
|
||||
DM: 'Dominica',
|
||||
DO: 'Dominican Republic',
|
||||
EC: 'Ecuador',
|
||||
EG: 'Egypt',
|
||||
SV: 'El Salvador',
|
||||
GQ: 'Equatorial Guinea',
|
||||
ER: 'Eritrea',
|
||||
EE: 'Estonia',
|
||||
ET: 'Ethiopia',
|
||||
FK: 'Falkland Islands (Malvinas)',
|
||||
FO: 'Faroe Islands',
|
||||
FJ: 'Fiji',
|
||||
FI: 'Finland',
|
||||
FR: 'France',
|
||||
GF: 'French Guiana',
|
||||
PF: 'French Polynesia',
|
||||
TF: 'French Southern Territories',
|
||||
GA: 'Gabon',
|
||||
GM: 'Gambia',
|
||||
GE: 'Georgia',
|
||||
DE: 'Germany',
|
||||
GH: 'Ghana',
|
||||
GI: 'Gibraltar',
|
||||
GR: 'Greece',
|
||||
GL: 'Greenland',
|
||||
GD: 'Grenada',
|
||||
GP: 'Guadeloupe',
|
||||
GU: 'Guam',
|
||||
GT: 'Guatemala',
|
||||
GG: 'Guernsey',
|
||||
GN: 'Guinea',
|
||||
GW: 'Guinea-Bissau',
|
||||
GY: 'Guyana',
|
||||
HT: 'Haiti',
|
||||
HM: 'Heard Island and McDonald Islands',
|
||||
VA: 'Holy See (Vatican City State)',
|
||||
HN: 'Honduras',
|
||||
HK: 'Hong Kong',
|
||||
HU: 'Hungary',
|
||||
IS: 'Iceland',
|
||||
IN: 'India',
|
||||
ID: 'Indonesia',
|
||||
IR: 'Iran, Islamic Republic of',
|
||||
IQ: 'Iraq',
|
||||
IE: 'Ireland',
|
||||
IM: 'Isle of Man',
|
||||
IL: 'Israel',
|
||||
IT: 'Italy',
|
||||
JM: 'Jamaica',
|
||||
JP: 'Japan',
|
||||
JE: 'Jersey',
|
||||
JO: 'Jordan',
|
||||
KZ: 'Kazakhstan',
|
||||
KE: 'Kenya',
|
||||
KI: 'Kiribati',
|
||||
KP: "Korea, Democratic People's Republic of",
|
||||
KR: 'Korea, Republic of',
|
||||
KW: 'Kuwait',
|
||||
KG: 'Kyrgyzstan',
|
||||
LA: "Lao People's Democratic Republic",
|
||||
LV: 'Latvia',
|
||||
LB: 'Lebanon',
|
||||
LS: 'Lesotho',
|
||||
LR: 'Liberia',
|
||||
LY: 'Libya',
|
||||
LI: 'Liechtenstein',
|
||||
LT: 'Lithuania',
|
||||
LU: 'Luxembourg',
|
||||
MO: 'Macao',
|
||||
MK: 'Macedonia, the Former Yugoslav Republic of',
|
||||
MG: 'Madagascar',
|
||||
MW: 'Malawi',
|
||||
MY: 'Malaysia',
|
||||
MV: 'Maldives',
|
||||
ML: 'Mali',
|
||||
MT: 'Malta',
|
||||
MH: 'Marshall Islands',
|
||||
MQ: 'Martinique',
|
||||
MR: 'Mauritania',
|
||||
MU: 'Mauritius',
|
||||
YT: 'Mayotte',
|
||||
MX: 'Mexico',
|
||||
FM: 'Micronesia, Federated States of',
|
||||
MD: 'Moldova, Republic of',
|
||||
MC: 'Monaco',
|
||||
MN: 'Mongolia',
|
||||
ME: 'Montenegro',
|
||||
MS: 'Montserrat',
|
||||
MA: 'Morocco',
|
||||
MZ: 'Mozambique',
|
||||
MM: 'Myanmar',
|
||||
NA: 'Namibia',
|
||||
NR: 'Nauru',
|
||||
NP: 'Nepal',
|
||||
NL: 'Netherlands',
|
||||
NC: 'New Caledonia',
|
||||
NZ: 'New Zealand',
|
||||
NI: 'Nicaragua',
|
||||
NE: 'Niger',
|
||||
NG: 'Nigeria',
|
||||
NU: 'Niue',
|
||||
NF: 'Norfolk Island',
|
||||
MP: 'Northern Mariana Islands',
|
||||
NO: 'Norway',
|
||||
OM: 'Oman',
|
||||
PK: 'Pakistan',
|
||||
PW: 'Palau',
|
||||
PS: 'Palestine, State of',
|
||||
PA: 'Panama',
|
||||
PG: 'Papua New Guinea',
|
||||
PY: 'Paraguay',
|
||||
PE: 'Peru',
|
||||
PH: 'Philippines',
|
||||
PN: 'Pitcairn',
|
||||
PL: 'Poland',
|
||||
PT: 'Portugal',
|
||||
PR: 'Puerto Rico',
|
||||
QA: 'Qatar',
|
||||
RE: 'R\u00e9union',
|
||||
RO: 'Romania',
|
||||
RU: 'Russian Federation',
|
||||
RW: 'Rwanda',
|
||||
BL: 'Saint Barth\u00e9lemy',
|
||||
SH: 'Saint Helena, Ascension and Tristan da Cunha',
|
||||
KN: 'Saint Kitts and Nevis',
|
||||
LC: 'Saint Lucia',
|
||||
MF: 'Saint Martin (French part)',
|
||||
PM: 'Saint Pierre and Miquelon',
|
||||
VC: 'Saint Vincent and the Grenadines',
|
||||
WS: 'Samoa',
|
||||
SM: 'San Marino',
|
||||
ST: 'Sao Tome and Principe',
|
||||
SA: 'Saudi Arabia',
|
||||
SN: 'Senegal',
|
||||
RS: 'Serbia',
|
||||
SC: 'Seychelles',
|
||||
SL: 'Sierra Leone',
|
||||
SG: 'Singapore',
|
||||
SX: 'Sint Maarten (Dutch part)',
|
||||
SK: 'Slovakia',
|
||||
SI: 'Slovenia',
|
||||
SB: 'Solomon Islands',
|
||||
SO: 'Somalia',
|
||||
ZA: 'South Africa',
|
||||
GS: 'South Georgia and the South Sandwich Islands',
|
||||
SS: 'South Sudan',
|
||||
ES: 'Spain',
|
||||
LK: 'Sri Lanka',
|
||||
SD: 'Sudan',
|
||||
SR: 'Suriname',
|
||||
SJ: 'Svalbard and Jan Mayen',
|
||||
SZ: 'Swaziland',
|
||||
SE: 'Sweden',
|
||||
CH: 'Switzerland',
|
||||
SY: 'Syrian Arab Republic',
|
||||
TW: 'Taiwan, Province of China',
|
||||
TJ: 'Tajikistan',
|
||||
TZ: 'Tanzania, United Republic of',
|
||||
TH: 'Thailand',
|
||||
TL: 'Timor-Leste',
|
||||
TG: 'Togo',
|
||||
TK: 'Tokelau',
|
||||
TO: 'Tonga',
|
||||
TT: 'Trinidad and Tobago',
|
||||
TN: 'Tunisia',
|
||||
TR: 'Turkey',
|
||||
TM: 'Turkmenistan',
|
||||
TC: 'Turks and Caicos Islands',
|
||||
TV: 'Tuvalu',
|
||||
UG: 'Uganda',
|
||||
UA: 'Ukraine',
|
||||
AE: 'United Arab Emirates',
|
||||
GB: 'United Kingdom',
|
||||
US: 'United States',
|
||||
UM: 'United States Minor Outlying Islands',
|
||||
UY: 'Uruguay',
|
||||
UZ: 'Uzbekistan',
|
||||
VU: 'Vanuatu',
|
||||
VE: 'Venezuela, Bolivarian Republic of',
|
||||
VN: 'Viet Nam',
|
||||
VG: 'Virgin Islands, British',
|
||||
VI: 'Virgin Islands, U.S.',
|
||||
WF: 'Wallis and Futuna',
|
||||
EH: 'Western Sahara',
|
||||
YE: 'Yemen',
|
||||
ZM: 'Zambia',
|
||||
ZW: 'Zimbabwe'
|
||||
}
|
||||
|
||||
export default {
|
||||
isValidURL,
|
||||
convertDuration,
|
||||
convertDurationSeparated,
|
||||
numberWithDots,
|
||||
debounce,
|
||||
COUNTRIES
|
||||
debounce
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user