@domicile-desktop/component-library 0.0.0-alpha-56dd6c187f4e → 0.0.0-alpha-c15ba93d1d2b

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 CHANGED
@@ -52,7 +52,7 @@ Each export is `@domicile-desktop/component-library/<name>`.
52
52
  | `ThemeProvider` | Light/dark theme state. Sets `data-theme` on `<html>`. |
53
53
  | `ThemeSwitch` | Button that flips the theme. |
54
54
  | `useApps` | Hook: names and icons of applications by app id, from their desktop entries. |
55
- | `usePictureUrl` | Hook: a URL an `<img>` can show for a file read through the SDK's `system`. |
55
+ | `usePictureUrl` | Hook: a URL an `<img>` can show for a file read through `domicile.files`. |
56
56
  | `usePortalWallpaper` | Hook: the pictures applications set through the Wallpaper portal, as paths. |
57
57
  | `theme-core`, `theme-source`, `standalone-theme-source` | Theme types and sources. Use the standalone source where there is no compositor, such as Storybook. |
58
58
  | `control-sizes` | `Size` type and `SIZES` array for sized controls. |
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "dependencies": {
3
3
  "@base-ui/react": "^1.8.0",
4
4
  "@cprussin/option-result": "^2.0.1",
5
- "@domicile-desktop/sdk": "0.0.0-alpha-56dd6c187f4e",
6
- "@domicile-desktop/system-apps": "0.0.0-alpha-56dd6c187f4e",
5
+ "@domicile-desktop/sdk": "0.0.0-alpha-c15ba93d1d2b",
6
+ "@domicile-desktop/system-apps": "0.0.0-alpha-c15ba93d1d2b",
7
7
  "@pandacss/dev": "^1.12.1",
8
8
  "@phosphor-icons/react": "^2.1.10"
9
9
  },
@@ -117,5 +117,5 @@
117
117
  "test:unit": "bun test"
118
118
  },
119
119
  "type": "module",
120
- "version": "0.0.0-alpha-56dd6c187f4e"
120
+ "version": "0.0.0-alpha-c15ba93d1d2b"
121
121
  }
@@ -1,4 +1,4 @@
1
- import type { System } from "@domicile-desktop/sdk/system";
1
+ import type { AppsDesktop } from "@domicile-desktop/system-apps/apps-desktop";
2
2
  import type { DescribedApp } from "@domicile-desktop/system-apps/describe-apps";
3
3
  import { AppWindowIcon } from "@phosphor-icons/react/dist/ssr/AppWindow";
4
4
  import { useId, useState } from "react";
@@ -35,7 +35,7 @@ type Props = {
35
35
  opened: string;
36
36
  screen: string | undefined;
37
37
  /** Where the applications' desktop entries are read. */
38
- system: System;
38
+ domicile: AppsDesktop;
39
39
  };
40
40
 
41
41
  /**
@@ -54,10 +54,10 @@ export const AppChooser = ({
54
54
  onChoose,
55
55
  opened,
56
56
  screen,
57
- system,
57
+ domicile,
58
58
  }: Props) => {
59
59
  const listId = useId();
60
- const described = useDescribedChoices(system, choices, contentType);
60
+ const described = useDescribedChoices(domicile, choices, contentType);
61
61
  const [picked, setPicked] = useState<string | undefined>();
62
62
  const apps = described?.apps ?? [];
63
63
  const first =
@@ -3,7 +3,8 @@
3
3
 
4
4
  import type { Result } from "@cprussin/option-result";
5
5
  import { Ok } from "@cprussin/option-result";
6
- import type { System, SystemError } from "@domicile-desktop/sdk/system";
6
+ import type { SystemError } from "@domicile-desktop/sdk/system";
7
+ import type { AppsDesktop } from "@domicile-desktop/system-apps/apps-desktop";
7
8
  import type { DescribedApp } from "@domicile-desktop/system-apps/describe-apps";
8
9
  import { describeApps } from "@domicile-desktop/system-apps/describe-apps";
9
10
  import { defaultApps } from "@domicile-desktop/system-apps/mime-apps";
@@ -20,15 +21,15 @@ export type DescribedChoices = {
20
21
  * when it is `undefined`.
21
22
  */
22
23
  export const describeChoices = async (
23
- system: System,
24
+ domicile: AppsDesktop,
24
25
  choices: readonly string[],
25
26
  contentType: string | undefined,
26
27
  ): Promise<Result<DescribedChoices, SystemError>> => {
27
28
  const [apps, defaults] = await Promise.all([
28
- describeApps(system, choices),
29
+ describeApps(domicile, choices),
29
30
  contentType === undefined
30
31
  ? Ok<string[], SystemError>([])
31
- : defaultApps(system, contentType),
32
+ : defaultApps(domicile, contentType),
32
33
  ]);
33
34
  return apps.andThen((found) =>
34
35
  defaults.map((ids) => ({
@@ -1,4 +1,4 @@
1
- import type { System } from "@domicile-desktop/sdk/system";
1
+ import type { AppsDesktop } from "@domicile-desktop/system-apps/apps-desktop";
2
2
  import { useEffect, useState } from "react";
3
3
 
4
4
  import type { DescribedChoices } from "./app-choices";
@@ -10,7 +10,7 @@ import { describeChoices } from "./app-choices";
10
10
  * the choices have moved past.
11
11
  */
12
12
  export const useDescribedChoices = (
13
- system: System,
13
+ domicile: AppsDesktop,
14
14
  choices: readonly string[],
15
15
  contentType: string | undefined,
16
16
  ): DescribedChoices | undefined => {
@@ -20,7 +20,7 @@ export const useDescribedChoices = (
20
20
 
21
21
  useEffect(() => {
22
22
  let current = true;
23
- describeChoices(system, key === "" ? [] : key.split("\n"), contentType)
23
+ describeChoices(domicile, key === "" ? [] : key.split("\n"), contentType)
24
24
  .then((result) => {
25
25
  result.match({
26
26
  Err: (error) => {
@@ -42,7 +42,7 @@ export const useDescribedChoices = (
42
42
  return () => {
43
43
  current = false;
44
44
  };
45
- }, [system, key, contentType]);
45
+ }, [domicile, key, contentType]);
46
46
 
47
47
  return described;
48
48
  };
@@ -1,7 +1,8 @@
1
- // The folders `FilePicker` browses, and the system calls behind them.
1
+ // The folders `FilePicker` browses, and the desktop's file calls behind them.
2
2
 
3
3
  import type { Result } from "@cprussin/option-result";
4
- import type { System, SystemError } from "@domicile-desktop/sdk/system";
4
+ import type { Files } from "@domicile-desktop/sdk/desktop/files";
5
+ import type { SystemError } from "@domicile-desktop/sdk/system";
5
6
 
6
7
  import { listDirectory } from "./list-directory";
7
8
 
@@ -21,11 +22,17 @@ export type Folders = {
21
22
  watch: (path: string, changed: () => void) => () => void;
22
23
  };
23
24
 
24
- /** {@link Folders} through the compositor's system calls. */
25
- export const systemFolders = (system: System): Folders => ({
26
- create: (path) => system.createDir(path),
27
- list: (path) => listDirectory(system, path),
28
- watch: (path, changed) => watchDirectory(system, path, changed),
25
+ /** The calls {@link desktopFolders} makes. */
26
+ export type FolderFiles = Pick<
27
+ Files,
28
+ "createDir" | "readDir" | "stat" | "watch"
29
+ >;
30
+
31
+ /** {@link Folders} through the desktop's files. */
32
+ export const desktopFolders = (files: FolderFiles): Folders => ({
33
+ create: (path) => files.createDir(path),
34
+ list: (path) => listDirectory(files, path),
35
+ watch: (path, changed) => watchDirectory(files, path, changed),
29
36
  });
30
37
 
31
38
  /**
@@ -34,7 +41,7 @@ export const systemFolders = (system: System): Folders => ({
34
41
  * works, without live updates.
35
42
  */
36
43
  const watchDirectory = (
37
- system: System,
44
+ files: FolderFiles,
38
45
  path: string,
39
46
  changed: () => void,
40
47
  ): (() => void) => {
@@ -42,7 +49,7 @@ const watchDirectory = (
42
49
  stop: undefined,
43
50
  stopped: false,
44
51
  };
45
- follow(system, path, watching, changed).catch((error: unknown) => {
52
+ follow(files, path, watching, changed).catch((error: unknown) => {
46
53
  // biome-ignore lint/suspicious/noConsole: reports a watch that broke
47
54
  console.error("The file picker's directory watch broke", error);
48
55
  });
@@ -56,12 +63,12 @@ type Watching = { stop: (() => void) | undefined; stopped: boolean };
56
63
 
57
64
  /** Starts the watch, then calls `changed` for each item until it ends. */
58
65
  const follow = async (
59
- system: System,
66
+ files: FolderFiles,
60
67
  path: string,
61
68
  watching: Watching,
62
69
  changed: () => void,
63
70
  ): Promise<void> => {
64
- const watch = (await system.watch(path)).match({
71
+ const watch = (await files.watch(path)).match({
65
72
  Err: (error) => {
66
73
  // biome-ignore lint/suspicious/noConsole: reports a watch that could not start
67
74
  console.error("The file picker could not watch a directory", error);
@@ -1,8 +1,12 @@
1
- // A directory's entries for the file picker, read through the system calls.
1
+ // A directory's entries for the file picker, read through the desktop's files.
2
2
 
3
- import type { DirEntry, System } from "@domicile-desktop/sdk/system";
3
+ import type { Files } from "@domicile-desktop/sdk/desktop/files";
4
+ import type { DirEntry } from "@domicile-desktop/sdk/system";
4
5
  import { FileType } from "@domicile-desktop/sdk/system";
5
6
 
7
+ /** The calls a listing makes. */
8
+ export type ListedFiles = Pick<Files, "readDir" | "stat">;
9
+
6
10
  /**
7
11
  * The entries of the directory at absolute `path`, as `FileRequest.list`
8
12
  * wants them: subdirectory names, and links to directories, end in `/`.
@@ -10,30 +14,30 @@ import { FileType } from "@domicile-desktop/sdk/system";
10
14
  * read.
11
15
  */
12
16
  export const listDirectory = async (
13
- system: System,
17
+ files: ListedFiles,
14
18
  path: string,
15
19
  ): Promise<string[]> =>
16
- (await system.readDir(path)).match({
20
+ (await files.readDir(path)).match({
17
21
  Err: (error) => {
18
22
  throw new DOMException(error.message, "NotReadableError");
19
23
  },
20
24
  Ok: (entries) =>
21
- Promise.all(entries.map((entry) => shownName(system, path, entry))),
25
+ Promise.all(entries.map((entry) => shownName(files, path, entry))),
22
26
  });
23
27
 
24
28
  const shownName = async (
25
- system: System,
29
+ files: ListedFiles,
26
30
  directory: string,
27
31
  { fileType, name }: DirEntry,
28
32
  ): Promise<string> =>
29
- (await isDirectory(system, directory, name, fileType)) ? `${name}/` : name;
33
+ (await isDirectory(files, directory, name, fileType)) ? `${name}/` : name;
30
34
 
31
35
  /**
32
36
  * Whether the entry is a directory, following a link. A dangling link counts
33
37
  * as a file: the picker lists it, and picking it fails where it is read.
34
38
  */
35
39
  const isDirectory = async (
36
- system: System,
40
+ files: ListedFiles,
37
41
  directory: string,
38
42
  name: string,
39
43
  fileType: FileType,
@@ -43,7 +47,7 @@ const isDirectory = async (
43
47
  return true;
44
48
  }
45
49
  case FileType.Symlink: {
46
- const target = await system.stat(childOf(directory, name));
50
+ const target = await files.stat(childOf(directory, name));
47
51
  return target.match({
48
52
  Err: () => false,
49
53
  Ok: (stat) => stat.fileType === FileType.Directory,
@@ -1,6 +1,6 @@
1
+ import type { Files } from "@domicile-desktop/sdk/desktop/files";
1
2
  import type { AccountBody, PortalAnswer } from "@domicile-desktop/sdk/portal";
2
3
  import { PortalAnswer as Answer } from "@domicile-desktop/sdk/portal";
3
- import type { System } from "@domicile-desktop/sdk/system";
4
4
  import { css } from "../../styled-system/css";
5
5
  import { hstack } from "../../styled-system/patterns";
6
6
  import { Button } from "../Button/Button";
@@ -13,7 +13,7 @@ type Props = {
13
13
  asker: string;
14
14
  body: AccountBody;
15
15
  /** Reads the user's picture. */
16
- files: Pick<System, "readFile">;
16
+ files: Pick<Files, "read">;
17
17
  screen: string | undefined;
18
18
  };
19
19
 
@@ -3,7 +3,7 @@ import type {
3
3
  PortalAnswer,
4
4
  } from "@domicile-desktop/sdk/portal";
5
5
  import { PortalAnswer as Answer } from "@domicile-desktop/sdk/portal";
6
- import type { System } from "@domicile-desktop/sdk/system";
6
+ import type { AppsDesktop } from "@domicile-desktop/system-apps/apps-desktop";
7
7
  import { AppChooser } from "../AppChooser/AppChooser";
8
8
 
9
9
  type Props = {
@@ -13,7 +13,7 @@ type Props = {
13
13
  body: AppChooserBody;
14
14
  screen: string | undefined;
15
15
  /** Where the applications' desktop entries are read. */
16
- system: System;
16
+ domicile: AppsDesktop;
17
17
  };
18
18
 
19
19
  /** The AppChooser portal's {@link AppChooser}. Dismissing it cancels. */
@@ -22,13 +22,14 @@ export const AppChooserDialog = ({
22
22
  asker,
23
23
  body,
24
24
  screen,
25
- system,
25
+ domicile,
26
26
  }: Props) => (
27
27
  <AppChooser
28
28
  address={body.filename === undefined ? body.uri : undefined}
29
29
  asker={asker}
30
30
  choices={body.choices}
31
31
  contentType={body.contentType}
32
+ domicile={domicile}
32
33
  lastChoice={body.lastChoice}
33
34
  onCancel={() => {
34
35
  answer(Answer.Canceled());
@@ -38,7 +39,6 @@ export const AppChooserDialog = ({
38
39
  }}
39
40
  opened={openedOf(body)}
40
41
  screen={screen}
41
- system={system}
42
42
  />
43
43
  );
44
44
 
@@ -1,28 +1,27 @@
1
1
  import type { CastClaim } from "@domicile-desktop/sdk/cast";
2
2
  import { CastClaimKind } from "@domicile-desktop/sdk/cast";
3
3
  import type {
4
- DomicileBrowserWindow,
5
- DomicileHost,
6
- } from "@domicile-desktop/sdk/domicile-host";
7
- import type { GlobalShortcutsHost } from "@domicile-desktop/sdk/global-shortcuts";
4
+ Browser,
5
+ BrowserWindow,
6
+ } from "@domicile-desktop/sdk/desktop/browser";
7
+ import type { Capture } from "@domicile-desktop/sdk/desktop/capture";
8
+ import type { Dbus } from "@domicile-desktop/sdk/desktop/dbus";
9
+ import type { Files } from "@domicile-desktop/sdk/desktop/files";
10
+ import type { Portals } from "@domicile-desktop/sdk/desktop/portals";
11
+ import type { Processes } from "@domicile-desktop/sdk/desktop/processes";
12
+ import type { GlobalShortcutsDesktop } from "@domicile-desktop/sdk/global-shortcuts";
8
13
  import { fireGlobalShortcuts } from "@domicile-desktop/sdk/global-shortcuts";
9
14
  import type { Capturing, PortalRequest } from "@domicile-desktop/sdk/portal";
10
15
  import {
11
- answerPortalRequest,
12
16
  CapturingKind,
13
17
  CastSourceKind,
14
18
  PortalAnswer,
15
19
  PortalKind,
16
20
  SHELL_APP_ID,
17
- stopCapturing,
18
- watchCapturing,
19
- watchPortalRequests,
20
21
  } from "@domicile-desktop/sdk/portal";
21
- import type { System, SystemHost } from "@domicile-desktop/sdk/system";
22
- import { system } from "@domicile-desktop/sdk/system";
23
22
  import { memo, useEffect, useMemo, useState } from "react";
24
23
  import type { Folders } from "../FilePicker/folders";
25
- import { systemFolders } from "../FilePicker/folders";
24
+ import { desktopFolders } from "../FilePicker/folders";
26
25
  import type { App } from "../useApps/useApps";
27
26
  import { useApps } from "../useApps/useApps";
28
27
  import { AccessDialog } from "./AccessDialog";
@@ -44,12 +43,30 @@ import { UsbDialog } from "./UsbDialog";
44
43
  import { useCastClaims } from "./useCastClaims";
45
44
  import { WallpaperDialog } from "./WallpaperDialog";
46
45
 
46
+ /** What the dialogs read and draw through. */
47
+ export type DialogDesktop = {
48
+ readonly capture: Pick<Capture, "preview">;
49
+ readonly dbus: Pick<Dbus, "call">;
50
+ readonly files: Omit<Files, "search">;
51
+ readonly processes: Pick<Processes, "run" | "spawn">;
52
+ };
53
+
54
+ /** What {@link PortalDialogs} needs of the desktop. */
55
+ export type PortalDialogsDesktop = DialogDesktop &
56
+ GlobalShortcutsDesktop & {
57
+ readonly browser: Pick<Browser, "windows">;
58
+ readonly portals: Pick<
59
+ Portals,
60
+ "answer" | "capturing" | "on" | "requests" | "stopCapturing"
61
+ >;
62
+ };
63
+
47
64
  /** No shell chords; one array, so the default is stable across renders. */
48
65
  const NONE: readonly string[] = [];
49
66
 
50
67
  type Props = {
51
68
  /** The desktop `Shell` is handed. */
52
- host: GlobalShortcutsHost & SystemHost & Pick<DomicileHost, "browserWindows">;
69
+ domicile: PortalDialogsDesktop;
53
70
  /**
54
71
  * The display to show a dialog on when it has no parent window. Needs a
55
72
  * `DisplayProvider`; without it, a dialog is centered on the whole page.
@@ -66,8 +83,6 @@ type Props = {
66
83
  * boxes. See {@link drawnWindows}.
67
84
  */
68
85
  shownWindows?: (() => readonly ShownWindow[]) | undefined;
69
- /** How dialogs read the desktop's files, for tests. */
70
- systemOf?: typeof system | undefined;
71
86
  /**
72
87
  * The display showing the `<app>` with this id, for a dialog modal over
73
88
  * it. `undefined` uses `screen`.
@@ -88,36 +103,41 @@ type Props = {
88
103
  */
89
104
  export const PortalDialogs = memo(
90
105
  ({
91
- host,
106
+ domicile,
92
107
  omitScreenCasts = false,
93
108
  screen,
94
109
  screenOf,
95
110
  shellChords = NONE,
96
111
  shownWindows = drawnOnPage,
97
- systemOf = system,
98
112
  }: Props) => {
99
113
  const [requests, setRequests] = useState<readonly PortalRequest[]>([]);
100
- const files = useMemo(() => systemOf(host), [systemOf, host]);
101
- const folders = useMemo(() => systemFolders(files), [files]);
114
+ const folders = useMemo(() => desktopFolders(domicile.files), [domicile]);
102
115
  const [sessions, setSessions] = useState<readonly Capturing[]>([]);
103
- const apps = useApps(files, [
116
+ const apps = useApps(domicile, [
104
117
  ...requests.flatMap(appIds),
105
118
  ...sessions.map((session) => session.appId),
106
119
  ]);
107
120
 
108
- const claims = useCastClaims(host, requests);
121
+ const { portals } = domicile;
122
+ const claims = useCastClaims(domicile, requests);
109
123
 
110
- useEffect(() => watchPortalRequests(host, setRequests), [host]);
111
- useEffect(() => watchCapturing(host, setSessions), [host]);
112
- useEffect(() => fireGlobalShortcuts(host), [host]);
124
+ useEffect(() => {
125
+ const heard = () => {
126
+ setRequests(portals.requests ?? []);
127
+ setSessions(portals.capturing ?? []);
128
+ };
129
+ heard();
130
+ return portals.on("change", heard);
131
+ }, [portals]);
132
+ useEffect(() => fireGlobalShortcuts(domicile), [domicile]);
113
133
 
114
134
  useEffect(() => {
115
135
  for (const request of requests) {
116
136
  if (request.kind === PortalKind.Unknown) {
117
- answerPortalRequest(host, request.id, PortalAnswer.Refused());
137
+ portals.answer(request.id, PortalAnswer.Refused());
118
138
  }
119
139
  }
120
- }, [host, requests]);
140
+ }, [portals, requests]);
121
141
 
122
142
  return (
123
143
  <>
@@ -132,7 +152,7 @@ export const PortalDialogs = memo(
132
152
  : sessions
133
153
  }
134
154
  stop={(id) => {
135
- stopCapturing(host, id);
155
+ portals.stopCapturing(id);
136
156
  }}
137
157
  />
138
158
  {stacked(
@@ -143,19 +163,19 @@ export const PortalDialogs = memo(
143
163
  ).map((shown) => (
144
164
  <Dialog
145
165
  answer={(answer) => {
146
- answerPortalRequest(host, shown.id, answer);
166
+ portals.answer(shown.id, answer);
147
167
  }}
148
168
  apps={apps}
149
169
  asker={apps(shown.appId).name}
150
- browserWindows={host.browserWindows ?? []}
170
+ browserWindows={domicile.browser.windows ?? []}
151
171
  claim={claims.get(shown.id)}
172
+ domicile={domicile}
152
173
  folders={folders}
153
174
  key={shown.id}
154
175
  request={shown}
155
176
  screen={screenFor(shown, screen, screenOf)}
156
177
  shellChords={shellChords}
157
178
  shownWindows={shownWindows}
158
- system={files}
159
179
  />
160
180
  ))}
161
181
  </>
@@ -178,12 +198,12 @@ const Dialog = ({
178
198
  screen,
179
199
  shellChords,
180
200
  shownWindows,
181
- system: files,
201
+ domicile,
182
202
  }: {
183
203
  answer: (answer: PortalAnswer) => void;
184
204
  apps: (appId: string) => App;
185
205
  asker: string;
186
- browserWindows: readonly DomicileBrowserWindow[];
206
+ browserWindows: readonly BrowserWindow[];
187
207
  /** What a cast to a TV that asked for `request` makes of it. */
188
208
  claim: CastClaim | undefined;
189
209
  folders: Folders;
@@ -191,7 +211,7 @@ const Dialog = ({
191
211
  screen: string | undefined;
192
212
  shellChords: readonly string[];
193
213
  shownWindows: () => readonly ShownWindow[];
194
- system: System;
214
+ domicile: DialogDesktop;
195
215
  }) => {
196
216
  switch (request.kind) {
197
217
  case PortalKind.Access:
@@ -227,8 +247,8 @@ const Dialog = ({
227
247
  answer={answer}
228
248
  asker={asker}
229
249
  body={request.body}
250
+ domicile={domicile}
230
251
  screen={screen}
231
- system={files}
232
252
  />
233
253
  );
234
254
  case PortalKind.FileChooser:
@@ -246,7 +266,7 @@ const Dialog = ({
246
266
  answer={answer}
247
267
  asker={asker}
248
268
  body={request.body}
249
- files={files}
269
+ files={domicile.files}
250
270
  screen={screen}
251
271
  />
252
272
  );
@@ -267,7 +287,7 @@ const Dialog = ({
267
287
  answer={answer}
268
288
  asker={asker}
269
289
  body={request.body}
270
- files={files}
290
+ files={domicile.files}
271
291
  screen={screen}
272
292
  />
273
293
  );
@@ -298,7 +318,7 @@ const Dialog = ({
298
318
  body={request.body}
299
319
  browserWindows={browserWindows}
300
320
  castTo={claim?.kind === CastClaimKind.Pick ? claim.to : undefined}
301
- previewWindow={files.previewWindow}
321
+ previewWindow={domicile.capture.preview}
302
322
  screen={screen}
303
323
  shownWindows={shownWindows}
304
324
  />
@@ -321,7 +341,7 @@ const Dialog = ({
321
341
  asker={request.appId === SHELL_APP_ID ? undefined : asker}
322
342
  body={request.body}
323
343
  browserWindows={browserWindows}
324
- previewWindow={files.previewWindow}
344
+ previewWindow={domicile.capture.preview}
325
345
  screen={screen}
326
346
  shownWindows={shownWindows}
327
347
  />
@@ -1,4 +1,4 @@
1
- import type { DomicileBrowserWindow } from "@domicile-desktop/sdk/domicile-host";
1
+ import type { BrowserWindow } from "@domicile-desktop/sdk/desktop/browser";
2
2
  import type {
3
3
  CastSource,
4
4
  PortalAnswer,
@@ -37,7 +37,7 @@ type Props = {
37
37
  asker: string;
38
38
  body: ScreenCastBody;
39
39
  /** The desktop's browser windows, as the engine lists them. */
40
- browserWindows: readonly DomicileBrowserWindow[];
40
+ browserWindows: readonly BrowserWindow[];
41
41
  /** The TV a cast that asked shows the pick on, titling the dialog for it. */
42
42
  castTo?: string | undefined;
43
43
  screen: string | undefined;
@@ -1,4 +1,4 @@
1
- import type { DomicileBrowserWindow } from "@domicile-desktop/sdk/domicile-host";
1
+ import type { BrowserWindow } from "@domicile-desktop/sdk/desktop/browser";
2
2
  import type {
3
3
  FrozenDesk,
4
4
  PortalAnswer,
@@ -30,7 +30,7 @@ type Props = {
30
30
  asker: string | undefined;
31
31
  body: FrozenDesk;
32
32
  /** The desktop's browser windows, as the engine lists them. */
33
- browserWindows: readonly DomicileBrowserWindow[];
33
+ browserWindows: readonly BrowserWindow[];
34
34
  screen: string | undefined;
35
35
  /** Draws a window that is not on screen. */
36
36
  previewWindow: DrawPreview;
@@ -1,9 +1,9 @@
1
+ import type { Files } from "@domicile-desktop/sdk/desktop/files";
1
2
  import type { PortalAnswer, WallpaperBody } from "@domicile-desktop/sdk/portal";
2
3
  import {
3
4
  PortalAnswer as Answer,
4
5
  WallpaperTarget,
5
6
  } from "@domicile-desktop/sdk/portal";
6
- import type { System } from "@domicile-desktop/sdk/system";
7
7
  import { css } from "../../styled-system/css";
8
8
  import { Button } from "../Button/Button";
9
9
  import { ModalDialog } from "../ModalDialog/ModalDialog";
@@ -14,7 +14,7 @@ type Props = {
14
14
  /** Who asks, as the dialog names them. */
15
15
  asker: string;
16
16
  body: WallpaperBody;
17
- files: Pick<System, "readFile">;
17
+ files: Pick<Files, "read">;
18
18
  screen: string | undefined;
19
19
  };
20
20
 
@@ -1,7 +1,7 @@
1
1
  import type { CastClaim } from "@domicile-desktop/sdk/cast";
2
2
  import { CastClaimKind, claimCastRequest } from "@domicile-desktop/sdk/cast";
3
- import type { PortalHost, PortalRequest } from "@domicile-desktop/sdk/portal";
4
- import { answerPortalRequest } from "@domicile-desktop/sdk/portal";
3
+ import type { Portals } from "@domicile-desktop/sdk/desktop/portals";
4
+ import type { PortalRequest } from "@domicile-desktop/sdk/portal";
5
5
  import { useLayoutEffect, useRef, useState } from "react";
6
6
 
7
7
  /**
@@ -10,7 +10,7 @@ import { useLayoutEffect, useRef, useState } from "react";
10
10
  * docs/architecture/CASTING-TO-TVS.md.
11
11
  */
12
12
  export const useCastClaims = (
13
- host: PortalHost,
13
+ domicile: { readonly portals: Pick<Portals, "answer"> },
14
14
  requests: readonly PortalRequest[],
15
15
  ): ReadonlyMap<number, CastClaim> => {
16
16
  // Every request seen, claimed or not: each is claimed once.
@@ -30,10 +30,10 @@ export const useCastClaims = (
30
30
  }
31
31
  for (const request of requests) {
32
32
  if (!known.has(request.id)) {
33
- const claim = claimCastRequest(host, request);
33
+ const claim = claimCastRequest(domicile, request);
34
34
  known.set(request.id, claim);
35
35
  if (claim?.kind === CastClaimKind.Answered) {
36
- answerPortalRequest(host, request.id, claim.answer);
36
+ domicile.portals.answer(request.id, claim.answer);
37
37
  }
38
38
  }
39
39
  }
@@ -44,7 +44,7 @@ export const useCastClaims = (
44
44
  ),
45
45
  ),
46
46
  );
47
- }, [host, requests]);
47
+ }, [domicile, requests]);
48
48
 
49
49
  return claims;
50
50
  };
@@ -1,5 +1,5 @@
1
- import type { System } from "@domicile-desktop/sdk/system";
2
1
  import { appIdName } from "@domicile-desktop/system-apps/app-id-name";
2
+ import type { AppsDesktop } from "@domicile-desktop/system-apps/apps-desktop";
3
3
  import type { DescribedApp } from "@domicile-desktop/system-apps/describe-apps";
4
4
  import { describeApps } from "@domicile-desktop/system-apps/describe-apps";
5
5
  import { useCallback, useEffect, useState } from "react";
@@ -17,7 +17,7 @@ export type App = {
17
17
  * yet, is named by its readable form; the empty id is "An application".
18
18
  */
19
19
  export const useApps = (
20
- system: System,
20
+ domicile: AppsDesktop,
21
21
  ids: readonly string[],
22
22
  ): ((appId: string) => App) => {
23
23
  const [described, setDescribed] = useState<ReadonlyMap<string, DescribedApp>>(
@@ -31,7 +31,7 @@ export const useApps = (
31
31
  return undefined;
32
32
  } else {
33
33
  let current = true;
34
- describeApps(system, key.split("\n"))
34
+ describeApps(domicile, key.split("\n"))
35
35
  .then((result) => {
36
36
  result.match({
37
37
  Err: (error) => {
@@ -54,7 +54,7 @@ export const useApps = (
54
54
  current = false;
55
55
  };
56
56
  }
57
- }, [system, key]);
57
+ }, [domicile, key]);
58
58
 
59
59
  return useCallback((appId) => app(described, appId), [described]);
60
60
  };
@@ -1,11 +1,11 @@
1
- import type { System } from "@domicile-desktop/sdk/system";
1
+ import type { Files as FileCalls } from "@domicile-desktop/sdk/desktop/files";
2
2
  import { useEffect, useState } from "react";
3
3
 
4
- type Files = Pick<System, "readFile">;
4
+ type Files = Pick<FileCalls, "read">;
5
5
 
6
6
  /**
7
- * A URL an `<img>` can show for the picture at `path`, read through the SDK's
8
- * `system`. `undefined` for no path, until it is read, or when it cannot be
7
+ * A URL an `<img>` can show for the picture at `path`, read through
8
+ * `domicile.files`. `undefined` for no path, until it is read, or when it cannot be
9
9
  * read (logged). The URL is revoked when the path changes or on unmount.
10
10
  */
11
11
  export const usePictureUrl = (
@@ -32,7 +32,7 @@ const showPicture = (
32
32
  let made: string | undefined;
33
33
  let current = true;
34
34
  files
35
- .readFile(path)
35
+ .read(path)
36
36
  .then((read) => {
37
37
  read.match({
38
38
  Err: (error) => {
@@ -1,20 +1,27 @@
1
- import type { PortalHost, PortalWallpaper } from "@domicile-desktop/sdk/portal";
2
- import { watchPortalWallpaper } from "@domicile-desktop/sdk/portal";
3
- import { useEffect, useState } from "react";
1
+ import type { Portals } from "@domicile-desktop/sdk/desktop/portals";
2
+ import type { PortalWallpaper } from "@domicile-desktop/sdk/portal";
3
+ import { useCallback, useSyncExternalStore } from "react";
4
4
 
5
5
  const NONE_SET: PortalWallpaper = {
6
6
  background: undefined,
7
7
  lockscreen: undefined,
8
8
  };
9
9
 
10
+ /** What {@link usePortalWallpaper} reads. */
11
+ export type WallpaperDesktop = {
12
+ readonly portals: Pick<Portals, "on" | "wallpaper">;
13
+ };
14
+
10
15
  /**
11
16
  * The pictures applications set through the Wallpaper portal, as paths. See
12
17
  * `docs/PORTALS.md`.
13
18
  */
14
- export const usePortalWallpaper = (host: PortalHost): PortalWallpaper => {
15
- const [wallpaper, setWallpaper] = useState(NONE_SET);
16
-
17
- useEffect(() => watchPortalWallpaper(host, setWallpaper), [host]);
18
-
19
- return wallpaper;
19
+ export const usePortalWallpaper = ({
20
+ portals,
21
+ }: WallpaperDesktop): PortalWallpaper => {
22
+ const subscribe = useCallback(
23
+ (changed: () => void) => portals.on("change", changed),
24
+ [portals],
25
+ );
26
+ return useSyncExternalStore(subscribe, () => portals.wallpaper ?? NONE_SET);
20
27
  };