[web] Change navigation bars

This commit is contained in:
Alain Nussbaumer 2024-09-09 21:00:35 +02:00
parent e0a2ab159e
commit 3d9cec4ded
21 changed files with 722 additions and 882 deletions

View File

@ -12,8 +12,7 @@
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
"@ts-pro/vue-eternal-loading": "^1.3.1", "@ts-pro/vue-eternal-loading": "^1.3.1",
"axios": "^1.7.4", "axios": "^1.7.4",
"bulma": "^0.9.4", "bulma": "^1.0.2",
"bulma-switch": "^2.0.4",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"mdi-vue": "^3.0.13", "mdi-vue": "^3.0.13",
"pinia": "^2.1.7", "pinia": "^2.1.7",
@ -66,12 +65,12 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.25.4", "version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
"integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.25.4" "@babel/types": "^7.25.6"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -81,9 +80,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.25.4", "version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
"integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.24.8", "@babel/helper-string-parser": "^7.24.8",
@ -525,9 +524,9 @@
} }
}, },
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.17.1", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
"integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -564,9 +563,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.9.0", "version": "9.10.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
"integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==", "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -583,6 +582,19 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/plugin-kit": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@humanwhocodes/module-importer": { "node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -798,9 +810,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz",
"integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==", "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -812,9 +824,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz",
"integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==", "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -826,9 +838,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz",
"integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==", "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -840,9 +852,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz",
"integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==", "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -854,9 +866,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz",
"integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==", "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -868,9 +880,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz",
"integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==", "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -882,9 +894,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz",
"integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==", "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -896,9 +908,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz",
"integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==", "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -910,9 +922,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz",
"integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==", "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -924,9 +936,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz",
"integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==", "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -938,9 +950,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz",
"integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==", "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -952,9 +964,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz",
"integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==", "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -966,9 +978,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz",
"integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==", "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -980,9 +992,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz",
"integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==", "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -994,9 +1006,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz",
"integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==", "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -1008,9 +1020,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz",
"integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==", "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1038,9 +1050,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "5.1.2", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.3.tgz",
"integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==", "integrity": "sha512-3xbWsKEKXYlmX82aOHufFQVnkbMC/v8fLpWwh6hWOUrK5fbbtBh9Q/WWse27BFgSy2/e2c0fz5Scgya9h2GLhw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1052,53 +1064,53 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.3.tgz",
"integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==", "integrity": "sha512-adAfy9boPkP233NTyvLbGEqVuIfK/R0ZsBsIOW4BZNfb4BRpRW41Do1u+ozJpsb+mdoy80O20IzAsHaihRb5qA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.24.7", "@babel/parser": "^7.25.3",
"@vue/shared": "3.4.38", "@vue/shared": "3.5.3",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.0" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.3.tgz",
"integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==", "integrity": "sha512-wnzFArg9zpvk/811CDOZOadJRugf1Bgl/TQ3RfV4nKfSPok4hi0w10ziYUQR6LnnBAUlEXYLUfZ71Oj9ds/+QA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.4.38", "@vue/compiler-core": "3.5.3",
"@vue/shared": "3.4.38" "@vue/shared": "3.5.3"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.3.tgz",
"integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==", "integrity": "sha512-P3uATLny2tfyvMB04OQFe7Sczteno7SLFxwrOA/dw01pBWQHB5HL15a8PosoNX2aG/EAMGqnXTu+1LnmzFhpTQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.24.7", "@babel/parser": "^7.25.3",
"@vue/compiler-core": "3.4.38", "@vue/compiler-core": "3.5.3",
"@vue/compiler-dom": "3.4.38", "@vue/compiler-dom": "3.5.3",
"@vue/compiler-ssr": "3.4.38", "@vue/compiler-ssr": "3.5.3",
"@vue/shared": "3.4.38", "@vue/shared": "3.5.3",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.10", "magic-string": "^0.30.11",
"postcss": "^8.4.40", "postcss": "^8.4.44",
"source-map-js": "^1.2.0" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.3.tgz",
"integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==", "integrity": "sha512-F/5f+r2WzL/2YAPl7UlKcJWHrvoZN8XwEBLnT7S4BXwncH25iDOabhO2M2DWioyTguJAGavDOawejkFXj8EM1w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.38", "@vue/compiler-dom": "3.5.3",
"@vue/shared": "3.4.38" "@vue/shared": "3.5.3"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@ -1108,53 +1120,53 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.3.tgz",
"integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==", "integrity": "sha512-2w61UnRWTP7+rj1H/j6FH706gRBHdFVpIqEkSDAyIpafBXYH8xt4gttstbbCWdU3OlcSWO8/3mbKl/93/HSMpw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/shared": "3.4.38" "@vue/shared": "3.5.3"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.3.tgz",
"integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==", "integrity": "sha512-5b2AQw5OZlmCzSsSBWYoZOsy75N4UdMWenTfDdI5bAzXnuVR7iR8Q4AOzQm2OGoA41xjk53VQKrqQhOz2ktWaw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.4.38", "@vue/reactivity": "3.5.3",
"@vue/shared": "3.4.38" "@vue/shared": "3.5.3"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.3.tgz",
"integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==", "integrity": "sha512-wPR1DEGc3XnQ7yHbmkTt3GoY0cEnVGQnARRdAkDzZ8MbUKEs26gogCQo6AOvvgahfjIcnvWJzkZArQ1fmWjcSg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.4.38", "@vue/reactivity": "3.5.3",
"@vue/runtime-core": "3.4.38", "@vue/runtime-core": "3.5.3",
"@vue/shared": "3.4.38", "@vue/shared": "3.5.3",
"csstype": "^3.1.3" "csstype": "^3.1.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.3.tgz",
"integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==", "integrity": "sha512-28volmaZVG2PGO3V3+gBPKoSHvLlE8FGfG/GKXKkjjfxLuj/50B/0OQGakM/g6ehQeqCrZYM4eHC4Ks48eig1Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.4.38", "@vue/compiler-ssr": "3.5.3",
"@vue/shared": "3.4.38" "@vue/shared": "3.5.3"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.4.38" "vue": "3.5.3"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.3.tgz",
"integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==", "integrity": "sha512-Jp2v8nylKBT+PlOUjun2Wp/f++TfJVFjshLzNtJDdmFJabJa7noGMncqXRM1vXGX+Yo2V7WykQFNxusSim8SCA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/acorn": { "node_modules/acorn": {
@ -1251,9 +1263,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.4", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
@ -1313,15 +1325,9 @@
} }
}, },
"node_modules/bulma": { "node_modules/bulma": {
"version": "0.9.4", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz", "resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.2.tgz",
"integrity": "sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ==", "integrity": "sha512-D7GnDuF6seb6HkcnRMM9E739QpEY9chDzzeFrHMyEns/EXyDJuQ0XA0KxbBl/B2NTsKSoDomW61jFGFaAxhK5A==",
"license": "MIT"
},
"node_modules/bulma-switch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.4.tgz",
"integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/callsites": { "node_modules/callsites": {
@ -1481,13 +1487,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.6", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -1601,17 +1607,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.9.0", "version": "9.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
"integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==", "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0", "@eslint-community/regexpp": "^4.11.0",
"@eslint/config-array": "^0.17.1", "@eslint/config-array": "^0.18.0",
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.9.0", "@eslint/js": "9.10.0",
"@eslint/plugin-kit": "^0.1.0",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0", "@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@ -1634,7 +1641,6 @@
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"is-path-inside": "^3.0.3", "is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -1674,9 +1680,9 @@
} }
}, },
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "9.27.0", "version": "9.28.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz",
"integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==", "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1685,7 +1691,7 @@
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"nth-check": "^2.1.1", "nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15", "postcss-selector-parser": "^6.0.15",
"semver": "^7.6.0", "semver": "^7.6.3",
"vue-eslint-parser": "^9.4.3", "vue-eslint-parser": "^9.4.3",
"xml-name-validator": "^4.0.0" "xml-name-validator": "^4.0.0"
}, },
@ -1952,9 +1958,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.6", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -2336,9 +2342,9 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.7", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2397,9 +2403,9 @@
} }
}, },
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -2542,9 +2548,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.1", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@ -2613,9 +2619,9 @@
} }
}, },
"node_modules/pkg-types": { "node_modules/pkg-types": {
"version": "1.1.3", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz",
"integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2625,9 +2631,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.41", "version": "8.4.45",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -2770,9 +2776,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.21.0", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",
"integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==", "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2786,22 +2792,22 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.21.0", "@rollup/rollup-android-arm-eabi": "4.21.2",
"@rollup/rollup-android-arm64": "4.21.0", "@rollup/rollup-android-arm64": "4.21.2",
"@rollup/rollup-darwin-arm64": "4.21.0", "@rollup/rollup-darwin-arm64": "4.21.2",
"@rollup/rollup-darwin-x64": "4.21.0", "@rollup/rollup-darwin-x64": "4.21.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.21.0", "@rollup/rollup-linux-arm-gnueabihf": "4.21.2",
"@rollup/rollup-linux-arm-musleabihf": "4.21.0", "@rollup/rollup-linux-arm-musleabihf": "4.21.2",
"@rollup/rollup-linux-arm64-gnu": "4.21.0", "@rollup/rollup-linux-arm64-gnu": "4.21.2",
"@rollup/rollup-linux-arm64-musl": "4.21.0", "@rollup/rollup-linux-arm64-musl": "4.21.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2",
"@rollup/rollup-linux-riscv64-gnu": "4.21.0", "@rollup/rollup-linux-riscv64-gnu": "4.21.2",
"@rollup/rollup-linux-s390x-gnu": "4.21.0", "@rollup/rollup-linux-s390x-gnu": "4.21.2",
"@rollup/rollup-linux-x64-gnu": "4.21.0", "@rollup/rollup-linux-x64-gnu": "4.21.2",
"@rollup/rollup-linux-x64-musl": "4.21.0", "@rollup/rollup-linux-x64-musl": "4.21.2",
"@rollup/rollup-win32-arm64-msvc": "4.21.0", "@rollup/rollup-win32-arm64-msvc": "4.21.2",
"@rollup/rollup-win32-ia32-msvc": "4.21.0", "@rollup/rollup-win32-ia32-msvc": "4.21.2",
"@rollup/rollup-win32-x64-msvc": "4.21.0", "@rollup/rollup-win32-x64-msvc": "4.21.2",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -2830,9 +2836,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.77.8", "version": "1.78.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.78.0.tgz",
"integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "integrity": "sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -2901,9 +2907,9 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -3017,19 +3023,25 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/unplugin": { "node_modules/unplugin": {
"version": "1.12.2", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.2.tgz", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.13.1.tgz",
"integrity": "sha512-bEqQxeC7rxtxPZ3M5V4Djcc4lQqKPgGe3mAWZvxcSmX5jhGxll19NliaRzQSQPrk4xJZSGniK3puLWpRuZN7VQ==", "integrity": "sha512-6Kq1iSSwg7KyjcThRUks9LuqDAKvtnioxbL9iEtB9ctTyBA5OmrB8gZd/d225VJu1w3UpUsKV7eGrvf59J7+VA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"acorn": "^8.12.1", "acorn": "^8.12.1",
"chokidar": "^3.6.0",
"webpack-sources": "^3.2.3",
"webpack-virtual-modules": "^0.6.2" "webpack-virtual-modules": "^0.6.2"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
},
"peerDependencies": {
"webpack-sources": "^3"
},
"peerDependenciesMeta": {
"webpack-sources": {
"optional": true
}
} }
}, },
"node_modules/uri-js": { "node_modules/uri-js": {
@ -3050,14 +3062,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.2", "version": "5.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
"integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.41", "postcss": "^8.4.43",
"rollup": "^4.20.0" "rollup": "^4.20.0"
}, },
"bin": { "bin": {
@ -3110,16 +3122,16 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.4.38", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.3.tgz",
"integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==", "integrity": "sha512-xvRbd0HpuLovYbOHXRHlSBsSvmUJbo0pzbkKTApWnQGf3/cu5Z39mQeA5cZdLRVIoNf3zI6MSoOgHUT5i2jO+Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.38", "@vue/compiler-dom": "3.5.3",
"@vue/compiler-sfc": "3.4.38", "@vue/compiler-sfc": "3.5.3",
"@vue/runtime-dom": "3.4.38", "@vue/runtime-dom": "3.5.3",
"@vue/server-renderer": "3.4.38", "@vue/server-renderer": "3.5.3",
"@vue/shared": "3.4.38" "@vue/shared": "3.5.3"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@ -3300,16 +3312,6 @@
"vue": "^3.0.1" "vue": "^3.0.1"
} }
}, },
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack-virtual-modules": { "node_modules/webpack-virtual-modules": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
@ -3354,9 +3356,9 @@
} }
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {

View File

@ -16,8 +16,7 @@
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
"@ts-pro/vue-eternal-loading": "^1.3.1", "@ts-pro/vue-eternal-loading": "^1.3.1",
"axios": "^1.7.4", "axios": "^1.7.4",
"bulma": "^0.9.4", "bulma": "^1.0.2",
"bulma-switch": "^2.0.4",
"luxon": "^3.4.4", "luxon": "^3.4.4",
"mdi-vue": "^3.0.13", "mdi-vue": "^3.0.13",
"pinia": "^2.1.7", "pinia": "^2.1.7",

View File

@ -1,5 +1,5 @@
<template> <template>
<a class="navbar-item" :href="href" @click.stop.prevent="open"> <a :href="href" @click.stop.prevent="open">
<slot /> <slot />
</a> </a>
</template> </template>
@ -8,7 +8,7 @@
import { useUIStore } from '@/stores/ui' import { useUIStore } from '@/stores/ui'
export default { export default {
name: 'NavbarItemLink', name: 'ControlLink',
props: { props: {
to: { required: true, type: Object } to: { required: true, type: Object }
}, },

View File

@ -0,0 +1,62 @@
<template>
<div class="media is-align-items-center pt-0">
<div class="media-left">
<a class="button is-white is-small" @click="toggle">
<mdicon class="icon" :name="icon" />
</a>
</div>
<div class="media-content">
<p class="heading" v-text="$t('navigation.volume')" />
<control-slider
v-model:value="player.volume"
:cursor="cursor"
:max="100"
@change="changeVolume"
/>
</div>
</div>
</template>
<script>
import ControlSlider from '@/components/ControlSlider.vue'
import { mdiCancel } from '@mdi/js'
import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi'
export default {
name: 'ControlVolume',
components: { ControlSlider },
setup() {
return {
player: usePlayerStore()
}
},
data() {
return {
cursor: mdiCancel,
old_volume: 0
}
},
computed: {
icon() {
return this.player.volume > 0 ? 'volume-high' : 'volume-off'
}
},
watch: {
'player.volume'() {
if (this.player.volume > 0) {
this.old_volume = this.player.volume
}
}
},
methods: {
changeVolume(value) {
webapi.player_volume(this.player.volume)
},
toggle() {
this.player.volume = this.player.volume > 0 ? 0 : this.old_volume
this.changeVolume()
}
}
}
</script>

View File

@ -0,0 +1,79 @@
<template>
<div class="media is-align-items-center pt-0">
<div class="media-left">
<a
class="button is-white is-small"
:class="{ 'has-text-grey-light': !output.selected }"
@click="toggle"
>
<mdicon class="icon" :name="icon" :title="output.type" />
</a>
</div>
<div class="media-content">
<p
class="heading"
:class="{ 'has-text-grey-light': !output.selected }"
v-text="output.name"
/>
<control-slider
v-model:value="volume"
:disabled="!output.selected"
:max="100"
:cursor="cursor"
@change="changeVolume"
/>
</div>
</div>
</template>
<script>
import ControlSlider from '@/components/ControlSlider.vue'
import { mdiCancel } from '@mdi/js'
import webapi from '@/webapi'
export default {
name: 'ControlOutputVolume',
components: {
ControlSlider
},
props: { output: { required: true, type: Object } },
data() {
return {
cursor: mdiCancel,
volume: this.output.selected ? this.output.volume : 0
}
},
computed: {
icon() {
if (this.output.type.startsWith('AirPlay')) {
return 'cast-variant'
} else if (this.output.type === 'Chromecast') {
return 'cast'
} else if (this.output.type === 'fifo') {
return 'pipe'
}
return 'server'
}
},
watch: {
output() {
this.volume = this.output.volume
}
},
methods: {
changeVolume() {
webapi.player_output_volume(this.output.id, this.volume)
},
toggle() {
const values = {
selected: !this.output.selected
}
webapi.output_update(this.output.id, values)
}
}
}
</script>

View File

@ -1,8 +1,8 @@
<template> <template>
<a v-if="visible" :disabled="disabled" @click="seek"> <a v-if="visible" :disabled="disabled" @click="seek">
<mdicon <mdicon
class="icon"
name="rewind-10" name="rewind-10"
:size="icon_size"
:title="$t('player.button.seek-backward')" :title="$t('player.button.seek-backward')"
/> />
</a> </a>
@ -14,10 +14,9 @@ import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonSeekBack', name: 'ControlPlayerBack',
props: { props: {
icon_size: { default: 16, type: Number }, offset: { required: true, type: Number }
seek_ms: { required: true, type: Number }
}, },
setup() { setup() {
@ -52,7 +51,7 @@ export default {
methods: { methods: {
seek() { seek() {
if (!this.disabled) { if (!this.disabled) {
webapi.player_seek(this.seek_ms * -1) webapi.player_seek(this.offset * -1)
} }
} }
} }

View File

@ -1,9 +1,9 @@
<template> <template>
<a :class="{ 'is-info': is_consume }" @click="toggle_consume_mode"> <a :class="{ 'is-info': is_consume }" @click="toggle">
<mdicon <mdicon
class="icon" class="icon"
name="fire" name="fire"
:size="icon_size" size="16"
:title="$t('player.button.consume')" :title="$t('player.button.consume')"
/> />
</a> </a>
@ -14,10 +14,7 @@ import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonConsume', name: 'ControlPlayerConsume',
props: {
icon_size: { default: 16, type: Number }
},
setup() { setup() {
return { return {
@ -32,11 +29,9 @@ export default {
}, },
methods: { methods: {
toggle_consume_mode() { toggle() {
webapi.player_consume(!this.is_consume) webapi.player_consume(!this.is_consume)
} }
} }
} }
</script> </script>
<style></style>

View File

@ -1,8 +1,8 @@
<template> <template>
<a v-if="visible" :disabled="disabled" @click="seek"> <a v-if="visible" :disabled="disabled" @click="seek">
<mdicon <mdicon
class="icon"
name="fast-forward-30" name="fast-forward-30"
:size="icon_size"
:title="$t('player.button.seek-forward')" :title="$t('player.button.seek-forward')"
/> />
</a> </a>
@ -14,10 +14,9 @@ import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonSeekForward', name: 'ControlPlayerForward',
props: { props: {
icon_size: { default: 16, type: Number }, offset: { required: true, type: Number }
seek_ms: { required: true, type: Number }
}, },
setup() { setup() {
@ -52,7 +51,7 @@ export default {
methods: { methods: {
seek() { seek() {
if (!this.disabled) { if (!this.disabled) {
webapi.player_seek(this.seek_ms) webapi.player_seek(this.offset)
} }
} }
} }

View File

@ -1,9 +1,9 @@
<template> <template>
<a :class="{ 'is-info': is_active }" @click="toggle_lyrics"> <a :class="{ 'is-info': is_active }" @click="toggle">
<mdicon <mdicon
class="icon" class="icon"
:name="icon_name" :name="icon"
:size="icon_size" :size="16"
:title="$t('player.button.toggle-lyrics')" :title="$t('player.button.toggle-lyrics')"
/> />
</a> </a>
@ -13,10 +13,7 @@
import { useLyricsStore } from '@/stores/lyrics' import { useLyricsStore } from '@/stores/lyrics'
export default { export default {
name: 'PlayerButtonLyrics', name: 'ControlPlayerLyrics',
props: {
icon_size: { default: 16, type: Number }
},
setup() { setup() {
return { return {
@ -25,7 +22,7 @@ export default {
}, },
computed: { computed: {
icon_name() { icon() {
return this.is_active ? 'script-text-play' : 'script-text-outline' return this.is_active ? 'script-text-play' : 'script-text-outline'
}, },
is_active() { is_active() {
@ -34,11 +31,9 @@ export default {
}, },
methods: { methods: {
toggle_lyrics() { toggle() {
this.lyricsStore.pane = !this.lyricsStore.pane this.lyricsStore.pane = !this.lyricsStore.pane
} }
} }
} }
</script> </script>
<style></style>

View File

@ -1,8 +1,8 @@
<template> <template>
<a :disabled="disabled" @click="play_next"> <a :disabled="disabled" @click="play_next">
<mdicon <mdicon
class="icon"
name="skip-forward" name="skip-forward"
:size="icon_size"
:title="$t('player.button.skip-forward')" :title="$t('player.button.skip-forward')"
/> />
</a> </a>
@ -13,10 +13,7 @@ import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonNext', name: 'ControlPlayerNext',
props: {
icon_size: { default: 16, type: Number }
},
computed: { computed: {
disabled() { disabled() {
@ -29,11 +26,8 @@ export default {
if (this.disabled) { if (this.disabled) {
return return
} }
webapi.player_next() webapi.player_next()
} }
} }
} }
</script> </script>
<style></style>

View File

@ -1,10 +1,6 @@
<template> <template>
<a :disabled="disabled" @click="toggle_play_pause"> <a :disabled="disabled" @click="toggle">
<mdicon <mdicon class="icon" :name="icon" :title="$t(`player.button.${icon}`)" />
:name="icon_name"
:size="icon_size"
:title="$t(`player.button.${icon_name}`)"
/>
</a> </a>
</template> </template>
@ -15,9 +11,8 @@ import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonPlayPause', name: 'ControlPlayerPlay',
props: { props: {
icon_size: { default: 16, type: Number },
show_disabled_message: Boolean show_disabled_message: Boolean
}, },
@ -33,7 +28,7 @@ export default {
disabled() { disabled() {
return this.queueStore?.count <= 0 return this.queueStore?.count <= 0
}, },
icon_name() { icon() {
if (!this.is_playing) { if (!this.is_playing) {
return 'play' return 'play'
} else if (this.is_pause_allowed) { } else if (this.is_pause_allowed) {
@ -51,7 +46,7 @@ export default {
}, },
methods: { methods: {
toggle_play_pause() { toggle() {
if (this.disabled) { if (this.disabled) {
if (this.show_disabled_message) { if (this.show_disabled_message) {
this.notificationsStore.add({ this.notificationsStore.add({
@ -74,5 +69,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -1,8 +1,8 @@
<template> <template>
<a :disabled="disabled" @click="play_previous"> <a :disabled="disabled" @click="play_previous">
<mdicon <mdicon
class="icon"
name="skip-backward" name="skip-backward"
:size="icon_size"
:title="$t('player.button.skip-backward')" :title="$t('player.button.skip-backward')"
/> />
</a> </a>
@ -13,10 +13,7 @@ import { useQueueStore } from '@/stores/queue'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonPrevious', name: 'ControlPlayerPrevious',
props: {
icon_size: { default: 16, type: Number }
},
setup() { setup() {
return { return {
@ -40,5 +37,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -1,10 +1,10 @@
<template> <template>
<a :class="{ 'is-info': !is_repeat_off }" @click="toggle_repeat_mode"> <a :class="{ 'is-info': !is_repeat_off }" @click="toggle">
<mdicon <mdicon
class="icon" class="icon"
:name="icon_name" :name="icon"
:size="icon_size" :size="16"
:title="$t(`player.button.${icon_name}`)" :title="$t(`player.button.${icon}`)"
/> />
</a> </a>
</template> </template>
@ -14,19 +14,14 @@ import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonRepeat', name: 'ControlPlayerRepeat',
props: {
icon_size: { default: 16, type: Number }
},
setup() { setup() {
return { return {
playerStore: usePlayerStore() playerStore: usePlayerStore()
} }
}, },
computed: { computed: {
icon_name() { icon() {
if (this.is_repeat_all) { if (this.is_repeat_all) {
return 'repeat' return 'repeat'
} else if (this.is_repeat_single) { } else if (this.is_repeat_single) {
@ -46,7 +41,7 @@ export default {
}, },
methods: { methods: {
toggle_repeat_mode() { toggle() {
if (this.is_repeat_all) { if (this.is_repeat_all) {
webapi.player_repeat('single') webapi.player_repeat('single')
} else if (this.is_repeat_single) { } else if (this.is_repeat_single) {
@ -58,5 +53,3 @@ export default {
} }
} }
</script> </script>
<style></style>

View File

@ -1,10 +1,10 @@
<template> <template>
<a :class="{ 'is-info': is_shuffle }" @click="toggle_shuffle_mode"> <a :class="{ 'is-info': is_shuffle }" @click="toggle">
<mdicon <mdicon
class="icon" class="icon"
:name="icon_name" :name="icon"
:size="icon_size" :size="16"
:title="$t(`player.button.${icon_name}`)" :title="$t(`player.button.${icon}`)"
/> />
</a> </a>
</template> </template>
@ -14,20 +14,14 @@ import { usePlayerStore } from '@/stores/player'
import webapi from '@/webapi' import webapi from '@/webapi'
export default { export default {
name: 'PlayerButtonShuffle', name: 'ControlPlayerShuffle',
props: {
icon_size: { default: 16, type: Number }
},
setup() { setup() {
return { return {
playerStore: usePlayerStore() playerStore: usePlayerStore()
} }
}, },
computed: { computed: {
icon_name() { icon() {
if (this.is_shuffle) { if (this.is_shuffle) {
return 'shuffle' return 'shuffle'
} }
@ -37,13 +31,10 @@ export default {
return this.playerStore.shuffle return this.playerStore.shuffle
} }
}, },
methods: { methods: {
toggle_shuffle_mode() { toggle() {
webapi.player_shuffle(!this.is_shuffle) webapi.player_shuffle(!this.is_shuffle)
} }
} }
} }
</script> </script>
<style></style>

View File

@ -0,0 +1,110 @@
<template>
<div class="media is-align-items-center pt-0">
<div class="media-left">
<a
class="button is-white is-small"
:class="{
'has-text-grey-light': !playing && !loading,
'is-loading': loading
}"
@click="togglePlay"
>
<mdicon class="icon" name="broadcast" />
</a>
</div>
<div class="media-content">
<div
class="is-flex is-align-content-center"
:class="{ 'has-text-grey-light': !playing }"
>
<p class="heading" v-text="$t('navigation.stream')" />
<a href="stream.mp3" class="heading ml-2" target="_blank">
<mdicon class="icon is-small" name="open-in-new" />
</a>
</div>
<control-slider
v-model:value="volume"
:cursor="cursor"
:disabled="!playing"
:max="100"
@change="changeVolume"
/>
</div>
</div>
</template>
<script>
import ControlSlider from '@/components/ControlSlider.vue'
import audio from '@/lib/Audio'
import { mdiCancel } from '@mdi/js'
export default {
name: 'ControlStreamVolume',
components: { ControlSlider },
emits: ['change', 'mute'],
data() {
return {
cursor: mdiCancel,
loading: false,
playing: false,
volume: 10
}
},
mounted() {
this.setupAudio()
},
unmounted() {
this.closeAudio()
},
methods: {
changeVolume() {
audio.setVolume(this.volume / 100)
},
closeAudio() {
audio.stop()
this.playing = false
},
playChannel() {
if (this.playing) {
return
}
this.loading = true
audio.play('/stream.mp3')
audio.setVolume(this.volume / 100)
},
setupAudio() {
const a = audio.setup()
a.addEventListener('waiting', () => {
this.playing = false
this.loading = true
})
a.addEventListener('playing', () => {
this.playing = true
this.loading = false
})
a.addEventListener('ended', () => {
this.playing = false
this.loading = false
})
a.addEventListener('error', () => {
this.closeAudio()
this.notificationsStore.add({
text: this.$t('navigation.stream-error'),
type: 'danger'
})
this.playing = false
this.loading = false
})
},
togglePlay() {
if (this.loading) {
return
}
if (this.playing) {
this.closeAudio()
}
this.playChannel()
}
}
}
</script>

View File

@ -223,4 +223,14 @@ export default {
.lyrics div:last-child { .lyrics div:last-child {
padding-bottom: calc(25vh - 3rem); padding-bottom: calc(25vh - 3rem);
} }
/* Lyrics animation */
@keyframes pop-color {
0% {
color: var(--bulma-black);
}
100% {
color: var(--bulma-success);
}
}
</style> </style>

View File

@ -1,250 +1,74 @@
<template> <template>
<nav <nav
class="navbar is-block is-white is-fixed-bottom fd-bottom-navbar" class="navbar is-fixed-bottom"
:class="{ :class="{ 'is-dark': !is_now_playing_page }"
'is-transparent': is_now_playing_page,
'is-dark': !is_now_playing_page
}"
role="navigation"
aria-label="player controls"
> >
<!-- Player menu for desktop -->
<div
class="navbar-item has-dropdown has-dropdown-up is-hidden-touch"
:class="{ 'is-active': show_player_menu }"
>
<div class="navbar-dropdown is-right fd-width-auto">
<div class="navbar-item">
<!-- Outputs: master volume -->
<div class="level is-mobile">
<div class="level-left is-flex-grow-1">
<div class="level-item is-flex-grow-0">
<a class="button is-white is-small" @click="toggle_mute_volume">
<mdicon
class="icon"
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18"
/>
</a>
</div>
<div class="level-item">
<div>
<p class="heading" v-text="$t('navigation.volume')" />
<control-slider
v-model:value="player.volume"
:max="100"
@change="change_volume"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Outputs: master volume -->
<hr class="my-3" />
<navbar-item-output
v-for="output in outputs"
:key="output.id"
:output="output"
/>
<!-- Outputs: stream volume -->
<hr class="my-3" />
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left is-flex-grow-1">
<div class="level-item is-flex-grow-0">
<a
class="button is-white is-small"
:class="{
'has-text-grey-light': !playing && !loading,
'is-loading': loading
}"
@click="togglePlay"
>
<mdicon class="icon" name="broadcast" size="18" />
</a>
</div>
<div class="level-item">
<div class="is-flex-grow-1">
<div
class="is-flex is-align-content-center"
:class="{ 'has-text-grey-light': !playing }"
>
<p class="heading" v-text="$t('navigation.stream')" />
<a href="stream.mp3" class="heading ml-2" target="_blank">
<mdicon
class="icon is-small"
name="open-in-new"
size="16"
/>
</a>
</div>
<control-slider
v-model:value="stream_volume"
:disabled="!playing"
:max="100"
:cursor="cursor"
@change="change_stream_volume"
/>
</div>
</div>
</div>
</div>
</div>
<hr class="my-3" />
<div class="navbar-item is-justify-content-center">
<div class="buttons has-addons">
<player-button-repeat class="button" />
<player-button-shuffle class="button" />
<player-button-consume class="button" />
<player-button-lyrics class="button" />
</div>
</div>
</div>
</div>
<div class="navbar-brand is-flex-grow-1"> <div class="navbar-brand is-flex-grow-1">
<navbar-item-link :to="{ name: 'queue' }" class="mr-auto"> <control-link class="navbar-item" :to="{ name: 'queue' }">
<mdicon class="icon" name="playlist-play" size="24" /> <mdicon class="icon" name="playlist-play" />
</navbar-item-link> </control-link>
<navbar-item-link <template v-if="is_now_playing_page">
v-if="!is_now_playing_page" <control-player-previous class="navbar-item ml-auto" />
:to="{ name: 'now-playing' }" <control-player-back class="navbar-item" :offset="10000" />
exact <control-player-play class="navbar-item" show_disabled_message />
class="is-expanded is-clipped is-size-7" <control-player-forward class="navbar-item" :offset="30000" />
> <control-player-next class="navbar-item mr-auto" />
<div class="fd-is-text-clipped"> </template>
<strong v-text="current.title" /> <template v-else>
<br /> <control-link
<span v-text="current.artist" /> :to="{ name: 'now-playing' }"
<span exact
v-if="current.album" class="navbar-item is-expanded is-clipped is-size-7"
v-text="$t('navigation.now-playing', { album: current.album })" >
/> <div class="fd-is-text-clipped">
</div> <strong v-text="current.title" />
</navbar-item-link> <br />
<player-button-previous <span v-text="current.artist" />
v-if="is_now_playing_page" <span
class="navbar-item px-2" v-if="current.album"
:icon_size="24" v-text="$t('navigation.now-playing', { album: current.album })"
/> />
<player-button-seek-back </div>
v-if="is_now_playing_page" </control-link>
:seek_ms="10000" <control-player-play class="navbar-item" show_disabled_message />
class="navbar-item px-2" </template>
:icon_size="24"
/>
<player-button-play-pause
class="navbar-item px-2"
:icon_size="36"
show_disabled_message
/>
<player-button-seek-forward
v-if="is_now_playing_page"
:seek_ms="30000"
class="navbar-item px-2"
:icon_size="24"
/>
<player-button-next
v-if="is_now_playing_page"
class="navbar-item px-2"
:icon_size="24"
/>
<a <a
class="navbar-item ml-auto" class="navbar-item"
@click="show_player_menu = !show_player_menu" @click="uiStore.show_player_menu = !uiStore.show_player_menu"
> >
<mdicon <mdicon
class="icon" class="icon"
:name="show_player_menu ? 'chevron-down' : 'chevron-up'" :name="uiStore.show_player_menu ? 'chevron-down' : 'chevron-up'"
/> />
</a> </a>
</div> <div
<!-- Player menu for mobile and tablet --> class="dropdown is-up is-right"
<div :class="{ 'is-active': uiStore.show_player_menu }"
class="navbar-menu is-hidden-desktop" >
:class="{ 'is-active': show_player_menu }" <div class="dropdown-menu" role="menu">
> <div class="dropdown-content">
<div class="navbar-item"> <div class="dropdown-item">
<div class="buttons has-addons is-centered"> <control-main-volume />
<player-button-repeat class="button" />
<player-button-shuffle class="button" />
<player-button-consume class="button" />
<player-button-lyrics class="button" />
</div>
</div>
<hr class="my-3" />
<!-- Outputs: master volume -->
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left is-flex-grow-1">
<div class="level-item is-flex-grow-0">
<a class="button is-white is-small" @click="toggle_mute_volume">
<mdicon
class="icon"
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18"
/>
</a>
</div> </div>
<div class="level-item"> <hr class="dropdown-divider" />
<div class="is-flex-grow-1"> <div class="dropdown-item">
<p class="heading" v-text="$t('navigation.volume')" /> <control-output-volume
<control-slider v-for="output in outputsStore.outputs"
v-model:value="player.volume" :key="output.id"
:max="100" :output="output"
@change="change_volume" />
/>
</div>
</div> </div>
</div> <hr class="dropdown-divider" />
</div> <div class="dropdown-item">
</div> <control-stream-volume />
<hr class="my-3" />
<!-- Outputs: speaker volumes -->
<navbar-item-output
v-for="output in outputs"
:key="output.id"
:output="output"
/>
<!-- Outputs: stream volume -->
<hr class="my-3" />
<div class="navbar-item mb-5">
<div class="level is-mobile">
<div class="level-left is-flex-grow-1">
<div class="level-item is-flex-grow-0">
<a
class="button is-white is-small"
:class="{
'has-text-grey-light': !playing && !loading,
'is-loading': loading
}"
@click="togglePlay"
>
<mdicon class="icon" name="radio-tower" size="16" />
</a>
</div> </div>
<div class="level-item"> <hr class="dropdown-divider" />
<div class="is-flex-grow-1"> <div class="dropdown-item is-flex is-justify-content-center">
<div <div class="buttons has-addons">
class="is-flex is-align-content-center" <control-player-repeat class="button" />
:class="{ 'has-text-grey-light': !playing }" <control-player-shuffle class="button" />
> <control-player-consume class="button" />
<p class="heading" v-text="$t('navigation.stream')" /> <control-player-lyrics class="button" />
<a href="stream.mp3" class="heading ml-2" target="_blank">
<mdicon
class="icon is-small"
name="open-in-new"
size="16"
/>
</a>
</div>
<control-slider
v-model:value="stream_volume"
:disabled="!playing"
:max="100"
:cursor="cursor"
@change="change_stream_volume"
/>
</div> </div>
</div> </div>
</div> </div>
@ -255,168 +79,58 @@
</template> </template>
<script> <script>
import ControlSlider from '@/components/ControlSlider.vue' import ControlLink from '@/components/ControlLink.vue'
import NavbarItemLink from '@/components/NavbarItemLink.vue' import ControlMainVolume from '@/components/ControlMainVolume.vue'
import NavbarItemOutput from '@/components/NavbarItemOutput.vue' import ControlOutputVolume from '@/components/ControlOutputVolume.vue'
import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue' import ControlPlayerBack from '@/components/ControlPlayerBack.vue'
import PlayerButtonLyrics from '@/components/PlayerButtonLyrics.vue' import ControlPlayerConsume from '@/components/ControlPlayerConsume.vue'
import PlayerButtonNext from '@/components/PlayerButtonNext.vue' import ControlPlayerForward from '@/components/ControlPlayerForward.vue'
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause.vue' import ControlPlayerLyrics from '@/components/ControlPlayerLyrics.vue'
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious.vue' import ControlPlayerNext from '@/components/ControlPlayerNext.vue'
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue' import ControlPlayerPlay from '@/components/ControlPlayerPlay.vue'
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue' import ControlPlayerPrevious from '@/components/ControlPlayerPrevious.vue'
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue' import ControlPlayerRepeat from '@/components/ControlPlayerRepeat.vue'
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle.vue' import ControlPlayerShuffle from '@/components/ControlPlayerShuffle.vue'
import audio from '@/lib/Audio' import ControlStreamVolume from '@/components/ControlStreamVolume.vue'
import { mdiCancel } from '@mdi/js'
import { useNotificationsStore } from '@/stores/notifications' import { useNotificationsStore } from '@/stores/notifications'
import { useOutputsStore } from '@/stores/outputs' import { useOutputsStore } from '@/stores/outputs'
import { usePlayerStore } from '@/stores/player'
import { useQueueStore } from '@/stores/queue' import { useQueueStore } from '@/stores/queue'
import { useUIStore } from '@/stores/ui' import { useUIStore } from '@/stores/ui'
import webapi from '@/webapi'
export default { export default {
name: 'NavbarBottom', name: 'NavbarBottom',
components: { components: {
ControlSlider, ControlLink,
NavbarItemLink, ControlOutputVolume,
NavbarItemOutput, ControlMainVolume,
PlayerButtonConsume, ControlPlayerBack,
PlayerButtonLyrics, ControlPlayerConsume,
PlayerButtonNext, ControlPlayerForward,
PlayerButtonPlayPause, ControlPlayerLyrics,
PlayerButtonPrevious, ControlPlayerNext,
PlayerButtonRepeat, ControlPlayerPlay,
PlayerButtonSeekBack, ControlPlayerPrevious,
PlayerButtonSeekForward, ControlPlayerRepeat,
PlayerButtonShuffle ControlPlayerShuffle,
ControlStreamVolume
}, },
setup() { setup() {
return { return {
notificationsStore: useNotificationsStore(), notificationsStore: useNotificationsStore(),
outputsStore: useOutputsStore(), outputsStore: useOutputsStore(),
playerStore: usePlayerStore(),
queueStore: useQueueStore(), queueStore: useQueueStore(),
uiStore: useUIStore() uiStore: useUIStore()
} }
}, },
data() {
return {
cursor: mdiCancel,
loading: false,
old_volume: 0,
playing: false,
show_desktop_outputs_menu: false,
show_outputs_menu: false,
stream_volume: 10
}
},
computed: { computed: {
is_now_playing_page() { is_now_playing_page() {
return this.$route.name === 'now-playing' return this.$route.name === 'now-playing'
}, },
current() { current() {
return this.queueStore.current return this.queueStore.current
},
outputs() {
return this.outputsStore.outputs
},
player() {
return this.playerStore
},
show_player_menu: {
get() {
return this.uiStore.show_player_menu
},
set(value) {
this.uiStore.show_player_menu = value
}
}
},
watch: {
'playerStore.volume'() {
if (this.player.volume > 0) {
this.old_volume = this.player.volume
}
}
},
// On app mounted
mounted() {
this.setupAudio()
},
// On app destroyed
unmounted() {
this.closeAudio()
},
methods: {
change_stream_volume() {
audio.setVolume(this.stream_volume / 100)
},
change_volume() {
webapi.player_volume(this.player.volume)
},
closeAudio() {
audio.stop()
this.playing = false
},
on_click_outside_outputs() {
this.show_outputs_menu = false
},
playChannel() {
if (this.playing) {
return
}
this.loading = true
audio.play('/stream.mp3')
audio.setVolume(this.stream_volume / 100)
},
setupAudio() {
const a = audio.setup()
a.addEventListener('waiting', () => {
this.playing = false
this.loading = true
})
a.addEventListener('playing', () => {
this.playing = true
this.loading = false
})
a.addEventListener('ended', () => {
this.playing = false
this.loading = false
})
a.addEventListener('error', () => {
this.closeAudio()
this.notificationsStore.add({
text: this.$t('navigation.stream-error'),
type: 'danger'
})
this.playing = false
this.loading = false
})
},
togglePlay() {
if (this.loading) {
return
}
if (this.playing) {
this.closeAudio()
}
this.playChannel()
},
toggle_mute_volume() {
this.player.volume = this.player.volume > 0 ? 0 : this.old_volume
this.change_volume()
} }
} }
} }
</script> </script>
<style></style>

View File

@ -1,93 +0,0 @@
<template>
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left is-flex-grow-1">
<div class="level-item is-flex-grow-0">
<a
class="button is-white is-small"
:class="{ 'has-text-grey-light': !output.selected }"
@click="set_enabled"
>
<mdicon
class="icon"
:name="type_class"
size="18"
:title="output.type"
/>
</a>
</div>
<div class="level-item">
<div class="is-flex-grow-1">
<p
class="heading"
:class="{ 'has-text-grey-light': !output.selected }"
v-text="output.name"
/>
<control-slider
v-model:value="volume"
:disabled="!output.selected"
:max="100"
:cursor="cursor"
@change="change_volume"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import ControlSlider from '@/components/ControlSlider.vue'
import { mdiCancel } from '@mdi/js'
import webapi from '@/webapi'
export default {
name: 'NavbarItemOutput',
components: {
ControlSlider
},
props: { output: { required: true, type: Object } },
data() {
return {
cursor: mdiCancel,
volume: this.output.selected ? this.output.volume : 0
}
},
computed: {
type_class() {
if (this.output.type.startsWith('AirPlay')) {
return 'cast-variant'
} else if (this.output.type === 'Chromecast') {
return 'cast'
} else if (this.output.type === 'fifo') {
return 'pipe'
}
return 'server'
}
},
watch: {
output() {
this.volume = this.output.volume
}
},
methods: {
change_volume() {
webapi.player_output_volume(this.output.id, this.volume)
},
set_enabled() {
const values = {
selected: !this.output.selected
}
webapi.output_update(this.output.id, values)
}
}
}
</script>
<style></style>

View File

@ -6,126 +6,148 @@
aria-label="main navigation" aria-label="main navigation"
> >
<div class="navbar-brand"> <div class="navbar-brand">
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_playlists" v-if="settingsStore.show_menu_item_playlists"
class="navbar-item"
:to="{ name: 'playlists' }" :to="{ name: 'playlists' }"
> >
<mdicon class="icon" name="music-box-multiple" size="16" /> <mdicon class="icon" name="music-box-multiple" size="16" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_music" v-if="settingsStore.show_menu_item_music"
class="navbar-item"
:to="{ name: 'music' }" :to="{ name: 'music' }"
> >
<mdicon class="icon" name="music" size="16" /> <mdicon class="icon" name="music" size="16" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_podcasts" v-if="settingsStore.show_menu_item_podcasts"
class="navbar-item"
:to="{ name: 'podcasts' }" :to="{ name: 'podcasts' }"
> >
<mdicon class="icon" name="microphone" size="16" /> <mdicon class="icon" name="microphone" size="16" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_audiobooks" v-if="settingsStore.show_menu_item_audiobooks"
class="navbar-item"
:to="{ name: 'audiobooks' }" :to="{ name: 'audiobooks' }"
> >
<mdicon class="icon" name="book-open-variant" size="16" /> <mdicon class="icon" name="book-open-variant" size="16" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_radio" v-if="settingsStore.show_menu_item_radio"
class="navbar-item"
:to="{ name: 'radio' }" :to="{ name: 'radio' }"
> >
<mdicon class="icon" name="radio" size="16" /> <mdicon class="icon" name="radio" size="16" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_files" v-if="settingsStore.show_menu_item_files"
class="navbar-item"
:to="{ name: 'files' }" :to="{ name: 'files' }"
> >
<mdicon class="icon" name="folder-open" size="16" /> <mdicon class="icon" name="folder-open" size="16" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
v-if="settingsStore.show_menu_item_search" v-if="settingsStore.show_menu_item_search"
class="navbar-item"
:to="{ name: searchStore.search_source }" :to="{ name: searchStore.search_source }"
> >
<mdicon class="icon" name="magnify" size="16" /> <mdicon class="icon" name="magnify" size="16" />
</navbar-item-link> </control-link>
<div
class="navbar-burger"
:class="{ 'is-active': show_burger_menu }"
@click="show_burger_menu = !show_burger_menu"
>
<span />
<span />
<span />
</div>
</div> </div>
<div class="navbar-menu" :class="{ 'is-active': show_burger_menu }"> <div class="navbar-end">
<div class="navbar-start" /> <a
<div class="navbar-end"> class="navbar-item"
<!-- Burger menu entries --> @click="uiStore.show_burger_menu = !uiStore.show_burger_menu"
<div >
class="navbar-item has-dropdown is-hoverable" <mdicon
:class="{ 'is-active': show_settings_menu }" class="icon"
@click="on_click_outside_settings" :name="uiStore.show_burger_menu ? 'close' : 'menu'"
> />
<a class="navbar-item is-arrowless is-hidden-touch"> </a>
<mdicon class="icon" name="menu" size="24" /> <div
</a> class="dropdown is-right"
<div class="navbar-dropdown is-right"> :class="{ 'is-active': uiStore.show_burger_menu }"
<navbar-item-link :to="{ name: 'playlists' }"> >
<mdicon class="icon" name="music-box-multiple" size="16" /> <div class="dropdown-menu">
<div class="dropdown-content">
<control-link class="dropdown-item" :to="{ name: 'playlists' }">
<span class="icon-text">
<mdicon class="icon" name="music-box-multiple" size="16" />
</span>
<b v-text="$t('navigation.playlists')" /> <b v-text="$t('navigation.playlists')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'music' }"> <control-link class="dropdown-item" :to="{ name: 'music' }">
<mdicon class="icon" name="music" size="16" /> <span class="icon-text">
<mdicon class="icon" name="music" size="16" />
</span>
<b v-text="$t('navigation.music')" /> <b v-text="$t('navigation.music')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'music-artists' }"> <control-link class="dropdown-item" :to="{ name: 'music-artists' }">
<span class="pl-5" v-text="$t('navigation.artists')" /> <span class="pl-5" v-text="$t('navigation.artists')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'music-albums' }"> <control-link class="dropdown-item" :to="{ name: 'music-albums' }">
<span class="pl-5" v-text="$t('navigation.albums')" /> <span class="pl-5" v-text="$t('navigation.albums')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'music-genres' }"> <control-link class="dropdown-item" :to="{ name: 'music-genres' }">
<span class="pl-5" v-text="$t('navigation.genres')" /> <span class="pl-5" v-text="$t('navigation.genres')" />
</navbar-item-link> </control-link>
<navbar-item-link <control-link
class="dropdown-item"
v-if="spotify_enabled" v-if="spotify_enabled"
:to="{ name: 'music-spotify' }" :to="{ name: 'music-spotify' }"
> >
<span class="pl-5" v-text="$t('navigation.spotify')" /> <span class="pl-5" v-text="$t('navigation.spotify')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'podcasts' }"> <control-link class="dropdown-item" :to="{ name: 'podcasts' }">
<mdicon class="icon" name="microphone" size="16" /> <span class="icon-text">
<mdicon class="icon" name="microphone" size="16" />
</span>
<b v-text="$t('navigation.podcasts')" /> <b v-text="$t('navigation.podcasts')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'audiobooks' }"> <control-link class="dropdown-item" :to="{ name: 'audiobooks' }">
<mdicon class="icon" name="book-open-variant" size="16" /> <span class="icon-text">
<mdicon class="icon" name="book-open-variant" size="16" />
</span>
<b v-text="$t('navigation.audiobooks')" /> <b v-text="$t('navigation.audiobooks')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'radio' }"> <control-link class="dropdown-item" :to="{ name: 'radio' }">
<mdicon class="icon" name="radio" size="16" /> <span class="icon-text">
<mdicon class="icon" name="radio" size="16" />
</span>
<b v-text="$t('navigation.radio')" /> <b v-text="$t('navigation.radio')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: 'files' }"> <control-link class="dropdown-item" :to="{ name: 'files' }">
<mdicon class="icon" name="folder-open" size="16" /> <span class="icon-text">
<mdicon class="icon" name="folder-open" size="16" />
</span>
<b v-text="$t('navigation.files')" /> <b v-text="$t('navigation.files')" />
</navbar-item-link> </control-link>
<navbar-item-link :to="{ name: searchStore.search_source }"> <control-link
<mdicon class="icon" name="magnify" size="16" /> class="dropdown-item"
:to="{ name: searchStore.search_source }"
>
<span class="icon-text">
<mdicon class="icon" name="magnify" size="16" />
</span>
<b v-text="$t('navigation.search')" /> <b v-text="$t('navigation.search')" />
</navbar-item-link> </control-link>
<hr class="my-3" /> <hr class="my-3" />
<navbar-item-link :to="{ name: 'settings-webinterface' }"> <control-link
class="dropdown-item"
:to="{ name: 'settings-webinterface' }"
>
{{ $t('navigation.settings') }} {{ $t('navigation.settings') }}
</navbar-item-link> </control-link>
<a <a
class="navbar-item" class="dropdown-item"
@click.stop.prevent="open_update_dialog()" @click.stop.prevent="open_update_dialog()"
v-text="$t('navigation.update-library')" v-text="$t('navigation.update-library')"
/> />
<navbar-item-link :to="{ name: 'about' }"> <control-link class="dropdown-item" :to="{ name: 'about' }">
{{ $t('navigation.about') }} {{ $t('navigation.about') }}
</navbar-item-link> </control-link>
</div> </div>
</div> </div>
</div> </div>
@ -139,7 +161,7 @@
</template> </template>
<script> <script>
import NavbarItemLink from '@/components/NavbarItemLink.vue' import ControlLink from '@/components/ControlLink.vue'
import { useSearchStore } from '@/stores/search' import { useSearchStore } from '@/stores/search'
import { useServicesStore } from '@/stores/services' import { useServicesStore } from '@/stores/services'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
@ -147,7 +169,7 @@ import { useUIStore } from '@/stores/ui'
export default { export default {
name: 'NavbarTop', name: 'NavbarTop',
components: { NavbarItemLink }, components: { ControlLink },
setup() { setup() {
return { return {
@ -165,22 +187,6 @@ export default {
}, },
computed: { computed: {
show_burger_menu: {
get() {
return this.uiStore.show_burger_menu
},
set(value) {
this.uiStore.show_burger_menu = value
}
},
show_update_dialog: {
get() {
return this.uiStore.show_update_dialog
},
set(value) {
this.uiStore.show_update_dialog = value
}
},
spotify_enabled() { spotify_enabled() {
return this.servicesStore.spotify.webapi_token_valid return this.servicesStore.spotify.webapi_token_valid
}, },
@ -203,12 +209,10 @@ export default {
this.show_settings_menu = !this.show_settings_menu this.show_settings_menu = !this.show_settings_menu
}, },
open_update_dialog() { open_update_dialog() {
this.show_update_dialog = true this.uiStore.show_update_dialog = true
this.show_settings_menu = false this.show_settings_menu = false
this.show_burger_menu = false this.uiStore.show_burger_menu = false
} }
} }
} }
</script> </script>
<style></style>

View File

@ -14,6 +14,7 @@ import {
mdiChevronDown, mdiChevronDown,
mdiChevronLeft, mdiChevronLeft,
mdiChevronUp, mdiChevronUp,
mdiClose,
mdiContentSave, mdiContentSave,
mdiDelete, mdiDelete,
mdiDeleteEmpty, mdiDeleteEmpty,
@ -79,6 +80,7 @@ export const icons = {
mdiChevronDown, mdiChevronDown,
mdiChevronLeft, mdiChevronLeft,
mdiChevronUp, mdiChevronUp,
mdiClose,
mdiContentSave, mdiContentSave,
mdiDelete, mdiDelete,
mdiDeleteEmpty, mdiDeleteEmpty,

View File

@ -41,8 +41,7 @@ import PageSettingsRemotesOutputs from '@/pages/PageSettingsRemotesOutputs.vue'
import PageSettingsWebinterface from '@/pages/PageSettingsWebinterface.vue' import PageSettingsWebinterface from '@/pages/PageSettingsWebinterface.vue'
import { useUIStore } from '@/stores/ui' import { useUIStore } from '@/stores/ui'
const TOP_WITH_TABS = 140 const TOP_WITH_TABS = 124
const TOP_WITHOUT_TABS = 110
export const router = createRouter({ export const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
@ -70,7 +69,7 @@ export const router = createRouter({
}, },
{ {
component: PageAlbums, component: PageAlbums,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'music-albums', name: 'music-albums',
path: '/music/albums' path: '/music/albums'
}, },
@ -88,7 +87,7 @@ export const router = createRouter({
}, },
{ {
component: PageArtists, component: PageArtists,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'music-artists', name: 'music-artists',
path: '/music/artists' path: '/music/artists'
}, },
@ -106,7 +105,7 @@ export const router = createRouter({
}, },
{ {
component: PageAudiobooksAlbums, component: PageAudiobooksAlbums,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'audiobooks-albums', name: 'audiobooks-albums',
path: '/audiobooks/albums' path: '/audiobooks/albums'
}, },
@ -118,13 +117,13 @@ export const router = createRouter({
}, },
{ {
component: PageAudiobooksArtists, component: PageAudiobooksArtists,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'audiobooks-artists', name: 'audiobooks-artists',
path: '/audiobooks/artists' path: '/audiobooks/artists'
}, },
{ {
component: PageAudiobooksGenres, component: PageAudiobooksGenres,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'audiobooks-genres', name: 'audiobooks-genres',
path: '/audiobooks/genres' path: '/audiobooks/genres'
}, },
@ -140,55 +139,55 @@ export const router = createRouter({
}, },
{ {
component: PageMusic, component: PageMusic,
meta: { has_tabs: true, show_progress: true }, meta: { show_progress: true },
name: 'music-history', name: 'music-history',
path: '/music/history' path: '/music/history'
}, },
{ {
component: PageMusicRecentlyAdded, component: PageMusicRecentlyAdded,
meta: { has_tabs: true, show_progress: true }, meta: { show_progress: true },
name: 'music-recently-added', name: 'music-recently-added',
path: '/music/recently-added' path: '/music/recently-added'
}, },
{ {
component: PageMusicRecentlyPlayed, component: PageMusicRecentlyPlayed,
meta: { has_tabs: true, show_progress: true }, meta: { show_progress: true },
name: 'music-recently-played', name: 'music-recently-played',
path: '/music/recently-played' path: '/music/recently-played'
}, },
{ {
component: PageMusicSpotify, component: PageMusicSpotify,
meta: { has_tabs: true, show_progress: true }, meta: { show_progress: true },
name: 'music-spotify', name: 'music-spotify',
path: '/music/spotify' path: '/music/spotify'
}, },
{ {
component: PageMusicSpotifyFeaturedPlaylists, component: PageMusicSpotifyFeaturedPlaylists,
meta: { has_tabs: true, show_progress: true }, meta: { show_progress: true },
name: 'music-spotify-featured-playlists', name: 'music-spotify-featured-playlists',
path: '/music/spotify/featured-playlists' path: '/music/spotify/featured-playlists'
}, },
{ {
component: PageMusicSpotifyNewReleases, component: PageMusicSpotifyNewReleases,
meta: { has_tabs: true, show_progress: true }, meta: { show_progress: true },
name: 'music-spotify-new-releases', name: 'music-spotify-new-releases',
path: '/music/spotify/new-releases' path: '/music/spotify/new-releases'
}, },
{ {
component: PageComposerAlbums, component: PageComposerAlbums,
meta: { has_index: true, show_progress: true }, meta: { show_progress: true },
name: 'music-composer-albums', name: 'music-composer-albums',
path: '/music/composers/:name/albums' path: '/music/composers/:name/albums'
}, },
{ {
component: PageComposerTracks, component: PageComposerTracks,
meta: { has_index: true, show_progress: true }, meta: { show_progress: true },
name: 'music-composer-tracks', name: 'music-composer-tracks',
path: '/music/composers/:name/tracks' path: '/music/composers/:name/tracks'
}, },
{ {
component: PageComposers, component: PageComposers,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'music-composers', name: 'music-composers',
path: '/music/composers' path: '/music/composers'
}, },
@ -212,7 +211,7 @@ export const router = createRouter({
}, },
{ {
component: PageGenres, component: PageGenres,
meta: { has_index: true, has_tabs: true, show_progress: true }, meta: { has_index: true, show_progress: true },
name: 'music-genres', name: 'music-genres',
path: '/music/genres' path: '/music/genres'
}, },
@ -313,14 +312,13 @@ export const router = createRouter({
* Staying on the same page and jumping to an anchor (e. g. index nav) * Staying on the same page and jumping to an anchor (e. g. index nav)
* As there is no transition, there is no timeout added * As there is no transition, there is no timeout added
*/ */
const top = to.meta.has_tabs ? TOP_WITH_TABS : TOP_WITHOUT_TABS return { behavior: 'smooth', el: to.hash, top: TOP_WITH_TABS }
return { behavior: 'smooth', el: to.hash, top }
} }
if (to.hash) { if (to.hash) {
// We are navigating to an anchor of a new page, add a timeout to let the transition effect finish before scrolling // We are navigating to an anchor of a new page, add a timeout to let the transition effect finish before scrolling
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
resolve({ el: to.hash, top: 120 }) resolve({ el: to.hash, top: TOP_WITH_TABS })
}, delay) }, delay)
}) })
} }
@ -330,9 +328,8 @@ export const router = createRouter({
* If a tab navigation exists, an offset to the "top" anchor is added * If a tab navigation exists, an offset to the "top" anchor is added
*/ */
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const top = to.meta.has_tabs ? TOP_WITH_TABS : TOP_WITHOUT_TABS
setTimeout(() => { setTimeout(() => {
resolve({ el: '#top', top }) resolve({ el: '#top', top: TOP_WITH_TABS })
}, delay) }, delay)
}) })
} }