@domicile-desktop/component-library 0.0.0-alpha-da1cf1c5441c → 0.0.0-alpha-e1457b57e6b2

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
@@ -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-da1cf1c5441c",
6
- "@domicile-desktop/system-apps": "0.0.0-alpha-da1cf1c5441c",
5
+ "@domicile-desktop/sdk": "0.0.0-alpha-e1457b57e6b2",
6
+ "@domicile-desktop/system-apps": "0.0.0-alpha-e1457b57e6b2",
7
7
  "@phosphor-icons/react": "^2.1.10"
8
8
  },
9
9
  "description": "React components and the Panda CSS design system that Domicile's shells are built from.",
@@ -103,5 +103,5 @@
103
103
  "test:unit": "bun test"
104
104
  },
105
105
  "type": "module",
106
- "version": "0.0.0-alpha-da1cf1c5441c"
106
+ "version": "0.0.0-alpha-e1457b57e6b2"
107
107
  }
@@ -1,57 +1,86 @@
1
1
  import type { AccountBody, PortalAnswer } from "@domicile-desktop/sdk/portal";
2
2
  import { PortalAnswer as Answer } from "@domicile-desktop/sdk/portal";
3
+ import type { System } from "@domicile-desktop/sdk/system";
3
4
  import { css } from "../../styled-system/css";
5
+ import { hstack } from "../../styled-system/patterns";
4
6
  import { Button } from "../Button/Button";
5
7
  import { ModalDialog } from "../ModalDialog/ModalDialog";
8
+ import { usePictureUrl } from "../usePictureUrl/usePictureUrl";
6
9
 
7
10
  type Props = {
8
11
  answer: (answer: PortalAnswer) => void;
9
12
  /** Who asks, as the dialog names them. */
10
13
  asker: string;
11
14
  body: AccountBody;
15
+ /** Reads the user's picture. */
16
+ files: Pick<System, "readFile">;
12
17
  screen: string | undefined;
13
18
  };
14
19
 
15
20
  /**
16
- * Whether an application may have the user's name and picture. Dismissing it
17
- * keeps them.
21
+ * Whether an application may have the user's name and picture, showing both.
22
+ * Dismissing it keeps them.
18
23
  */
19
- export const AccountDialog = ({ answer, asker, body, screen }: Props) => (
20
- <ModalDialog
21
- closeButton={false}
22
- footer={
23
- <>
24
- <Button
25
- onClick={() => {
26
- answer(Answer.Canceled());
27
- }}
28
- variant="outline"
29
- >
30
- Don't share
31
- </Button>
32
- <Button
33
- onClick={() => {
34
- answer(Answer.Access());
35
- }}
36
- >
37
- Share
38
- </Button>
39
- </>
40
- }
41
- onOpenChange={(open) => {
42
- if (!open) {
43
- answer(Answer.Canceled());
24
+ export const AccountDialog = ({
25
+ answer,
26
+ asker,
27
+ body,
28
+ files,
29
+ screen,
30
+ }: Props) => {
31
+ const picture = usePictureUrl(files, body.image);
32
+ return (
33
+ <ModalDialog
34
+ closeButton={false}
35
+ footer={
36
+ <>
37
+ <Button
38
+ onClick={() => {
39
+ answer(Answer.Canceled());
40
+ }}
41
+ variant="outline"
42
+ >
43
+ Don't share
44
+ </Button>
45
+ <Button
46
+ onClick={() => {
47
+ answer(Answer.Access());
48
+ }}
49
+ >
50
+ Share
51
+ </Button>
52
+ </>
44
53
  }
45
- }}
46
- open
47
- screen={screen}
48
- title="Share your name and picture?"
49
- >
50
- <p className={askerStyles}>{`${asker} asks`}</p>
51
- {body.reason !== undefined && <p className={textStyles}>{body.reason}</p>}
52
- </ModalDialog>
53
- );
54
+ onOpenChange={(open) => {
55
+ if (!open) {
56
+ answer(Answer.Canceled());
57
+ }
58
+ }}
59
+ open
60
+ screen={screen}
61
+ title="Share your name and picture?"
62
+ >
63
+ <p className={askerStyles}>{`${asker} asks`}</p>
64
+ {body.reason !== undefined && <p className={textStyles}>{body.reason}</p>}
65
+ <div className={userStyles}>
66
+ {picture !== undefined && (
67
+ <img alt={body.name} className={pictureStyles} src={picture} />
68
+ )}
69
+ <span className={textStyles}>{body.name}</span>
70
+ </div>
71
+ </ModalDialog>
72
+ );
73
+ };
54
74
 
55
75
  const askerStyles = css({ color: "muted", fontSize: "sm", margin: 0 });
56
76
 
57
77
  const textStyles = css({ color: "foreground", margin: 0 });
78
+
79
+ const userStyles = hstack({ gap: 3, marginBlockStart: 3 });
80
+
81
+ const pictureStyles = css({
82
+ blockSize: 10,
83
+ borderRadius: "full",
84
+ inlineSize: 10,
85
+ objectFit: "cover",
86
+ });
@@ -4,6 +4,7 @@ import type { Capturing, PortalRequest } from "@domicile-desktop/sdk/portal";
4
4
  import {
5
5
  answerPortalRequest,
6
6
  CapturingKind,
7
+ CastSourceKind,
7
8
  PortalAnswer,
8
9
  PortalKind,
9
10
  stopCapturing,
@@ -14,6 +15,7 @@ import type { System, SystemHost } from "@domicile-desktop/sdk/system";
14
15
  import { system } from "@domicile-desktop/sdk/system";
15
16
  import { useCallback, useEffect, useMemo, useState } from "react";
16
17
  import { listDirectory } from "../FilePicker/list-directory";
18
+ import type { App } from "../useApps/useApps";
17
19
  import { useApps } from "../useApps/useApps";
18
20
  import { AccessDialog } from "./AccessDialog";
19
21
  import { AccountDialog } from "./AccountDialog";
@@ -82,7 +84,7 @@ export const PortalDialogs = ({
82
84
  );
83
85
  const [sessions, setSessions] = useState<readonly Capturing[]>([]);
84
86
  const apps = useApps(files, [
85
- ...requests.map((request) => request.appId),
87
+ ...requests.flatMap(appIds),
86
88
  ...sessions.map((session) => session.appId),
87
89
  ]);
88
90
 
@@ -120,6 +122,7 @@ export const PortalDialogs = ({
120
122
  answer={(answer) => {
121
123
  answerPortalRequest(host, shown.id, answer);
122
124
  }}
125
+ apps={apps}
123
126
  asker={apps(shown.appId).name}
124
127
  key={shown.id}
125
128
  list={list}
@@ -136,6 +139,7 @@ export const PortalDialogs = ({
136
139
  /** The dialog for `request`'s kind. */
137
140
  const Dialog = ({
138
141
  answer,
142
+ apps,
139
143
  asker,
140
144
  list,
141
145
  request,
@@ -144,6 +148,7 @@ const Dialog = ({
144
148
  system: files,
145
149
  }: {
146
150
  answer: (answer: PortalAnswer) => void;
151
+ apps: (appId: string) => App;
147
152
  asker: string;
148
153
  list: (path: string) => Promise<readonly string[]>;
149
154
  request: PortalRequest;
@@ -204,6 +209,7 @@ const Dialog = ({
204
209
  answer={answer}
205
210
  asker={asker}
206
211
  body={request.body}
212
+ files={files}
207
213
  screen={screen}
208
214
  />
209
215
  );
@@ -249,6 +255,7 @@ const Dialog = ({
249
255
  return (
250
256
  <ScreenCastDialog
251
257
  answer={answer}
258
+ apps={apps}
252
259
  asker={asker}
253
260
  body={request.body}
254
261
  screen={screen}
@@ -300,6 +307,17 @@ const screenFor = (
300
307
  return parents ?? screen;
301
308
  };
302
309
 
310
+ /** The applications `request` names: who asks, and windows it may record. */
311
+ const appIds = (request: PortalRequest): string[] =>
312
+ request.kind === PortalKind.ScreenCast
313
+ ? [
314
+ request.appId,
315
+ ...request.body.sources.flatMap((source) =>
316
+ source.kind === CastSourceKind.Window ? [source.appId] : [],
317
+ ),
318
+ ]
319
+ : [request.appId];
320
+
303
321
  /** Whether `request` is a question this draws a dialog for. */
304
322
  const isAsked = (request: PortalRequest): boolean => {
305
323
  switch (request.kind) {
@@ -17,10 +17,13 @@ import { css } from "../../styled-system/css";
17
17
  import { Button } from "../Button/Button";
18
18
  import { ModalDialog } from "../ModalDialog/ModalDialog";
19
19
  import { Tabs } from "../Tabs/Tabs";
20
+ import type { App } from "../useApps/useApps";
20
21
  import { RegionPicker } from "./RegionPicker";
21
22
 
22
23
  type Props = {
23
24
  answer: (answer: PortalAnswer) => void;
25
+ /** Names a window's application by its Wayland app id. */
26
+ apps: (appId: string) => App;
24
27
  /** Who asks, as the dialog names them. */
25
28
  asker: string;
26
29
  body: ScreenCastBody;
@@ -33,7 +36,13 @@ type Props = {
33
36
  * asks for one. Picks one, or several when the application asks for more.
34
37
  * Dismissing it cancels.
35
38
  */
36
- export const ScreenCastDialog = ({ answer, asker, body, screen }: Props) => {
39
+ export const ScreenCastDialog = ({
40
+ answer,
41
+ apps,
42
+ asker,
43
+ body,
44
+ screen,
45
+ }: Props) => {
37
46
  const [picked, setPicked] = useState<readonly string[]>([]);
38
47
  const [drawing, setDrawing] = useState(false);
39
48
  const windows = body.sources.filter(
@@ -72,8 +81,8 @@ export const ScreenCastDialog = ({ answer, asker, body, screen }: Props) => {
72
81
  key={sourceKey(source)}
73
82
  value={sourceKey(source)}
74
83
  >
75
- <SourceIcon source={source} />
76
- <span>{sourceName(source)}</span>
84
+ <SourceIcon app={appOf(apps, source)} source={source} />
85
+ <span>{sourceName(source, appOf(apps, source))}</span>
77
86
  </Toggle>
78
87
  ))}
79
88
  </ToggleGroup>
@@ -152,13 +161,19 @@ export const ScreenCastDialog = ({ answer, asker, body, screen }: Props) => {
152
161
  };
153
162
 
154
163
  /** A window's application icon, a window outline without one, or a screen. */
155
- const SourceIcon = ({ source }: { source: CastSource }) => {
164
+ const SourceIcon = ({
165
+ app,
166
+ source,
167
+ }: {
168
+ app: App | undefined;
169
+ source: CastSource;
170
+ }) => {
156
171
  switch (source.kind) {
157
172
  case CastSourceKind.Window:
158
- return source.icon === undefined ? (
173
+ return app?.icon === undefined ? (
159
174
  <AppWindowIcon aria-hidden className={iconStyles} />
160
175
  ) : (
161
- <img alt="" className={iconStyles} src={source.icon} />
176
+ <img alt="" className={iconStyles} src={app.icon} />
162
177
  );
163
178
  case CastSourceKind.Monitor:
164
179
  case CastSourceKind.Region:
@@ -226,14 +241,21 @@ const sourceKey = (source: CastSource): string => {
226
241
  }
227
242
  };
228
243
 
229
- /** How the picker names a source. */
230
- const sourceName = (source: CastSource): string => {
244
+ /** A window's application, or `undefined` for a screen or a window with no app id. */
245
+ const appOf = (
246
+ apps: (appId: string) => App,
247
+ source: CastSource,
248
+ ): App | undefined =>
249
+ source.kind === CastSourceKind.Window && source.appId !== ""
250
+ ? apps(source.appId)
251
+ : undefined;
252
+
253
+ /** How the picker names a source, a window by its `app` too. */
254
+ const sourceName = (source: CastSource, app: App | undefined): string => {
231
255
  switch (source.kind) {
232
256
  case CastSourceKind.Window: {
233
257
  const title = source.title === "" ? "Untitled window" : source.title;
234
- return source.appName === undefined
235
- ? title
236
- : `${source.appName}: ${title}`;
258
+ return app === undefined ? title : `${app.name}: ${title}`;
237
259
  }
238
260
  case CastSourceKind.Monitor:
239
261
  return source.description === ""