Full document, spreadsheet, slideshow, and diagram tooling
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}