Final touches on locations page

This commit is contained in:
Donkie
2024-11-23 22:42:47 +01:00
parent bf06408368
commit 67c5b34bf5
10 changed files with 339 additions and 99 deletions

View File

@@ -4,15 +4,11 @@ import { useRef, useState } from "react";
import { useDrag, useDrop } from "react-dnd";
import { DeleteOutlined } from "@ant-design/icons";
import { useUpdate } from "@refinedev/core";
import { ISpool } from "../../spools/model";
import { ItemTypes } from "../itemTypes";
import { DragItem, ItemTypes, SpoolDragItem } from "../dnd";
import { SpoolList } from "./spoolList";
interface DragItem {
index: number;
title: string;
}
export function Location({
index,
title,
@@ -36,10 +32,24 @@ export function Location({
}) {
const [editTitle, setEditTitle] = useState(false);
const [newTitle, setNewTitle] = useState(title);
const { mutate: updateSpool } = useUpdate({
resource: "spool",
mutationMode: "optimistic",
successNotification: false,
});
const moveSpoolLocation = (spool_id: number, location: string) => {
updateSpool({
id: spool_id,
values: {
location: location,
},
});
};
const ref = useRef<HTMLDivElement>(null);
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
accept: ItemTypes.CONTAINER,
accept: [ItemTypes.CONTAINER, ItemTypes.SPOOL],
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
@@ -49,6 +59,21 @@ export function Location({
if (!ref.current) {
return null;
}
if ("spool" in item) {
// Only allow dropping spools on the container if it's empty.
if (spools.length > 0) {
return null;
}
const spoolitem = item as SpoolDragItem;
if (spoolitem.spool.location !== title) {
moveSpoolLocation(spoolitem.spool.id, title);
spoolitem.spool.location = title;
}
return null;
}
const dragIndex = item.index;
const hoverIndex = index;
@@ -127,12 +152,7 @@ export function Location({
)}
{showDelete && <Button icon={<DeleteOutlined />} size="small" type="text" onClick={onDelete} />}
</h3>
<SpoolList
location={title}
spools={spools}
spoolOrder={locationSpoolOrder}
setSpoolOrder={setLocationSpoolOrder}
/>
<SpoolList spools={spools} spoolOrder={locationSpoolOrder} setSpoolOrder={setLocationSpoolOrder} />
</div>
);
}

View File

@@ -4,21 +4,25 @@ import { Button } from "antd";
import { useEffect, useMemo } from "react";
import { useSetSetting } from "../../../utils/querySettings";
import { ISpool } from "../../spools/model";
import { renameSpoolLocation, useLocations, useLocationsSpoolOrders } from "../functions";
import { useLocations, useLocationsSpoolOrders, useRenameSpoolLocation } from "../functions";
import { Location } from "./location";
export function LocationContainer() {
const t = useTranslate();
const invalidate = useInvalidate();
const renameSpoolLocation = useRenameSpoolLocation();
const settingsLocations = useLocations();
const setLocationsSetting = useSetSetting<string[]>("locations");
const locationsSpoolOrders = useLocationsSpoolOrders();
console.log("locationsSpoolOrders", locationsSpoolOrders);
const setLocationsSpoolOrders = useSetSetting<Record<string, number[]>>("locations_spoolorders");
const { data, isLoading, isError } = useList<ISpool>({
const {
data: spoolData,
isLoading,
isError,
} = useList<ISpool>({
resource: "spool",
meta: {
queryParams: {
@@ -30,12 +34,11 @@ export function LocationContainer() {
},
});
// Grab spools and sort by ID
const spools = data?.data ?? [];
spools.sort((a, b) => a.id - b.id);
// Group spools by location
const spoolLocations = useMemo(() => {
const spoolLocations = (() => {
const spools = spoolData?.data ?? [];
spools.sort((a, b) => a.id - b.id);
const grouped: Record<string, ISpool[]> = {};
spools.forEach((spool) => {
const loc = spool.location ?? t("spool.no_location");
@@ -44,14 +47,32 @@ export function LocationContainer() {
}
grouped[loc].push(spool);
});
// Sort spools in the locations by the spool order
for (const loc of Object.keys(grouped)) {
if (!locationsSpoolOrders[loc]) {
continue;
}
grouped[loc].sort((a, b) => {
let aidx = locationsSpoolOrders[loc].indexOf(a.id);
if (aidx === -1) {
aidx = 999999;
}
let bidx = locationsSpoolOrders[loc].indexOf(b.id);
if (bidx === -1) {
bidx = 999999;
}
return aidx - bidx;
});
}
return grouped;
}, [spools]);
})();
// Create list of locations that's sorted
const locationsList = useMemo(() => {
// Start with the locations setting
let allLocs = settingsLocations;
console.log("settingsLocations", settingsLocations);
// Add any missing locations from the spools
for (const loc of Object.keys(spoolLocations)) {
@@ -77,8 +98,7 @@ export function LocationContainer() {
// Update all spool locations in the database
if (spoolLocations[location] && spoolLocations[location].length > 0) {
await renameSpoolLocation(location, newTitle);
await invalidate({ resource: "spool", invalidates: ["list", "detail"] });
renameSpoolLocation.mutate({ old: location, new: newTitle });
}
// Update the value in the settings

View File

@@ -1,30 +1,118 @@
import { useNavigation, useTranslate } from "@refinedev/core";
import { useNavigation, useTranslate, useUpdate } from "@refinedev/core";
import { Button, theme } from "antd";
import { useDrag } from "react-dnd";
import type { Identifier, XYCoord } from "dnd-core";
import { useDrag, useDrop } from "react-dnd";
import { EditOutlined, EyeOutlined } from "@ant-design/icons";
import { useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import SpoolIcon from "../../../components/spoolIcon";
import { ISpool } from "../../spools/model";
import { ItemTypes } from "../itemTypes";
import { ItemTypes, SpoolDragItem, useCurrentDraggedSpool } from "../dnd";
const { useToken } = theme;
export function SpoolCard({ spool }: { spool: ISpool }) {
export function SpoolCard({
index,
spool,
moveSpoolOrder,
}: {
index: number;
spool: ISpool;
moveSpoolOrder: (dragIndex: number, hoverIndex: number) => void;
}) {
const { token } = useToken();
const t = useTranslate();
const [{ opacity }, dragRef] = useDrag(
() => ({
type: ItemTypes.SPOOL,
item: spool,
collect: (monitor) => ({
opacity: monitor.isDragging() ? 0.5 : 1,
}),
}),
[]
);
const { editUrl, showUrl } = useNavigation();
// Using a global state for this, because the drag handlers are reset when the spool changes location
const { draggedSpoolId, setDraggedSpoolId } = useCurrentDraggedSpool();
const { mutate: updateSpool } = useUpdate({
resource: "spool",
mutationMode: "optimistic",
successNotification: false,
});
const moveSpoolLocation = (spool_id: number, location: string) => {
updateSpool({
id: spool_id,
values: {
location: location,
},
});
};
const ref = useRef<HTMLDivElement>(null);
const [{ handlerId }, drop] = useDrop<SpoolDragItem, void, { handlerId: Identifier | null }>({
accept: ItemTypes.SPOOL,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
};
},
hover(item, monitor) {
if (!ref.current || item.spool.id === spool.id) {
return null;
}
if (item.spool.location !== spool.location && spool.location) {
moveSpoolLocation(item.spool.id, spool.location);
item.spool.location = spool.location;
return;
}
const dragIndex = item.index;
const hoverIndex = index;
// Determine rectangle on screen
const hoverBoundingRect = ref.current?.getBoundingClientRect();
// Get horizontal middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// Determine mouse position
const clientOffset = monitor.getClientOffset();
// Get pixels to the top
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
// Time to actually perform the action
moveSpoolOrder(item.spool.id, hoverIndex);
item.index = hoverIndex;
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.SPOOL,
item: () => {
return { spool, index };
},
collect: (monitor: any) => ({
isDragging: monitor.isDragging(),
}),
end() {
setDraggedSpoolId(-1);
},
});
useEffect(() => {
if (isDragging) {
setDraggedSpoolId(spool.id);
}
}, [isDragging]);
const colorObj = spool.filament.multi_color_hexes
? {
colors: spool.filament.multi_color_hexes.split(","),
@@ -39,13 +127,15 @@ export function SpoolCard({ spool }: { spool: ISpool }) {
filament_name = spool.filament.name ?? spool.filament.id.toString();
}
const opacity = draggedSpoolId === spool.id ? 0 : 1;
const style = {
opacity,
backgroundColor: token.colorBgContainerDisabled,
};
drag(drop(ref));
return (
<div className="spool" ref={dragRef} style={style}>
<div className="spool" ref={ref} style={style} data-handler-id={handlerId}>
<SpoolIcon color={colorObj} />
<div className="info">
<div className="title">

View File

@@ -1,39 +1,19 @@
import { useInvalidate } from "@refinedev/core";
import { useDrop } from "react-dnd";
import { theme } from "antd";
import { ISpool } from "../../spools/model";
import { ItemTypes } from "../itemTypes";
import { setSpoolLocation } from "./../functions";
import { SpoolCard } from "./spoolCard";
const { useToken } = theme;
export function SpoolList({
location,
spools,
spoolOrder,
setSpoolOrder,
}: {
location: string;
spools: ISpool[];
spoolOrder: number[];
setSpoolOrder: (spoolOrder: number[]) => void;
}) {
const { token } = useToken();
const invalidate = useInvalidate();
const [, spoolDrop] = useDrop(() => ({
accept: ItemTypes.SPOOL,
drop: async (item: ISpool) => {
if (item.location === location) return;
await setSpoolLocation(item.id, location);
await invalidate({
resource: "spool",
id: item.id,
invalidates: ["list", "detail"],
});
},
}));
// Make sure all spools are in the spoolOrders array
const finalSpoolOrder = [...spoolOrder].filter((id) => spools.find((spool) => spool.id === id)); // Remove any spools that are not in the spools array
@@ -41,8 +21,23 @@ export function SpoolList({
if (!finalSpoolOrder.includes(spool.id)) finalSpoolOrder.push(spool.id);
});
// Reorder the spools based on the spoolOrder array
const reorderedSpools = finalSpoolOrder.map((id) => spools.find((spool) => spool.id === id)!);
const moveSpoolOrder = (spool_id: number, hoverIndex: number) => {
// Move spool spool_id to position hoverIndex
let curIdx = finalSpoolOrder.indexOf(spool_id);
if (curIdx === -1) {
// Spool is missing from spool order array, add it to the end of the array
finalSpoolOrder.push(spool_id);
curIdx = finalSpoolOrder.length - 1;
} else if (curIdx === hoverIndex) {
// Spool is already in the right position
return;
}
const newSpoolOrder = [...finalSpoolOrder];
newSpoolOrder.splice(curIdx, 1);
newSpoolOrder.splice(hoverIndex, 0, finalSpoolOrder[curIdx]);
setSpoolOrder(newSpoolOrder);
};
const style = {
backgroundColor: token.colorBgContainer,
@@ -50,9 +45,9 @@ export function SpoolList({
};
return (
<div className="loc-spools" ref={spoolDrop} style={style}>
{reorderedSpools.map((spool) => (
<SpoolCard key={spool.id} spool={spool} />
<div className="loc-spools" style={style}>
{spools.map((spool, idx) => (
<SpoolCard key={spool.id} index={idx} spool={spool} moveSpoolOrder={moveSpoolOrder} />
))}
</div>
);