|
1 | 1 | import React from 'react'; |
| 2 | +import fs from 'fs'; |
2 | 3 | import { useInterBrainStore } from '../../store/interbrain-store'; |
3 | 4 | import { serviceManager } from '../../services/service-manager'; |
4 | 5 | import { UIService } from '../../services/ui-service'; |
@@ -59,11 +60,113 @@ export default function EditModeOverlay() { |
59 | 60 |
|
60 | 61 | // Get the active service for persistence |
61 | 62 | const dreamNodeService = serviceManager.getActive(); |
62 | | - |
| 63 | + |
63 | 64 | // 1. Handle new DreamTalk media file if provided |
64 | 65 | if (editMode.newDreamTalkFile) { |
65 | | - console.log(`EditModeOverlay: Saving new DreamTalk media: ${editMode.newDreamTalkFile.name}`); |
66 | | - await dreamNodeService.addFilesToNode(editMode.editingNode.id, [editMode.newDreamTalkFile]); |
| 66 | + const file = editMode.newDreamTalkFile; |
| 67 | + |
| 68 | + // Try to read the file - if it fails, it's likely an internal file already in the repo |
| 69 | + let fileIsReadable = false; |
| 70 | + let fileHash: string | null = null; |
| 71 | + |
| 72 | + try { |
| 73 | + const buffer = await file.arrayBuffer(); |
| 74 | + fileIsReadable = true; |
| 75 | + // Calculate hash of the dropped file |
| 76 | + const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); |
| 77 | + fileHash = Array.from(new Uint8Array(hashBuffer)) |
| 78 | + .map(b => b.toString(16).padStart(2, '0')) |
| 79 | + .join(''); |
| 80 | + } catch { |
| 81 | + // File not readable - it's an internal file reference |
| 82 | + fileIsReadable = false; |
| 83 | + } |
| 84 | + |
| 85 | + if (!fileIsReadable) { |
| 86 | + // Internal file - just update the dreamTalk path in metadata, no file copy needed |
| 87 | + console.log(`EditModeOverlay: File not readable, setting existing file as DreamTalk: ${file.name}`); |
| 88 | + |
| 89 | + // Load the file data from disk for immediate display |
| 90 | + const vaultService = serviceManager.getVaultService(); |
| 91 | + const targetPath = `${editMode.editingNode.repoPath}/${file.name}`; |
| 92 | + let dataUrl = ''; |
| 93 | + let fileSize = 0; |
| 94 | + |
| 95 | + if (vaultService) { |
| 96 | + try { |
| 97 | + dataUrl = await vaultService.readFileAsDataURL(targetPath); |
| 98 | + const fullPath = vaultService.getFullPath(targetPath); |
| 99 | + const stats = fs.statSync(fullPath); |
| 100 | + fileSize = stats.size; |
| 101 | + } catch (err) { |
| 102 | + console.warn(`EditModeOverlay: Could not load file data for preview: ${err}`); |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + const updates: Partial<DreamNode> = { |
| 107 | + dreamTalkMedia: [{ |
| 108 | + path: file.name, |
| 109 | + absolutePath: targetPath, |
| 110 | + type: file.type || 'application/octet-stream', |
| 111 | + data: dataUrl, |
| 112 | + size: fileSize |
| 113 | + }] |
| 114 | + }; |
| 115 | + await dreamNodeService.update(editMode.editingNode.id, updates); |
| 116 | + } else if (fileHash) { |
| 117 | + // File is readable - check if it already exists in the repo by comparing hashes |
| 118 | + const vaultService = serviceManager.getVaultService(); |
| 119 | + if (vaultService) { |
| 120 | + const targetPath = `${editMode.editingNode.repoPath}/${file.name}`; |
| 121 | + const existingFileExists = await vaultService.fileExists(targetPath); |
| 122 | + |
| 123 | + if (existingFileExists) { |
| 124 | + // File exists - compare hashes using Node.js fs |
| 125 | + const fullPath = vaultService.getFullPath(targetPath); |
| 126 | + const existingContent = fs.readFileSync(fullPath); |
| 127 | + const existingHashBuffer = await crypto.subtle.digest('SHA-256', existingContent); |
| 128 | + const existingHash = Array.from(new Uint8Array(existingHashBuffer)) |
| 129 | + .map(b => b.toString(16).padStart(2, '0')) |
| 130 | + .join(''); |
| 131 | + |
| 132 | + if (existingHash === fileHash) { |
| 133 | + // Same file - just update the metadata reference, no copy needed |
| 134 | + console.log(`EditModeOverlay: File already exists with same hash, updating reference: ${file.name}`); |
| 135 | + |
| 136 | + // Load the file data from disk for immediate display |
| 137 | + let dataUrl = ''; |
| 138 | + try { |
| 139 | + dataUrl = await vaultService.readFileAsDataURL(targetPath); |
| 140 | + } catch (err) { |
| 141 | + console.warn(`EditModeOverlay: Could not load file data for preview: ${err}`); |
| 142 | + } |
| 143 | + |
| 144 | + const updates: Partial<DreamNode> = { |
| 145 | + dreamTalkMedia: [{ |
| 146 | + path: file.name, |
| 147 | + absolutePath: targetPath, |
| 148 | + type: file.type || 'application/octet-stream', |
| 149 | + data: dataUrl, |
| 150 | + size: file.size |
| 151 | + }] |
| 152 | + }; |
| 153 | + await dreamNodeService.update(editMode.editingNode.id, updates); |
| 154 | + } else { |
| 155 | + // Different file with same name - copy and update |
| 156 | + console.log(`EditModeOverlay: File exists but different hash, replacing: ${file.name}`); |
| 157 | + await dreamNodeService.addFilesToNode(editMode.editingNode.id, [file]); |
| 158 | + } |
| 159 | + } else { |
| 160 | + // File doesn't exist - copy to repo |
| 161 | + console.log(`EditModeOverlay: Saving new DreamTalk media: ${file.name}`); |
| 162 | + await dreamNodeService.addFilesToNode(editMode.editingNode.id, [file]); |
| 163 | + } |
| 164 | + } else { |
| 165 | + // No vault service - fall back to direct save |
| 166 | + console.log(`EditModeOverlay: Saving new DreamTalk media (no vault service): ${file.name}`); |
| 167 | + await dreamNodeService.addFilesToNode(editMode.editingNode.id, [file]); |
| 168 | + } |
| 169 | + } |
67 | 170 | } |
68 | 171 |
|
69 | 172 | // 2. Save metadata changes (let service layer handle if no changes) |
|
0 commit comments