diff --git a/biome.json b/biome.json
index 1fb32962..2ba78d13 100644
--- a/biome.json
+++ b/biome.json
@@ -28,7 +28,7 @@
},
"javascript": {
"formatter": {
- "trailingComma": "none",
+ "trailingCommas": "none",
"arrowParentheses": "always"
}
},
diff --git a/package.json b/package.json
index 093a3049..38e6d087 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"build:local": "spicetify-creator --out=dist --minify",
"build:prod": "pnpm build:local && pnpm copy:docs",
"copy:docs": "copyfiles README.md dist/",
- "lint": "biome check --apply *",
+ "lint": "biome check --write ./src",
"lint:ci": "biome check *",
"type-check": "tsc --noEmit",
"watch": "spicetify-creator --watch",
@@ -41,6 +41,7 @@
"i18next": "^23.11.3",
"i18next-browser-languagedetector": "^8.0.0",
"prismjs": "^1.29.0",
+ "react-beautiful-dnd": "^13.1.1",
"react-dropdown": "^1.11.0",
"react-i18next": "^14.1.0",
"react-simple-code-editor": "^0.13.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7c0c66d0..bc7111a8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
prismjs:
specifier: ^1.29.0
version: 1.29.0
+ react-beautiful-dnd:
+ specifier: ^13.1.1
+ version: 13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-dropdown:
specifier: ^1.11.0
version: 1.11.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -132,12 +135,18 @@ packages:
'@types/chroma-js@2.4.4':
resolution: {integrity: sha512-/DTccpHTaKomqussrn+ciEvfW4k6NAHzNzs/sts1TCqg333qNxOhy8TNIoQCmbGG3Tl8KdEhkGAssb1n3mTXiQ==}
+ '@types/hoist-non-react-statics@3.3.5':
+ resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==}
+
'@types/prop-types@15.7.8':
resolution: {integrity: sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==}
'@types/react-dom@18.2.0':
resolution: {integrity: sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==}
+ '@types/react-redux@7.1.33':
+ resolution: {integrity: sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==}
+
'@types/react@18.2.0':
resolution: {integrity: sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==}
@@ -233,6 +242,9 @@ packages:
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+ css-box-model@1.2.1:
+ resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==}
+
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -489,6 +501,9 @@ packages:
resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==}
engines: {node: '>= 0.4.0'}
+ hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
homedir-polyfill@1.0.3:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
engines: {node: '>=0.10.0'}
@@ -622,6 +637,9 @@ packages:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
+ memoize-one@5.2.1:
+ resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
+
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@@ -668,6 +686,10 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -748,9 +770,21 @@ packages:
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+ raf-schd@4.0.3:
+ resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==}
+
+ react-beautiful-dnd@13.1.1:
+ resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==}
+ peerDependencies:
+ react: ^16.8.5 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0
+
react-dom@18.2.0:
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
@@ -775,6 +809,24 @@ packages:
react-native:
optional: true
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ react-redux@7.2.9:
+ resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==}
+ peerDependencies:
+ react: ^16.8.3 || ^17 || ^18
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
react-simple-code-editor@0.13.1:
resolution: {integrity: sha512-XYeVwRZwgyKtjNIYcAEgg2FaQcCZwhbarnkJIV20U2wkCU9q/CPFBo8nRXrK4GXUz3AvbqZFsZRrpUTkqqEYyQ==}
peerDependencies:
@@ -795,6 +847,9 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
+ redux@4.2.1:
+ resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
+
regenerator-runtime@0.14.0:
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
@@ -900,6 +955,9 @@ packages:
through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
+ tiny-invariant@1.3.3:
+ resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
tmp@0.2.1:
resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==}
engines: {node: '>=8.17.0'}
@@ -939,6 +997,11 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ use-memo-one@1.1.3:
+ resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -1024,12 +1087,24 @@ snapshots:
'@types/chroma-js@2.4.4': {}
+ '@types/hoist-non-react-statics@3.3.5':
+ dependencies:
+ '@types/react': 18.2.0
+ hoist-non-react-statics: 3.3.2
+
'@types/prop-types@15.7.8': {}
'@types/react-dom@18.2.0':
dependencies:
'@types/react': 18.2.0
+ '@types/react-redux@7.1.33':
+ dependencies:
+ '@types/hoist-non-react-statics': 3.3.5
+ '@types/react': 18.2.0
+ hoist-non-react-statics: 3.3.2
+ redux: 4.2.1
+
'@types/react@18.2.0':
dependencies:
'@types/prop-types': 15.7.8
@@ -1140,6 +1215,10 @@ snapshots:
core-util-is@1.0.3: {}
+ css-box-model@1.2.1:
+ dependencies:
+ tiny-invariant: 1.3.3
+
cssesc@3.0.0: {}
csstype@3.1.2: {}
@@ -1345,6 +1424,10 @@ snapshots:
has@1.0.4: {}
+ hoist-non-react-statics@3.3.2:
+ dependencies:
+ react-is: 16.13.1
+
homedir-polyfill@1.0.3:
dependencies:
parse-passwd: 1.0.0
@@ -1470,6 +1553,8 @@ snapshots:
semver: 5.7.2
optional: true
+ memoize-one@5.2.1: {}
+
mime@1.6.0:
optional: true
@@ -1508,6 +1593,8 @@ snapshots:
normalize-range@0.1.2: {}
+ object-assign@4.1.1: {}
+
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -1579,9 +1666,31 @@ snapshots:
process-nextick-args@2.0.1: {}
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
prr@1.0.1:
optional: true
+ raf-schd@4.0.3: {}
+
+ react-beautiful-dnd@13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ dependencies:
+ '@babel/runtime': 7.24.1
+ css-box-model: 1.2.1
+ memoize-one: 5.2.1
+ raf-schd: 4.0.3
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-redux: 7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ redux: 4.2.1
+ use-memo-one: 1.1.3(react@18.2.0)
+ transitivePeerDependencies:
+ - react-native
+
react-dom@18.2.0(react@18.2.0):
dependencies:
loose-envify: 1.4.0
@@ -1603,6 +1712,22 @@ snapshots:
optionalDependencies:
react-dom: 18.2.0(react@18.2.0)
+ react-is@16.13.1: {}
+
+ react-is@17.0.2: {}
+
+ react-redux@7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ dependencies:
+ '@babel/runtime': 7.24.1
+ '@types/react-redux': 7.1.33
+ hoist-non-react-statics: 3.3.2
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-is: 17.0.2
+ optionalDependencies:
+ react-dom: 18.2.0(react@18.2.0)
+
react-simple-code-editor@0.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
@@ -1633,6 +1758,10 @@ snapshots:
dependencies:
picomatch: 2.3.1
+ redux@4.2.1:
+ dependencies:
+ '@babel/runtime': 7.24.1
+
regenerator-runtime@0.14.0: {}
require-directory@2.1.1: {}
@@ -1754,6 +1883,8 @@ snapshots:
readable-stream: 2.3.8
xtend: 4.0.2
+ tiny-invariant@1.3.3: {}
+
tmp@0.2.1:
dependencies:
rimraf: 3.0.2
@@ -1782,6 +1913,10 @@ snapshots:
escalade: 3.1.1
picocolors: 1.0.0
+ use-memo-one@1.1.3(react@18.2.0):
+ dependencies:
+ react: 18.2.0
+
util-deprecate@1.0.2: {}
void-elements@3.1.0: {}
diff --git a/src/components/Modals/Settings/DnDList.tsx b/src/components/Modals/Settings/DnDList.tsx
new file mode 100644
index 00000000..85ee2595
--- /dev/null
+++ b/src/components/Modals/Settings/DnDList.tsx
@@ -0,0 +1,106 @@
+import React, { Component } from "react";
+import { DragDropContext, Draggable, type DropResult, Droppable } from "react-beautiful-dnd";
+import { LOCALSTORAGE_KEYS } from "../../../constants";
+import type { Config, TabItemConfig } from "../../../types/marketplace-types";
+
+const DnDList = (props: {
+ modalConfig: Config;
+ updateConfig: (CONFIG: Config) => void;
+}) => {
+ // Get Value of CSS variable
+ const colorVariable = getComputedStyle(document.body).getPropertyValue("--spice-button-disabled");
+
+ const getItemStyle = (isDragging, draggableStyle, isEnabled) => ({
+ borderRadius: "5px",
+ border: isEnabled ? `2px solid ${colorVariable}` : "2px solid red",
+ userSelect: "none",
+ paddingTop: 12,
+ paddingBottom: 12,
+ width: "110px",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ textDecoration: isEnabled ? "none" : "line-through",
+ ...draggableStyle
+ });
+
+ const getListStyle = (isDraggingOver) => ({
+ display: "flex",
+ paddingTop: 8,
+ paddingBottom: 8,
+ gap: 8
+ });
+
+ const onDragEnd = (result: DropResult) => {
+ const { source, destination } = result;
+ if (!destination) return;
+
+ reorder(props.modalConfig.tabs, source.index, destination.index);
+ };
+
+ function reorder(tabs: TabItemConfig[], start: number, end: number) {
+ const result = Array.from(tabs);
+ const [removed] = result.splice(start, 1);
+ result.splice(end, 0, removed);
+
+ props.modalConfig.tabs = result;
+
+ localStorage.setItem(LOCALSTORAGE_KEYS.tabs, JSON.stringify(props.modalConfig.tabs));
+
+ props.updateConfig(props.modalConfig);
+ }
+
+ const onToggleEnabled = (name) => {
+ const updatedTabs = props.modalConfig.tabs.map((tab) => (tab.name === name ? { ...tab, enabled: !tab.enabled } : tab));
+
+ props.modalConfig.tabs = updatedTabs;
+ localStorage.setItem(LOCALSTORAGE_KEYS.tabs, JSON.stringify(props.modalConfig.tabs));
+ props.updateConfig(props.modalConfig);
+ };
+
+ return (
+