@domicile-desktop/component-library 0.0.0-alpha-8f594a0bdff4 → 0.0.0-alpha-ffa113fe68d4
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-
|
|
6
|
-
"@domicile-desktop/system-apps": "0.0.0-alpha-
|
|
5
|
+
"@domicile-desktop/sdk": "0.0.0-alpha-ffa113fe68d4",
|
|
6
|
+
"@domicile-desktop/system-apps": "0.0.0-alpha-ffa113fe68d4",
|
|
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.",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"./SlideOver": "./src/SlideOver/SlideOver.tsx",
|
|
59
59
|
"./Slider": "./src/Slider/Slider.tsx",
|
|
60
60
|
"./Switch": "./src/Switch/Switch.tsx",
|
|
61
|
+
"./shown-windows": "./src/PortalDialogs/shown-windows.ts",
|
|
61
62
|
"./source-name": "./src/PortalDialogs/source-name.ts",
|
|
62
63
|
"./spacing": "./src/spacing.ts",
|
|
63
64
|
"./standalone-theme-source": "./src/ThemeSwitch/standalone-theme-source.ts",
|
|
@@ -104,5 +105,5 @@
|
|
|
104
105
|
"test:unit": "bun test"
|
|
105
106
|
},
|
|
106
107
|
"type": "module",
|
|
107
|
-
"version": "0.0.0-alpha-
|
|
108
|
+
"version": "0.0.0-alpha-ffa113fe68d4"
|
|
108
109
|
}
|
|
@@ -35,6 +35,8 @@ import { PrintDialog } from "./PrintDialog";
|
|
|
35
35
|
import { RemoteDesktopDialog } from "./RemoteDesktopDialog";
|
|
36
36
|
import { ScreenCastDialog } from "./ScreenCastDialog";
|
|
37
37
|
import { ScreenshotDialog } from "./ScreenshotDialog";
|
|
38
|
+
import type { ShownWindow } from "./shown-windows";
|
|
39
|
+
import { drawnWindows } from "./shown-windows";
|
|
38
40
|
import { UsbDialog } from "./UsbDialog";
|
|
39
41
|
import { WallpaperDialog } from "./WallpaperDialog";
|
|
40
42
|
|
|
@@ -54,6 +56,12 @@ type Props = {
|
|
|
54
56
|
* them itself.
|
|
55
57
|
*/
|
|
56
58
|
omitScreenCasts?: boolean | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* The windows the shell draws on screen, with their whole frames. Without
|
|
61
|
+
* it, the screenshot dialog reads the page's `<app>` and `<webview window>`
|
|
62
|
+
* boxes. See {@link drawnWindows}.
|
|
63
|
+
*/
|
|
64
|
+
shownWindows?: (() => readonly ShownWindow[]) | undefined;
|
|
57
65
|
/** How dialogs read the desktop's files, for tests. */
|
|
58
66
|
systemOf?: typeof system | undefined;
|
|
59
67
|
/**
|
|
@@ -79,6 +87,7 @@ export const PortalDialogs = ({
|
|
|
79
87
|
screen,
|
|
80
88
|
screenOf,
|
|
81
89
|
shellChords = NONE,
|
|
90
|
+
shownWindows = drawnOnPage,
|
|
82
91
|
systemOf = system,
|
|
83
92
|
}: Props) => {
|
|
84
93
|
const [requests, setRequests] = useState<readonly PortalRequest[]>([]);
|
|
@@ -135,6 +144,7 @@ export const PortalDialogs = ({
|
|
|
135
144
|
request={shown}
|
|
136
145
|
screen={screenFor(shown, screen, screenOf)}
|
|
137
146
|
shellChords={shellChords}
|
|
147
|
+
shownWindows={shownWindows}
|
|
138
148
|
system={files}
|
|
139
149
|
/>
|
|
140
150
|
)}
|
|
@@ -142,6 +152,9 @@ export const PortalDialogs = ({
|
|
|
142
152
|
);
|
|
143
153
|
};
|
|
144
154
|
|
|
155
|
+
/** The windows this page draws. */
|
|
156
|
+
const drawnOnPage = () => drawnWindows(document);
|
|
157
|
+
|
|
145
158
|
/** The dialog for `request`'s kind. */
|
|
146
159
|
const Dialog = ({
|
|
147
160
|
answer,
|
|
@@ -152,6 +165,7 @@ const Dialog = ({
|
|
|
152
165
|
request,
|
|
153
166
|
screen,
|
|
154
167
|
shellChords,
|
|
168
|
+
shownWindows,
|
|
155
169
|
system: files,
|
|
156
170
|
}: {
|
|
157
171
|
answer: (answer: PortalAnswer) => void;
|
|
@@ -162,6 +176,7 @@ const Dialog = ({
|
|
|
162
176
|
request: PortalRequest;
|
|
163
177
|
screen: string | undefined;
|
|
164
178
|
shellChords: readonly string[];
|
|
179
|
+
shownWindows: () => readonly ShownWindow[];
|
|
165
180
|
system: System;
|
|
166
181
|
}) => {
|
|
167
182
|
switch (request.kind) {
|
|
@@ -287,6 +302,7 @@ const Dialog = ({
|
|
|
287
302
|
body={request.body}
|
|
288
303
|
browserWindows={browserWindows}
|
|
289
304
|
screen={screen}
|
|
305
|
+
shownWindows={shownWindows}
|
|
290
306
|
/>
|
|
291
307
|
);
|
|
292
308
|
case PortalKind.PickColor:
|
|
@@ -15,9 +15,11 @@ import { flex } from "../../styled-system/patterns";
|
|
|
15
15
|
import { Button } from "../Button/Button";
|
|
16
16
|
import { ModalDialog } from "../ModalDialog/ModalDialog";
|
|
17
17
|
import type { App } from "../useApps/useApps";
|
|
18
|
-
import { browserWindowsOn } from "./browser-windows";
|
|
19
18
|
import type { FramePoint } from "./frame-point";
|
|
20
19
|
import { draggedArea, framePoint } from "./frame-point";
|
|
20
|
+
import { onFrame } from "./on-frame";
|
|
21
|
+
import type { ShownWindow } from "./shown-windows";
|
|
22
|
+
import { ShownWindowKind } from "./shown-windows";
|
|
21
23
|
|
|
22
24
|
type Props = {
|
|
23
25
|
answer: (answer: PortalAnswer) => void;
|
|
@@ -29,15 +31,33 @@ type Props = {
|
|
|
29
31
|
/** The desktop's browser windows, as the engine lists them. */
|
|
30
32
|
browserWindows: readonly DomicileBrowserWindow[];
|
|
31
33
|
screen: string | undefined;
|
|
34
|
+
/** The windows the shell draws on screen, read once as the dialog opens. */
|
|
35
|
+
shownWindows: () => readonly ShownWindow[];
|
|
32
36
|
};
|
|
33
37
|
|
|
38
|
+
/** What saving a choice keeps. */
|
|
39
|
+
enum PickKind {
|
|
40
|
+
Area,
|
|
41
|
+
Window,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const Pick = {
|
|
45
|
+
/** An area of the frozen desk. */
|
|
46
|
+
Area: (area: ShotRect) => ({ area, kind: PickKind.Area as const }),
|
|
47
|
+
/** A window not on screen, from its own last frame. */
|
|
48
|
+
Window: (id: string) => ({ id, kind: PickKind.Window as const }),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type Pick = ReturnType<(typeof Pick)[keyof typeof Pick]>;
|
|
52
|
+
|
|
34
53
|
/** Something the dialog offers to save, and how it shows it. */
|
|
35
|
-
type Choice = {
|
|
54
|
+
type Choice = { icon: ReactNode; name: string; pick: Pick };
|
|
36
55
|
|
|
37
56
|
/**
|
|
38
|
-
* Picks
|
|
39
|
-
*
|
|
40
|
-
*
|
|
57
|
+
* Picks what to save of a frozen desk: all of it, a screen, a window, or an
|
|
58
|
+
* area dragged over it. A window the shell shows is its whole frame on the
|
|
59
|
+
* desk; one it does not show, such as a hidden tab, is saved from its own
|
|
60
|
+
* last frame. Dismissing it cancels.
|
|
41
61
|
*/
|
|
42
62
|
export const ScreenshotDialog = ({
|
|
43
63
|
answer,
|
|
@@ -46,41 +66,39 @@ export const ScreenshotDialog = ({
|
|
|
46
66
|
body,
|
|
47
67
|
browserWindows,
|
|
48
68
|
screen,
|
|
69
|
+
shownWindows,
|
|
49
70
|
}: Props) => {
|
|
50
71
|
// Read once, as the desk froze; a later move would not match the frame.
|
|
51
|
-
const [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
const [shown] = useState(shownWindows);
|
|
73
|
+
const screens: Choice[] = body.monitors.map(
|
|
74
|
+
({ area, description, name }) => ({
|
|
75
|
+
icon: <MonitorIcon aria-hidden />,
|
|
76
|
+
name: description === "" ? name : description,
|
|
77
|
+
pick: Pick.Area(area),
|
|
78
|
+
}),
|
|
57
79
|
);
|
|
58
|
-
const screens: Choice[] = body.monitors.map(({ area, name }) => ({
|
|
59
|
-
area,
|
|
60
|
-
icon: <MonitorIcon aria-hidden />,
|
|
61
|
-
name,
|
|
62
|
-
}));
|
|
63
80
|
const windows: Choice[] = [
|
|
64
|
-
...body.windows.map(({ appId,
|
|
65
|
-
windowChoice(
|
|
81
|
+
...body.windows.map(({ appId, id, title }) =>
|
|
82
|
+
windowChoice(
|
|
83
|
+
pickOf(body, shown, id),
|
|
84
|
+
titled(title),
|
|
85
|
+
appId === "" ? undefined : apps(appId),
|
|
86
|
+
),
|
|
66
87
|
),
|
|
67
|
-
...
|
|
68
|
-
area,
|
|
69
|
-
icon: <GlobeIcon aria-hidden />,
|
|
70
|
-
name: `Browser: ${titled(title)}`,
|
|
71
|
-
})),
|
|
88
|
+
...shown.flatMap((window) => browserChoices(body, browserWindows, window)),
|
|
72
89
|
];
|
|
73
90
|
const whole: Choice = {
|
|
74
|
-
area: { height: body.height, width: body.width, x: 0, y: 0 },
|
|
75
91
|
icon: undefined,
|
|
76
92
|
name: "Whole desk",
|
|
93
|
+
pick: Pick.Area({ height: body.height, width: body.width, x: 0, y: 0 }),
|
|
77
94
|
};
|
|
78
95
|
const choices = [whole, ...screens, ...windows];
|
|
79
96
|
// The index of the choice picked, or the area dragged.
|
|
80
97
|
const [picked, setPicked] = useState<number | ShotRect>(0);
|
|
81
98
|
const [dragFrom, setDragFrom] = useState<FramePoint | undefined>(undefined);
|
|
82
|
-
const
|
|
83
|
-
|
|
99
|
+
const pick =
|
|
100
|
+
typeof picked === "number" ? choices[picked]?.pick : Pick.Area(picked);
|
|
101
|
+
if (pick === undefined) {
|
|
84
102
|
throw new Error(`no screenshot choice ${String(picked)}`);
|
|
85
103
|
}
|
|
86
104
|
const button = (choice: Choice, index: number) => (
|
|
@@ -119,7 +137,7 @@ export const ScreenshotDialog = ({
|
|
|
119
137
|
</Button>
|
|
120
138
|
<Button
|
|
121
139
|
onClick={() => {
|
|
122
|
-
answer(
|
|
140
|
+
answer(answerOf(pick));
|
|
123
141
|
}}
|
|
124
142
|
>
|
|
125
143
|
Save
|
|
@@ -152,6 +170,11 @@ export const ScreenshotDialog = ({
|
|
|
152
170
|
</ChoiceGroup>
|
|
153
171
|
)}
|
|
154
172
|
</div>
|
|
173
|
+
{pick.kind === PickKind.Window && (
|
|
174
|
+
<p className={askerStyles}>
|
|
175
|
+
Not on screen: saved as it last drew itself.
|
|
176
|
+
</p>
|
|
177
|
+
)}
|
|
155
178
|
<div
|
|
156
179
|
aria-label="Drag to pick an area"
|
|
157
180
|
className={deskStyles}
|
|
@@ -176,7 +199,9 @@ export const ScreenshotDialog = ({
|
|
|
176
199
|
draggable={false}
|
|
177
200
|
src={body.frame}
|
|
178
201
|
/>
|
|
179
|
-
|
|
202
|
+
{pick.kind === PickKind.Area && (
|
|
203
|
+
<div className={pickedStyles} style={placed(pick.area, body)} />
|
|
204
|
+
)}
|
|
180
205
|
</div>
|
|
181
206
|
</ModalDialog>
|
|
182
207
|
);
|
|
@@ -252,13 +277,66 @@ const placed = (
|
|
|
252
277
|
const titled = (title: string): string =>
|
|
253
278
|
title === "" ? "Untitled window" : title;
|
|
254
279
|
|
|
280
|
+
/** The answer that saves `pick`. */
|
|
281
|
+
const answerOf = (pick: Pick): PortalAnswer => {
|
|
282
|
+
switch (pick.kind) {
|
|
283
|
+
case PickKind.Area:
|
|
284
|
+
return Answer.Screenshot(pick.area);
|
|
285
|
+
case PickKind.Window:
|
|
286
|
+
return Answer.ScreenshotWindow(pick.id);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* What saving window `id` keeps: its frame on the desk while the shell shows
|
|
292
|
+
* it there, else its own last frame.
|
|
293
|
+
*/
|
|
294
|
+
const pickOf = (
|
|
295
|
+
desk: FrozenDesk,
|
|
296
|
+
shown: readonly ShownWindow[],
|
|
297
|
+
id: string,
|
|
298
|
+
): Pick => {
|
|
299
|
+
const window = shown.find(
|
|
300
|
+
(window) => window.kind === ShownWindowKind.App && window.appId === id,
|
|
301
|
+
);
|
|
302
|
+
const area = window === undefined ? undefined : onFrame(desk, window.box);
|
|
303
|
+
return area === undefined ? Pick.Window(id) : Pick.Area(area);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* `window` as a choice when it is a listed browser window on the desk. The
|
|
308
|
+
* engine draws a browser window, so one not on screen has nothing to save.
|
|
309
|
+
*/
|
|
310
|
+
const browserChoices = (
|
|
311
|
+
desk: FrozenDesk,
|
|
312
|
+
listed: readonly DomicileBrowserWindow[],
|
|
313
|
+
window: ShownWindow,
|
|
314
|
+
): Choice[] => {
|
|
315
|
+
switch (window.kind) {
|
|
316
|
+
case ShownWindowKind.App:
|
|
317
|
+
return [];
|
|
318
|
+
case ShownWindowKind.Browser: {
|
|
319
|
+
const browser = listed.find(({ id }) => id === window.id);
|
|
320
|
+
const area = onFrame(desk, window.box);
|
|
321
|
+
return browser === undefined || area === undefined
|
|
322
|
+
? []
|
|
323
|
+
: [
|
|
324
|
+
{
|
|
325
|
+
icon: <GlobeIcon aria-hidden />,
|
|
326
|
+
name: `Browser: ${titled(browser.title)}`,
|
|
327
|
+
pick: Pick.Area(area),
|
|
328
|
+
},
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
255
334
|
/** A window of the desk, named and drawn with its application if it has one. */
|
|
256
335
|
const windowChoice = (
|
|
257
|
-
|
|
336
|
+
pick: Pick,
|
|
258
337
|
title: string,
|
|
259
338
|
app: App | undefined,
|
|
260
339
|
): Choice => ({
|
|
261
|
-
area,
|
|
262
340
|
icon:
|
|
263
341
|
app?.icon === undefined ? (
|
|
264
342
|
<AppWindowIcon aria-hidden />
|
|
@@ -266,4 +344,5 @@ const windowChoice = (
|
|
|
266
344
|
<img alt="" className={appIconStyles} src={app.icon} />
|
|
267
345
|
),
|
|
268
346
|
name: app === undefined ? title : `${app.name}: ${title}`,
|
|
347
|
+
pick,
|
|
269
348
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FrozenDesk, ShotRect } from "@domicile-desktop/sdk/portal";
|
|
2
|
+
import type { PageBox } from "./shown-windows";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `box`, in the page's CSS pixels, which are the desk's logical pixels, in
|
|
6
|
+
* `desk`'s frame pixels. Clipped to the desk; `undefined` off it.
|
|
7
|
+
*/
|
|
8
|
+
export const onFrame = (
|
|
9
|
+
desk: FrozenDesk,
|
|
10
|
+
box: PageBox,
|
|
11
|
+
): ShotRect | undefined => {
|
|
12
|
+
const [deskX, deskY] = desk.desk.position;
|
|
13
|
+
const [deskWidth, deskHeight] = desk.desk.size;
|
|
14
|
+
const left = Math.max(box.x, deskX) - deskX;
|
|
15
|
+
const top = Math.max(box.y, deskY) - deskY;
|
|
16
|
+
const right = Math.min(box.x + box.width, deskX + deskWidth) - deskX;
|
|
17
|
+
const bottom = Math.min(box.y + box.height, deskY + deskHeight) - deskY;
|
|
18
|
+
const area = {
|
|
19
|
+
height: pixels(desk, bottom) - pixels(desk, top),
|
|
20
|
+
width: pixels(desk, right) - pixels(desk, left),
|
|
21
|
+
x: pixels(desk, left),
|
|
22
|
+
y: pixels(desk, top),
|
|
23
|
+
};
|
|
24
|
+
return area.width > 0 && area.height > 0 ? area : undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The frame pixel `logical` pixels into `desk`, rounded as the compositor
|
|
29
|
+
* rounds its monitors.
|
|
30
|
+
*/
|
|
31
|
+
const pixels = (desk: FrozenDesk, logical: number): number =>
|
|
32
|
+
Math.round((logical * desk.width) / desk.desk.size[0]);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** A rectangle of the page, in its CSS pixels. */
|
|
2
|
+
export type PageBox = { x: number; y: number; width: number; height: number };
|
|
3
|
+
|
|
4
|
+
/** The kind of a {@link ShownWindow}. */
|
|
5
|
+
export enum ShownWindowKind {
|
|
6
|
+
App,
|
|
7
|
+
Browser,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A window the shell draws on screen, and its whole frame there: title bar,
|
|
12
|
+
* address bar or tab strip included. The screenshot dialog crops a shown
|
|
13
|
+
* window from the frozen desk by this box.
|
|
14
|
+
*/
|
|
15
|
+
export const ShownWindow = {
|
|
16
|
+
/** An `<app>`'s window, by host app id. */
|
|
17
|
+
App: (appId: string, box: PageBox) => ({
|
|
18
|
+
appId,
|
|
19
|
+
box,
|
|
20
|
+
kind: ShownWindowKind.App as const,
|
|
21
|
+
}),
|
|
22
|
+
/** A browser window, by the engine's id. */
|
|
23
|
+
Browser: (id: string, box: PageBox) => ({
|
|
24
|
+
box,
|
|
25
|
+
id,
|
|
26
|
+
kind: ShownWindowKind.Browser as const,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ShownWindow = ReturnType<
|
|
31
|
+
(typeof ShownWindow)[keyof typeof ShownWindow]
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The windows `page` draws: each `<app>` and each `<webview window>` with a
|
|
36
|
+
* box. Their boxes leave out the shell's frame, and a window hidden under
|
|
37
|
+
* another still has one; a shell that knows better passes its own list.
|
|
38
|
+
*/
|
|
39
|
+
export const drawnWindows = (page: ParentNode): ShownWindow[] => [
|
|
40
|
+
...[...page.querySelectorAll("app[app-id]")].flatMap((app) =>
|
|
41
|
+
drawn(app, (box) => ShownWindow.App(attribute(app, "app-id"), box)),
|
|
42
|
+
),
|
|
43
|
+
...[...page.querySelectorAll("webview[window]")].flatMap((view) =>
|
|
44
|
+
drawn(view, (box) => ShownWindow.Browser(attribute(view, "window"), box)),
|
|
45
|
+
),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** `element` as `shown` makes it, unless it has no box. */
|
|
49
|
+
const drawn = (
|
|
50
|
+
element: Element,
|
|
51
|
+
shown: (box: PageBox) => ShownWindow,
|
|
52
|
+
): ShownWindow[] => {
|
|
53
|
+
const { height, width, x, y } = element.getBoundingClientRect();
|
|
54
|
+
return width > 0 && height > 0 ? [shown({ height, width, x, y })] : [];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** `element`'s `name` attribute, which its selector guarantees. */
|
|
58
|
+
const attribute = (element: Element, name: string): string => {
|
|
59
|
+
const value = element.getAttribute(name);
|
|
60
|
+
if (value === null) {
|
|
61
|
+
throw new Error(`a selected element has no ${name}`);
|
|
62
|
+
} else {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { DomicileBrowserWindow } from "@domicile-desktop/sdk/domicile-host";
|
|
2
|
-
import type { FrozenDesk, ShotRect } from "@domicile-desktop/sdk/portal";
|
|
3
|
-
|
|
4
|
-
/** A browser window a page draws, where it is on a frozen desk's frame. */
|
|
5
|
-
export type ShotBrowserWindow = { title: string; area: ShotRect };
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The browser windows `views` draw, where they are on `desk`'s frame. The
|
|
9
|
-
* compositor sees only `<app>` windows, so the page places these itself.
|
|
10
|
-
*
|
|
11
|
-
* A view's box is in the page's CSS pixels, which are the desk's logical
|
|
12
|
-
* pixels. It is clipped to the desk, and left out when off it, as one on a
|
|
13
|
-
* hidden workspace is.
|
|
14
|
-
*/
|
|
15
|
-
export const browserWindowsOn = (
|
|
16
|
-
desk: FrozenDesk,
|
|
17
|
-
listed: readonly DomicileBrowserWindow[],
|
|
18
|
-
views: Iterable<Element>,
|
|
19
|
-
): ShotBrowserWindow[] =>
|
|
20
|
-
[...views].flatMap((view) => {
|
|
21
|
-
const shown = listed.find(({ id }) => id === view.getAttribute("window"));
|
|
22
|
-
const area = onFrame(desk, view.getBoundingClientRect());
|
|
23
|
-
return shown === undefined || area === undefined
|
|
24
|
-
? []
|
|
25
|
-
: [{ area, title: shown.title }];
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
/** `box`, in logical pixels, in `desk`'s frame pixels; `undefined` off it. */
|
|
29
|
-
const onFrame = (desk: FrozenDesk, box: DOMRect): ShotRect | undefined => {
|
|
30
|
-
const [deskX, deskY] = desk.desk.position;
|
|
31
|
-
const [deskWidth, deskHeight] = desk.desk.size;
|
|
32
|
-
const left = Math.max(box.left, deskX) - deskX;
|
|
33
|
-
const top = Math.max(box.top, deskY) - deskY;
|
|
34
|
-
const right = Math.min(box.right, deskX + deskWidth) - deskX;
|
|
35
|
-
const bottom = Math.min(box.bottom, deskY + deskHeight) - deskY;
|
|
36
|
-
const area = {
|
|
37
|
-
height: pixels(desk, bottom) - pixels(desk, top),
|
|
38
|
-
width: pixels(desk, right) - pixels(desk, left),
|
|
39
|
-
x: pixels(desk, left),
|
|
40
|
-
y: pixels(desk, top),
|
|
41
|
-
};
|
|
42
|
-
return area.width > 0 && area.height > 0 ? area : undefined;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The frame pixel `logical` pixels into `desk`, rounded as the compositor
|
|
47
|
-
* rounds its windows.
|
|
48
|
-
*/
|
|
49
|
-
const pixels = (desk: FrozenDesk, logical: number): number =>
|
|
50
|
-
Math.round((logical * desk.width) / desk.desk.size[0]);
|