diff --git a/src/App.tsx b/src/App.tsx index b7a2608..af320df 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,6 +37,15 @@ export default function App() { const [isDragging, setIsDragging] = useState(false); const [dropIdx, setDropIdx] = useState(null); + // Dialog shown when dropping fewer than all source pages, asking user + // whether to insert only the dragged selection or all source pages. + const [insertAllDialog, setInsertAllDialog] = useState<{ + insertAt: number; + selectedIds: string[]; + selectedCount: number; + totalCount: number; + } | null>(null); + const [saving, setSaving] = useState(false); const [loading, setLoading] = useState(null); const [mainFilePath, setMainFilePath] = useState(null); @@ -112,8 +121,24 @@ export default function App() { return; } const insertAt = computeInsertionIndex(e.clientY); + const allSrcPages = srcPagesRef.current; const idSet = new Set(drag.ids); - const toInsert = srcPagesRef.current + const selectedCount = allSrcPages.filter(p => idSet.has(p.id)).length; + + // When only a subset of source pages is being dragged, ask the user + // whether to insert the selection or all source pages. + if (selectedCount > 0 && allSrcPages.length > selectedCount) { + setInsertAllDialog({ + insertAt, + selectedIds: drag.ids, + selectedCount, + totalCount: allSrcPages.length, + }); + setDropIdx(null); + return; + } + + const toInsert = allSrcPages .filter(p => idSet.has(p.id)) .map(p => ({ ...p, id: crypto.randomUUID() })); if (toInsert.length) { @@ -396,6 +421,38 @@ export default function App() { setMainSelected(new Set()); }; + // ------------------------------------------------------------------------- + // Insert-all dialog handlers + // ------------------------------------------------------------------------- + + /** Called when the user picks "selected" or "all" from the insert dialog. */ + const handleInsertDialogChoice = (insertAll: boolean) => { + if (!insertAllDialog) return; + const { insertAt, selectedIds } = insertAllDialog; + setInsertAllDialog(null); + + const pages = srcPagesRef.current; + const idSet = new Set(selectedIds); + const toInsert = (insertAll ? pages : pages.filter(p => idSet.has(p.id))) + .map(p => ({ ...p, id: crypto.randomUUID() })); + + if (toInsert.length) { + setMainPages(prev => { + const next = [...prev]; + next.splice(insertAt, 0, ...toInsert); + return next; + }); + } + }; + + /** Appends every source page to the end of the main document. */ + const handleInsertAllAtEnd = () => { + const pages = srcPagesRef.current; + if (!pages.length) return; + const toInsert = pages.map(p => ({ ...p, id: crypto.randomUUID() })); + setMainPages(prev => [...prev, ...toInsert]); + }; + // ------------------------------------------------------------------------- // Zoom helpers // ------------------------------------------------------------------------- @@ -515,6 +572,16 @@ export default function App() { + {srcPages.length > 0 && ( + + )} @@ -557,6 +624,39 @@ export default function App() { + + {/* Insert-all dialog ------------------------------------------------ */} + {insertAllDialog && ( +
setInsertAllDialog(null)}> +
e.stopPropagation()}> +

Insert pages

+

+ Insert into position  + {insertAllDialog.insertAt + 1} of the main document: +

+
+ + + +
+
+
+ )} ); } diff --git a/src/styles.css b/src/styles.css index 2dcde42..608c0b9 100644 --- a/src/styles.css +++ b/src/styles.css @@ -376,4 +376,69 @@ button:disabled { } .page-list::-webkit-scrollbar-thumb:hover { background: #aaa; +} + +/* =================================================================== + Insert-all modal dialog + =================================================================== */ + +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; +} + +.modal { + background: #ffffff; + border-radius: 8px; + padding: 20px 24px; + min-width: 260px; + max-width: 380px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25); +} + +.modal-title { + font-size: 0.95rem; + font-weight: 600; + color: #1a1a1a; + margin-bottom: 8px; +} + +.modal-body { + font-size: 0.82rem; + color: #555; + margin-bottom: 16px; + line-height: 1.5; +} + +.modal-actions { + display: flex; + flex-direction: column; + gap: 8px; +} + +.modal-btn { + width: 100%; + padding: 7px 14px !important; + font-size: 0.82rem !important; + text-align: center; +} + +.modal-btn--primary { + background: #1a73e8 !important; + border-color: #1558b0 !important; + color: #fff !important; +} +.modal-btn--primary:hover:not(:disabled) { + background: #1558b0 !important; + border-color: #0f44a0 !important; +} + +.modal-btn--cancel { + color: #888 !important; + border-color: #ddd !important; } \ No newline at end of file