Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 146 lines 4.8 kB view raw
1/** 2 * File import via drag-and-drop and file picker on the landing page. 3 * Handles reading files, creating encrypted documents, and navigating 4 * to the editor with pending import data. 5 * 6 * Extracted from landing.ts for decomposition. 7 */ 8 9import type { FolderAssignments } from './landing-types.js'; 10import { generateKey, exportKey } from './lib/crypto.js'; 11import { storeKey } from './lib/key-sync.js'; 12import { createDocument as createLocalDoc } from './lib/local-store.js'; 13import { moveToFolder } from './landing-utils.js'; 14import { 15 getFileType, 16 getImportType, 17 pendingImportKey, 18 buildEditorUrl, 19} from './landing-dragdrop.js'; 20import { showToast } from './landing-toast.js'; 21 22// ── Deps interface ─────────────────────────────────────────── 23 24export interface ImportDeps { 25 getCurrentFolderId: () => string | null; 26 getFolderAssignments: () => FolderAssignments; 27 setFolderAssignments: (a: FolderAssignments) => void; 28} 29 30// ── Import a single file ──────────────────────────────────── 31 32export async function importFile(deps: ImportDeps, file: File): Promise<void> { 33 const docType = getFileType(file.name); 34 const importType = getImportType(file.name); 35 36 if (!docType || !importType) { 37 showToast(`Unsupported file type: .${file.name.split('.').pop()}`, 4000, true); 38 return; 39 } 40 41 try { 42 // Generate encryption key 43 const key = await generateKey(); 44 const keyStr = await exportKey(key); 45 46 // Use filename (without extension) as the document name 47 const fileBaseName = file.name.replace(/\.[^.]+$/, ''); 48 const defaultName = fileBaseName || (docType === 'doc' ? 'Untitled Document' : 'Untitled Spreadsheet'); 49 50 // Create document in IndexedDB 51 const doc = await createLocalDoc( 52 docType as 'doc' | 'sheet' | 'form' | 'slide' | 'diagram' | 'calendar', 53 defaultName, 54 new ArrayBuffer(0), 55 key, 56 ); 57 const id = doc.id; 58 59 await storeKey(id, keyStr); 60 61 // If inside a folder, assign to it 62 const currentFolderId = deps.getCurrentFolderId(); 63 if (currentFolderId) { 64 const updated = moveToFolder(deps.getFolderAssignments(), id, currentFolderId); 65 deps.setFolderAssignments(updated); 66 localStorage.setItem('atmos-folder-assignments', JSON.stringify(updated)); 67 } 68 69 // Read file and store in sessionStorage for the editor to pick up 70 const reader = new FileReader(); 71 reader.onload = () => { 72 const payload = JSON.stringify({ 73 name: file.name, 74 type: importType, 75 data: reader.result, // data URL (base64-encoded) 76 }); 77 sessionStorage.setItem(pendingImportKey(id), payload); 78 79 // Navigate to the editor 80 window.location.href = buildEditorUrl(docType, id, keyStr); 81 }; 82 reader.onerror = () => { 83 showToast('Failed to read file', 4000, true); 84 }; 85 reader.readAsDataURL(file); 86 } catch (err) { 87 showToast('Failed to create document for import', 4000, true); 88 } 89} 90 91// ── Drag-and-drop setup ───────────────────────────────────── 92 93export function setupDragAndDrop(deps: ImportDeps): void { 94 const dropOverlay = document.getElementById('drop-overlay'); 95 let dragCounter = 0; 96 97 function showDropOverlay() { 98 if (dropOverlay) dropOverlay.style.display = ''; 99 } 100 101 function hideDropOverlay() { 102 if (dropOverlay) dropOverlay.style.display = 'none'; 103 } 104 105 document.addEventListener('dragenter', (e) => { 106 e.preventDefault(); 107 dragCounter++; 108 if (dragCounter === 1) showDropOverlay(); 109 }); 110 111 document.addEventListener('dragover', (e) => { 112 e.preventDefault(); 113 if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; 114 }); 115 116 document.addEventListener('dragleave', (e) => { 117 e.preventDefault(); 118 dragCounter--; 119 if (dragCounter <= 0) { 120 dragCounter = 0; 121 hideDropOverlay(); 122 } 123 }); 124 125 document.addEventListener('drop', async (e) => { 126 e.preventDefault(); 127 dragCounter = 0; 128 hideDropOverlay(); 129 130 const file = e.dataTransfer?.files[0]; 131 if (!file) return; 132 importFile(deps, file); 133 }); 134 135 // File import button (mobile-friendly alternative to drag-drop) 136 const fileImportBtn = document.getElementById('file-import-btn'); 137 const fileImportInput = document.getElementById('file-import-input') as HTMLInputElement | null; 138 if (fileImportBtn && fileImportInput) { 139 fileImportBtn.addEventListener('click', () => fileImportInput.click()); 140 fileImportInput.addEventListener('change', () => { 141 const file = fileImportInput.files?.[0]; 142 if (file) importFile(deps, file); 143 fileImportInput.value = ''; 144 }); 145 } 146}