feat: add WindowContext with useReducer
This commit is contained in:
166
src/context/WindowContext.tsx
Normal file
166
src/context/WindowContext.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
||||
import type { OSState, OSAction, WindowState } from '../types';
|
||||
import { APPS } from '../config/apps';
|
||||
|
||||
function initWindows(): Record<string, WindowState> {
|
||||
const map: Record<string, WindowState> = {};
|
||||
for (const app of APPS) {
|
||||
map[app.id] = {
|
||||
id: app.id,
|
||||
isOpen: false,
|
||||
isMinimized: false,
|
||||
isMaximized: false,
|
||||
zIndex: 1,
|
||||
position: { ...app.defaultPosition },
|
||||
size: { ...app.defaultSize },
|
||||
prevPosition: { ...app.defaultPosition },
|
||||
prevSize: { ...app.defaultSize },
|
||||
};
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
const initialState: OSState = {
|
||||
windows: initWindows(),
|
||||
topZ: 10,
|
||||
wallpaper: 0,
|
||||
};
|
||||
|
||||
function reducer(state: OSState, action: OSAction): OSState {
|
||||
switch (action.type) {
|
||||
case 'OPEN': {
|
||||
const newZ = state.topZ + 1;
|
||||
return {
|
||||
...state,
|
||||
topZ: newZ,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: {
|
||||
...state.windows[action.id],
|
||||
isOpen: true,
|
||||
isMinimized: false,
|
||||
zIndex: newZ,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'CLOSE':
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: { ...state.windows[action.id], isOpen: false, isMinimized: false, isMaximized: false },
|
||||
},
|
||||
};
|
||||
case 'MINIMIZE':
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: { ...state.windows[action.id], isMinimized: true },
|
||||
},
|
||||
};
|
||||
case 'MAXIMIZE': {
|
||||
const win = state.windows[action.id];
|
||||
if (win.isMaximized) {
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: {
|
||||
...win,
|
||||
isMaximized: false,
|
||||
position: { ...win.prevPosition },
|
||||
size: { ...win.prevSize },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: {
|
||||
...win,
|
||||
isMaximized: true,
|
||||
prevPosition: { ...win.position },
|
||||
prevSize: { ...win.size },
|
||||
position: { x: 0, y: 28 }, // 28px = menubar height
|
||||
// viewport dims passed in from caller — keeps reducer pure
|
||||
size: { width: action.viewportWidth, height: action.viewportHeight - 28 - 72 },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'FOCUS': {
|
||||
const newZ = state.topZ + 1;
|
||||
return {
|
||||
...state,
|
||||
topZ: newZ,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: { ...state.windows[action.id], zIndex: newZ, isMinimized: false },
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'MOVE':
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: { ...state.windows[action.id], position: { x: action.x, y: action.y } },
|
||||
},
|
||||
};
|
||||
case 'RESIZE':
|
||||
return {
|
||||
...state,
|
||||
windows: {
|
||||
...state.windows,
|
||||
[action.id]: { ...state.windows[action.id], size: { width: action.width, height: action.height } },
|
||||
},
|
||||
};
|
||||
case 'SET_WALLPAPER':
|
||||
return { ...state, wallpaper: action.index };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
interface WindowContextType {
|
||||
state: OSState;
|
||||
openWindow: (id: string) => void;
|
||||
closeWindow: (id: string) => void;
|
||||
minimizeWindow: (id: string) => void;
|
||||
maximizeWindow: (id: string, viewportWidth: number, viewportHeight: number) => void;
|
||||
focusWindow: (id: string) => void;
|
||||
moveWindow: (id: string, x: number, y: number) => void;
|
||||
resizeWindow: (id: string, width: number, height: number) => void;
|
||||
setWallpaper: (index: number) => void;
|
||||
}
|
||||
|
||||
const WindowContext = createContext<WindowContextType | null>(null);
|
||||
|
||||
export function WindowProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const openWindow = useCallback((id: string) => dispatch({ type: 'OPEN', id }), []);
|
||||
const closeWindow = useCallback((id: string) => dispatch({ type: 'CLOSE', id }), []);
|
||||
const minimizeWindow = useCallback((id: string) => dispatch({ type: 'MINIMIZE', id }), []);
|
||||
const maximizeWindow = useCallback((id: string, viewportWidth: number, viewportHeight: number) => dispatch({ type: 'MAXIMIZE', id, viewportWidth, viewportHeight }), []);
|
||||
const focusWindow = useCallback((id: string) => dispatch({ type: 'FOCUS', id }), []);
|
||||
const moveWindow = useCallback((id: string, x: number, y: number) => dispatch({ type: 'MOVE', id, x, y }), []);
|
||||
const resizeWindow = useCallback((id: string, w: number, h: number) => dispatch({ type: 'RESIZE', id, width: w, height: h }), []);
|
||||
const setWallpaper = useCallback((index: number) => dispatch({ type: 'SET_WALLPAPER', index }), []);
|
||||
|
||||
return (
|
||||
<WindowContext.Provider value={{ state, openWindow, closeWindow, minimizeWindow, maximizeWindow, focusWindow, moveWindow, resizeWindow, setWallpaper }}>
|
||||
{children}
|
||||
</WindowContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useOS() {
|
||||
const ctx = useContext(WindowContext);
|
||||
if (!ctx) throw new Error('useOS must be used within WindowProvider');
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user