Spaces:
Running
Running
Fix FlutterRunner to load public dart files and add manual save
Browse files- Add polling mechanism to detect new files loaded into sessionStorage
- Fix issue where public dart files wouldn't load when FlutterRunner already open
- Replace auto-save with manual save button (floppy disk icon)
- Disable save for public files, only allow for secure storage files
- Add visual feedback for save status and loading states
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- app/components/FlutterRunner.tsx +77 -24
app/components/FlutterRunner.tsx
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
| 6 |
Download,
|
| 7 |
SidebarSimple,
|
| 8 |
FileCode,
|
| 9 |
-
CaretDown
|
|
|
|
| 10 |
} from '@phosphor-icons/react'
|
| 11 |
import Window from './Window'
|
| 12 |
|
|
@@ -167,32 +168,72 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 167 |
}
|
| 168 |
}, [initialCode])
|
| 169 |
|
| 170 |
-
//
|
| 171 |
useEffect(() => {
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
}
|
| 192 |
-
}
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
-
return () =>
|
| 195 |
-
}, [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
const handleDownload = () => {
|
| 198 |
const blob = new Blob([code], { type: 'text/plain' })
|
|
@@ -248,6 +289,18 @@ export function FlutterRunner({ onClose, onMinimize, onMaximize, onFocus, zIndex
|
|
| 248 |
</div>
|
| 249 |
|
| 250 |
<div className="flex items-center gap-2">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
<button
|
| 252 |
onClick={handleDownload}
|
| 253 |
className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
|
|
|
|
| 6 |
Download,
|
| 7 |
SidebarSimple,
|
| 8 |
FileCode,
|
| 9 |
+
CaretDown,
|
| 10 |
+
FloppyDisk
|
| 11 |
} from '@phosphor-icons/react'
|
| 12 |
import Window from './Window'
|
| 13 |
|
|
|
|
| 168 |
}
|
| 169 |
}, [initialCode])
|
| 170 |
|
| 171 |
+
// Listen for new files being loaded while the component is already open
|
| 172 |
useEffect(() => {
|
| 173 |
+
const checkForNewFile = () => {
|
| 174 |
+
const sessionFileContent = sessionStorage.getItem('flutterFileContent')
|
| 175 |
+
const sessionFileName = sessionStorage.getItem('currentFileName')
|
| 176 |
+
const sessionPasskey = sessionStorage.getItem('currentPasskey')
|
| 177 |
+
|
| 178 |
+
if (sessionFileContent) {
|
| 179 |
+
console.log('Detected new file in session storage:', sessionFileName)
|
| 180 |
+
setCode(sessionFileContent)
|
| 181 |
+
setActiveFileName(sessionFileName || 'main.dart')
|
| 182 |
+
|
| 183 |
+
if (sessionPasskey) {
|
| 184 |
+
setPasskey(sessionPasskey)
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
// Update file structure
|
| 188 |
+
setFiles([{
|
| 189 |
+
id: 'root',
|
| 190 |
+
name: 'lib',
|
| 191 |
+
type: 'folder',
|
| 192 |
+
isOpen: true,
|
| 193 |
+
children: [
|
| 194 |
+
{ id: 'main', name: sessionFileName || 'main.dart', type: 'file', content: sessionFileContent }
|
| 195 |
+
]
|
| 196 |
+
}])
|
| 197 |
+
|
| 198 |
+
// Clear session storage after loading
|
| 199 |
+
sessionStorage.removeItem('flutterFileContent')
|
| 200 |
}
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
// Check every 500ms for new files
|
| 204 |
+
const interval = setInterval(checkForNewFile, 500)
|
| 205 |
|
| 206 |
+
return () => clearInterval(interval)
|
| 207 |
+
}, [])
|
| 208 |
+
|
| 209 |
+
// Manual save function
|
| 210 |
+
const handleSave = async () => {
|
| 211 |
+
if (!passkey || !activeFileName) {
|
| 212 |
+
alert('Cannot save: No passkey set. This file is from public storage.')
|
| 213 |
+
return
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
try {
|
| 217 |
+
setLoading(true)
|
| 218 |
+
await fetch('/api/data', {
|
| 219 |
+
method: 'POST',
|
| 220 |
+
headers: { 'Content-Type': 'application/json' },
|
| 221 |
+
body: JSON.stringify({
|
| 222 |
+
key: passkey,
|
| 223 |
+
passkey: passkey,
|
| 224 |
+
action: 'save_file',
|
| 225 |
+
fileName: activeFileName,
|
| 226 |
+
content: code
|
| 227 |
+
})
|
| 228 |
+
})
|
| 229 |
+
setLastSaved(new Date())
|
| 230 |
+
} catch (error) {
|
| 231 |
+
console.error('Save failed:', error)
|
| 232 |
+
alert('Failed to save file')
|
| 233 |
+
} finally {
|
| 234 |
+
setLoading(false)
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
|
| 238 |
const handleDownload = () => {
|
| 239 |
const blob = new Blob([code], { type: 'text/plain' })
|
|
|
|
| 289 |
</div>
|
| 290 |
|
| 291 |
<div className="flex items-center gap-2">
|
| 292 |
+
<button
|
| 293 |
+
onClick={handleSave}
|
| 294 |
+
disabled={!passkey || loading}
|
| 295 |
+
className={`p-1.5 rounded transition-colors ${
|
| 296 |
+
passkey && !loading
|
| 297 |
+
? 'text-blue-400 hover:text-blue-300 hover:bg-[#3e3e42]'
|
| 298 |
+
: 'text-gray-600 cursor-not-allowed'
|
| 299 |
+
}`}
|
| 300 |
+
title={passkey ? "Save to Secure Storage" : "Cannot save public files"}
|
| 301 |
+
>
|
| 302 |
+
<FloppyDisk size={16} />
|
| 303 |
+
</button>
|
| 304 |
<button
|
| 305 |
onClick={handleDownload}
|
| 306 |
className="p-1.5 text-gray-400 hover:text-white hover:bg-[#3e3e42] rounded transition-colors"
|