From ce42d687ed91d2d50026ddfca2053f038187f7b9 Mon Sep 17 00:00:00 2001 From: Donkie Date: Wed, 20 Nov 2024 21:08:43 +0100 Subject: [PATCH] Some file rearranging --- .../pages/locations/components/location.tsx | 129 ++++++ .../components/locationContainer.tsx | 153 +++++++ .../pages/locations/components/spoolCard.tsx | 75 ++++ .../pages/locations/components/spoolList.tsx | 40 ++ client/src/pages/locations/index.tsx | 383 +----------------- client/src/pages/locations/itemTypes.ts | 4 + 6 files changed, 406 insertions(+), 378 deletions(-) create mode 100644 client/src/pages/locations/components/location.tsx create mode 100644 client/src/pages/locations/components/locationContainer.tsx create mode 100644 client/src/pages/locations/components/spoolCard.tsx create mode 100644 client/src/pages/locations/components/spoolList.tsx create mode 100644 client/src/pages/locations/itemTypes.ts diff --git a/client/src/pages/locations/components/location.tsx b/client/src/pages/locations/components/location.tsx new file mode 100644 index 0000000..eaa8dea --- /dev/null +++ b/client/src/pages/locations/components/location.tsx @@ -0,0 +1,129 @@ +import { Button, Input } from "antd"; +import type { Identifier, XYCoord } from "dnd-core"; +import { useRef, useState } from "react"; +import { useDrag, useDrop } from "react-dnd"; + +import { DeleteOutlined } from "@ant-design/icons"; +import { ISpool } from "../../spools/model"; +import { ItemTypes } from "../itemTypes"; +import { SpoolList } from "./spoolList"; + +interface DragItem { + index: number; + title: string; +} + +export function Location({ + index, + title, + spools, + showDelete, + onDelete, + moveLocation, + onEditTitle, +}: { + index: number; + title: string; + spools: ISpool[]; + showDelete?: boolean; + onDelete?: () => void; + moveLocation: (dragIndex: number, hoverIndex: number) => void; + onEditTitle: (newTitle: string) => void; +}) { + const [editTitle, setEditTitle] = useState(false); + const [newTitle, setNewTitle] = useState(title); + + 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, + canDrag: !editTitle, + item: () => { + return { title, index }; + }, + collect: (monitor: any) => ({ + isDragging: monitor.isDragging(), + }), + }); + + const opacity = isDragging ? 0 : 1; + drag(drop(ref)); + + return ( +
+

+ {editTitle ? ( + setEditTitle(false)} + onChange={(e) => setNewTitle(e.target.value)} + onPressEnter={() => { + setEditTitle(false); + return onEditTitle(newTitle); + }} + /> + ) : ( + { + setNewTitle(title); + setEditTitle(true); + }} + > + {title} + + )} + {showDelete &&

+ +
+ ); +} diff --git a/client/src/pages/locations/components/locationContainer.tsx b/client/src/pages/locations/components/locationContainer.tsx new file mode 100644 index 0000000..78852ea --- /dev/null +++ b/client/src/pages/locations/components/locationContainer.tsx @@ -0,0 +1,153 @@ +import { PlusOutlined } from "@ant-design/icons"; +import { useInvalidate, useList, useTranslate } from "@refinedev/core"; +import { Button } from "antd"; +import { useEffect, useMemo } from "react"; +import { useSetSetting } from "../../../utils/querySettings"; +import { ISpool } from "../../spools/model"; +import { renameSpoolLocation, useLocations } from "../functions"; +import { Location } from "./location"; + +export function LocationContainer() { + const t = useTranslate(); + const invalidate = useInvalidate(); + + const settingsLocations = useLocations(); + const setLocationsSetting = useSetSetting("locations"); + + const { data, isLoading, isError } = useList({ + resource: "spool", + meta: { + queryParams: { + ["allow_archived"]: false, + }, + }, + pagination: { + mode: "off", + }, + }); + + // 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 grouped: Record = {}; + spools.forEach((spool) => { + const loc = spool.location ?? t("spool.no_location"); + if (!grouped[loc]) { + grouped[loc] = []; + } + grouped[loc].push(spool); + }); + 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)) { + if (!allLocs.includes(loc)) { + allLocs.push(loc); + } + } + + return allLocs; + }, [spoolLocations, settingsLocations]); + + const moveLocation = (dragIndex: number, hoverIndex: number) => { + const newLocs = [...locationsList]; + newLocs.splice(dragIndex, 1); + newLocs.splice(hoverIndex, 0, locationsList[dragIndex]); + setLocationsSetting.mutate(newLocs); + }; + + const onEditTitle = async (location: string, newTitle: string) => { + if (newTitle == location) return; + if (newTitle == "") return; + if (locationsList.includes(newTitle)) return; + + // 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"] }); + } + + // Update the value in the settings + const newLocs = [...locationsList]; + newLocs[locationsList.indexOf(location)] = newTitle; + setLocationsSetting.mutate(newLocs); + }; + + // Create containers + const containers = locationsList.map((loc, idx) => { + const spools = spoolLocations[loc] ?? []; + return ( + { + setLocationsSetting.mutate(locationsList.filter((l) => l !== loc)); + }} + moveLocation={moveLocation} + onEditTitle={(newTitle: string) => onEditTitle(loc, newTitle)} + /> + ); + }); + + // Update locations settings so it always includes all spool locations + useEffect(() => { + // Check if they're not the same + if (JSON.stringify(locationsList) !== JSON.stringify(settingsLocations)) { + console.log("Updating locations settings", locationsList, settingsLocations); + setLocationsSetting.mutate(locationsList); + } + }, [spoolLocations]); + + if (isLoading) { + return
Loading...
; + } + + if (isError) { + return
Failed to load spools
; + } + + const addNewLocation = () => { + let newLocationName = "New Location"; + + const newLocs = [...locationsList]; + let i = 1; + while (newLocs.includes(newLocationName)) { + newLocationName = "New Location " + i; + i++; + } + newLocs.push(newLocationName); + + setLocationsSetting.mutate(newLocs); + }; + + return ( +
+ {containers} +
+
+
+ ); +} diff --git a/client/src/pages/locations/components/spoolCard.tsx b/client/src/pages/locations/components/spoolCard.tsx new file mode 100644 index 0000000..5785bcc --- /dev/null +++ b/client/src/pages/locations/components/spoolCard.tsx @@ -0,0 +1,75 @@ +import { useNavigation, useTranslate } from "@refinedev/core"; +import { Button, theme } from "antd"; +import { useDrag } from "react-dnd"; + +import { EditOutlined, EyeOutlined } from "@ant-design/icons"; +import { Link } from "react-router-dom"; +import SpoolIcon from "../../../components/spoolIcon"; +import { ISpool } from "../../spools/model"; +import { ItemTypes } from "../itemTypes"; + +const { useToken } = theme; + +export function SpoolCard({ spool }: { spool: ISpool }) { + 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(); + + const colorObj = spool.filament.multi_color_hexes + ? { + colors: spool.filament.multi_color_hexes.split(","), + vertical: spool.filament.multi_color_direction === "longitudinal", + } + : spool.filament.color_hex || "#000000"; + + let filament_name: string; + if (spool.filament.vendor && "name" in spool.filament.vendor) { + filament_name = `${spool.filament.vendor.name} - ${spool.filament.name}`; + } else { + filament_name = spool.filament.name ?? spool.filament.id.toString(); + } + + const style = { + opacity, + backgroundColor: token.colorBgContainerDisabled, + }; + + return ( +
+ +
+
+ + #{spool.id} {filament_name} + +
+ +
+
+
+ {spool.remaining_weight} / {spool.filament.weight} g +
+
+
+ ); +} diff --git a/client/src/pages/locations/components/spoolList.tsx b/client/src/pages/locations/components/spoolList.tsx new file mode 100644 index 0000000..0b68fdc --- /dev/null +++ b/client/src/pages/locations/components/spoolList.tsx @@ -0,0 +1,40 @@ +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 }: { location: string; spools: ISpool[] }) { + const { token } = useToken(); + const invalidate = useInvalidate(); + const [, spoolDrop] = useDrop(() => ({ + accept: ItemTypes.SPOOL, + drop: (item: ISpool) => { + setSpoolLocation(item.id, location).then(() => { + invalidate({ + resource: "spool", + id: item.id, + invalidates: ["list", "detail"], + }); + }); + }, + })); + + const style = { + backgroundColor: token.colorBgContainer, + borderRadius: token.borderRadiusLG, + }; + + return ( +
+ {spools.map((spool) => ( + + ))} +
+ ); +} diff --git a/client/src/pages/locations/index.tsx b/client/src/pages/locations/index.tsx index 50be7f4..f9f127e 100644 --- a/client/src/pages/locations/index.tsx +++ b/client/src/pages/locations/index.tsx @@ -1,392 +1,19 @@ -import { IResourceComponentsProps, useInvalidate, useList, useNavigation, useTranslate } from "@refinedev/core"; -import { Button, Input, theme } from "antd"; +import { IResourceComponentsProps } from "@refinedev/core"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; -import type { Identifier, XYCoord } from "dnd-core"; -import React, { useEffect, useMemo, useRef, useState } from "react"; -import { DndProvider, useDrag, useDrop } from "react-dnd"; +import React from "react"; +import { DndProvider } 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 { useSetSetting } from "../../utils/querySettings"; -import { ISpool } from "../spools/model"; -import { renameSpoolLocation, setSpoolLocation, useLocations } from "./functions"; +import { LocationContainer } from "./components/locationContainer"; import "./locations.css"; dayjs.extend(utc); -const ItemTypes = { - SPOOL: "spool", - CONTAINER: "spool-container", -}; - -const { useToken } = theme; - -function SpoolCard({ spool }: { spool: ISpool }) { - 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(); - - const colorObj = spool.filament.multi_color_hexes - ? { - colors: spool.filament.multi_color_hexes.split(","), - vertical: spool.filament.multi_color_direction === "longitudinal", - } - : spool.filament.color_hex || "#000000"; - - let filament_name: string; - if (spool.filament.vendor && "name" in spool.filament.vendor) { - filament_name = `${spool.filament.vendor.name} - ${spool.filament.name}`; - } else { - filament_name = spool.filament.name ?? spool.filament.id.toString(); - } - - const style = { - opacity, - backgroundColor: token.colorBgContainerDisabled, - }; - - return ( -
- -
-
- - #{spool.id} {filament_name} - -
- -
-
-
- {spool.remaining_weight} / {spool.filament.weight} g -
-
-
- ); -} - -function SpoolList({ location, spools }: { location: string; spools: ISpool[] }) { - const { token } = useToken(); - const invalidate = useInvalidate(); - const [, spoolDrop] = useDrop(() => ({ - accept: ItemTypes.SPOOL, - drop: (item: ISpool) => { - setSpoolLocation(item.id, location).then(() => { - invalidate({ - resource: "spool", - id: item.id, - invalidates: ["list", "detail"], - }); - }); - }, - })); - - const style = { - backgroundColor: token.colorBgContainer, - borderRadius: token.borderRadiusLG, - }; - - return ( -
- {spools.map((spool) => ( - - ))} -
- ); -} - -interface DragItem { - index: number; - title: string; -} - -function LocationContainer({ - index, - title, - spools, - showDelete, - onDelete, - moveLocation, - onEditTitle, -}: { - index: number; - title: string; - spools: ISpool[]; - showDelete?: boolean; - onDelete?: () => void; - moveLocation: (dragIndex: number, hoverIndex: number) => void; - onEditTitle: (newTitle: string) => void; -}) { - const [editTitle, setEditTitle] = useState(false); - const [newTitle, setNewTitle] = useState(title); - - 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, - canDrag: !editTitle, - item: () => { - return { title, index }; - }, - collect: (monitor: any) => ({ - isDragging: monitor.isDragging(), - }), - }); - - const opacity = isDragging ? 0 : 1; - drag(drop(ref)); - - return ( -
-

- {editTitle ? ( - setEditTitle(false)} - onChange={(e) => setNewTitle(e.target.value)} - onPressEnter={() => { - setEditTitle(false); - return onEditTitle(newTitle); - }} - /> - ) : ( - { - setNewTitle(title); - setEditTitle(true); - }} - > - {title} - - )} - {showDelete &&

- -
- ); -} - -function LocationMetaContainer() { - const t = useTranslate(); - const invalidate = useInvalidate(); - - const settingsLocations = useLocations(); - const setLocationsSetting = useSetSetting("locations"); - - const { data, isLoading, isError } = useList({ - resource: "spool", - meta: { - queryParams: { - ["allow_archived"]: false, - }, - }, - pagination: { - mode: "off", - }, - }); - - // 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 grouped: Record = {}; - spools.forEach((spool) => { - const loc = spool.location ?? t("spool.no_location"); - if (!grouped[loc]) { - grouped[loc] = []; - } - grouped[loc].push(spool); - }); - 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)) { - if (!allLocs.includes(loc)) { - allLocs.push(loc); - } - } - - return allLocs; - }, [spoolLocations, settingsLocations]); - - const moveLocation = (dragIndex: number, hoverIndex: number) => { - const newLocs = [...locationsList]; - newLocs.splice(dragIndex, 1); - newLocs.splice(hoverIndex, 0, locationsList[dragIndex]); - setLocationsSetting.mutate(newLocs); - }; - - const onEditTitle = async (location: string, newTitle: string) => { - if (newTitle == location) return; - if (newTitle == "") return; - if (locationsList.includes(newTitle)) return; - - // 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"] }); - } - - // Update the value in the settings - const newLocs = [...locationsList]; - newLocs[locationsList.indexOf(location)] = newTitle; - setLocationsSetting.mutate(newLocs); - }; - - // Create containers - const containers = locationsList.map((loc, idx) => { - const spools = spoolLocations[loc] ?? []; - return ( - { - setLocationsSetting.mutate(locationsList.filter((l) => l !== loc)); - }} - moveLocation={moveLocation} - onEditTitle={(newTitle: string) => onEditTitle(loc, newTitle)} - /> - ); - }); - - // Update locations settings so it always includes all spool locations - useEffect(() => { - // Check if they're not the same - if (JSON.stringify(locationsList) !== JSON.stringify(settingsLocations)) { - console.log("Updating locations settings", locationsList, settingsLocations); - setLocationsSetting.mutate(locationsList); - } - }, [spoolLocations]); - - if (isLoading) { - return
Loading...
; - } - - if (isError) { - return
Failed to load spools
; - } - - const addNewLocation = () => { - let newLocationName = "New Location"; - - const newLocs = [...locationsList]; - let i = 1; - while (newLocs.includes(newLocationName)) { - newLocationName = "New Location " + i; - i++; - } - newLocs.push(newLocationName); - - setLocationsSetting.mutate(newLocs); - }; - - return ( -
- {containers} -
-
-
- ); -} - export const Locations: React.FC = () => { return ( - + ); }; diff --git a/client/src/pages/locations/itemTypes.ts b/client/src/pages/locations/itemTypes.ts new file mode 100644 index 0000000..76cc06a --- /dev/null +++ b/client/src/pages/locations/itemTypes.ts @@ -0,0 +1,4 @@ +export const ItemTypes = { + SPOOL: "spool", + CONTAINER: "spool-container", +};