diff --git a/frontend/src/component/FileManager/Explorer/ListView/Cell.tsx b/frontend/src/component/FileManager/Explorer/ListView/Cell.tsx index 8636bf5d..1c09468f 100644 --- a/frontend/src/component/FileManager/Explorer/ListView/Cell.tsx +++ b/frontend/src/component/FileManager/Explorer/ListView/Cell.tsx @@ -1,4 +1,4 @@ -import { Box, Fade, Grow, InputBase, PopoverProps, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Fade, Grow, IconButton, InputBase, PopoverProps, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material"; import React, { memo, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { sizeToString } from "../../../../util"; import CrUri, { SearchParam } from "../../../../util/uri.ts"; @@ -21,6 +21,7 @@ import { loadFileThumb, patchCustomProp } from "../../../../redux/thunks/file.ts import AutoHeight from "../../../Common/AutoHeight.tsx"; import { NoWrapBox } from "../../../Common/StyledComponents.tsx"; import TimeBadge from "../../../Common/TimeBadge.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; import Info from "../../../Icons/Info.tsx"; import { DisplayOption } from "../../ContextMenu/useActionDisplayOpt.ts"; import FileBadge from "../../FileBadge.tsx"; @@ -392,6 +393,7 @@ const CustomPropsCell = memo(({ file, customProp, readOnly }: CustomPropsCellPro const dispatch = useAppDispatch(); const fmIndex = useContext(FmIndexContext); const [loading, setLoading] = useState(false); + const [mouseOver, setMouseOver] = useState(false); const stopPropagation = useCallback((e: React.SyntheticEvent) => { e.stopPropagation(); @@ -407,6 +409,18 @@ const CustomPropsCell = memo(({ file, customProp, readOnly }: CustomPropsCellPro [dispatch, fmIndex, file, customProp.id], ); + const onDelete = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + if (loading || readOnly) return; + setLoading(true); + dispatch(patchCustomProp(fmIndex, file, customProp.id, "", true)).finally(() => { + setLoading(false); + }); + }, + [dispatch, fmIndex, file, customProp.id, loading, readOnly], + ); + return ( setMouseOver(true)} + onMouseLeave={() => setMouseOver(false)} + sx={{ width: "100%", display: "flex", alignItems: "center" }} > - {getPropsContent(customProp, onChange, loading, readOnly)} + {getPropsContent(customProp, onChange, loading, readOnly)} + {!readOnly && ( + + + + + + )} ); }); diff --git a/frontend/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx b/frontend/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx index 5883e0fd..1c86a80c 100644 --- a/frontend/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx +++ b/frontend/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx @@ -5,6 +5,8 @@ import { TransitionGroup } from "react-transition-group"; import { sendMetadataPatch } from "../../../../api/api.ts"; import { CustomProps as CustomPropsType, FileResponse } from "../../../../api/explorer.ts"; import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { fileUpdated } from "../../../../redux/fileManagerSlice.ts"; +import { FileManagerIndex } from "../../FileManager.tsx"; import { DisplayOption } from "../../ContextMenu/useActionDisplayOpt.ts"; import AddButton from "./AddButton.tsx"; import CustomPropsCard from "./CustomPropsItem.tsx"; @@ -28,22 +30,28 @@ const CustomProps = ({ file, setTarget, targetDisplayOptions }: CustomPropsProps const [loading, setLoading] = useState(false); const dispatch = useAppDispatch(); const custom_props = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + // `file` is a snapshot taken when the sidebar opened; prefer live metadata + // from the file list so props patched from list view appear immediately. + const liveMetadata = useAppSelector( + (state) => state.fileManager[FileManagerIndex.main]?.list?.files.find((f) => f.path === file.path)?.metadata, + ); + const metadata = liveMetadata ?? file.metadata; const existingProps = useMemo(() => { - if (!file.metadata) { + if (!metadata) { return []; } - return Object.keys(file.metadata) + return Object.keys(metadata) .filter((key) => key.startsWith(customPropsMetadataPrefix)) .map((key) => { const propId = key.slice(customPropsMetadataPrefix.length); return { id: propId, props: custom_props?.find((prop) => prop.id === propId), - value: file.metadata?.[key] ?? "", + value: metadata?.[key] ?? "", } as CustomPropsItem; }); - }, [file.metadata]); + }, [metadata, custom_props]); const existingPropIds = useMemo(() => { return existingProps?.map((prop) => prop.id) ?? []; @@ -62,21 +70,22 @@ const CustomProps = ({ file, setTarget, targetDisplayOptions }: CustomPropsProps }), ) .then(() => { - if (remove) { - const newMetadata = { ...file.metadata }; - props.forEach((prop) => { + const newMetadata = { ...metadata }; + props.forEach((prop) => { + if (remove) { delete newMetadata[customPropsMetadataPrefix + prop.id]; - }); - setTarget({ ...file, metadata: newMetadata }); - } else { - setTarget({ - ...file, - metadata: { - ...file.metadata, - ...Object.assign({}, ...props.map((prop) => ({ [customPropsMetadataPrefix + prop.id]: prop.value }))), - }, - }); - } + } else { + newMetadata[customPropsMetadataPrefix + prop.id] = prop.value; + } + }); + const updated = { ...file, metadata: newMetadata }; + setTarget(updated); + dispatch( + fileUpdated({ + index: FileManagerIndex.main, + value: [{ file: updated, oldPath: file.path, includeMetadata: true }], + }), + ); }) .finally(() => { setLoading(false);