From aba7cb302ff0aecbe0a1917b404da391edd3af79 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:47:57 -0800 Subject: [PATCH] add navidrome version check for smart playlists --- package-lock.json | 264 ++++++------------ package.json | 1 + src/renderer/api/controller.ts | 2 +- .../api/navidrome/navidrome-controller.ts | 81 +++++- src/renderer/api/navidrome/navidrome-types.ts | 4 + src/renderer/api/types.ts | 3 + src/renderer/api/utils.ts | 17 ++ .../features/lyrics/queries/lyric-query.ts | 3 +- .../components/create-playlist-form.tsx | 15 +- src/renderer/hooks/use-server-version.ts | 3 +- src/renderer/types.ts | 20 +- 11 files changed, 201 insertions(+), 212 deletions(-) diff --git a/package-lock.json b/package-lock.json index 662bb006..c1a9bd7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "react-virtualized-auto-sizer": "^1.0.17", "react-window": "^1.8.9", "react-window-infinite-loader": "^1.0.9", + "semver": "^7.5.4", "styled-components": "^6.0.8", "swiper": "^9.3.1", "zod": "^3.22.3", @@ -5023,7 +5024,7 @@ "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "devOptional": true + "dev": true }, "node_modules/@types/qs": { "version": "6.9.7", @@ -5041,7 +5042,7 @@ "version": "18.0.26", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", - "devOptional": true, + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -5113,7 +5114,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "devOptional": true + "dev": true }, "node_modules/@types/semver": { "version": "7.3.13", @@ -6445,30 +6446,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, - "node_modules/babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", - "optional": true, - "peer": true - }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -9979,6 +9956,21 @@ "@mdn/browser-compat-data": "^5.2.34" } }, + "node_modules/eslint-plugin-compat/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-import": { "version": "2.27.5", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", @@ -17952,9 +17944,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -18251,21 +18243,6 @@ "node": ">=10" } }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/sirv": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", @@ -21972,8 +21949,7 @@ "@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "requires": {} + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -22847,8 +22823,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz", "integrity": "sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==", - "dev": true, - "requires": {} + "dev": true }, "@csstools/css-tokenizer": { "version": "2.2.0", @@ -22860,15 +22835,13 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", - "dev": true, - "requires": {} + "dev": true }, "@csstools/selector-specificity": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", - "dev": true, - "requires": {} + "dev": true }, "@develar/schema-utils": { "version": "2.6.5", @@ -23453,8 +23426,7 @@ "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", - "requires": {} + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==" }, "@emotion/utils": { "version": "1.2.0", @@ -24007,8 +23979,7 @@ "@mantine/hooks": { "version": "6.0.17", "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.17.tgz", - "integrity": "sha512-7vf2w1NlzKlUynSuyI2DAIKoEOYKYC8k+tlSsk3BRdbzhbJAiWxcYzJy5seg5dFW1WIpKAZ0wiVdHXf/WRlRgg==", - "requires": {} + "integrity": "sha512-7vf2w1NlzKlUynSuyI2DAIKoEOYKYC8k+tlSsk3BRdbzhbJAiWxcYzJy5seg5dFW1WIpKAZ0wiVdHXf/WRlRgg==" }, "@mantine/modals": { "version": "6.0.17", @@ -24051,8 +24022,7 @@ "@mantine/utils": { "version": "6.0.17", "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.17.tgz", - "integrity": "sha512-U6SWV/asYE6NhiHx4ltmVZdQR3HwGVqJxVulhOylMcV1tX/P1LMQUCbGV2Oe4O9jbX4/YW5B/CBb4BbEhENQFQ==", - "requires": {} + "integrity": "sha512-U6SWV/asYE6NhiHx4ltmVZdQR3HwGVqJxVulhOylMcV1tX/P1LMQUCbGV2Oe4O9jbX4/YW5B/CBb4BbEhENQFQ==" }, "@mdn/browser-compat-data": { "version": "5.3.5", @@ -24609,8 +24579,7 @@ "@ts-rest/core": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@ts-rest/core/-/core-3.23.0.tgz", - "integrity": "sha512-2vJwa682m9yS/xQPvPxZBluJfIZwNkt2HY9ER3UtGnu8Dijw+8iymSyIyjRLpFFWUyRnVp9IqrEi/d84bkNFIw==", - "requires": {} + "integrity": "sha512-2vJwa682m9yS/xQPvPxZBluJfIZwNkt2HY9ER3UtGnu8Dijw+8iymSyIyjRLpFFWUyRnVp9IqrEi/d84bkNFIw==" }, "@tsconfig/node10": { "version": "1.0.8", @@ -24978,7 +24947,7 @@ "version": "15.7.4", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", - "devOptional": true + "dev": true }, "@types/qs": { "version": "6.9.7", @@ -24996,7 +24965,7 @@ "version": "18.0.26", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz", "integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==", - "devOptional": true, + "dev": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -25068,7 +25037,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "devOptional": true + "dev": true }, "@types/semver": { "version": "7.3.13", @@ -25447,8 +25416,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", - "dev": true, - "requires": {} + "dev": true }, "@webpack-cli/info": { "version": "1.4.1", @@ -25463,8 +25431,7 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", - "dev": true, - "requires": {} + "dev": true }, "@xhayper/discord-rpc": { "version": "1.0.24", @@ -25478,8 +25445,7 @@ "ws": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "requires": {} + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==" } } }, @@ -25557,15 +25523,13 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "requires": {} + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "7.2.0", @@ -25651,8 +25615,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-escapes": { "version": "4.3.2", @@ -26095,27 +26058,6 @@ "@babel/helper-define-polyfill-provider": "^0.4.2" } }, - "babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", - "optional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" - } - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", - "optional": true, - "peer": true - }, "babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -26374,8 +26316,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/browserslist-config-erb/-/browserslist-config-erb-0.0.3.tgz", "integrity": "sha512-y47DryCY92lxkKyRVMlaZvXAolIY7U33q9e4CS0MdWeJkoAht7OzsrkfdZFCBOP3H5q1EVUxS0L7VVsKM6gZCQ==", - "dev": true, - "requires": {} + "dev": true }, "bs-logger": { "version": "0.2.6", @@ -27215,8 +27156,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", "integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==", - "dev": true, - "requires": {} + "dev": true }, "css-functions-list": { "version": "3.2.0", @@ -27412,8 +27352,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "requires": {} + "dev": true }, "csso": { "version": "4.2.0", @@ -28685,8 +28624,7 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.7", @@ -28803,6 +28741,15 @@ "requires": { "@mdn/browser-compat-data": "^5.2.34" } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -28918,8 +28865,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-react": { "version": "7.33.0", @@ -28976,8 +28922,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-sort-keys-fix": { "version": "1.1.2", @@ -30572,8 +30517,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} + "dev": true }, "idb-keyval": { "version": "6.2.1", @@ -31404,8 +31348,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "27.5.1", @@ -32935,8 +32878,7 @@ "overlayscrollbars-react": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/overlayscrollbars-react/-/overlayscrollbars-react-0.5.1.tgz", - "integrity": "sha512-0xw9J1CT/cQ+ELYy3hudG6nY1H5dgJ1DdVW3d8aZwqx6wyHNZV4nsBQXUxoHmPo3dmlJ5MvOLzpKWA4X6nL4QA==", - "requires": {} + "integrity": "sha512-0xw9J1CT/cQ+ELYy3hudG6nY1H5dgJ1DdVW3d8aZwqx6wyHNZV4nsBQXUxoHmPo3dmlJ5MvOLzpKWA4X6nL4QA==" }, "p-cancelable": { "version": "2.1.1", @@ -33317,29 +33259,25 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-media-query-parser": { "version": "0.2.3", @@ -33413,8 +33351,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -33449,8 +33386,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "requires": {} + "dev": true }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -33565,15 +33501,13 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", - "dev": true, - "requires": {} + "dev": true }, "postcss-scss": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz", "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==", - "dev": true, - "requires": {} + "dev": true }, "postcss-selector-parser": { "version": "6.0.13", @@ -33618,8 +33552,7 @@ "version": "0.36.2", "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", - "dev": true, - "requires": {} + "dev": true }, "postcss-unique-selectors": { "version": "5.1.1", @@ -33964,8 +33897,7 @@ "react-icons": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz", - "integrity": "sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==", - "requires": {} + "integrity": "sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==" }, "react-is": { "version": "17.0.2", @@ -34002,8 +33934,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.4.tgz", "integrity": "sha512-ySsBExEFik5Jjf7NoXtFbzUk2rYWM4gF5gg+wRTNmp9p7B2uMpAAa339FHWqmB8EAr0e6mzzskAXxc0Jd04fBw==", - "dev": true, - "requires": {} + "dev": true }, "react-remove-scroll": { "version": "2.5.5", @@ -34056,8 +33987,7 @@ "react-simple-img": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-simple-img/-/react-simple-img-3.0.0.tgz", - "integrity": "sha512-I0sG/GgY9c+04BgWf1YRlipWBQxR3oG2s/bagU8EO7zals3/Vkfk1PJMeYh/wHfjxJtUmal+y7HWEBm4MzXVsQ==", - "requires": {} + "integrity": "sha512-I0sG/GgY9c+04BgWf1YRlipWBQxR3oG2s/bagU8EO7zals3/Vkfk1PJMeYh/wHfjxJtUmal+y7HWEBm4MzXVsQ==" }, "react-style-singleton": { "version": "2.2.1", @@ -34112,8 +34042,7 @@ "react-virtualized-auto-sizer": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.20.tgz", - "integrity": "sha512-OdIyHwj4S4wyhbKHOKM1wLSj/UDXm839Z3Cvfg2a9j+He6yDa6i5p0qQvEiCnyQlGO/HyfSnigQwuxvYalaAXA==", - "requires": {} + "integrity": "sha512-OdIyHwj4S4wyhbKHOKM1wLSj/UDXm839Z3Cvfg2a9j+He6yDa6i5p0qQvEiCnyQlGO/HyfSnigQwuxvYalaAXA==" }, "react-window": { "version": "1.8.9", @@ -34134,8 +34063,7 @@ "react-window-infinite-loader": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/react-window-infinite-loader/-/react-window-infinite-loader-1.0.9.tgz", - "integrity": "sha512-5Hg89IdU4Vrp0RT8kZYKeTIxWZYhNkVXeI1HbKo01Vm/Z7qztDvXljwx16sMzsa9yapRJQW3ODZfMUw38SOWHw==", - "requires": {} + "integrity": "sha512-5Hg89IdU4Vrp0RT8kZYKeTIxWZYhNkVXeI1HbKo01Vm/Z7qztDvXljwx16sMzsa9yapRJQW3ODZfMUw38SOWHw==" }, "read-config-file": { "version": "6.3.2", @@ -34650,9 +34578,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -34895,17 +34823,6 @@ "dev": true, "requires": { "semver": "^7.5.3" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "sirv": { @@ -35293,8 +35210,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "dev": true, - "requires": {} + "dev": true }, "style-search": { "version": "0.1.0", @@ -35503,8 +35419,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz", "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", - "dev": true, - "requires": {} + "dev": true }, "stylelint-order": { "version": "6.0.3", @@ -35522,8 +35437,7 @@ "version": "13.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", - "dev": true, - "requires": {} + "dev": true }, "stylelint-config-standard": { "version": "34.0.0", @@ -35559,8 +35473,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz", "integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -35577,8 +35490,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz", "integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==", - "dev": true, - "requires": {} + "dev": true } } } @@ -36297,8 +36209,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/typescript-plugin-styled-components/-/typescript-plugin-styled-components-3.0.0.tgz", "integrity": "sha512-QWlhTl6NqsFxtJyxn7pJjm3RhgzXSByUftZ3AoQClrMMpa4yAaHuJKTN1gFpH3Ti+Rwm56fNUfG9pXSBU+WW3A==", - "dev": true, - "requires": {} + "dev": true }, "unbox-primitive": { "version": "1.0.2", @@ -36454,14 +36365,12 @@ "use-composed-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", - "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", - "requires": {} + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==" }, "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "requires": {} + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" }, "use-latest": { "version": "1.2.1", @@ -36483,8 +36392,7 @@ "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "requires": {} + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" }, "utf8-byte-length": { "version": "1.0.4", @@ -37012,8 +36920,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -37162,8 +37069,7 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, - "requires": {} + "dev": true }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index adb41546..8b2e6854 100644 --- a/package.json +++ b/package.json @@ -345,6 +345,7 @@ "react-virtualized-auto-sizer": "^1.0.17", "react-window": "^1.8.9", "react-window-infinite-loader": "^1.0.9", + "semver": "^7.5.4", "styled-components": "^6.0.8", "swiper": "^9.3.1", "zod": "^3.22.3", diff --git a/src/renderer/api/controller.ts b/src/renderer/api/controller.ts index 6cd4d623..6d203a98 100644 --- a/src/renderer/api/controller.ts +++ b/src/renderer/api/controller.ts @@ -173,7 +173,7 @@ const endpoints: ApiController = { getPlaylistList: ndController.getPlaylistList, getPlaylistSongList: ndController.getPlaylistSongList, getRandomSongList: ssController.getRandomSongList, - getServerInfo: ssController.getServerInfo, + getServerInfo: ndController.getServerInfo, getSongDetail: ndController.getSongDetail, getSongList: ndController.getSongList, getStructuredLyrics: ssController.getStructuredLyrics, diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 486cb1bc..162be26a 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -1,3 +1,9 @@ +import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api'; +import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize'; +import { NavidromeExtensions, ndType } from '/@/renderer/api/navidrome/navidrome-types'; +import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api'; +import semverCoerce from 'semver/functions/coerce'; +import semverGte from 'semver/functions/gte'; import { AlbumArtistDetailArgs, AlbumArtistDetailResponse, @@ -39,11 +45,10 @@ import { RemoveFromPlaylistResponse, RemoveFromPlaylistArgs, genreListSortMap, + ServerInfo, + ServerInfoArgs, } from '../types'; -import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api'; -import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize'; -import { ndType } from '/@/renderer/api/navidrome/navidrome-types'; -import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api'; +import { hasFeature } from '/@/renderer/api/utils'; const authenticate = async ( url: string, @@ -355,6 +360,16 @@ const deletePlaylist = async (args: DeletePlaylistArgs): Promise => { const { query, apiClientProps } = args; + const customQuery = query._custom?.navidrome; + + // Smart playlists only became available in 0.48.0. Do not filter for previous versions + if ( + customQuery && + customQuery.smart !== undefined && + !hasFeature(apiClientProps.server, NavidromeExtensions.SMART_PLAYLISTS) + ) { + customQuery.smart = undefined; + } const res = await ndApiClient(apiClientProps).getPlaylistList({ query: { @@ -363,7 +378,7 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise]> = [ + ['0.48.0', { [NavidromeExtensions.SMART_PLAYLISTS]: [1] }], +]; + +const getFeatures = (version: string): Record => { + const cleanVersion = semverCoerce(version); + const features: Record = {}; + let matched = cleanVersion === null; + + for (const [version, supportedFeatures] of VERSION_INFO) { + if (!matched) { + matched = semverGte(cleanVersion!, version); + } + + if (matched) { + for (const [feature, feat] of Object.entries(supportedFeatures)) { + if (feature in features) { + features[feature].push(...feat); + } else { + features[feature] = feat; + } + } + } + } + + return features; +}; + +const getServerInfo = async (args: ServerInfoArgs): Promise => { + const { apiClientProps } = args; + + // Navidrome will always populate serverVersion + const ping = await ssApiClient(apiClientProps).ping(); + + if (ping.status !== 200) { + throw new Error('Failed to ping server'); + } + + const features: Record = getFeatures(ping.body.serverVersion!); + + if (ping.body.openSubsonic) { + const res = await ssApiClient(apiClientProps).getServerInfo(); + + if (res.status !== 200) { + throw new Error('Failed to get server extensions'); + } + + for (const extension of res.body.openSubsonicExtensions) { + features[extension.name] = extension.versions; + } + } + + return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion! }; +}; + export const ndController = { addToPlaylist, authenticate, @@ -478,6 +548,7 @@ export const ndController = { getPlaylistDetail, getPlaylistList, getPlaylistSongList, + getServerInfo, getSongDetail, getSongList, getUserList, diff --git a/src/renderer/api/navidrome/navidrome-types.ts b/src/renderer/api/navidrome/navidrome-types.ts index d01174f6..9e8f4e43 100644 --- a/src/renderer/api/navidrome/navidrome-types.ts +++ b/src/renderer/api/navidrome/navidrome-types.ts @@ -342,6 +342,10 @@ const removeFromPlaylistParameters = z.object({ id: z.array(z.string()), }); +export enum NavidromeExtensions { + SMART_PLAYLISTS = 'smartPlaylists', +} + export const ndType = { _enum: { albumArtistList: ndAlbumArtistListSort, diff --git a/src/renderer/api/types.ts b/src/renderer/api/types.ts index 7241d82e..9a8fb815 100644 --- a/src/renderer/api/types.ts +++ b/src/renderer/api/types.ts @@ -57,13 +57,16 @@ export type User = { export type ServerListItem = { credential: string; + features?: Record; id: string; name: string; ndCredential?: string; + savePassword?: boolean; type: ServerType; url: string; userId: string | null; username: string; + version?: string; }; export enum ServerType { diff --git a/src/renderer/api/utils.ts b/src/renderer/api/utils.ts index 0063fae9..5c676bb5 100644 --- a/src/renderer/api/utils.ts +++ b/src/renderer/api/utils.ts @@ -38,3 +38,20 @@ export const authenticationFailure = (currentServer: ServerListItem | null) => { useAuthStore.getState().actions.setCurrentServer(null); } }; + +export const hasFeature = ( + server: ServerListItem | null, + feature: string, + version = 1, +): boolean => { + if (!server || !server.features) { + return false; + } + + const versions = server.features[feature]; + if (!versions || versions.length === 0) { + return false; + } + + return versions.includes(version); +}; diff --git a/src/renderer/features/lyrics/queries/lyric-query.ts b/src/renderer/features/lyrics/queries/lyric-query.ts index a02da327..66e5c74d 100644 --- a/src/renderer/features/lyrics/queries/lyric-query.ts +++ b/src/renderer/features/lyrics/queries/lyric-query.ts @@ -15,6 +15,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { ServerType } from '/@/renderer/types'; import { api } from '/@/renderer/api'; import isElectron from 'is-electron'; +import { hasFeature } from '/@/renderer/api/utils'; const lyricsIpc = isElectron() ? window.electron.lyrics : null; @@ -112,7 +113,7 @@ export const useSongLyricsBySong = ( source: server?.name ?? 'music server', }; } - } else if (server.features && SubsonicExtensions.SONG_LYRICS in server.features) { + } else if (hasFeature(server, SubsonicExtensions.SONG_LYRICS)) { const subsonicLyrics = await api.controller .getStructuredLyrics({ apiClientProps: { server, signal }, diff --git a/src/renderer/features/playlists/components/create-playlist-form.tsx b/src/renderer/features/playlists/components/create-playlist-form.tsx index 63c9fc47..e597292b 100644 --- a/src/renderer/features/playlists/components/create-playlist-form.tsx +++ b/src/renderer/features/playlists/components/create-playlist-form.tsx @@ -11,6 +11,8 @@ import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/crea import { convertQueryGroupToNDQuery } from '/@/renderer/features/playlists/utils'; import { useCurrentServer } from '/@/renderer/store'; import { useTranslation } from 'react-i18next'; +import { hasFeature } from '/@/renderer/api/utils'; +import { NavidromeExtensions } from '/@/renderer/api/navidrome/navidrome-types'; interface CreatePlaylistFormProps { onCancel: () => void; @@ -120,12 +122,13 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => { })} /> )} - {server?.type === ServerType.NAVIDROME && ( - setIsSmartPlaylist(e.currentTarget.checked)} - /> - )} + {server?.type === ServerType.NAVIDROME && + hasFeature(server, NavidromeExtensions.SMART_PLAYLISTS) && ( + setIsSmartPlaylist(e.currentTarget.checked)} + /> + )} {server?.type === ServerType.NAVIDROME && isSmartPlaylist && ( diff --git a/src/renderer/hooks/use-server-version.ts b/src/renderer/hooks/use-server-version.ts index 6ca1327d..5565457e 100644 --- a/src/renderer/hooks/use-server-version.ts +++ b/src/renderer/hooks/use-server-version.ts @@ -3,6 +3,7 @@ import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store'; import { useQuery } from '@tanstack/react-query'; import { queryKeys } from '/@/renderer/api/query-keys'; import { controller } from '/@/renderer/api/controller'; +import isEqual from 'lodash/isEqual'; export const useServerVersion = () => { const { updateServer } = useAuthStoreActions(); @@ -24,7 +25,7 @@ export const useServerVersion = () => { useEffect(() => { if (server && server.id === serverInfo.data?.id) { const { version, features } = serverInfo.data; - if (version !== server.version) { + if (version !== server.version || !isEqual(features, server.features)) { updateServer(server.id, { features, version, diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 5df65673..0f4e400a 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -54,25 +54,7 @@ export enum Platform { WINDOWS = 'windows', } -export enum ServerType { - JELLYFIN = 'jellyfin', - NAVIDROME = 'navidrome', - SUBSONIC = 'subsonic', -} - -export type ServerListItem = { - credential: string; - features?: Record; - id: string; - name: string; - ndCredential?: string; - savePassword?: boolean; - type: ServerType; - url: string; - userId: string | null; - username: string; - version?: string; -}; +export { ServerType, ServerListItem } from '/@/renderer/api/types'; export enum PlayerStatus { PAUSED = 'paused',