Improvements to default location
This commit is contained in:
@@ -4,9 +4,10 @@ import { useRef, useState } from "react";
|
|||||||
import { useDrag, useDrop } from "react-dnd";
|
import { useDrag, useDrop } from "react-dnd";
|
||||||
|
|
||||||
import { DeleteOutlined } from "@ant-design/icons";
|
import { DeleteOutlined } from "@ant-design/icons";
|
||||||
import { useUpdate } from "@refinedev/core";
|
import { useTranslate, useUpdate } from "@refinedev/core";
|
||||||
import { ISpool } from "../../spools/model";
|
import { ISpool } from "../../spools/model";
|
||||||
import { DragItem, ItemTypes, SpoolDragItem } from "../dnd";
|
import { DragItem, ItemTypes, SpoolDragItem } from "../dnd";
|
||||||
|
import { EMPTYLOC } from "../functions";
|
||||||
import { SpoolList } from "./spoolList";
|
import { SpoolList } from "./spoolList";
|
||||||
|
|
||||||
export function Location({
|
export function Location({
|
||||||
@@ -30,6 +31,7 @@ export function Location({
|
|||||||
locationSpoolOrder: number[];
|
locationSpoolOrder: number[];
|
||||||
setLocationSpoolOrder: (spoolOrder: number[]) => void;
|
setLocationSpoolOrder: (spoolOrder: number[]) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslate();
|
||||||
const [editTitle, setEditTitle] = useState(false);
|
const [editTitle, setEditTitle] = useState(false);
|
||||||
const [newTitle, setNewTitle] = useState(title);
|
const [newTitle, setNewTitle] = useState(title);
|
||||||
const { mutate: updateSpool } = useUpdate({
|
const { mutate: updateSpool } = useUpdate({
|
||||||
@@ -47,9 +49,11 @@ export function Location({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dropTypes = title == EMPTYLOC ? [ItemTypes.SPOOL] : [ItemTypes.CONTAINER, ItemTypes.SPOOL];
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
|
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
|
||||||
accept: [ItemTypes.CONTAINER, ItemTypes.SPOOL],
|
accept: dropTypes,
|
||||||
collect(monitor) {
|
collect(monitor) {
|
||||||
return {
|
return {
|
||||||
handlerId: monitor.getHandlerId(),
|
handlerId: monitor.getHandlerId(),
|
||||||
@@ -113,7 +117,7 @@ export function Location({
|
|||||||
|
|
||||||
const [{ isDragging }, drag] = useDrag({
|
const [{ isDragging }, drag] = useDrag({
|
||||||
type: ItemTypes.CONTAINER,
|
type: ItemTypes.CONTAINER,
|
||||||
canDrag: !editTitle,
|
canDrag: !editTitle && title != EMPTYLOC,
|
||||||
item: () => {
|
item: () => {
|
||||||
return { title, index };
|
return { title, index };
|
||||||
},
|
},
|
||||||
@@ -122,11 +126,20 @@ export function Location({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const displayTitle = title == EMPTYLOC ? t("locations.no_location") : title;
|
||||||
|
|
||||||
const opacity = isDragging ? 0 : 1;
|
const opacity = isDragging ? 0 : 1;
|
||||||
drag(drop(ref));
|
drag(drop(ref));
|
||||||
|
|
||||||
|
const canEditTitle = title != EMPTYLOC;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="loc-container" ref={ref} style={{ opacity }} data-handler-id={handlerId}>
|
<div
|
||||||
|
className={"loc-container " + (title != EMPTYLOC ? "grabable" : "")}
|
||||||
|
ref={ref}
|
||||||
|
style={{ opacity }}
|
||||||
|
data-handler-id={handlerId}
|
||||||
|
>
|
||||||
<h3>
|
<h3>
|
||||||
{editTitle ? (
|
{editTitle ? (
|
||||||
<Input
|
<Input
|
||||||
@@ -142,12 +155,14 @@ export function Location({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
|
className={canEditTitle ? "editable" : ""}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!canEditTitle) return;
|
||||||
setNewTitle(title);
|
setNewTitle(title);
|
||||||
setEditTitle(true);
|
setEditTitle(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{displayTitle}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{showDelete && <Button icon={<DeleteOutlined />} size="small" type="text" onClick={onDelete} />}
|
{showDelete && <Button icon={<DeleteOutlined />} size="small" type="text" onClick={onDelete} />}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { PlusOutlined } from "@ant-design/icons";
|
import { PlusOutlined } from "@ant-design/icons";
|
||||||
import { useInvalidate, useList, useTranslate } from "@refinedev/core";
|
import { useList, useTranslate } from "@refinedev/core";
|
||||||
import { Button } from "antd";
|
import { Button } from "antd";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useSetSetting } from "../../../utils/querySettings";
|
import { useSetSetting } from "../../../utils/querySettings";
|
||||||
import { ISpool } from "../../spools/model";
|
import { ISpool } from "../../spools/model";
|
||||||
import { useLocations, useLocationsSpoolOrders, useRenameSpoolLocation } from "../functions";
|
import { EMPTYLOC, useLocations, useLocationsSpoolOrders, useRenameSpoolLocation } from "../functions";
|
||||||
import { Location } from "./location";
|
import { Location } from "./location";
|
||||||
|
|
||||||
export function LocationContainer() {
|
export function LocationContainer() {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const invalidate = useInvalidate();
|
|
||||||
const renameSpoolLocation = useRenameSpoolLocation();
|
const renameSpoolLocation = useRenameSpoolLocation();
|
||||||
|
|
||||||
const settingsLocations = useLocations();
|
const settingsLocations = useLocations();
|
||||||
@@ -41,7 +40,7 @@ export function LocationContainer() {
|
|||||||
|
|
||||||
const grouped: Record<string, ISpool[]> = {};
|
const grouped: Record<string, ISpool[]> = {};
|
||||||
spools.forEach((spool) => {
|
spools.forEach((spool) => {
|
||||||
const loc = spool.location ?? t("locations.no_location");
|
const loc = spool.location ?? EMPTYLOC;
|
||||||
if (!grouped[loc]) {
|
if (!grouped[loc]) {
|
||||||
grouped[loc] = [];
|
grouped[loc] = [];
|
||||||
}
|
}
|
||||||
@@ -71,12 +70,18 @@ export function LocationContainer() {
|
|||||||
|
|
||||||
// Create list of locations that's sorted
|
// Create list of locations that's sorted
|
||||||
const locationsList = useMemo(() => {
|
const locationsList = useMemo(() => {
|
||||||
// Start with the locations setting
|
// Start with the default loc
|
||||||
let allLocs = settingsLocations;
|
let allLocs = [];
|
||||||
|
if (EMPTYLOC in spoolLocations) {
|
||||||
|
allLocs.push(EMPTYLOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add from the locations setting
|
||||||
|
if (settingsLocations) allLocs.push(...settingsLocations);
|
||||||
|
|
||||||
// Add any missing locations from the spools
|
// Add any missing locations from the spools
|
||||||
for (const loc of Object.keys(spoolLocations)) {
|
for (const loc of Object.keys(spoolLocations)) {
|
||||||
if (!allLocs.includes(loc)) {
|
if (loc != EMPTYLOC && !allLocs.includes(loc)) {
|
||||||
allLocs.push(loc);
|
allLocs.push(loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,13 +93,14 @@ export function LocationContainer() {
|
|||||||
const newLocs = [...locationsList];
|
const newLocs = [...locationsList];
|
||||||
newLocs.splice(dragIndex, 1);
|
newLocs.splice(dragIndex, 1);
|
||||||
newLocs.splice(hoverIndex, 0, locationsList[dragIndex]);
|
newLocs.splice(hoverIndex, 0, locationsList[dragIndex]);
|
||||||
setLocationsSetting.mutate(newLocs);
|
setLocationsSetting.mutate(newLocs.filter((loc) => loc != EMPTYLOC));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEditTitle = async (location: string, newTitle: string) => {
|
const onEditTitle = async (location: string, newTitle: string) => {
|
||||||
if (newTitle == location) return;
|
if (location == "") return; // Can't edit the default location
|
||||||
if (newTitle == "") return;
|
if (newTitle == location) return; // No change
|
||||||
if (locationsList.includes(newTitle)) return;
|
if (newTitle == "") return; // Can't have an empty location
|
||||||
|
if (locationsList.includes(newTitle)) return; // Location already exists
|
||||||
|
|
||||||
// Update all spool locations in the database
|
// Update all spool locations in the database
|
||||||
if (spoolLocations[location] && spoolLocations[location].length > 0) {
|
if (spoolLocations[location] && spoolLocations[location].length > 0) {
|
||||||
@@ -102,7 +108,7 @@ export function LocationContainer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the value in the settings
|
// Update the value in the settings
|
||||||
const newLocs = [...locationsList];
|
const newLocs = [...locationsList].filter((loc) => loc != EMPTYLOC);
|
||||||
newLocs[locationsList.indexOf(location)] = newTitle;
|
newLocs[locationsList.indexOf(location)] = newTitle;
|
||||||
setLocationsSetting.mutate(newLocs);
|
setLocationsSetting.mutate(newLocs);
|
||||||
};
|
};
|
||||||
@@ -140,10 +146,11 @@ export function LocationContainer() {
|
|||||||
// Update locations settings so it always includes all spool locations
|
// Update locations settings so it always includes all spool locations
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if they're not the same
|
// Check if they're not the same
|
||||||
if (JSON.stringify(locationsList) !== JSON.stringify(settingsLocations)) {
|
const curLocList = locationsList.filter((l) => l != EMPTYLOC);
|
||||||
setLocationsSetting.mutate(locationsList);
|
if (settingsLocations != null && JSON.stringify(curLocList) !== JSON.stringify(settingsLocations)) {
|
||||||
|
setLocationsSetting.mutate(curLocList);
|
||||||
}
|
}
|
||||||
}, [spoolLocations]);
|
}, [locationsList, settingsLocations, setLocationsSetting]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function SpoolCard({
|
|||||||
}) {
|
}) {
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const { editUrl, showUrl } = useNavigation();
|
const { showUrl } = useNavigation();
|
||||||
|
|
||||||
// Using a global state for this, because the drag handlers are reset when the spool changes location
|
// Using a global state for this, because the drag handlers are reset when the spool changes location
|
||||||
const { draggedSpoolId, setDraggedSpoolId } = useCurrentDraggedSpool();
|
const { draggedSpoolId, setDraggedSpoolId } = useCurrentDraggedSpool();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useGetSetting } from "../../utils/querySettings";
|
|||||||
import { getAPIURL } from "../../utils/url";
|
import { getAPIURL } from "../../utils/url";
|
||||||
import { ISpool } from "../spools/model";
|
import { ISpool } from "../spools/model";
|
||||||
|
|
||||||
|
export const EMPTYLOC = "";
|
||||||
|
|
||||||
interface LocationRename {
|
interface LocationRename {
|
||||||
old: string;
|
old: string;
|
||||||
new: string;
|
new: string;
|
||||||
@@ -74,11 +76,11 @@ export function useRenameSpoolLocation() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLocations(): string[] {
|
export function useLocations(): string[] | null {
|
||||||
const query = useGetSetting("locations");
|
const query = useGetSetting("locations");
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!query.data) return [];
|
if (!query.data) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data = (JSON.parse(query.data.value) ?? []) as string[];
|
let data = (JSON.parse(query.data.value) ?? []) as string[];
|
||||||
@@ -86,7 +88,7 @@ export function useLocations(): string[] {
|
|||||||
return data;
|
return data;
|
||||||
} catch {
|
} catch {
|
||||||
console.warn("Failed to parse locations", query.data.value);
|
console.warn("Failed to parse locations", query.data.value);
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
}, [query.data]);
|
}, [query.data]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,14 @@
|
|||||||
.loc-container {
|
.loc-container {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
width: 24em;
|
width: 24em;
|
||||||
cursor: pointer;
|
}
|
||||||
|
|
||||||
|
.loc-container.grabable,
|
||||||
|
.loc-container .spool {
|
||||||
|
cursor: move;
|
||||||
|
cursor: grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: -webkit-grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loc-container h3 {
|
.loc-container h3 {
|
||||||
@@ -15,6 +22,13 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loc-container h3 span {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loc-container h3 span.editable {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +45,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
min-height: 3em;
|
min-height: 3em;
|
||||||
max-height: 100em;
|
max-height: 50em;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +53,6 @@
|
|||||||
padding: 0.5em 0.5em 0.5em 0;
|
padding: 0.5em 0.5em 0.5em 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user