Added ability to rename location
This commit is contained in:
@@ -33,3 +33,19 @@ export function useLocations(): string[] {
|
|||||||
}
|
}
|
||||||
}, [query.data]);
|
}, [query.data]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function renameSpoolLocation(location: string, newName: string): Promise<string> {
|
||||||
|
const response = await fetch(getAPIURL() + "/location/" + location, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: newName,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
return await response.text();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { IResourceComponentsProps, useInvalidate, useList, useNavigation, useTranslate } from "@refinedev/core";
|
import { IResourceComponentsProps, useInvalidate, useList, useNavigation, useTranslate } from "@refinedev/core";
|
||||||
import { Button, theme } from "antd";
|
import { Button, Input, theme } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import type { Identifier, XYCoord } from "dnd-core";
|
import type { Identifier, XYCoord } from "dnd-core";
|
||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { DndProvider, useDrag, useDrop } from "react-dnd";
|
import { DndProvider, useDrag, useDrop } from "react-dnd";
|
||||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import { Link } from "react-router-dom";
|
|||||||
import SpoolIcon from "../../components/spoolIcon";
|
import SpoolIcon from "../../components/spoolIcon";
|
||||||
import { useSetSetting } from "../../utils/querySettings";
|
import { useSetSetting } from "../../utils/querySettings";
|
||||||
import { ISpool } from "../spools/model";
|
import { ISpool } from "../spools/model";
|
||||||
import { setSpoolLocation, useLocations } from "./functions";
|
import { renameSpoolLocation, setSpoolLocation, useLocations } from "./functions";
|
||||||
import "./locations.css";
|
import "./locations.css";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
@@ -130,6 +130,7 @@ function LocationContainer({
|
|||||||
showDelete,
|
showDelete,
|
||||||
onDelete,
|
onDelete,
|
||||||
moveLocation,
|
moveLocation,
|
||||||
|
onEditTitle,
|
||||||
}: {
|
}: {
|
||||||
index: number;
|
index: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -137,7 +138,11 @@ function LocationContainer({
|
|||||||
showDelete?: boolean;
|
showDelete?: boolean;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
moveLocation: (dragIndex: number, hoverIndex: number) => void;
|
moveLocation: (dragIndex: number, hoverIndex: number) => void;
|
||||||
|
onEditTitle: (newTitle: string) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const [editTitle, setEditTitle] = useState(false);
|
||||||
|
const [newTitle, setNewTitle] = useState(title);
|
||||||
|
|
||||||
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,
|
accept: ItemTypes.CONTAINER,
|
||||||
@@ -189,6 +194,7 @@ function LocationContainer({
|
|||||||
|
|
||||||
const [{ isDragging }, drag] = useDrag({
|
const [{ isDragging }, drag] = useDrag({
|
||||||
type: ItemTypes.CONTAINER,
|
type: ItemTypes.CONTAINER,
|
||||||
|
canDrag: !editTitle,
|
||||||
item: () => {
|
item: () => {
|
||||||
return { title, index };
|
return { title, index };
|
||||||
},
|
},
|
||||||
@@ -203,7 +209,28 @@ function LocationContainer({
|
|||||||
return (
|
return (
|
||||||
<div className="loc-container" ref={ref} style={{ opacity }} data-handler-id={handlerId}>
|
<div className="loc-container" ref={ref} style={{ opacity }} data-handler-id={handlerId}>
|
||||||
<h3>
|
<h3>
|
||||||
<span>{title}</span>
|
{editTitle ? (
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
variant="borderless"
|
||||||
|
value={newTitle}
|
||||||
|
onBlur={() => setEditTitle(false)}
|
||||||
|
onChange={(e) => setNewTitle(e.target.value)}
|
||||||
|
onPressEnter={() => {
|
||||||
|
setEditTitle(false);
|
||||||
|
return onEditTitle(newTitle);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
onClick={() => {
|
||||||
|
setNewTitle(title);
|
||||||
|
setEditTitle(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{showDelete && <Button icon={<DeleteOutlined />} size="small" type="text" onClick={onDelete} />}
|
{showDelete && <Button icon={<DeleteOutlined />} size="small" type="text" onClick={onDelete} />}
|
||||||
</h3>
|
</h3>
|
||||||
<SpoolList location={title} spools={spools} />
|
<SpoolList location={title} spools={spools} />
|
||||||
@@ -213,6 +240,7 @@ function LocationContainer({
|
|||||||
|
|
||||||
function LocationMetaContainer() {
|
function LocationMetaContainer() {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
const invalidate = useInvalidate();
|
||||||
|
|
||||||
const settingsLocations = useLocations();
|
const settingsLocations = useLocations();
|
||||||
const setLocationsSetting = useSetSetting<string[]>("locations");
|
const setLocationsSetting = useSetSetting<string[]>("locations");
|
||||||
@@ -266,7 +294,23 @@ function LocationMetaContainer() {
|
|||||||
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]);
|
||||||
console.log("newLocs", newLocs);
|
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);
|
setLocationsSetting.mutate(newLocs);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,6 +328,7 @@ function LocationMetaContainer() {
|
|||||||
setLocationsSetting.mutate(locationsList.filter((l) => l !== loc));
|
setLocationsSetting.mutate(locationsList.filter((l) => l !== loc));
|
||||||
}}
|
}}
|
||||||
moveLocation={moveLocation}
|
moveLocation={moveLocation}
|
||||||
|
onEditTitle={(newTitle: string) => onEditTitle(loc, newTitle)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
.loc-container {
|
.loc-container {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
width: 24em;
|
width: 24em;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loc-container h3 {
|
.loc-container h3 {
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loc-container .loc-spools {
|
.loc-container .loc-spools {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
from pydantic import BaseModel, Field, RootModel
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from spoolman.database import filament, spool
|
from spoolman.database import filament, spool
|
||||||
@@ -123,3 +124,25 @@ async def find_locations(
|
|||||||
db: Annotated[AsyncSession, Depends(get_db_session)],
|
db: Annotated[AsyncSession, Depends(get_db_session)],
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
return await spool.find_locations(db=db)
|
return await spool.find_locations(db=db)
|
||||||
|
|
||||||
|
|
||||||
|
class RenameLocationBody(BaseModel):
|
||||||
|
name: str = Field(description="The new name of the location.", min_length=1)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch(
|
||||||
|
"/location/{location}",
|
||||||
|
name="Rename location",
|
||||||
|
description="Rename a spool location. All spools in this location will be moved to the new location.",
|
||||||
|
response_model_exclude_none=True,
|
||||||
|
response_model=RootModel[str],
|
||||||
|
)
|
||||||
|
async def rename_location(
|
||||||
|
location: str,
|
||||||
|
*,
|
||||||
|
db: Annotated[AsyncSession, Depends(get_db_session)],
|
||||||
|
body: RenameLocationBody,
|
||||||
|
) -> str:
|
||||||
|
logger.info("Renaming location %s to %s", location, body.name)
|
||||||
|
await spool.rename_location(db=db, current_name=location, new_name=body.name)
|
||||||
|
return body.name
|
||||||
|
|||||||
@@ -462,3 +462,15 @@ async def reset_initial_weight(db: AsyncSession, spool_id: int, weight: float) -
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
await spool_changed(spool, EventType.UPDATED)
|
await spool_changed(spool, EventType.UPDATED)
|
||||||
return spool
|
return spool
|
||||||
|
|
||||||
|
|
||||||
|
async def rename_location(
|
||||||
|
*,
|
||||||
|
db: AsyncSession,
|
||||||
|
current_name: str,
|
||||||
|
new_name: str,
|
||||||
|
) -> None:
|
||||||
|
"""Rename all spools with the current location name to the new name."""
|
||||||
|
await db.execute(
|
||||||
|
sqlalchemy.update(models.Spool).where(models.Spool.location == current_name).values(location=new_name),
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user