@owdproject/react-theme-win95 4.0.0-alpha.3 → 4.0.0-alpha.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owdproject/react-theme-win95",
3
- "version": "4.0.0-alpha.3",
3
+ "version": "4.0.0-alpha.4",
4
4
  "description": "Windows 95 theme for Open Web Desktop built with React, @owdproject/core and @owdproject/react.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -21,8 +21,7 @@
21
21
  "./package.json": "./package.json"
22
22
  },
23
23
  "files": [
24
- "src",
25
- "playground"
24
+ "src"
26
25
  ],
27
26
  "scripts": {
28
27
  "dev": "vite --config playground/vite.config.ts",
@@ -6,15 +6,16 @@ import { DesktopRuntimeContext } from './desktop-context'
6
6
  import { DesktopWindow } from './components/DesktopWindow'
7
7
  import { SystemBar } from './components/SystemBar'
8
8
  import { StartMenuPanel } from './components/StartMenuPanel'
9
- import { builtinApps } from './apps'
9
+ import { registerDesktopApps } from './applications'
10
10
  import type { DesktopApp } from './types'
11
11
 
12
12
  const SYSTEMBAR_HEIGHT = 28
13
+ const EMPTY_APPS: DesktopApp[] = []
13
14
 
14
15
  export interface DesktopShellProps {
15
16
  /** A `@owdproject/core` Desktop instance. */
16
17
  desktop: Desktop
17
- /** Applications exposed in the start menu / task bar. */
18
+ /** Menu entries; components/configs register with the engine on mount. */
18
19
  apps?: DesktopApp[]
19
20
  /** Centered desktop watermark text. */
20
21
  watermark?: ReactNode
@@ -25,12 +26,15 @@ export interface DesktopShellProps {
25
26
  * Composes `@owdproject/react-kit-react95` chrome with `@owdproject/react` hooks
26
27
  * and the headless `@owdproject/core` engine.
27
28
  */
28
- export function DesktopShell({ desktop, apps = builtinApps, watermark = 'Open Web Desktop' }: DesktopShellProps) {
29
+ export function DesktopShell({ desktop, apps = EMPTY_APPS, watermark = 'Open Web Desktop' }: DesktopShellProps) {
29
30
  const rootRef = useRef<HTMLDivElement>(null)
30
31
  const [startOpen, setStartOpen] = useState(false)
31
- const idRef = useRef(0)
32
32
  const { windows } = useWindowManager(desktop.windows)
33
33
 
34
+ useEffect(() => {
35
+ registerDesktopApps(desktop, apps)
36
+ }, [desktop, apps])
37
+
34
38
  const getWorkarea = useCallback(() => {
35
39
  const el = rootRef.current
36
40
  const width = el?.clientWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024)
@@ -48,19 +52,7 @@ export function DesktopShell({ desktop, apps = builtinApps, watermark = 'Open We
48
52
  const runtime = useMemo(() => ({ desktop, getWorkarea }), [desktop, getWorkarea])
49
53
 
50
54
  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
- })
55
+ void desktop.applications.launch(app.id, app.entryKey, app.argument)
64
56
  setStartOpen(false)
65
57
  }
66
58
 
@@ -74,7 +66,7 @@ export function DesktopShell({ desktop, apps = builtinApps, watermark = 'Open We
74
66
  <div className="owd-shell__desktop r95-owd-desktop">
75
67
  {watermark ? <div className="owd-shell__watermark">{watermark}</div> : null}
76
68
  {activeWindows.map((win) => (
77
- <DesktopWindow key={win.id} window={win} app={appById.get(win.model)} />
69
+ <DesktopWindow key={win.id} window={win} app={appById.get(win.applicationId ?? '')} />
78
70
  ))}
79
71
  </div>
80
72
 
@@ -0,0 +1,25 @@
1
+ import type { Desktop } from '@owdproject/core'
2
+ import { registerReactApplication } from '@owdproject/react'
3
+ import type { DesktopApp } from './types'
4
+
5
+ /** Register shorthand apps once. Existing registrations remain owned by the consumer. */
6
+ export function registerDesktopApps(desktop: Desktop, apps: readonly DesktopApp[]): void {
7
+ for (const app of apps) {
8
+ if (desktop.applications.get(app.id) || (!app.component && !app.config)) continue
9
+ const { entries = {}, ...config } = app.config ?? {}
10
+ registerReactApplication(desktop, {
11
+ ...config,
12
+ id: app.id,
13
+ title: app.title,
14
+ entries,
15
+ windows: config.windows ?? (app.component ? {
16
+ main: {
17
+ title: app.title,
18
+ size: app.defaultSize ?? { width: 480, height: 340 },
19
+ position: { x: 48, y: 40 },
20
+ component: app.component,
21
+ },
22
+ } : undefined),
23
+ })
24
+ }
25
+ }
@@ -1,6 +1,6 @@
1
1
  import { useReducer, useRef, type CSSProperties } from 'react'
2
2
  import type { ResizeDirection, WindowBounds, WindowInstance, WindowState } from '@owdproject/core'
3
- import { useDesktopDrag, useDesktopResize, useWindowInstance } from '@owdproject/react'
3
+ import { closeReactApplicationWindow, ReactApplicationWindow, useDesktopDrag, useDesktopResize, useWindowInstance } from '@owdproject/react'
4
4
  import { OwdWindow } from '@owdproject/react-kit-react95'
5
5
  import { useDesktopRuntime } from '../desktop-context'
6
6
  import type { DesktopApp } from '../types'
@@ -88,11 +88,10 @@ export function DesktopWindow({ window: instance, app }: DesktopWindowProps) {
88
88
  }
89
89
 
90
90
  const focus = () => desktop.windows.focusWindow(instance.id)
91
- const AppComponent = app?.component
92
91
 
93
92
  return (
94
93
  <OwdWindow
95
- title={app?.title ?? instance.config.title ?? instance.id}
94
+ title={instance.config.title ?? app?.title ?? instance.id}
96
95
  icon={app?.icon}
97
96
  active={state.focused}
98
97
  maximized={maximized}
@@ -104,6 +103,8 @@ export function DesktopWindow({ window: instance, app }: DesktopWindowProps) {
104
103
  onPointerDownCapture={focus}
105
104
  onTitlePointerDown={(event) => {
106
105
  if (event.button !== 0) return
106
+ // Capturing pointers from title-bar controls prevents their click action.
107
+ if (event.target instanceof Element && event.target.closest('button, [role="button"]')) return
107
108
  focus()
108
109
  startDrag(event.nativeEvent)
109
110
  }}
@@ -116,15 +117,15 @@ export function DesktopWindow({ window: instance, app }: DesktopWindowProps) {
116
117
  focus()
117
118
  startResize(event.nativeEvent, direction as ResizeDirection)
118
119
  }}
119
- onClose={() => desktop.windows.closeWindow(instance.id)}
120
+ onClose={() => { void closeReactApplicationWindow(desktop, instance) }}
120
121
  onMinimize={() => instance.minimize()}
121
122
  onToggleMaximize={() => {
122
123
  if (state.maximizable === false) return
123
124
  instance.setLayout(maximized ? 'normal' : 'maximize', getWorkarea())
124
125
  }}
125
126
  >
126
- {AppComponent ? (
127
- <AppComponent window={instance} desktop={desktop} />
127
+ {instance.config.component ? (
128
+ <ReactApplicationWindow window={instance} desktop={desktop} />
128
129
  ) : (
129
130
  <div className="owd-window__empty">No component registered for “{instance.model}”.</div>
130
131
  )}
@@ -46,8 +46,8 @@ export function SystemBar({ apps, onStartClick, startActive }: SystemBarProps) {
46
46
 
47
47
  <Frame className="owd-taskbar__tasks">
48
48
  {windows.map((win) => {
49
- const app = appById.get(win.model)
50
- const title = app?.title ?? win.config.title ?? win.id
49
+ const app = appById.get(win.applicationId ?? '')
50
+ const title = win.config.title ?? app?.title ?? win.id
51
51
  const isActive = focused?.id === win.id
52
52
  return (
53
53
  <Button
package/src/index.ts CHANGED
@@ -11,7 +11,6 @@ export { DesktopWindow, type DesktopWindowProps } from './components/DesktopWind
11
11
  export { SystemBar, type SystemBarProps } from './components/SystemBar'
12
12
  export { StartMenuPanel, type StartMenuPanelProps } from './components/StartMenuPanel'
13
13
  export { useDesktopRuntime, DesktopRuntimeContext, type DesktopRuntime } from './desktop-context'
14
- export { builtinApps, AboutApp, NotepadApp } from './apps'
15
14
  export type { DesktopApp, DesktopAppRenderProps } from './types'
16
15
 
17
16
  // Re-export the kit primitives so consumers get one entry point.
package/src/theme.css CHANGED
@@ -3,6 +3,10 @@
3
3
  * Visual primitives come from @react95/core (via @owdproject/react-kit-react95).
4
4
  */
5
5
 
6
+ /* The theme owns its kit: loading the theme styles also loads the kit's OWD
7
+ window glue, so consumers only import this single stylesheet. */
8
+ @import '@owdproject/react-kit-react95/styles.css';
9
+
6
10
  .owd-shell {
7
11
  position: fixed;
8
12
  inset: 0;
package/src/types.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  import type { ComponentType, ReactElement } from 'react'
2
- import type { Desktop, WindowInstance } from '@owdproject/core'
2
+ import type { ReactApplicationConfig, ReactApplicationWindowProps } from '@owdproject/react'
3
3
 
4
4
  /** Props passed to every application window component. */
5
- export interface DesktopAppRenderProps {
6
- window: WindowInstance
7
- desktop: Desktop
8
- }
5
+ export type DesktopAppRenderProps = ReactApplicationWindowProps
9
6
 
10
7
  /** A React application that can be launched from the start menu / task bar. */
11
8
  export interface DesktopApp {
@@ -14,5 +11,12 @@ export interface DesktopApp {
14
11
  /** React95 icon used in the start menu, window title bar and task bar. */
15
12
  icon?: ReactElement
16
13
  defaultSize?: { width: number; height: number }
17
- component: ComponentType<DesktopAppRenderProps>
14
+ /** Optional shorthand for a single `main` window. Registered apps need no component here. */
15
+ component?: ComponentType<DesktopAppRenderProps>
16
+ /** Engine options, including entries, models, singleton and lifecycle callbacks. */
17
+ config?: Omit<ReactApplicationConfig, 'id' | 'title' | 'entries'> & {
18
+ entries?: ReactApplicationConfig['entries']
19
+ }
20
+ entryKey?: string
21
+ argument?: string
18
22
  }
@@ -1,12 +0,0 @@
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>
@@ -1,30 +0,0 @@
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
- }
@@ -1,14 +0,0 @@
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
- )
@@ -1,15 +0,0 @@
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
- }
@@ -1,43 +0,0 @@
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
- })
@@ -1,27 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
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 }