@domicile-desktop/component-library 0.0.0-alpha-59b63cdb292c → 0.0.0-alpha-452a746292aa
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-452a746292aa",
|
|
6
|
+
"@domicile-desktop/system-apps": "0.0.0-alpha-452a746292aa",
|
|
7
7
|
"@pandacss/dev": "^1.12.1",
|
|
8
8
|
"@phosphor-icons/react": "^2.1.10"
|
|
9
9
|
},
|
|
@@ -115,5 +115,5 @@
|
|
|
115
115
|
"test:unit": "bun test"
|
|
116
116
|
},
|
|
117
117
|
"type": "module",
|
|
118
|
-
"version": "0.0.0-alpha-
|
|
118
|
+
"version": "0.0.0-alpha-452a746292aa"
|
|
119
119
|
}
|
|
@@ -89,6 +89,11 @@ export const ScreenCastDialog = ({
|
|
|
89
89
|
);
|
|
90
90
|
// An area of the desk shot dragged in place of the picks.
|
|
91
91
|
const [dragged, setDragged] = useState<ShotRect | undefined>(undefined);
|
|
92
|
+
// A part dragged over the window picked last, drawn alone as it is not on
|
|
93
|
+
// screen, by the window's index.
|
|
94
|
+
const [part, setPart] = useState<
|
|
95
|
+
{ index: number; area: ShotRect } | undefined
|
|
96
|
+
>(undefined);
|
|
92
97
|
const [drawing, setDrawing] = useState(false);
|
|
93
98
|
const screens: Offered[] = body.sources.flatMap((source) =>
|
|
94
99
|
source.kind === CastSourceKind.Monitor
|
|
@@ -173,7 +178,13 @@ export const ScreenCastDialog = ({
|
|
|
173
178
|
desk !== undefined && dragged !== undefined
|
|
174
179
|
? [Source.Region(onDesk(desk, dragged))]
|
|
175
180
|
: offered.flatMap(({ source }, index) =>
|
|
176
|
-
picked.includes(index)
|
|
181
|
+
picked.includes(index)
|
|
182
|
+
? [
|
|
183
|
+
part?.index === index
|
|
184
|
+
? partOf(source, part.area)
|
|
185
|
+
: source,
|
|
186
|
+
]
|
|
187
|
+
: [],
|
|
177
188
|
),
|
|
178
189
|
),
|
|
179
190
|
);
|
|
@@ -195,9 +206,10 @@ export const ScreenCastDialog = ({
|
|
|
195
206
|
>
|
|
196
207
|
<p className={askerStyles}>{`${asker} wants to record`}</p>
|
|
197
208
|
<SourcePicker
|
|
198
|
-
caption={caption(offered, picked, dragged, body.region)}
|
|
209
|
+
caption={caption(offered, picked, dragged, part, body.region)}
|
|
199
210
|
desk={desk}
|
|
200
211
|
dragged={dragged}
|
|
212
|
+
draggedPart={part?.area}
|
|
201
213
|
label="What to share"
|
|
202
214
|
multiple={body.multiple}
|
|
203
215
|
onDrag={
|
|
@@ -205,12 +217,20 @@ export const ScreenCastDialog = ({
|
|
|
205
217
|
? (area) => {
|
|
206
218
|
setDragged(area);
|
|
207
219
|
setPicked([]);
|
|
220
|
+
setPart(undefined);
|
|
208
221
|
}
|
|
209
222
|
: undefined
|
|
210
223
|
}
|
|
224
|
+
onDragPart={(area) => {
|
|
225
|
+
const index = picked.at(-1);
|
|
226
|
+
if (index !== undefined) {
|
|
227
|
+
setPart({ area, index });
|
|
228
|
+
}
|
|
229
|
+
}}
|
|
211
230
|
onPick={(indices) => {
|
|
212
231
|
setPicked(indices);
|
|
213
232
|
setDragged(undefined);
|
|
233
|
+
setPart(undefined);
|
|
214
234
|
}}
|
|
215
235
|
picked={picked}
|
|
216
236
|
previewWindow={previewWindow}
|
|
@@ -241,14 +261,27 @@ const title = (
|
|
|
241
261
|
}
|
|
242
262
|
};
|
|
243
263
|
|
|
264
|
+
/** `source`, or `area` of it when it is a window. */
|
|
265
|
+
const partOf = (source: CastSource, area: ShotRect): CastSource => {
|
|
266
|
+
switch (source.kind) {
|
|
267
|
+
case CastSourceKind.Window:
|
|
268
|
+
case CastSourceKind.BrowserWindow:
|
|
269
|
+
return { ...source, area };
|
|
270
|
+
case CastSourceKind.Monitor:
|
|
271
|
+
case CastSourceKind.Region:
|
|
272
|
+
return source;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
244
276
|
/**
|
|
245
|
-
* What the desk says of the sources `picked` from `offered`,
|
|
246
|
-
* `dragged`, and whether a `region` may be dragged.
|
|
277
|
+
* What the desk says of the sources `picked` from `offered`, of the area
|
|
278
|
+
* `dragged` or of a window's `part`, and whether a `region` may be dragged.
|
|
247
279
|
*/
|
|
248
280
|
const caption = (
|
|
249
281
|
offered: readonly Offered[],
|
|
250
282
|
picked: readonly number[],
|
|
251
283
|
dragged: ShotRect | undefined,
|
|
284
|
+
part: { index: number; area: ShotRect } | undefined,
|
|
252
285
|
region: boolean,
|
|
253
286
|
): string => {
|
|
254
287
|
if (dragged !== undefined) {
|
|
@@ -257,8 +290,10 @@ const caption = (
|
|
|
257
290
|
return region
|
|
258
291
|
? "Pick what to share, or drag over the desk for an area."
|
|
259
292
|
: "Pick what to share.";
|
|
293
|
+
} else if (part !== undefined) {
|
|
294
|
+
return "A part of the window, shared as it draws itself.";
|
|
260
295
|
} else if (picked.some((index) => offered[index]?.area === undefined)) {
|
|
261
|
-
return "Not on screen: shared as it draws itself.";
|
|
296
|
+
return "Not on screen: shared as it draws itself. Drag over it to pick a part.";
|
|
262
297
|
} else {
|
|
263
298
|
return "Shared live, as it changes.";
|
|
264
299
|
}
|
|
@@ -52,12 +52,20 @@ const Pick = {
|
|
|
52
52
|
* A browser window from the engine's capture of its page, without what is
|
|
53
53
|
* over it. A hidden one draws while it is captured.
|
|
54
54
|
*/
|
|
55
|
-
BrowserWindow: (id: string) => ({
|
|
55
|
+
BrowserWindow: (id: string, part?: ShotRect | undefined) => ({
|
|
56
56
|
id,
|
|
57
57
|
kind: PickKind.BrowserWindow as const,
|
|
58
|
+
part,
|
|
59
|
+
}),
|
|
60
|
+
/**
|
|
61
|
+
* A window from its own last frame, without what is over it, or `part` of
|
|
62
|
+
* it, in the frame's pixels.
|
|
63
|
+
*/
|
|
64
|
+
Window: (id: string, part?: ShotRect | undefined) => ({
|
|
65
|
+
id,
|
|
66
|
+
kind: PickKind.Window as const,
|
|
67
|
+
part,
|
|
58
68
|
}),
|
|
59
|
-
/** A window from its own last frame, without what is over it. */
|
|
60
|
-
Window: (id: string) => ({ id, kind: PickKind.Window as const }),
|
|
61
69
|
};
|
|
62
70
|
|
|
63
71
|
type Pick = ReturnType<(typeof Pick)[keyof typeof Pick]>;
|
|
@@ -116,9 +124,12 @@ export const ScreenshotDialog = ({
|
|
|
116
124
|
const choices = [whole, ...screens, ...windows];
|
|
117
125
|
// The index of the choice picked, or the area dragged.
|
|
118
126
|
const [picked, setPicked] = useState<number | ShotRect>(0);
|
|
127
|
+
// A part dragged over the window picked, drawn alone as it is not on
|
|
128
|
+
// screen.
|
|
129
|
+
const [part, setPart] = useState<ShotRect | undefined>(undefined);
|
|
119
130
|
const pick =
|
|
120
131
|
typeof picked === "number"
|
|
121
|
-
? pickAt(choices, picked).pick
|
|
132
|
+
? partOf(pickAt(choices, picked).pick, part)
|
|
122
133
|
: Pick.Area(picked);
|
|
123
134
|
useEffect(() => {
|
|
124
135
|
const copy = (event: KeyboardEvent) => {
|
|
@@ -174,22 +185,27 @@ export const ScreenshotDialog = ({
|
|
|
174
185
|
>
|
|
175
186
|
{asker !== undefined && <p className={askerStyles}>{`${asker} asks`}</p>}
|
|
176
187
|
<SourcePicker
|
|
177
|
-
caption={
|
|
188
|
+
caption={caption(
|
|
178
189
|
typeof picked === "number" &&
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
190
|
+
pickAt(choices, picked).area === undefined,
|
|
191
|
+
part,
|
|
192
|
+
)}
|
|
183
193
|
desk={body}
|
|
184
194
|
dragged={typeof picked === "number" ? undefined : picked}
|
|
195
|
+
draggedPart={part}
|
|
185
196
|
label="What to save"
|
|
186
197
|
multiple={false}
|
|
187
|
-
onDrag={
|
|
198
|
+
onDrag={(area) => {
|
|
199
|
+
setPicked(area);
|
|
200
|
+
setPart(undefined);
|
|
201
|
+
}}
|
|
202
|
+
onDragPart={setPart}
|
|
188
203
|
onPick={([index]) => {
|
|
189
204
|
if (index === undefined) {
|
|
190
205
|
throw new Error("a screenshot pick of nothing");
|
|
191
206
|
} else {
|
|
192
207
|
setPicked(index);
|
|
208
|
+
setPart(undefined);
|
|
193
209
|
}
|
|
194
210
|
}}
|
|
195
211
|
picked={typeof picked === "number" ? [picked] : []}
|
|
@@ -216,15 +232,38 @@ const pickAt = (choices: readonly Picked[], index: number): Picked => {
|
|
|
216
232
|
}
|
|
217
233
|
};
|
|
218
234
|
|
|
235
|
+
/** `pick`, or `part` of it when it is a window and a part is dragged. */
|
|
236
|
+
const partOf = (pick: Pick, part: ShotRect | undefined): Pick => {
|
|
237
|
+
switch (pick.kind) {
|
|
238
|
+
case PickKind.Area:
|
|
239
|
+
return pick;
|
|
240
|
+
case PickKind.Window:
|
|
241
|
+
return Pick.Window(pick.id, part);
|
|
242
|
+
case PickKind.BrowserWindow:
|
|
243
|
+
return Pick.BrowserWindow(pick.id, part);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/** Under the desk, for a window `hidden` from it, with a `part` of it. */
|
|
248
|
+
const caption = (hidden: boolean, part: ShotRect | undefined): string => {
|
|
249
|
+
if (part !== undefined) {
|
|
250
|
+
return "A part of the window, as it last drew itself.";
|
|
251
|
+
} else if (hidden) {
|
|
252
|
+
return "Not on screen: saved as it last drew itself. Drag over it to pick a part.";
|
|
253
|
+
} else {
|
|
254
|
+
return "Drag over the desk to pick an area.";
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
219
258
|
/** The answer that saves `pick`, and copies it too when `copy` is set. */
|
|
220
259
|
const answerOf = (pick: Pick, copy: boolean): PortalAnswer => {
|
|
221
260
|
switch (pick.kind) {
|
|
222
261
|
case PickKind.Area:
|
|
223
262
|
return Answer.Screenshot(pick.area, copy);
|
|
224
263
|
case PickKind.Window:
|
|
225
|
-
return Answer.ScreenshotWindow(pick.id, copy);
|
|
264
|
+
return Answer.ScreenshotWindow(pick.id, copy, pick.part);
|
|
226
265
|
case PickKind.BrowserWindow:
|
|
227
|
-
return Answer.ScreenshotBrowserWindow(pick.id, copy);
|
|
266
|
+
return Answer.ScreenshotBrowserWindow(pick.id, copy, pick.part);
|
|
228
267
|
}
|
|
229
268
|
};
|
|
230
269
|
|
|
@@ -50,6 +50,13 @@ type Props = {
|
|
|
50
50
|
dragged?: ShotRect | undefined;
|
|
51
51
|
/** Called with a dragged area; without it, the desk cannot be dragged. */
|
|
52
52
|
onDrag?: ((area: ShotRect) => void) | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* A part dragged over a window drawn alone, in its frame's pixels, outlined
|
|
55
|
+
* on it.
|
|
56
|
+
*/
|
|
57
|
+
draggedPart?: ShotRect | undefined;
|
|
58
|
+
/** Called with a part dragged over a window drawn alone. */
|
|
59
|
+
onDragPart?: ((area: ShotRect) => void) | undefined;
|
|
53
60
|
onPick: (picked: number[]) => void;
|
|
54
61
|
/** The indexes, across every section, of the choices picked. */
|
|
55
62
|
picked: readonly number[];
|
|
@@ -61,16 +68,19 @@ type Props = {
|
|
|
61
68
|
/**
|
|
62
69
|
* Picks from a list of sources beside one preview of the frozen desk, which
|
|
63
70
|
* outlines each pick with its size. A window picked last that the desk does
|
|
64
|
-
* not show is drawn alone instead
|
|
65
|
-
* checkboxes. The screenshot and screen
|
|
71
|
+
* not show is drawn alone instead, and a part of it may be dragged over it.
|
|
72
|
+
* One source is a radio, several are checkboxes. The screenshot and screen
|
|
73
|
+
* cast pickers share it.
|
|
66
74
|
*/
|
|
67
75
|
export const SourcePicker = ({
|
|
68
76
|
caption,
|
|
69
77
|
desk,
|
|
70
78
|
dragged,
|
|
79
|
+
draggedPart,
|
|
71
80
|
label,
|
|
72
81
|
multiple,
|
|
73
82
|
onDrag,
|
|
83
|
+
onDragPart,
|
|
74
84
|
onPick,
|
|
75
85
|
picked,
|
|
76
86
|
previewWindow,
|
|
@@ -121,9 +131,11 @@ export const SourcePicker = ({
|
|
|
121
131
|
<figure className={stageStyles}>
|
|
122
132
|
<Stage
|
|
123
133
|
desk={desk}
|
|
134
|
+
draggedPart={draggedPart}
|
|
124
135
|
frame={frame}
|
|
125
136
|
hidden={hidden}
|
|
126
137
|
onDrag={onDrag}
|
|
138
|
+
onDragPart={onDragPart}
|
|
127
139
|
outlined={outlined}
|
|
128
140
|
/>
|
|
129
141
|
<figcaption className={captionStyles}>{caption}</figcaption>
|
|
@@ -138,15 +150,19 @@ export const SourcePicker = ({
|
|
|
138
150
|
*/
|
|
139
151
|
const Stage = ({
|
|
140
152
|
desk,
|
|
153
|
+
draggedPart,
|
|
141
154
|
frame,
|
|
142
155
|
hidden,
|
|
143
156
|
onDrag,
|
|
157
|
+
onDragPart,
|
|
144
158
|
outlined,
|
|
145
159
|
}: {
|
|
146
160
|
desk: FrozenDesk | undefined;
|
|
161
|
+
draggedPart: ShotRect | undefined;
|
|
147
162
|
frame: Result<string, SystemError> | undefined;
|
|
148
163
|
hidden: Choice | undefined;
|
|
149
164
|
onDrag: ((area: ShotRect) => void) | undefined;
|
|
165
|
+
onDragPart: ((area: ShotRect) => void) | undefined;
|
|
150
166
|
outlined: readonly Outline[];
|
|
151
167
|
}) => {
|
|
152
168
|
if (hidden?.preview !== undefined) {
|
|
@@ -157,7 +173,15 @@ const Stage = ({
|
|
|
157
173
|
frame.match({
|
|
158
174
|
Err: () => <span className={noDeskStyles}>No preview</span>,
|
|
159
175
|
Ok: (src) => (
|
|
160
|
-
<
|
|
176
|
+
<Preview
|
|
177
|
+
alt={hidden.label}
|
|
178
|
+
box={desk}
|
|
179
|
+
dragged={draggedPart}
|
|
180
|
+
// Keyed so a part outlined on one window is not kept on the next.
|
|
181
|
+
key={src}
|
|
182
|
+
onDrag={onDragPart}
|
|
183
|
+
src={src}
|
|
184
|
+
/>
|
|
161
185
|
),
|
|
162
186
|
})
|
|
163
187
|
);
|
|
@@ -257,6 +281,57 @@ const Row = ({
|
|
|
257
281
|
);
|
|
258
282
|
};
|
|
259
283
|
|
|
284
|
+
/**
|
|
285
|
+
* A window drawn alone from `src`, fitted in `box`. With `onDrag`, a part of
|
|
286
|
+
* it may be dragged, in its frame's pixels, and `dragged` is outlined.
|
|
287
|
+
*/
|
|
288
|
+
const Preview = ({
|
|
289
|
+
alt,
|
|
290
|
+
box,
|
|
291
|
+
dragged,
|
|
292
|
+
onDrag,
|
|
293
|
+
src,
|
|
294
|
+
}: {
|
|
295
|
+
alt: string;
|
|
296
|
+
box: { width: number; height: number } | undefined;
|
|
297
|
+
dragged: ShotRect | undefined;
|
|
298
|
+
onDrag: ((area: ShotRect) => void) | undefined;
|
|
299
|
+
src: string;
|
|
300
|
+
}) => {
|
|
301
|
+
// The frame's size, which the drag is in, once it loads.
|
|
302
|
+
const [frame, setFrame] = useState<
|
|
303
|
+
{ width: number; height: number } | undefined
|
|
304
|
+
>(undefined);
|
|
305
|
+
return (
|
|
306
|
+
<>
|
|
307
|
+
{/* biome-ignore lint/a11y/noNoninteractiveElementInteractions: reads the frame's size as it loads, which is no interaction */}
|
|
308
|
+
<img
|
|
309
|
+
alt={alt}
|
|
310
|
+
className={previewStyles}
|
|
311
|
+
draggable={false}
|
|
312
|
+
onLoad={(event) => {
|
|
313
|
+
setFrame({
|
|
314
|
+
height: event.currentTarget.naturalHeight,
|
|
315
|
+
width: event.currentTarget.naturalWidth,
|
|
316
|
+
});
|
|
317
|
+
}}
|
|
318
|
+
src={src}
|
|
319
|
+
/>
|
|
320
|
+
{frame !== undefined && box !== undefined && onDrag !== undefined && (
|
|
321
|
+
<Dragged
|
|
322
|
+
frame={frame}
|
|
323
|
+
label="Drag to pick a part of the window"
|
|
324
|
+
onDrag={onDrag}
|
|
325
|
+
outlined={
|
|
326
|
+
dragged === undefined ? [] : [{ area: dragged, key: "dragged" }]
|
|
327
|
+
}
|
|
328
|
+
style={{ ...contained(frame, box), position: "absolute" }}
|
|
329
|
+
/>
|
|
330
|
+
)}
|
|
331
|
+
</>
|
|
332
|
+
);
|
|
333
|
+
};
|
|
334
|
+
|
|
260
335
|
/** The frozen desk, outlining `outlined`, and dragged when `onDrag` is set. */
|
|
261
336
|
const Desk = ({
|
|
262
337
|
desk,
|
|
@@ -266,45 +341,62 @@ const Desk = ({
|
|
|
266
341
|
desk: FrozenDesk;
|
|
267
342
|
onDrag: ((area: ShotRect) => void) | undefined;
|
|
268
343
|
outlined: readonly Outline[];
|
|
344
|
+
}) => {
|
|
345
|
+
const image = (
|
|
346
|
+
<img
|
|
347
|
+
alt="The desk"
|
|
348
|
+
className={frameStyles}
|
|
349
|
+
draggable={false}
|
|
350
|
+
src={desk.frame}
|
|
351
|
+
/>
|
|
352
|
+
);
|
|
353
|
+
return onDrag === undefined ? (
|
|
354
|
+
<div className={deskStyles} style={fitted(desk)}>
|
|
355
|
+
{image}
|
|
356
|
+
<Outlines frame={desk} outlined={outlined} />
|
|
357
|
+
</div>
|
|
358
|
+
) : (
|
|
359
|
+
<Dragged
|
|
360
|
+
frame={desk}
|
|
361
|
+
label="Drag to pick an area"
|
|
362
|
+
onDrag={onDrag}
|
|
363
|
+
outlined={outlined}
|
|
364
|
+
style={fitted(desk)}
|
|
365
|
+
>
|
|
366
|
+
{image}
|
|
367
|
+
</Dragged>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* A box over a frame of `frame`'s size that an area is dragged on, in the
|
|
373
|
+
* frame's pixels, outlining `outlined`.
|
|
374
|
+
*/
|
|
375
|
+
const Dragged = ({
|
|
376
|
+
children,
|
|
377
|
+
frame,
|
|
378
|
+
label,
|
|
379
|
+
onDrag,
|
|
380
|
+
outlined,
|
|
381
|
+
style,
|
|
382
|
+
}: {
|
|
383
|
+
children?: ReactNode;
|
|
384
|
+
frame: { width: number; height: number };
|
|
385
|
+
label: string;
|
|
386
|
+
onDrag: (area: ShotRect) => void;
|
|
387
|
+
outlined: readonly Outline[];
|
|
388
|
+
style: CSSProperties;
|
|
269
389
|
}) => {
|
|
270
390
|
const [dragFrom, setDragFrom] = useState<FramePoint | undefined>(undefined);
|
|
271
391
|
const pointAt = (event: PointerEvent<HTMLDivElement>) =>
|
|
272
392
|
framePoint(
|
|
273
393
|
{ x: event.clientX, y: event.clientY },
|
|
274
394
|
event.currentTarget.getBoundingClientRect(),
|
|
275
|
-
|
|
395
|
+
frame,
|
|
276
396
|
);
|
|
277
|
-
|
|
278
|
-
<>
|
|
279
|
-
<img
|
|
280
|
-
alt="The desk"
|
|
281
|
-
className={frameStyles}
|
|
282
|
-
draggable={false}
|
|
283
|
-
src={desk.frame}
|
|
284
|
-
/>
|
|
285
|
-
{outlined.map(({ area, key }) => (
|
|
286
|
-
<div
|
|
287
|
-
className={pickedStyles}
|
|
288
|
-
// One pick shades the rest of the desk; several would shade each
|
|
289
|
-
// other.
|
|
290
|
-
data-shaded={outlined.length === 1 ? "" : undefined}
|
|
291
|
-
key={key}
|
|
292
|
-
style={placed(area, desk)}
|
|
293
|
-
>
|
|
294
|
-
<span className={sizeStyles}>
|
|
295
|
-
{`${String(area.width)} × ${String(area.height)}`}
|
|
296
|
-
</span>
|
|
297
|
-
</div>
|
|
298
|
-
))}
|
|
299
|
-
</>
|
|
300
|
-
);
|
|
301
|
-
return onDrag === undefined ? (
|
|
302
|
-
<div className={deskStyles} style={fitted(desk)}>
|
|
303
|
-
{frame}
|
|
304
|
-
</div>
|
|
305
|
-
) : (
|
|
397
|
+
return (
|
|
306
398
|
<div
|
|
307
|
-
aria-label=
|
|
399
|
+
aria-label={label}
|
|
308
400
|
className={deskStyles}
|
|
309
401
|
data-draggable=""
|
|
310
402
|
onPointerDown={(event) => {
|
|
@@ -321,13 +413,37 @@ const Desk = ({
|
|
|
321
413
|
setDragFrom(undefined);
|
|
322
414
|
}}
|
|
323
415
|
role="group"
|
|
324
|
-
style={
|
|
416
|
+
style={style}
|
|
325
417
|
>
|
|
326
|
-
{
|
|
418
|
+
{children}
|
|
419
|
+
<Outlines frame={frame} outlined={outlined} />
|
|
327
420
|
</div>
|
|
328
421
|
);
|
|
329
422
|
};
|
|
330
423
|
|
|
424
|
+
/** `outlined` over a frame of `frame`'s size, each with its size. */
|
|
425
|
+
const Outlines = ({
|
|
426
|
+
frame,
|
|
427
|
+
outlined,
|
|
428
|
+
}: {
|
|
429
|
+
frame: { width: number; height: number };
|
|
430
|
+
outlined: readonly Outline[];
|
|
431
|
+
}) =>
|
|
432
|
+
outlined.map(({ area, key }) => (
|
|
433
|
+
<div
|
|
434
|
+
className={pickedStyles}
|
|
435
|
+
// One pick shades the rest of the frame; several would shade each
|
|
436
|
+
// other.
|
|
437
|
+
data-shaded={outlined.length === 1 ? "" : undefined}
|
|
438
|
+
key={key}
|
|
439
|
+
style={placed(area, frame)}
|
|
440
|
+
>
|
|
441
|
+
<span className={sizeStyles}>
|
|
442
|
+
{`${String(area.width)} × ${String(area.height)}`}
|
|
443
|
+
</span>
|
|
444
|
+
</div>
|
|
445
|
+
));
|
|
446
|
+
|
|
331
447
|
const layoutStyles = css({
|
|
332
448
|
alignItems: "stretch",
|
|
333
449
|
display: "grid",
|
|
@@ -495,6 +611,24 @@ const fitted = (frame: { width: number; height: number }): CSSProperties => {
|
|
|
495
611
|
};
|
|
496
612
|
};
|
|
497
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Where a frame of `frame`'s size is drawn in `box`, scaled to fit and
|
|
616
|
+
* centered as `object-fit: contain` draws it, in percent of the box.
|
|
617
|
+
*/
|
|
618
|
+
const contained = (
|
|
619
|
+
frame: { width: number; height: number },
|
|
620
|
+
box: { width: number; height: number },
|
|
621
|
+
): CSSProperties => {
|
|
622
|
+
const wider = frame.width / frame.height / (box.width / box.height);
|
|
623
|
+
const [width, height] = wider > 1 ? [100, 100 / wider] : [100 * wider, 100];
|
|
624
|
+
return {
|
|
625
|
+
height: `${String(height)}%`,
|
|
626
|
+
left: `${String((100 - width) / 2)}%`,
|
|
627
|
+
top: `${String((100 - height) / 2)}%`,
|
|
628
|
+
width: `${String(width)}%`,
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
|
|
498
632
|
/** `area`'s place over the drawn frame, in percent of `frame`. */
|
|
499
633
|
const placed = (
|
|
500
634
|
area: ShotRect,
|