@orz/ui-content 0.0.0-stage → 0.1.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/LICENSE +29 -0
- package/README.md +410 -2
- package/dist/attachment/card-content.d.ts +26 -0
- package/dist/attachment/card-content.d.ts.map +1 -0
- package/dist/attachment/card-content.js +49 -0
- package/dist/attachment/card-content.js.map +1 -0
- package/dist/attachment/deleted.d.ts +9 -0
- package/dist/attachment/deleted.d.ts.map +1 -0
- package/dist/attachment/deleted.js +11 -0
- package/dist/attachment/deleted.js.map +1 -0
- package/dist/attachment/document-card.d.ts +32 -0
- package/dist/attachment/document-card.d.ts.map +1 -0
- package/dist/attachment/document-card.js +38 -0
- package/dist/attachment/document-card.js.map +1 -0
- package/dist/attachment/icon.d.ts +13 -0
- package/dist/attachment/icon.d.ts.map +1 -0
- package/dist/attachment/icon.js +34 -0
- package/dist/attachment/icon.js.map +1 -0
- package/dist/attachment/image-canvas.d.ts +25 -0
- package/dist/attachment/image-canvas.d.ts.map +1 -0
- package/dist/attachment/image-canvas.js +301 -0
- package/dist/attachment/image-canvas.js.map +1 -0
- package/dist/attachment/image-look.d.ts +18 -0
- package/dist/attachment/image-look.d.ts.map +1 -0
- package/dist/attachment/image-look.js +86 -0
- package/dist/attachment/image-look.js.map +1 -0
- package/dist/attachment/image-zoom.d.ts +67 -0
- package/dist/attachment/image-zoom.d.ts.map +1 -0
- package/dist/attachment/image-zoom.js +108 -0
- package/dist/attachment/image-zoom.js.map +1 -0
- package/dist/attachment/index.d.ts +12 -0
- package/dist/attachment/index.d.ts.map +1 -0
- package/dist/attachment/index.js +12 -0
- package/dist/attachment/index.js.map +1 -0
- package/dist/attachment/link.d.ts +18 -0
- package/dist/attachment/link.d.ts.map +1 -0
- package/dist/attachment/link.js +30 -0
- package/dist/attachment/link.js.map +1 -0
- package/dist/attachment/list.d.ts +26 -0
- package/dist/attachment/list.d.ts.map +1 -0
- package/dist/attachment/list.js +53 -0
- package/dist/attachment/list.js.map +1 -0
- package/dist/attachment/media-content.d.ts +21 -0
- package/dist/attachment/media-content.d.ts.map +1 -0
- package/dist/attachment/media-content.js +133 -0
- package/dist/attachment/media-content.js.map +1 -0
- package/dist/attachment/media-player.d.ts +23 -0
- package/dist/attachment/media-player.d.ts.map +1 -0
- package/dist/attachment/media-player.js +137 -0
- package/dist/attachment/media-player.js.map +1 -0
- package/dist/attachment/use-media.d.ts +27 -0
- package/dist/attachment/use-media.d.ts.map +1 -0
- package/dist/attachment/use-media.js +133 -0
- package/dist/attachment/use-media.js.map +1 -0
- package/dist/attachment/viewer.d.ts +85 -0
- package/dist/attachment/viewer.d.ts.map +1 -0
- package/dist/attachment/viewer.js +245 -0
- package/dist/attachment/viewer.js.map +1 -0
- package/dist/file-kind.d.ts +61 -0
- package/dist/file-kind.d.ts.map +1 -0
- package/dist/file-kind.js +166 -0
- package/dist/file-kind.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +44 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +109 -0
- package/dist/registry.js.map +1 -0
- package/dist/source-text.d.ts +29 -0
- package/dist/source-text.d.ts.map +1 -0
- package/dist/source-text.js +101 -0
- package/dist/source-text.js.map +1 -0
- package/dist/title-bar.d.ts +23 -0
- package/dist/title-bar.d.ts.map +1 -0
- package/dist/title-bar.js +24 -0
- package/dist/title-bar.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -3
- package/styles.css +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FileLike } from "../file-kind.ts";
|
|
2
|
+
/**
|
|
3
|
+
* One table behind every attachment icon on the page — list rows, link chips,
|
|
4
|
+
* document cards — so a file cannot wear two hats in one viewport.
|
|
5
|
+
*
|
|
6
|
+
* The three binary kinds are tested last, after the three that were here
|
|
7
|
+
* first: only files that fall through to `FileIcon` today can move. `.ts` is
|
|
8
|
+
* why that ordering is load-bearing rather than merely cautious — it is a
|
|
9
|
+
* TypeScript source and an MPEG transport stream, and `isTextFile` has to get
|
|
10
|
+
* the first look at it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fileIcon(file: FileLike): import("lucide-react").LucideIcon;
|
|
13
|
+
//# sourceMappingURL=icon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../../src/attachment/icon.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,QAAQ,EAQd,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,qCAatC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AppWindowIcon, FileArchiveIcon, FileDiffIcon, FileHeadphoneIcon, FileIcon, FilePlayIcon, FileTextIcon, ImageIcon, } from "lucide-react";
|
|
2
|
+
import { isArchiveFile, isAudioFile, isHtmlFile, isImageFile, isPatchFile, isTextFile, isVideoFile, } from "../file-kind.js";
|
|
3
|
+
/**
|
|
4
|
+
* One table behind every attachment icon on the page — list rows, link chips,
|
|
5
|
+
* document cards — so a file cannot wear two hats in one viewport.
|
|
6
|
+
*
|
|
7
|
+
* The three binary kinds are tested last, after the three that were here
|
|
8
|
+
* first: only files that fall through to `FileIcon` today can move. `.ts` is
|
|
9
|
+
* why that ordering is load-bearing rather than merely cautious — it is a
|
|
10
|
+
* TypeScript source and an MPEG transport stream, and `isTextFile` has to get
|
|
11
|
+
* the first look at it.
|
|
12
|
+
*/
|
|
13
|
+
export function fileIcon(file) {
|
|
14
|
+
if (isImageFile(file))
|
|
15
|
+
return ImageIcon;
|
|
16
|
+
if (isHtmlFile(file))
|
|
17
|
+
return AppWindowIcon;
|
|
18
|
+
if (isPatchFile(file))
|
|
19
|
+
return FileDiffIcon;
|
|
20
|
+
if (isTextFile(file))
|
|
21
|
+
return FileTextIcon;
|
|
22
|
+
if (isArchiveFile(file))
|
|
23
|
+
return FileArchiveIcon;
|
|
24
|
+
// `FileHeadphone` and `FilePlay` are lucide's current names for the audio
|
|
25
|
+
// and video file glyphs; `FileAudio` and `FileVideo` still import, as
|
|
26
|
+
// aliases, but render under the new names — which is what the class in the
|
|
27
|
+
// DOM says, and what the tests therefore have to match.
|
|
28
|
+
if (isAudioFile(file))
|
|
29
|
+
return FileHeadphoneIcon;
|
|
30
|
+
if (isVideoFile(file))
|
|
31
|
+
return FilePlayIcon;
|
|
32
|
+
return FileIcon;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=icon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon.js","sourceRoot":"","sources":["../../src/attachment/icon.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAc;IACrC,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,CAAC;IAC3C,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IAC3C,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IAC1C,IAAI,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,eAAe,CAAC;IAChD,0EAA0E;IAC1E,sEAAsE;IACtE,2EAA2E;IAC3E,wDAAwD;IACxD,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAChD,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IAC3C,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ImageLook } from "./image-look.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The attachment viewer's image: wheel or pinch zooms about the pointer, drag
|
|
4
|
+
* pans, a click toggles fit ↔ actual pixels, and − 100%▾ + sit in the
|
|
5
|
+
* viewer's title bar, the readout opening fit, 100% and the background and
|
|
6
|
+
* scaling choices. Mount it keyed by the image so paging starts every image
|
|
7
|
+
* at fit; `lastHeight` carries the canvas height across that remount, so the
|
|
8
|
+
* next image tweens from it. While `held` is set the canvas keeps that height
|
|
9
|
+
* whatever the zoom or image asks for, and moves to its own once it is
|
|
10
|
+
* cleared.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ImageCanvas({ src, alt, maximized, toolbar, look, onLookChange, lastHeight, held, className, }: {
|
|
13
|
+
src: string;
|
|
14
|
+
alt: string;
|
|
15
|
+
maximized: boolean;
|
|
16
|
+
toolbar: HTMLElement | null;
|
|
17
|
+
look: ImageLook;
|
|
18
|
+
onLookChange(look: ImageLook): void;
|
|
19
|
+
lastHeight: {
|
|
20
|
+
current: number | null;
|
|
21
|
+
};
|
|
22
|
+
held: number | null;
|
|
23
|
+
className?: string;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=image-canvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-canvas.d.ts","sourceRoot":"","sources":["../../src/attachment/image-canvas.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AA+BjD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,GAAG,EACH,SAAS,EACT,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,SAAS,GACV,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACvC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAyaA"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@orz/ui/components/button";
|
|
3
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from "@orz/ui/components/dropdown-menu";
|
|
4
|
+
import { cn } from "@orz/ui/lib/utils";
|
|
5
|
+
import { ChevronDownIcon, MinusIcon, PlusIcon } from "lucide-react";
|
|
6
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
|
|
7
|
+
import { createPortal, flushSync } from "react-dom";
|
|
8
|
+
import { actualAt, canvasHeight, fitScale, fitView, isAtMax, isAtMin, isFitted, panBy, reframe, toggleAt, wheelFactor, zoomAt, } from "./image-zoom.js";
|
|
9
|
+
// In screen pixels, never scaled with the image: at 800% a scaled checker
|
|
10
|
+
// would be squares bigger than the pixels it is meant to show through.
|
|
11
|
+
const CHECKER = "repeating-conic-gradient(#d4d4d4 0 25%, #fff 0 50%) 0 0 / 16px 16px";
|
|
12
|
+
/** Movement under this is still a click/tap, not a drag. */
|
|
13
|
+
const TAP_SLOP = 4;
|
|
14
|
+
const STEP = 1.25;
|
|
15
|
+
/** The restored canvas's height changes. */
|
|
16
|
+
const TWEEN = { duration: 200, easing: "cubic-bezier(0.2, 0, 0, 1)" };
|
|
17
|
+
/**
|
|
18
|
+
* The attachment viewer's image: wheel or pinch zooms about the pointer, drag
|
|
19
|
+
* pans, a click toggles fit ↔ actual pixels, and − 100%▾ + sit in the
|
|
20
|
+
* viewer's title bar, the readout opening fit, 100% and the background and
|
|
21
|
+
* scaling choices. Mount it keyed by the image so paging starts every image
|
|
22
|
+
* at fit; `lastHeight` carries the canvas height across that remount, so the
|
|
23
|
+
* next image tweens from it. While `held` is set the canvas keeps that height
|
|
24
|
+
* whatever the zoom or image asks for, and moves to its own once it is
|
|
25
|
+
* cleared.
|
|
26
|
+
*/
|
|
27
|
+
export function ImageCanvas({ src, alt, maximized, toolbar, look, onLookChange, lastHeight, held, className, }) {
|
|
28
|
+
const canvasRef = useRef(null);
|
|
29
|
+
const [natural, setNatural] = useState(null);
|
|
30
|
+
const [frame, setFrame] = useState(null);
|
|
31
|
+
const [view, setView] = useState(null);
|
|
32
|
+
const [dragging, setDragging] = useState(false);
|
|
33
|
+
const [failed, setFailed] = useState(false);
|
|
34
|
+
// Kept until this image is measured, rather than collapsing to min-h-24
|
|
35
|
+
// while it loads and tweening up from there.
|
|
36
|
+
const [startHeight] = useState(() => lastHeight.current);
|
|
37
|
+
// Every handler reads and writes the geometry here, never the rendered
|
|
38
|
+
// state: one touch frame delivers a pointermove per finger before React
|
|
39
|
+
// re-renders, and the second must build on the first.
|
|
40
|
+
const live = useRef({
|
|
41
|
+
frame: null,
|
|
42
|
+
view: null,
|
|
43
|
+
});
|
|
44
|
+
const place = useCallback((f, v) => {
|
|
45
|
+
live.current = { frame: f, view: v };
|
|
46
|
+
setFrame(f);
|
|
47
|
+
setView(v);
|
|
48
|
+
}, []);
|
|
49
|
+
// The height this view asks for is rendered, and the ResizeObserver
|
|
50
|
+
// reframes the view against every step of the canvas getting there.
|
|
51
|
+
const apply = useCallback((next) => {
|
|
52
|
+
if (live.current.frame === null)
|
|
53
|
+
return;
|
|
54
|
+
live.current.view = next;
|
|
55
|
+
setView(next);
|
|
56
|
+
}, []);
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
const el = canvasRef.current;
|
|
59
|
+
if (el === null || natural === null)
|
|
60
|
+
return;
|
|
61
|
+
const measure = () => {
|
|
62
|
+
const next = {
|
|
63
|
+
width: el.clientWidth,
|
|
64
|
+
height: el.clientHeight,
|
|
65
|
+
naturalWidth: natural.w,
|
|
66
|
+
naturalHeight: natural.h,
|
|
67
|
+
upscale: maximized,
|
|
68
|
+
};
|
|
69
|
+
const { frame: prev, view: v } = live.current;
|
|
70
|
+
lastHeight.current = maximized ? null : next.height;
|
|
71
|
+
place(next, prev === null || v === null ? fitView(next) : reframe(prev, next, v));
|
|
72
|
+
};
|
|
73
|
+
measure();
|
|
74
|
+
if (typeof ResizeObserver === "undefined")
|
|
75
|
+
return;
|
|
76
|
+
// Committed before the frame paints: otherwise each frame of a tween
|
|
77
|
+
// would show the canvas at its new height with the view placed for the
|
|
78
|
+
// old one, and the image would shake.
|
|
79
|
+
const ro = new ResizeObserver(() => flushSync(measure));
|
|
80
|
+
ro.observe(el);
|
|
81
|
+
return () => ro.disconnect();
|
|
82
|
+
}, [natural, maximized, place, lastHeight]);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const el = canvasRef.current;
|
|
85
|
+
if (el === null)
|
|
86
|
+
return;
|
|
87
|
+
const local = (clientX, clientY) => {
|
|
88
|
+
const r = el.getBoundingClientRect();
|
|
89
|
+
return { x: clientX - r.left, y: clientY - r.top };
|
|
90
|
+
};
|
|
91
|
+
// React's onWheel is passive, so it cannot stop the page from scrolling
|
|
92
|
+
// or Chrome's ctrl+wheel from zooming the whole page.
|
|
93
|
+
const onWheel = (e) => {
|
|
94
|
+
const { frame: f, view: v } = live.current;
|
|
95
|
+
if (f === null || v === null)
|
|
96
|
+
return;
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
const p = local(e.clientX, e.clientY);
|
|
99
|
+
apply(zoomAt(f, v, wheelFactor(e.deltaY, e.deltaMode, e.ctrlKey), p.x, p.y));
|
|
100
|
+
};
|
|
101
|
+
// Safari reports trackpad pinch as gesture events rather than ctrl+wheel.
|
|
102
|
+
let gestureStart = 1;
|
|
103
|
+
const onGestureStart = (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
gestureStart = live.current.view?.z ?? 1;
|
|
106
|
+
};
|
|
107
|
+
const onGestureChange = (e) => {
|
|
108
|
+
const { frame: f, view: v } = live.current;
|
|
109
|
+
if (f === null || v === null)
|
|
110
|
+
return;
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
const g = e;
|
|
113
|
+
const p = local(g.clientX, g.clientY);
|
|
114
|
+
apply(zoomAt(f, v, (gestureStart * g.scale) / v.z, p.x, p.y));
|
|
115
|
+
};
|
|
116
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
117
|
+
el.addEventListener("gesturestart", onGestureStart);
|
|
118
|
+
el.addEventListener("gesturechange", onGestureChange);
|
|
119
|
+
return () => {
|
|
120
|
+
el.removeEventListener("wheel", onWheel);
|
|
121
|
+
el.removeEventListener("gesturestart", onGestureStart);
|
|
122
|
+
el.removeEventListener("gesturechange", onGestureChange);
|
|
123
|
+
};
|
|
124
|
+
}, [apply]);
|
|
125
|
+
const atCentre = useCallback((op) => {
|
|
126
|
+
const { frame: f, view: v } = live.current;
|
|
127
|
+
if (f === null || v === null)
|
|
128
|
+
return;
|
|
129
|
+
apply(op(f, v, f.width / 2, f.height / 2));
|
|
130
|
+
}, [apply]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const onKey = (e) => {
|
|
133
|
+
// Ctrl/⌘ +/−/0 stay the browser's page zoom.
|
|
134
|
+
if (e.ctrlKey || e.metaKey || e.altKey)
|
|
135
|
+
return;
|
|
136
|
+
if (e.key === "+" || e.key === "=")
|
|
137
|
+
atCentre((f, v, x, y) => zoomAt(f, v, STEP, x, y));
|
|
138
|
+
else if (e.key === "-")
|
|
139
|
+
atCentre((f, v, x, y) => zoomAt(f, v, 1 / STEP, x, y));
|
|
140
|
+
else if (e.key === "0")
|
|
141
|
+
atCentre((f) => fitView(f));
|
|
142
|
+
else if (e.key === "1")
|
|
143
|
+
atCentre(actualAt);
|
|
144
|
+
else
|
|
145
|
+
return;
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
};
|
|
148
|
+
window.addEventListener("keydown", onKey);
|
|
149
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
150
|
+
}, [atCentre]);
|
|
151
|
+
// Pointer bookkeeping: every active pointer's last position, plus where
|
|
152
|
+
// the gesture started so a click can be told apart from a drag.
|
|
153
|
+
const pointers = useRef(new Map());
|
|
154
|
+
const moved = useRef(false);
|
|
155
|
+
const downAt = useRef({ x: 0, y: 0 });
|
|
156
|
+
const localOf = (e) => {
|
|
157
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
158
|
+
return { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
159
|
+
};
|
|
160
|
+
const onPointerDown = (e) => {
|
|
161
|
+
if (e.pointerType === "mouse" && e.button !== 0)
|
|
162
|
+
return;
|
|
163
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
164
|
+
const p = localOf(e);
|
|
165
|
+
pointers.current.set(e.pointerId, p);
|
|
166
|
+
if (pointers.current.size === 1) {
|
|
167
|
+
moved.current = false;
|
|
168
|
+
downAt.current = p;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// A second finger makes this a pinch, never a tap.
|
|
172
|
+
moved.current = true;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const onPointerMove = (e) => {
|
|
176
|
+
const prev = pointers.current.get(e.pointerId);
|
|
177
|
+
const { frame, view } = live.current;
|
|
178
|
+
if (prev === undefined || frame === null || view === null)
|
|
179
|
+
return;
|
|
180
|
+
const p = localOf(e);
|
|
181
|
+
if (!moved.current &&
|
|
182
|
+
Math.hypot(p.x - downAt.current.x, p.y - downAt.current.y) > TAP_SLOP) {
|
|
183
|
+
moved.current = true;
|
|
184
|
+
}
|
|
185
|
+
if (pointers.current.size === 2) {
|
|
186
|
+
const [a, b] = [...pointers.current.entries()];
|
|
187
|
+
const other = a?.[0] === e.pointerId ? b?.[1] : a?.[1];
|
|
188
|
+
if (other === undefined)
|
|
189
|
+
return;
|
|
190
|
+
const before = Math.hypot(prev.x - other.x, prev.y - other.y);
|
|
191
|
+
const after = Math.hypot(p.x - other.x, p.y - other.y);
|
|
192
|
+
const mid = { x: (p.x + other.x) / 2, y: (p.y + other.y) / 2 };
|
|
193
|
+
const shift = { x: (p.x - prev.x) / 2, y: (p.y - prev.y) / 2 };
|
|
194
|
+
const zoomed = before > 0 ? zoomAt(frame, view, after / before, mid.x, mid.y) : view;
|
|
195
|
+
apply(panBy(frame, zoomed, shift.x, shift.y));
|
|
196
|
+
}
|
|
197
|
+
else if (pointers.current.size === 1 && moved.current) {
|
|
198
|
+
if (!isFitted(frame, view)) {
|
|
199
|
+
setDragging(true);
|
|
200
|
+
apply(panBy(frame, view, p.x - prev.x, p.y - prev.y));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
pointers.current.set(e.pointerId, p);
|
|
204
|
+
};
|
|
205
|
+
const onPointerUp = (e) => {
|
|
206
|
+
if (!pointers.current.delete(e.pointerId))
|
|
207
|
+
return;
|
|
208
|
+
if (pointers.current.size > 0)
|
|
209
|
+
return;
|
|
210
|
+
setDragging(false);
|
|
211
|
+
const { frame, view } = live.current;
|
|
212
|
+
if (moved.current || frame === null || view === null)
|
|
213
|
+
return;
|
|
214
|
+
const p = localOf(e);
|
|
215
|
+
apply(toggleAt(frame, view, p.x, p.y));
|
|
216
|
+
};
|
|
217
|
+
const onPointerCancel = (e) => {
|
|
218
|
+
pointers.current.delete(e.pointerId);
|
|
219
|
+
if (pointers.current.size === 0)
|
|
220
|
+
setDragging(false);
|
|
221
|
+
};
|
|
222
|
+
const ready = frame !== null && view !== null;
|
|
223
|
+
const fitted = !ready || isFitted(frame, view);
|
|
224
|
+
const height = maximized
|
|
225
|
+
? undefined
|
|
226
|
+
: (held ??
|
|
227
|
+
(ready ? canvasHeight(frame, view.z) : (startHeight ?? undefined)));
|
|
228
|
+
// From the last measured height, which mid-tween is the one on screen.
|
|
229
|
+
// Only between two restored heights at one width: maximize, restore and
|
|
230
|
+
// the first load land at once, and a resized window should not trail.
|
|
231
|
+
const tweened = useRef(null);
|
|
232
|
+
useLayoutEffect(() => {
|
|
233
|
+
const el = canvasRef.current;
|
|
234
|
+
const prev = tweened.current;
|
|
235
|
+
const from = live.current.frame;
|
|
236
|
+
tweened.current =
|
|
237
|
+
height === undefined ? null : { height, width: from?.width ?? null };
|
|
238
|
+
// happy-dom has no Web Animations.
|
|
239
|
+
if (el === null || typeof el.getAnimations !== "function")
|
|
240
|
+
return;
|
|
241
|
+
for (const a of el.getAnimations())
|
|
242
|
+
a.cancel();
|
|
243
|
+
if (from === null || prev === null || height === undefined)
|
|
244
|
+
return;
|
|
245
|
+
if (prev.width !== null && prev.width !== from.width)
|
|
246
|
+
return;
|
|
247
|
+
if (matchMedia("(prefers-reduced-motion: reduce)").matches)
|
|
248
|
+
return;
|
|
249
|
+
// The capped height, not the requested one: a tween to a height past
|
|
250
|
+
// the pane's cap would stop dead at the cap partway through its curve.
|
|
251
|
+
const to = el.clientHeight;
|
|
252
|
+
if (to !== from.height)
|
|
253
|
+
el.animate([{ height: `${from.height}px` }, { height: `${to}px` }], TWEEN);
|
|
254
|
+
}, [height]);
|
|
255
|
+
// − and + give way to pinch on a phone, where the title bar has no room
|
|
256
|
+
// for them beside the filename.
|
|
257
|
+
const controls = (_jsxs("span", { className: "inline-flex shrink-0 items-center gap-0.5", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "zoom out", disabled: !ready || isAtMin(frame, view), className: "max-sm:hidden", onClick: () => atCentre((f, v, x, y) => zoomAt(f, v, 1 / STEP, x, y)), children: _jsx(MinusIcon, {}) }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { type: "button", size: "xs", variant: "ghost", disabled: !ready, "aria-label": "zoom and display options", className: "min-w-12 font-normal text-muted-foreground tabular-nums", children: [ready ? `${Math.round(view.z * 100)}%` : "–", _jsx(ChevronDownIcon, {})] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-44", children: [_jsxs(DropdownMenuItem, { onSelect: () => atCentre((f) => fitView(f)), children: ["Fit", _jsx(DropdownMenuShortcut, { className: "pointer-coarse:hidden", children: "0" })] }), _jsxs(DropdownMenuItem, { onSelect: () => atCentre(actualAt), children: ["100%", _jsx(DropdownMenuShortcut, { className: "pointer-coarse:hidden", children: "1" })] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Background" }), _jsxs(DropdownMenuRadioGroup, { value: look.background, onValueChange: (v) => onLookChange({
|
|
258
|
+
...look,
|
|
259
|
+
background: v,
|
|
260
|
+
}), children: [_jsx(DropdownMenuRadioItem, { value: "black", children: "Black" }), _jsx(DropdownMenuRadioItem, { value: "checker", children: "Checkerboard" })] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Scaling" }), _jsxs(DropdownMenuRadioGroup, { value: look.smoothing, onValueChange: (v) => onLookChange({ ...look, smoothing: v }), children: [_jsx(DropdownMenuRadioItem, { value: "smooth", children: "Smooth" }), _jsx(DropdownMenuRadioItem, { value: "pixelated", children: "Pixelated" })] })] })] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "zoom in", disabled: !ready || isAtMax(frame, view), className: "max-sm:hidden", onClick: () => atCentre((f, v, x, y) => zoomAt(f, v, STEP, x, y)), children: _jsx(PlusIcon, {}) })] }));
|
|
261
|
+
// Nothing to measure or zoom; the static image's alt text and
|
|
262
|
+
// broken-image icon are the error.
|
|
263
|
+
if (failed) {
|
|
264
|
+
return (_jsx("img", { src: src, alt: alt, className: cn("mx-auto max-h-(--attachment-pane-height) rounded-md object-contain", className) }));
|
|
265
|
+
}
|
|
266
|
+
return (_jsxs(_Fragment, { children: [toolbar !== null && createPortal(controls, toolbar), _jsxs("div", { ref: canvasRef, "data-testid": "image-canvas", "data-zoom": view === null ? undefined : view.z.toFixed(4), "data-fit": frame === null ? undefined : fitScale(frame).toFixed(4), className: cn("relative touch-none overflow-hidden select-none", look.background === "black" && "bg-black", maximized ? "h-full" : "max-h-(--attachment-pane-height) min-h-24", fitted
|
|
267
|
+
? "cursor-zoom-in"
|
|
268
|
+
: dragging
|
|
269
|
+
? "cursor-grabbing"
|
|
270
|
+
: "cursor-grab", className), style: height === undefined ? undefined : { height }, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerCancel, children: [look.background === "checker" && natural !== null && view !== null && (_jsx("div", { "aria-hidden": true, "data-testid": "image-checker", className: "absolute", style: {
|
|
271
|
+
left: view.x,
|
|
272
|
+
top: view.y,
|
|
273
|
+
width: natural.w * view.z,
|
|
274
|
+
height: natural.h * view.z,
|
|
275
|
+
background: CHECKER,
|
|
276
|
+
borderRadius: fitted ? 6 : 0,
|
|
277
|
+
} })), _jsx("img", { src: src, alt: alt, draggable: false, onError: () => {
|
|
278
|
+
lastHeight.current = null;
|
|
279
|
+
setFailed(true);
|
|
280
|
+
}, onLoad: (e) => setNatural({
|
|
281
|
+
w: e.currentTarget.naturalWidth,
|
|
282
|
+
h: e.currentTarget.naturalHeight,
|
|
283
|
+
}), className: cn("absolute top-0 left-0 max-w-none origin-top-left", view === null && "invisible"), style: natural === null || view === null
|
|
284
|
+
? undefined
|
|
285
|
+
: {
|
|
286
|
+
width: natural.w,
|
|
287
|
+
height: natural.h,
|
|
288
|
+
transform: `translate(${view.x}px, ${view.y}px) scale(${view.z})`,
|
|
289
|
+
// A static image's rounded-md, divided back out of the
|
|
290
|
+
// scale; zoomed in, a rounded corner would read as the
|
|
291
|
+
// image's own edge.
|
|
292
|
+
borderRadius: fitted ? 6 / view.z : 0,
|
|
293
|
+
// Shrunk, nearest-neighbour drops whole rows and columns;
|
|
294
|
+
// pixelated only means something once an image pixel is
|
|
295
|
+
// bigger than a screen pixel.
|
|
296
|
+
imageRendering: look.smoothing === "pixelated" && view.z > 1
|
|
297
|
+
? "pixelated"
|
|
298
|
+
: undefined,
|
|
299
|
+
} })] })] }));
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=image-canvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-canvas.js","sourceRoot":"","sources":["../../src/attachment/image-canvas.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the attachment viewer paints an image: what shows through its
|
|
3
|
+
* transparent pixels, and whether a zoomed-in pixel stays a square.
|
|
4
|
+
*/
|
|
5
|
+
export type ImageLook = {
|
|
6
|
+
background: "black" | "checker";
|
|
7
|
+
smoothing: "smooth" | "pixelated";
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_IMAGE_LOOK: ImageLook;
|
|
10
|
+
/** Parse a stored payload; an unknown or missing field falls back to its default. */
|
|
11
|
+
export declare function parseImageLook(raw: string | null): ImageLook;
|
|
12
|
+
/**
|
|
13
|
+
* The viewer's image look kept in localStorage under `storageKey`, following
|
|
14
|
+
* other tabs. Hand the pair to the viewer as `imageLook` and
|
|
15
|
+
* `onImageLookChange`. Name the key after the app; there is no default.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useStoredImageLook(storageKey: string): [ImageLook, (look: ImageLook) => void];
|
|
18
|
+
//# sourceMappingURL=image-look.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-look.d.ts","sourceRoot":"","sources":["../../src/attachment/image-look.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,SAAS,EAAE,QAAQ,GAAG,WAAW,CAAC;CACnC,CAAC;AAIF,eAAO,MAAM,kBAAkB,EAAE,SAGhC,CAAC;AAEF,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAqB5D;AA+CD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,GACjB,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC,CAWxC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
// Checkerboard under the image only: an opaque image then looks the same as
|
|
3
|
+
// with no background at all, and a transparent one shows where it ends.
|
|
4
|
+
export const DEFAULT_IMAGE_LOOK = {
|
|
5
|
+
background: "checker",
|
|
6
|
+
smoothing: "smooth",
|
|
7
|
+
};
|
|
8
|
+
/** Parse a stored payload; an unknown or missing field falls back to its default. */
|
|
9
|
+
export function parseImageLook(raw) {
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = raw === null ? null : JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
parsed = null;
|
|
16
|
+
}
|
|
17
|
+
const stored = typeof parsed === "object" && parsed !== null
|
|
18
|
+
? parsed
|
|
19
|
+
: {};
|
|
20
|
+
return {
|
|
21
|
+
background: stored.background === "black" || stored.background === "checker"
|
|
22
|
+
? stored.background
|
|
23
|
+
: DEFAULT_IMAGE_LOOK.background,
|
|
24
|
+
smoothing: stored.smoothing === "smooth" || stored.smoothing === "pixelated"
|
|
25
|
+
? stored.smoothing
|
|
26
|
+
: DEFAULT_IMAGE_LOOK.smoothing,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// One cached value per key, shared by every hook reading it, so a choice made
|
|
30
|
+
// in one viewer is what the next one opens with.
|
|
31
|
+
const looks = new Map();
|
|
32
|
+
const listeners = new Set();
|
|
33
|
+
function read(key) {
|
|
34
|
+
let look = looks.get(key);
|
|
35
|
+
if (look === undefined) {
|
|
36
|
+
try {
|
|
37
|
+
look = parseImageLook(localStorage.getItem(key));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Storage may be unavailable (private mode); the defaults still render.
|
|
41
|
+
look = DEFAULT_IMAGE_LOOK;
|
|
42
|
+
}
|
|
43
|
+
looks.set(key, look);
|
|
44
|
+
}
|
|
45
|
+
return look;
|
|
46
|
+
}
|
|
47
|
+
function write(key, look) {
|
|
48
|
+
looks.set(key, look);
|
|
49
|
+
try {
|
|
50
|
+
localStorage.setItem(key, JSON.stringify(look));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Unsaved, but this page still switches.
|
|
54
|
+
}
|
|
55
|
+
for (const listener of listeners)
|
|
56
|
+
listener();
|
|
57
|
+
}
|
|
58
|
+
// Another tab's choice applies here too.
|
|
59
|
+
function onStorage(e) {
|
|
60
|
+
if (e.key === null || !looks.has(e.key))
|
|
61
|
+
return;
|
|
62
|
+
looks.set(e.key, parseImageLook(e.newValue));
|
|
63
|
+
for (const listener of listeners)
|
|
64
|
+
listener();
|
|
65
|
+
}
|
|
66
|
+
function subscribe(listener) {
|
|
67
|
+
if (listeners.size === 0)
|
|
68
|
+
window.addEventListener("storage", onStorage);
|
|
69
|
+
listeners.add(listener);
|
|
70
|
+
return () => {
|
|
71
|
+
listeners.delete(listener);
|
|
72
|
+
if (listeners.size === 0)
|
|
73
|
+
window.removeEventListener("storage", onStorage);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The viewer's image look kept in localStorage under `storageKey`, following
|
|
78
|
+
* other tabs. Hand the pair to the viewer as `imageLook` and
|
|
79
|
+
* `onImageLookChange`. Name the key after the app; there is no default.
|
|
80
|
+
*/
|
|
81
|
+
export function useStoredImageLook(storageKey) {
|
|
82
|
+
const look = useSyncExternalStore(subscribe, () => read(storageKey), () => DEFAULT_IMAGE_LOOK);
|
|
83
|
+
const setLook = useCallback((next) => write(storageKey, next), [storageKey]);
|
|
84
|
+
return [look, setLook];
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=image-look.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-look.js","sourceRoot":"","sources":["../../src/attachment/image-look.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry for the attachment viewer's zoomable image. Everything is
|
|
3
|
+
* in the canvas's own pixel space: `x`/`y` is where the image's top-left sits,
|
|
4
|
+
* `z` is display pixels per natural pixel.
|
|
5
|
+
*/
|
|
6
|
+
export type Frame = {
|
|
7
|
+
/** Canvas size. */
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
/** Image's natural size. */
|
|
11
|
+
naturalWidth: number;
|
|
12
|
+
naturalHeight: number;
|
|
13
|
+
/** Whether fit may enlarge an image smaller than the canvas (maximized). */
|
|
14
|
+
upscale: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type View = {
|
|
17
|
+
z: number;
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
};
|
|
21
|
+
/** Deepest zoom, in natural pixels: far enough to read a single pixel. */
|
|
22
|
+
export declare const MAX_NATURAL_SCALE = 8;
|
|
23
|
+
export declare function fitScale(f: Frame): number;
|
|
24
|
+
/**
|
|
25
|
+
* An upscaled fit can already sit past the natural-pixel cap (a 48px icon
|
|
26
|
+
* maximized), so the ceiling always leaves room to double whatever fit is.
|
|
27
|
+
*/
|
|
28
|
+
export declare function maxScale(f: Frame): number;
|
|
29
|
+
/**
|
|
30
|
+
* Never smaller than fit, except that actual pixels stay reachable when an
|
|
31
|
+
* upscaled fit (maximized) is already larger than them.
|
|
32
|
+
*/
|
|
33
|
+
export declare function minScale(f: Frame): number;
|
|
34
|
+
export declare function fitView(f: Frame): View;
|
|
35
|
+
/** Within a hair of fit counts as fit, so float drift cannot strand a view. */
|
|
36
|
+
export declare function isFitted(f: Frame, v: View): boolean;
|
|
37
|
+
export declare function isAtMin(f: Frame, v: View): boolean;
|
|
38
|
+
export declare function isAtMax(f: Frame, v: View): boolean;
|
|
39
|
+
export declare function clampView(f: Frame, v: View): View;
|
|
40
|
+
/**
|
|
41
|
+
* The height a restored (not maximized) canvas asks for at zoom `z`: the
|
|
42
|
+
* image's own, so zooming in grows the dialog until its CSS cap takes over
|
|
43
|
+
* and only then starts panning. Never shorter than at fit, so the request
|
|
44
|
+
* does not depend on the measured height it produces. Whole pixels, because
|
|
45
|
+
* the canvas is measured back with clientHeight.
|
|
46
|
+
*/
|
|
47
|
+
export declare function canvasHeight(f: Frame, z: number): number;
|
|
48
|
+
/**
|
|
49
|
+
* Carry a view across a canvas resize. A fitted view refits; any other keeps
|
|
50
|
+
* whatever sat at the canvas centre there — the dialog is centred, so that
|
|
51
|
+
* is also what keeps the image still on screen while the canvas grows.
|
|
52
|
+
*/
|
|
53
|
+
export declare function reframe(prev: Frame, next: Frame, v: View): View;
|
|
54
|
+
/** Scale by `factor` keeping canvas point (px, py) over the same image pixel. */
|
|
55
|
+
export declare function zoomAt(f: Frame, v: View, factor: number, px: number, py: number): View;
|
|
56
|
+
/** Actual pixels (100%), keeping canvas point (px, py) over the same image pixel. */
|
|
57
|
+
export declare function actualAt(f: Frame, v: View, px: number, py: number): View;
|
|
58
|
+
export declare function panBy(f: Frame, v: View, dx: number, dy: number): View;
|
|
59
|
+
/**
|
|
60
|
+
* Click / tap: from fit, zoom at the pointer to actual pixels or twice the
|
|
61
|
+
* fit, whichever is deeper — a fit of 0.8 jumping to 1.0 would look like
|
|
62
|
+
* nothing happened. From anything else, back to fit.
|
|
63
|
+
*/
|
|
64
|
+
export declare function toggleAt(f: Frame, v: View, px: number, py: number): View;
|
|
65
|
+
/** Wheel delta → multiplicative factor. Pinch arrives as ctrl+wheel with small deltas. */
|
|
66
|
+
export declare function wheelFactor(deltaY: number, deltaMode: number, ctrlKey: boolean): number;
|
|
67
|
+
//# sourceMappingURL=image-zoom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-zoom.d.ts","sourceRoot":"","sources":["../../src/attachment/image-zoom.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAMzC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAEzC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAEzC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAOtC;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAEnD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAElD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAElD;AASD,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,CAOjD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,CAO/D;AAED,iFAAiF;AACjF,wBAAgB,MAAM,CACpB,CAAC,EAAE,KAAK,EACR,CAAC,EAAE,IAAI,EACP,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT,IAAI,CAIN;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAErE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAIxE;AAED,0FAA0F;AAC1F,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,GACf,MAAM,CAIR"}
|