diff --git a/client/src/pages/locations/functions.ts b/client/src/pages/locations/functions.ts
index 4bb91af..a500400 100644
--- a/client/src/pages/locations/functions.ts
+++ b/client/src/pages/locations/functions.ts
@@ -1,3 +1,5 @@
+import { useMemo } from "react";
+import { useGetSetting } from "../../utils/querySettings";
import { getAPIURL } from "../../utils/url";
import { ISpool } from "../spools/model";
@@ -16,3 +18,18 @@ export async function setSpoolLocation(spool_id: number, location: string | null
}
return response.json();
}
+
+export function useLocations(): string[] {
+ const query = useGetSetting("locations");
+
+ return useMemo(() => {
+ if (!query.data) return [];
+
+ try {
+ return JSON.parse(query.data.value) as string[];
+ } catch {
+ console.warn("Failed to parse locations", query.data.value);
+ return [];
+ }
+ }, [query.data]);
+}
diff --git a/client/src/pages/locations/index.tsx b/client/src/pages/locations/index.tsx
index 818c540..7083d7e 100644
--- a/client/src/pages/locations/index.tsx
+++ b/client/src/pages/locations/index.tsx
@@ -2,20 +2,26 @@ import { IResourceComponentsProps, useInvalidate, useList, useNavigation, useTra
import { Button, theme } from "antd";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
-import React, { useEffect, useMemo } from "react";
+import type { Identifier, XYCoord } from "dnd-core";
+import React, { useEffect, useMemo, useRef } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { DeleteOutlined, EditOutlined, EyeOutlined, PlusOutlined } from "@ant-design/icons";
import { Link } from "react-router-dom";
import SpoolIcon from "../../components/spoolIcon";
-import { useGetSetting, useSetSetting } from "../../utils/querySettings";
+import { useSetSetting } from "../../utils/querySettings";
import { ISpool } from "../spools/model";
-import { setSpoolLocation } from "./functions";
+import { setSpoolLocation, useLocations } from "./functions";
import "./locations.css";
dayjs.extend(utc);
+const ItemTypes = {
+ SPOOL: "spool",
+ CONTAINER: "spool-container",
+};
+
const { useToken } = theme;
function SpoolCard({ spool }: { spool: ISpool }) {
@@ -23,7 +29,7 @@ function SpoolCard({ spool }: { spool: ISpool }) {
const t = useTranslate();
const [{ opacity }, dragRef] = useDrag(
() => ({
- type: "spool",
+ type: ItemTypes.SPOOL,
item: spool,
collect: (monitor) => ({
opacity: monitor.isDragging() ? 0.5 : 1,
@@ -82,24 +88,13 @@ function SpoolCard({ spool }: { spool: ISpool }) {
);
}
-function LocationContainer({
- title,
- spools,
- showDelete,
- onDelete,
-}: {
- title: string;
- spools: ISpool[];
- showDelete?: boolean;
- onDelete?: () => void;
-}) {
+function SpoolList({ location, spools }: { location: string; spools: ISpool[] }) {
const { token } = useToken();
const invalidate = useInvalidate();
-
- const [, drop] = useDrop(() => ({
- accept: "spool",
+ const [, spoolDrop] = useDrop(() => ({
+ accept: ItemTypes.SPOOL,
drop: (item: ISpool) => {
- setSpoolLocation(item.id, title).then(() => {
+ setSpoolLocation(item.id, location).then(() => {
invalidate({
resource: "spool",
id: item.id,
@@ -115,36 +110,108 @@ function LocationContainer({
};
return (
-
-
- {title}
- {showDelete && } size="small" type="text" onClick={onDelete} />}
-
-
- {spools.map((spool) => (
-
- ))}
-
+
+ {spools.map((spool) => (
+
+ ))}
);
}
-function useLocations(): string[] {
- const query = useGetSetting("locations");
-
- return useMemo(() => {
- if (!query.data) return [];
-
- try {
- return JSON.parse(query.data.value) as string[];
- } catch {
- console.warn("Failed to parse locations", query.data.value);
- return [];
- }
- }, [query.data]);
+interface DragItem {
+ index: number;
+ title: string;
}
-export const Locations: React.FC
= () => {
+function LocationContainer({
+ index,
+ title,
+ spools,
+ showDelete,
+ onDelete,
+ moveLocation,
+}: {
+ index: number;
+ title: string;
+ spools: ISpool[];
+ showDelete?: boolean;
+ onDelete?: () => void;
+ moveLocation: (dragIndex: number, hoverIndex: number) => void;
+}) {
+ const ref = useRef(null);
+ const [{ handlerId }, drop] = useDrop({
+ accept: ItemTypes.CONTAINER,
+ collect(monitor) {
+ return {
+ handlerId: monitor.getHandlerId(),
+ };
+ },
+ hover(item, monitor) {
+ if (!ref.current) {
+ return null;
+ }
+ const dragIndex = item.index;
+ const hoverIndex = index;
+
+ // Don't replace items with themselves
+ if (dragIndex === hoverIndex) {
+ return;
+ }
+
+ // Determine rectangle on screen
+ const hoverBoundingRect = ref.current?.getBoundingClientRect();
+
+ // Get horizontal middle
+ const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
+
+ // Determine mouse position
+ const clientOffset = monitor.getClientOffset();
+
+ // Get pixels to the left
+ const hoverClientX = (clientOffset as XYCoord).x - hoverBoundingRect.left;
+
+ // Dragging downwards
+ if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) {
+ return;
+ }
+
+ // Dragging upwards
+ if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) {
+ return;
+ }
+
+ // Time to actually perform the action
+ moveLocation(dragIndex, hoverIndex);
+
+ item.index = hoverIndex;
+ },
+ });
+
+ const [{ isDragging }, drag] = useDrag({
+ type: ItemTypes.CONTAINER,
+ item: () => {
+ return { title, index };
+ },
+ collect: (monitor: any) => ({
+ isDragging: monitor.isDragging(),
+ }),
+ });
+
+ const opacity = isDragging ? 0 : 1;
+ drag(drop(ref));
+
+ return (
+
+
+ {title}
+ {showDelete && } size="small" type="text" onClick={onDelete} />}
+
+
+
+ );
+}
+
+function LocationMetaContainer() {
const t = useTranslate();
const settingsLocations = useLocations();
@@ -195,18 +262,28 @@ export const Locations: React.FC = () => {
return allLocs;
}, [spoolLocations, settingsLocations]);
+ const moveLocation = (dragIndex: number, hoverIndex: number) => {
+ const newLocs = [...locationsList];
+ newLocs.splice(dragIndex, 1);
+ newLocs.splice(hoverIndex, 0, locationsList[dragIndex]);
+ console.log("newLocs", newLocs);
+ setLocationsSetting.mutate(newLocs);
+ };
+
// Create containers
- const containers = locationsList.map((loc) => {
+ const containers = locationsList.map((loc, idx) => {
const spools = spoolLocations[loc] ?? [];
return (
{
setLocationsSetting.mutate(locationsList.filter((l) => l !== loc));
}}
+ moveLocation={moveLocation}
/>
);
});
@@ -229,22 +306,28 @@ export const Locations: React.FC = () => {
}
return (
-
-
- {containers}
-
- }
- size="large"
- style={{
- margin: "1em",
- }}
- onClick={() => setLocationsSetting.mutate([...settingsLocations, "New Location"])}
- />
-
+
+ {containers}
+
+ }
+ size="large"
+ style={{
+ margin: "1em",
+ }}
+ onClick={() => setLocationsSetting.mutate([...settingsLocations, "New Location"])}
+ />
+
+ );
+}
+
+export const Locations: React.FC
= () => {
+ return (
+
+
);
};