mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add new context menu implementation
This commit is contained in:
Vendored
+3
-4
@@ -14,9 +14,7 @@
|
|||||||
".eslintignore": "ignore"
|
".eslintignore": "ignore"
|
||||||
},
|
},
|
||||||
"eslint.validate": ["typescript", "typescriptreact"],
|
"eslint.validate": ["typescript", "typescriptreact"],
|
||||||
"eslint.workingDirectories": [
|
"eslint.workingDirectories": [{ "directory": "./", "changeProcessCWD": true }],
|
||||||
{ "directory": "./", "changeProcessCWD": true },
|
|
||||||
],
|
|
||||||
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
|
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
@@ -50,7 +48,8 @@
|
|||||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||||
"@mantine/core",
|
"@mantine/core",
|
||||||
"@mantine/modals",
|
"@mantine/modals",
|
||||||
"@mantine/dates"
|
"@mantine/dates",
|
||||||
|
"@radix-ui/react-context-menu"
|
||||||
],
|
],
|
||||||
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||||
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true,
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true,
|
||||||
|
|||||||
+2
-1
@@ -78,6 +78,7 @@
|
|||||||
"@mantine/modals": "^8.2.8",
|
"@mantine/modals": "^8.2.8",
|
||||||
"@mantine/notifications": "^8.2.8",
|
"@mantine/notifications": "^8.2.8",
|
||||||
"@offlegacy/nuqs-hash-router": "^0.1.1",
|
"@offlegacy/nuqs-hash-router": "^0.1.1",
|
||||||
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@tanstack/react-query": "^5.89.0",
|
"@tanstack/react-query": "^5.89.0",
|
||||||
"@tanstack/react-query-devtools": "^5.89.0",
|
"@tanstack/react-query-devtools": "^5.89.0",
|
||||||
"@tanstack/react-query-persist-client": "^5.89.0",
|
"@tanstack/react-query-persist-client": "^5.89.0",
|
||||||
@@ -105,7 +106,6 @@
|
|||||||
"immer": "^9.0.21",
|
"immer": "^9.0.21",
|
||||||
"is-electron": "^2.2.2",
|
"is-electron": "^2.2.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mantine-contextmenu": "^8.2.0",
|
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"motion": "^12.18.1",
|
"motion": "^12.18.1",
|
||||||
@@ -118,6 +118,7 @@
|
|||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
"react-call": "^1.8.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^11.18.6",
|
||||||
|
|||||||
Generated
+520
-20
@@ -62,6 +62,9 @@ importers:
|
|||||||
'@offlegacy/nuqs-hash-router':
|
'@offlegacy/nuqs-hash-router':
|
||||||
specifier: ^0.1.1
|
specifier: ^0.1.1
|
||||||
version: 0.1.1(nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0))(react@19.1.0)
|
version: 0.1.1(nuqs@2.7.1(react-router-dom@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-router@7.9.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-context-menu':
|
||||||
|
specifier: ^2.2.16
|
||||||
|
version: 2.2.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^5.89.0
|
specifier: ^5.89.0
|
||||||
version: 5.89.0(react@19.1.0)
|
version: 5.89.0(react@19.1.0)
|
||||||
@@ -143,9 +146,6 @@ importers:
|
|||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
mantine-contextmenu:
|
|
||||||
specifier: ^8.2.0
|
|
||||||
version: 8.2.0(@mantine/core@8.2.8(@mantine/hooks@8.2.8(react@19.1.0))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.2.8(react@19.1.0))(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
md5:
|
md5:
|
||||||
specifier: ^2.3.0
|
specifier: ^2.3.0
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
@@ -182,6 +182,9 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 19.1.0
|
version: 19.1.0
|
||||||
|
react-call:
|
||||||
|
specifier: ^1.8.1
|
||||||
|
version: 1.8.1(react@19.1.0)
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 19.1.0(react@19.1.0)
|
version: 19.1.0(react@19.1.0)
|
||||||
@@ -1597,87 +1600,353 @@ packages:
|
|||||||
'@radix-ui/primitive@1.0.0':
|
'@radix-ui/primitive@1.0.0':
|
||||||
resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==}
|
resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.3':
|
||||||
|
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||||
|
|
||||||
|
'@radix-ui/react-arrow@1.1.7':
|
||||||
|
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-collection@1.1.7':
|
||||||
|
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-compose-refs@1.0.0':
|
'@radix-ui/react-compose-refs@1.0.0':
|
||||||
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==}
|
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2':
|
||||||
|
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-context-menu@2.2.16':
|
||||||
|
resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-context@1.0.0':
|
'@radix-ui/react-context@1.0.0':
|
||||||
resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==}
|
resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2':
|
||||||
|
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-dialog@1.0.0':
|
'@radix-ui/react-dialog@1.0.0':
|
||||||
resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==}
|
resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-direction@1.1.1':
|
||||||
|
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-dismissable-layer@1.0.0':
|
'@radix-ui/react-dismissable-layer@1.0.0':
|
||||||
resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==}
|
resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.11':
|
||||||
|
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-focus-guards@1.0.0':
|
'@radix-ui/react-focus-guards@1.0.0':
|
||||||
resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==}
|
resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.3':
|
||||||
|
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.0.0':
|
'@radix-ui/react-focus-scope@1.0.0':
|
||||||
resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==}
|
resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-scope@1.1.7':
|
||||||
|
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-id@1.0.0':
|
'@radix-ui/react-id@1.0.0':
|
||||||
resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==}
|
resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1':
|
||||||
|
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-menu@2.1.16':
|
||||||
|
resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.8':
|
||||||
|
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.0.0':
|
'@radix-ui/react-portal@1.0.0':
|
||||||
resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==}
|
resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9':
|
||||||
|
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-presence@1.0.0':
|
'@radix-ui/react-presence@1.0.0':
|
||||||
resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==}
|
resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.5':
|
||||||
|
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-primitive@1.0.0':
|
'@radix-ui/react-primitive@1.0.0':
|
||||||
resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==}
|
resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3':
|
||||||
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-roving-focus@1.1.11':
|
||||||
|
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.0.0':
|
'@radix-ui/react-slot@1.0.0':
|
||||||
resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==}
|
resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3':
|
||||||
|
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-callback-ref@1.0.0':
|
'@radix-ui/react-use-callback-ref@1.0.0':
|
||||||
resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==}
|
resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||||
|
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-controllable-state@1.0.0':
|
'@radix-ui/react-use-controllable-state@1.0.0':
|
||||||
resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==}
|
resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2':
|
||||||
|
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2':
|
||||||
|
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-escape-keydown@1.0.0':
|
'@radix-ui/react-use-escape-keydown@1.0.0':
|
||||||
resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==}
|
resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||||
|
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-layout-effect@1.0.0':
|
'@radix-ui/react-use-layout-effect@1.0.0':
|
||||||
resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==}
|
resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8 || ^17.0 || ^18.0
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-rect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-size@1.1.1':
|
||||||
|
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/rect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.9':
|
'@rolldown/pluginutils@1.0.0-beta.9':
|
||||||
resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==}
|
resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==}
|
||||||
|
|
||||||
@@ -3897,15 +4166,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==}
|
resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==}
|
||||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||||
|
|
||||||
mantine-contextmenu@8.2.0:
|
|
||||||
resolution: {integrity: sha512-GKxC13wTnwCmToh6UvQtXN/vVbdbnScwXYtgzyKOzVGGEPBDmkqhKjG/IYq+JqSIqf/t9WoVHPm/81Jqi5FJgg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@mantine/core': '>=8.2'
|
|
||||||
'@mantine/hooks': '>=8.2'
|
|
||||||
clsx: '>=2'
|
|
||||||
react: '>=19'
|
|
||||||
react-dom: '>=19'
|
|
||||||
|
|
||||||
map-stream@0.1.0:
|
map-stream@0.1.0:
|
||||||
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
|
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
|
||||||
|
|
||||||
@@ -4489,6 +4749,11 @@ packages:
|
|||||||
randombytes@2.1.0:
|
randombytes@2.1.0:
|
||||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||||
|
|
||||||
|
react-call@1.8.1:
|
||||||
|
resolution: {integrity: sha512-HztXMrthuK+XHeUcOJjBsT2U+oKpc2nKcUWii1aYBIs3vpgTFRQ7nm/RbjU8JCC41djRpT0GC0KgF8XYVgxOUQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
|
||||||
react-dom@19.1.0:
|
react-dom@19.1.0:
|
||||||
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -7157,16 +7422,65 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
|
|
||||||
|
'@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-compose-refs@1.0.0(react@19.1.0)':
|
'@radix-ui/react-compose-refs@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-context-menu@2.2.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-context@1.0.0(react@19.1.0)':
|
'@radix-ui/react-context@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-dialog@1.0.0(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@radix-ui/react-dialog@1.0.0(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
@@ -7189,6 +7503,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
|
|
||||||
|
'@radix-ui/react-direction@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-dismissable-layer@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@radix-ui/react-dismissable-layer@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
@@ -7200,11 +7520,30 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-focus-guards@1.0.0(react@19.1.0)':
|
'@radix-ui/react-focus-guards@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@radix-ui/react-focus-scope@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
@@ -7214,12 +7553,74 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-id@1.0.0(react@19.1.0)':
|
'@radix-ui/react-id@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
'@radix-ui/react-use-layout-effect': 1.0.0(react@19.1.0)
|
'@radix-ui/react-use-layout-effect': 1.0.0(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
react-remove-scroll: 2.7.0(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@radix-ui/react-portal@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
@@ -7227,6 +7628,16 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-presence@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@radix-ui/react-presence@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
@@ -7235,6 +7646,16 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-primitive@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@radix-ui/react-primitive@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
@@ -7242,34 +7663,117 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
|
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
'@types/react-dom': 18.3.7(@types/react@18.3.23)
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.0.0(react@19.1.0)':
|
'@radix-ui/react-slot@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
'@radix-ui/react-compose-refs': 1.0.0(react@19.1.0)
|
'@radix-ui/react-compose-refs': 1.0.0(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-use-callback-ref@1.0.0(react@19.1.0)':
|
'@radix-ui/react-use-callback-ref@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-use-controllable-state@1.0.0(react@19.1.0)':
|
'@radix-ui/react-use-controllable-state@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@19.1.0)
|
'@radix-ui/react-use-callback-ref': 1.0.0(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-use-escape-keydown@1.0.0(react@19.1.0)':
|
'@radix-ui/react-use-escape-keydown@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@19.1.0)
|
'@radix-ui/react-use-callback-ref': 1.0.0(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
'@radix-ui/react-use-layout-effect@1.0.0(react@19.1.0)':
|
'@radix-ui/react-use-layout-effect@1.0.0(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-use-rect@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/react-use-size@1.1.1(@types/react@18.3.23)(react@19.1.0)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.23
|
||||||
|
|
||||||
|
'@radix-ui/rect@1.1.1': {}
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.9': {}
|
'@rolldown/pluginutils@1.0.0-beta.9': {}
|
||||||
|
|
||||||
'@rollup/plugin-babel@5.3.1(@babel/core@7.27.1)(@types/babel__core@7.20.5)(rollup@2.79.2)':
|
'@rollup/plugin-babel@5.3.1(@babel/core@7.27.1)(@types/babel__core@7.20.5)(rollup@2.79.2)':
|
||||||
@@ -9867,14 +10371,6 @@ snapshots:
|
|||||||
- bluebird
|
- bluebird
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
mantine-contextmenu@8.2.0(@mantine/core@8.2.8(@mantine/hooks@8.2.8(react@19.1.0))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.2.8(react@19.1.0))(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
'@mantine/core': 8.2.8(@mantine/hooks@8.2.8(react@19.1.0))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
'@mantine/hooks': 8.2.8(react@19.1.0)
|
|
||||||
clsx: 2.1.1
|
|
||||||
react: 19.1.0
|
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
|
|
||||||
map-stream@0.1.0: {}
|
map-stream@0.1.0: {}
|
||||||
|
|
||||||
matcher-collection@2.0.1:
|
matcher-collection@2.0.1:
|
||||||
@@ -10389,6 +10885,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
react-call@1.8.1(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
react-dom@19.1.0(react@19.1.0):
|
react-dom@19.1.0(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
"moveToNext": "move to next",
|
"moveToNext": "move to next",
|
||||||
"moveToBottom": "move to bottom",
|
"moveToBottom": "move to bottom",
|
||||||
"moveToTop": "move to top",
|
"moveToTop": "move to top",
|
||||||
|
"moveItems": "move items",
|
||||||
|
"shuffle": "shuffle",
|
||||||
|
"shuffleAll": "shuffle all",
|
||||||
|
"shuffleSelected": "shuffle selected",
|
||||||
"refresh": "$t(common.refresh)",
|
"refresh": "$t(common.refresh)",
|
||||||
"removeFromFavorites": "remove from $t(entity.favorite_other)",
|
"removeFromFavorites": "remove from $t(entity.favorite_other)",
|
||||||
"removeFromPlaylist": "remove from $t(entity.playlist_one)",
|
"removeFromPlaylist": "remove from $t(entity.playlist_one)",
|
||||||
@@ -366,6 +370,7 @@
|
|||||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||||
"deselectAll": "$t(action.deselectAll)",
|
"deselectAll": "$t(action.deselectAll)",
|
||||||
"download": "download",
|
"download": "download",
|
||||||
|
"moveItems": "$t(action.moveItems)",
|
||||||
"moveToNext": "$t(action.moveToNext)",
|
"moveToNext": "$t(action.moveToNext)",
|
||||||
"moveToBottom": "$t(action.moveToBottom)",
|
"moveToBottom": "$t(action.moveToBottom)",
|
||||||
"moveToTop": "$t(action.moveToTop)",
|
"moveToTop": "$t(action.moveToTop)",
|
||||||
@@ -378,6 +383,7 @@
|
|||||||
"setRating": "$t(action.setRating)",
|
"setRating": "$t(action.setRating)",
|
||||||
"playShuffled": "$t(player.shuffle)",
|
"playShuffled": "$t(player.shuffle)",
|
||||||
"shareItem": "share item",
|
"shareItem": "share item",
|
||||||
|
"goTo": "go to",
|
||||||
"goToAlbum": "go to $t(entity.album_one)",
|
"goToAlbum": "go to $t(entity.album_one)",
|
||||||
"goToAlbumArtist": "go to $t(entity.albumArtist_one)",
|
"goToAlbumArtist": "go to $t(entity.albumArtist_one)",
|
||||||
"showDetails": "get info"
|
"showDetails": "get info"
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import { AppRouter } from '/@/renderer/router/app-router';
|
|||||||
import { useCssSettings, useHotkeySettings, useSettingsStore } from '/@/renderer/store';
|
import { useCssSettings, useHotkeySettings, useSettingsStore } from '/@/renderer/store';
|
||||||
import { useAppTheme } from '/@/renderer/themes/use-app-theme';
|
import { useAppTheme } from '/@/renderer/themes/use-app-theme';
|
||||||
import { sanitizeCss } from '/@/renderer/utils/sanitize';
|
import { sanitizeCss } from '/@/renderer/utils/sanitize';
|
||||||
import '/styles/overlayscrollbars.css';
|
|
||||||
import { WebAudio } from '/@/shared/types/types';
|
import { WebAudio } from '/@/shared/types/types';
|
||||||
|
import '/styles/overlayscrollbars.css';
|
||||||
|
|
||||||
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
.container {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
padding: var(--theme-spacing-xs);
|
|
||||||
background: var(--theme-colors-surface);
|
|
||||||
border: 1px solid var(--theme-colors-border);
|
|
||||||
border-radius: var(--theme-radius-md);
|
|
||||||
box-shadow: 2px 2px 10px 2px rgb(0 0 0 / 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu-button {
|
|
||||||
display: flex;
|
|
||||||
padding: var(--theme-spacing-sm);
|
|
||||||
font-family: var(--theme-content-font-family);
|
|
||||||
font-size: var(--theme-font-size-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--theme-colors-surface-foreground);
|
|
||||||
text-align: left;
|
|
||||||
cursor: default;
|
|
||||||
background: var(--theme-colors-surface);
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: lighten(var(--theme-colors-surface), 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background: transparent;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
margin-right: 3rem;
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { motion, Variants } from 'motion/react';
|
|
||||||
import { ComponentPropsWithoutRef, forwardRef, ReactNode, Ref } from 'react';
|
|
||||||
|
|
||||||
import styles from './context-menu.module.css';
|
|
||||||
|
|
||||||
import { Group } from '/@/shared/components/group/group';
|
|
||||||
|
|
||||||
interface ContextMenuProps {
|
|
||||||
children: ReactNode;
|
|
||||||
maxWidth?: number;
|
|
||||||
minWidth?: number;
|
|
||||||
xPos: number;
|
|
||||||
yPos: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ContextMenuButton = forwardRef(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
children,
|
|
||||||
leftIcon,
|
|
||||||
rightIcon,
|
|
||||||
...props
|
|
||||||
}: ComponentPropsWithoutRef<'button'> & {
|
|
||||||
leftIcon?: ReactNode;
|
|
||||||
rightIcon?: ReactNode;
|
|
||||||
},
|
|
||||||
ref: any,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
{...props}
|
|
||||||
className={styles.contextMenuButton}
|
|
||||||
disabled={props.disabled}
|
|
||||||
key={props.key}
|
|
||||||
onClick={props.onClick}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
<Group justify="space-between" w="100%">
|
|
||||||
<Group className={styles.left} gap="md">
|
|
||||||
{leftIcon}
|
|
||||||
{children}
|
|
||||||
</Group>
|
|
||||||
{rightIcon}
|
|
||||||
</Group>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const variants: Variants = {
|
|
||||||
closed: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: {
|
|
||||||
duration: 0.1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
open: {
|
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
|
||||||
duration: 0.1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ContextMenu = forwardRef(
|
|
||||||
({ children, maxWidth, minWidth, xPos, yPos }: ContextMenuProps, ref: Ref<HTMLDivElement>) => {
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
animate="open"
|
|
||||||
className={styles.container}
|
|
||||||
initial="closed"
|
|
||||||
ref={ref}
|
|
||||||
style={{
|
|
||||||
left: xPos,
|
|
||||||
maxWidth,
|
|
||||||
minWidth,
|
|
||||||
top: yPos,
|
|
||||||
}}
|
|
||||||
variants={variants}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -4,6 +4,7 @@ import { useNavigate } from 'react-router';
|
|||||||
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
||||||
import { ItemListStateItemWithRequiredProperties } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import { ItemListStateItemWithRequiredProperties } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { DefaultItemControlProps, ItemControls } from '/@/renderer/components/item-list/types';
|
import { DefaultItemControlProps, ItemControls } from '/@/renderer/components/item-list/types';
|
||||||
|
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { Play, TableColumn } from '/@/shared/types/types';
|
import { Play, TableColumn } from '/@/shared/types/types';
|
||||||
@@ -230,8 +231,38 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
|
|||||||
player.setFavorite(item._serverId, [item.id], itemType, favorite);
|
player.setFavorite(item._serverId, [item.id], itemType, favorite);
|
||||||
},
|
},
|
||||||
|
|
||||||
onMore: ({ internalState, item, itemType }: DefaultItemControlProps) => {
|
onMore: ({ event, internalState, item, itemType }: DefaultItemControlProps) => {
|
||||||
console.log('handleItemMore', item, itemType, internalState);
|
if (!item || !internalState || !event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowId = internalState.extractRowId(item);
|
||||||
|
|
||||||
|
if (!rowId) return;
|
||||||
|
|
||||||
|
// If none selected, select this item
|
||||||
|
if (internalState.getSelected().length === 0) {
|
||||||
|
internalState.setSelected([item]);
|
||||||
|
return ContextMenuController.call({
|
||||||
|
cmd: { items: [item] as any[], type: itemType as any },
|
||||||
|
event,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If this item is not already selected, replace the selection with this item
|
||||||
|
else if (!internalState.isSelected(rowId)) {
|
||||||
|
internalState.setSelected([item]);
|
||||||
|
return ContextMenuController.call({
|
||||||
|
cmd: { items: [item] as any[], type: itemType as any },
|
||||||
|
event,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedItems = internalState.getSelected();
|
||||||
|
|
||||||
|
return ContextMenuController.call({
|
||||||
|
cmd: { items: selectedItems as any[], type: itemType as any },
|
||||||
|
event,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onPlay: ({
|
onPlay: ({
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
import { openContextModal } from '@mantine/modals';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAlbumArtistSongsById,
|
||||||
|
getAlbumSongsById,
|
||||||
|
getGenreSongsById,
|
||||||
|
getPlaylistSongsById,
|
||||||
|
} from '/@/renderer/features/player/utils';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
|
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
||||||
|
import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-to-playlist-mutation';
|
||||||
|
import { useCurrentServer, useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
|
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||||
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
|
import { LibraryItem, PlaylistListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface AddToPlaylistActionProps {
|
||||||
|
items: string[];
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const addToPlaylistMutation = useAddToPlaylist({});
|
||||||
|
|
||||||
|
const playlistsQuery = useQuery(
|
||||||
|
playlistsQueries.list({
|
||||||
|
query: {
|
||||||
|
_custom: {
|
||||||
|
navidrome: {
|
||||||
|
smart: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sortBy: PlaylistListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { recentPlaylistId } = useRecentPlaylists(serverId);
|
||||||
|
|
||||||
|
const fuse = useMemo(() => {
|
||||||
|
if (!playlistsQuery.data?.items) return null;
|
||||||
|
|
||||||
|
return new Fuse(playlistsQuery.data.items, {
|
||||||
|
fieldNormWeight: 1,
|
||||||
|
ignoreLocation: true,
|
||||||
|
keys: ['name'],
|
||||||
|
threshold: 0.3,
|
||||||
|
});
|
||||||
|
}, [playlistsQuery.data?.items]);
|
||||||
|
|
||||||
|
const recentPlaylist = useMemo(() => {
|
||||||
|
if (!playlistsQuery.data?.items || !recentPlaylistId) return null;
|
||||||
|
|
||||||
|
const playlist = playlistsQuery.data.items.find((p) => p.id === recentPlaylistId);
|
||||||
|
if (!playlist) return null;
|
||||||
|
|
||||||
|
if (searchTerm && fuse) {
|
||||||
|
const results = fuse.search(searchTerm);
|
||||||
|
const found = results.find((result) => result.item.id === recentPlaylistId);
|
||||||
|
if (!found) return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
}, [playlistsQuery.data?.items, recentPlaylistId, searchTerm, fuse]);
|
||||||
|
|
||||||
|
const filteredPlaylists = useMemo(() => {
|
||||||
|
if (!playlistsQuery.data?.items) return [];
|
||||||
|
if (!searchTerm || !fuse) {
|
||||||
|
// Exclude recent playlist from the list if it exists
|
||||||
|
return recentPlaylistId
|
||||||
|
? playlistsQuery.data.items.filter((p) => p.id !== recentPlaylistId)
|
||||||
|
: playlistsQuery.data.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = fuse.search(searchTerm);
|
||||||
|
const filtered = results.map((result) => result.item);
|
||||||
|
// Exclude recent playlist from the filtered results if it exists
|
||||||
|
return recentPlaylistId ? filtered.filter((p) => p.id !== recentPlaylistId) : filtered;
|
||||||
|
}, [playlistsQuery.data?.items, searchTerm, fuse, recentPlaylistId]);
|
||||||
|
|
||||||
|
const getSongsByAlbum = useCallback(
|
||||||
|
async (albumId: string) => {
|
||||||
|
if (!server) return null;
|
||||||
|
return getAlbumSongsById({
|
||||||
|
id: [albumId],
|
||||||
|
queryClient,
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[queryClient, server],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getSongsByArtist = useCallback(
|
||||||
|
async (artistId: string) => {
|
||||||
|
if (!server) return null;
|
||||||
|
return getAlbumArtistSongsById({
|
||||||
|
id: [artistId],
|
||||||
|
queryClient,
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[queryClient, server],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getSongsByGenre = useCallback(
|
||||||
|
async (genreIds: string[]) => {
|
||||||
|
if (!server) return null;
|
||||||
|
return getGenreSongsById({
|
||||||
|
id: genreIds,
|
||||||
|
queryClient,
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[queryClient, server],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getSongsByPlaylist = useCallback(
|
||||||
|
async (playlistId: string) => {
|
||||||
|
if (!server) return null;
|
||||||
|
return getPlaylistSongsById({
|
||||||
|
id: playlistId,
|
||||||
|
queryClient,
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[queryClient, server],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddToPlaylist = useCallback(
|
||||||
|
async (playlistId: string) => {
|
||||||
|
if (items.length === 0 || !serverId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let allSongIds: string[] = [];
|
||||||
|
|
||||||
|
if (itemType === LibraryItem.SONG) {
|
||||||
|
allSongIds = items;
|
||||||
|
} else if (itemType === LibraryItem.ALBUM) {
|
||||||
|
for (const id of items) {
|
||||||
|
const songs = await getSongsByAlbum(id);
|
||||||
|
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
itemType === LibraryItem.ALBUM_ARTIST ||
|
||||||
|
itemType === LibraryItem.ARTIST
|
||||||
|
) {
|
||||||
|
for (const id of items) {
|
||||||
|
const songs = await getSongsByArtist(id);
|
||||||
|
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||||
|
}
|
||||||
|
} else if (itemType === LibraryItem.GENRE) {
|
||||||
|
const songs = await getSongsByGenre(items);
|
||||||
|
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||||
|
} else if (itemType === LibraryItem.PLAYLIST) {
|
||||||
|
for (const id of items) {
|
||||||
|
const songs = await getSongsByPlaylist(id);
|
||||||
|
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allSongIds.length === 0) {
|
||||||
|
toast.error({
|
||||||
|
message: t('error.noItemsSelected', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToPlaylistMutation.mutate(
|
||||||
|
{
|
||||||
|
apiClientProps: { serverId },
|
||||||
|
body: {
|
||||||
|
songId: allSongIds,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
id: playlistId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error({
|
||||||
|
message: err.message,
|
||||||
|
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error({
|
||||||
|
message: (error as Error).message,
|
||||||
|
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
addToPlaylistMutation,
|
||||||
|
getSongsByAlbum,
|
||||||
|
getSongsByArtist,
|
||||||
|
getSongsByGenre,
|
||||||
|
getSongsByPlaylist,
|
||||||
|
itemType,
|
||||||
|
items,
|
||||||
|
serverId,
|
||||||
|
t,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOpenModal = useCallback(() => {
|
||||||
|
const modalProps: {
|
||||||
|
albumId?: string[];
|
||||||
|
artistId?: string[];
|
||||||
|
genreId?: string[];
|
||||||
|
initialSelectedIds?: string[];
|
||||||
|
playlistId?: string[];
|
||||||
|
songId?: string[];
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
switch (itemType) {
|
||||||
|
case LibraryItem.ALBUM:
|
||||||
|
modalProps.albumId = items;
|
||||||
|
break;
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
case LibraryItem.ARTIST:
|
||||||
|
modalProps.artistId = items;
|
||||||
|
break;
|
||||||
|
case LibraryItem.GENRE:
|
||||||
|
modalProps.genreId = items;
|
||||||
|
break;
|
||||||
|
case LibraryItem.PLAYLIST:
|
||||||
|
modalProps.playlistId = items;
|
||||||
|
break;
|
||||||
|
case LibraryItem.PLAYLIST_SONG:
|
||||||
|
case LibraryItem.QUEUE_SONG:
|
||||||
|
case LibraryItem.SONG:
|
||||||
|
modalProps.songId = items;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openContextModal({
|
||||||
|
innerProps: {
|
||||||
|
itemIds: items,
|
||||||
|
resourceType: itemType,
|
||||||
|
},
|
||||||
|
modal: 'addToPlaylist',
|
||||||
|
title: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
}, [itemType, items, t]);
|
||||||
|
|
||||||
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
|
const searchInput = (
|
||||||
|
<TextInput
|
||||||
|
autoFocus
|
||||||
|
leftSection={<Icon icon="search" />}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
pb="xs"
|
||||||
|
placeholder={t('common.search', { postProcess: 'sentenceCase' })}
|
||||||
|
size="sm"
|
||||||
|
value={searchTerm}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu isCloseDisabled>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="playlist"
|
||||||
|
onSelect={handleOpenModal}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent stickyContent={searchInput}>
|
||||||
|
{playlistsQuery.isLoading && (
|
||||||
|
<ContextMenu.Item disabled>
|
||||||
|
<Spinner container />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
)}
|
||||||
|
{playlistsQuery.isError && (
|
||||||
|
<ContextMenu.Item disabled>
|
||||||
|
{t('error.genericError', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
)}
|
||||||
|
{recentPlaylist && (
|
||||||
|
<>
|
||||||
|
<ContextMenu.Item
|
||||||
|
key={recentPlaylist.id}
|
||||||
|
onSelect={() => handleAddToPlaylist(recentPlaylist.id)}
|
||||||
|
>
|
||||||
|
{recentPlaylist.name}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
{filteredPlaylists.length > 0 && <ContextMenu.Divider />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{filteredPlaylists.length === 0 && !playlistsQuery.isLoading && (
|
||||||
|
<ContextMenu.Item disabled>
|
||||||
|
{t('common.noResultsFromQuery', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
)}
|
||||||
|
{filteredPlaylists.map((playlist) => (
|
||||||
|
<ContextMenu.Item
|
||||||
|
key={playlist.id}
|
||||||
|
onSelect={() => handleAddToPlaylist(playlist.id)}
|
||||||
|
>
|
||||||
|
{playlist.name}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
))}
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
|
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { ConfirmModal } from '/@/shared/components/modal/modal';
|
||||||
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
|
import { Playlist } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface DeletePlaylistActionProps {
|
||||||
|
items: Playlist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeletePlaylistAction = ({ items }: DeletePlaylistActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
const deletePlaylistMutation = useDeletePlaylist({});
|
||||||
|
|
||||||
|
const handleDeletePlaylist = useCallback(() => {
|
||||||
|
if (items.length === 0 || !serverId) return;
|
||||||
|
|
||||||
|
const playlist = items[0];
|
||||||
|
|
||||||
|
deletePlaylistMutation.mutate(
|
||||||
|
{
|
||||||
|
apiClientProps: { serverId },
|
||||||
|
query: { id: playlist.id },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error({
|
||||||
|
message: err.message,
|
||||||
|
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
navigate(AppRoute.PLAYLISTS, { replace: true });
|
||||||
|
toast.success({
|
||||||
|
message: t('action.deletePlaylist', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
closeAllModals();
|
||||||
|
}, [deletePlaylistMutation, items, navigate, serverId, t]);
|
||||||
|
|
||||||
|
const openDeletePlaylistModal = useCallback(() => {
|
||||||
|
if (items.length === 0) return;
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
children: (
|
||||||
|
<ConfirmModal onConfirm={handleDeletePlaylist}>
|
||||||
|
{t('common.areYouSure', { postProcess: 'sentenceCase' })}
|
||||||
|
</ConfirmModal>
|
||||||
|
),
|
||||||
|
title: t('form.deletePlaylist.title', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
}, [handleDeletePlaylist, items.length, t]);
|
||||||
|
|
||||||
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Item leftIcon="remove" onSelect={openDeletePlaylistModal}>
|
||||||
|
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import isElectron from 'is-electron';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
|
|
||||||
|
interface DownloadActionProps {
|
||||||
|
ids: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const utils = isElectron() ? window.api.utils : null;
|
||||||
|
|
||||||
|
export const DownloadAction = ({ ids }: DownloadActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
|
const onSelect = useCallback(async () => {
|
||||||
|
if (!utils) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const id of ids) {
|
||||||
|
const downloadUrl = api.controller.getDownloadUrl({
|
||||||
|
apiClientProps: { serverId: server.id },
|
||||||
|
query: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
utils.download(downloadUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success({
|
||||||
|
message: t('action.downloadStarted', {
|
||||||
|
count: ids.length,
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download items:', error);
|
||||||
|
}
|
||||||
|
}, [ids, server, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Item disabled={ids.length > 1} leftIcon="download" onSelect={onSelect}>
|
||||||
|
{t('page.contextMenu.download', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { openModal } from '@mantine/modals';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ItemDetailsModal,
|
||||||
|
ItemDetailsModalProps,
|
||||||
|
} from '/@/renderer/features/item-details/components/item-details-modal';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
|
||||||
|
interface GetInfoActionProps {
|
||||||
|
disabled?: boolean;
|
||||||
|
item: ItemDetailsModalProps['item'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetInfoAction = ({ disabled, item }: GetInfoActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
|
const onSelect = useCallback(async () => {
|
||||||
|
if (!server) return;
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
children: <ItemDetailsModal item={item} />,
|
||||||
|
size: 'lg',
|
||||||
|
title: item.name || t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
}, [item, server, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Item disabled={disabled} leftIcon="info" onSelect={onSelect}>
|
||||||
|
{t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { generatePath, useNavigate } from 'react-router';
|
||||||
|
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import {
|
||||||
|
Album,
|
||||||
|
AlbumArtist,
|
||||||
|
Artist,
|
||||||
|
LibraryItem,
|
||||||
|
QueueSong,
|
||||||
|
Song,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface GoToActionProps {
|
||||||
|
items: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GoToAction = ({ items }: GoToActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { albumArtists, albumId } = useMemo(() => {
|
||||||
|
const firstItem = items[0];
|
||||||
|
|
||||||
|
if (firstItem._itemType === LibraryItem.ALBUM) {
|
||||||
|
return {
|
||||||
|
albumArtists: firstItem.albumArtists || [],
|
||||||
|
albumId: firstItem.id,
|
||||||
|
};
|
||||||
|
} else if (firstItem._itemType === LibraryItem.SONG) {
|
||||||
|
return {
|
||||||
|
albumArtists: firstItem.albumArtists || [],
|
||||||
|
albumId: firstItem.albumId,
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
firstItem._itemType === LibraryItem.ARTIST ||
|
||||||
|
firstItem._itemType === LibraryItem.ALBUM_ARTIST
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
albumArtists: [{ id: firstItem.id, name: firstItem.name }],
|
||||||
|
albumId: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
albumArtists: [],
|
||||||
|
albumId: null,
|
||||||
|
};
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
const handleGoToAlbum = useCallback(() => {
|
||||||
|
if (!albumId) return;
|
||||||
|
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId }));
|
||||||
|
}, [albumId, navigate]);
|
||||||
|
|
||||||
|
const handleGoToAlbumArtist = useCallback(
|
||||||
|
(albumArtistId: string) => {
|
||||||
|
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId }));
|
||||||
|
},
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAlbum = !!albumId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu disabled={items.length !== 1}>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="externalLink"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('page.contextMenu.goTo', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent>
|
||||||
|
{hasAlbum && (
|
||||||
|
<ContextMenu.Item leftIcon="album" onSelect={handleGoToAlbum}>
|
||||||
|
{t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
)}
|
||||||
|
{albumArtists.map((albumArtist) => (
|
||||||
|
<ContextMenu.Item
|
||||||
|
key={albumArtist.id}
|
||||||
|
leftIcon="artist"
|
||||||
|
onSelect={() => handleGoToAlbumArtist(albumArtist.id)}
|
||||||
|
>
|
||||||
|
{`${t('page.contextMenu.goTo', { postProcess: 'sentenceCase' })} ${albumArtist.name}`}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
))}
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface MoveQueueItemsActionProps {
|
||||||
|
items: QueueSong[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MoveQueueItemsAction = ({ items }: MoveQueueItemsActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const player = usePlayer();
|
||||||
|
|
||||||
|
const handleMoveToTop = useCallback(() => {
|
||||||
|
player.moveSelectedToTop(items);
|
||||||
|
}, [items, player]);
|
||||||
|
|
||||||
|
const handleMoveToNext = useCallback(() => {
|
||||||
|
player.moveSelectedToNext(items);
|
||||||
|
}, [items, player]);
|
||||||
|
|
||||||
|
const handleMoveToBottom = useCallback(() => {
|
||||||
|
player.moveSelectedToBottom(items);
|
||||||
|
}, [items, player]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="dragVertical"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('page.contextMenu.moveItems', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent>
|
||||||
|
<ContextMenu.Item leftIcon="arrowUpToLine" onSelect={handleMoveToTop}>
|
||||||
|
{t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handleMoveToNext}>
|
||||||
|
{t('page.contextMenu.moveToNext', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item leftIcon="arrowDownToLine" onSelect={handleMoveToBottom}>
|
||||||
|
{t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
interface PlayActionProps {
|
||||||
|
ids: string[];
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlayAction = ({ ids, itemType }: PlayActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const player = usePlayer();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
|
||||||
|
const handlePlay = useCallback(
|
||||||
|
(playType: Play) => {
|
||||||
|
if (ids.length === 0 || !serverId) return;
|
||||||
|
player.addToQueueByFetch(serverId, ids, itemType, playType);
|
||||||
|
},
|
||||||
|
[ids, itemType, player, serverId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePlayNow = useCallback(() => {
|
||||||
|
handlePlay(Play.NOW);
|
||||||
|
}, [handlePlay]);
|
||||||
|
|
||||||
|
const handlePlayNext = useCallback(() => {
|
||||||
|
handlePlay(Play.NEXT);
|
||||||
|
}, [handlePlay]);
|
||||||
|
|
||||||
|
const handlePlayLast = useCallback(() => {
|
||||||
|
handlePlay(Play.LAST);
|
||||||
|
}, [handlePlay]);
|
||||||
|
|
||||||
|
const handlePlayShuffled = useCallback(() => {
|
||||||
|
handlePlay(Play.SHUFFLE);
|
||||||
|
}, [handlePlay]);
|
||||||
|
|
||||||
|
if (ids.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="mediaPlay"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent>
|
||||||
|
<ContextMenu.Item leftIcon="mediaPlay" onSelect={handlePlayNow}>
|
||||||
|
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item leftIcon="mediaPlayNext" onSelect={handlePlayNext}>
|
||||||
|
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item leftIcon="mediaPlayLast" onSelect={handlePlayLast}>
|
||||||
|
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item leftIcon="mediaShuffle" onSelect={handlePlayShuffled}>
|
||||||
|
{t('player.shuffle', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
|
import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation';
|
||||||
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
|
import { Song } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface RemoveFromPlaylistActionProps {
|
||||||
|
items: Song[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RemoveFromPlaylistAction = ({ items }: RemoveFromPlaylistActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
const { playlistId } = useParams() as { playlistId?: string };
|
||||||
|
const removeFromPlaylistMutation = useRemoveFromPlaylist();
|
||||||
|
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
const handleRemoveFromPlaylist = useCallback(() => {
|
||||||
|
if (ids.length === 0 || !serverId || !playlistId) return;
|
||||||
|
|
||||||
|
removeFromPlaylistMutation.mutate(
|
||||||
|
{
|
||||||
|
apiClientProps: { serverId },
|
||||||
|
query: {
|
||||||
|
id: playlistId,
|
||||||
|
songId: ids,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error({
|
||||||
|
message: err.message,
|
||||||
|
title: t('error.genericError', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success({
|
||||||
|
message: t('action.removeFromPlaylist', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, [ids, playlistId, removeFromPlaylistMutation, serverId, t]);
|
||||||
|
|
||||||
|
if (ids.length === 0 || !playlistId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Item leftIcon="remove" onSelect={handleRemoveFromPlaylist}>
|
||||||
|
{t('action.removeFromPlaylist', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface RemoveFromQueueActionProps {
|
||||||
|
items: QueueSong[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RemoveFromQueueAction = ({ items }: RemoveFromQueueActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const player = usePlayer();
|
||||||
|
|
||||||
|
const onSelect = useCallback(() => {
|
||||||
|
player.clearSelected(items);
|
||||||
|
}, [items, player]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Item leftIcon="remove" onSelect={onSelect}>
|
||||||
|
{t('action.removeFromQueue', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
|
||||||
|
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
|
||||||
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface SetFavoriteActionProps {
|
||||||
|
ids: string[];
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SetFavoriteAction = ({ ids, itemType }: SetFavoriteActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
|
||||||
|
const createFavoriteMutation = useCreateFavorite({});
|
||||||
|
const deleteFavoriteMutation = useDeleteFavorite({});
|
||||||
|
|
||||||
|
const handleAddToFavorites = useCallback(() => {
|
||||||
|
if (ids.length === 0 || !serverId) return;
|
||||||
|
|
||||||
|
createFavoriteMutation.mutate({
|
||||||
|
apiClientProps: { serverId },
|
||||||
|
query: {
|
||||||
|
id: ids,
|
||||||
|
type: itemType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [createFavoriteMutation, ids, itemType, serverId]);
|
||||||
|
|
||||||
|
const handleRemoveFromFavorites = useCallback(() => {
|
||||||
|
if (ids.length === 0 || !serverId) return;
|
||||||
|
|
||||||
|
deleteFavoriteMutation.mutate({
|
||||||
|
apiClientProps: { serverId },
|
||||||
|
query: {
|
||||||
|
id: ids,
|
||||||
|
type: itemType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [deleteFavoriteMutation, ids, itemType, serverId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="favorite"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('common.favorite', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent>
|
||||||
|
<ContextMenu.Item leftIcon="favorite" onSelect={handleAddToFavorites}>
|
||||||
|
{t('action.addToFavorites', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item leftIcon="unfavorite" onSelect={handleRemoveFromFavorites}>
|
||||||
|
{t('action.removeFromFavorites', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
|
||||||
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { Rating } from '/@/shared/components/rating/rating';
|
||||||
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface SetRatingActionProps {
|
||||||
|
ids: string[];
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SetRatingAction = ({ ids, itemType }: SetRatingActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
|
||||||
|
const setRatingMutation = useSetRating({});
|
||||||
|
|
||||||
|
const onRating = (rating: number) => {
|
||||||
|
setRatingMutation.mutate({
|
||||||
|
apiClientProps: { serverId },
|
||||||
|
query: {
|
||||||
|
id: ids,
|
||||||
|
rating,
|
||||||
|
type: itemType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="star"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('action.setRating', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent>
|
||||||
|
<ContextMenu.Item onSelect={() => onRating(0)}>
|
||||||
|
<Rating readOnly value={0} />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item onSelect={() => onRating(1)}>
|
||||||
|
<Rating readOnly value={1} />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item onSelect={() => onRating(2)}>
|
||||||
|
<Rating readOnly value={2} />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item onSelect={() => onRating(3)}>
|
||||||
|
<Rating readOnly value={3} />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item onSelect={() => onRating(4)}>
|
||||||
|
<Rating readOnly value={4} />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item onSelect={() => onRating(5)}>
|
||||||
|
<Rating readOnly value={5} />
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { openContextModal } from '@mantine/modals';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface ShareActionProps {
|
||||||
|
ids: string[];
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShareAction = ({ ids, itemType }: ShareActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const resourceType = useMemo(() => {
|
||||||
|
switch (itemType) {
|
||||||
|
case LibraryItem.ALBUM:
|
||||||
|
return 'album';
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
return 'albumArtist';
|
||||||
|
case LibraryItem.PLAYLIST:
|
||||||
|
return 'playlist';
|
||||||
|
case LibraryItem.SONG:
|
||||||
|
return 'song';
|
||||||
|
default:
|
||||||
|
return 'song';
|
||||||
|
}
|
||||||
|
}, [itemType]);
|
||||||
|
|
||||||
|
const onSelect = useCallback(() => {
|
||||||
|
openContextModal({
|
||||||
|
innerProps: {
|
||||||
|
itemIds: ids,
|
||||||
|
resourceType,
|
||||||
|
},
|
||||||
|
modal: 'shareItem',
|
||||||
|
title: t('page.contextMenu.shareItem', { postProcess: 'titleCase' }),
|
||||||
|
});
|
||||||
|
}, [ids, resourceType, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Item leftIcon="share" onSelect={onSelect}>
|
||||||
|
{t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface ShuffleItemsActionProps {
|
||||||
|
items: QueueSong[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShuffleItemsAction = ({ items }: ShuffleItemsActionProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const player = usePlayer();
|
||||||
|
|
||||||
|
const handleShuffleSelected = useCallback(() => {
|
||||||
|
player.shuffleSelected(items);
|
||||||
|
}, [items, player]);
|
||||||
|
|
||||||
|
const handleShuffleAll = useCallback(() => {
|
||||||
|
player.shuffleAll();
|
||||||
|
}, [player]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Submenu>
|
||||||
|
<ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.Item
|
||||||
|
leftIcon="mediaShuffle"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
rightIcon="arrowRightS"
|
||||||
|
>
|
||||||
|
{t('action.shuffle', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuTarget>
|
||||||
|
<ContextMenu.SubmenuContent>
|
||||||
|
<ContextMenu.Item onSelect={handleShuffleSelected}>
|
||||||
|
{t('action.shuffleSelected', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item onSelect={handleShuffleAll}>
|
||||||
|
{t('action.shuffleAll', { postProcess: 'sentenceCase' })}
|
||||||
|
</ContextMenu.Item>
|
||||||
|
</ContextMenu.SubmenuContent>
|
||||||
|
</ContextMenu.Submenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { createCallable } from 'react-call';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
|
import { AlbumArtistContextMenu } from '/@/renderer/features/context-menu/menus/album-artist-context-menu';
|
||||||
|
import { AlbumContextMenu } from '/@/renderer/features/context-menu/menus/album-context-menu';
|
||||||
|
import { ArtistContextMenu } from '/@/renderer/features/context-menu/menus/artist-context-menu';
|
||||||
|
import { GenreContextMenu } from '/@/renderer/features/context-menu/menus/genre-context-menu';
|
||||||
|
import { PlaylistContextMenu } from '/@/renderer/features/context-menu/menus/playlist-context-menu';
|
||||||
|
import { PlaylistSongContextMenu } from '/@/renderer/features/context-menu/menus/playlist-song-context-menu';
|
||||||
|
import { QueueContextMenu } from '/@/renderer/features/context-menu/menus/queue-context-menu';
|
||||||
|
import { SongContextMenu } from '/@/renderer/features/context-menu/menus/song-context-menu';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import {
|
||||||
|
Album,
|
||||||
|
AlbumArtist,
|
||||||
|
Artist,
|
||||||
|
Genre,
|
||||||
|
LibraryItem,
|
||||||
|
Playlist,
|
||||||
|
QueueSong,
|
||||||
|
Song,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface ContextMenuControllerProps {
|
||||||
|
cmd: ContextMenuCommand;
|
||||||
|
event: React.MouseEvent<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContextMenuController = createCallable<ContextMenuControllerProps, void>(
|
||||||
|
({ call, cmd, event }) => {
|
||||||
|
const { libraryId } = useParams() as { libraryId: string };
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isExecuted = useRef<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isExecuted.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!triggerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleContextMenu = () => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
triggerRef.current?.dispatchEvent(
|
||||||
|
new MouseEvent('contextmenu', {
|
||||||
|
bubbles: true,
|
||||||
|
clientX: event.clientX,
|
||||||
|
clientY: event.clientY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
isExecuted.current = true;
|
||||||
|
|
||||||
|
handleContextMenu();
|
||||||
|
}, [call, cmd, event, event.clientX, event.clientY, libraryId, queryClient]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu>
|
||||||
|
<ContextMenu.Target>
|
||||||
|
<div
|
||||||
|
ref={triggerRef}
|
||||||
|
style={{
|
||||||
|
height: 0,
|
||||||
|
left: 0,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
userSelect: 'none',
|
||||||
|
width: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ContextMenu.Target>
|
||||||
|
{cmd.type === LibraryItem.QUEUE_SONG && <QueueContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.ALBUM && <AlbumContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.ALBUM_ARTIST && <AlbumArtistContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.ARTIST && <ArtistContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.GENRE && <GenreContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.PLAYLIST && <PlaylistContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.PLAYLIST_SONG && <PlaylistSongContextMenu {...cmd} />}
|
||||||
|
{cmd.type === LibraryItem.SONG && <SongContextMenu {...cmd} />}
|
||||||
|
</ContextMenu>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export type ContextMenuCommand =
|
||||||
|
| AlbumArtistContextMenuProps
|
||||||
|
| AlbumContextMenuProps
|
||||||
|
| ArtistContextMenuProps
|
||||||
|
| GenreContextMenuProps
|
||||||
|
| PlaylistContextMenuProps
|
||||||
|
| PlaylistSongContextMenuProps
|
||||||
|
| QueueSongContextMenuProps
|
||||||
|
| SongContextMenuProps;
|
||||||
|
|
||||||
|
type AlbumArtistContextMenuProps = {
|
||||||
|
items: AlbumArtist[];
|
||||||
|
type: LibraryItem.ALBUM_ARTIST;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AlbumContextMenuProps = {
|
||||||
|
items: Album[];
|
||||||
|
type: LibraryItem.ALBUM;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ArtistContextMenuProps = {
|
||||||
|
items: Artist[];
|
||||||
|
type: LibraryItem.ARTIST;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GenreContextMenuProps = {
|
||||||
|
items: Genre[];
|
||||||
|
type: LibraryItem.GENRE;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlaylistContextMenuProps = {
|
||||||
|
items: Playlist[];
|
||||||
|
type: LibraryItem.PLAYLIST;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlaylistSongContextMenuProps = {
|
||||||
|
items: Song[];
|
||||||
|
type: LibraryItem.PLAYLIST_SONG;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QueueSongContextMenuProps = {
|
||||||
|
items: QueueSong[];
|
||||||
|
type: LibraryItem.QUEUE_SONG;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SongContextMenuProps = {
|
||||||
|
items: Song[];
|
||||||
|
type: LibraryItem.SONG;
|
||||||
|
};
|
||||||
@@ -1,123 +1,364 @@
|
|||||||
import { SetContextMenuItems } from '/@/renderer/features/context-menu/events';
|
import React from 'react';
|
||||||
|
|
||||||
export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
import { AppIconSelection } from '/@/shared/components/icon/icon';
|
||||||
{ divider: true, id: 'removeFromQueue' },
|
|
||||||
{ id: 'moveToNextOfQueue' },
|
|
||||||
{ id: 'moveToBottomOfQueue' },
|
|
||||||
{ divider: true, id: 'moveToTopOfQueue' },
|
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
|
||||||
{ id: 'addToFavorites' },
|
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
|
||||||
{ disabled: false, divider: true, id: 'deselectAll' },
|
|
||||||
{ id: 'download' },
|
|
||||||
{ divider: true, id: 'shareItem' },
|
|
||||||
{ id: 'goToAlbum' },
|
|
||||||
{ id: 'goToAlbumArtist' },
|
|
||||||
{ divider: true, id: 'showDetails' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export enum ContextMenuItemKey {
|
||||||
{ id: 'play' },
|
ADD_TO_FAVORITES = 'addToFavorites',
|
||||||
{ id: 'playLast' },
|
ADD_TO_PLAYLIST = 'addToPlaylist',
|
||||||
{ id: 'playNext' },
|
CREATE_PLAYLIST = 'createPlaylist',
|
||||||
{ id: 'playShuffled' },
|
DELETE_PLAYLIST = 'deletePlaylist',
|
||||||
{ divider: true, id: 'playSimilarSongs' },
|
DESELECT_ALL = 'deselectAll',
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
DOWNLOAD = 'download',
|
||||||
{ id: 'addToFavorites' },
|
GO_TO_ALBUM = 'goToAlbum',
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
GO_TO_ALBUM_ARTIST = 'goToAlbumArtist',
|
||||||
{ children: true, disabled: false, divider: true, id: 'setRating' },
|
MOVE_TO_BOTTOM_OF_QUEUE = 'moveToBottomOfQueue',
|
||||||
{ id: 'download' },
|
MOVE_TO_NEXT_OF_QUEUE = 'moveToNextOfQueue',
|
||||||
{ divider: true, id: 'shareItem' },
|
MOVE_TO_TOP_OF_QUEUE = 'moveToTopOfQueue',
|
||||||
{ id: 'goToAlbum' },
|
PLAY = 'play',
|
||||||
{ id: 'goToAlbumArtist' },
|
PLAY_LAST = 'playLast',
|
||||||
{ divider: true, id: 'showDetails' },
|
PLAY_NEXT = 'playNext',
|
||||||
];
|
PLAY_SHUFFLED = 'playShuffled',
|
||||||
|
PLAY_SIMILAR_SONGS = 'playSimilarSongs',
|
||||||
|
REMOVE_FROM_FAVORITES = 'removeFromFavorites',
|
||||||
|
REMOVE_FROM_PLAYLIST = 'removeFromPlaylist',
|
||||||
|
REMOVE_FROM_QUEUE = 'removeFromQueue',
|
||||||
|
SET_RATING = 'setRating',
|
||||||
|
SET_RATING_1 = 'setRating1',
|
||||||
|
SET_RATING_2 = 'setRating2',
|
||||||
|
SET_RATING_3 = 'setRating3',
|
||||||
|
SET_RATING_4 = 'setRating4',
|
||||||
|
SET_RATING_5 = 'setRating5',
|
||||||
|
SHARE_ITEM = 'shareItem',
|
||||||
|
SHOW_DETAILS = 'showDetails',
|
||||||
|
}
|
||||||
|
|
||||||
export const SONG_ALBUM_PAGE: SetContextMenuItems = [
|
export type ContextMenuHandlers = Partial<Record<ContextMenuItemKeys, () => void>>;
|
||||||
{ id: 'play' },
|
|
||||||
{ id: 'playLast' },
|
|
||||||
{ id: 'playNext' },
|
|
||||||
{ divider: true, id: 'playShuffled' },
|
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export interface ContextMenuItem {
|
||||||
{ id: 'play' },
|
disabled?: boolean;
|
||||||
{ id: 'playLast' },
|
hidden?: boolean;
|
||||||
{ id: 'playNext' },
|
icon?: React.ReactNode;
|
||||||
{ id: 'playShuffled' },
|
items?: ContextMenuItem[];
|
||||||
{ divider: true, id: 'playSimilarSongs' },
|
key: string;
|
||||||
{ id: 'addToPlaylist' },
|
onClick?: () => void;
|
||||||
{ divider: true, id: 'removeFromPlaylist' },
|
}
|
||||||
{ id: 'addToFavorites' },
|
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
|
||||||
{ id: 'download' },
|
|
||||||
{ divider: true, id: 'shareItem' },
|
|
||||||
{ id: 'goToAlbum' },
|
|
||||||
{ id: 'goToAlbumArtist' },
|
|
||||||
{ divider: true, id: 'showDetails' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export type ContextMenuItemDefinition = {
|
||||||
{ id: 'play' },
|
children?: ContextMenuItemDefinition[];
|
||||||
{ id: 'playLast' },
|
disabled?: boolean;
|
||||||
{ id: 'playNext' },
|
key: ContextMenuItemKeys;
|
||||||
{ divider: true, id: 'playShuffled' },
|
};
|
||||||
{ divider: true, id: 'playSimilarSongs' },
|
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
|
||||||
{ id: 'addToFavorites' },
|
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
|
||||||
{ id: 'download' },
|
|
||||||
{ divider: true, id: 'shareItem' },
|
|
||||||
{ id: 'goToAlbum' },
|
|
||||||
{ id: 'goToAlbumArtist' },
|
|
||||||
{ divider: true, id: 'showDetails' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export type ContextMenuItemKeys =
|
||||||
{ id: 'play' },
|
| ContextMenuItemKey.ADD_TO_FAVORITES
|
||||||
{ id: 'playLast' },
|
| ContextMenuItemKey.ADD_TO_PLAYLIST
|
||||||
{ id: 'playNext' },
|
| ContextMenuItemKey.CREATE_PLAYLIST
|
||||||
{ divider: true, id: 'playShuffled' },
|
| ContextMenuItemKey.DELETE_PLAYLIST
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
| ContextMenuItemKey.DESELECT_ALL
|
||||||
{ id: 'addToFavorites' },
|
| ContextMenuItemKey.DOWNLOAD
|
||||||
{ id: 'removeFromFavorites' },
|
| ContextMenuItemKey.GO_TO_ALBUM
|
||||||
{ children: true, disabled: false, divider: true, id: 'setRating' },
|
| ContextMenuItemKey.GO_TO_ALBUM_ARTIST
|
||||||
{ divider: true, id: 'shareItem' },
|
| ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE
|
||||||
{ id: 'goToAlbumArtist' },
|
| ContextMenuItemKey.MOVE_TO_NEXT_OF_QUEUE
|
||||||
{ divider: true, id: 'showDetails' },
|
| ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE
|
||||||
];
|
| ContextMenuItemKey.PLAY
|
||||||
|
| ContextMenuItemKey.PLAY_LAST
|
||||||
|
| ContextMenuItemKey.PLAY_NEXT
|
||||||
|
| ContextMenuItemKey.PLAY_SHUFFLED
|
||||||
|
| ContextMenuItemKey.PLAY_SIMILAR_SONGS
|
||||||
|
| ContextMenuItemKey.REMOVE_FROM_FAVORITES
|
||||||
|
| ContextMenuItemKey.REMOVE_FROM_PLAYLIST
|
||||||
|
| ContextMenuItemKey.REMOVE_FROM_QUEUE
|
||||||
|
| ContextMenuItemKey.SET_RATING
|
||||||
|
| ContextMenuItemKey.SET_RATING_1
|
||||||
|
| ContextMenuItemKey.SET_RATING_2
|
||||||
|
| ContextMenuItemKey.SET_RATING_3
|
||||||
|
| ContextMenuItemKey.SET_RATING_4
|
||||||
|
| ContextMenuItemKey.SET_RATING_5
|
||||||
|
| ContextMenuItemKey.SHARE_ITEM
|
||||||
|
| ContextMenuItemKey.SHOW_DETAILS;
|
||||||
|
|
||||||
export const GENRE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export type ContextMenuItems = Array<ContextMenuItem>;
|
||||||
{ id: 'play' },
|
|
||||||
{ id: 'playLast' },
|
|
||||||
{ id: 'playNext' },
|
|
||||||
{ divider: true, id: 'playShuffled' },
|
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ARTIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
const ICON_MAP: Partial<Record<ContextMenuItemKeys, AppIconSelection>> = {
|
||||||
{ id: 'play' },
|
[ContextMenuItemKey.ADD_TO_FAVORITES]: 'favorite',
|
||||||
{ id: 'playLast' },
|
[ContextMenuItemKey.ADD_TO_PLAYLIST]: 'playlistAdd',
|
||||||
{ id: 'playNext' },
|
[ContextMenuItemKey.DELETE_PLAYLIST]: 'playlistDelete',
|
||||||
{ divider: true, id: 'playShuffled' },
|
[ContextMenuItemKey.DESELECT_ALL]: 'remove',
|
||||||
{ divider: true, id: 'addToPlaylist' },
|
[ContextMenuItemKey.DOWNLOAD]: 'download',
|
||||||
{ id: 'addToFavorites' },
|
[ContextMenuItemKey.GO_TO_ALBUM]: 'album',
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
[ContextMenuItemKey.GO_TO_ALBUM_ARTIST]: 'artist',
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
[ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE]: 'arrowDownToLine',
|
||||||
{ divider: true, id: 'shareItem' },
|
[ContextMenuItemKey.MOVE_TO_NEXT_OF_QUEUE]: 'mediaPlayNext',
|
||||||
{ divider: true, id: 'showDetails' },
|
[ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE]: 'arrowUpToLine',
|
||||||
];
|
[ContextMenuItemKey.PLAY]: 'mediaPlay',
|
||||||
|
[ContextMenuItemKey.PLAY_LAST]: 'mediaPlayLast',
|
||||||
|
[ContextMenuItemKey.PLAY_NEXT]: 'mediaPlayNext',
|
||||||
|
[ContextMenuItemKey.PLAY_SHUFFLED]: 'mediaShuffle',
|
||||||
|
[ContextMenuItemKey.PLAY_SIMILAR_SONGS]: 'radio',
|
||||||
|
[ContextMenuItemKey.REMOVE_FROM_FAVORITES]: 'unfavorite',
|
||||||
|
[ContextMenuItemKey.REMOVE_FROM_PLAYLIST]: 'playlistDelete',
|
||||||
|
[ContextMenuItemKey.REMOVE_FROM_QUEUE]: 'delete',
|
||||||
|
[ContextMenuItemKey.SET_RATING]: 'star',
|
||||||
|
[ContextMenuItemKey.SHARE_ITEM]: 'share',
|
||||||
|
[ContextMenuItemKey.SHOW_DETAILS]: 'info',
|
||||||
|
};
|
||||||
|
|
||||||
export const PLAYLIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
// export const convertToContextMenuItems = (
|
||||||
{ id: 'play' },
|
// definitions: ContextMenuItemDefinition[],
|
||||||
{ id: 'playLast' },
|
// handlers: ContextMenuHandlers,
|
||||||
{ id: 'playNext' },
|
// ): ContextMenuItemOptions[] => {
|
||||||
{ divider: true, id: 'playShuffled' },
|
// const items: ContextMenuItemOptions[] = [];
|
||||||
{ divider: true, id: 'shareItem' },
|
|
||||||
{ divider: true, id: 'deletePlaylist' },
|
// for (const def of definitions) {
|
||||||
{ divider: true, id: 'showDetails' },
|
// if ('divider' in def && def.divider) {
|
||||||
];
|
// items.push({ key: 'divider' });
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!('key' in def)) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const handler = handlers[def.key];
|
||||||
|
|
||||||
|
// if (!handler) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const icon = ICON_MAP[def.key];
|
||||||
|
// const menuItem: ContextMenuItemOptions = {
|
||||||
|
// disabled: def.disabled,
|
||||||
|
// icon: icon ? <Icon icon={icon} /> : undefined,
|
||||||
|
// key: def.key,
|
||||||
|
// onClick: handler,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// if (def.children) {
|
||||||
|
// menuItem.items = undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// items.push(menuItem);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Remove trailing divider
|
||||||
|
// const lastItem = items[items.length - 1];
|
||||||
|
// if (items.length > 0 && lastItem && 'type' in lastItem && lastItem.type === 'divider') {
|
||||||
|
// items.pop();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return items;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const QUEUE_CONTEXT_MENU_ITEMS = (): ContextMenuItemOptions[] => {
|
||||||
|
// return [
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_QUEUE },
|
||||||
|
// // { key: ContextMenuItemKey.MOVE_TO_NEXT_OF_QUEUE },
|
||||||
|
// // { key: ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE },
|
||||||
|
// // { key: 'divider_1' },
|
||||||
|
// // { key: ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE },
|
||||||
|
// // { key: 'divider_2' },
|
||||||
|
// // { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// // { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||||
|
// // { key: 'divider_3' },
|
||||||
|
// // { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||||
|
// // { key: ContextMenuItemKey.SET_RATING },
|
||||||
|
// // { key: ContextMenuItemKey.DESELECT_ALL },
|
||||||
|
// // { key: 'divider_4' },
|
||||||
|
// // { key: ContextMenuItemKey.DOWNLOAD },
|
||||||
|
// // { key: 'divider_5' },
|
||||||
|
// // { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// // { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||||
|
// // { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||||
|
// // { key: 'divider_6' },
|
||||||
|
// // { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const SONG_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// {
|
||||||
|
// children: [
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// ],
|
||||||
|
// key: ContextMenuItemKey.PLAY,
|
||||||
|
// },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SIMILAR_SONGS },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||||
|
// {
|
||||||
|
// children: [
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||||
|
// ],
|
||||||
|
// key: ContextMenuItemKey.SET_RATING,
|
||||||
|
// },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.DOWNLOAD },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const SONG_ALBUM_PAGE: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SIMILAR_SONGS },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||||
|
// {
|
||||||
|
// children: [
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||||
|
// ],
|
||||||
|
// key: ContextMenuItemKey.SET_RATING,
|
||||||
|
// },
|
||||||
|
// { key: ContextMenuItemKey.DOWNLOAD },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SIMILAR_SONGS },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||||
|
// {
|
||||||
|
// children: [
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||||
|
// ],
|
||||||
|
// key: ContextMenuItemKey.SET_RATING,
|
||||||
|
// },
|
||||||
|
// { key: ContextMenuItemKey.DOWNLOAD },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const ALBUM_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||||
|
// {
|
||||||
|
// children: [
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||||
|
// ],
|
||||||
|
// key: ContextMenuItemKey.SET_RATING,
|
||||||
|
// },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// { key: ContextMenuItemKey.GO_TO_ALBUM_ARTIST },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const GENRE_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const ARTIST_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.ADD_TO_FAVORITES },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.REMOVE_FROM_FAVORITES },
|
||||||
|
// {
|
||||||
|
// children: [
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_1 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_2 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_3 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_4 },
|
||||||
|
// { key: ContextMenuItemKey.SET_RATING_5 },
|
||||||
|
// ],
|
||||||
|
// key: ContextMenuItemKey.SET_RATING,
|
||||||
|
// },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// export const PLAYLIST_CONTEXT_MENU_ITEMS: ContextMenuItemDefinition[] = [
|
||||||
|
// { key: ContextMenuItemKey.PLAY },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_LAST },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_NEXT },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.PLAY_SHUFFLED },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHARE_ITEM },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.DELETE_PLAYLIST },
|
||||||
|
// { key: ContextMenuItemKey.DIVIDER },
|
||||||
|
// { key: ContextMenuItemKey.SHOW_DETAILS },
|
||||||
|
// ];
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,74 +8,18 @@ export type ContextMenuEvents = {
|
|||||||
openContextMenu: (args: OpenContextMenuProps) => void;
|
openContextMenu: (args: OpenContextMenuProps) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextMenuItemType =
|
export const CONTEXT_MENU_ITEM_MAPPING: { [k in ContextMenuItemKeys]?: string } = {
|
||||||
| 'addToFavorites'
|
[ContextMenuItemKey.MOVE_TO_BOTTOM_OF_QUEUE]: 'moveToBottom',
|
||||||
| 'addToPlaylist'
|
[ContextMenuItemKey.MOVE_TO_TOP_OF_QUEUE]: 'moveToTop',
|
||||||
| 'createPlaylist'
|
[ContextMenuItemKey.PLAY_LAST]: 'addLast',
|
||||||
| 'deletePlaylist'
|
[ContextMenuItemKey.PLAY_NEXT]: 'addNext',
|
||||||
| 'deselectAll'
|
|
||||||
| 'download'
|
|
||||||
| 'goToAlbum'
|
|
||||||
| 'goToAlbumArtist'
|
|
||||||
| 'moveToBottomOfQueue'
|
|
||||||
| 'moveToNextOfQueue'
|
|
||||||
| 'moveToTopOfQueue'
|
|
||||||
| 'play'
|
|
||||||
| 'playLast'
|
|
||||||
| 'playNext'
|
|
||||||
| 'playShuffled'
|
|
||||||
| 'playSimilarSongs'
|
|
||||||
| 'removeFromFavorites'
|
|
||||||
| 'removeFromPlaylist'
|
|
||||||
| 'removeFromQueue'
|
|
||||||
| 'setRating'
|
|
||||||
| 'shareItem'
|
|
||||||
| 'showDetails';
|
|
||||||
|
|
||||||
export type OpenContextMenuProps = {
|
|
||||||
context?: any;
|
|
||||||
data: any[];
|
|
||||||
dataNodes?: RowNode[];
|
|
||||||
menuItems: SetContextMenuItems;
|
|
||||||
resetGridCache?: () => void;
|
|
||||||
tableApi?: GridOptions['api'];
|
|
||||||
type: LibraryItem;
|
|
||||||
xPos: number;
|
|
||||||
yPos: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CONFIGURABLE_CONTEXT_MENU_ITEMS: ContextMenuItemType[] = [
|
|
||||||
'moveToBottomOfQueue',
|
|
||||||
'moveToTopOfQueue',
|
|
||||||
'play',
|
|
||||||
'playLast',
|
|
||||||
'playNext',
|
|
||||||
'playShuffled',
|
|
||||||
'playSimilarSongs',
|
|
||||||
'addToPlaylist',
|
|
||||||
'removeFromPlaylist',
|
|
||||||
'addToFavorites',
|
|
||||||
'removeFromFavorites',
|
|
||||||
'setRating',
|
|
||||||
'download',
|
|
||||||
'shareItem',
|
|
||||||
'goToAlbum',
|
|
||||||
'goToAlbumArtist',
|
|
||||||
'showDetails',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const CONTEXT_MENU_ITEM_MAPPING: { [k in ContextMenuItemType]?: string } = {
|
|
||||||
moveToBottomOfQueue: 'moveToBottom',
|
|
||||||
moveToTopOfQueue: 'moveToTop',
|
|
||||||
playLast: 'addLast',
|
|
||||||
playNext: 'addNext',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SetContextMenuItems = {
|
export type SetContextMenuItems = {
|
||||||
children?: boolean;
|
children?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
id: ContextMenuItemType;
|
id: ContextMenuItemKeys;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
import { CellContextMenuEvent, GridApi } from '@ag-grid-community/core';
|
|
||||||
import sortBy from 'lodash/sortBy';
|
|
||||||
|
|
||||||
import { openContextMenu, SetContextMenuItems } from '/@/renderer/features/context-menu/events';
|
|
||||||
import {
|
|
||||||
Album,
|
|
||||||
AlbumArtist,
|
|
||||||
Artist,
|
|
||||||
LibraryItem,
|
|
||||||
QueueSong,
|
|
||||||
Song,
|
|
||||||
} from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useHandleTableContextMenu = (
|
|
||||||
itemType: LibraryItem,
|
|
||||||
contextMenuItems: SetContextMenuItems,
|
|
||||||
context?: any,
|
|
||||||
) => {
|
|
||||||
const handleContextMenu = (
|
|
||||||
e?: CellContextMenuEvent,
|
|
||||||
gridApi?: GridApi<any>,
|
|
||||||
click?: MouseEvent,
|
|
||||||
) => {
|
|
||||||
let clickEvent: MouseEvent | undefined = click;
|
|
||||||
if (e) {
|
|
||||||
if (!e?.event) return;
|
|
||||||
clickEvent = e?.event as MouseEvent;
|
|
||||||
clickEvent.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = gridApi || e?.api;
|
|
||||||
|
|
||||||
if (!api) return;
|
|
||||||
|
|
||||||
let selectedNodes = sortBy(api.getSelectedNodes(), ['rowIndex']);
|
|
||||||
let selectedRows = selectedNodes.map((node) => node.data);
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
if (!e.data?.id) return;
|
|
||||||
|
|
||||||
const shouldReplaceSelected = !selectedNodes
|
|
||||||
.map((node) => node.data.id)
|
|
||||||
.includes(e.data.id);
|
|
||||||
|
|
||||||
if (shouldReplaceSelected) {
|
|
||||||
e.api.deselectAll();
|
|
||||||
e.node.setSelected(true);
|
|
||||||
selectedRows = [e.data];
|
|
||||||
selectedNodes = e.api.getSelectedNodes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openContextMenu({
|
|
||||||
context,
|
|
||||||
data: selectedRows,
|
|
||||||
dataNodes: selectedNodes,
|
|
||||||
menuItems: contextMenuItems,
|
|
||||||
tableApi: api,
|
|
||||||
type: itemType,
|
|
||||||
xPos: clickEvent?.clientX || 0,
|
|
||||||
yPos: clickEvent?.clientY || 0,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return handleContextMenu;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleGeneralContextMenu = (
|
|
||||||
itemType: LibraryItem,
|
|
||||||
contextMenuItems: SetContextMenuItems,
|
|
||||||
context?: any,
|
|
||||||
) => {
|
|
||||||
const handleContextMenu = (
|
|
||||||
e: any,
|
|
||||||
data: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[],
|
|
||||||
) => {
|
|
||||||
if (!e) return;
|
|
||||||
const clickEvent = e as MouseEvent;
|
|
||||||
clickEvent.preventDefault();
|
|
||||||
|
|
||||||
openContextMenu({
|
|
||||||
context,
|
|
||||||
data,
|
|
||||||
dataNodes: undefined,
|
|
||||||
menuItems: contextMenuItems,
|
|
||||||
type: itemType,
|
|
||||||
xPos: clickEvent.clientX + 15,
|
|
||||||
yPos: clickEvent.clientY + 5,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return handleContextMenu;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleGridContextMenu = (
|
|
||||||
itemType: LibraryItem,
|
|
||||||
contextMenuItems: SetContextMenuItems,
|
|
||||||
resetGridCache?: () => void,
|
|
||||||
context?: any,
|
|
||||||
) => {
|
|
||||||
const handleContextMenu = (
|
|
||||||
e: any,
|
|
||||||
data: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[],
|
|
||||||
) => {
|
|
||||||
if (!e) return;
|
|
||||||
const clickEvent = e as MouseEvent;
|
|
||||||
clickEvent.preventDefault();
|
|
||||||
|
|
||||||
openContextMenu({
|
|
||||||
context,
|
|
||||||
data,
|
|
||||||
dataNodes: undefined,
|
|
||||||
menuItems: contextMenuItems,
|
|
||||||
resetGridCache,
|
|
||||||
type: itemType,
|
|
||||||
xPos: clickEvent.clientX + 15,
|
|
||||||
yPos: clickEvent.clientY + 5,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return handleContextMenu;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
|
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||||
|
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { AlbumArtist, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface AlbumArtistContextMenuProps {
|
||||||
|
items: AlbumArtist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlbumArtistContextMenu = ({ items }: AlbumArtistContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<SetFavoriteAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||||
|
<SetRatingAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DownloadAction ids={ids} />
|
||||||
|
<ShareAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GoToAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
|
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||||
|
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { Album, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface AlbumContextMenuProps {
|
||||||
|
items: Album[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlbumContextMenu = ({ items }: AlbumContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<SetFavoriteAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<SetRatingAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DownloadAction ids={ids} />
|
||||||
|
<ShareAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GoToAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
|
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||||
|
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { Artist, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface ArtistContextMenuProps {
|
||||||
|
items: Artist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArtistContextMenu = ({ items }: ArtistContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<SetFavoriteAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||||
|
<SetRatingAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DownloadAction ids={ids} />
|
||||||
|
<ShareAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GoToAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { Genre, LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface GenreContextMenuProps {
|
||||||
|
items: Genre[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GenreContextMenu = ({ items }: GenreContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DeletePlaylistAction } from '/@/renderer/features/context-menu/actions/delete-playlist-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem, Playlist } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface PlaylistContextMenuProps {
|
||||||
|
items: Playlist[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaylistContextMenu = ({ items }: PlaylistContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DeletePlaylistAction items={items} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { RemoveFromPlaylistAction } from '/@/renderer/features/context-menu/actions/remove-from-playlist-action';
|
||||||
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
|
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||||
|
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface PlaylistSongContextMenuProps {
|
||||||
|
items: Song[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlaylistSongContextMenu = ({ items }: PlaylistSongContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<RemoveFromPlaylistAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<SetFavoriteAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<SetRatingAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DownloadAction ids={ids} />
|
||||||
|
<ShareAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GoToAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||||
|
import { MoveQueueItemsAction } from '/@/renderer/features/context-menu/actions/move-queue-items-action';
|
||||||
|
import { RemoveFromQueueAction } from '/@/renderer/features/context-menu/actions/remove-from-queue-action';
|
||||||
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
|
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||||
|
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||||
|
import { ShuffleItemsAction } from '/@/renderer/features/context-menu/actions/shuffle-items-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface QueueContextMenuProps {
|
||||||
|
items: QueueSong[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const QueueContextMenu = ({ items }: QueueContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<RemoveFromQueueAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<MoveQueueItemsAction items={items} />
|
||||||
|
<ShuffleItemsAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<SetFavoriteAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<SetRatingAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DownloadAction ids={ids} />
|
||||||
|
<ShareAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GoToAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
|
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
||||||
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
|
import { SetRatingAction } from '/@/renderer/features/context-menu/actions/set-rating-action';
|
||||||
|
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
|
||||||
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
|
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface SongContextMenuProps {
|
||||||
|
items: Song[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SongContextMenu = ({ items }: SongContextMenuProps) => {
|
||||||
|
const { ids } = useMemo(() => {
|
||||||
|
const ids = items.map((item) => item.id);
|
||||||
|
return { ids };
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<PlayAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<SetFavoriteAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<SetRatingAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<DownloadAction ids={ids} />
|
||||||
|
<ShareAction ids={ids} itemType={LibraryItem.SONG} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GoToAction items={items} />
|
||||||
|
<ContextMenu.Divider />
|
||||||
|
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
||||||
|
</ContextMenu.Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -17,7 +17,7 @@ import { Separator } from '/@/shared/components/separator/separator';
|
|||||||
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
||||||
import { Table } from '/@/shared/components/table/table';
|
import { Table } from '/@/shared/components/table/table';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { ExplicitStatus } from '/@/shared/types/domain-types';
|
import { Artist, ExplicitStatus } from '/@/shared/types/domain-types';
|
||||||
import {
|
import {
|
||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export type ItemDetailsModalProps = {
|
export type ItemDetailsModalProps = {
|
||||||
item: Album | AlbumArtist | Playlist | Song;
|
item: Album | AlbumArtist | Artist | Playlist | Song;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ItemDetailRow<T> = {
|
type ItemDetailRow<T> = {
|
||||||
@@ -402,7 +402,7 @@ export const ItemDetailsModal = ({ item }: ItemDetailsModalProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
let body: ReactNode[] = [];
|
let body: ReactNode[] = [];
|
||||||
|
|
||||||
switch (item.itemType) {
|
switch (item._itemType) {
|
||||||
case LibraryItem.ALBUM:
|
case LibraryItem.ALBUM:
|
||||||
body = AlbumPropertyMapping.map((rule) => handleRow(t, item, rule));
|
body = AlbumPropertyMapping.map((rule) => handleRow(t, item, rule));
|
||||||
body.push(...handleParticipants(item, t));
|
body.push(...handleParticipants(item, t));
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { generatePath, Link } from 'react-router';
|
|||||||
|
|
||||||
import styles from './left-controls.module.css';
|
import styles from './left-controls.module.css';
|
||||||
|
|
||||||
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||||
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import {
|
import {
|
||||||
useAppStoreActions,
|
useAppStoreActions,
|
||||||
@@ -41,11 +40,6 @@ export const LeftControls = () => {
|
|||||||
|
|
||||||
const isSongDefined = Boolean(currentSong?.id);
|
const isSongDefined = Boolean(currentSong?.id);
|
||||||
|
|
||||||
const handleGeneralContextMenu = useHandleGeneralContextMenu(
|
|
||||||
LibraryItem.SONG,
|
|
||||||
SONG_CONTEXT_MENU_ITEMS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggleFullScreenPlayer = (e?: KeyboardEvent | MouseEvent<HTMLDivElement>) => {
|
const handleToggleFullScreenPlayer = (e?: KeyboardEvent | MouseEvent<HTMLDivElement>) => {
|
||||||
// don't toggle if right click
|
// don't toggle if right click
|
||||||
if (e && 'button' in e && e.button === 2) {
|
if (e && 'button' in e && e.button === 2) {
|
||||||
@@ -65,9 +59,14 @@ export const LeftControls = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (isSongDefined && !isFullScreenPlayerExpanded) {
|
if (!currentSong) {
|
||||||
handleGeneralContextMenu(e, [currentSong!]);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContextMenuController.call({
|
||||||
|
cmd: { items: [currentSong], type: LibraryItem.SONG },
|
||||||
|
event: e,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopPropagation = (e?: MouseEvent) => e?.stopPropagation();
|
const stopPropagation = (e?: MouseEvent) => e?.stopPropagation();
|
||||||
@@ -156,7 +155,7 @@ export const LeftControls = () => {
|
|||||||
{isSongDefined && (
|
{isSongDefined && (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon="ellipsisVertical"
|
icon="ellipsisVertical"
|
||||||
onClick={(e) => handleGeneralContextMenu(e, [currentSong!])}
|
// onClick={(e) => handleGeneralContextMenu(e, [currentSong!])}
|
||||||
size="xs"
|
size="xs"
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useSessionStorage } from '/@/shared/hooks/use-session-storage';
|
||||||
|
|
||||||
|
interface RecentPlaylists {
|
||||||
|
[serverId: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECENT_PLAYLISTS_KEY = 'recent-playlists';
|
||||||
|
const DEFAULT_VALUE: RecentPlaylists = {};
|
||||||
|
|
||||||
|
export const useRecentPlaylists = (serverId: null | string) => {
|
||||||
|
const [recentPlaylists, setRecentPlaylists] = useSessionStorage<RecentPlaylists>({
|
||||||
|
defaultValue: DEFAULT_VALUE,
|
||||||
|
key: RECENT_PLAYLISTS_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRecentPlaylistId = useCallback((): null | string => {
|
||||||
|
if (!serverId) return null;
|
||||||
|
return recentPlaylists[serverId] || null;
|
||||||
|
}, [recentPlaylists, serverId]);
|
||||||
|
|
||||||
|
const addRecentPlaylist = useCallback(
|
||||||
|
(playlistId: string) => {
|
||||||
|
if (!serverId || !playlistId) return;
|
||||||
|
|
||||||
|
setRecentPlaylists({
|
||||||
|
...recentPlaylists,
|
||||||
|
[serverId]: playlistId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[recentPlaylists, serverId, setRecentPlaylists],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
addRecentPlaylist,
|
||||||
|
recentPlaylistId: getRecentPlaylistId(),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -3,12 +3,17 @@ import { AxiosError } from 'axios';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
|
||||||
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
import { MutationHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { useCurrentServerId } from '/@/renderer/store';
|
||||||
import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/shared/types/domain-types';
|
import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const useAddToPlaylist = (args: MutationHookArgs) => {
|
export const useAddToPlaylist = (args: MutationHookArgs) => {
|
||||||
const { options } = args || {};
|
const { options } = args || {};
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const serverId = useCurrentServerId();
|
||||||
|
|
||||||
|
const { addRecentPlaylist } = useRecentPlaylists(serverId);
|
||||||
|
|
||||||
return useMutation<AddToPlaylistResponse, AxiosError, AddToPlaylistArgs, null>({
|
return useMutation<AddToPlaylistResponse, AxiosError, AddToPlaylistArgs, null>({
|
||||||
mutationFn: (args) => {
|
mutationFn: (args) => {
|
||||||
@@ -17,7 +22,7 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
|
|||||||
apiClientProps: { serverId: args.apiClientProps.serverId },
|
apiClientProps: { serverId: args.apiClientProps.serverId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: (_data, variables) => {
|
onSuccess: (_data, variables, context) => {
|
||||||
const { apiClientProps } = variables;
|
const { apiClientProps } = variables;
|
||||||
const serverId = apiClientProps.serverId;
|
const serverId = apiClientProps.serverId;
|
||||||
|
|
||||||
@@ -33,6 +38,10 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.playlists.songList(serverId, variables.query.id),
|
queryKey: queryKeys.playlists.songList(serverId, variables.query.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addRecentPlaylist(variables.query.id);
|
||||||
|
|
||||||
|
options?.onSuccess?.(_data, variables, context);
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CONFIGURABLE_CONTEXT_MENU_ITEMS,
|
|
||||||
CONTEXT_MENU_ITEM_MAPPING,
|
|
||||||
} from '/@/renderer/features/context-menu/events';
|
|
||||||
import { SettingsOptions } from '/@/renderer/features/settings/components/settings-option';
|
|
||||||
import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store';
|
|
||||||
import { Button } from '/@/shared/components/button/button';
|
|
||||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
|
||||||
|
|
||||||
export const ContextMenuSettings = () => {
|
|
||||||
const disabledItems = useSettingsStore((state) => state.general.disabledContextMenu);
|
|
||||||
const { toggleContextMenuItem } = useSettingsStoreActions();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SettingsOptions
|
|
||||||
control={
|
|
||||||
<Button onClick={() => setOpen(!open)} size="compact-md" variant="filled">
|
|
||||||
{t(open ? 'common.close' : 'common.edit', { postProcess: 'titleCase' })}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
description={t('setting.contextMenu', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
})}
|
|
||||||
title={t('setting.contextMenu', {
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{open && (
|
|
||||||
<Stack>
|
|
||||||
{CONFIGURABLE_CONTEXT_MENU_ITEMS.map((item) => (
|
|
||||||
<Checkbox
|
|
||||||
checked={!disabledItems[item]}
|
|
||||||
key={item}
|
|
||||||
label={t(
|
|
||||||
`page.contextMenu.${CONTEXT_MENU_ITEM_MAPPING[item] || item}`,
|
|
||||||
{
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
onChange={() => toggleContextMenuItem(item)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
<Divider />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@ import isElectron from 'is-electron';
|
|||||||
|
|
||||||
import { ApplicationSettings } from '/@/renderer/features/settings/components/general/application-settings';
|
import { ApplicationSettings } from '/@/renderer/features/settings/components/general/application-settings';
|
||||||
import { ArtistSettings } from '/@/renderer/features/settings/components/general/artist-settings';
|
import { ArtistSettings } from '/@/renderer/features/settings/components/general/artist-settings';
|
||||||
import { ContextMenuSettings } from '/@/renderer/features/settings/components/general/context-menu-settings';
|
|
||||||
import { ControlSettings } from '/@/renderer/features/settings/components/general/control-settings';
|
import { ControlSettings } from '/@/renderer/features/settings/components/general/control-settings';
|
||||||
import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings';
|
import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings';
|
||||||
import { RemoteSettings } from '/@/renderer/features/settings/components/general/remote-settings';
|
import { RemoteSettings } from '/@/renderer/features/settings/components/general/remote-settings';
|
||||||
@@ -22,7 +21,6 @@ export const GeneralTab = () => {
|
|||||||
<ArtistSettings />
|
<ArtistSettings />
|
||||||
<SidebarReorder />
|
<SidebarReorder />
|
||||||
<SidebarSettings />
|
<SidebarSettings />
|
||||||
<ContextMenuSettings />
|
|
||||||
{isElectron() && <RemoteSettings />}
|
{isElectron() && <RemoteSettings />}
|
||||||
<CacheSettings />
|
<CacheSettings />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { useLocation } from 'react-router';
|
|||||||
|
|
||||||
import styles from './sidebar.module.css';
|
import styles from './sidebar.module.css';
|
||||||
|
|
||||||
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||||
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
|
|
||||||
import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
|
import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
|
||||||
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
||||||
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
||||||
@@ -17,8 +16,8 @@ import {
|
|||||||
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
|
} from '/@/renderer/features/sidebar/components/sidebar-playlist-list';
|
||||||
import {
|
import {
|
||||||
useAppStoreActions,
|
useAppStoreActions,
|
||||||
usePlayerSong,
|
|
||||||
useFullScreenPlayerStore,
|
useFullScreenPlayerStore,
|
||||||
|
usePlayerSong,
|
||||||
useSetFullScreenPlayerStore,
|
useSetFullScreenPlayerStore,
|
||||||
useSidebarStore,
|
useSidebarStore,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
@@ -73,17 +72,19 @@ export const Sidebar = () => {
|
|||||||
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
|
setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGeneralContextMenu = useHandleGeneralContextMenu(
|
|
||||||
LibraryItem.SONG,
|
|
||||||
SONG_CONTEXT_MENU_ITEMS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggleContextMenu = (e: MouseEvent<HTMLDivElement>) => {
|
const handleToggleContextMenu = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!currentSong) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSongDefined && !isFullScreenPlayerExpanded) {
|
if (isSongDefined && !isFullScreenPlayerExpanded) {
|
||||||
handleGeneralContextMenu(e, [currentSong!]);
|
ContextMenuController.call({
|
||||||
|
cmd: { items: [currentSong!], type: LibraryItem.SONG },
|
||||||
|
event: e,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
import { ErrorFallback } from '/@/renderer/features/action-required/components/error-fallback';
|
import { ErrorFallback } from '/@/renderer/features/action-required/components/error-fallback';
|
||||||
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
|
|
||||||
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { usePlayButtonBehavior, useTableSettings } from '/@/renderer/store';
|
import { usePlayButtonBehavior, useTableSettings } from '/@/renderer/store';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
@@ -21,7 +20,7 @@ export type SimilarSongsListProps = {
|
|||||||
|
|
||||||
export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListProps) => {
|
export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListProps) => {
|
||||||
const tableRef = useRef<AgGridReact<Song> | null>(null);
|
const tableRef = useRef<AgGridReact<Song> | null>(null);
|
||||||
const tableConfig = useTableSettings(fullScreen ? 'fullScreen' : 'songs');
|
// const tableConfig = useTableSettings(fullScreen ? 'fullScreen' : 'songs');
|
||||||
|
|
||||||
const songQuery = useQuery(
|
const songQuery = useQuery(
|
||||||
songsQueries.similar({
|
songsQueries.similar({
|
||||||
@@ -37,12 +36,12 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const columnDefs = useMemo(
|
// const columnDefs = useMemo(
|
||||||
() => getColumnDefs(tableConfig.columns, false, 'generic'),
|
// () => getColumnDefs(tableConfig.columns, false, 'generic'),
|
||||||
[tableConfig.columns],
|
// [tableConfig.columns],
|
||||||
);
|
// );
|
||||||
|
|
||||||
const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
|
// const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
|
||||||
|
|
||||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<Song>) => {
|
const handleRowDoubleClick = (e: RowDoubleClickedEvent<Song>) => {
|
||||||
if (!e.data || !songQuery.data) return;
|
if (!e.data || !songQuery.data) return;
|
||||||
@@ -58,7 +57,7 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr
|
|||||||
<Spinner container size={25} />
|
<Spinner container size={25} />
|
||||||
) : (
|
) : (
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<VirtualTable
|
{/* <VirtualTable
|
||||||
autoFitColumns={tableConfig.autoFit}
|
autoFitColumns={tableConfig.autoFit}
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
context={{
|
context={{
|
||||||
@@ -76,7 +75,7 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr
|
|||||||
rowData={songQuery.data ?? []}
|
rowData={songQuery.data ?? []}
|
||||||
rowHeight={tableConfig.rowHeight || 40}
|
rowHeight={tableConfig.rowHeight || 40}
|
||||||
shouldUpdateSong
|
shouldUpdateSong
|
||||||
/>
|
/> */}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useNavigate } from 'react-router';
|
|||||||
|
|
||||||
import styles from './default-layout.module.css';
|
import styles from './default-layout.module.css';
|
||||||
|
|
||||||
|
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||||
import { CommandPalette } from '/@/renderer/features/search/components/command-palette';
|
import { CommandPalette } from '/@/renderer/features/search/components/command-palette';
|
||||||
import { MainContent } from '/@/renderer/layouts/default-layout/main-content';
|
import { MainContent } from '/@/renderer/layouts/default-layout/main-content';
|
||||||
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
|
import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar';
|
||||||
@@ -88,6 +89,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => {
|
|||||||
<PlayerBar />
|
<PlayerBar />
|
||||||
</div>
|
</div>
|
||||||
<CommandPalette modalProps={{ handlers, opened }} />
|
<CommandPalette modalProps={{ handlers, opened }} />
|
||||||
|
<ContextMenuController.Root />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
PLAYLIST_TABLE_COLUMNS,
|
PLAYLIST_TABLE_COLUMNS,
|
||||||
SONG_TABLE_COLUMNS,
|
SONG_TABLE_COLUMNS,
|
||||||
} from '/@/renderer/components/item-list/item-table-list/default-columns';
|
} from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { ContextMenuItemType } from '/@/renderer/features/context-menu/events';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { mergeOverridingColumns } from '/@/renderer/store/utils';
|
import { mergeOverridingColumns } from '/@/renderer/store/utils';
|
||||||
import { FontValueSchema } from '/@/renderer/types/fonts';
|
import { FontValueSchema } from '/@/renderer/types/fonts';
|
||||||
@@ -457,7 +456,6 @@ export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> {
|
|||||||
setSidebarItems: (items: SidebarItemType[]) => void;
|
setSidebarItems: (items: SidebarItemType[]) => void;
|
||||||
setTable: (type: ItemListKey, data: DataTableProps) => void;
|
setTable: (type: ItemListKey, data: DataTableProps) => void;
|
||||||
setTranscodingConfig: (config: TranscodingConfig) => void;
|
setTranscodingConfig: (config: TranscodingConfig) => void;
|
||||||
toggleContextMenuItem: (item: ContextMenuItemType) => void;
|
|
||||||
toggleMediaSession: () => void;
|
toggleMediaSession: () => void;
|
||||||
toggleSidebarCollapseShare: () => void;
|
toggleSidebarCollapseShare: () => void;
|
||||||
};
|
};
|
||||||
@@ -1158,12 +1156,6 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
|||||||
state.playback.transcode = config;
|
state.playback.transcode = config;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleContextMenuItem: (item: ContextMenuItemType) => {
|
|
||||||
set((state) => {
|
|
||||||
state.general.disabledContextMenu[item] =
|
|
||||||
!state.general.disabledContextMenu[item];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
toggleMediaSession: () => {
|
toggleMediaSession: () => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.playback.mediaSession = !state.playback.mediaSession;
|
state.playback.mediaSession = !state.playback.mediaSession;
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
.content {
|
||||||
|
z-index: 1000;
|
||||||
|
width: 20rem;
|
||||||
|
min-width: 20rem;
|
||||||
|
max-width: 20rem;
|
||||||
|
padding: var(--theme-spacing-xs);
|
||||||
|
color: var(--theme-colors-foreground);
|
||||||
|
background: var(--theme-colors-background);
|
||||||
|
border: 1px solid var(--theme-colors-border);
|
||||||
|
border-radius: var(--theme-radius-md);
|
||||||
|
filter: drop-shadow(0 0 5px rgb(0 0 0 / 50%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--base-gap-xs);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 8rem;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: var(--theme-spacing-sm) var(--theme-spacing-md);
|
||||||
|
font-size: var(--theme-font-size-sm);
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.has-left-icon {
|
||||||
|
padding-left: calc(var(--theme-spacing-md) + 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-right-icon {
|
||||||
|
padding-right: calc(var(--theme-spacing-md) + 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > *:not(.left-icon, .right-icon) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--theme-colors-surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item[data-highlighted] {
|
||||||
|
background: var(--theme-colors-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: var(--theme-spacing-md);
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: var(--theme-spacing-md);
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.selected {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 2px;
|
||||||
|
width: 4px;
|
||||||
|
height: 50%;
|
||||||
|
content: '';
|
||||||
|
background-color: var(--theme-colors-primary-filled);
|
||||||
|
border-radius: var(--theme-border-radius-xl);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: var(--theme-spacing-xs) 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--theme-colors-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-height {
|
||||||
|
max-height: 36rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import type { Dispatch, SetStateAction } from 'react';
|
||||||
|
|
||||||
|
import * as RadixContextMenu from '@radix-ui/react-context-menu';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { AnimatePresence, motion } from 'motion/react';
|
||||||
|
import { createContext, Fragment, type ReactNode, useContext, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import styles from './context-menu.module.css';
|
||||||
|
|
||||||
|
import { animationVariants } from '/@/shared/components/animations/animation-variants';
|
||||||
|
import { AppIcon, Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
||||||
|
|
||||||
|
interface ContextMenuContext {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContextMenuContext = createContext<ContextMenuContext | null>(null);
|
||||||
|
|
||||||
|
interface ContentProps {
|
||||||
|
children: ReactNode;
|
||||||
|
onCloseAutoFocus?: (event: FocusEvent) => void;
|
||||||
|
onEscapeKeyDown?: (event: KeyboardEvent) => void;
|
||||||
|
onFocusOutside?: (event: FocusEvent) => void;
|
||||||
|
onPointerDownOutside?: (event: PointerEvent) => void;
|
||||||
|
stickyContent?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextMenuProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DividerProps {}
|
||||||
|
|
||||||
|
interface ItemProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
isSelected?: boolean;
|
||||||
|
leftIcon?: keyof typeof AppIcon;
|
||||||
|
onSelect?: (event: Event) => void;
|
||||||
|
rightIcon?: keyof typeof AppIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LabelProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubmenuContext {
|
||||||
|
disabled?: boolean;
|
||||||
|
isCloseDisabled?: boolean;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextMenu(props: ContextMenuProps) {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const context = useMemo(() => ({ open, setOpen }), [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixContextMenu.Root onOpenChange={setOpen}>
|
||||||
|
<ContextMenuContext.Provider value={context}>{children}</ContextMenuContext.Provider>
|
||||||
|
</RadixContextMenu.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content(props: ContentProps) {
|
||||||
|
const { children, stickyContent } = props;
|
||||||
|
const { open } = useContext(ContextMenuContext) as ContextMenuContext;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{open && (
|
||||||
|
<RadixContextMenu.Portal forceMount>
|
||||||
|
<RadixContextMenu.Content asChild className={styles.content}>
|
||||||
|
<motion.div
|
||||||
|
animate="show"
|
||||||
|
className={styles.content}
|
||||||
|
exit="hidden"
|
||||||
|
initial="hidden"
|
||||||
|
>
|
||||||
|
{stickyContent}
|
||||||
|
<ScrollArea className={styles.maxHeight}>{children}</ScrollArea>
|
||||||
|
</motion.div>
|
||||||
|
</RadixContextMenu.Content>
|
||||||
|
</RadixContextMenu.Portal>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Divider(props: DividerProps) {
|
||||||
|
return <RadixContextMenu.Separator {...props} className={styles.divider} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Item(props: ItemProps) {
|
||||||
|
const { children, className, disabled, isSelected, leftIcon, onSelect, rightIcon } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixContextMenu.Item
|
||||||
|
className={clsx(styles.item, className, {
|
||||||
|
[styles.disabled]: disabled,
|
||||||
|
[styles.selected]: isSelected,
|
||||||
|
[styles['has-left-icon']]: !!leftIcon,
|
||||||
|
[styles['has-right-icon']]: !!rightIcon,
|
||||||
|
})}
|
||||||
|
disabled={disabled}
|
||||||
|
onSelect={onSelect}
|
||||||
|
>
|
||||||
|
{leftIcon && <Icon className={styles.leftIcon} icon={leftIcon} />}
|
||||||
|
{children}
|
||||||
|
{rightIcon && <Icon className={styles.rightIcon} icon={rightIcon} />}
|
||||||
|
</RadixContextMenu.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Label(props: LabelProps) {
|
||||||
|
const { children, className, ...htmlProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixContextMenu.Label className={clsx(styles.label, className)} {...htmlProps}>
|
||||||
|
{children}
|
||||||
|
</RadixContextMenu.Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Target(props: TargetProps) {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixContextMenu.Trigger asChild className={styles.target}>
|
||||||
|
{children}
|
||||||
|
</RadixContextMenu.Trigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubmenuContext = createContext<null | SubmenuContext>(null);
|
||||||
|
|
||||||
|
interface SubmenuContentProps {
|
||||||
|
children: ReactNode;
|
||||||
|
stickyContent?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubmenuProps {
|
||||||
|
children: ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
isCloseDisabled?: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubmenuTargetProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Submenu(props: SubmenuProps) {
|
||||||
|
const { children, disabled, isCloseDisabled, open: isManuallyOpen } = props;
|
||||||
|
const [open, setOpen] = useState(isManuallyOpen ?? false);
|
||||||
|
const context = useMemo(
|
||||||
|
() => ({ disabled, isCloseDisabled, open, setOpen }),
|
||||||
|
[disabled, isCloseDisabled, open],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixContextMenu.Sub open={open}>
|
||||||
|
<SubmenuContext.Provider value={context}>{children}</SubmenuContext.Provider>
|
||||||
|
</RadixContextMenu.Sub>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SubmenuContent(props: SubmenuContentProps) {
|
||||||
|
const { children, stickyContent } = props;
|
||||||
|
const { isCloseDisabled, open, setOpen } = useContext(SubmenuContext) as SubmenuContext;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{open && (
|
||||||
|
<RadixContextMenu.Portal forceMount>
|
||||||
|
<RadixContextMenu.SubContent
|
||||||
|
className={styles.content}
|
||||||
|
onMouseEnter={() => setOpen(true)}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
if (!isCloseDisabled) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate="show"
|
||||||
|
className={styles.innerContent}
|
||||||
|
initial="hidden"
|
||||||
|
variants={animationVariants.fadeIn}
|
||||||
|
>
|
||||||
|
{stickyContent}
|
||||||
|
<ScrollArea className={styles.maxHeight}>{children}</ScrollArea>
|
||||||
|
</motion.div>
|
||||||
|
</RadixContextMenu.SubContent>
|
||||||
|
</RadixContextMenu.Portal>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SubmenuTarget(props: SubmenuTargetProps) {
|
||||||
|
const { children } = props;
|
||||||
|
const { disabled, setOpen } = useContext(SubmenuContext) as SubmenuContext;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixContextMenu.SubTrigger
|
||||||
|
className={clsx({ [styles.disabled]: disabled })}
|
||||||
|
disabled={disabled}
|
||||||
|
onMouseEnter={() => setOpen(true)}
|
||||||
|
onMouseLeave={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RadixContextMenu.SubTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextMenu.Target = Target;
|
||||||
|
ContextMenu.Content = Content;
|
||||||
|
ContextMenu.Item = Item;
|
||||||
|
ContextMenu.Label = Label;
|
||||||
|
ContextMenu.Group = RadixContextMenu.Group;
|
||||||
|
ContextMenu.Submenu = Submenu;
|
||||||
|
ContextMenu.SubmenuTarget = SubmenuTarget;
|
||||||
|
ContextMenu.SubmenuContent = SubmenuContent;
|
||||||
|
ContextMenu.Divider = Divider;
|
||||||
|
ContextMenu.Arrow = RadixContextMenu.Arrow;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { useSessionStorage as useMantineSessionStorage } from '@mantine/hooks';
|
||||||
|
|
||||||
|
export const useSessionStorage = useMantineSessionStorage;
|
||||||
Reference in New Issue
Block a user