open as worspace implementation
This commit is contained in:
223
src/App.tsx
223
src/App.tsx
@@ -17,6 +17,7 @@ import type {
|
||||
import {
|
||||
createInitialPageRefs,
|
||||
createPageRefId,
|
||||
createPdfId,
|
||||
createWorkspaceId,
|
||||
defaultWorkspaceNameFromPdfName,
|
||||
normalizeRotation,
|
||||
@@ -37,6 +38,11 @@ import {
|
||||
} from "./pdf/pdfService";
|
||||
import { usePdfThumbnails } from "./pdf/usePdfThumbnails";
|
||||
import { usePdfGeneratedOutputs } from "./hooks/usePdfGeneratedOutputs";
|
||||
import {
|
||||
createSelectionPdfName,
|
||||
createSelectionWorkspaceName,
|
||||
getSelectedPagesInVisualOrder,
|
||||
} from './workspace/workspaceSelection';
|
||||
|
||||
function isEditableKeyboardTarget(target: EventTarget | null): boolean {
|
||||
if (!(target instanceof HTMLElement)) return false;
|
||||
@@ -127,6 +133,21 @@ const App: React.FC = () => {
|
||||
onError: handleThumbnailError,
|
||||
});
|
||||
|
||||
const closeActionDialog = useCallback(() => {
|
||||
setActionDialog(null);
|
||||
}, []);
|
||||
|
||||
const openActionDialog = useCallback(
|
||||
(dialog: {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
actions: ActionDialogAction[];
|
||||
}) => {
|
||||
setActionDialog(dialog);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const refreshWorkspaces = async () => {
|
||||
try {
|
||||
const summaries = await listWorkspaces();
|
||||
@@ -668,7 +689,41 @@ const App: React.FC = () => {
|
||||
setLastSelectedVisualIndex(null);
|
||||
};
|
||||
|
||||
const handleDeleteSelected = () => {
|
||||
const performDeleteSelected = useCallback(
|
||||
(pageIdsToDelete: string[]) => {
|
||||
if (pageIdsToDelete.length === 0) return;
|
||||
|
||||
const before = getCurrentCommandState();
|
||||
const selectedSet = new Set(pageIdsToDelete);
|
||||
|
||||
executeWorkspaceCommand(
|
||||
createWorkspaceCommand({
|
||||
type: 'pages.delete',
|
||||
label:
|
||||
pageIdsToDelete.length === 1
|
||||
? 'Deleted selected page'
|
||||
: `Deleted ${pageIdsToDelete.length} selected pages`,
|
||||
before,
|
||||
after: {
|
||||
pages: pages.filter((page) => !selectedSet.has(page.id)),
|
||||
selectedPageIds: [],
|
||||
lastSelectedVisualIndex: null,
|
||||
},
|
||||
details: {
|
||||
count: pageIdsToDelete.length,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[
|
||||
createWorkspaceCommand,
|
||||
executeWorkspaceCommand,
|
||||
getCurrentCommandState,
|
||||
pages,
|
||||
]
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(() => {
|
||||
if (selectedPageIds.length === 0) return;
|
||||
|
||||
const idsToDelete = [...selectedPageIds];
|
||||
@@ -706,33 +761,12 @@ const App: React.FC = () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const performDeleteSelected = (pageIdsToDelete: string[]) => {
|
||||
if (pageIdsToDelete.length === 0) return;
|
||||
|
||||
const before = getCurrentCommandState();
|
||||
const selectedSet = new Set(pageIdsToDelete);
|
||||
|
||||
executeWorkspaceCommand(
|
||||
createWorkspaceCommand({
|
||||
type: "pages.delete",
|
||||
label:
|
||||
pageIdsToDelete.length === 1
|
||||
? "Deleted selected page"
|
||||
: `Deleted ${pageIdsToDelete.length} selected pages`,
|
||||
before,
|
||||
after: {
|
||||
pages: pages.filter((page) => !selectedSet.has(page.id)),
|
||||
selectedPageIds: [],
|
||||
lastSelectedVisualIndex: null,
|
||||
},
|
||||
details: {
|
||||
count: pageIdsToDelete.length,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
}, [
|
||||
closeActionDialog,
|
||||
openActionDialog,
|
||||
performDeleteSelected,
|
||||
selectedPageIds,
|
||||
]);
|
||||
|
||||
const handleCopyPagesToSlot = (pageIds: string[], insertSlot: number) => {
|
||||
if (!pdf || pageIds.length === 0) return;
|
||||
@@ -780,18 +814,6 @@ const App: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const closeActionDialog = () => {
|
||||
setActionDialog(null);
|
||||
};
|
||||
|
||||
const openActionDialog = (dialog: {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
actions: ActionDialogAction[];
|
||||
}) => {
|
||||
setActionDialog(dialog);
|
||||
};
|
||||
|
||||
const handleOpenPreview = (pageId: string) => {
|
||||
setPreviewPageId(pageId);
|
||||
};
|
||||
@@ -909,8 +931,13 @@ const App: React.FC = () => {
|
||||
setIsBusy(true);
|
||||
|
||||
try {
|
||||
const selectedSet = new Set(selectedPageIds);
|
||||
const selectedPages = pages.filter((page) => selectedSet.has(page.id));
|
||||
const selectedPages = getSelectedPagesInVisualOrder(
|
||||
pages,
|
||||
selectedPageIds
|
||||
);
|
||||
|
||||
if (selectedPages.length === 0) return;
|
||||
|
||||
const blob = await exportPages(pdf, selectedPages);
|
||||
const base = pdf.name.replace(/\.pdf$/i, "");
|
||||
const filename = `${base}_selected.pdf`;
|
||||
@@ -923,6 +950,117 @@ const App: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const performOpenSelectionAsWorkspace = async () => {
|
||||
if (!pdf || selectedPageIds.length === 0) return;
|
||||
|
||||
const selectedPages = getSelectedPagesInVisualOrder(pages, selectedPageIds);
|
||||
if (selectedPages.length === 0) return;
|
||||
|
||||
setError(null);
|
||||
setIsBusy(true);
|
||||
|
||||
try {
|
||||
const selectedPageCount = selectedPages.length;
|
||||
const blob = await exportPages(pdf, selectedPages);
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
const doc = await PDFDocument.load(arrayBuffer);
|
||||
const pdfName = createSelectionPdfName(pdf.name, selectedPageCount);
|
||||
const workspaceName = createSelectionWorkspaceName(
|
||||
pdf.name,
|
||||
selectedPageCount
|
||||
);
|
||||
|
||||
const extractedPdf: PdfFile = {
|
||||
id: createPdfId(),
|
||||
name: pdfName,
|
||||
doc,
|
||||
pageCount: doc.getPageCount(),
|
||||
arrayBuffer,
|
||||
};
|
||||
|
||||
setPdf(extractedPdf);
|
||||
replaceWorkspaceState({
|
||||
pages: createInitialPageRefs(extractedPdf.pageCount),
|
||||
selectedPageIds: [],
|
||||
lastSelectedVisualIndex: null,
|
||||
history: [],
|
||||
redoHistory: [],
|
||||
dirty: true,
|
||||
message: `Created a new workspace from ${selectedPageCount} selected ${
|
||||
selectedPageCount === 1 ? "page" : "pages"
|
||||
}.`,
|
||||
});
|
||||
|
||||
setActiveWorkspaceId(null);
|
||||
setWorkspaceName(workspaceName);
|
||||
setPreviewPageId(null);
|
||||
clearGeneratedOutputs();
|
||||
clearThumbnailCache();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError("Error while opening selection as a new workspace.");
|
||||
} finally {
|
||||
setIsBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenSelectionAsWorkspace = () => {
|
||||
if (!pdf || selectedPageIds.length === 0) return;
|
||||
|
||||
const selectedPages = getSelectedPagesInVisualOrder(pages, selectedPageIds);
|
||||
if (selectedPages.length === 0) return;
|
||||
|
||||
if (!workspaceDirty) {
|
||||
void performOpenSelectionAsWorkspace();
|
||||
return;
|
||||
}
|
||||
|
||||
openActionDialog({
|
||||
title: "Open selection as new workspace?",
|
||||
content: (
|
||||
<>
|
||||
<p style={{ marginTop: 0 }}>
|
||||
This will replace the current in-memory workspace with a new
|
||||
workspace built from {selectedPages.length}{' '}
|
||||
{selectedPages.length === 1 ? "selected page" : "selected pages"}.
|
||||
</p>
|
||||
<p style={{ marginBottom: 0 }}>
|
||||
The current workspace has unsaved changes. Do you want to save it
|
||||
before opening the selection as a new workspace?
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
label: "Cancel",
|
||||
variant: "secondary",
|
||||
onClick: closeActionDialog,
|
||||
},
|
||||
{
|
||||
label: "Open without saving",
|
||||
variant: "danger",
|
||||
onClick: () => {
|
||||
closeActionDialog();
|
||||
void performOpenSelectionAsWorkspace();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Save and open",
|
||||
variant: "primary",
|
||||
autoFocus: true,
|
||||
onClick: async () => {
|
||||
closeActionDialog();
|
||||
const saved = await handleSaveWorkspace();
|
||||
if (saved) {
|
||||
await performOpenSelectionAsWorkspace();
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const handleExportReordered = async () => {
|
||||
if (!pdf || pages.length === 0) return;
|
||||
setError(null);
|
||||
@@ -1102,6 +1240,7 @@ const App: React.FC = () => {
|
||||
selectedCount={selectedPageIds.length}
|
||||
onSplit={handleSplit}
|
||||
onExtractSelected={handleExtractSelected}
|
||||
onOpenSelectionAsWorkspace={handleOpenSelectionAsWorkspace}
|
||||
onExportReordered={handleExportReordered}
|
||||
splitDownloads={splitDownloads}
|
||||
subsetDownload={subsetDownload}
|
||||
|
||||
Reference in New Issue
Block a user