Reubencf commited on
Commit
734142c
·
1 Parent(s): 166d3ff

made some changes

Browse files
app/api/files/route.ts CHANGED
@@ -18,11 +18,14 @@ if (!fs.existsSync(DOCS_DIR)) {
18
 
19
  interface FileItem {
20
  name: string
21
- type: 'file' | 'folder'
22
  size?: number
23
  modified?: string
24
  path: string
25
  extension?: string
 
 
 
26
  }
27
 
28
  function getFileExtension(filename: string): string {
@@ -55,14 +58,45 @@ function getFilesRecursively(dir: string, basePath: string = ''): FileItem[] {
55
  const subFiles = getFilesRecursively(fullPath, relativePath)
56
  files.push(...subFiles)
57
  } else {
58
- files.push({
59
- name: item,
60
- type: 'file',
61
- size: stats.size,
62
- modified: stats.mtime.toISOString(),
63
- path: relativePath,
64
- extension: getFileExtension(item)
65
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
  }
68
  } catch (error) {
 
18
 
19
  interface FileItem {
20
  name: string
21
+ type: 'file' | 'folder' | 'flutter_app'
22
  size?: number
23
  modified?: string
24
  path: string
25
  extension?: string
26
+ dartCode?: string
27
+ dependencies?: string[]
28
+ pubspecYaml?: string
29
  }
30
 
31
  function getFileExtension(filename: string): string {
 
58
  const subFiles = getFilesRecursively(fullPath, relativePath)
59
  files.push(...subFiles)
60
  } else {
61
+ // Check if this is a Flutter app file
62
+ if (item.endsWith('.flutter.json')) {
63
+ try {
64
+ const fileContent = fs.readFileSync(fullPath, 'utf-8')
65
+ const flutterAppData = JSON.parse(fileContent)
66
+
67
+ files.push({
68
+ name: flutterAppData.name || item.replace('.flutter.json', ''),
69
+ type: 'flutter_app',
70
+ size: stats.size,
71
+ modified: stats.mtime.toISOString(),
72
+ path: relativePath,
73
+ extension: 'flutter',
74
+ dartCode: flutterAppData.dartCode,
75
+ dependencies: flutterAppData.dependencies,
76
+ pubspecYaml: flutterAppData.pubspecYaml
77
+ })
78
+ } catch (error) {
79
+ // If parsing fails, treat it as a regular file
80
+ console.error('Error parsing Flutter app file:', error)
81
+ files.push({
82
+ name: item,
83
+ type: 'file',
84
+ size: stats.size,
85
+ modified: stats.mtime.toISOString(),
86
+ path: relativePath,
87
+ extension: getFileExtension(item)
88
+ })
89
+ }
90
+ } else {
91
+ files.push({
92
+ name: item,
93
+ type: 'file',
94
+ size: stats.size,
95
+ modified: stats.mtime.toISOString(),
96
+ path: relativePath,
97
+ extension: getFileExtension(item)
98
+ })
99
+ }
100
  }
101
  }
102
  } catch (error) {
app/components/Calendar.tsx CHANGED
@@ -1,9 +1,10 @@
1
  'use client'
2
 
3
  import React, { useState } from 'react'
4
- import { X, Minus, Square, CaretLeft, CaretRight } from '@phosphor-icons/react'
5
  import { motion } from 'framer-motion'
6
  import { useKV } from '../hooks/useKV'
 
7
 
8
  interface CalendarProps {
9
  onClose: () => void
@@ -32,42 +33,9 @@ const defaultHolidays: Event[] = [
32
 
33
  export function Calendar({ onClose }: CalendarProps) {
34
  const [currentDate, setCurrentDate] = useState(new Date())
35
- const [windowPos, setWindowPos] = useState({ x: 100, y: 100 })
36
- const [isDragging, setIsDragging] = useState(false)
37
- const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
38
  const [events, setEvents] = useKV<Event[]>('calendar-events', defaultHolidays)
39
  const [selectedDay, setSelectedDay] = useState<number | null>(null)
40
 
41
- const handleMouseDown = (e: React.MouseEvent) => {
42
- if ((e.target as HTMLElement).closest('.window-controls')) return
43
- setIsDragging(true)
44
- setDragStart({ x: e.clientX - windowPos.x, y: e.clientY - windowPos.y })
45
- }
46
-
47
- const handleMouseMove = (e: MouseEvent) => {
48
- if (isDragging) {
49
- setWindowPos({
50
- x: e.clientX - dragStart.x,
51
- y: e.clientY - dragStart.y,
52
- })
53
- }
54
- }
55
-
56
- const handleMouseUp = () => {
57
- setIsDragging(false)
58
- }
59
-
60
- React.useEffect(() => {
61
- if (isDragging) {
62
- window.addEventListener('mousemove', handleMouseMove)
63
- window.addEventListener('mouseup', handleMouseUp)
64
- return () => {
65
- window.removeEventListener('mousemove', handleMouseMove)
66
- window.removeEventListener('mouseup', handleMouseUp)
67
- }
68
- }
69
- }, [isDragging, dragStart])
70
-
71
  const monthNames = [
72
  'January', 'February', 'March', 'April', 'May', 'June',
73
  'July', 'August', 'September', 'October', 'November', 'December'
@@ -133,37 +101,18 @@ export function Calendar({ onClose }: CalendarProps) {
133
  const selectedDayEvents = selectedDay ? getEventsForDay(selectedDay) : []
134
 
135
  return (
136
- <motion.div
137
- style={{ left: windowPos.x, top: windowPos.y }}
138
- className="fixed w-[420px] bg-white rounded-lg shadow-2xl overflow-hidden flex flex-col z-30 select-none"
 
 
 
 
 
 
 
139
  >
140
- <div
141
- onMouseDown={handleMouseDown}
142
- className="h-11 bg-gradient-to-b from-[#f6f5f4] to-[#edebe9] border-b border-[#d0d0d0] flex items-center justify-between px-3 cursor-move"
143
- >
144
- <div className="flex items-center gap-2 flex-1">
145
- <div className="flex items-center gap-1 window-controls">
146
- <button
147
- onClick={(e) => {
148
- e.stopPropagation()
149
- onClose()
150
- }}
151
- className="w-5 h-5 rounded-full bg-[#E95420] hover:bg-[#d14818] flex items-center justify-center group transition-colors"
152
- >
153
- <X size={12} weight="bold" className="text-white opacity-0 group-hover:opacity-100 transition-opacity" />
154
- </button>
155
- <button className="w-5 h-5 rounded-full bg-[#ddd] hover:bg-[#ccc] flex items-center justify-center group transition-colors">
156
- <Minus size={12} weight="bold" className="text-[#666] opacity-0 group-hover:opacity-100 transition-opacity" />
157
- </button>
158
- <button className="w-5 h-5 rounded-full bg-[#ddd] hover:bg-[#ccc] flex items-center justify-center group transition-colors">
159
- <Square size={10} weight="bold" className="text-[#666] opacity-0 group-hover:opacity-100 transition-opacity" />
160
- </button>
161
- </div>
162
- <span className="text-sm font-medium text-[#2c2c2c] ml-2">Calendar</span>
163
- </div>
164
- </div>
165
-
166
- <div className="p-6">
167
  <div className="flex items-center justify-between mb-4">
168
  <button
169
  onClick={previousMonth}
@@ -212,11 +161,10 @@ export function Calendar({ onClose }: CalendarProps) {
212
  {getEventsForDay(day).slice(0, 3).map((event, i) => (
213
  <div
214
  key={i}
215
- className={`w-1 h-1 rounded-full ${
216
- event.type === 'holiday'
217
  ? isToday(day) ? 'bg-white' : 'bg-purple-500'
218
  : isToday(day) ? 'bg-white' : 'bg-blue-500'
219
- }`}
220
  />
221
  ))}
222
  </div>
@@ -232,20 +180,19 @@ export function Calendar({ onClose }: CalendarProps) {
232
  </div>
233
 
234
  {selectedDayEvents.length > 0 && (
235
- <div className="mt-4 pt-4 border-t border-[#e0e0e0]">
236
  <div className="text-xs text-[#666] mb-2">
237
  {monthNames[currentDate.getMonth()]} {selectedDay}, {currentDate.getFullYear()}
238
  </div>
239
- <div className="space-y-2 max-h-32 overflow-y-auto">
240
  {selectedDayEvents.map((event) => (
241
  <div
242
  key={event.id}
243
  className="flex items-start gap-2 p-2 bg-[#f9f9f9] rounded"
244
  >
245
  <div
246
- className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${
247
- event.type === 'holiday' ? 'bg-purple-500' : 'bg-blue-500'
248
- }`}
249
  />
250
  <div className="flex-1">
251
  <div className="text-sm font-medium text-[#2c2c2c]">
@@ -262,14 +209,14 @@ export function Calendar({ onClose }: CalendarProps) {
262
  )}
263
 
264
  {!selectedDay && (
265
- <div className="mt-4 pt-4 border-t border-[#e0e0e0]">
266
  <div className="text-xs text-[#666] mb-2">Today</div>
267
  <div className="text-sm font-medium text-[#2c2c2c]">
268
- {today.toLocaleDateString('en-US', {
269
- weekday: 'long',
270
- year: 'numeric',
271
- month: 'long',
272
- day: 'numeric'
273
  })}
274
  </div>
275
  {getEventsForDay(today.getDate()).length > 0 && (
@@ -280,9 +227,8 @@ export function Calendar({ onClose }: CalendarProps) {
280
  className="flex items-start gap-2 p-2 bg-[#fff3e6] rounded"
281
  >
282
  <div
283
- className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${
284
- event.type === 'holiday' ? 'bg-purple-500' : 'bg-blue-500'
285
- }`}
286
  />
287
  <div className="flex-1">
288
  <div className="text-sm font-medium text-[#2c2c2c]">
@@ -296,6 +242,6 @@ export function Calendar({ onClose }: CalendarProps) {
296
  </div>
297
  )}
298
  </div>
299
- </motion.div>
300
  )
301
  }
 
1
  'use client'
2
 
3
  import React, { useState } from 'react'
4
+ import { CaretLeft, CaretRight } from '@phosphor-icons/react'
5
  import { motion } from 'framer-motion'
6
  import { useKV } from '../hooks/useKV'
7
+ import Window from './Window'
8
 
9
  interface CalendarProps {
10
  onClose: () => void
 
33
 
34
  export function Calendar({ onClose }: CalendarProps) {
35
  const [currentDate, setCurrentDate] = useState(new Date())
 
 
 
36
  const [events, setEvents] = useKV<Event[]>('calendar-events', defaultHolidays)
37
  const [selectedDay, setSelectedDay] = useState<number | null>(null)
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  const monthNames = [
40
  'January', 'February', 'March', 'April', 'May', 'June',
41
  'July', 'August', 'September', 'October', 'November', 'December'
 
101
  const selectedDayEvents = selectedDay ? getEventsForDay(selectedDay) : []
102
 
103
  return (
104
+ <Window
105
+ id="calendar"
106
+ title="Calendar"
107
+ isOpen={true}
108
+ onClose={onClose}
109
+ width={420}
110
+ height={500}
111
+ x={100}
112
+ y={100}
113
+ className="calendar-window"
114
  >
115
+ <div className="p-6 h-full flex flex-col">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  <div className="flex items-center justify-between mb-4">
117
  <button
118
  onClick={previousMonth}
 
161
  {getEventsForDay(day).slice(0, 3).map((event, i) => (
162
  <div
163
  key={i}
164
+ className={`w-1 h-1 rounded-full ${event.type === 'holiday'
 
165
  ? isToday(day) ? 'bg-white' : 'bg-purple-500'
166
  : isToday(day) ? 'bg-white' : 'bg-blue-500'
167
+ }`}
168
  />
169
  ))}
170
  </div>
 
180
  </div>
181
 
182
  {selectedDayEvents.length > 0 && (
183
+ <div className="mt-4 pt-4 border-t border-[#e0e0e0] flex-1 overflow-y-auto">
184
  <div className="text-xs text-[#666] mb-2">
185
  {monthNames[currentDate.getMonth()]} {selectedDay}, {currentDate.getFullYear()}
186
  </div>
187
+ <div className="space-y-2">
188
  {selectedDayEvents.map((event) => (
189
  <div
190
  key={event.id}
191
  className="flex items-start gap-2 p-2 bg-[#f9f9f9] rounded"
192
  >
193
  <div
194
+ className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${event.type === 'holiday' ? 'bg-purple-500' : 'bg-blue-500'
195
+ }`}
 
196
  />
197
  <div className="flex-1">
198
  <div className="text-sm font-medium text-[#2c2c2c]">
 
209
  )}
210
 
211
  {!selectedDay && (
212
+ <div className="mt-4 pt-4 border-t border-[#e0e0e0] flex-1 overflow-y-auto">
213
  <div className="text-xs text-[#666] mb-2">Today</div>
214
  <div className="text-sm font-medium text-[#2c2c2c]">
215
+ {today.toLocaleDateString('en-US', {
216
+ weekday: 'long',
217
+ year: 'numeric',
218
+ month: 'long',
219
+ day: 'numeric'
220
  })}
221
  </div>
222
  {getEventsForDay(today.getDate()).length > 0 && (
 
227
  className="flex items-start gap-2 p-2 bg-[#fff3e6] rounded"
228
  >
229
  <div
230
+ className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${event.type === 'holiday' ? 'bg-purple-500' : 'bg-blue-500'
231
+ }`}
 
232
  />
233
  <div className="flex-1">
234
  <div className="text-sm font-medium text-[#2c2c2c]">
 
242
  </div>
243
  )}
244
  </div>
245
+ </Window>
246
  )
247
  }
app/components/Desktop.tsx CHANGED
@@ -18,6 +18,7 @@ import { SpotlightSearch } from './SpotlightSearch'
18
  import { ContextMenu } from './ContextMenu'
19
  import { AboutModal } from './AboutModal'
20
  import { SessionManagerWindow } from './SessionManagerWindow'
 
21
  import { motion, AnimatePresence } from 'framer-motion'
22
 
23
  export function Desktop() {
@@ -39,9 +40,11 @@ export function Desktop() {
39
  const [contextMenuOpen, setContextMenuOpen] = useState(false)
40
  const [contextMenuPos, setContextMenuPos] = useState({ x: 0, y: 0 })
41
  const [backgroundSelectorOpen, setBackgroundSelectorOpen] = useState(false)
42
- const [currentBackground, setCurrentBackground] = useState('/background.png')
43
  const [aboutModalOpen, setAboutModalOpen] = useState(false)
44
  const [sessionManagerOpen, setSessionManagerOpen] = useState(false)
 
 
45
 
46
  const openFileManager = (path: string) => {
47
  setCurrentPath(path)
@@ -100,8 +103,18 @@ export function Desktop() {
100
  setSessionManagerOpen(false)
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
103
  const handleOpenApp = (appId: string) => {
104
- switch(appId) {
105
  case 'files':
106
  openFileManager('')
107
  break
@@ -127,7 +140,7 @@ export function Desktop() {
127
  }
128
 
129
  const handleContextMenuAction = (action: string) => {
130
- switch(action) {
131
  case 'change-wallpaper':
132
  setBackgroundSelectorOpen(true)
133
  break
@@ -253,6 +266,7 @@ export function Desktop() {
253
  const getBackgroundStyle = () => {
254
  if (currentBackground.startsWith('gradient-')) {
255
  const gradients: Record<string, string> = {
 
256
  'gradient-purple': 'linear-gradient(135deg, #77216F 0%, #5E2750 50%, #2C001E 100%)',
257
  'gradient-blue': 'linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e8ba3 100%)',
258
  'gradient-green': 'linear-gradient(135deg, #134e5e 0%, #71b280 50%, #a8e063 100%)',
@@ -260,7 +274,7 @@ export function Desktop() {
260
  'gradient-dark': 'linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 50%, #2d2d2d 100%)',
261
  'gradient-cosmic': 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)'
262
  }
263
- return { background: gradients[currentBackground] || gradients['gradient-purple'] }
264
  } else {
265
  return {
266
  backgroundImage: `url('${currentBackground}')`,
@@ -274,7 +288,7 @@ export function Desktop() {
274
  return (
275
  <div className="relative h-screen w-screen overflow-hidden flex" onContextMenu={handleDesktopRightClick}>
276
  <div
277
- className="absolute inset-0"
278
  style={getBackgroundStyle()}
279
  >
280
  {/* Overlay for better text visibility */}
@@ -286,10 +300,10 @@ export function Desktop() {
286
  }}
287
  />
288
  )}
289
- <svg className="absolute inset-0 w-full h-full opacity-10" xmlns="http://www.w3.org/2000/svg">
290
  <defs>
291
  <pattern id="ubuntu-pattern" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
292
- <circle cx="50" cy="50" r="2" fill="white" opacity="0.3"/>
293
  </pattern>
294
  </defs>
295
  <rect width="100%" height="100%" fill="url(#ubuntu-pattern)" />
@@ -300,140 +314,170 @@ export function Desktop() {
300
 
301
  <Dock onOpenFileManager={openFileManager} onOpenCalendar={openCalendar} onOpenClock={openClock} onOpenBrowser={openBrowser} onOpenGeminiChat={openGeminiChat} />
302
 
303
- <div className="flex-1">
304
  {/* Desktop Icons - Positioned in a grid layout */}
305
- <div className="absolute top-20 left-8">
306
- <DraggableDesktopIcon
307
- id="files"
308
- label="Files"
309
- iconType="files"
310
- initialPosition={{ x: 20, y: 20 }}
311
- onClick={() => {}}
312
- onDoubleClick={() => openFileManager('')}
313
- />
314
- <DraggableDesktopIcon
315
- id="browser"
316
- label="Browser"
317
- iconType="browser"
318
- initialPosition={{ x: 120, y: 20 }}
319
- onClick={() => {}}
320
- onDoubleClick={openBrowser}
321
- />
322
- <DraggableDesktopIcon
323
- id="gemini"
324
- label="Gemini"
325
- iconType="gemini"
326
- initialPosition={{ x: 220, y: 20 }}
327
- onClick={() => {}}
328
- onDoubleClick={openGeminiChat}
329
- />
330
- <DraggableDesktopIcon
331
- id="clock"
332
- label="Clock"
333
- iconType="clock"
334
- initialPosition={{ x: 20, y: 120 }}
335
- onClick={() => {}}
336
- onDoubleClick={openClock}
337
- />
338
- <DraggableDesktopIcon
339
- id="calendar"
340
- label="Calendar"
341
- iconType="calendar"
342
- initialPosition={{ x: 120, y: 120 }}
343
- onClick={() => {}}
344
- onDoubleClick={openCalendar}
345
- />
346
- <DraggableDesktopIcon
347
- id="terminal"
348
- label="Terminal"
349
- iconType="terminal"
350
- initialPosition={{ x: 20, y: 220 }}
351
- onClick={() => {}}
352
- onDoubleClick={openTerminal}
353
- />
354
- <DraggableDesktopIcon
355
- id="harddrive"
356
- label="System"
357
- iconType="harddrive"
358
- initialPosition={{ x: 120, y: 220 }}
359
- onClick={() => {}}
360
- onDoubleClick={() => openFileManager('')}
361
- />
362
- <DraggableDesktopIcon
363
- id="sessions"
364
- label="Sessions"
365
- iconType="key"
366
- initialPosition={{ x: 220, y: 120 }}
367
- onClick={() => {}}
368
- onDoubleClick={openSessionManager}
369
- />
370
- </div>
371
-
372
- {fileManagerOpen && (
373
- <motion.div
374
- initial={{ scale: 0.95, opacity: 0 }}
375
- animate={{ scale: 1, opacity: 1 }}
376
- transition={{ duration: 0.15 }}
377
- >
378
- <FileManager
379
- currentPath={currentPath}
380
- onNavigate={setCurrentPath}
381
- onClose={closeFileManager}
382
  />
383
- </motion.div>
384
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
- {calendarOpen && (
387
- <motion.div
388
- initial={{ scale: 0.95, opacity: 0 }}
389
- animate={{ scale: 1, opacity: 1 }}
390
- transition={{ duration: 0.15 }}
391
- >
392
- <Calendar onClose={closeCalendar} />
393
- </motion.div>
394
- )}
 
 
 
 
 
 
 
 
 
395
 
396
- {clockOpen && (
397
- <motion.div
398
- initial={{ scale: 0.95, opacity: 0 }}
399
- animate={{ scale: 1, opacity: 1 }}
400
- transition={{ duration: 0.15 }}
401
- >
402
- <Clock onClose={closeClock} />
403
- </motion.div>
404
- )}
 
 
 
405
 
406
- {browserOpen && (
407
- <motion.div
408
- initial={{ scale: 0.95, opacity: 0 }}
409
- animate={{ scale: 1, opacity: 1 }}
410
- transition={{ duration: 0.15 }}
411
- >
412
- <WebBrowserApp onClose={closeBrowser} />
413
- </motion.div>
414
- )}
 
 
 
415
 
416
- {geminiChatOpen && (
417
- <GeminiChat onClose={closeGeminiChat} />
418
- )}
 
 
 
 
 
 
 
 
 
419
 
420
- {terminalOpen && (
421
- <motion.div
422
- initial={{ scale: 0.95, opacity: 0 }}
423
- animate={{ scale: 1, opacity: 1 }}
424
- transition={{ duration: 0.15 }}
425
- >
426
- <Terminal onClose={closeTerminal} />
427
- </motion.div>
428
- )}
 
 
 
429
 
430
- <AnimatePresence>
431
  {sessionManagerOpen && sessionInitialized && (
432
  <motion.div
 
433
  initial={{ scale: 0.9, opacity: 0 }}
434
  animate={{ scale: 1, opacity: 1 }}
435
  exit={{ scale: 0.9, opacity: 0 }}
436
- transition={{ duration: 0.2, ease: "easeInOut" }}
 
437
  >
438
  <SessionManagerWindow
439
  onClose={closeSessionManager}
@@ -442,8 +486,37 @@ export function Desktop() {
442
  />
443
  </motion.div>
444
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  </AnimatePresence>
446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  </div>
448
 
449
  {/* Spotlight Search */}
 
18
  import { ContextMenu } from './ContextMenu'
19
  import { AboutModal } from './AboutModal'
20
  import { SessionManagerWindow } from './SessionManagerWindow'
21
+ import { FlutterRunner } from './FlutterRunner'
22
  import { motion, AnimatePresence } from 'framer-motion'
23
 
24
  export function Desktop() {
 
40
  const [contextMenuOpen, setContextMenuOpen] = useState(false)
41
  const [contextMenuPos, setContextMenuPos] = useState({ x: 0, y: 0 })
42
  const [backgroundSelectorOpen, setBackgroundSelectorOpen] = useState(false)
43
+ const [currentBackground, setCurrentBackground] = useState('gradient-sonoma')
44
  const [aboutModalOpen, setAboutModalOpen] = useState(false)
45
  const [sessionManagerOpen, setSessionManagerOpen] = useState(false)
46
+ const [flutterRunnerOpen, setFlutterRunnerOpen] = useState(false)
47
+ const [activeFlutterApp, setActiveFlutterApp] = useState<any>(null)
48
 
49
  const openFileManager = (path: string) => {
50
  setCurrentPath(path)
 
103
  setSessionManagerOpen(false)
104
  }
105
 
106
+ const openFlutterRunner = (appFile: any) => {
107
+ setActiveFlutterApp(appFile)
108
+ setFlutterRunnerOpen(true)
109
+ }
110
+
111
+ const closeFlutterRunner = () => {
112
+ setFlutterRunnerOpen(false)
113
+ setActiveFlutterApp(null)
114
+ }
115
+
116
  const handleOpenApp = (appId: string) => {
117
+ switch (appId) {
118
  case 'files':
119
  openFileManager('')
120
  break
 
140
  }
141
 
142
  const handleContextMenuAction = (action: string) => {
143
+ switch (action) {
144
  case 'change-wallpaper':
145
  setBackgroundSelectorOpen(true)
146
  break
 
266
  const getBackgroundStyle = () => {
267
  if (currentBackground.startsWith('gradient-')) {
268
  const gradients: Record<string, string> = {
269
+ 'gradient-sonoma': 'linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%)',
270
  'gradient-purple': 'linear-gradient(135deg, #77216F 0%, #5E2750 50%, #2C001E 100%)',
271
  'gradient-blue': 'linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e8ba3 100%)',
272
  'gradient-green': 'linear-gradient(135deg, #134e5e 0%, #71b280 50%, #a8e063 100%)',
 
274
  'gradient-dark': 'linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 50%, #2d2d2d 100%)',
275
  'gradient-cosmic': 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)'
276
  }
277
+ return { background: gradients[currentBackground] || gradients['gradient-sonoma'] }
278
  } else {
279
  return {
280
  backgroundImage: `url('${currentBackground}')`,
 
288
  return (
289
  <div className="relative h-screen w-screen overflow-hidden flex" onContextMenu={handleDesktopRightClick}>
290
  <div
291
+ className="absolute inset-0 transition-all duration-500 ease-in-out"
292
  style={getBackgroundStyle()}
293
  >
294
  {/* Overlay for better text visibility */}
 
300
  }}
301
  />
302
  )}
303
+ <svg className="absolute inset-0 w-full h-full opacity-10 pointer-events-none" xmlns="http://www.w3.org/2000/svg">
304
  <defs>
305
  <pattern id="ubuntu-pattern" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
306
+ <circle cx="50" cy="50" r="2" fill="white" opacity="0.3" />
307
  </pattern>
308
  </defs>
309
  <rect width="100%" height="100%" fill="url(#ubuntu-pattern)" />
 
314
 
315
  <Dock onOpenFileManager={openFileManager} onOpenCalendar={openCalendar} onOpenClock={openClock} onOpenBrowser={openBrowser} onOpenGeminiChat={openGeminiChat} />
316
 
317
+ <div className="flex-1 z-10 relative">
318
  {/* Desktop Icons - Positioned in a grid layout */}
319
+ <div className="absolute top-16 left-6 grid grid-flow-col grid-rows-[repeat(auto-fill,100px)] gap-x-4 gap-y-2 h-[calc(100vh-140px)] w-full pointer-events-none">
320
+ <div className="pointer-events-auto w-24 h-24">
321
+ <DraggableDesktopIcon
322
+ id="files"
323
+ label="Files"
324
+ iconType="files"
325
+ initialPosition={{ x: 0, y: 0 }}
326
+ onClick={() => { }}
327
+ onDoubleClick={() => openFileManager('')}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  />
329
+ </div>
330
+ <div className="pointer-events-auto w-24 h-24">
331
+ <DraggableDesktopIcon
332
+ id="browser"
333
+ label="Browser"
334
+ iconType="browser"
335
+ initialPosition={{ x: 0, y: 0 }}
336
+ onClick={() => { }}
337
+ onDoubleClick={openBrowser}
338
+ />
339
+ </div>
340
+ <div className="pointer-events-auto w-24 h-24">
341
+ <DraggableDesktopIcon
342
+ id="gemini"
343
+ label="Gemini"
344
+ iconType="gemini"
345
+ initialPosition={{ x: 0, y: 0 }}
346
+ onClick={() => { }}
347
+ onDoubleClick={openGeminiChat}
348
+ />
349
+ </div>
350
+ <div className="pointer-events-auto w-24 h-24">
351
+ <DraggableDesktopIcon
352
+ id="clock"
353
+ label="Clock"
354
+ iconType="clock"
355
+ initialPosition={{ x: 0, y: 0 }}
356
+ onClick={() => { }}
357
+ onDoubleClick={openClock}
358
+ />
359
+ </div>
360
+ <div className="pointer-events-auto w-24 h-24">
361
+ <DraggableDesktopIcon
362
+ id="calendar"
363
+ label="Calendar"
364
+ iconType="calendar"
365
+ initialPosition={{ x: 0, y: 0 }}
366
+ onClick={() => { }}
367
+ onDoubleClick={openCalendar}
368
+ />
369
+ </div>
370
+ <div className="pointer-events-auto w-24 h-24">
371
+ <DraggableDesktopIcon
372
+ id="terminal"
373
+ label="Terminal"
374
+ iconType="terminal"
375
+ initialPosition={{ x: 0, y: 0 }}
376
+ onClick={() => { }}
377
+ onDoubleClick={openTerminal}
378
+ />
379
+ </div>
380
+ <div className="pointer-events-auto w-24 h-24">
381
+ <DraggableDesktopIcon
382
+ id="harddrive"
383
+ label="System"
384
+ iconType="harddrive"
385
+ initialPosition={{ x: 0, y: 0 }}
386
+ onClick={() => { }}
387
+ onDoubleClick={() => openFileManager('')}
388
+ />
389
+ </div>
390
+ <div className="pointer-events-auto w-24 h-24">
391
+ <DraggableDesktopIcon
392
+ id="sessions"
393
+ label="Sessions"
394
+ iconType="key"
395
+ initialPosition={{ x: 0, y: 0 }}
396
+ onClick={() => { }}
397
+ onDoubleClick={openSessionManager}
398
+ />
399
+ </div>
400
+ </div>
401
 
402
+ <AnimatePresence>
403
+ {fileManagerOpen && (
404
+ <motion.div
405
+ key="file-manager"
406
+ initial={{ scale: 0.95, opacity: 0 }}
407
+ animate={{ scale: 1, opacity: 1 }}
408
+ exit={{ scale: 0.95, opacity: 0 }}
409
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
410
+ className="absolute inset-0 pointer-events-none"
411
+ >
412
+ <FileManager
413
+ currentPath={currentPath}
414
+ onNavigate={setCurrentPath}
415
+ onClose={closeFileManager}
416
+ onOpenFlutterApp={openFlutterRunner}
417
+ />
418
+ </motion.div>
419
+ )}
420
 
421
+ {calendarOpen && (
422
+ <motion.div
423
+ key="calendar"
424
+ initial={{ scale: 0.95, opacity: 0 }}
425
+ animate={{ scale: 1, opacity: 1 }}
426
+ exit={{ scale: 0.95, opacity: 0 }}
427
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
428
+ className="absolute inset-0 pointer-events-none"
429
+ >
430
+ <Calendar onClose={closeCalendar} />
431
+ </motion.div>
432
+ )}
433
 
434
+ {clockOpen && (
435
+ <motion.div
436
+ key="clock"
437
+ initial={{ scale: 0.95, opacity: 0 }}
438
+ animate={{ scale: 1, opacity: 1 }}
439
+ exit={{ scale: 0.95, opacity: 0 }}
440
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
441
+ className="absolute inset-0 pointer-events-none"
442
+ >
443
+ <Clock onClose={closeClock} />
444
+ </motion.div>
445
+ )}
446
 
447
+ {browserOpen && (
448
+ <motion.div
449
+ key="browser"
450
+ initial={{ scale: 0.95, opacity: 0 }}
451
+ animate={{ scale: 1, opacity: 1 }}
452
+ exit={{ scale: 0.95, opacity: 0 }}
453
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
454
+ className="absolute inset-0 pointer-events-none"
455
+ >
456
+ <WebBrowserApp onClose={closeBrowser} />
457
+ </motion.div>
458
+ )}
459
 
460
+ {terminalOpen && (
461
+ <motion.div
462
+ key="terminal"
463
+ initial={{ scale: 0.95, opacity: 0 }}
464
+ animate={{ scale: 1, opacity: 1 }}
465
+ exit={{ scale: 0.95, opacity: 0 }}
466
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
467
+ className="absolute inset-0 pointer-events-none"
468
+ >
469
+ <Terminal onClose={closeTerminal} />
470
+ </motion.div>
471
+ )}
472
 
 
473
  {sessionManagerOpen && sessionInitialized && (
474
  <motion.div
475
+ key="session-manager"
476
  initial={{ scale: 0.9, opacity: 0 }}
477
  animate={{ scale: 1, opacity: 1 }}
478
  exit={{ scale: 0.9, opacity: 0 }}
479
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
480
+ className="absolute inset-0 pointer-events-none"
481
  >
482
  <SessionManagerWindow
483
  onClose={closeSessionManager}
 
486
  />
487
  </motion.div>
488
  )}
489
+
490
+ {flutterRunnerOpen && activeFlutterApp && (
491
+ <motion.div
492
+ key="flutter-runner"
493
+ initial={{ scale: 0.95, opacity: 0 }}
494
+ animate={{ scale: 1, opacity: 1 }}
495
+ exit={{ scale: 0.95, opacity: 0 }}
496
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
497
+ className="absolute inset-0 pointer-events-none"
498
+ >
499
+ <FlutterRunner
500
+ file={activeFlutterApp}
501
+ onClose={closeFlutterRunner}
502
+ />
503
+ </motion.div>
504
+ )}
505
  </AnimatePresence>
506
 
507
+ {geminiChatOpen && (
508
+ <motion.div
509
+ key="gemini"
510
+ initial={{ scale: 0.95, opacity: 0 }}
511
+ animate={{ scale: 1, opacity: 1 }}
512
+ exit={{ scale: 0.95, opacity: 0 }}
513
+ transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
514
+ className="absolute inset-0 pointer-events-none"
515
+ >
516
+ <GeminiChat onClose={closeGeminiChat} />
517
+ </motion.div>
518
+ )}
519
+
520
  </div>
521
 
522
  {/* Spotlight Search */}
app/components/Dock.tsx CHANGED
@@ -11,6 +11,7 @@ import {
11
  FolderOpen,
12
  Compass
13
  } from '@phosphor-icons/react'
 
14
 
15
  interface DockProps {
16
  onOpenFileManager: (path: string) => void
@@ -26,14 +27,18 @@ interface DockItemProps {
26
  onClick: () => void
27
  isActive?: boolean
28
  className?: string
 
29
  }
30
 
31
- const DockItem: React.FC<DockItemProps> = ({ icon, label, onClick, isActive = false, className = '' }) => {
32
  const [isHovered, setIsHovered] = useState(false)
 
33
 
34
  return (
35
- <div className="dock-item group">
36
- <button
 
 
37
  onMouseEnter={() => setIsHovered(true)}
38
  onMouseLeave={() => setIsHovered(false)}
39
  onClick={onClick}
@@ -41,7 +46,7 @@ const DockItem: React.FC<DockItemProps> = ({ icon, label, onClick, isActive = fa
41
  title={label}
42
  >
43
  {icon}
44
- </button>
45
  <div
46
  className={`dock-dot ${isActive ? 'opacity-100' : ''}`}
47
  id={`dot-${label.toLowerCase().replace(/\s+/g, '-')}`}
@@ -57,6 +62,8 @@ export function Dock({
57
  onOpenBrowser,
58
  onOpenGeminiChat
59
  }: DockProps) {
 
 
60
  const dockItems = [
61
  {
62
  icon: (
@@ -117,10 +124,14 @@ export function Dock({
117
 
118
  return (
119
  <div className="dock-container">
120
- <div className="dock-glass px-3 pb-2 pt-3 rounded-2xl flex items-end gap-2 shadow-2xl border border-white/20 transition-all duration-300">
 
 
 
 
121
  {dockItems.map((item, index) => (
122
  <React.Fragment key={item.label}>
123
- <DockItem {...item} />
124
  {index === dockItems.length - 1 && (
125
  <>
126
  <div className="w-px h-10 bg-gray-400/30 mx-1" />
@@ -131,8 +142,9 @@ export function Dock({
131
  </div>
132
  }
133
  label="Trash"
134
- onClick={() => {}}
135
  className=""
 
136
  />
137
  </>
138
  )}
 
11
  FolderOpen,
12
  Compass
13
  } from '@phosphor-icons/react'
14
+ import { motion } from 'framer-motion'
15
 
16
  interface DockProps {
17
  onOpenFileManager: (path: string) => void
 
27
  onClick: () => void
28
  isActive?: boolean
29
  className?: string
30
+ mouseX: React.MutableRefObject<number | null>
31
  }
32
 
33
+ const DockItem: React.FC<DockItemProps> = ({ icon, label, onClick, isActive = false, className = '', mouseX }) => {
34
  const [isHovered, setIsHovered] = useState(false)
35
+ const ref = React.useRef<HTMLDivElement>(null)
36
 
37
  return (
38
+ <div className="dock-item group" ref={ref}>
39
+ <motion.button
40
+ whileHover={{ scale: 1.2, y: -10 }}
41
+ whileTap={{ scale: 0.95 }}
42
  onMouseEnter={() => setIsHovered(true)}
43
  onMouseLeave={() => setIsHovered(false)}
44
  onClick={onClick}
 
46
  title={label}
47
  >
48
  {icon}
49
+ </motion.button>
50
  <div
51
  className={`dock-dot ${isActive ? 'opacity-100' : ''}`}
52
  id={`dot-${label.toLowerCase().replace(/\s+/g, '-')}`}
 
62
  onOpenBrowser,
63
  onOpenGeminiChat
64
  }: DockProps) {
65
+ const mouseX = React.useRef<number | null>(null)
66
+
67
  const dockItems = [
68
  {
69
  icon: (
 
124
 
125
  return (
126
  <div className="dock-container">
127
+ <div
128
+ className="dock-glass px-3 pb-2 pt-3 rounded-2xl flex items-end gap-3 shadow-2xl border border-white/20 transition-all duration-300"
129
+ onMouseMove={(e) => mouseX.current = e.pageX}
130
+ onMouseLeave={() => mouseX.current = null}
131
+ >
132
  {dockItems.map((item, index) => (
133
  <React.Fragment key={item.label}>
134
+ <DockItem {...item} mouseX={mouseX} />
135
  {index === dockItems.length - 1 && (
136
  <>
137
  <div className="w-px h-10 bg-gray-400/30 mx-1" />
 
142
  </div>
143
  }
144
  label="Trash"
145
+ onClick={() => { }}
146
  className=""
147
+ mouseX={mouseX}
148
  />
149
  </>
150
  )}
app/components/DraggableDesktopIcon.tsx CHANGED
@@ -107,6 +107,12 @@ export function DraggableDesktopIcon({
107
  <Key size={32} weight="bold" className="text-white" />
108
  </div>
109
  )
 
 
 
 
 
 
110
  default:
111
  return (
112
  <div className="bg-gray-400 w-full h-full rounded-xl flex items-center justify-center">
 
107
  <Key size={32} weight="bold" className="text-white" />
108
  </div>
109
  )
110
+ case 'flutter':
111
+ return (
112
+ <div className="bg-gradient-to-br from-cyan-400 to-blue-500 w-full h-full rounded-xl flex items-center justify-center shadow-inner border border-cyan-300/30">
113
+ <Code size={32} weight="bold" className="text-white" />
114
+ </div>
115
+ )
116
  default:
117
  return (
118
  <div className="bg-gray-400 w-full h-full rounded-xl flex items-center justify-center">
app/components/FileManager.tsx CHANGED
@@ -21,36 +21,39 @@ import {
21
  Plus,
22
  Eye,
23
  Users,
24
- Globe
 
25
  } from '@phosphor-icons/react'
26
  import { motion } from 'framer-motion'
27
  import { FilePreview } from './FilePreview'
 
28
 
29
  interface FileManagerProps {
30
  currentPath: string
31
  onNavigate: (path: string) => void
32
  onClose: () => void
 
33
  }
34
 
35
  interface FileItem {
36
  name: string
37
- type: 'folder' | 'file'
38
  size?: number
39
  modified?: string
40
  path: string
41
  extension?: string
 
 
 
42
  }
43
 
44
- export function FileManager({ currentPath, onNavigate, onClose }: FileManagerProps) {
45
  const [files, setFiles] = useState<FileItem[]>([])
46
  const [loading, setLoading] = useState(true)
47
  const [searchQuery, setSearchQuery] = useState('')
48
  const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set())
49
  const [uploadModalOpen, setUploadModalOpen] = useState(false)
50
  const [previewFile, setPreviewFile] = useState<FileItem | null>(null)
51
- const [windowPos, setWindowPos] = useState({ x: 60, y: 60 })
52
- const [isDragging, setIsDragging] = useState(false)
53
- const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
54
  const [isPublicFolder, setIsPublicFolder] = useState(false)
55
 
56
  // Load files when path changes
@@ -199,6 +202,10 @@ export function FileManager({ currentPath, onNavigate, onClose }: FileManagerPro
199
  return <FolderIcon size={48} weight="fill" className="text-orange-400" />
200
  }
201
 
 
 
 
 
202
  const ext = file.extension?.toLowerCase()
203
  switch (ext) {
204
  case 'pdf':
@@ -241,214 +248,164 @@ export function FileManager({ currentPath, onNavigate, onClose }: FileManagerPro
241
  return !relativePath.includes('/')
242
  })
243
 
244
- const handleMouseDown = (e: React.MouseEvent) => {
245
- if ((e.target as HTMLElement).closest('.window-controls')) return
246
- setIsDragging(true)
247
- setDragStart({ x: e.clientX - windowPos.x, y: e.clientY - windowPos.y })
248
- }
249
-
250
- const handleMouseMove = (e: MouseEvent) => {
251
- if (isDragging) {
252
- setWindowPos({
253
- x: e.clientX - dragStart.x,
254
- y: e.clientY - dragStart.y,
255
- })
256
- }
257
- }
258
-
259
- const handleMouseUp = () => {
260
- setIsDragging(false)
261
- }
262
-
263
- useEffect(() => {
264
- if (isDragging) {
265
- window.addEventListener('mousemove', handleMouseMove)
266
- window.addEventListener('mouseup', handleMouseUp)
267
- return () => {
268
- window.removeEventListener('mousemove', handleMouseMove)
269
- window.removeEventListener('mouseup', handleMouseUp)
270
- }
271
- }
272
- }, [isDragging, dragStart])
273
-
274
  return (
275
  <>
276
- <motion.div
277
- style={{ left: windowPos.x, top: windowPos.y }}
278
- className="fixed w-[800px] h-[600px] bg-white rounded-lg shadow-2xl overflow-hidden flex flex-col z-30 select-none"
 
 
 
 
 
 
 
279
  >
280
- {/* Title Bar */}
281
- <div
282
- onMouseDown={handleMouseDown}
283
- className="h-11 bg-gradient-to-b from-[#f6f5f4] to-[#edebe9] border-b border-[#d0d0d0] flex items-center justify-between px-3 cursor-move"
284
- >
285
- <div className="flex items-center gap-2 flex-1">
286
- <div className="flex items-center gap-1 window-controls">
287
  <button
288
- onClick={onClose}
289
- className="w-5 h-5 rounded-full bg-[#E95420] hover:bg-[#d14818] flex items-center justify-center group"
 
 
 
 
290
  >
291
- <X size={12} weight="bold" className="text-white opacity-0 group-hover:opacity-100" />
292
- </button>
293
- <button className="w-5 h-5 rounded-full bg-[#ddd] hover:bg-[#ccc] flex items-center justify-center group">
294
- <Minus size={12} weight="bold" className="text-[#666] opacity-0 group-hover:opacity-100" />
295
  </button>
296
- <button className="w-5 h-5 rounded-full bg-[#ddd] hover:bg-[#ccc] flex items-center justify-center group">
297
- <Square size={10} weight="bold" className="text-[#666] opacity-0 group-hover:opacity-100" />
 
 
 
298
  </button>
299
  </div>
300
- <span className="text-sm font-medium text-[#2c2c2c] ml-2 flex items-center gap-2">
301
- File Manager - {isPublicFolder ? (
302
- <>
303
- <Globe size={16} className="text-purple-500" />
304
- Public Folder {currentPath.replace('public', '').replace('/', '')}
305
- </>
306
- ) : (currentPath || 'Documents')}
307
- </span>
308
- </div>
309
- </div>
310
-
311
- {/* Toolbar */}
312
- <div className="h-12 bg-[#fafafa] border-b border-[#e0e0e0] flex items-center px-3 gap-2">
313
- <div className="flex items-center gap-1">
314
- <button
315
- onClick={() => {
316
- const parent = currentPath.split('/').slice(0, -1).join('/')
317
- onNavigate(parent)
318
- }}
319
- disabled={!currentPath}
320
- className="p-1.5 hover:bg-[#e8e8e8] rounded disabled:opacity-40 disabled:hover:bg-transparent"
321
- >
322
- <CaretLeft size={16} weight="bold" />
323
- </button>
324
- <button
325
- onClick={() => onNavigate('')}
326
- className="p-1.5 hover:bg-[#e8e8e8] rounded"
327
- >
328
- <House size={16} weight="regular" />
329
- </button>
330
- </div>
331
 
332
- <div className="flex items-center gap-1 px-2 py-1.5 bg-white border border-[#ddd] rounded flex-1">
333
- <House size={14} weight="regular" className="text-[#666]" />
334
- <span className="text-xs text-[#666]">/</span>
335
- <span className="text-xs text-[#2c2c2c]">data/documents/{currentPath}</span>
336
- </div>
337
 
338
- <div className="flex items-center gap-1">
339
- <button
340
- onClick={handleCreateFolder}
341
- className="p-1.5 hover:bg-[#e8e8e8] rounded"
342
- title="New Folder"
343
- >
344
- <Plus size={16} weight="bold" />
345
- </button>
346
- <button
347
- onClick={() => setUploadModalOpen(true)}
348
- className="p-1.5 hover:bg-[#e8e8e8] rounded"
349
- title="Upload File"
350
- >
351
- <Upload size={16} weight="regular" />
352
- </button>
353
- <div className="flex items-center gap-1 px-2 py-1 bg-white border border-[#ddd] rounded">
354
- <MagnifyingGlass size={14} weight="regular" className="text-[#666]" />
355
- <input
356
- type="text"
357
- placeholder="Search..."
358
- value={searchQuery}
359
- onChange={(e) => setSearchQuery(e.target.value)}
360
- className="text-xs outline-none w-32"
361
- />
 
362
  </div>
363
  </div>
364
- </div>
365
 
366
- {/* File List */}
367
- <div className="flex-1 overflow-auto p-4">
368
- {loading ? (
369
- <div className="flex items-center justify-center h-full text-[#999] text-sm">
370
- Loading files...
371
- </div>
372
- ) : currentLevelFiles.length === 0 ? (
373
- <div className="flex items-center justify-center h-full text-[#999] text-sm">
374
- {searchQuery ? 'No files found' : 'Folder is empty'}
375
- </div>
376
- ) : (
377
- <div className="grid grid-cols-6 gap-4">
378
- {currentLevelFiles.map((file) => (
379
- <div
380
- key={file.path}
381
- className="group relative"
382
- >
383
- <button
384
- onClick={() => {
385
- if (file.type === 'folder') {
386
- onNavigate(file.path)
387
- } else {
388
- handlePreview(file)
389
- }
390
- }}
391
- className="flex flex-col items-center gap-2 p-2 hover:bg-[#f0f0f0] rounded w-full"
392
  >
393
- <div className="w-16 h-16 flex items-center justify-center">
394
- {getFileIcon(file)}
395
- </div>
396
- <span className="text-xs text-[#2c2c2c] text-center break-all w-full line-clamp-2">
397
- {file.name}
398
- </span>
399
- {file.size && (
400
- <span className="text-xs text-[#666]">
401
- {formatFileSize(file.size)}
 
 
 
 
 
 
 
 
402
  </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  )}
404
- </button>
405
-
406
- {/* File Actions */}
407
- {file.type === 'file' && (
408
- <div className="absolute top-1 right-1 hidden group-hover:flex gap-1">
409
- <button
410
- onClick={(e) => {
411
- e.stopPropagation()
412
- handlePreview(file)
413
- }}
414
- className="p-1 bg-white rounded shadow hover:bg-[#f0f0f0]"
415
- title="Preview"
416
- >
417
- <Eye size={14} />
418
- </button>
419
- <button
420
- onClick={(e) => {
421
- e.stopPropagation()
422
- handleDownload(file)
423
- }}
424
- className="p-1 bg-white rounded shadow hover:bg-[#f0f0f0]"
425
- title="Download"
426
- >
427
- <Download size={14} />
428
- </button>
429
- <button
430
- onClick={(e) => {
431
- e.stopPropagation()
432
- handleDelete(file)
433
- }}
434
- className="p-1 bg-white rounded shadow hover:bg-red-100"
435
- title="Delete"
436
- >
437
- <Trash size={14} className="text-red-600" />
438
- </button>
439
- </div>
440
- )}
441
- </div>
442
- ))}
443
- </div>
444
- )}
445
- </div>
446
 
447
- {/* Status Bar */}
448
- <div className="h-6 bg-[#f6f5f4] border-t border-[#d0d0d0] flex items-center px-3 text-xs text-[#666]">
449
- {currentLevelFiles.length} items • {files.filter(f => f.type === 'folder').length} folders • {files.filter(f => f.type === 'file').length} files
 
450
  </div>
451
- </motion.div>
452
 
453
  {/* Upload Modal */}
454
  {uploadModalOpen && (
 
21
  Plus,
22
  Eye,
23
  Users,
24
+ Globe,
25
+ Code
26
  } from '@phosphor-icons/react'
27
  import { motion } from 'framer-motion'
28
  import { FilePreview } from './FilePreview'
29
+ import Window from './Window'
30
 
31
  interface FileManagerProps {
32
  currentPath: string
33
  onNavigate: (path: string) => void
34
  onClose: () => void
35
+ onOpenFlutterApp?: (appFile: any) => void
36
  }
37
 
38
  interface FileItem {
39
  name: string
40
+ type: 'folder' | 'file' | 'flutter_app'
41
  size?: number
42
  modified?: string
43
  path: string
44
  extension?: string
45
+ dartCode?: string
46
+ dependencies?: string[]
47
+ pubspecYaml?: string
48
  }
49
 
50
+ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp }: FileManagerProps) {
51
  const [files, setFiles] = useState<FileItem[]>([])
52
  const [loading, setLoading] = useState(true)
53
  const [searchQuery, setSearchQuery] = useState('')
54
  const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set())
55
  const [uploadModalOpen, setUploadModalOpen] = useState(false)
56
  const [previewFile, setPreviewFile] = useState<FileItem | null>(null)
 
 
 
57
  const [isPublicFolder, setIsPublicFolder] = useState(false)
58
 
59
  // Load files when path changes
 
202
  return <FolderIcon size={48} weight="fill" className="text-orange-400" />
203
  }
204
 
205
+ if (file.type === 'flutter_app') {
206
+ return <Code size={48} weight="fill" className="text-cyan-500" />
207
+ }
208
+
209
  const ext = file.extension?.toLowerCase()
210
  switch (ext) {
211
  case 'pdf':
 
248
  return !relativePath.includes('/')
249
  })
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  return (
252
  <>
253
+ <Window
254
+ id="files"
255
+ title={isPublicFolder ? `Public Folder ${currentPath.replace('public', '').replace('/', '')}` : (currentPath || 'Documents')}
256
+ isOpen={true}
257
+ onClose={onClose}
258
+ width={800}
259
+ height={600}
260
+ x={60}
261
+ y={60}
262
+ className="file-manager-window"
263
  >
264
+ <div className="flex flex-col h-full">
265
+ {/* Toolbar */}
266
+ <div className="h-12 bg-[#fafafa] border-b border-[#e0e0e0] flex items-center px-3 gap-2">
267
+ <div className="flex items-center gap-1">
 
 
 
268
  <button
269
+ onClick={() => {
270
+ const parent = currentPath.split('/').slice(0, -1).join('/')
271
+ onNavigate(parent)
272
+ }}
273
+ disabled={!currentPath}
274
+ className="p-1.5 hover:bg-[#e8e8e8] rounded disabled:opacity-40 disabled:hover:bg-transparent"
275
  >
276
+ <CaretLeft size={16} weight="bold" />
 
 
 
277
  </button>
278
+ <button
279
+ onClick={() => onNavigate('')}
280
+ className="p-1.5 hover:bg-[#e8e8e8] rounded"
281
+ >
282
+ <House size={16} weight="regular" />
283
  </button>
284
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
+ <div className="flex items-center gap-1 px-2 py-1.5 bg-white border border-[#ddd] rounded flex-1">
287
+ <House size={14} weight="regular" className="text-[#666]" />
288
+ <span className="text-xs text-[#666]">/</span>
289
+ <span className="text-xs text-[#2c2c2c]">data/documents/{currentPath}</span>
290
+ </div>
291
 
292
+ <div className="flex items-center gap-1">
293
+ <button
294
+ onClick={handleCreateFolder}
295
+ className="p-1.5 hover:bg-[#e8e8e8] rounded"
296
+ title="New Folder"
297
+ >
298
+ <Plus size={16} weight="bold" />
299
+ </button>
300
+ <button
301
+ onClick={() => setUploadModalOpen(true)}
302
+ className="p-1.5 hover:bg-[#e8e8e8] rounded"
303
+ title="Upload File"
304
+ >
305
+ <Upload size={16} weight="regular" />
306
+ </button>
307
+ <div className="flex items-center gap-1 px-2 py-1 bg-white border border-[#ddd] rounded">
308
+ <MagnifyingGlass size={14} weight="regular" className="text-[#666]" />
309
+ <input
310
+ type="text"
311
+ placeholder="Search..."
312
+ value={searchQuery}
313
+ onChange={(e) => setSearchQuery(e.target.value)}
314
+ className="text-xs outline-none w-32"
315
+ />
316
+ </div>
317
  </div>
318
  </div>
 
319
 
320
+ {/* File List */}
321
+ <div className="flex-1 overflow-auto p-4 bg-white">
322
+ {loading ? (
323
+ <div className="flex items-center justify-center h-full text-[#999] text-sm">
324
+ Loading files...
325
+ </div>
326
+ ) : currentLevelFiles.length === 0 ? (
327
+ <div className="flex items-center justify-center h-full text-[#999] text-sm">
328
+ {searchQuery ? 'No files found' : 'Folder is empty'}
329
+ </div>
330
+ ) : (
331
+ <div className="grid grid-cols-6 gap-4">
332
+ {currentLevelFiles.map((file) => (
333
+ <div
334
+ key={file.path}
335
+ className="group relative"
 
 
 
 
 
 
 
 
 
 
336
  >
337
+ <button
338
+ onClick={() => {
339
+ if (file.type === 'folder') {
340
+ onNavigate(file.path)
341
+ } else if (file.type === 'flutter_app' && onOpenFlutterApp) {
342
+ onOpenFlutterApp(file)
343
+ } else {
344
+ handlePreview(file)
345
+ }
346
+ }}
347
+ className="flex flex-col items-center gap-2 p-2 hover:bg-[#f0f0f0] rounded w-full"
348
+ >
349
+ <div className="w-16 h-16 flex items-center justify-center">
350
+ {getFileIcon(file)}
351
+ </div>
352
+ <span className="text-xs text-[#2c2c2c] text-center break-all w-full line-clamp-2">
353
+ {file.name}
354
  </span>
355
+ {file.size && (
356
+ <span className="text-xs text-[#666]">
357
+ {formatFileSize(file.size)}
358
+ </span>
359
+ )}
360
+ </button>
361
+
362
+ {/* File Actions */}
363
+ {file.type === 'file' && (
364
+ <div className="absolute top-1 right-1 hidden group-hover:flex gap-1">
365
+ <button
366
+ onClick={(e) => {
367
+ e.stopPropagation()
368
+ handlePreview(file)
369
+ }}
370
+ className="p-1 bg-white rounded shadow hover:bg-[#f0f0f0]"
371
+ title="Preview"
372
+ >
373
+ <Eye size={14} />
374
+ </button>
375
+ <button
376
+ onClick={(e) => {
377
+ e.stopPropagation()
378
+ handleDownload(file)
379
+ }}
380
+ className="p-1 bg-white rounded shadow hover:bg-[#f0f0f0]"
381
+ title="Download"
382
+ >
383
+ <Download size={14} />
384
+ </button>
385
+ <button
386
+ onClick={(e) => {
387
+ e.stopPropagation()
388
+ handleDelete(file)
389
+ }}
390
+ className="p-1 bg-white rounded shadow hover:bg-red-100"
391
+ title="Delete"
392
+ >
393
+ <Trash size={14} className="text-red-600" />
394
+ </button>
395
+ </div>
396
  )}
397
+ </div>
398
+ ))}
399
+ </div>
400
+ )}
401
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
+ {/* Status Bar */}
404
+ <div className="h-6 bg-[#f6f5f4] border-t border-[#d0d0d0] flex items-center px-3 text-xs text-[#666]">
405
+ {currentLevelFiles.length} items • {files.filter(f => f.type === 'folder').length} folders • {files.filter(f => f.type === 'file').length} files
406
+ </div>
407
  </div>
408
+ </Window>
409
 
410
  {/* Upload Modal */}
411
  {uploadModalOpen && (
app/components/FlutterRunner.tsx ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import React, { useState } from 'react'
4
+ import Window from './Window'
5
+ import { Copy, CaretRight, CaretLeft, Code as CodeIcon } from '@phosphor-icons/react'
6
+
7
+ interface FlutterRunnerProps {
8
+ file: {
9
+ name: string
10
+ dartCode: string
11
+ dependencies?: string[]
12
+ pubspecYaml?: string
13
+ }
14
+ onClose: () => void
15
+ }
16
+
17
+ export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
18
+ const [sidebarOpen, setSidebarOpen] = useState(true)
19
+ const [copySuccess, setCopySuccess] = useState(false)
20
+
21
+ const handleCopyCode = async () => {
22
+ try {
23
+ await navigator.clipboard.writeText(file.dartCode)
24
+ setCopySuccess(true)
25
+ setTimeout(() => setCopySuccess(false), 2000)
26
+ } catch (err) {
27
+ console.error('Failed to copy code:', err)
28
+ }
29
+ }
30
+
31
+ const handleCopyPubspec = async () => {
32
+ if (file.pubspecYaml) {
33
+ try {
34
+ await navigator.clipboard.writeText(file.pubspecYaml)
35
+ setCopySuccess(true)
36
+ setTimeout(() => setCopySuccess(false), 2000)
37
+ } catch (err) {
38
+ console.error('Failed to copy pubspec:', err)
39
+ }
40
+ }
41
+ }
42
+
43
+ return (
44
+ <Window
45
+ id="flutter-runner"
46
+ title={`Flutter App: ${file.name}`}
47
+ isOpen={true}
48
+ onClose={onClose}
49
+ width={1200}
50
+ height={700}
51
+ x={100}
52
+ y={80}
53
+ className="flutter-runner-window"
54
+ >
55
+ <div className="flex h-full bg-white relative">
56
+ {/* Main Zapp iframe area */}
57
+ <div className="flex-1 relative">
58
+ <iframe
59
+ src="https://zapp.run"
60
+ className="w-full h-full border-0"
61
+ allow="accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write"
62
+ sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts allow-downloads"
63
+ title="Zapp Flutter IDE"
64
+ />
65
+
66
+ {/* Instruction overlay */}
67
+ <div className="absolute top-4 left-4 right-4 bg-blue-500/90 backdrop-blur-sm text-white px-4 py-3 rounded-lg shadow-lg border border-blue-400/50">
68
+ <div className="flex items-center gap-2">
69
+ <CodeIcon size={20} weight="bold" />
70
+ <p className="text-sm font-medium">
71
+ Use the sidebar to copy your Flutter code and dependencies, then paste into Zapp's editor.
72
+ </p>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ {/* Collapsible Sidebar */}
78
+ {sidebarOpen && (
79
+ <div className="w-[350px] border-l border-gray-200 bg-gradient-to-b from-gray-50 to-white flex flex-col shadow-lg">
80
+ {/* Sidebar Header */}
81
+ <div className="h-12 bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-between px-4 text-white">
82
+ <div className="flex items-center gap-2">
83
+ <CodeIcon size={20} weight="bold" />
84
+ <span className="font-semibold text-sm">Source Code</span>
85
+ </div>
86
+ <button
87
+ onClick={() => setSidebarOpen(false)}
88
+ className="hover:bg-white/20 p-1 rounded transition-colors"
89
+ title="Close sidebar"
90
+ >
91
+ <CaretRight size={20} weight="bold" />
92
+ </button>
93
+ </div>
94
+
95
+ {/* Sidebar Content */}
96
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
97
+ {/* Dart Code Section */}
98
+ <div className="space-y-2">
99
+ <div className="flex items-center justify-between">
100
+ <h3 className="text-sm font-semibold text-gray-700">Main Dart Code</h3>
101
+ <button
102
+ onClick={handleCopyCode}
103
+ className="flex items-center gap-1 px-3 py-1.5 bg-cyan-500 hover:bg-cyan-600 text-white text-xs rounded-md transition-colors shadow-sm"
104
+ >
105
+ <Copy size={14} weight="bold" />
106
+ {copySuccess ? 'Copied!' : 'Copy'}
107
+ </button>
108
+ </div>
109
+ <div className="bg-gray-900 rounded-lg p-3 max-h-64 overflow-y-auto">
110
+ <pre className="text-xs text-green-400 font-mono whitespace-pre-wrap break-words">
111
+ {file.dartCode}
112
+ </pre>
113
+ </div>
114
+ <p className="text-xs text-gray-600 italic">
115
+ 💡 Paste this code into <code className="bg-gray-100 px-1 py-0.5 rounded">lib/main.dart</code> in Zapp
116
+ </p>
117
+ </div>
118
+
119
+ {/* Dependencies Section */}
120
+ {file.dependencies && file.dependencies.length > 0 && (
121
+ <div className="space-y-2">
122
+ <h3 className="text-sm font-semibold text-gray-700">Dependencies</h3>
123
+ <div className="bg-blue-50 rounded-lg p-3 border border-blue-100">
124
+ <ul className="space-y-1">
125
+ {file.dependencies.map((dep, index) => (
126
+ <li key={index} className="text-xs font-mono text-blue-900">
127
+ • {dep}
128
+ </li>
129
+ ))}
130
+ </ul>
131
+ </div>
132
+ <p className="text-xs text-gray-600 italic">
133
+ 💡 Add these to <code className="bg-gray-100 px-1 py-0.5 rounded">pubspec.yaml</code> in Zapp
134
+ </p>
135
+ </div>
136
+ )}
137
+
138
+ {/* Pubspec.yaml Section */}
139
+ {file.pubspecYaml && (
140
+ <div className="space-y-2">
141
+ <div className="flex items-center justify-between">
142
+ <h3 className="text-sm font-semibold text-gray-700">pubspec.yaml</h3>
143
+ <button
144
+ onClick={handleCopyPubspec}
145
+ className="flex items-center gap-1 px-3 py-1.5 bg-purple-500 hover:bg-purple-600 text-white text-xs rounded-md transition-colors shadow-sm"
146
+ >
147
+ <Copy size={14} weight="bold" />
148
+ Copy
149
+ </button>
150
+ </div>
151
+ <div className="bg-gray-900 rounded-lg p-3 max-h-48 overflow-y-auto">
152
+ <pre className="text-xs text-yellow-400 font-mono whitespace-pre-wrap break-words">
153
+ {file.pubspecYaml}
154
+ </pre>
155
+ </div>
156
+ <p className="text-xs text-gray-600 italic">
157
+ 💡 Replace the entire <code className="bg-gray-100 px-1 py-0.5 rounded">pubspec.yaml</code> file
158
+ </p>
159
+ </div>
160
+ )}
161
+
162
+ {/* Instructions */}
163
+ <div className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg p-4 border border-green-200">
164
+ <h3 className="text-sm font-semibold text-green-800 mb-2">Quick Start</h3>
165
+ <ol className="text-xs text-green-900 space-y-1.5 list-decimal list-inside">
166
+ <li>Copy the Dart code above</li>
167
+ <li>In Zapp, open <code className="bg-white px-1 py-0.5 rounded text-xs">lib/main.dart</code></li>
168
+ <li>Paste and replace all content</li>
169
+ <li>Add dependencies to <code className="bg-white px-1 py-0.5 rounded text-xs">pubspec.yaml</code></li>
170
+ <li>Click the run button in Zapp!</li>
171
+ </ol>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ )}
176
+
177
+ {/* Sidebar Toggle Button (when closed) */}
178
+ {!sidebarOpen && (
179
+ <button
180
+ onClick={() => setSidebarOpen(true)}
181
+ className="absolute right-0 top-1/2 -translate-y-1/2 bg-gradient-to-r from-cyan-500 to-blue-500 text-white p-2 rounded-l-lg shadow-lg hover:from-cyan-600 hover:to-blue-600 transition-all"
182
+ title="Open sidebar"
183
+ >
184
+ <CaretLeft size={20} weight="bold" />
185
+ </button>
186
+ )}
187
+ </div>
188
+ </Window>
189
+ )
190
+ }
191
+
app/components/SessionManagerWindow.tsx CHANGED
@@ -1,7 +1,6 @@
1
  'use client'
2
 
3
- import React, { useState, useEffect, useRef } from 'react'
4
- import Draggable from 'react-draggable'
5
  import { motion, AnimatePresence } from 'framer-motion'
6
  import {
7
  Key,
@@ -14,17 +13,13 @@ import {
14
  Copy,
15
  Check,
16
  X,
17
- Trash,
18
  FileText,
19
  Table as FileSpreadsheet,
20
  Presentation as FilePresentation,
21
  FilePdf,
22
- Plus,
23
  ArrowsClockwise as RefreshCw,
24
- Minus,
25
- Square,
26
- XCircle
27
  } from '@phosphor-icons/react'
 
28
 
29
  interface Session {
30
  id: string
@@ -47,7 +42,6 @@ interface SessionManagerWindowProps {
47
  }
48
 
49
  export function SessionManagerWindow({ onClose, sessionId, sessionKey: initialSessionKey }: SessionManagerWindowProps) {
50
- const [isMaximized, setIsMaximized] = useState(false)
51
  const [sessionKey] = useState(initialSessionKey)
52
  const [files, setFiles] = useState<FileItem[]>([])
53
  const [publicFiles, setPublicFiles] = useState<FileItem[]>([])
@@ -59,7 +53,6 @@ export function SessionManagerWindow({ onClose, sessionId, sessionKey: initialSe
59
  const [activeTab, setActiveTab] = useState<'session' | 'public'>('session')
60
  const [uploadFile, setUploadFile] = useState<File | null>(null)
61
  const [isPublicUpload, setIsPublicUpload] = useState(false)
62
- const nodeRef = useRef(null)
63
 
64
  // Session is automatically created - no manual creation needed!
65
 
@@ -305,61 +298,22 @@ export function SessionManagerWindow({ onClose, sessionId, sessionKey: initialSe
305
  }, [error])
306
 
307
  return (
308
- <Draggable
309
- handle=".window-header"
310
- defaultPosition={{ x: 100, y: 100 }}
311
- disabled={isMaximized}
312
- nodeRef={nodeRef}
 
 
 
 
 
 
 
313
  >
314
- <div
315
- ref={nodeRef}
316
- className={`bg-gray-900/95 backdrop-blur-xl rounded-xl shadow-2xl border border-purple-500/30 overflow-hidden ${
317
- isMaximized ? 'fixed inset-4' : 'w-[900px]'
318
- }`}
319
- style={{ zIndex: 1000 }}
320
- >
321
- {/* Window Header */}
322
- <div className="window-header bg-gradient-to-r from-purple-600/20 to-purple-800/20 p-3 flex items-center justify-between border-b border-purple-500/20 cursor-move">
323
- <div className="flex items-center gap-2">
324
- <Key size={20} className="text-purple-400" />
325
- <span className="text-white font-semibold">Session Manager</span>
326
- </div>
327
- <div className="flex items-center gap-1">
328
- <button
329
- onClick={(e) => {
330
- e.stopPropagation();
331
- setIsMaximized(false);
332
- }}
333
- className="w-7 h-7 flex items-center justify-center hover:bg-yellow-500/20 rounded-md transition-colors group"
334
- title="Minimize"
335
- >
336
- <Minus size={14} className="text-yellow-400 group-hover:text-yellow-300" />
337
- </button>
338
- <button
339
- onClick={(e) => {
340
- e.stopPropagation();
341
- setIsMaximized(!isMaximized);
342
- }}
343
- className="w-7 h-7 flex items-center justify-center hover:bg-green-500/20 rounded-md transition-colors group"
344
- title={isMaximized ? "Restore" : "Maximize"}
345
- >
346
- <Square size={14} className="text-green-400 group-hover:text-green-300" />
347
- </button>
348
- <button
349
- onClick={(e) => {
350
- e.stopPropagation();
351
- onClose();
352
- }}
353
- className="w-7 h-7 flex items-center justify-center hover:bg-red-500/30 rounded-md transition-colors group"
354
- title="Close Window"
355
- >
356
- <X size={14} className="text-red-400 group-hover:text-red-300" />
357
- </button>
358
- </div>
359
- </div>
360
-
361
  {/* Window Content */}
362
- <div className={`p-6 ${isMaximized ? 'h-[calc(100%-50px)] overflow-auto' : 'h-[600px] overflow-auto'}`}>
363
  {/* Session Info */}
364
  <div className="mb-6">
365
  <div>
@@ -503,22 +457,20 @@ export function SessionManagerWindow({ onClose, sessionId, sessionKey: initialSe
503
  <div className="flex gap-4 mb-4">
504
  <button
505
  onClick={() => setActiveTab('session')}
506
- className={`px-4 py-2 rounded-lg transition-colors flex items-center gap-2 ${
507
- activeTab === 'session'
508
- ? 'bg-purple-600 text-white'
509
- : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
510
- }`}
511
  >
512
  <Lock size={20} />
513
  Session Files ({files.length})
514
  </button>
515
  <button
516
  onClick={() => setActiveTab('public')}
517
- className={`px-4 py-2 rounded-lg transition-colors flex items-center gap-2 ${
518
- activeTab === 'public'
519
- ? 'bg-purple-600 text-white'
520
- : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
521
- }`}
522
  >
523
  <Globe size={20} />
524
  Public Files ({publicFiles.length})
@@ -597,6 +549,6 @@ export function SessionManagerWindow({ onClose, sessionId, sessionKey: initialSe
597
  )}
598
  </AnimatePresence>
599
  </div>
600
- </Draggable>
601
  )
602
  }
 
1
  'use client'
2
 
3
+ import React, { useState, useEffect } from 'react'
 
4
  import { motion, AnimatePresence } from 'framer-motion'
5
  import {
6
  Key,
 
13
  Copy,
14
  Check,
15
  X,
 
16
  FileText,
17
  Table as FileSpreadsheet,
18
  Presentation as FilePresentation,
19
  FilePdf,
 
20
  ArrowsClockwise as RefreshCw,
 
 
 
21
  } from '@phosphor-icons/react'
22
+ import Window from './Window'
23
 
24
  interface Session {
25
  id: string
 
42
  }
43
 
44
  export function SessionManagerWindow({ onClose, sessionId, sessionKey: initialSessionKey }: SessionManagerWindowProps) {
 
45
  const [sessionKey] = useState(initialSessionKey)
46
  const [files, setFiles] = useState<FileItem[]>([])
47
  const [publicFiles, setPublicFiles] = useState<FileItem[]>([])
 
53
  const [activeTab, setActiveTab] = useState<'session' | 'public'>('session')
54
  const [uploadFile, setUploadFile] = useState<File | null>(null)
55
  const [isPublicUpload, setIsPublicUpload] = useState(false)
 
56
 
57
  // Session is automatically created - no manual creation needed!
58
 
 
298
  }, [error])
299
 
300
  return (
301
+ <Window
302
+ id="session-manager"
303
+ title="Session Manager"
304
+ isOpen={true}
305
+ onClose={onClose}
306
+ width={900}
307
+ height={600}
308
+ x={100}
309
+ y={100}
310
+ className="session-manager-window"
311
+ headerClassName="bg-gradient-to-r from-purple-600/20 to-purple-800/20 border-b border-purple-500/20"
312
+ darkMode={true}
313
  >
314
+ <div className="flex flex-col h-full bg-gray-900/95 backdrop-blur-xl">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  {/* Window Content */}
316
+ <div className="p-6 flex-1 overflow-auto">
317
  {/* Session Info */}
318
  <div className="mb-6">
319
  <div>
 
457
  <div className="flex gap-4 mb-4">
458
  <button
459
  onClick={() => setActiveTab('session')}
460
+ className={`px-4 py-2 rounded-lg transition-colors flex items-center gap-2 ${activeTab === 'session'
461
+ ? 'bg-purple-600 text-white'
462
+ : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
463
+ }`}
 
464
  >
465
  <Lock size={20} />
466
  Session Files ({files.length})
467
  </button>
468
  <button
469
  onClick={() => setActiveTab('public')}
470
+ className={`px-4 py-2 rounded-lg transition-colors flex items-center gap-2 ${activeTab === 'public'
471
+ ? 'bg-purple-600 text-white'
472
+ : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
473
+ }`}
 
474
  >
475
  <Globe size={20} />
476
  Public Files ({publicFiles.length})
 
549
  )}
550
  </AnimatePresence>
551
  </div>
552
+ </Window>
553
  )
554
  }
app/components/TopBar.tsx CHANGED
@@ -42,7 +42,7 @@ export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }
42
  }, [])
43
 
44
  return (
45
- <div className="fixed top-0 left-0 right-0 h-8 glass flex items-center justify-between px-4 text-sm z-50 shadow-sm">
46
  <div className="flex items-center space-x-4">
47
  <button
48
  onClick={() => setAppleMenuOpen(!appleMenuOpen)}
@@ -51,14 +51,16 @@ export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }
51
  <CirclesFour size={20} weight="fill" className="text-blue-600" />
52
  </button>
53
  <span className="font-bold text-gray-800">Reuben OS</span>
54
- <span className="text-gray-600 text-xs ml-2">{activeAppName}</span>
55
  </div>
56
 
57
  <div className="flex items-center space-x-4">
58
- <BatteryFull size={16} weight="fill" className="text-gray-700" />
59
- <WifiHigh size={16} weight="fill" className="text-gray-700" />
60
- <MagnifyingGlass size={16} weight="regular" className="text-gray-700" />
61
- <div className="font-medium text-gray-800">{currentTime}</div>
 
 
62
  </div>
63
 
64
  <AnimatePresence>
@@ -73,25 +75,25 @@ export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }
73
  animate={{ opacity: 1, y: 0 }}
74
  exit={{ opacity: 0, y: -5 }}
75
  transition={{ duration: 0.15 }}
76
- className="absolute top-8 left-2 w-48 glass rounded-lg shadow-xl flex flex-col py-1 z-[60] text-sm"
77
  >
78
  <button
79
  onClick={() => {
80
  if (onAboutClick) onAboutClick()
81
  setAppleMenuOpen(false)
82
  }}
83
- className="text-left px-4 py-1 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
84
  About Reuben OS
85
  </button>
86
- <div className="h-px bg-gray-300 my-1 mx-2" />
87
- <button className="text-left px-4 py-1 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
88
  System Settings...
89
  </button>
90
- <div className="h-px bg-gray-300 my-1 mx-2" />
91
- <button className="text-left px-4 py-1 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
92
  Sleep
93
  </button>
94
- <button className="text-left px-4 py-1 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
95
  Restart...
96
  </button>
97
  <button
@@ -99,17 +101,17 @@ export function TopBar({ onPowerAction, activeAppName = 'Finder', onAboutClick }
99
  if (onPowerAction) onPowerAction()
100
  setAppleMenuOpen(false)
101
  }}
102
- className="text-left px-4 py-1 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
103
  >
104
  Shut Down...
105
  </button>
106
- <div className="h-px bg-gray-300 my-1 mx-2" />
107
  <button
108
  onClick={() => {
109
  if (onPowerAction) onPowerAction()
110
  setAppleMenuOpen(false)
111
  }}
112
- className="text-left px-4 py-1 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
113
  >
114
  Log Out...
115
  </button>
 
42
  }, [])
43
 
44
  return (
45
+ <div className="fixed top-0 left-0 right-0 h-8 glass flex items-center justify-between px-4 text-sm z-50 shadow-sm backdrop-blur-xl bg-white/40 border-b border-white/20">
46
  <div className="flex items-center space-x-4">
47
  <button
48
  onClick={() => setAppleMenuOpen(!appleMenuOpen)}
 
51
  <CirclesFour size={20} weight="fill" className="text-blue-600" />
52
  </button>
53
  <span className="font-bold text-gray-800">Reuben OS</span>
54
+ <span className="text-gray-600 text-xs ml-2 font-medium">{activeAppName}</span>
55
  </div>
56
 
57
  <div className="flex items-center space-x-4">
58
+ <div className="flex items-center space-x-3 text-gray-700">
59
+ <BatteryFull size={18} weight="fill" />
60
+ <WifiHigh size={18} weight="bold" />
61
+ <MagnifyingGlass size={18} weight="bold" />
62
+ </div>
63
+ <div className="font-medium text-gray-800 min-w-[140px] text-right">{currentTime}</div>
64
  </div>
65
 
66
  <AnimatePresence>
 
75
  animate={{ opacity: 1, y: 0 }}
76
  exit={{ opacity: 0, y: -5 }}
77
  transition={{ duration: 0.15 }}
78
+ className="absolute top-9 left-2 w-56 glass rounded-lg shadow-2xl flex flex-col py-1.5 z-[60] text-sm border border-white/40 backdrop-blur-2xl bg-white/80"
79
  >
80
  <button
81
  onClick={() => {
82
  if (onAboutClick) onAboutClick()
83
  setAppleMenuOpen(false)
84
  }}
85
+ className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800 font-medium">
86
  About Reuben OS
87
  </button>
88
+ <div className="h-px bg-gray-300/50 my-1 mx-2" />
89
+ <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
90
  System Settings...
91
  </button>
92
+ <div className="h-px bg-gray-300/50 my-1 mx-2" />
93
+ <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
94
  Sleep
95
  </button>
96
+ <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800">
97
  Restart...
98
  </button>
99
  <button
 
101
  if (onPowerAction) onPowerAction()
102
  setAppleMenuOpen(false)
103
  }}
104
+ className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
105
  >
106
  Shut Down...
107
  </button>
108
+ <div className="h-px bg-gray-300/50 my-1 mx-2" />
109
  <button
110
  onClick={() => {
111
  if (onPowerAction) onPowerAction()
112
  setAppleMenuOpen(false)
113
  }}
114
+ className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"
115
  >
116
  Log Out...
117
  </button>
app/components/Window.tsx CHANGED
@@ -58,7 +58,7 @@ const Window: React.FC<WindowProps> = ({
58
  };
59
 
60
  const windowClass = darkMode ? 'bg-gray-900 border-gray-700' : 'bg-[#f5f5f5] border-gray-300/50';
61
- const headerClass = darkMode ? 'bg-gray-800 border-gray-700' : 'bg-[#f1f1f1] border-gray-300';
62
 
63
  if (isMaximized) {
64
  return (
@@ -66,14 +66,14 @@ const Window: React.FC<WindowProps> = ({
66
  className={`fixed inset-0 top-8 z-50 flex flex-col overflow-hidden ${windowClass} ${className}`}
67
  >
68
  <div
69
- className={`h-12 flex items-center px-4 space-x-4 border-b ${headerClass} ${headerClassName}`}
70
  >
71
- <div className="flex space-x-2">
72
  <div className="traffic-light traffic-close" onClick={onClose} />
73
  <div className="traffic-light traffic-min" onClick={onMinimize} />
74
  <div className="traffic-light traffic-max" onClick={handleMaximize} />
75
  </div>
76
- <span className="font-semibold text-gray-700 flex-1 text-center pr-16">{title}</span>
77
  </div>
78
  <div className="flex-1 overflow-auto">{children}</div>
79
  </div>
@@ -105,23 +105,23 @@ const Window: React.FC<WindowProps> = ({
105
  });
106
  setCurrentPosition(position);
107
  }}
108
- className="window-transition"
109
  style={{ zIndex: 50 }}
110
  >
111
  <div
112
- className={`h-full rounded-xl shadow-2xl overflow-hidden border ${windowClass} ${className}`}
113
  >
114
  <div
115
- className={`window-drag-handle h-12 flex items-center px-4 space-x-4 border-b cursor-move ${headerClass} ${headerClassName}`}
116
  >
117
- <div className="flex space-x-2">
118
  <div className="traffic-light traffic-close" onClick={onClose} />
119
  <div className="traffic-light traffic-min" onClick={onMinimize} />
120
  <div className="traffic-light traffic-max" onClick={handleMaximize} />
121
  </div>
122
- <span className="font-semibold text-gray-700 flex-1 text-center pr-16">{title}</span>
123
  </div>
124
- <div className="flex-1 overflow-auto bg-white" style={{ height: 'calc(100% - 3rem)' }}>
125
  {children}
126
  </div>
127
  </div>
 
58
  };
59
 
60
  const windowClass = darkMode ? 'bg-gray-900 border-gray-700' : 'bg-[#f5f5f5] border-gray-300/50';
61
+ const headerClass = darkMode ? 'bg-gray-800 border-gray-700' : 'macos-window-header';
62
 
63
  if (isMaximized) {
64
  return (
 
66
  className={`fixed inset-0 top-8 z-50 flex flex-col overflow-hidden ${windowClass} ${className}`}
67
  >
68
  <div
69
+ className={`h-10 flex items-center px-4 space-x-4 border-b ${headerClass} ${headerClassName}`}
70
  >
71
+ <div className="flex space-x-2 group">
72
  <div className="traffic-light traffic-close" onClick={onClose} />
73
  <div className="traffic-light traffic-min" onClick={onMinimize} />
74
  <div className="traffic-light traffic-max" onClick={handleMaximize} />
75
  </div>
76
+ <span className="font-semibold text-gray-700 flex-1 text-center pr-16 text-sm">{title}</span>
77
  </div>
78
  <div className="flex-1 overflow-auto">{children}</div>
79
  </div>
 
105
  });
106
  setCurrentPosition(position);
107
  }}
108
+ className="pointer-events-auto"
109
  style={{ zIndex: 50 }}
110
  >
111
  <div
112
+ className={`h-full macos-window flex flex-col ${className}`}
113
  >
114
  <div
115
+ className={`window-drag-handle h-10 flex items-center px-4 space-x-4 border-b cursor-move ${headerClass} ${headerClassName}`}
116
  >
117
+ <div className="flex space-x-2 group">
118
  <div className="traffic-light traffic-close" onClick={onClose} />
119
  <div className="traffic-light traffic-min" onClick={onMinimize} />
120
  <div className="traffic-light traffic-max" onClick={handleMaximize} />
121
  </div>
122
+ <span className="font-semibold text-gray-700 flex-1 text-center pr-16 text-sm select-none">{title}</span>
123
  </div>
124
+ <div className="flex-1 overflow-auto bg-white/90 backdrop-blur-sm" style={{ height: 'calc(100% - 2.5rem)' }}>
125
  {children}
126
  </div>
127
  </div>
app/globals.css CHANGED
@@ -4,6 +4,7 @@
4
 
5
  /* Tailwind base layer for compatibility */
6
  @layer base {
 
7
  *,
8
  ::after,
9
  ::before,
@@ -132,6 +133,7 @@
132
  from {
133
  height: 0;
134
  }
 
135
  to {
136
  height: var(--radix-accordion-content-height);
137
  }
@@ -141,6 +143,7 @@
141
  from {
142
  height: var(--radix-accordion-content-height);
143
  }
 
144
  to {
145
  height: 0;
146
  }
@@ -163,50 +166,49 @@
163
  }
164
  }
165
 
166
- /* Sonoma Background */
167
- .bg-sonoma {
168
- background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
169
- background-size: cover;
170
- background-position: center;
171
- }
172
-
173
  /* Glassmorphism utilities */
174
  .glass {
175
- background: rgba(255, 255, 255, 0.65);
176
- backdrop-filter: blur(20px);
177
- -webkit-backdrop-filter: blur(20px);
178
  border: 1px solid rgba(255, 255, 255, 0.3);
 
179
  }
180
 
181
  .glass-dark {
182
- background: rgba(0, 0, 0, 0.5);
183
- backdrop-filter: blur(20px);
184
- -webkit-backdrop-filter: blur(20px);
185
- border: 1px solid rgba(255, 255, 255, 0.1);
 
186
  }
187
 
188
  .dock-glass {
189
  background: rgba(255, 255, 255, 0.4);
190
- backdrop-filter: blur(15px);
191
- -webkit-backdrop-filter: blur(15px);
192
  border: 1px solid rgba(255, 255, 255, 0.2);
 
193
  }
194
 
195
  /* App Icon Styles */
196
  .app-icon {
197
- transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
198
  }
 
199
  .app-icon:hover {
200
- transform: scale(1.2) translateY(-10px);
201
  }
 
202
  .app-icon:active {
203
  transform: scale(0.95);
204
  }
205
 
206
  /* Window Animations */
207
  .window-transition {
208
- transition: opacity 0.2s, transform 0.2s;
209
  }
 
210
  .window-hidden {
211
  opacity: 0;
212
  transform: scale(0.95);
@@ -223,14 +225,17 @@
223
  cursor: pointer;
224
  transition: all 0.2s ease;
225
  }
 
226
  .traffic-close {
227
  background-color: #ff5f56;
228
  border: 1px solid #e0443e;
229
  }
 
230
  .traffic-min {
231
  background-color: #ffbd2e;
232
  border: 1px solid #dea123;
233
  }
 
234
  .traffic-max {
235
  background-color: #27c93f;
236
  border: 1px solid #1aab29;
@@ -247,40 +252,61 @@
247
  align-items: center;
248
  justify-content: center;
249
  font-size: 8px;
250
- color: rgba(0,0,0,0.5);
251
  font-weight: bold;
252
  }
253
- .traffic-close:hover::after { content: '×'; }
254
- .traffic-min:hover::after { content: '−'; }
255
- .traffic-max:hover::after { content: '+'; }
 
 
 
 
 
 
 
 
 
256
 
257
  /* Custom Scrollbar */
258
  ::-webkit-scrollbar {
259
- width: 8px;
260
- height: 8px;
261
  }
 
262
  ::-webkit-scrollbar-track {
263
  background: transparent;
264
  }
 
265
  ::-webkit-scrollbar-thumb {
266
- background: rgba(0,0,0,0.2);
267
- border-radius: 4px;
 
 
268
  }
 
269
  ::-webkit-scrollbar-thumb:hover {
270
- background: rgba(0,0,0,0.3);
271
  }
272
 
273
  /* Desktop Icon Grid */
274
  .desktop-icon {
275
- @apply flex flex-col items-center gap-1 cursor-pointer transition-all;
276
  }
 
277
  .desktop-icon:hover {
278
- transform: scale(1.05);
 
 
279
  }
 
280
  .desktop-icon-label {
281
  @apply text-xs text-white font-medium px-2 py-0.5 rounded;
282
- background: rgba(0,0,0,0.3);
283
- text-shadow: 0 1px 2px rgba(0,0,0,0.5);
 
 
 
284
  }
285
 
286
  /* macOS style window */
@@ -288,17 +314,18 @@
288
  @apply rounded-xl shadow-2xl overflow-hidden;
289
  background: #f5f5f5;
290
  border: 1px solid rgba(0, 0, 0, 0.1);
 
291
  }
292
 
293
  .macos-window-header {
294
  @apply h-10 flex items-center px-4 justify-between;
295
- background: #f1f1f1;
296
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
297
  }
298
 
299
  /* Dock styles */
300
  .dock-container {
301
- @apply fixed bottom-2 left-0 right-0 flex justify-center z-50;
302
  }
303
 
304
  .dock-item {
@@ -307,7 +334,7 @@
307
 
308
  .dock-dot {
309
  @apply w-1 h-1 rounded-full mt-1 opacity-0 transition-opacity;
310
- background: rgba(0, 0, 0, 0.5);
311
  }
312
 
313
  .dock-item:hover .dock-dot {
 
4
 
5
  /* Tailwind base layer for compatibility */
6
  @layer base {
7
+
8
  *,
9
  ::after,
10
  ::before,
 
133
  from {
134
  height: 0;
135
  }
136
+
137
  to {
138
  height: var(--radix-accordion-content-height);
139
  }
 
143
  from {
144
  height: var(--radix-accordion-content-height);
145
  }
146
+
147
  to {
148
  height: 0;
149
  }
 
166
  }
167
  }
168
 
 
 
 
 
 
 
 
169
  /* Glassmorphism utilities */
170
  .glass {
171
+ background: rgba(255, 255, 255, 0.75);
172
+ backdrop-filter: blur(25px) saturate(180%);
173
+ -webkit-backdrop-filter: blur(25px) saturate(180%);
174
  border: 1px solid rgba(255, 255, 255, 0.3);
175
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
176
  }
177
 
178
  .glass-dark {
179
+ background: rgba(20, 20, 20, 0.75);
180
+ backdrop-filter: blur(25px) saturate(180%);
181
+ -webkit-backdrop-filter: blur(25px) saturate(180%);
182
+ border: 1px solid rgba(255, 255, 255, 0.08);
183
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
184
  }
185
 
186
  .dock-glass {
187
  background: rgba(255, 255, 255, 0.4);
188
+ backdrop-filter: blur(20px) saturate(180%);
189
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
190
  border: 1px solid rgba(255, 255, 255, 0.2);
191
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
192
  }
193
 
194
  /* App Icon Styles */
195
  .app-icon {
196
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
197
  }
198
+
199
  .app-icon:hover {
200
+ transform: scale(1.15) translateY(-8px);
201
  }
202
+
203
  .app-icon:active {
204
  transform: scale(0.95);
205
  }
206
 
207
  /* Window Animations */
208
  .window-transition {
209
+ transition: opacity 0.2s ease-out;
210
  }
211
+
212
  .window-hidden {
213
  opacity: 0;
214
  transform: scale(0.95);
 
225
  cursor: pointer;
226
  transition: all 0.2s ease;
227
  }
228
+
229
  .traffic-close {
230
  background-color: #ff5f56;
231
  border: 1px solid #e0443e;
232
  }
233
+
234
  .traffic-min {
235
  background-color: #ffbd2e;
236
  border: 1px solid #dea123;
237
  }
238
+
239
  .traffic-max {
240
  background-color: #27c93f;
241
  border: 1px solid #1aab29;
 
252
  align-items: center;
253
  justify-content: center;
254
  font-size: 8px;
255
+ color: rgba(0, 0, 0, 0.5);
256
  font-weight: bold;
257
  }
258
+
259
+ .traffic-close:hover::after {
260
+ content: '×';
261
+ }
262
+
263
+ .traffic-min:hover::after {
264
+ content: '−';
265
+ }
266
+
267
+ .traffic-max:hover::after {
268
+ content: '+';
269
+ }
270
 
271
  /* Custom Scrollbar */
272
  ::-webkit-scrollbar {
273
+ width: 10px;
274
+ height: 10px;
275
  }
276
+
277
  ::-webkit-scrollbar-track {
278
  background: transparent;
279
  }
280
+
281
  ::-webkit-scrollbar-thumb {
282
+ background: rgba(0, 0, 0, 0.15);
283
+ border-radius: 5px;
284
+ border: 2px solid transparent;
285
+ background-clip: content-box;
286
  }
287
+
288
  ::-webkit-scrollbar-thumb:hover {
289
+ background-color: rgba(0, 0, 0, 0.25);
290
  }
291
 
292
  /* Desktop Icon Grid */
293
  .desktop-icon {
294
+ @apply flex flex-col items-center gap-1.5 cursor-pointer transition-all;
295
  }
296
+
297
  .desktop-icon:hover {
298
+ background-color: rgba(255, 255, 255, 0.15);
299
+ border-radius: 8px;
300
+ box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.15);
301
  }
302
+
303
  .desktop-icon-label {
304
  @apply text-xs text-white font-medium px-2 py-0.5 rounded;
305
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
306
+ }
307
+
308
+ .desktop-icon:hover .desktop-icon-label {
309
+ background: rgba(0, 0, 0, 0.5);
310
  }
311
 
312
  /* macOS style window */
 
314
  @apply rounded-xl shadow-2xl overflow-hidden;
315
  background: #f5f5f5;
316
  border: 1px solid rgba(0, 0, 0, 0.1);
317
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
318
  }
319
 
320
  .macos-window-header {
321
  @apply h-10 flex items-center px-4 justify-between;
322
+ background: linear-gradient(to bottom, #f6f6f6, #e8e8e8);
323
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
324
  }
325
 
326
  /* Dock styles */
327
  .dock-container {
328
+ @apply fixed bottom-4 left-0 right-0 flex justify-center z-50;
329
  }
330
 
331
  .dock-item {
 
334
 
335
  .dock-dot {
336
  @apply w-1 h-1 rounded-full mt-1 opacity-0 transition-opacity;
337
+ background: rgba(0, 0, 0, 0.6);
338
  }
339
 
340
  .dock-item:hover .dock-dot {
backend/mcp_server.py CHANGED
@@ -14,6 +14,7 @@ from tools.document_tools import register_document_tools
14
  from tools.calendar_tools import register_calendar_tools
15
  from tools.ai_study_tools import register_ai_study_tools
16
  from tools.claude_integration_tools import register_claude_integration_tools
 
17
 
18
  # Setup logging
19
  logging.basicConfig(level=logging.INFO)
@@ -68,6 +69,9 @@ register_ai_study_tools(mcp)
68
  logger.info("Registering Claude integration tools")
69
  register_claude_integration_tools(mcp, DATA_DIR)
70
 
 
 
 
71
  # Add server information tool
72
  @mcp.tool()
73
  def get_server_info() -> dict:
@@ -115,6 +119,13 @@ def get_server_info() -> dict:
115
  "execute_matplotlib_code",
116
  "list_claude_generated_content",
117
  "get_code_output"
 
 
 
 
 
 
 
118
  ]
119
  },
120
  "tips": [
@@ -123,7 +134,9 @@ def get_server_info() -> dict:
123
  "Use 'execute_matplotlib_code' for data visualization with matplotlib",
124
  "Use 'list_claude_generated_content' to see all saved content",
125
  "Upload operations require a passcode for security",
126
- "Reuben OS can execute your Python code and save outputs automatically!"
 
 
127
  ]
128
  }
129
 
 
14
  from tools.calendar_tools import register_calendar_tools
15
  from tools.ai_study_tools import register_ai_study_tools
16
  from tools.claude_integration_tools import register_claude_integration_tools
17
+ from tools.flutter_tools import register_flutter_tools
18
 
19
  # Setup logging
20
  logging.basicConfig(level=logging.INFO)
 
69
  logger.info("Registering Claude integration tools")
70
  register_claude_integration_tools(mcp, DATA_DIR)
71
 
72
+ logger.info("Registering Flutter app tools")
73
+ register_flutter_tools(mcp, DATA_DIR, UPLOAD_PASSCODE)
74
+
75
  # Add server information tool
76
  @mcp.tool()
77
  def get_server_info() -> dict:
 
119
  "execute_matplotlib_code",
120
  "list_claude_generated_content",
121
  "get_code_output"
122
+ ],
123
+ "flutter_tools": [
124
+ "create_flutter_app (requires passcode)",
125
+ "get_flutter_app",
126
+ "list_flutter_apps",
127
+ "update_flutter_app (requires passcode)",
128
+ "delete_flutter_app (requires passcode)"
129
  ]
130
  },
131
  "tips": [
 
134
  "Use 'execute_matplotlib_code' for data visualization with matplotlib",
135
  "Use 'list_claude_generated_content' to see all saved content",
136
  "Upload operations require a passcode for security",
137
+ "Reuben OS can execute your Python code and save outputs automatically!",
138
+ "Use 'create_flutter_app' to create Flutter apps that run in Zapp",
139
+ "Use 'list_flutter_apps' to see all available Flutter applications"
140
  ]
141
  }
142
 
backend/tools/flutter_tools.py ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flutter App Tools for Reuben OS MCP Server
3
+ Provides tools for creating, retrieving, and managing Flutter apps
4
+ """
5
+
6
+ import os
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Dict, Any, Optional, List
10
+ import datetime
11
+
12
+ def register_flutter_tools(mcp, data_dir: str, upload_passcode: str):
13
+ """Register Flutter app related MCP tools"""
14
+
15
+ # Create flutter_apps directory
16
+ flutter_apps_dir = os.path.join(data_dir, "documents", "flutter_apps")
17
+ os.makedirs(flutter_apps_dir, exist_ok=True)
18
+
19
+ def validate_passcode(passcode: str) -> bool:
20
+ """Validate the upload passcode"""
21
+ return passcode == upload_passcode
22
+
23
+ @mcp.tool()
24
+ def create_flutter_app(
25
+ name: str,
26
+ dart_code: str,
27
+ dependencies: Optional[List[str]] = None,
28
+ pubspec_yaml: Optional[str] = None,
29
+ passcode: str = ""
30
+ ) -> Dict[str, Any]:
31
+ """
32
+ Create a new Flutter app and save it to storage
33
+
34
+ Args:
35
+ name: Name of the Flutter app (will be used as filename)
36
+ dart_code: The Dart/Flutter code (typically main.dart content)
37
+ dependencies: List of dependencies (e.g., ["http: ^0.13.0", "provider: ^6.0.0"])
38
+ pubspec_yaml: Complete pubspec.yaml content (optional)
39
+ passcode: Upload passcode for authentication
40
+
41
+ Returns:
42
+ Dictionary with success status and file path
43
+ """
44
+ # Validate passcode
45
+ if not validate_passcode(passcode):
46
+ return {
47
+ "status": "error",
48
+ "message": "Invalid passcode"
49
+ }
50
+
51
+ # Validate inputs
52
+ if not name or not dart_code:
53
+ return {
54
+ "status": "error",
55
+ "message": "Name and dart_code are required"
56
+ }
57
+
58
+ # Sanitize name
59
+ safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
60
+ if not safe_name:
61
+ return {
62
+ "status": "error",
63
+ "message": "Invalid app name"
64
+ }
65
+
66
+ # Generate default pubspec.yaml if not provided
67
+ if not pubspec_yaml:
68
+ dep_lines = []
69
+ if dependencies:
70
+ for dep in dependencies:
71
+ dep_lines.append(f" {dep}")
72
+
73
+ deps_section = "\n".join(dep_lines) if dep_lines else ""
74
+ pubspec_yaml = f"""name: {safe_name}
75
+ description: A Flutter application created via Reuben OS
76
+ version: 1.0.0
77
+
78
+ environment:
79
+ sdk: '>=3.0.0 <4.0.0'
80
+
81
+ dependencies:
82
+ flutter:
83
+ sdk: flutter
84
+ {deps_section}
85
+
86
+ flutter:
87
+ uses-material-design: true
88
+ """
89
+
90
+ # Create Flutter app object
91
+ flutter_app = {
92
+ "type": "flutter_app",
93
+ "name": safe_name,
94
+ "dartCode": dart_code,
95
+ "dependencies": dependencies or [],
96
+ "pubspecYaml": pubspec_yaml,
97
+ "metadata": {
98
+ "created": datetime.datetime.now().isoformat(),
99
+ "modified": datetime.datetime.now().isoformat(),
100
+ "author": "claude"
101
+ }
102
+ }
103
+
104
+ # Save to file
105
+ filename = f"{safe_name}.flutter.json"
106
+ file_path = os.path.join(flutter_apps_dir, filename)
107
+
108
+ # Check if file exists
109
+ if os.path.exists(file_path):
110
+ return {
111
+ "status": "error",
112
+ "message": f"Flutter app '{safe_name}' already exists. Use update_flutter_app to modify it."
113
+ }
114
+
115
+ try:
116
+ with open(file_path, 'w', encoding='utf-8') as f:
117
+ json.dump(flutter_app, f, indent=2, ensure_ascii=False)
118
+
119
+ return {
120
+ "status": "success",
121
+ "message": f"Flutter app '{safe_name}' created successfully",
122
+ "app_name": safe_name,
123
+ "file_path": file_path,
124
+ "app_data": flutter_app
125
+ }
126
+ except Exception as e:
127
+ return {
128
+ "status": "error",
129
+ "message": f"Failed to save Flutter app: {str(e)}"
130
+ }
131
+
132
+ @mcp.tool()
133
+ def get_flutter_app(name: str) -> Dict[str, Any]:
134
+ """
135
+ Retrieve a Flutter app by name
136
+
137
+ Args:
138
+ name: Name of the Flutter app
139
+
140
+ Returns:
141
+ Dictionary with app data or error
142
+ """
143
+ # Sanitize name
144
+ safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
145
+ filename = f"{safe_name}.flutter.json"
146
+ file_path = os.path.join(flutter_apps_dir, filename)
147
+
148
+ if not os.path.exists(file_path):
149
+ return {
150
+ "status": "error",
151
+ "message": f"Flutter app '{safe_name}' not found"
152
+ }
153
+
154
+ try:
155
+ with open(file_path, 'r', encoding='utf-8') as f:
156
+ app_data = json.load(f)
157
+
158
+ return {
159
+ "status": "success",
160
+ "app_name": safe_name,
161
+ "app_data": app_data
162
+ }
163
+ except Exception as e:
164
+ return {
165
+ "status": "error",
166
+ "message": f"Failed to read Flutter app: {str(e)}"
167
+ }
168
+
169
+ @mcp.tool()
170
+ def list_flutter_apps() -> Dict[str, Any]:
171
+ """
172
+ List all available Flutter apps
173
+
174
+ Returns:
175
+ Dictionary with list of Flutter apps
176
+ """
177
+ try:
178
+ apps = []
179
+
180
+ if not os.path.exists(flutter_apps_dir):
181
+ return {
182
+ "status": "success",
183
+ "apps": [],
184
+ "count": 0
185
+ }
186
+
187
+ for filename in os.listdir(flutter_apps_dir):
188
+ if filename.endswith('.flutter.json'):
189
+ file_path = os.path.join(flutter_apps_dir, filename)
190
+ try:
191
+ with open(file_path, 'r', encoding='utf-8') as f:
192
+ app_data = json.load(f)
193
+
194
+ apps.append({
195
+ "name": app_data.get("name", filename.replace('.flutter.json', '')),
196
+ "created": app_data.get("metadata", {}).get("created"),
197
+ "modified": app_data.get("metadata", {}).get("modified"),
198
+ "dependencies_count": len(app_data.get("dependencies", [])),
199
+ "file_path": file_path
200
+ })
201
+ except Exception as e:
202
+ # Skip malformed files
203
+ continue
204
+
205
+ return {
206
+ "status": "success",
207
+ "apps": apps,
208
+ "count": len(apps)
209
+ }
210
+ except Exception as e:
211
+ return {
212
+ "status": "error",
213
+ "message": f"Failed to list Flutter apps: {str(e)}"
214
+ }
215
+
216
+ @mcp.tool()
217
+ def update_flutter_app(
218
+ name: str,
219
+ dart_code: Optional[str] = None,
220
+ dependencies: Optional[List[str]] = None,
221
+ pubspec_yaml: Optional[str] = None,
222
+ passcode: str = ""
223
+ ) -> Dict[str, Any]:
224
+ """
225
+ Update an existing Flutter app
226
+
227
+ Args:
228
+ name: Name of the Flutter app to update
229
+ dart_code: New Dart/Flutter code (optional, keeps existing if not provided)
230
+ dependencies: New dependencies list (optional, keeps existing if not provided)
231
+ pubspec_yaml: New pubspec.yaml content (optional, keeps existing if not provided)
232
+ passcode: Upload passcode for authentication
233
+
234
+ Returns:
235
+ Dictionary with success status
236
+ """
237
+ # Validate passcode
238
+ if not validate_passcode(passcode):
239
+ return {
240
+ "status": "error",
241
+ "message": "Invalid passcode"
242
+ }
243
+
244
+ # Sanitize name
245
+ safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
246
+ filename = f"{safe_name}.flutter.json"
247
+ file_path = os.path.join(flutter_apps_dir, filename)
248
+
249
+ if not os.path.exists(file_path):
250
+ return {
251
+ "status": "error",
252
+ "message": f"Flutter app '{safe_name}' not found"
253
+ }
254
+
255
+ try:
256
+ # Read existing app
257
+ with open(file_path, 'r', encoding='utf-8') as f:
258
+ app_data = json.load(f)
259
+
260
+ # Update fields if provided
261
+ if dart_code is not None:
262
+ app_data["dartCode"] = dart_code
263
+ if dependencies is not None:
264
+ app_data["dependencies"] = dependencies
265
+ if pubspec_yaml is not None:
266
+ app_data["pubspecYaml"] = pubspec_yaml
267
+
268
+ # Update metadata
269
+ app_data["metadata"]["modified"] = datetime.datetime.now().isoformat()
270
+
271
+ # Save updated app
272
+ with open(file_path, 'w', encoding='utf-8') as f:
273
+ json.dump(app_data, f, indent=2, ensure_ascii=False)
274
+
275
+ return {
276
+ "status": "success",
277
+ "message": f"Flutter app '{safe_name}' updated successfully",
278
+ "app_data": app_data
279
+ }
280
+ except Exception as e:
281
+ return {
282
+ "status": "error",
283
+ "message": f"Failed to update Flutter app: {str(e)}"
284
+ }
285
+
286
+ @mcp.tool()
287
+ def delete_flutter_app(name: str, passcode: str) -> Dict[str, Any]:
288
+ """
289
+ Delete a Flutter app
290
+
291
+ Args:
292
+ name: Name of the Flutter app to delete
293
+ passcode: Upload passcode for authentication
294
+
295
+ Returns:
296
+ Dictionary with success status
297
+ """
298
+ # Validate passcode
299
+ if not validate_passcode(passcode):
300
+ return {
301
+ "status": "error",
302
+ "message": "Invalid passcode"
303
+ }
304
+
305
+ # Sanitize name
306
+ safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
307
+ filename = f"{safe_name}.flutter.json"
308
+ file_path = os.path.join(flutter_apps_dir, filename)
309
+
310
+ if not os.path.exists(file_path):
311
+ return {
312
+ "status": "error",
313
+ "message": f"Flutter app '{safe_name}' not found"
314
+ }
315
+
316
+ try:
317
+ os.remove(file_path)
318
+ return {
319
+ "status": "success",
320
+ "message": f"Flutter app '{safe_name}' deleted successfully"
321
+ }
322
+ except Exception as e:
323
+ return {
324
+ "status": "error",
325
+ "message": f"Failed to delete Flutter app: {str(e)}"
326
+ }
327
+