admin08077 commited on
Commit
ee48497
·
verified ·
1 Parent(s): 1100577

Upload 4 files

Browse files
components/desktop/DesktopView.tsx ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import { FeatureDock } from './FeatureDock.tsx';
3
+ import { Window } from './Window.tsx';
4
+ import { Taskbar } from './Taskbar.tsx';
5
+ import { ALL_FEATURES } from '../features/index.ts';
6
+ import type { Feature } from '../../types.ts';
7
+
8
+ interface WindowState {
9
+ id: string;
10
+ position: { x: number; y: number };
11
+ size: { width: number; height: number };
12
+ zIndex: number;
13
+ isMinimized: boolean;
14
+ }
15
+
16
+ const Z_INDEX_BASE = 10;
17
+
18
+ export const DesktopView: React.FC<{ openFeatureId?: string }> = ({ openFeatureId }) => {
19
+ const [windows, setWindows] = useState<Record<string, WindowState>>({});
20
+ const [activeId, setActiveId] = useState<string | null>(null);
21
+ const [nextZIndex, setNextZIndex] = useState(Z_INDEX_BASE);
22
+
23
+ const openWindow = useCallback((featureId: string) => {
24
+ const newZIndex = nextZIndex + 1;
25
+ setNextZIndex(newZIndex);
26
+ setActiveId(featureId);
27
+
28
+ setWindows(prev => {
29
+ const existingWindow = prev[featureId];
30
+ if (existingWindow) {
31
+ return {
32
+ ...prev,
33
+ [featureId]: {
34
+ ...existingWindow,
35
+ isMinimized: false,
36
+ zIndex: newZIndex,
37
+ }
38
+ };
39
+ }
40
+
41
+ const openWindowsCount = Object.values(prev).filter(w => !w.isMinimized).length;
42
+ const newWindow: WindowState = {
43
+ id: featureId,
44
+ position: { x: 50 + openWindowsCount * 30, y: 50 + openWindowsCount * 30 },
45
+ size: { width: 800, height: 600 },
46
+ zIndex: newZIndex,
47
+ isMinimized: false,
48
+ };
49
+ return { ...prev, [featureId]: newWindow };
50
+ });
51
+ }, [nextZIndex]);
52
+
53
+ useEffect(() => {
54
+ if(openFeatureId) {
55
+ openWindow(openFeatureId);
56
+ }
57
+ }, [openFeatureId, openWindow])
58
+
59
+ const closeWindow = (id: string) => {
60
+ setWindows(prev => {
61
+ const newState = { ...prev };
62
+ delete newState[id];
63
+ return newState;
64
+ });
65
+ };
66
+
67
+ const minimizeWindow = (id: string) => {
68
+ setWindows(prev => ({
69
+ ...prev,
70
+ [id]: { ...prev[id], isMinimized: true }
71
+ }));
72
+ setActiveId(null);
73
+ };
74
+
75
+ const focusWindow = (id: string) => {
76
+ if (id === activeId) return;
77
+ const newZIndex = nextZIndex + 1;
78
+ setNextZIndex(newZIndex);
79
+ setActiveId(id);
80
+ setWindows(prev => ({
81
+ ...prev,
82
+ [id]: { ...prev[id], zIndex: newZIndex }
83
+ }));
84
+ };
85
+
86
+ const updateWindowState = (id: string, updates: Partial<WindowState>) => {
87
+ setWindows(prev => ({
88
+ ...prev,
89
+ [id]: { ...prev[id], ...updates }
90
+ }));
91
+ }
92
+
93
+ const openWindows = Object.values(windows).filter(w => !w.isMinimized);
94
+ const minimizedWindows = Object.values(windows).filter(w => w.isMinimized);
95
+ const featuresMap = new Map(ALL_FEATURES.map(f => [f.id, f]));
96
+
97
+ return (
98
+ <div className="h-full flex flex-col bg-transparent">
99
+ <FeatureDock onOpen={openWindow} />
100
+ <div className="flex-grow relative overflow-hidden">
101
+ {openWindows.map(win => {
102
+ const feature = featuresMap.get(win.id);
103
+ if (!feature) return null;
104
+ return (
105
+ <Window
106
+ key={win.id}
107
+ feature={feature}
108
+ state={win}
109
+ isActive={win.id === activeId}
110
+ onClose={() => closeWindow(win.id)}
111
+ onMinimize={() => minimizeWindow(win.id)}
112
+ onFocus={() => focusWindow(win.id)}
113
+ onUpdate={updateWindowState}
114
+ />
115
+ );
116
+ })}
117
+ </div>
118
+ <Taskbar
119
+ minimizedWindows={minimizedWindows.map(w => featuresMap.get(w.id)).filter(Boolean) as Feature[]}
120
+ onRestore={openWindow}
121
+ />
122
+ </div>
123
+ );
124
+ };
components/desktop/FeatureDock.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { ALL_FEATURES } from '../features/index.ts';
3
+ import type { Feature } from '../../types.ts';
4
+
5
+ interface FeatureButtonProps {
6
+ feature: Feature;
7
+ onOpen: (id: string) => void;
8
+ }
9
+
10
+ const FeatureButton: React.FC<FeatureButtonProps> = ({ feature, onOpen }) => {
11
+ return (
12
+ <button
13
+ onClick={() => onOpen(feature.id)}
14
+ className="w-24 h-24 flex flex-col items-center justify-center p-2 rounded-lg bg-slate-800/50 hover:bg-slate-700/80 transition-colors group"
15
+ title={feature.name}
16
+ >
17
+ <div className="text-cyan-400 group-hover:scale-110 transition-transform">{feature.icon}</div>
18
+ <span className="text-xs text-slate-300 mt-2 text-center w-full break-words">{feature.name}</span>
19
+ </button>
20
+ );
21
+ };
22
+
23
+ interface FeatureDockProps {
24
+ onOpen: (id: string) => void;
25
+ }
26
+
27
+ export const FeatureDock: React.FC<FeatureDockProps> = ({ onOpen }) => {
28
+ return (
29
+ <div className="h-96 flex-shrink-0 bg-slate-900/50 backdrop-blur-sm border-b border-slate-800 p-3 overflow-y-auto">
30
+ <div className="flex flex-wrap gap-3 justify-center">
31
+ {ALL_FEATURES.map(feature => (
32
+ <FeatureButton key={feature.id} feature={feature} onOpen={onOpen} />
33
+ ))}
34
+ </div>
35
+ </div>
36
+ );
37
+ };
components/desktop/Taskbar.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import type { Feature } from '../../types.ts';
3
+
4
+ interface TaskbarProps {
5
+ minimizedWindows: Feature[];
6
+ onRestore: (id: string) => void;
7
+ }
8
+
9
+ export const Taskbar: React.FC<TaskbarProps> = ({ minimizedWindows, onRestore }) => {
10
+ if (minimizedWindows.length === 0) {
11
+ return null;
12
+ }
13
+
14
+ return (
15
+ <div className="absolute bottom-0 left-20 right-0 h-10 bg-slate-900/80 backdrop-blur-sm border-t border-slate-700 flex items-center px-2 gap-2 z-[999]">
16
+ {minimizedWindows.map(feature => (
17
+ <button
18
+ key={feature.id}
19
+ onClick={() => onRestore(feature.id)}
20
+ className="h-8 px-3 flex items-center gap-2 rounded-md bg-slate-700 hover:bg-slate-600 text-slate-200 text-sm"
21
+ title={`Restore ${feature.name}`}
22
+ >
23
+ <div className="w-4 h-4">{feature.icon}</div>
24
+ <span>{feature.name}</span>
25
+ </button>
26
+ ))}
27
+ </div>
28
+ );
29
+ };
components/desktop/Window.tsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { Suspense, useRef, useState } from 'react';
2
+ import type { Feature } from '../../types.ts';
3
+ import { FEATURES_MAP } from '../features/index.ts';
4
+ import { LoadingIndicator } from '../../App.tsx';
5
+ import { MinimizeIcon, XMarkIcon } from '../icons/InterfaceIcons.tsx';
6
+
7
+ interface WindowState {
8
+ id: string;
9
+ position: { x: number; y: number };
10
+ size: { width: number; height: number };
11
+ zIndex: number;
12
+ isMinimized: boolean;
13
+ }
14
+
15
+ interface WindowProps {
16
+ feature: Feature;
17
+ state: WindowState;
18
+ isActive: boolean;
19
+ onClose: (id: string) => void;
20
+ onMinimize: (id: string) => void;
21
+ onFocus: (id: string) => void;
22
+ onUpdate: (id: string, updates: Partial<WindowState>) => void;
23
+ }
24
+
25
+ export const Window: React.FC<WindowProps> = ({ feature, state, isActive, onClose, onMinimize, onFocus, onUpdate }) => {
26
+ const dragStartPos = useRef<{ x: number; y: number } | null>(null);
27
+ const initialPos = useRef<{ x: number; y: number } | null>(null);
28
+
29
+ const FeatureComponent = FEATURES_MAP.get(feature.id)?.component;
30
+
31
+ const handleDragStart = (e: React.MouseEvent<HTMLDivElement>) => {
32
+ e.preventDefault();
33
+ onFocus(feature.id);
34
+ dragStartPos.current = { x: e.clientX, y: e.clientY };
35
+ initialPos.current = { x: state.position.x, y: state.position.y };
36
+ window.addEventListener('mousemove', handleDragMove);
37
+ window.addEventListener('mouseup', handleDragEnd);
38
+ };
39
+
40
+ const handleDragMove = (e: MouseEvent) => {
41
+ if (!dragStartPos.current || !initialPos.current) return;
42
+ const dx = e.clientX - dragStartPos.current.x;
43
+ const dy = e.clientY - dragStartPos.current.y;
44
+ onUpdate(feature.id, { position: { x: initialPos.current.x + dx, y: initialPos.current.y + dy }});
45
+ };
46
+
47
+ const handleDragEnd = () => {
48
+ dragStartPos.current = null;
49
+ initialPos.current = null;
50
+ window.removeEventListener('mousemove', handleDragMove);
51
+ window.removeEventListener('mouseup', handleDragEnd);
52
+ };
53
+
54
+ return (
55
+ <div
56
+ className={`absolute bg-slate-800/70 backdrop-blur-md border rounded-lg shadow-2xl shadow-black/50 flex flex-col transition-all duration-100 ${isActive ? 'border-cyan-500/50' : 'border-slate-700/50'}`}
57
+ style={{
58
+ left: state.position.x,
59
+ top: state.position.y,
60
+ width: state.size.width,
61
+ height: state.size.height,
62
+ zIndex: state.zIndex
63
+ }}
64
+ onMouseDown={() => onFocus(feature.id)}
65
+ >
66
+ <header
67
+ className={`flex items-center justify-between h-8 px-2 border-b ${isActive ? 'bg-slate-700/50 border-slate-600' : 'bg-slate-800/50 border-slate-700'} rounded-t-lg cursor-move`}
68
+ onMouseDown={handleDragStart}
69
+ >
70
+ <div className="flex items-center gap-2 text-xs">
71
+ <div className="w-4 h-4">{feature.icon}</div>
72
+ <span>{feature.name}</span>
73
+ </div>
74
+ <div className="flex items-center gap-1">
75
+ <button onClick={() => onMinimize(feature.id)} className="p-1 rounded hover:bg-slate-600"><MinimizeIcon /></button>
76
+ <button onClick={() => onClose(feature.id)} className="p-1 rounded hover:bg-red-500/50"><XMarkIcon className="w-4 h-4"/></button>
77
+ </div>
78
+ </header>
79
+ <main className="flex-1 overflow-auto bg-slate-800/50 rounded-b-lg">
80
+ {FeatureComponent ? (
81
+ <Suspense fallback={<LoadingIndicator/>}>
82
+ <FeatureComponent />
83
+ </Suspense>
84
+ ) : (
85
+ <div className="p-4 text-red-400">Error: Component not found for {feature.name}</div>
86
+ )}
87
+ </main>
88
+ </div>
89
+ );
90
+ };