diff --git a/client/package-lock.json b/client/package-lock.json index 9e4e22b..1a43ce1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.0", "dependencies": { "-": "^0.0.1", - "@dnd-kit/core": "^6.3.1", "@fluentui/react-components": "^9.69.0", "@fluentui/react-datepicker-compat": "^0.6.14", "@fluentui/react-icons": "^2.0.309", @@ -1937,42 +1936,6 @@ "node": ">=10" } }, - "node_modules/@dnd-kit/accessibility": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", - "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/core": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", - "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "dependencies": { - "@dnd-kit/accessibility": "^3.1.1", - "@dnd-kit/utilities": "^3.2.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@dnd-kit/utilities": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", - "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", diff --git a/client/package.json b/client/package.json index 8d8d6b1..10f838c 100644 --- a/client/package.json +++ b/client/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "-": "^0.0.1", - "@dnd-kit/core": "^6.3.1", "@fluentui/react-components": "^9.69.0", "@fluentui/react-datepicker-compat": "^0.6.14", "@fluentui/react-icons": "^2.0.309", diff --git a/client/src/pages/MapTest.tsx b/client/src/pages/MapTest.tsx index 948d1bf..8f454fc 100644 --- a/client/src/pages/MapTest.tsx +++ b/client/src/pages/MapTest.tsx @@ -1,11 +1,13 @@ import { Tab, TabList } from "@fluentui/react-tabs"; import MapComponent from "../components/map/MapComponent"; +import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; import { useAppStore, setCurrentTab, deleteMapTab, addMapTab, + reorderTabs, } from "../store/app"; import { initializeMapState, useMapStore } from "../store/map"; import { initializeObjectsState } from "../store/objects"; @@ -13,9 +15,86 @@ import { Button } from "@fluentui/react-components"; import { Add12Filled, Dismiss12Filled, Map16Regular } from "@fluentui/react-icons"; function MapTest() { - const { mapTab, currentTab } = useAppStore() + const { mapTab, currentTab, tabOrder } = useAppStore() const { id } = useMapStore() + const handleDragEnd = (result: any) => { + if (!result.destination) return + reorderTabs(result.source.index, result.destination.index) + } + + return ( +
+
+ + + {(provided) => ( + setCurrentTab(data.value as string)} + style={{ borderBottom: '1px solid var(--colorNeutralShadowKey)' }} + onDragStart={(e) => { + e.stopPropagation(); // stop TabList from also handling it + }} + onDrag={(e) => e.stopPropagation()} + > + {tabOrder.map((key, index) => ( + + {(dragProvided) => ( +
+ }> + {id[key]?.mapLabel ?? `Tab ${key}`} +
+ )} +
+ ))} + + {provided.placeholder} + +
+
+ ) + return (
diff --git a/client/src/store/app.ts b/client/src/store/app.ts index 5560f40..529dc19 100644 --- a/client/src/store/app.ts +++ b/client/src/store/app.ts @@ -4,7 +4,6 @@ import { initializeObjectsState } from './objects' import { initializeMapState } from './map' export type Mode = 'edit' | 'view' - export type ColorScheme = 'light' | 'dark' | 'auto' export interface MapTabState { @@ -14,15 +13,16 @@ export interface MapTabState { } export interface AppState { - colorScheme: ColorScheme, - mapTab: Record, - currentTab: string | null; + colorScheme: ColorScheme + mapTab: Record + tabOrder: string[] // 👈 defines tab order + currentTab: string | null } export const useAppStore = create(() => { const firstId = uuidv4() - initializeObjectsState(firstId, null, null, null, null); + initializeObjectsState(firstId, null, null, null, null) initializeMapState(firstId) return { @@ -31,15 +31,15 @@ export const useAppStore = create(() => { mapTab: { [firstId]: { year: null, region: null, district: null }, }, + tabOrder: [firstId], } }) -export const getColorScheme = () => { - useAppStore.getState().colorScheme -} +// getters/setters +export const getColorScheme = () => useAppStore.getState().colorScheme export const setColorScheme = (colorScheme: ColorScheme) => { - useAppStore.setState(() => ({ colorScheme: colorScheme })) + useAppStore.setState(() => ({ colorScheme })) localStorage.setItem('colorScheme', colorScheme.toString()) } @@ -47,40 +47,46 @@ export const getCurrentTab = () => useAppStore.getState().currentTab export const setCurrentTab = (id: string | null) => useAppStore.setState(() => ({ currentTab: id })) export const setMapTabYear = (id: string, year: number | null) => - useAppStore.setState((state) => { - return { - mapTab: { - ...state.mapTab, - [id]: { ...state.mapTab[id], year: year } - } - } - }) + useAppStore.setState((state) => ({ + mapTab: { + ...state.mapTab, + [id]: { ...state.mapTab[id], year }, + }, + })) export const deleteMapTab = (id: string) => useAppStore.setState((state) => { - const { [id]: _, ...remainingTabs } = state.mapTab; - const keys = Object.keys(remainingTabs); + const { [id]: _, ...remainingTabs } = state.mapTab + const newOrder = state.tabOrder.filter((tid) => tid !== id) return { mapTab: remainingTabs, - currentTab: keys.length > 0 ? keys[keys.length - 1] : null, - }; + tabOrder: newOrder, + currentTab: newOrder.length > 0 ? newOrder[newOrder.length - 1] : null, + } }) export const addMapTab = () => { - const id = uuidv4(); + const id = uuidv4() + initializeObjectsState(id, null, null, null, null) + initializeMapState(id) useAppStore.setState((state) => ({ mapTab: { ...state.mapTab, - [id]: { - year: null, - region: null, - district: null, - }, + [id]: { year: null, region: null, district: null }, }, - currentTab: id, // optionally switch to this new tab - })); + tabOrder: [...state.tabOrder, id], + currentTab: id, + })) - return id; // so you can use the new id in your components -} \ No newline at end of file + return id +} + +export const reorderTabs = (from: number, to: number) => + useAppStore.setState((state) => { + const newOrder = Array.from(state.tabOrder) + const [moved] = newOrder.splice(from, 1) + newOrder.splice(to, 0, moved) + return { tabOrder: newOrder } + }) diff --git a/client/yarn.lock b/client/yarn.lock index c264158..b9a05e9 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -987,29 +987,6 @@ resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz" integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== -"@dnd-kit/accessibility@^3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz" - integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw== - dependencies: - tslib "^2.0.0" - -"@dnd-kit/core@^6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz" - integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ== - dependencies: - "@dnd-kit/accessibility" "^3.1.1" - "@dnd-kit/utilities" "^3.2.2" - tslib "^2.0.0" - -"@dnd-kit/utilities@^3.2.2": - version "3.2.2" - resolved "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz" - integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== - dependencies: - tslib "^2.0.0" - "@emotion/hash@^0.9.0": version "0.9.2" resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" @@ -5584,7 +5561,7 @@ rc@^1.0.1, rc@^1.1.6: minimist "^1.2.0" strip-json-comments "~2.0.1" -"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react-dom@^18.0.0, react-dom@^18.2.0, "react-dom@>=16.14.0 <19.0.0", "react-dom@>=16.14.0 <20.0.0", react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, "react-dom@>=16.8.0 <19.0.0", "react-dom@>=16.8.0 <20.0.0", react-dom@>=18.0.0: +"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react-dom@^18.0.0, react-dom@^18.2.0, "react-dom@>=16.14.0 <19.0.0", "react-dom@>=16.14.0 <20.0.0", react-dom@>=16.6.0, react-dom@>=16.8, "react-dom@>=16.8.0 <19.0.0", "react-dom@>=16.8.0 <20.0.0", react-dom@>=18.0.0: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -5640,7 +5617,7 @@ react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" -"react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18.0 || ^19", react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16", "react@>=16.14.0 <19.0.0", "react@>=16.14.0 <20.0.0", react@>=16.6.0, react@>=16.8, react@>=16.8.0, "react@>=16.8.0 <19.0.0", "react@>=16.8.0 <20.0.0", react@>=18.0.0: +"react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18.0 || ^19", react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16", "react@>=16.14.0 <19.0.0", "react@>=16.14.0 <20.0.0", react@>=16.6.0, react@>=16.8, "react@>=16.8.0 <19.0.0", "react@>=16.8.0 <20.0.0", react@>=18.0.0: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -6529,7 +6506,7 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -tslib@^2.0.0, tslib@^2.1.0, tslib@^2.8.0, tslib@^2.8.1: +tslib@^2.1.0, tslib@^2.8.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==