{entries.map((entry) => {
@@ -424,22 +430,7 @@ function GridView({
onSelectFile(entry.file!.id, e.shiftKey, e.metaKey || e.ctrlKey)
}
onDoubleClick={() => onOpenFile(entry.file!)}
- onRemove={() => onRemoveFiles([entry.file!.id])}
- onMove={() => {
- const target = selectedFileIds.has(entry.file!.id)
- ? Array.from(selectedFileIds)
- : [entry.file!.id];
- onPromptMoveFiles(target);
- }}
- onSaveToServer={
- onSaveToServer ? () => onSaveToServer(entry.file!) : undefined
- }
- onVersionHistory={
- onVersionHistory
- ? () => onVersionHistory(entry.file!)
- : undefined
- }
- saveToServerDisabledReason={saveToServerDisabledReason}
+ {...menuHandlersFor(entry.file)}
/>
);
}
@@ -626,7 +617,222 @@ function PolicyBadges({ fileId }: { fileId: string }) {
return
;
}
-interface FileCardProps {
+/** Per-file actions. Shared verbatim by the grid card and the list row, and
+ * kept in step with the file sidebar's kebab so both surfaces offer the same. */
+interface FileActionsMenuProps {
+ file: StirlingFileStub;
+ triggerRef: React.RefObject
;
+ onOpen: () => void;
+ onMove: () => void;
+ onRemove: () => void;
+ onDownload?: () => void;
+ onRename?: () => void;
+ onDuplicate?: () => void;
+ onSaveToServer?: () => void;
+ onVersionHistory?: () => void;
+ saveToServerDisabledReason?: string | null;
+}
+
+function FileActionsMenu({
+ file,
+ triggerRef,
+ onOpen,
+ onMove,
+ onRemove,
+ onDownload,
+ onRename,
+ onDuplicate,
+ onSaveToServer,
+ onVersionHistory,
+ saveToServerDisabledReason,
+}: FileActionsMenuProps) {
+ const { t } = useTranslation();
+ const terminology = useFileActionTerminology();
+ const DownloadIcon = useFileActionIcons().download;
+ const showSaveToServer =
+ Boolean(onSaveToServer) && file.remoteStorageId == null;
+ const showVersionHistory =
+ Boolean(onVersionHistory) && (file.versionNumber ?? 1) > 1;
+ return (
+
+ );
+}
+
+/** Binds one file's kebab handlers, so grid and list wire them identically. */
+function useFileMenuHandlers(
+ props: FileGridProps,
+): (file: StirlingFileStub) => FileMenuHandlers {
+ const {
+ selectedFileIds,
+ onRemoveFiles,
+ onPromptMoveFiles,
+ onSaveToServer,
+ onVersionHistory,
+ onDownloadFile,
+ onRenameFile,
+ onDuplicateFile,
+ saveToServerDisabledReason,
+ } = props;
+ return (file: StirlingFileStub) => ({
+ onRemove: () => onRemoveFiles([file.id]),
+ // A move acts on the whole selection when this file is part of it.
+ onMove: () =>
+ onPromptMoveFiles(
+ selectedFileIds.has(file.id) ? Array.from(selectedFileIds) : [file.id],
+ ),
+ onDownload: onDownloadFile ? () => onDownloadFile(file) : undefined,
+ onRename: onRenameFile ? () => onRenameFile(file) : undefined,
+ onDuplicate: onDuplicateFile ? () => onDuplicateFile(file) : undefined,
+ onSaveToServer: onSaveToServer ? () => onSaveToServer(file) : undefined,
+ onVersionHistory: onVersionHistory
+ ? () => onVersionHistory(file)
+ : undefined,
+ saveToServerDisabledReason,
+ });
+}
+
+/** Per-file kebab handlers, shared by the card and row wrappers. */
+interface FileMenuHandlers {
+ onRemove: () => void;
+ onMove: () => void;
+ onDownload?: () => void;
+ onRename?: () => void;
+ onDuplicate?: () => void;
+ /** Kebab Save to server; only fires when file is local-only. */
+ onSaveToServer?: () => void;
+ /** Open the version-history modal; shown only when file has >1 version. */
+ onVersionHistory?: () => void;
+ /** When set, the kebab Save to server is disabled with this tooltip. */
+ saveToServerDisabledReason?: string | null;
+}
+
+interface FileCardProps extends FileMenuHandlers {
file: StirlingFileStub;
isSelected: boolean;
isInWorkspace: boolean;
@@ -637,14 +843,6 @@ interface FileCardProps {
multiSelectActive: boolean;
onClick: (e: React.MouseEvent) => void;
onDoubleClick: () => void;
- onRemove: () => void;
- onMove: () => void;
- /** Kebab Save to server; only fires when file is local-only. */
- onSaveToServer?: () => void;
- /** Open the version-history modal; shown only when file has >1 version. */
- onVersionHistory?: () => void;
- /** When set, the kebab Save to server is disabled with this tooltip. */
- saveToServerDisabledReason?: string | null;
}
function FileCard({
@@ -656,11 +854,7 @@ function FileCard({
multiSelectActive,
onClick,
onDoubleClick,
- onRemove,
- onMove,
- onSaveToServer,
- onVersionHistory,
- saveToServerDisabledReason,
+ ...menuHandlers
}: FileCardProps) {
const { t } = useTranslation();
const cardRef = useRef(null);
@@ -790,120 +984,40 @@ function FileCard({