@owdproject/react-theme-win95 0.0.0-stage → 4.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/package.json +55 -4
- package/playground/index.html +12 -0
- package/playground/src/App.tsx +30 -0
- package/playground/src/main.tsx +14 -0
- package/playground/tsconfig.json +15 -0
- package/playground/vite.config.ts +43 -0
- package/src/DesktopShell.tsx +92 -0
- package/src/apps/About.tsx +27 -0
- package/src/apps/Notepad.tsx +24 -0
- package/src/apps/index.tsx +24 -0
- package/src/components/DesktopWindow.tsx +133 -0
- package/src/components/StartMenuPanel.tsx +32 -0
- package/src/components/SystemBar.tsx +73 -0
- package/src/desktop-context.ts +18 -0
- package/src/index.ts +18 -0
- package/src/theme.css +217 -0
- package/src/types.ts +18 -0
package/README.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @owdproject/react-theme-win95
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Windows 95 theme for **Open Web Desktop**, built with **React** and
|
|
4
|
+
[React95](https://react95.io).
|
|
5
|
+
|
|
6
|
+
- **Engine**: [`@owdproject/core`](https://github.com/owdproject/core) — headless windows/apps/workspaces.
|
|
7
|
+
- **Bridges**: [`@owdproject/react`](https://github.com/owdproject/core) — `useSyncExternalStore` hooks.
|
|
8
|
+
- **UI**: [`@owdproject/react-kit-react95`](../owd-kit-react95) — [React95](https://react95.io) `@react95/core` integration.
|
|
9
|
+
|
|
10
|
+
This is a **minimal vertical slice**: a working desktop shell (background, draggable/resizable
|
|
11
|
+
React95 windows, task bar, start menu) plus two demo apps. It proves the whole React chain works
|
|
12
|
+
end-to-end on top of the OWD engine.
|
|
13
|
+
|
|
14
|
+
## Run it
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# from the repo root (client/)
|
|
18
|
+
pnpm install
|
|
19
|
+
pnpm --filter @owdproject/react-theme-win95 dev
|
|
20
|
+
# open http://localhost:5173
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use it
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { createDesktop } from '@owdproject/core'
|
|
27
|
+
import { DesktopShell } from '@owdproject/react-theme-win95'
|
|
28
|
+
import '@owdproject/react-kit-react95/styles.css'
|
|
29
|
+
import '@owdproject/react-theme-win95/styles.css'
|
|
30
|
+
|
|
31
|
+
const desktop = createDesktop()
|
|
32
|
+
|
|
33
|
+
export default function App() {
|
|
34
|
+
return <DesktopShell desktop={desktop} watermark="React win95" />
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Custom apps
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import type { DesktopApp } from '@owdproject/react-theme-win95'
|
|
42
|
+
import { Notepad } from '@react95/icons'
|
|
43
|
+
|
|
44
|
+
const apps: DesktopApp[] = [
|
|
45
|
+
{
|
|
46
|
+
id: 'hello',
|
|
47
|
+
title: 'Hello',
|
|
48
|
+
icon: <Notepad variant="16x16_4" />,
|
|
49
|
+
defaultSize: { width: 420, height: 300 },
|
|
50
|
+
component: ({ window, desktop }) => (
|
|
51
|
+
<div style={{ padding: 8 }}>
|
|
52
|
+
<p>Window {window.id}</p>
|
|
53
|
+
<button onClick={() => desktop.windows.closeWindow(window.id)}>Close</button>
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Architecture
|
|
61
|
+
|
|
62
|
+
| File | Role |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `src/DesktopShell.tsx` | Root shell: workarea, background, windows, start menu, task bar. |
|
|
65
|
+
| `src/components/DesktopWindow.tsx` | One window. Reads state from `useWindowInstance`, moves/resizes via `useDesktopDrag` / `useDesktopResize`. Commits geometry back to the core on release. |
|
|
66
|
+
| `src/components/SystemBar.tsx` | Task bar (React95 `Frame` + `Button`), driven by `useWindowManager`. |
|
|
67
|
+
| `src/components/StartMenuPanel.tsx` | Start menu (React95 `List`). |
|
|
68
|
+
| `src/apps/*` | Demo apps (`About`, `Notepad`). |
|
|
69
|
+
| `src/theme.css` | Layout + app styles (visual primitives come from the kit). |
|
|
70
|
+
| `playground/` | Vite + React playground app. |
|
|
71
|
+
|
|
72
|
+
`@owdproject/core` is the single source of truth for window geometry and focus; React95 only
|
|
73
|
+
renders the chrome, and `@owdproject/react` bridges the immutable core snapshots to React.
|
|
74
|
+
|
|
75
|
+
> ℹ️ The playground Vite config includes a `build.rollupOptions.treeshake.moduleSideEffects`
|
|
76
|
+
> rule for `@react95/*` (they ship no `sideEffects: false`), otherwise all 1000+ React95 icons
|
|
77
|
+
> get bundled. Copy it into your own app's Vite config.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owdproject/react-theme-win95",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "4.0.0-alpha.3",
|
|
4
|
+
"description": "Windows 95 theme for Open Web Desktop built with React, @owdproject/core and @owdproject/react.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Diego Villa",
|
|
8
|
+
"url": "https://github.com/dxlliv"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./src/index.ts",
|
|
12
|
+
"module": "./src/index.ts",
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"import": "./src/index.ts",
|
|
18
|
+
"default": "./src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"./styles.css": "./src/theme.css",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"playground"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "vite --config playground/vite.config.ts",
|
|
29
|
+
"build": "vite build --config playground/vite.config.ts",
|
|
30
|
+
"preview": "vite preview --config playground/vite.config.ts",
|
|
31
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@owdproject/core": "^4.0.0-alpha.3",
|
|
35
|
+
"@owdproject/react-kit-react95": "^4.0.0-alpha.3",
|
|
36
|
+
"@owdproject/react": "^4.0.0-alpha.3",
|
|
37
|
+
"@react95/icons": "^2.8.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=18.0.0",
|
|
41
|
+
"react-dom": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/react": "^19.0.0",
|
|
45
|
+
"@types/react-dom": "^19.0.0",
|
|
46
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
47
|
+
"react": "^19.0.0",
|
|
48
|
+
"react-dom": "^19.0.0",
|
|
49
|
+
"typescript": "~6.0.3",
|
|
50
|
+
"vite": "^6.0.0"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"tag": "alpha",
|
|
55
|
+
"registry": "https://registry.npmjs.org/"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Open Web Desktop — React win95 theme</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { createDesktop, type Desktop } from '@owdproject/core'
|
|
3
|
+
import { DesktopShell } from '../../src'
|
|
4
|
+
|
|
5
|
+
export function App() {
|
|
6
|
+
const desktopRef = useRef<Desktop | null>(null)
|
|
7
|
+
if (!desktopRef.current) {
|
|
8
|
+
desktopRef.current = createDesktop({
|
|
9
|
+
workspaces: ['workspace-1'],
|
|
10
|
+
workarea: { x: 0, y: 0, width: 0, height: 0 },
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
const desktop = desktopRef.current
|
|
14
|
+
|
|
15
|
+
// Open a welcome window on first mount.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
desktop.windows.createWindow({
|
|
18
|
+
id: 'about-welcome',
|
|
19
|
+
model: 'about',
|
|
20
|
+
applicationId: 'about',
|
|
21
|
+
config: {
|
|
22
|
+
title: 'About Open Web Desktop',
|
|
23
|
+
size: { width: 380, height: 240 },
|
|
24
|
+
position: { x: 72, y: 56 },
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
}, [desktop])
|
|
28
|
+
|
|
29
|
+
return <DesktopShell desktop={desktop} watermark="React win95" />
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import '@owdproject/react-kit-react95/styles.css'
|
|
4
|
+
import '../../src/theme.css'
|
|
5
|
+
import { App } from './App'
|
|
6
|
+
|
|
7
|
+
const container = document.getElementById('root')
|
|
8
|
+
if (!container) throw new Error('#root not found')
|
|
9
|
+
|
|
10
|
+
createRoot(container).render(
|
|
11
|
+
<StrictMode>
|
|
12
|
+
<App />
|
|
13
|
+
</StrictMode>,
|
|
14
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"types": ["vite/client"],
|
|
12
|
+
"noEmit": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*", "vite.config.ts"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from 'node:url'
|
|
2
|
+
import { defineConfig } from 'vite'
|
|
3
|
+
import react from '@vitejs/plugin-react'
|
|
4
|
+
|
|
5
|
+
const here = fileURLToPath(new URL('.', import.meta.url))
|
|
6
|
+
// pnpm workspace root (client/)
|
|
7
|
+
const workspaceRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
root: here,
|
|
11
|
+
plugins: [react()],
|
|
12
|
+
build: {
|
|
13
|
+
outDir: '../dist',
|
|
14
|
+
emptyOutDir: true,
|
|
15
|
+
rollupOptions: {
|
|
16
|
+
treeshake: {
|
|
17
|
+
// @react95/* ships no `sideEffects: false`, so Rollup otherwise keeps
|
|
18
|
+
// every icon/component module (and its CSS). Prune pure-JS modules but
|
|
19
|
+
// keep `.css`/`.css.mjs` so component styles still load.
|
|
20
|
+
moduleSideEffects(id) {
|
|
21
|
+
if (id.includes('@react95/')) return id.includes('.css')
|
|
22
|
+
return true
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
server: {
|
|
28
|
+
port: 5173,
|
|
29
|
+
fs: {
|
|
30
|
+
// Allow serving sources of the linked workspace packages.
|
|
31
|
+
allow: [workspaceRoot],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
optimizeDeps: {
|
|
35
|
+
// Workspace packages are consumed as raw TypeScript sources.
|
|
36
|
+
exclude: [
|
|
37
|
+
'@owdproject/core',
|
|
38
|
+
'@owdproject/react',
|
|
39
|
+
'@owdproject/react-kit-react95',
|
|
40
|
+
'@owdproject/react-theme-win95',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
|
2
|
+
import type { Desktop } from '@owdproject/core'
|
|
3
|
+
import { useWindowManager } from '@owdproject/react'
|
|
4
|
+
import { React95Provider } from '@owdproject/react-kit-react95'
|
|
5
|
+
import { DesktopRuntimeContext } from './desktop-context'
|
|
6
|
+
import { DesktopWindow } from './components/DesktopWindow'
|
|
7
|
+
import { SystemBar } from './components/SystemBar'
|
|
8
|
+
import { StartMenuPanel } from './components/StartMenuPanel'
|
|
9
|
+
import { builtinApps } from './apps'
|
|
10
|
+
import type { DesktopApp } from './types'
|
|
11
|
+
|
|
12
|
+
const SYSTEMBAR_HEIGHT = 28
|
|
13
|
+
|
|
14
|
+
export interface DesktopShellProps {
|
|
15
|
+
/** A `@owdproject/core` Desktop instance. */
|
|
16
|
+
desktop: Desktop
|
|
17
|
+
/** Applications exposed in the start menu / task bar. */
|
|
18
|
+
apps?: DesktopApp[]
|
|
19
|
+
/** Centered desktop watermark text. */
|
|
20
|
+
watermark?: ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Root Windows 95 desktop shell for React.
|
|
25
|
+
* Composes `@owdproject/react-kit-react95` chrome with `@owdproject/react` hooks
|
|
26
|
+
* and the headless `@owdproject/core` engine.
|
|
27
|
+
*/
|
|
28
|
+
export function DesktopShell({ desktop, apps = builtinApps, watermark = 'Open Web Desktop' }: DesktopShellProps) {
|
|
29
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
30
|
+
const [startOpen, setStartOpen] = useState(false)
|
|
31
|
+
const idRef = useRef(0)
|
|
32
|
+
const { windows } = useWindowManager(desktop.windows)
|
|
33
|
+
|
|
34
|
+
const getWorkarea = useCallback(() => {
|
|
35
|
+
const el = rootRef.current
|
|
36
|
+
const width = el?.clientWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024)
|
|
37
|
+
const height = (el?.clientHeight ?? (typeof window !== 'undefined' ? window.innerHeight : 768)) - SYSTEMBAR_HEIGHT
|
|
38
|
+
return { x: 0, y: 0, width, height }
|
|
39
|
+
}, [])
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const sync = () => desktop.workspaces.setWorkarea(getWorkarea())
|
|
43
|
+
sync()
|
|
44
|
+
window.addEventListener('resize', sync)
|
|
45
|
+
return () => window.removeEventListener('resize', sync)
|
|
46
|
+
}, [desktop, getWorkarea])
|
|
47
|
+
|
|
48
|
+
const runtime = useMemo(() => ({ desktop, getWorkarea }), [desktop, getWorkarea])
|
|
49
|
+
|
|
50
|
+
const launch = (app: DesktopApp) => {
|
|
51
|
+
idRef.current += 1
|
|
52
|
+
const count = windows.length
|
|
53
|
+
const size = app.defaultSize ?? { width: 480, height: 340 }
|
|
54
|
+
desktop.windows.createWindow({
|
|
55
|
+
id: `${app.id}-${idRef.current}`,
|
|
56
|
+
model: app.id,
|
|
57
|
+
applicationId: app.id,
|
|
58
|
+
config: {
|
|
59
|
+
title: app.title,
|
|
60
|
+
size,
|
|
61
|
+
position: { x: 48 + (count % 7) * 26, y: 40 + (count % 7) * 26 },
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
setStartOpen(false)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const activeWindows = windows.filter((win) => win.state.active !== false)
|
|
68
|
+
const appById = new Map(apps.map((app) => [app.id, app]))
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<DesktopRuntimeContext.Provider value={runtime}>
|
|
72
|
+
<React95Provider>
|
|
73
|
+
<div ref={rootRef} className="owd-shell" onPointerDown={() => setStartOpen(false)}>
|
|
74
|
+
<div className="owd-shell__desktop r95-owd-desktop">
|
|
75
|
+
{watermark ? <div className="owd-shell__watermark">{watermark}</div> : null}
|
|
76
|
+
{activeWindows.map((win) => (
|
|
77
|
+
<DesktopWindow key={win.id} window={win} app={appById.get(win.model)} />
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{startOpen ? (
|
|
82
|
+
<div className="owd-shell__startmenu" onPointerDown={(event) => event.stopPropagation()}>
|
|
83
|
+
<StartMenuPanel apps={apps} onLaunch={launch} />
|
|
84
|
+
</div>
|
|
85
|
+
) : null}
|
|
86
|
+
|
|
87
|
+
<SystemBar apps={apps} startActive={startOpen} onStartClick={() => setStartOpen((open) => !open)} />
|
|
88
|
+
</div>
|
|
89
|
+
</React95Provider>
|
|
90
|
+
</DesktopRuntimeContext.Provider>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Computer } from '@react95/icons'
|
|
2
|
+
import { Button } from '@owdproject/react-kit-react95'
|
|
3
|
+
import type { DesktopAppRenderProps } from '../types'
|
|
4
|
+
|
|
5
|
+
export function AboutApp({ window, desktop }: DesktopAppRenderProps) {
|
|
6
|
+
return (
|
|
7
|
+
<div className="owd-about">
|
|
8
|
+
<div className="owd-about__banner">
|
|
9
|
+
<Computer variant="32x32_4" />
|
|
10
|
+
<div>
|
|
11
|
+
<strong>Open Web Desktop</strong>
|
|
12
|
+
<div className="owd-about__small">React theme — win95 (React95)</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<hr className="owd-about__divider" />
|
|
16
|
+
<p className="owd-about__text">
|
|
17
|
+
Running on <code>@owdproject/core</code> + <code>@owdproject/react</code> +{' '}
|
|
18
|
+
<code>@react95/core</code>
|
|
19
|
+
<br />
|
|
20
|
+
Window <code>{window.id}</code> · model <code>{window.model}</code>
|
|
21
|
+
</p>
|
|
22
|
+
<div className="owd-about__actions">
|
|
23
|
+
<Button onClick={() => desktop.windows.closeWindow(window.id)}>OK</Button>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import type { DesktopAppRenderProps } from '../types'
|
|
3
|
+
|
|
4
|
+
export function NotepadApp(_props: DesktopAppRenderProps) {
|
|
5
|
+
const [text, setText] = useState(
|
|
6
|
+
'Hello from a React window.\r\n\r\nDrag the title bar to move me, or grab an edge to resize.',
|
|
7
|
+
)
|
|
8
|
+
return (
|
|
9
|
+
<div className="owd-notepad">
|
|
10
|
+
<div className="owd-notepad__menu">
|
|
11
|
+
<span>File</span>
|
|
12
|
+
<span>Edit</span>
|
|
13
|
+
<span>Search</span>
|
|
14
|
+
<span>Help</span>
|
|
15
|
+
</div>
|
|
16
|
+
<textarea
|
|
17
|
+
className="owd-notepad__area"
|
|
18
|
+
value={text}
|
|
19
|
+
spellCheck={false}
|
|
20
|
+
onChange={(event) => setText(event.target.value)}
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DesktopApp } from '../types'
|
|
2
|
+
import { InfoBubble, Notepad } from '@react95/icons'
|
|
3
|
+
import { AboutApp } from './About'
|
|
4
|
+
import { NotepadApp } from './Notepad'
|
|
5
|
+
|
|
6
|
+
/** Built-in apps shipped with the win95 React theme. */
|
|
7
|
+
export const builtinApps: DesktopApp[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 'about',
|
|
10
|
+
title: 'About Open Web Desktop',
|
|
11
|
+
icon: <InfoBubble variant="32x32_4" />,
|
|
12
|
+
defaultSize: { width: 380, height: 240 },
|
|
13
|
+
component: AboutApp,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'notepad',
|
|
17
|
+
title: 'Untitled - Notepad',
|
|
18
|
+
icon: <Notepad variant="16x16_4" />,
|
|
19
|
+
defaultSize: { width: 480, height: 340 },
|
|
20
|
+
component: NotepadApp,
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export { AboutApp, NotepadApp }
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { useReducer, useRef, type CSSProperties } from 'react'
|
|
2
|
+
import type { ResizeDirection, WindowBounds, WindowInstance, WindowState } from '@owdproject/core'
|
|
3
|
+
import { useDesktopDrag, useDesktopResize, useWindowInstance } from '@owdproject/react'
|
|
4
|
+
import { OwdWindow } from '@owdproject/react-kit-react95'
|
|
5
|
+
import { useDesktopRuntime } from '../desktop-context'
|
|
6
|
+
import type { DesktopApp } from '../types'
|
|
7
|
+
|
|
8
|
+
const FALLBACK_SIZE = { width: 480, height: 340 }
|
|
9
|
+
|
|
10
|
+
function readBounds(state: WindowState, fallback: { width: number; height: number }): WindowBounds {
|
|
11
|
+
const pos = state.position ?? {}
|
|
12
|
+
const size = state.size ?? {}
|
|
13
|
+
return {
|
|
14
|
+
x: pos.x ?? 0,
|
|
15
|
+
y: pos.y ?? 0,
|
|
16
|
+
width: typeof size.width === 'number' ? size.width : fallback.width,
|
|
17
|
+
height: typeof size.height === 'number' ? size.height : fallback.height,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DesktopWindowProps {
|
|
22
|
+
window: WindowInstance
|
|
23
|
+
app?: DesktopApp
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A single desktop window: reads live state from `@owdproject/react`'s
|
|
28
|
+
* `useWindowInstance`, drives move/resize through `@owdproject/core`'s pointer
|
|
29
|
+
* sessions (`useDesktopDrag` / `useDesktopResize`) and renders React95 window
|
|
30
|
+
* chrome via the kit's `OwdWindow`.
|
|
31
|
+
*
|
|
32
|
+
* While dragging, geometry is kept in local state for smoothness and committed
|
|
33
|
+
* back to the core engine on release.
|
|
34
|
+
*/
|
|
35
|
+
export function DesktopWindow({ window: instance, app }: DesktopWindowProps) {
|
|
36
|
+
const { desktop, getWorkarea } = useDesktopRuntime()
|
|
37
|
+
const { state } = useWindowInstance(instance)
|
|
38
|
+
const fallback = app?.defaultSize ?? FALLBACK_SIZE
|
|
39
|
+
|
|
40
|
+
const liveBounds = useRef<WindowBounds | null>(null)
|
|
41
|
+
const [, forceRender] = useReducer((n: number) => n + 1, 0)
|
|
42
|
+
|
|
43
|
+
const commit = (bounds: WindowBounds) => {
|
|
44
|
+
liveBounds.current = null
|
|
45
|
+
instance.setPosition({ x: bounds.x, y: bounds.y })
|
|
46
|
+
instance.setSize({ width: bounds.width, height: bounds.height })
|
|
47
|
+
forceRender()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { startDrag } = useDesktopDrag({
|
|
51
|
+
getBounds: () => readBounds(instance.getSnapshot(), fallback),
|
|
52
|
+
getWorkarea,
|
|
53
|
+
onMove: (bounds) => {
|
|
54
|
+
liveBounds.current = bounds
|
|
55
|
+
forceRender()
|
|
56
|
+
},
|
|
57
|
+
onEnd: commit,
|
|
58
|
+
onCancel: () => {
|
|
59
|
+
liveBounds.current = null
|
|
60
|
+
forceRender()
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const { startResize } = useDesktopResize({
|
|
65
|
+
getBounds: () => readBounds(instance.getSnapshot(), fallback),
|
|
66
|
+
minWidth: 180,
|
|
67
|
+
minHeight: 120,
|
|
68
|
+
onMove: (bounds) => {
|
|
69
|
+
liveBounds.current = bounds
|
|
70
|
+
forceRender()
|
|
71
|
+
},
|
|
72
|
+
onEnd: commit,
|
|
73
|
+
onCancel: () => {
|
|
74
|
+
liveBounds.current = null
|
|
75
|
+
forceRender()
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const bounds = liveBounds.current ?? readBounds(state, fallback)
|
|
80
|
+
const maximized = state.maximized === true
|
|
81
|
+
|
|
82
|
+
const style: CSSProperties = {
|
|
83
|
+
left: bounds.x,
|
|
84
|
+
top: bounds.y,
|
|
85
|
+
width: bounds.width,
|
|
86
|
+
height: bounds.height,
|
|
87
|
+
zIndex: state.position?.z ?? 0,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const focus = () => desktop.windows.focusWindow(instance.id)
|
|
91
|
+
const AppComponent = app?.component
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<OwdWindow
|
|
95
|
+
title={app?.title ?? instance.config.title ?? instance.id}
|
|
96
|
+
icon={app?.icon}
|
|
97
|
+
active={state.focused}
|
|
98
|
+
maximized={maximized}
|
|
99
|
+
resizable={state.resizable !== false}
|
|
100
|
+
minimizable={state.minimizable !== false}
|
|
101
|
+
maximizable={state.maximizable !== false}
|
|
102
|
+
closable={state.destroyable !== false}
|
|
103
|
+
style={style}
|
|
104
|
+
onPointerDownCapture={focus}
|
|
105
|
+
onTitlePointerDown={(event) => {
|
|
106
|
+
if (event.button !== 0) return
|
|
107
|
+
focus()
|
|
108
|
+
startDrag(event.nativeEvent)
|
|
109
|
+
}}
|
|
110
|
+
onTitleDoubleClick={() => {
|
|
111
|
+
if (state.maximizable === false) return
|
|
112
|
+
instance.setLayout(maximized ? 'normal' : 'maximize', getWorkarea())
|
|
113
|
+
}}
|
|
114
|
+
onResizePointerDown={(event, direction) => {
|
|
115
|
+
if (event.button !== 0) return
|
|
116
|
+
focus()
|
|
117
|
+
startResize(event.nativeEvent, direction as ResizeDirection)
|
|
118
|
+
}}
|
|
119
|
+
onClose={() => desktop.windows.closeWindow(instance.id)}
|
|
120
|
+
onMinimize={() => instance.minimize()}
|
|
121
|
+
onToggleMaximize={() => {
|
|
122
|
+
if (state.maximizable === false) return
|
|
123
|
+
instance.setLayout(maximized ? 'normal' : 'maximize', getWorkarea())
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{AppComponent ? (
|
|
127
|
+
<AppComponent window={instance} desktop={desktop} />
|
|
128
|
+
) : (
|
|
129
|
+
<div className="owd-window__empty">No component registered for “{instance.model}”.</div>
|
|
130
|
+
)}
|
|
131
|
+
</OwdWindow>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { PowerOff } from '@react95/icons'
|
|
2
|
+
import { List } from '@owdproject/react-kit-react95'
|
|
3
|
+
import type { DesktopApp } from '../types'
|
|
4
|
+
|
|
5
|
+
export interface StartMenuPanelProps {
|
|
6
|
+
apps: DesktopApp[]
|
|
7
|
+
onLaunch: (app: DesktopApp) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Start menu built with React95 `List` + `List.Item`. */
|
|
11
|
+
export function StartMenuPanel({ apps, onLaunch }: StartMenuPanelProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="owd-startmenu">
|
|
14
|
+
<div className="owd-startmenu__rail">
|
|
15
|
+
<span>
|
|
16
|
+
Open Web <strong>Desktop</strong>
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
<List className="owd-startmenu__list">
|
|
20
|
+
{apps.map((app) => (
|
|
21
|
+
<List.Item key={app.id} icon={app.icon} onClick={() => onLaunch(app)}>
|
|
22
|
+
{app.title}
|
|
23
|
+
</List.Item>
|
|
24
|
+
))}
|
|
25
|
+
<List.Divider />
|
|
26
|
+
<List.Item icon={<PowerOff variant="16x16_4" />} disabled>
|
|
27
|
+
Shut Down…
|
|
28
|
+
</List.Item>
|
|
29
|
+
</List>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { Logo } from '@react95/icons'
|
|
3
|
+
import { Button, Frame } from '@owdproject/react-kit-react95'
|
|
4
|
+
import { useWindowManager } from '@owdproject/react'
|
|
5
|
+
import { useDesktopRuntime } from '../desktop-context'
|
|
6
|
+
import type { DesktopApp } from '../types'
|
|
7
|
+
|
|
8
|
+
function useClock() {
|
|
9
|
+
const [now, setNow] = useState(() => new Date())
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const id = setInterval(() => setNow(new Date()), 10_000)
|
|
12
|
+
return () => clearInterval(id)
|
|
13
|
+
}, [])
|
|
14
|
+
return now
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SystemBarProps {
|
|
18
|
+
apps: DesktopApp[]
|
|
19
|
+
onStartClick: () => void
|
|
20
|
+
startActive: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Bottom system bar built from React95 `Frame` + `Button` primitives.
|
|
25
|
+
* Unlike React95's own `TaskBar`, it is driven by the OWD window manager
|
|
26
|
+
* (`useWindowManager`), so it lists real `@owdproject/core` windows.
|
|
27
|
+
*/
|
|
28
|
+
export function SystemBar({ apps, onStartClick, startActive }: SystemBarProps) {
|
|
29
|
+
const { desktop } = useDesktopRuntime()
|
|
30
|
+
const { windows, focused } = useWindowManager(desktop.windows)
|
|
31
|
+
const now = useClock()
|
|
32
|
+
|
|
33
|
+
const appById = new Map(apps.map((app) => [app.id, app]))
|
|
34
|
+
const clock = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Frame className="owd-taskbar">
|
|
38
|
+
<Button
|
|
39
|
+
className={`owd-taskbar__start${startActive ? ' is-active' : ''}`}
|
|
40
|
+
onClick={onStartClick}
|
|
41
|
+
aria-pressed={startActive}
|
|
42
|
+
>
|
|
43
|
+
<Logo variant="16x16_4" />
|
|
44
|
+
<span className="owd-taskbar__start-label">Start</span>
|
|
45
|
+
</Button>
|
|
46
|
+
|
|
47
|
+
<Frame className="owd-taskbar__tasks">
|
|
48
|
+
{windows.map((win) => {
|
|
49
|
+
const app = appById.get(win.model)
|
|
50
|
+
const title = app?.title ?? win.config.title ?? win.id
|
|
51
|
+
const isActive = focused?.id === win.id
|
|
52
|
+
return (
|
|
53
|
+
<Button
|
|
54
|
+
key={win.id}
|
|
55
|
+
className={`owd-task${isActive ? ' is-active' : ''}`}
|
|
56
|
+
title={title}
|
|
57
|
+
onClick={() => {
|
|
58
|
+
if (win.state.active === false) win.unminimize()
|
|
59
|
+
desktop.windows.focusWindow(win.id)
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<span className="owd-task__label">{title}</span>
|
|
63
|
+
</Button>
|
|
64
|
+
)
|
|
65
|
+
})}
|
|
66
|
+
</Frame>
|
|
67
|
+
|
|
68
|
+
<Frame className="owd-taskbar__tray">
|
|
69
|
+
<span className="owd-taskbar__clock">{clock}</span>
|
|
70
|
+
</Frame>
|
|
71
|
+
</Frame>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import type { Desktop, DesktopWorkareaRect } from '@owdproject/core'
|
|
3
|
+
|
|
4
|
+
export interface DesktopRuntime {
|
|
5
|
+
desktop: Desktop
|
|
6
|
+
/** Current usable desktop area (excluding the system bar), in shell coordinates. */
|
|
7
|
+
getWorkarea: () => DesktopWorkareaRect
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DesktopRuntimeContext = createContext<DesktopRuntime | null>(null)
|
|
11
|
+
|
|
12
|
+
export function useDesktopRuntime(): DesktopRuntime {
|
|
13
|
+
const runtime = useContext(DesktopRuntimeContext)
|
|
14
|
+
if (!runtime) {
|
|
15
|
+
throw new Error('[owd-react-theme-win95] useDesktopRuntime() must be used inside <DesktopShell>.')
|
|
16
|
+
}
|
|
17
|
+
return runtime
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @owdproject/react-theme-win95
|
|
3
|
+
* Windows 95 Open Web Desktop theme for React.
|
|
4
|
+
*
|
|
5
|
+
* Built on the headless `@owdproject/core` engine, the `@owdproject/react`
|
|
6
|
+
* hooks and the `@owdproject/react-kit-react95` visual primitives.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { DesktopShell, type DesktopShellProps } from './DesktopShell'
|
|
10
|
+
export { DesktopWindow, type DesktopWindowProps } from './components/DesktopWindow'
|
|
11
|
+
export { SystemBar, type SystemBarProps } from './components/SystemBar'
|
|
12
|
+
export { StartMenuPanel, type StartMenuPanelProps } from './components/StartMenuPanel'
|
|
13
|
+
export { useDesktopRuntime, DesktopRuntimeContext, type DesktopRuntime } from './desktop-context'
|
|
14
|
+
export { builtinApps, AboutApp, NotepadApp } from './apps'
|
|
15
|
+
export type { DesktopApp, DesktopAppRenderProps } from './types'
|
|
16
|
+
|
|
17
|
+
// Re-export the kit primitives so consumers get one entry point.
|
|
18
|
+
export * from '@owdproject/react-kit-react95'
|
package/src/theme.css
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @owdproject/react-theme-win95 — theme layout & app styles.
|
|
3
|
+
* Visual primitives come from @react95/core (via @owdproject/react-kit-react95).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.owd-shell {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: 0;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.owd-shell__desktop {
|
|
13
|
+
position: absolute;
|
|
14
|
+
inset: 0 0 28px 0;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.owd-shell__watermark {
|
|
19
|
+
position: absolute;
|
|
20
|
+
right: 16px;
|
|
21
|
+
bottom: 16px;
|
|
22
|
+
color: rgba(255, 255, 255, 0.6);
|
|
23
|
+
font-weight: bold;
|
|
24
|
+
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.35);
|
|
25
|
+
pointer-events: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.owd-shell__startmenu {
|
|
29
|
+
position: absolute;
|
|
30
|
+
left: 2px;
|
|
31
|
+
bottom: 28px;
|
|
32
|
+
z-index: 100000;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ----------------------------------------------------------- System bar */
|
|
36
|
+
|
|
37
|
+
.owd-taskbar {
|
|
38
|
+
position: fixed;
|
|
39
|
+
left: 0;
|
|
40
|
+
right: 0;
|
|
41
|
+
bottom: 0;
|
|
42
|
+
height: 28px;
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 4px;
|
|
46
|
+
padding: 2px;
|
|
47
|
+
z-index: 90000;
|
|
48
|
+
background: var(--r95-color-material, #c3c7cb);
|
|
49
|
+
box-shadow:
|
|
50
|
+
inset 1px 1px 0 var(--r95-color-borderLightest, #fff),
|
|
51
|
+
inset -1px -1px 0 var(--r95-color-borderDark, #868a8e);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.owd-taskbar__start {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 4px;
|
|
58
|
+
font-weight: bold;
|
|
59
|
+
flex: 0 0 auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.owd-taskbar__start.is-active {
|
|
63
|
+
padding-top: 2px;
|
|
64
|
+
padding-left: 2px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.owd-taskbar__start-label {
|
|
68
|
+
line-height: 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.owd-taskbar__tasks {
|
|
72
|
+
flex: 1 1 auto;
|
|
73
|
+
min-width: 0;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 3px;
|
|
77
|
+
background: transparent;
|
|
78
|
+
box-shadow: none;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.owd-task {
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
min-width: 110px;
|
|
86
|
+
max-width: 168px;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.owd-task.is-active {
|
|
91
|
+
box-shadow:
|
|
92
|
+
inset 1px 1px 0 var(--r95-color-borderDark, #868a8e),
|
|
93
|
+
inset -1px -1px 0 var(--r95-color-borderLightest, #fff);
|
|
94
|
+
background-color: var(--r95-color-borderLight, #d2d2d2);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.owd-task__label {
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
text-overflow: ellipsis;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.owd-taskbar__tray {
|
|
104
|
+
flex: 0 0 auto;
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
padding: 0 6px;
|
|
108
|
+
background: transparent;
|
|
109
|
+
box-shadow: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.owd-taskbar__clock {
|
|
113
|
+
padding: 0 2px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ------------------------------------------------------------ Start menu */
|
|
117
|
+
|
|
118
|
+
.owd-startmenu {
|
|
119
|
+
display: flex;
|
|
120
|
+
background: var(--r95-color-material, #c3c7cb);
|
|
121
|
+
border: 2px solid;
|
|
122
|
+
border-color: var(--r95-color-borderLight) var(--r95-color-borderDarkest)
|
|
123
|
+
var(--r95-color-borderDarkest) var(--r95-color-borderLight);
|
|
124
|
+
box-shadow: inset 1px 1px 0 var(--r95-color-borderLightest);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.owd-startmenu__rail {
|
|
128
|
+
width: 26px;
|
|
129
|
+
background: linear-gradient(180deg, #000e7a, #1084d0);
|
|
130
|
+
color: #fff;
|
|
131
|
+
writing-mode: vertical-rl;
|
|
132
|
+
transform: rotate(180deg);
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: flex-end;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
padding: 8px 0;
|
|
137
|
+
font-weight: bold;
|
|
138
|
+
letter-spacing: 1px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.owd-startmenu__list {
|
|
142
|
+
border: 0;
|
|
143
|
+
box-shadow: none;
|
|
144
|
+
background: transparent;
|
|
145
|
+
min-width: 190px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ------------------------------------------------------- App: About box */
|
|
149
|
+
|
|
150
|
+
.owd-about {
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
gap: 8px;
|
|
154
|
+
height: 100%;
|
|
155
|
+
padding: 4px;
|
|
156
|
+
user-select: text;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.owd-about__banner {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
gap: 10px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.owd-about__small {
|
|
166
|
+
color: #404040;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.owd-about__divider {
|
|
170
|
+
width: 100%;
|
|
171
|
+
border: 0;
|
|
172
|
+
border-top: 1px solid var(--r95-color-borderDark);
|
|
173
|
+
border-bottom: 1px solid var(--r95-color-borderLight);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.owd-about__text {
|
|
177
|
+
margin: 0;
|
|
178
|
+
line-height: 1.5;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.owd-about__actions {
|
|
182
|
+
margin-top: auto;
|
|
183
|
+
display: flex;
|
|
184
|
+
justify-content: flex-end;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* --------------------------------------------------------- App: Notepad */
|
|
188
|
+
|
|
189
|
+
.owd-notepad {
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
height: 100%;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.owd-notepad__menu {
|
|
196
|
+
display: flex;
|
|
197
|
+
gap: 14px;
|
|
198
|
+
padding: 2px 6px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.owd-notepad__area {
|
|
202
|
+
flex: 1 1 auto;
|
|
203
|
+
width: 100%;
|
|
204
|
+
resize: none;
|
|
205
|
+
border: 0;
|
|
206
|
+
outline: none;
|
|
207
|
+
padding: 4px;
|
|
208
|
+
background: #fff;
|
|
209
|
+
font-family: 'Lucida Console', 'Courier New', monospace;
|
|
210
|
+
font-size: 12px;
|
|
211
|
+
line-height: 1.4;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.owd-window__empty {
|
|
215
|
+
padding: 8px;
|
|
216
|
+
color: #404040;
|
|
217
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement } from 'react'
|
|
2
|
+
import type { Desktop, WindowInstance } from '@owdproject/core'
|
|
3
|
+
|
|
4
|
+
/** Props passed to every application window component. */
|
|
5
|
+
export interface DesktopAppRenderProps {
|
|
6
|
+
window: WindowInstance
|
|
7
|
+
desktop: Desktop
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A React application that can be launched from the start menu / task bar. */
|
|
11
|
+
export interface DesktopApp {
|
|
12
|
+
id: string
|
|
13
|
+
title: string
|
|
14
|
+
/** React95 icon used in the start menu, window title bar and task bar. */
|
|
15
|
+
icon?: ReactElement
|
|
16
|
+
defaultSize?: { width: number; height: number }
|
|
17
|
+
component: ComponentType<DesktopAppRenderProps>
|
|
18
|
+
}
|