@liberation-data/desk 0.4.0 → 0.7.0
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 +63 -2
- package/dist/core/desk.d.ts +13 -0
- package/dist/core/desk.d.ts.map +1 -1
- package/dist/core/desk.js +55 -1
- package/dist/core/desk.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/css/apps.css +57 -0
- package/dist/css/controls.css +975 -0
- package/dist/css/conversation.css +232 -0
- package/dist/css/dock.css +272 -0
- package/dist/css/menus.css +204 -0
- package/dist/css/overlays.css +237 -0
- package/dist/css/search.css +149 -0
- package/dist/css/setup.css +284 -0
- package/dist/css/tokens.css +90 -0
- package/dist/css/windows.css +321 -0
- package/dist/desk.css +1080 -1070
- package/dist/react/Desktop.d.ts +9 -2
- package/dist/react/Desktop.d.ts.map +1 -1
- package/dist/react/Desktop.js +85 -23
- package/dist/react/Desktop.js.map +1 -1
- package/dist/react/appFrame.d.ts +9 -2
- package/dist/react/appFrame.d.ts.map +1 -1
- package/dist/react/appFrame.js +26 -5
- package/dist/react/appFrame.js.map +1 -1
- package/llms.txt +12 -1
- package/package.json +4 -3
- package/src/core/desk.ts +67 -1
- package/src/core/index.ts +2 -1
- package/src/core/types.ts +8 -0
- package/src/css/apps.css +56 -0
- package/src/css/controls.css +974 -0
- package/src/css/conversation.css +231 -0
- package/src/css/dock.css +271 -0
- package/src/css/menus.css +203 -0
- package/src/css/overlays.css +236 -0
- package/src/css/search.css +148 -0
- package/src/css/setup.css +283 -0
- package/src/css/tokens.css +89 -0
- package/src/css/windows.css +320 -0
- package/src/react/Desktop.tsx +97 -26
- package/src/react/appFrame.tsx +48 -5
- package/src/desk.css +0 -2820
package/src/react/appFrame.tsx
CHANGED
|
@@ -20,8 +20,27 @@ import { useBus } from './events.js'
|
|
|
20
20
|
/** The protocol version. Every message carries it, so a mismatch is ignored rather than misread. */
|
|
21
21
|
const PROTOCOL = 1
|
|
22
22
|
|
|
23
|
+
/*
|
|
24
|
+
* THE HOST'S LOOK, OFFERED TO THE APP.
|
|
25
|
+
*
|
|
26
|
+
* An app runs in a frame of its own, so by default it wears whatever its author hard-coded: a white
|
|
27
|
+
* page inside a dark desk, a dark one inside a light desk. Switching the desk's theme should carry
|
|
28
|
+
* the app too — the person switched the whole thing, not the chrome around it.
|
|
29
|
+
*
|
|
30
|
+
* So the host offers its palette, and the app takes it or ignores it. `mode` says which way round
|
|
31
|
+
* the page is, for an app that decides its own colours but wants to know; `tokens` are custom
|
|
32
|
+
* properties the bridge sets on the app's own root, so an app that reads them gets the desk's
|
|
33
|
+
* colours with nothing to write.
|
|
34
|
+
*/
|
|
35
|
+
export interface AppTheme {
|
|
36
|
+
readonly mode: 'light' | 'dark'
|
|
37
|
+
/** Custom properties, named as they will be set: `{ '--sb-bg-dark': '#0b0f1a' }`. */
|
|
38
|
+
readonly tokens: Readonly<Record<string, string>>
|
|
39
|
+
}
|
|
40
|
+
|
|
23
41
|
type HostMessage =
|
|
24
|
-
| { readonly desk: 1; readonly kind: 'hello'; readonly window: string | null; readonly listens: readonly string[]; readonly says: readonly string[] }
|
|
42
|
+
| { readonly desk: 1; readonly kind: 'hello'; readonly window: string | null; readonly listens: readonly string[]; readonly says: readonly string[]; readonly theme: AppTheme | null }
|
|
43
|
+
| { readonly desk: 1; readonly kind: 'theme'; readonly theme: AppTheme }
|
|
25
44
|
| { readonly desk: 1; readonly kind: 'event'; readonly topic: string; readonly payload: unknown; readonly from: string | null }
|
|
26
45
|
| { readonly desk: 1; readonly kind: 'drop'; readonly type: string; readonly payload: unknown; readonly from: string | null }
|
|
27
46
|
|
|
@@ -43,6 +62,8 @@ export interface AppFrameProps {
|
|
|
43
62
|
readonly listens?: readonly string[]
|
|
44
63
|
/** Topics the app may say. Anything else it publishes is dropped. Default: none. */
|
|
45
64
|
readonly says?: readonly string[]
|
|
65
|
+
/** The desk's own look, offered to the app: it is applied to the app's root and re-sent on change. */
|
|
66
|
+
readonly theme?: AppTheme
|
|
46
67
|
/** What may be dropped on it. Default: nothing. */
|
|
47
68
|
readonly accepts?: Accepts
|
|
48
69
|
/** Windows the app may ask to open. Default: none. */
|
|
@@ -61,6 +82,7 @@ export function AppFrame({
|
|
|
61
82
|
srcDoc,
|
|
62
83
|
listens = [],
|
|
63
84
|
says = [],
|
|
85
|
+
theme,
|
|
64
86
|
accepts,
|
|
65
87
|
opens = [],
|
|
66
88
|
sandbox = 'allow-scripts allow-forms',
|
|
@@ -79,8 +101,8 @@ export function AppFrame({
|
|
|
79
101
|
frame.current?.contentWindow?.postMessage(message, '*')
|
|
80
102
|
}
|
|
81
103
|
|
|
82
|
-
const latest = useRef({ listens, says, opens, windowId })
|
|
83
|
-
latest.current = { listens, says, opens, windowId }
|
|
104
|
+
const latest = useRef({ listens, says, opens, windowId, theme })
|
|
105
|
+
latest.current = { listens, says, opens, windowId, theme }
|
|
84
106
|
|
|
85
107
|
// What the app says: only from this frame, only on topics it was granted.
|
|
86
108
|
useEffect(() => {
|
|
@@ -89,7 +111,7 @@ export function AppFrame({
|
|
|
89
111
|
const message = event.data
|
|
90
112
|
const granted = latest.current
|
|
91
113
|
if (message.kind === 'ready') {
|
|
92
|
-
post({ desk: PROTOCOL, kind: 'hello', window: granted.windowId, listens: granted.listens, says: granted.says })
|
|
114
|
+
post({ desk: PROTOCOL, kind: 'hello', window: granted.windowId, listens: granted.listens, says: granted.says, theme: granted.theme ?? null })
|
|
93
115
|
} else if (message.kind === 'publish') {
|
|
94
116
|
if (typeof message.topic === 'string' && granted.says.some(pattern => topicMatches(pattern, message.topic))) {
|
|
95
117
|
bus.publish(message.topic, message.payload, { from: granted.windowId })
|
|
@@ -102,6 +124,14 @@ export function AppFrame({
|
|
|
102
124
|
return () => window.removeEventListener('message', onMessage)
|
|
103
125
|
}, [bus, desk])
|
|
104
126
|
|
|
127
|
+
// The desk's look, again, whenever it changes: a person switching theme switches the app too.
|
|
128
|
+
const themeKey = theme ? `${theme.mode}|${Object.entries(theme.tokens).map(([k, v]) => `${k}:${v}`).join(';')}` : ''
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!latest.current.theme) return
|
|
131
|
+
post({ desk: PROTOCOL, kind: 'theme', theme: latest.current.theme })
|
|
132
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
133
|
+
}, [themeKey])
|
|
134
|
+
|
|
105
135
|
// What the app hears: only the topics it was granted, and never its own words back.
|
|
106
136
|
const listenKey = listens.join('|')
|
|
107
137
|
useEffect(() => {
|
|
@@ -154,10 +184,22 @@ export const APP_BRIDGE_SCRIPT = `(() => {
|
|
|
154
184
|
const handlers = new Map();
|
|
155
185
|
const drops = [];
|
|
156
186
|
const send = message => parent.postMessage(Object.assign({ desk: ${PROTOCOL} }, message), '*');
|
|
187
|
+
const applyTheme = theme => {
|
|
188
|
+
if (!theme) return;
|
|
189
|
+
desk.theme = theme;
|
|
190
|
+
const root = document.documentElement;
|
|
191
|
+
root.dataset.deskTheme = theme.mode;
|
|
192
|
+
root.style.colorScheme = theme.mode;
|
|
193
|
+
for (const [name, value] of Object.entries(theme.tokens || {})) root.style.setProperty(name, value);
|
|
194
|
+
themed.forEach(h => h(theme));
|
|
195
|
+
};
|
|
196
|
+
const themed = [];
|
|
157
197
|
const desk = {
|
|
158
198
|
window: null,
|
|
159
199
|
listens: [],
|
|
160
200
|
says: [],
|
|
201
|
+
theme: null,
|
|
202
|
+
onTheme(handler) { themed.push(handler); if (desk.theme) handler(desk.theme); },
|
|
161
203
|
on(topic, handler) { (handlers.get(topic) || handlers.set(topic, []).get(topic)).push(handler); },
|
|
162
204
|
onDrop(handler) { drops.push(handler); },
|
|
163
205
|
publish(topic, payload) { send({ kind: 'publish', topic, payload }); },
|
|
@@ -168,7 +210,8 @@ export const APP_BRIDGE_SCRIPT = `(() => {
|
|
|
168
210
|
addEventListener('message', event => {
|
|
169
211
|
if (event.source !== parent || !event.data || event.data.desk !== ${PROTOCOL}) return;
|
|
170
212
|
const message = event.data;
|
|
171
|
-
if (message.kind === 'hello') { desk.window = message.window; desk.listens = message.listens; desk.says = message.says; }
|
|
213
|
+
if (message.kind === 'hello') { desk.window = message.window; desk.listens = message.listens; desk.says = message.says; applyTheme(message.theme); }
|
|
214
|
+
if (message.kind === 'theme') applyTheme(message.theme);
|
|
172
215
|
if (message.kind === 'event') for (const [pattern, list] of handlers) if (matches(pattern, message.topic)) list.forEach(h => h(message));
|
|
173
216
|
if (message.kind === 'drop') drops.forEach(h => h(message));
|
|
174
217
|
});
|