@scrawl-board/board 0.1.0-beta.10 → 0.1.0-beta.11
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/dist/browser.d.ts +24 -1
- package/dist/browser.js +58 -7
- package/dist/index.d.ts +130 -9
- package/dist/index.js +1397 -630
- package/dist/react.d.ts +130 -9
- package/dist/react.js +1397 -630
- package/dist/styles.css +383 -94
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5939,12 +5939,35 @@ function toDataUri(bytes, mediaType) {
|
|
|
5939
5939
|
return `data:${mediaType};base64,${btoa(binary)}`;
|
|
5940
5940
|
}
|
|
5941
5941
|
//#endregion
|
|
5942
|
+
//#region src/persistence/adapters/imageDecode.ts
|
|
5943
|
+
var ORIENTED = { imageOrientation: "from-image" };
|
|
5944
|
+
/**
|
|
5945
|
+
* Decodes image bytes the way a browser displays them: EXIF orientation
|
|
5946
|
+
* applied, so a phone photo comes out upright and its width/height are the
|
|
5947
|
+
* displayed ones.
|
|
5948
|
+
*
|
|
5949
|
+
* `"from-image"` is already the default in every browser this SDK supports
|
|
5950
|
+
* (Chrome/Edge 112+, Firefox 111+, Safari 16+); passing it just makes that
|
|
5951
|
+
* explicit. Engines from before `"none"` was renamed to `"from-image"` reject
|
|
5952
|
+
* the value with a TypeError — they get their own default decode instead of
|
|
5953
|
+
* no image at all.
|
|
5954
|
+
*/
|
|
5955
|
+
async function decodeImageBitmap(blob) {
|
|
5956
|
+
try {
|
|
5957
|
+
return await createImageBitmap(blob, ORIENTED);
|
|
5958
|
+
} catch (error) {
|
|
5959
|
+
if (error instanceof TypeError) return createImageBitmap(blob);
|
|
5960
|
+
throw error;
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
//#endregion
|
|
5942
5964
|
//#region src/persistence/adapters/assetIngestion.ts
|
|
5965
|
+
/** Displayed dimensions — EXIF orientation applied, so a portrait phone photo measures as portrait. */
|
|
5943
5966
|
async function decodeDimensionsWithImageBitmap(bytes, mediaType) {
|
|
5944
5967
|
const blob = new Blob([bytes], { type: mediaType });
|
|
5945
5968
|
let bitmap;
|
|
5946
5969
|
try {
|
|
5947
|
-
bitmap = await
|
|
5970
|
+
bitmap = await decodeImageBitmap(blob);
|
|
5948
5971
|
} catch (cause) {
|
|
5949
5972
|
throw new AssetResolutionError("decode-failed", false, cause instanceof Error ? cause.message : String(cause));
|
|
5950
5973
|
}
|
|
@@ -6134,10 +6157,7 @@ var AssetTextureCache = class {
|
|
|
6134
6157
|
}
|
|
6135
6158
|
let texture;
|
|
6136
6159
|
try {
|
|
6137
|
-
|
|
6138
|
-
texture = new THREE.Texture(bitmap);
|
|
6139
|
-
texture.colorSpace = THREE.SRGBColorSpace;
|
|
6140
|
-
texture.needsUpdate = true;
|
|
6160
|
+
texture = uprightBitmapTexture(await decodeImageBitmap(new Blob([outcome.bytes], { type: outcome.mediaType })));
|
|
6141
6161
|
} catch (cause) {
|
|
6142
6162
|
onError(toDecodeError(cause, ref));
|
|
6143
6163
|
return;
|
|
@@ -6192,6 +6212,23 @@ var AssetTextureCache = class {
|
|
|
6192
6212
|
function toDecodeError(cause, ref) {
|
|
6193
6213
|
return new AssetResolutionError("decode-failed", false, cause instanceof Error ? cause.message : String(cause), ref);
|
|
6194
6214
|
}
|
|
6215
|
+
/**
|
|
6216
|
+
* A texture that shows `bitmap` the right way up. Three.js normally flips an
|
|
6217
|
+
* image vertically on upload (`flipY`) to match WebGL's bottom-up texture
|
|
6218
|
+
* rows, but WebGL ignores that flag for ImageBitmap sources — so every
|
|
6219
|
+
* bitmap-backed image drew upside-down. The flip happens in the texture's UV
|
|
6220
|
+
* transform instead, which every material using this shared texture (built-in
|
|
6221
|
+
* images and Custom SceneImages alike) picks up.
|
|
6222
|
+
*/
|
|
6223
|
+
function uprightBitmapTexture(bitmap) {
|
|
6224
|
+
const texture = new THREE.Texture(bitmap);
|
|
6225
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
6226
|
+
texture.flipY = false;
|
|
6227
|
+
texture.repeat.set(1, -1);
|
|
6228
|
+
texture.offset.set(0, 1);
|
|
6229
|
+
texture.needsUpdate = true;
|
|
6230
|
+
return texture;
|
|
6231
|
+
}
|
|
6195
6232
|
//#endregion
|
|
6196
6233
|
//#region src/renderer/canvas/raycast.ts
|
|
6197
6234
|
var origin$1 = new THREE.Vector3();
|
|
@@ -19469,7 +19506,7 @@ var ScrawlEngine = class {
|
|
|
19469
19506
|
this.eraserRadius = 2;
|
|
19470
19507
|
this.tableRows = 3;
|
|
19471
19508
|
this.tableCols = 3;
|
|
19472
|
-
this.tool = "
|
|
19509
|
+
this.tool = "select";
|
|
19473
19510
|
this.voter = null;
|
|
19474
19511
|
this.stampKind = "emoji:grinning";
|
|
19475
19512
|
this.timerDurationMs = TIMER_DEFAULT_DURATION_MS;
|
|
@@ -21302,7 +21339,8 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
21302
21339
|
const storeListeners = /* @__PURE__ */ new Set();
|
|
21303
21340
|
const eventListeners = /* @__PURE__ */ new Map();
|
|
21304
21341
|
const documentListeners = /* @__PURE__ */ new Set();
|
|
21305
|
-
let tool = "
|
|
21342
|
+
let tool = "select";
|
|
21343
|
+
let inlineEditing = null;
|
|
21306
21344
|
let style = Object.freeze({
|
|
21307
21345
|
inkColor: "#1C1C1E",
|
|
21308
21346
|
highlightColor: HIGHLIGHT_COLORS.yellow,
|
|
@@ -21877,6 +21915,7 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
21877
21915
|
readOnly,
|
|
21878
21916
|
selection: Object.freeze([...selection]),
|
|
21879
21917
|
focusedItem: computeFocusedItem(),
|
|
21918
|
+
inlineEditing,
|
|
21880
21919
|
strokeCount: [...document.all()].length,
|
|
21881
21920
|
objectCount: allObjects().length,
|
|
21882
21921
|
canUndo: history.canUndo,
|
|
@@ -22503,6 +22542,8 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
22503
22542
|
setInlineEditing(target) {
|
|
22504
22543
|
assertActive();
|
|
22505
22544
|
engine?.setInlineEditing(target);
|
|
22545
|
+
inlineEditing = target ? { ...target } : null;
|
|
22546
|
+
changed();
|
|
22506
22547
|
},
|
|
22507
22548
|
centerOn(point) {
|
|
22508
22549
|
assertActive();
|
|
@@ -22948,6 +22989,15 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
22948
22989
|
query: {
|
|
22949
22990
|
get: readObject,
|
|
22950
22991
|
all: () => allObjects(),
|
|
22992
|
+
bounds: (id) => {
|
|
22993
|
+
const box = document.bbox(id);
|
|
22994
|
+
return box ? {
|
|
22995
|
+
minX: box.minX,
|
|
22996
|
+
minY: box.minY,
|
|
22997
|
+
maxX: box.maxX,
|
|
22998
|
+
maxY: box.maxY
|
|
22999
|
+
} : void 0;
|
|
23000
|
+
},
|
|
22951
23001
|
search: (query) => searchBoard(query, {
|
|
22952
23002
|
notes: document.allNotes(),
|
|
22953
23003
|
texts: document.allTexts(),
|
|
@@ -22958,6 +23008,7 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
22958
23008
|
})
|
|
22959
23009
|
},
|
|
22960
23010
|
comments: {
|
|
23011
|
+
list: () => comments,
|
|
22961
23012
|
sync(next) {
|
|
22962
23013
|
comments = Object.freeze([...next]);
|
|
22963
23014
|
if (engine) engine.setComments(comments);
|
|
@@ -24402,9 +24453,17 @@ var PATHS = {
|
|
|
24402
24453
|
"shape:curved-connector": "M5 18C5 8 19 16 19 6M3 18h4v4H3ZM17 2h4v4h-4Z",
|
|
24403
24454
|
"shape:polyline-connector": "m5 12 7-7 7 5-4 9-10-7M3 10h4v4H3ZM10 3h4v4h-4ZM17 8h4v4h-4ZM13 17h4v4h-4Z",
|
|
24404
24455
|
chevronDown: "m6 9 6 6 6-6",
|
|
24456
|
+
chevronRight: "m9 6 6 6-6 6",
|
|
24457
|
+
notebook: "M4 4.5A1.5 1.5 0 0 1 5.5 3H18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H6a2 2 0 0 1-2-2ZM4 19a2 2 0 0 1 2-2h13",
|
|
24458
|
+
section: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z",
|
|
24459
|
+
page: "M7 3h7l4 4v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM14 3v4h4",
|
|
24405
24460
|
pages: "M7 3h13v16H7ZM4 7H2v15h13v-1M10 7h7M10 11h7M10 15h4",
|
|
24406
24461
|
close: "M5 5l14 14M19 5 5 19",
|
|
24407
24462
|
check: "m5 12 5 5L20 7",
|
|
24463
|
+
activity: "M3 12h4l3-8 4 16 3-8h4",
|
|
24464
|
+
shield: "M12 3 4 6v6c0 5 3.5 8 8 9 4.5-1 8-4 8-9V6Z",
|
|
24465
|
+
moreVertical: "M12 5h.01M12 12h.01M12 19h.01",
|
|
24466
|
+
share: "M18 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM6 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM18 16a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM8.6 10.6l6.8-3.2M8.6 13.4l6.8 3.2",
|
|
24408
24467
|
lock: "M5 10h14v11H5ZM8 10V6a4 4 0 0 1 8 0v4",
|
|
24409
24468
|
unlock: "M5 10h14v11H5ZM8 10V6a4 4 0 0 1 8 0",
|
|
24410
24469
|
duplicate: "M8 8h13v13H8ZM16 8V3H3v13h5",
|
|
@@ -24414,8 +24473,8 @@ var PATHS = {
|
|
|
24414
24473
|
color: "M12 3C9 8 5 11 5 15a7 7 0 0 0 14 0c0-4-4-7-7-12Z",
|
|
24415
24474
|
none: "M4 4l16 16M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Z",
|
|
24416
24475
|
search: "M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14ZM15 15l6 6",
|
|
24417
|
-
import: "M12
|
|
24418
|
-
export: "M12
|
|
24476
|
+
import: "M12 16V3M7 8l5-5 5 5M4 16v5h16v-5",
|
|
24477
|
+
export: "M12 3v12M7 10l5 5 5-5M4 16v5h16v-5",
|
|
24419
24478
|
fit: "M3 9V3h6M15 3h6v6M21 15v6h-6M9 21H3v-6M8 8h8v8H8Z",
|
|
24420
24479
|
select: "m5 3 14 9-7 1-3 7Z",
|
|
24421
24480
|
marker: "m4 20 2-6L16 4a2 2 0 0 1 3 3L9 17Zm2-6 3 3M14 6l3 3",
|
|
@@ -24444,7 +24503,8 @@ var PATHS = {
|
|
|
24444
24503
|
undo: "M9 5 4 10l5 5M4 10h10a6 6 0 0 1 6 6v3",
|
|
24445
24504
|
redo: "m15 5 5 5-5 5M20 10H10a6 6 0 0 0-6 6v3",
|
|
24446
24505
|
addRow: "M4 6h16M4 12h16M4 18h10M17 16v5M14.5 18.5h5",
|
|
24447
|
-
addColumn: "M6 4v16M12 4v16M18 4v10M16 16.5h5M18.5 14v5"
|
|
24506
|
+
addColumn: "M6 4v16M12 4v16M18 4v10M16 16.5h5M18.5 14v5",
|
|
24507
|
+
deleteRow: "M4 6h16M4 12h16M4 18h10M15 16.5l5 5M20 16.5l-5 5"
|
|
24448
24508
|
};
|
|
24449
24509
|
function ToolIcon({ tool }) {
|
|
24450
24510
|
return /* @__PURE__ */ jsx("svg", {
|
|
@@ -24501,127 +24561,197 @@ function NavigatorName({ name, kind, onRename }) {
|
|
|
24501
24561
|
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "marker" })
|
|
24502
24562
|
});
|
|
24503
24563
|
}
|
|
24504
|
-
/**
|
|
24505
|
-
|
|
24506
|
-
* left) and a breadcrumb on the canvas, just past the sidebar and tool rail.
|
|
24507
|
-
* Both stay visible whether or not the sidebar is open.
|
|
24508
|
-
*/
|
|
24509
|
-
function NotebookNavigation({ controller, snapshot, onOpen }) {
|
|
24564
|
+
/** A crumb (or the switcher) with its menu: pick an item, rename one, or add one. */
|
|
24565
|
+
function NavigatorMenu({ label, items, activeId, kind, mutable, onSelect, onAdd, onRename, children, className, triggerLabel, triggerProps }) {
|
|
24510
24566
|
const [open, setOpen] = useState(false);
|
|
24511
24567
|
const root = useRef(null);
|
|
24512
24568
|
const trigger = useRef(null);
|
|
24513
24569
|
const menuId = useId();
|
|
24514
|
-
const notebook = snapshot.notebooks.find((item) => item.id === snapshot.activeNotebookId);
|
|
24515
|
-
const section = snapshot.sections.find((item) => item.id === snapshot.activeSectionId);
|
|
24516
|
-
const page = snapshot.pages.find((item) => item.id === snapshot.activePageId);
|
|
24517
|
-
const mutable = snapshot.status === "ready" && !snapshot.readOnly;
|
|
24518
24570
|
useEffect(() => {
|
|
24519
24571
|
if (!open) return;
|
|
24520
24572
|
const outside = (event) => {
|
|
24521
24573
|
if (!root.current?.contains(event.target)) setOpen(false);
|
|
24522
24574
|
};
|
|
24575
|
+
const escape = (event) => {
|
|
24576
|
+
if (event.key !== "Escape") return;
|
|
24577
|
+
setOpen(false);
|
|
24578
|
+
trigger.current?.focus();
|
|
24579
|
+
};
|
|
24523
24580
|
document.addEventListener("pointerdown", outside);
|
|
24524
|
-
|
|
24581
|
+
document.addEventListener("keydown", escape);
|
|
24582
|
+
return () => {
|
|
24583
|
+
document.removeEventListener("pointerdown", outside);
|
|
24584
|
+
document.removeEventListener("keydown", escape);
|
|
24585
|
+
};
|
|
24525
24586
|
}, [open]);
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
|
|
24532
|
-
|
|
24533
|
-
|
|
24534
|
-
|
|
24535
|
-
|
|
24536
|
-
|
|
24537
|
-
|
|
24538
|
-
|
|
24539
|
-
|
|
24540
|
-
|
|
24541
|
-
|
|
24542
|
-
|
|
24543
|
-
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24548
|
-
|
|
24549
|
-
|
|
24550
|
-
|
|
24551
|
-
|
|
24552
|
-
|
|
24553
|
-
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
|
|
24560
|
-
|
|
24561
|
-
|
|
24562
|
-
|
|
24563
|
-
|
|
24564
|
-
"
|
|
24565
|
-
|
|
24566
|
-
|
|
24567
|
-
|
|
24568
|
-
|
|
24569
|
-
|
|
24570
|
-
|
|
24571
|
-
setOpen(false);
|
|
24572
|
-
onOpen();
|
|
24573
|
-
trigger.current?.focus();
|
|
24574
|
-
},
|
|
24575
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
24587
|
+
const close = () => {
|
|
24588
|
+
setOpen(false);
|
|
24589
|
+
trigger.current?.focus();
|
|
24590
|
+
};
|
|
24591
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24592
|
+
className,
|
|
24593
|
+
"data-open": open ? "" : void 0,
|
|
24594
|
+
ref: root,
|
|
24595
|
+
onKeyDown: (event) => {
|
|
24596
|
+
event.stopPropagation();
|
|
24597
|
+
if (event.key === "Escape") {
|
|
24598
|
+
event.preventDefault();
|
|
24599
|
+
close();
|
|
24600
|
+
}
|
|
24601
|
+
},
|
|
24602
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
24603
|
+
...triggerProps,
|
|
24604
|
+
ref: trigger,
|
|
24605
|
+
type: "button",
|
|
24606
|
+
"aria-label": triggerLabel,
|
|
24607
|
+
"aria-expanded": open,
|
|
24608
|
+
"aria-controls": menuId,
|
|
24609
|
+
"aria-haspopup": "true",
|
|
24610
|
+
onClick: () => setOpen((value) => !value),
|
|
24611
|
+
children
|
|
24612
|
+
}), open && /* @__PURE__ */ jsxs("div", {
|
|
24613
|
+
className: "scrawl-board__navigator-menu",
|
|
24614
|
+
id: menuId,
|
|
24615
|
+
role: "group",
|
|
24616
|
+
"aria-label": label,
|
|
24617
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
24618
|
+
className: "scrawl-board__navigator-options",
|
|
24619
|
+
children: items.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
24620
|
+
className: "scrawl-board__page-row",
|
|
24621
|
+
"data-active": item.id === activeId,
|
|
24622
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
24623
|
+
className: "scrawl-board__page-select",
|
|
24624
|
+
type: "button",
|
|
24625
|
+
"aria-pressed": item.id === activeId,
|
|
24626
|
+
onClick: () => {
|
|
24627
|
+
onSelect(item.id);
|
|
24628
|
+
close();
|
|
24629
|
+
},
|
|
24630
|
+
children: [
|
|
24631
|
+
item.color !== void 0 ? /* @__PURE__ */ jsx("span", {
|
|
24576
24632
|
className: "scrawl-board__notebook-color",
|
|
24577
24633
|
style: { background: item.color }
|
|
24578
|
-
})
|
|
24579
|
-
|
|
24580
|
-
|
|
24581
|
-
|
|
24582
|
-
|
|
24583
|
-
|
|
24584
|
-
|
|
24585
|
-
|
|
24586
|
-
|
|
24587
|
-
|
|
24588
|
-
|
|
24589
|
-
|
|
24590
|
-
|
|
24591
|
-
|
|
24592
|
-
|
|
24593
|
-
|
|
24594
|
-
|
|
24595
|
-
|
|
24596
|
-
|
|
24634
|
+
}) : /* @__PURE__ */ jsx(ToolIcon, { tool: kind === "Section" ? "section" : "page" }),
|
|
24635
|
+
/* @__PURE__ */ jsx("span", {
|
|
24636
|
+
className: "scrawl-board__navigator-label",
|
|
24637
|
+
children: item.name
|
|
24638
|
+
}),
|
|
24639
|
+
item.id === activeId && /* @__PURE__ */ jsx(ToolIcon, { tool: "check" })
|
|
24640
|
+
]
|
|
24641
|
+
}), mutable && /* @__PURE__ */ jsx(NavigatorName, {
|
|
24642
|
+
name: item.name,
|
|
24643
|
+
kind,
|
|
24644
|
+
onRename: (name) => onRename(item.id, name)
|
|
24645
|
+
})]
|
|
24646
|
+
}, item.id))
|
|
24647
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
24648
|
+
className: "scrawl-board__navigator-add",
|
|
24649
|
+
type: "button",
|
|
24650
|
+
disabled: !mutable,
|
|
24651
|
+
onClick: () => {
|
|
24652
|
+
onAdd();
|
|
24653
|
+
close();
|
|
24654
|
+
},
|
|
24655
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "more" }), /* @__PURE__ */ jsxs("span", { children: ["New ", kind.toLowerCase()] })]
|
|
24597
24656
|
})]
|
|
24598
|
-
})
|
|
24657
|
+
})]
|
|
24658
|
+
});
|
|
24659
|
+
}
|
|
24660
|
+
/**
|
|
24661
|
+
* The floating navigator: a notebook switcher in the top-left corner, and a
|
|
24662
|
+
* breadcrumb across the top whose notebook, section and page crumbs each open
|
|
24663
|
+
* their own menu (pick, rename, or add one).
|
|
24664
|
+
*/
|
|
24665
|
+
function NotebookNavigation({ controller, snapshot }) {
|
|
24666
|
+
const notebook = snapshot.notebooks.find((item) => item.id === snapshot.activeNotebookId);
|
|
24667
|
+
const section = snapshot.sections.find((item) => item.id === snapshot.activeSectionId);
|
|
24668
|
+
const page = snapshot.pages.find((item) => item.id === snapshot.activePageId);
|
|
24669
|
+
const mutable = snapshot.status === "ready" && !snapshot.readOnly;
|
|
24670
|
+
const sections = snapshot.sections.filter((item) => item.notebookId === snapshot.activeNotebookId);
|
|
24671
|
+
const pages = snapshot.pages.filter((item) => item.sectionId === snapshot.activeSectionId);
|
|
24672
|
+
const notebookMenu = {
|
|
24673
|
+
items: snapshot.notebooks,
|
|
24674
|
+
activeId: snapshot.activeNotebookId,
|
|
24675
|
+
kind: "Notebook",
|
|
24676
|
+
mutable,
|
|
24677
|
+
onSelect: (id) => controller.notebooks.select(id),
|
|
24678
|
+
onAdd: () => controller.notebooks.add(),
|
|
24679
|
+
onRename: (id, name) => controller.notebooks.rename(id, name)
|
|
24680
|
+
};
|
|
24681
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(NavigatorMenu, {
|
|
24682
|
+
...notebookMenu,
|
|
24683
|
+
label: "Notebooks",
|
|
24684
|
+
className: "scrawl-board__notebook-switcher",
|
|
24685
|
+
triggerLabel: "Choose notebook",
|
|
24686
|
+
triggerProps: { className: "scrawl-board__notebook-trigger" },
|
|
24687
|
+
children: [
|
|
24688
|
+
/* @__PURE__ */ jsx("span", {
|
|
24689
|
+
className: "scrawl-board__notebook-color",
|
|
24690
|
+
style: { background: notebook?.color }
|
|
24691
|
+
}),
|
|
24692
|
+
/* @__PURE__ */ jsx("span", {
|
|
24693
|
+
className: "scrawl-board__navigator-label",
|
|
24694
|
+
children: notebook?.name ?? "Notebooks"
|
|
24695
|
+
}),
|
|
24696
|
+
/* @__PURE__ */ jsx(ToolIcon, { tool: "chevronDown" })
|
|
24697
|
+
]
|
|
24599
24698
|
}), /* @__PURE__ */ jsx("nav", {
|
|
24600
24699
|
className: "scrawl-board__breadcrumb",
|
|
24601
24700
|
"aria-label": "Breadcrumb",
|
|
24602
24701
|
children: /* @__PURE__ */ jsxs("ol", { children: [
|
|
24603
|
-
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */
|
|
24604
|
-
|
|
24605
|
-
|
|
24606
|
-
|
|
24607
|
-
|
|
24608
|
-
},
|
|
24609
|
-
children: notebook
|
|
24702
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(NavigatorMenu, {
|
|
24703
|
+
...notebookMenu,
|
|
24704
|
+
label: "Notebooks",
|
|
24705
|
+
className: "scrawl-board__crumb-group",
|
|
24706
|
+
triggerLabel: `Notebook: ${notebook?.name ?? "Notebook"}`,
|
|
24707
|
+
triggerProps: { className: "scrawl-board__crumb" },
|
|
24708
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "notebook" }), /* @__PURE__ */ jsx("span", {
|
|
24709
|
+
className: "scrawl-board__navigator-label",
|
|
24710
|
+
children: notebook?.name ?? "Notebook"
|
|
24711
|
+
})]
|
|
24610
24712
|
}) }),
|
|
24611
|
-
/* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
24615
|
-
|
|
24616
|
-
|
|
24617
|
-
|
|
24713
|
+
/* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "chevronRight" }), /* @__PURE__ */ jsxs(NavigatorMenu, {
|
|
24714
|
+
label: "Sections",
|
|
24715
|
+
items: sections,
|
|
24716
|
+
activeId: snapshot.activeSectionId,
|
|
24717
|
+
kind: "Section",
|
|
24718
|
+
mutable: true,
|
|
24719
|
+
onSelect: (id) => controller.sections.select(id),
|
|
24720
|
+
onAdd: () => controller.sections.add(),
|
|
24721
|
+
onRename: (id, name) => controller.sections.rename(id, name),
|
|
24722
|
+
className: "scrawl-board__crumb-group",
|
|
24723
|
+
triggerLabel: `Section: ${section?.name ?? "Section"}`,
|
|
24724
|
+
triggerProps: { className: "scrawl-board__crumb" },
|
|
24725
|
+
children: [
|
|
24726
|
+
/* @__PURE__ */ jsx(ToolIcon, { tool: "section" }),
|
|
24727
|
+
/* @__PURE__ */ jsx("span", {
|
|
24728
|
+
className: "scrawl-board__navigator-label",
|
|
24729
|
+
children: section?.name ?? "Section"
|
|
24730
|
+
}),
|
|
24731
|
+
/* @__PURE__ */ jsx(ToolIcon, { tool: "chevronDown" })
|
|
24732
|
+
]
|
|
24618
24733
|
})] }),
|
|
24619
|
-
/* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("
|
|
24620
|
-
|
|
24621
|
-
|
|
24622
|
-
|
|
24623
|
-
|
|
24624
|
-
|
|
24734
|
+
/* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "chevronRight" }), /* @__PURE__ */ jsxs(NavigatorMenu, {
|
|
24735
|
+
label: "Pages",
|
|
24736
|
+
items: pages,
|
|
24737
|
+
activeId: snapshot.activePageId,
|
|
24738
|
+
kind: "Page",
|
|
24739
|
+
mutable: true,
|
|
24740
|
+
onSelect: (id) => controller.pages.select(id),
|
|
24741
|
+
onAdd: () => controller.pages.add(),
|
|
24742
|
+
onRename: (id, name) => controller.pages.rename(id, name),
|
|
24743
|
+
className: "scrawl-board__crumb-group",
|
|
24744
|
+
triggerLabel: `Page: ${page?.name ?? "Page"}`,
|
|
24745
|
+
triggerProps: { className: "scrawl-board__crumb scrawl-board__crumb--current" },
|
|
24746
|
+
children: [
|
|
24747
|
+
/* @__PURE__ */ jsx(ToolIcon, { tool: "page" }),
|
|
24748
|
+
/* @__PURE__ */ jsx("span", {
|
|
24749
|
+
className: "scrawl-board__navigator-label",
|
|
24750
|
+
"aria-current": "page",
|
|
24751
|
+
children: page?.name ?? "Page"
|
|
24752
|
+
}),
|
|
24753
|
+
/* @__PURE__ */ jsx(ToolIcon, { tool: "chevronDown" })
|
|
24754
|
+
]
|
|
24625
24755
|
})] })
|
|
24626
24756
|
] })
|
|
24627
24757
|
})] });
|
|
@@ -24710,14 +24840,6 @@ function PagesPanel({ controller, snapshot, open, onClose }) {
|
|
|
24710
24840
|
onClick: () => controller.sections.add(),
|
|
24711
24841
|
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "more" })
|
|
24712
24842
|
}),
|
|
24713
|
-
/* @__PURE__ */ jsx("button", {
|
|
24714
|
-
"aria-label": "New page",
|
|
24715
|
-
"data-tooltip": "New page",
|
|
24716
|
-
type: "button",
|
|
24717
|
-
disabled: !mutable,
|
|
24718
|
-
onClick: addPage,
|
|
24719
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "more" })
|
|
24720
|
-
}),
|
|
24721
24843
|
/* @__PURE__ */ jsx("button", {
|
|
24722
24844
|
"aria-label": "Close pages",
|
|
24723
24845
|
"data-tooltip": "Close pages",
|
|
@@ -25138,298 +25260,425 @@ async function insertImageFile(controller, file) {
|
|
|
25138
25260
|
return id;
|
|
25139
25261
|
}
|
|
25140
25262
|
//#endregion
|
|
25141
|
-
//#region src/ui/
|
|
25142
|
-
/**
|
|
25143
|
-
function
|
|
25144
|
-
const
|
|
25145
|
-
const
|
|
25146
|
-
const
|
|
25147
|
-
const measureRef = useRef(null);
|
|
25148
|
-
const coveredRow = editor?.kind === "table-cell" ? editor.row : void 0;
|
|
25149
|
-
const coveredCol = editor?.kind === "table-cell" ? editor.col : void 0;
|
|
25150
|
-
const coveredId = editor && editor.kind !== "note" ? editor.id : null;
|
|
25263
|
+
//#region src/ui/panels/board-panels.tsx
|
|
25264
|
+
/** A floating drawer under the header (a bottom sheet on narrow boards). */
|
|
25265
|
+
function BoardDrawer({ label, icon, subtitle, onClose, onMarkAllRead, markAllDisabled, footer, children }) {
|
|
25266
|
+
const headingId = useId();
|
|
25267
|
+
const ref = useRef(null);
|
|
25268
|
+
const closeRef = useRef(onClose);
|
|
25151
25269
|
useEffect(() => {
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25159
|
-
if (controller.getSnapshot().status !== "disposed") controller.view.setInlineEditing(null);
|
|
25270
|
+
closeRef.current = onClose;
|
|
25271
|
+
});
|
|
25272
|
+
useEffect(() => {
|
|
25273
|
+
ref.current?.querySelector("button:not(:disabled), [tabindex='0']")?.focus({ preventScroll: true });
|
|
25274
|
+
const outside = (event) => {
|
|
25275
|
+
const target = event.target;
|
|
25276
|
+
if (!ref.current?.contains(target) && !target.closest?.(".scrawl-board__actions")) closeRef.current();
|
|
25160
25277
|
};
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
|
|
25164
|
-
|
|
25165
|
-
|
|
25166
|
-
|
|
25167
|
-
|
|
25168
|
-
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
const cs = getComputedStyle(el);
|
|
25172
|
-
measureRef.current ??= document.createElement("canvas").getContext("2d");
|
|
25173
|
-
const measure = measureRef.current;
|
|
25174
|
-
if (measure) {
|
|
25175
|
-
measure.font = `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`;
|
|
25176
|
-
const widest = Math.max(...textValue.split("\n").map((line) => measure.measureText(line || " ").width));
|
|
25177
|
-
const chrome = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight) + parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth);
|
|
25178
|
-
const cap = Math.min(window.innerWidth * .6, 640);
|
|
25179
|
-
el.style.width = `${Math.ceil(Math.min(cap, widest + chrome + 4))}px`;
|
|
25180
|
-
}
|
|
25181
|
-
el.style.height = "auto";
|
|
25182
|
-
el.style.height = `${el.scrollHeight + parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth)}px`;
|
|
25183
|
-
}, [textValue, editor?.kind === "text" ? editor.fontSizePx : null]);
|
|
25184
|
-
/** Portal children are positioned relative to the Board root, not the viewport. */
|
|
25185
|
-
const rootOffset = () => {
|
|
25186
|
-
const rootRect = (anchorRef.current?.closest("[data-scrawl-root]"))?.getBoundingClientRect();
|
|
25187
|
-
return {
|
|
25188
|
-
left: rootRect?.left ?? 0,
|
|
25189
|
-
top: rootRect?.top ?? 0
|
|
25278
|
+
const escape = (event) => {
|
|
25279
|
+
if (event.key !== "Escape" || event.defaultPrevented || document.activeElement && document.activeElement !== document.body) return;
|
|
25280
|
+
event.preventDefault();
|
|
25281
|
+
closeRef.current();
|
|
25282
|
+
};
|
|
25283
|
+
document.addEventListener("pointerdown", outside);
|
|
25284
|
+
document.addEventListener("keydown", escape);
|
|
25285
|
+
return () => {
|
|
25286
|
+
document.removeEventListener("pointerdown", outside);
|
|
25287
|
+
document.removeEventListener("keydown", escape);
|
|
25190
25288
|
};
|
|
25289
|
+
}, []);
|
|
25290
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
25291
|
+
"aria-labelledby": headingId,
|
|
25292
|
+
className: "scrawl-board__drawer",
|
|
25293
|
+
ref,
|
|
25294
|
+
role: "dialog",
|
|
25295
|
+
tabIndex: -1,
|
|
25296
|
+
onKeyDown: (event) => {
|
|
25297
|
+
event.stopPropagation();
|
|
25298
|
+
if (event.key === "Escape") {
|
|
25299
|
+
event.preventDefault();
|
|
25300
|
+
onClose();
|
|
25301
|
+
}
|
|
25302
|
+
},
|
|
25303
|
+
children: [
|
|
25304
|
+
/* @__PURE__ */ jsxs("header", {
|
|
25305
|
+
className: "scrawl-board__drawer-header",
|
|
25306
|
+
children: [
|
|
25307
|
+
icon && /* @__PURE__ */ jsx("span", {
|
|
25308
|
+
"aria-hidden": "true",
|
|
25309
|
+
className: "scrawl-board__drawer-icon",
|
|
25310
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: icon })
|
|
25311
|
+
}),
|
|
25312
|
+
/* @__PURE__ */ jsxs("div", {
|
|
25313
|
+
className: "scrawl-board__drawer-titles",
|
|
25314
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
25315
|
+
id: headingId,
|
|
25316
|
+
children: label
|
|
25317
|
+
}), subtitle && /* @__PURE__ */ jsx("p", { children: subtitle })]
|
|
25318
|
+
}),
|
|
25319
|
+
onMarkAllRead && /* @__PURE__ */ jsx("button", {
|
|
25320
|
+
className: "scrawl-board__drawer-link",
|
|
25321
|
+
disabled: markAllDisabled,
|
|
25322
|
+
onClick: () => {
|
|
25323
|
+
onMarkAllRead();
|
|
25324
|
+
ref.current?.focus();
|
|
25325
|
+
},
|
|
25326
|
+
type: "button",
|
|
25327
|
+
children: "Mark all as read"
|
|
25328
|
+
}),
|
|
25329
|
+
/* @__PURE__ */ jsx("button", {
|
|
25330
|
+
"aria-label": `Close ${label}`,
|
|
25331
|
+
className: "scrawl-board__drawer-close",
|
|
25332
|
+
onClick: onClose,
|
|
25333
|
+
type: "button",
|
|
25334
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "close" })
|
|
25335
|
+
})
|
|
25336
|
+
]
|
|
25337
|
+
}),
|
|
25338
|
+
/* @__PURE__ */ jsx("div", {
|
|
25339
|
+
className: "scrawl-board__drawer-body",
|
|
25340
|
+
children
|
|
25341
|
+
}),
|
|
25342
|
+
footer && /* @__PURE__ */ jsx("footer", {
|
|
25343
|
+
className: "scrawl-board__drawer-footer",
|
|
25344
|
+
children: footer
|
|
25345
|
+
})
|
|
25346
|
+
]
|
|
25347
|
+
});
|
|
25348
|
+
}
|
|
25349
|
+
function DrawerEmpty({ icon, title, hint }) {
|
|
25350
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
25351
|
+
className: "scrawl-board__drawer-empty",
|
|
25352
|
+
children: [
|
|
25353
|
+
/* @__PURE__ */ jsx("span", {
|
|
25354
|
+
"aria-hidden": "true",
|
|
25355
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: icon })
|
|
25356
|
+
}),
|
|
25357
|
+
/* @__PURE__ */ jsx("p", {
|
|
25358
|
+
className: "scrawl-board__drawer-empty-title",
|
|
25359
|
+
children: title
|
|
25360
|
+
}),
|
|
25361
|
+
/* @__PURE__ */ jsx("p", { children: hint })
|
|
25362
|
+
]
|
|
25363
|
+
});
|
|
25364
|
+
}
|
|
25365
|
+
function CommentsDrawer({ controller, comments, unread, onMarkAllRead, onClose }) {
|
|
25366
|
+
return /* @__PURE__ */ jsx(BoardDrawer, {
|
|
25367
|
+
icon: "comment",
|
|
25368
|
+
label: "Comments",
|
|
25369
|
+
markAllDisabled: unread === 0,
|
|
25370
|
+
onClose,
|
|
25371
|
+
onMarkAllRead,
|
|
25372
|
+
children: comments.length === 0 ? /* @__PURE__ */ jsx(DrawerEmpty, {
|
|
25373
|
+
hint: "Pick the Comment tool and click the board to start a conversation.",
|
|
25374
|
+
icon: "comment",
|
|
25375
|
+
title: "No comments yet"
|
|
25376
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
25377
|
+
className: "scrawl-board__drawer-list",
|
|
25378
|
+
children: comments.map((comment) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
|
|
25379
|
+
"aria-label": `Open comment by ${comment.authorName}: ${comment.text}`,
|
|
25380
|
+
className: "scrawl-board__drawer-row",
|
|
25381
|
+
type: "button",
|
|
25382
|
+
onClick: () => {
|
|
25383
|
+
onClose();
|
|
25384
|
+
controller.view.centerOn({
|
|
25385
|
+
x: comment.x,
|
|
25386
|
+
y: comment.y
|
|
25387
|
+
});
|
|
25388
|
+
controller.comments.open(comment.id);
|
|
25389
|
+
},
|
|
25390
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
25391
|
+
"aria-hidden": "true",
|
|
25392
|
+
className: "scrawl-board__comment-avatar",
|
|
25393
|
+
style: { background: comment.authorColor },
|
|
25394
|
+
children: comment.authorName.slice(0, 1).toUpperCase()
|
|
25395
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
25396
|
+
className: "scrawl-board__drawer-row-text",
|
|
25397
|
+
children: [
|
|
25398
|
+
/* @__PURE__ */ jsxs("strong", { children: [comment.authorName, comment.resolved ? " · Resolved" : ""] }),
|
|
25399
|
+
/* @__PURE__ */ jsx("span", { children: comment.text }),
|
|
25400
|
+
comment.replies.length > 0 && /* @__PURE__ */ jsxs("small", { children: [
|
|
25401
|
+
comment.replies.length,
|
|
25402
|
+
" ",
|
|
25403
|
+
comment.replies.length === 1 ? "reply" : "replies"
|
|
25404
|
+
] })
|
|
25405
|
+
]
|
|
25406
|
+
})]
|
|
25407
|
+
}) }, comment.id))
|
|
25408
|
+
})
|
|
25409
|
+
});
|
|
25410
|
+
}
|
|
25411
|
+
var KIND_ICON = {
|
|
25412
|
+
added: "more",
|
|
25413
|
+
removed: "delete",
|
|
25414
|
+
updated: "marker",
|
|
25415
|
+
commented: "comment",
|
|
25416
|
+
shared: "share",
|
|
25417
|
+
exported: "export"
|
|
25418
|
+
};
|
|
25419
|
+
function timeLabels(at, now) {
|
|
25420
|
+
const date = new Date(at);
|
|
25421
|
+
const seconds = Math.max(0, Math.round((now - date.getTime()) / 1e3));
|
|
25422
|
+
return {
|
|
25423
|
+
relative: seconds < 45 ? "just now" : seconds < 3600 ? `${Math.round(seconds / 60)} min ago` : seconds < 86400 ? `${Math.round(seconds / 3600)} h ago` : `${Math.round(seconds / 86400)} d ago`,
|
|
25424
|
+
absolute: date.toLocaleString(void 0, {
|
|
25425
|
+
dateStyle: "medium",
|
|
25426
|
+
timeStyle: "short"
|
|
25427
|
+
})
|
|
25191
25428
|
};
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
|
|
25207
|
-
|
|
25208
|
-
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
}
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25229
|
-
|
|
25230
|
-
|
|
25231
|
-
|
|
25232
|
-
|
|
25233
|
-
|
|
25234
|
-
|
|
25235
|
-
|
|
25236
|
-
|
|
25237
|
-
|
|
25238
|
-
|
|
25429
|
+
}
|
|
25430
|
+
function ActivityDrawer({ entries, unread, onMarkAllRead, onJump, onClose }) {
|
|
25431
|
+
const [now] = useState(() => Date.now());
|
|
25432
|
+
return /* @__PURE__ */ jsx(BoardDrawer, {
|
|
25433
|
+
footer: /* @__PURE__ */ jsxs("span", { children: [
|
|
25434
|
+
entries.length,
|
|
25435
|
+
" ",
|
|
25436
|
+
entries.length === 1 ? "entry" : "entries"
|
|
25437
|
+
] }),
|
|
25438
|
+
icon: "activity",
|
|
25439
|
+
label: "Recent Activities",
|
|
25440
|
+
markAllDisabled: unread === 0,
|
|
25441
|
+
onClose,
|
|
25442
|
+
onMarkAllRead,
|
|
25443
|
+
subtitle: "Track board actions",
|
|
25444
|
+
children: entries.length === 0 ? /* @__PURE__ */ jsx(DrawerEmpty, {
|
|
25445
|
+
hint: "Changes to this board will show up here.",
|
|
25446
|
+
icon: "activity",
|
|
25447
|
+
title: "No activity yet"
|
|
25448
|
+
}) : /* @__PURE__ */ jsx("ol", {
|
|
25449
|
+
className: "scrawl-board__drawer-list scrawl-board__timeline",
|
|
25450
|
+
children: entries.map((entry) => {
|
|
25451
|
+
const time = timeLabels(entry.at, now);
|
|
25452
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
25453
|
+
"aria-hidden": "true",
|
|
25454
|
+
className: "scrawl-board__timeline-icon",
|
|
25455
|
+
"data-kind": entry.kind,
|
|
25456
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: KIND_ICON[entry.kind] })
|
|
25457
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
25458
|
+
className: "scrawl-board__drawer-row-text",
|
|
25459
|
+
children: [
|
|
25460
|
+
/* @__PURE__ */ jsx("strong", { children: entry.title }),
|
|
25461
|
+
entry.actorName && /* @__PURE__ */ jsxs("span", { children: ["by ", entry.actorName] }),
|
|
25462
|
+
/* @__PURE__ */ jsx("small", { children: /* @__PURE__ */ jsxs("time", {
|
|
25463
|
+
dateTime: new Date(entry.at).toISOString(),
|
|
25464
|
+
children: [
|
|
25465
|
+
time.relative,
|
|
25466
|
+
" · ",
|
|
25467
|
+
time.absolute
|
|
25468
|
+
]
|
|
25469
|
+
}) })
|
|
25470
|
+
]
|
|
25471
|
+
})] });
|
|
25472
|
+
return /* @__PURE__ */ jsx("li", { children: entry.location ? /* @__PURE__ */ jsx("button", {
|
|
25473
|
+
"aria-label": `${entry.title}${entry.actorName ? ` by ${entry.actorName}` : ""} — show on board`,
|
|
25474
|
+
className: "scrawl-board__drawer-row",
|
|
25475
|
+
type: "button",
|
|
25476
|
+
onClick: () => {
|
|
25477
|
+
onClose();
|
|
25478
|
+
onJump(entry.location);
|
|
25479
|
+
},
|
|
25480
|
+
children: content
|
|
25481
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
25482
|
+
className: "scrawl-board__drawer-row",
|
|
25483
|
+
children: content
|
|
25484
|
+
}) }, entry.id);
|
|
25485
|
+
})
|
|
25486
|
+
})
|
|
25487
|
+
});
|
|
25488
|
+
}
|
|
25489
|
+
var ROLE_LABEL = {
|
|
25490
|
+
viewer: "View",
|
|
25491
|
+
editor: "Edit",
|
|
25492
|
+
admin: "Admin"
|
|
25493
|
+
};
|
|
25494
|
+
var ROLES = [
|
|
25495
|
+
"viewer",
|
|
25496
|
+
"editor",
|
|
25497
|
+
"admin"
|
|
25498
|
+
];
|
|
25499
|
+
function PermissionsDrawer({ permissions, onClose }) {
|
|
25500
|
+
const [pending, setPending] = useState({});
|
|
25501
|
+
const saveRef = useRef(null);
|
|
25502
|
+
const [status, setStatus] = useState({ kind: "idle" });
|
|
25503
|
+
const changes = permissions.members.filter((member) => pending[member.id] && pending[member.id] !== member.role).map((member) => ({
|
|
25504
|
+
memberId: member.id,
|
|
25505
|
+
role: pending[member.id]
|
|
25239
25506
|
}));
|
|
25240
|
-
|
|
25241
|
-
if (
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
25249
|
-
|
|
25250
|
-
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25507
|
+
useEffect(() => {
|
|
25508
|
+
if (status.kind !== "saved") return;
|
|
25509
|
+
const timer = setTimeout(() => setStatus({ kind: "idle" }), 1800);
|
|
25510
|
+
return () => clearTimeout(timer);
|
|
25511
|
+
}, [status]);
|
|
25512
|
+
const save = async () => {
|
|
25513
|
+
setStatus({ kind: "saving" });
|
|
25514
|
+
try {
|
|
25515
|
+
await permissions.onSave(changes);
|
|
25516
|
+
setPending({});
|
|
25517
|
+
setStatus({ kind: "saved" });
|
|
25518
|
+
saveRef.current?.closest(".scrawl-board__drawer")?.focus();
|
|
25519
|
+
} catch (error) {
|
|
25520
|
+
setStatus({
|
|
25521
|
+
kind: "error",
|
|
25522
|
+
message: error instanceof Error ? error.message : "Your changes weren't saved."
|
|
25523
|
+
});
|
|
25256
25524
|
}
|
|
25257
|
-
if (nextRow >= table.rows) nextRow = 0;
|
|
25258
|
-
if (nextRow < 0) nextRow = table.rows - 1;
|
|
25259
|
-
let leftBoard = table.x;
|
|
25260
|
-
for (let c = 0; c < nextCol; c++) leftBoard += table.colWidths[c] ?? 8;
|
|
25261
|
-
let topBoard = table.y;
|
|
25262
|
-
for (let r = 0; r < nextRow; r++) topBoard -= table.rowHeights[r] ?? 3.6;
|
|
25263
|
-
const colWidth = table.colWidths[nextCol] ?? 8;
|
|
25264
|
-
const rowHeight = table.rowHeights[nextRow] ?? 3.6;
|
|
25265
|
-
const topLeft = controller.view.boardToScreen({
|
|
25266
|
-
x: leftBoard,
|
|
25267
|
-
y: topBoard
|
|
25268
|
-
});
|
|
25269
|
-
const bottomRight = controller.view.boardToScreen({
|
|
25270
|
-
x: leftBoard + colWidth,
|
|
25271
|
-
y: topBoard - rowHeight
|
|
25272
|
-
});
|
|
25273
|
-
const offset = rootOffset();
|
|
25274
|
-
setEditor({
|
|
25275
|
-
kind: "table-cell",
|
|
25276
|
-
id: editor.id,
|
|
25277
|
-
row: nextRow,
|
|
25278
|
-
col: nextCol,
|
|
25279
|
-
text: table.cells[`${nextRow},${nextCol}`] ?? "",
|
|
25280
|
-
fontSizePx: editor.fontSizePx,
|
|
25281
|
-
screenRect: {
|
|
25282
|
-
left: topLeft.x,
|
|
25283
|
-
top: topLeft.y,
|
|
25284
|
-
width: Math.max(40, bottomRight.x - topLeft.x),
|
|
25285
|
-
height: Math.max(24, bottomRight.y - topLeft.y)
|
|
25286
|
-
},
|
|
25287
|
-
left: topLeft.x - offset.left,
|
|
25288
|
-
top: topLeft.y - offset.top
|
|
25289
|
-
});
|
|
25290
25525
|
};
|
|
25291
|
-
|
|
25292
|
-
|
|
25293
|
-
|
|
25294
|
-
|
|
25295
|
-
|
|
25296
|
-
|
|
25297
|
-
|
|
25298
|
-
|
|
25526
|
+
return /* @__PURE__ */ jsxs(BoardDrawer, {
|
|
25527
|
+
icon: "shield",
|
|
25528
|
+
label: "Pod Member Permissions",
|
|
25529
|
+
onClose,
|
|
25530
|
+
subtitle: "Manage whiteboard access",
|
|
25531
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
25532
|
+
"aria-live": "polite",
|
|
25533
|
+
className: "scrawl-board__drawer-status",
|
|
25534
|
+
"data-kind": status.kind,
|
|
25535
|
+
children: status.kind === "saved" ? "Saved" : status.kind === "error" ? status.message : status.kind === "saving" ? "Saving…" : ""
|
|
25536
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
25537
|
+
className: "scrawl-board__drawer-primary",
|
|
25538
|
+
ref: saveRef,
|
|
25539
|
+
disabled: changes.length === 0 || status.kind === "saving",
|
|
25540
|
+
onClick: () => void save(),
|
|
25541
|
+
type: "button",
|
|
25542
|
+
children: "Save Permissions"
|
|
25543
|
+
})] }),
|
|
25544
|
+
children: [/* @__PURE__ */ jsxs("dl", {
|
|
25545
|
+
className: "scrawl-board__role-legend",
|
|
25546
|
+
children: [
|
|
25547
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "View Only" }), /* @__PURE__ */ jsx("dd", { children: "Can see the board but not change it." })] }),
|
|
25548
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Edit Access" }), /* @__PURE__ */ jsx("dd", { children: "Can draw, add and change content." })] }),
|
|
25549
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Admin Access" }), /* @__PURE__ */ jsx("dd", { children: "Can edit and manage who has access." })] })
|
|
25550
|
+
]
|
|
25551
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
25552
|
+
className: "scrawl-board__member-list",
|
|
25553
|
+
children: permissions.members.map((member) => {
|
|
25554
|
+
const role = pending[member.id] ?? member.role;
|
|
25555
|
+
const isYou = member.id === permissions.currentUserId;
|
|
25556
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
25557
|
+
className: "scrawl-board__member",
|
|
25558
|
+
children: [
|
|
25559
|
+
member.avatarUrl ? /* @__PURE__ */ jsx("img", {
|
|
25560
|
+
alt: "",
|
|
25561
|
+
className: "scrawl-board__member-avatar",
|
|
25562
|
+
src: member.avatarUrl
|
|
25563
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
25564
|
+
"aria-hidden": "true",
|
|
25565
|
+
className: "scrawl-board__member-avatar",
|
|
25566
|
+
children: initials(member.name)
|
|
25567
|
+
}),
|
|
25568
|
+
/* @__PURE__ */ jsxs("div", {
|
|
25569
|
+
className: "scrawl-board__member-info",
|
|
25570
|
+
children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
25571
|
+
/* @__PURE__ */ jsx("strong", { children: member.name }),
|
|
25572
|
+
isYou && /* @__PURE__ */ jsx("span", {
|
|
25573
|
+
className: "scrawl-board__member-you",
|
|
25574
|
+
children: " (you)"
|
|
25575
|
+
}),
|
|
25576
|
+
member.online !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
25577
|
+
className: "scrawl-board__member-status",
|
|
25578
|
+
"data-online": member.online,
|
|
25579
|
+
children: member.online ? "Online" : "Offline"
|
|
25580
|
+
})
|
|
25581
|
+
] }), member.email && /* @__PURE__ */ jsx("p", {
|
|
25582
|
+
className: "scrawl-board__member-email",
|
|
25583
|
+
children: member.email
|
|
25584
|
+
})]
|
|
25585
|
+
}),
|
|
25586
|
+
/* @__PURE__ */ jsx("div", {
|
|
25587
|
+
"aria-label": `Access for ${member.name}`,
|
|
25588
|
+
className: "scrawl-board__segmented",
|
|
25589
|
+
role: "radiogroup",
|
|
25590
|
+
children: ROLES.map((option) => /* @__PURE__ */ jsx("button", {
|
|
25591
|
+
"aria-checked": role === option,
|
|
25592
|
+
disabled: isYou,
|
|
25593
|
+
role: "radio",
|
|
25594
|
+
type: "button",
|
|
25595
|
+
onClick: () => {
|
|
25596
|
+
setStatus({ kind: "idle" });
|
|
25597
|
+
setPending((current) => ({
|
|
25598
|
+
...current,
|
|
25599
|
+
[member.id]: option
|
|
25600
|
+
}));
|
|
25601
|
+
},
|
|
25602
|
+
children: ROLE_LABEL[option]
|
|
25603
|
+
}, option))
|
|
25604
|
+
})
|
|
25605
|
+
]
|
|
25606
|
+
}, member.id);
|
|
25607
|
+
})
|
|
25608
|
+
})]
|
|
25609
|
+
});
|
|
25610
|
+
}
|
|
25611
|
+
function initials(name) {
|
|
25612
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0].toUpperCase()).join("");
|
|
25613
|
+
}
|
|
25614
|
+
/** The ⋮ overflow menu in the board actions. */
|
|
25615
|
+
function BoardMoreMenu({ items, onSelect, onClose }) {
|
|
25616
|
+
const ref = useRef(null);
|
|
25617
|
+
const closeRef = useRef(onClose);
|
|
25618
|
+
useEffect(() => {
|
|
25619
|
+
closeRef.current = onClose;
|
|
25620
|
+
});
|
|
25621
|
+
useEffect(() => {
|
|
25622
|
+
ref.current?.querySelector("button")?.focus({ preventScroll: true });
|
|
25623
|
+
const outside = (event) => {
|
|
25624
|
+
const target = event.target;
|
|
25625
|
+
if (!ref.current?.contains(target) && !target.closest?.("[aria-label=\"More board actions\"]")) closeRef.current();
|
|
25299
25626
|
};
|
|
25300
|
-
|
|
25301
|
-
|
|
25627
|
+
document.addEventListener("pointerdown", outside);
|
|
25628
|
+
return () => document.removeEventListener("pointerdown", outside);
|
|
25302
25629
|
}, []);
|
|
25303
|
-
|
|
25304
|
-
|
|
25305
|
-
|
|
25306
|
-
|
|
25307
|
-
|
|
25308
|
-
ref: anchorRef,
|
|
25309
|
-
style: { display: "none" }
|
|
25310
|
-
}), renderPortal(editor.kind === "note" ? /* @__PURE__ */ jsx("textarea", {
|
|
25311
|
-
"aria-label": "Edit note",
|
|
25312
|
-
autoFocus: true,
|
|
25313
|
-
className: "scrawl-board__inline-editor scrawl-board__inline-editor--note",
|
|
25314
|
-
onBlur: commitNote,
|
|
25315
|
-
onChange: (event) => setEditor({
|
|
25316
|
-
...editor,
|
|
25317
|
-
text: event.currentTarget.value
|
|
25318
|
-
}),
|
|
25319
|
-
onKeyDown: (event) => {
|
|
25320
|
-
event.stopPropagation();
|
|
25321
|
-
if (event.key === "Escape") commitNote();
|
|
25322
|
-
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) commitNote();
|
|
25323
|
-
},
|
|
25324
|
-
ref: focusEditor,
|
|
25325
|
-
style: {
|
|
25326
|
-
left: editor.left,
|
|
25327
|
-
top: editor.top,
|
|
25328
|
-
width: editor.screenRect.width,
|
|
25329
|
-
height: editor.screenRect.height,
|
|
25330
|
-
backgroundColor: editor.color,
|
|
25331
|
-
fontSize: Math.max(12, editor.screenRect.width * 42 / 512),
|
|
25332
|
-
lineHeight: 1.3,
|
|
25333
|
-
padding: editor.screenRect.width * 40 / 512
|
|
25334
|
-
},
|
|
25335
|
-
value: editor.text
|
|
25336
|
-
}) : editor.kind === "text" ? /* @__PURE__ */ jsx("textarea", {
|
|
25337
|
-
"aria-label": "Edit text",
|
|
25338
|
-
autoFocus: true,
|
|
25339
|
-
className: "scrawl-board__inline-editor scrawl-board__inline-editor--text",
|
|
25340
|
-
onBlur: commitText,
|
|
25341
|
-
onChange: (event) => setEditor({
|
|
25342
|
-
...editor,
|
|
25343
|
-
text: event.currentTarget.value
|
|
25344
|
-
}),
|
|
25630
|
+
return /* @__PURE__ */ jsx("div", {
|
|
25631
|
+
"aria-label": "More board actions",
|
|
25632
|
+
className: "scrawl-board__more-menu",
|
|
25633
|
+
ref,
|
|
25634
|
+
role: "menu",
|
|
25345
25635
|
onKeyDown: (event) => {
|
|
25346
25636
|
event.stopPropagation();
|
|
25347
|
-
if (event.key === "Escape")
|
|
25348
|
-
|
|
25349
|
-
|
|
25350
|
-
|
|
25351
|
-
|
|
25352
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25355
|
-
|
|
25356
|
-
fontSize: Math.max(12, editor.fontSizePx)
|
|
25357
|
-
},
|
|
25358
|
-
value: editor.text
|
|
25359
|
-
}) : /* @__PURE__ */ jsxs("div", {
|
|
25360
|
-
className: "scrawl-board__inline-editor--table-cell",
|
|
25361
|
-
style: {
|
|
25362
|
-
left: editor.left,
|
|
25363
|
-
top: editor.top,
|
|
25364
|
-
width: editor.screenRect.width,
|
|
25365
|
-
minHeight: editor.screenRect.height
|
|
25637
|
+
if (event.key === "Escape") {
|
|
25638
|
+
event.preventDefault();
|
|
25639
|
+
onClose();
|
|
25640
|
+
}
|
|
25641
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
25642
|
+
event.preventDefault();
|
|
25643
|
+
const buttons = [...ref.current?.querySelectorAll("button") ?? []];
|
|
25644
|
+
buttons[(buttons.indexOf(document.activeElement) + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length]?.focus();
|
|
25645
|
+
}
|
|
25366
25646
|
},
|
|
25367
|
-
children:
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25371
|
-
onBlur: commitTableCell,
|
|
25372
|
-
onChange: (event) => setEditor({
|
|
25373
|
-
...editor,
|
|
25374
|
-
text: event.currentTarget.value
|
|
25375
|
-
}),
|
|
25376
|
-
onKeyDown: (event) => {
|
|
25377
|
-
event.stopPropagation();
|
|
25378
|
-
if (event.key === "Tab") {
|
|
25379
|
-
event.preventDefault();
|
|
25380
|
-
navigateTableCell(0, event.shiftKey ? -1 : 1);
|
|
25381
|
-
} else if (event.key === "Enter" && !event.shiftKey) {
|
|
25382
|
-
event.preventDefault();
|
|
25383
|
-
navigateTableCell(1, 0);
|
|
25384
|
-
} else if (event.key === "Escape") commitTableCell();
|
|
25647
|
+
children: items.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
25648
|
+
onClick: () => {
|
|
25649
|
+
onClose();
|
|
25650
|
+
onSelect(item.id);
|
|
25385
25651
|
},
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
children:
|
|
25392
|
-
|
|
25393
|
-
|
|
25394
|
-
|
|
25395
|
-
|
|
25396
|
-
|
|
25397
|
-
|
|
25398
|
-
|
|
25399
|
-
|
|
25400
|
-
|
|
25401
|
-
|
|
25402
|
-
|
|
25403
|
-
|
|
25404
|
-
|
|
25405
|
-
|
|
25406
|
-
|
|
25407
|
-
|
|
25408
|
-
|
|
25409
|
-
|
|
25410
|
-
|
|
25411
|
-
|
|
25412
|
-
|
|
25413
|
-
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
type: "button",
|
|
25417
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "addColumn" })
|
|
25418
|
-
}),
|
|
25419
|
-
/* @__PURE__ */ jsx("button", {
|
|
25420
|
-
"aria-label": "Delete row",
|
|
25421
|
-
onMouseDown: (event) => {
|
|
25422
|
-
event.preventDefault();
|
|
25423
|
-
controller.content.table.deleteRow(editor.id, editor.row);
|
|
25424
|
-
setEditor(null);
|
|
25425
|
-
},
|
|
25426
|
-
"data-tooltip": "Delete row",
|
|
25427
|
-
type: "button",
|
|
25428
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "delete" })
|
|
25429
|
-
})
|
|
25430
|
-
]
|
|
25431
|
-
})]
|
|
25432
|
-
}))] });
|
|
25652
|
+
role: "menuitem",
|
|
25653
|
+
type: "button",
|
|
25654
|
+
children: [item.icon ?? /* @__PURE__ */ jsx("span", {
|
|
25655
|
+
"aria-hidden": "true",
|
|
25656
|
+
className: "scrawl-board__menu-icon-space"
|
|
25657
|
+
}), /* @__PURE__ */ jsx("span", { children: item.label })]
|
|
25658
|
+
}, item.id))
|
|
25659
|
+
});
|
|
25660
|
+
}
|
|
25661
|
+
/** A persistent pin where the last activity jump landed, with a pulse on arrival. */
|
|
25662
|
+
function JumpPin({ controller, location, arrival }) {
|
|
25663
|
+
const ref = useRef(null);
|
|
25664
|
+
useLayoutEffect(() => {
|
|
25665
|
+
const place = () => {
|
|
25666
|
+
const pin = ref.current;
|
|
25667
|
+
if (!pin) return;
|
|
25668
|
+
const root = pin.closest("[data-scrawl-root]")?.getBoundingClientRect();
|
|
25669
|
+
const screen = controller.view.boardToScreen(location);
|
|
25670
|
+
pin.style.left = `${screen.x - (root?.left ?? 0)}px`;
|
|
25671
|
+
pin.style.top = `${screen.y - (root?.top ?? 0)}px`;
|
|
25672
|
+
};
|
|
25673
|
+
place();
|
|
25674
|
+
return controller.on("view-change", place);
|
|
25675
|
+
}, [controller, location]);
|
|
25676
|
+
return /* @__PURE__ */ jsx("span", {
|
|
25677
|
+
"aria-hidden": "true",
|
|
25678
|
+
className: "scrawl-board__jump-pin",
|
|
25679
|
+
ref,
|
|
25680
|
+
children: /* @__PURE__ */ jsx("span", { className: "scrawl-board__jump-pulse" }, arrival)
|
|
25681
|
+
});
|
|
25433
25682
|
}
|
|
25434
25683
|
//#endregion
|
|
25435
25684
|
//#region src/ui/toolbar/compact-picker.tsx
|
|
@@ -25570,9 +25819,11 @@ var HANDLE_CORNERS = [
|
|
|
25570
25819
|
* Floating toolbar above the focused note, shape, text block, or semantic
|
|
25571
25820
|
* Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
|
|
25572
25821
|
* (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Images (photos and
|
|
25573
|
-
* placed emoji/icons) get Lock/Duplicate/Delete only —
|
|
25574
|
-
* already
|
|
25575
|
-
*
|
|
25822
|
+
* placed emoji/icons) and tables get Lock/Duplicate/Delete only — each
|
|
25823
|
+
* already has its own resize affordance (the native gizmo for images, edge
|
|
25824
|
+
* handles for tables), and a table's row/column actions live in its cell
|
|
25825
|
+
* editor. Timer/custom objects and plain (non-shape) ink strokes never get a
|
|
25826
|
+
* toolbar here — out of scope for this destination.
|
|
25576
25827
|
*
|
|
25577
25828
|
* Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
|
|
25578
25829
|
* the legacy ink-stroke shape below: both draw from the same toolbar
|
|
@@ -25636,7 +25887,8 @@ function FocusedItemToolbar({ controller, snapshot }) {
|
|
|
25636
25887
|
locked: focused.locked,
|
|
25637
25888
|
anchor: focused.screenPosition
|
|
25638
25889
|
}, obj.id);
|
|
25639
|
-
if (obj.type === "
|
|
25890
|
+
if (obj.type === "table" && snapshot.inlineEditing?.id === obj.id) return null;
|
|
25891
|
+
if (obj.type === "image" || obj.type === "table") return /* @__PURE__ */ jsx(ToolbarShell, {
|
|
25640
25892
|
anchor: focused.screenPosition,
|
|
25641
25893
|
children: /* @__PURE__ */ jsx(LockDuplicateDelete, {
|
|
25642
25894
|
controller,
|
|
@@ -25657,8 +25909,8 @@ function ToolbarShell({ anchor, children }) {
|
|
|
25657
25909
|
const board = root?.getBoundingClientRect();
|
|
25658
25910
|
const left = Math.max(8, board?.left ?? 0);
|
|
25659
25911
|
const right = Math.min(window.innerWidth, board?.right ?? window.innerWidth) - 8;
|
|
25660
|
-
const
|
|
25661
|
-
const top = Math.max(8,
|
|
25912
|
+
const navigator = root?.querySelector(".scrawl-board__breadcrumb ol")?.getBoundingClientRect();
|
|
25913
|
+
const top = Math.max(8, navigator ? navigator.bottom + 8 : board?.top ?? 0);
|
|
25662
25914
|
toolbar.style.left = `${Math.max(left, Math.min(anchor.x - toolbar.offsetWidth / 2, right - toolbar.offsetWidth))}px`;
|
|
25663
25915
|
toolbar.style.top = `${Math.max(top, anchor.y - toolbar.offsetHeight - 12)}px`;
|
|
25664
25916
|
};
|
|
@@ -26017,140 +26269,451 @@ function ShapeObjectToolbar({ controller, snapshot, shape, locked, anchor }) {
|
|
|
26017
26269
|
/* @__PURE__ */ jsxs(ToolbarShell, {
|
|
26018
26270
|
anchor,
|
|
26019
26271
|
children: [
|
|
26020
|
-
/* @__PURE__ */ jsx(ColorPicker, {
|
|
26021
|
-
label: "Shape fill",
|
|
26022
|
-
color: shape.fill,
|
|
26023
|
-
colors: INK_COLORS,
|
|
26024
|
-
disabled: locked_,
|
|
26025
|
-
itemLabel: "fill",
|
|
26026
|
-
allowNone: true,
|
|
26027
|
-
onChange: (value) => controller.content.update(shape.id, { fill: value })
|
|
26272
|
+
/* @__PURE__ */ jsx(ColorPicker, {
|
|
26273
|
+
label: "Shape fill",
|
|
26274
|
+
color: shape.fill,
|
|
26275
|
+
colors: INK_COLORS,
|
|
26276
|
+
disabled: locked_,
|
|
26277
|
+
itemLabel: "fill",
|
|
26278
|
+
allowNone: true,
|
|
26279
|
+
onChange: (value) => controller.content.update(shape.id, { fill: value })
|
|
26280
|
+
}),
|
|
26281
|
+
/* @__PURE__ */ jsx(ColorPicker, {
|
|
26282
|
+
label: "Shape stroke",
|
|
26283
|
+
color: shape.stroke,
|
|
26284
|
+
colors: INK_COLORS,
|
|
26285
|
+
disabled: locked_,
|
|
26286
|
+
itemLabel: "stroke",
|
|
26287
|
+
onChange: (value) => controller.content.update(shape.id, { stroke: value })
|
|
26288
|
+
}),
|
|
26289
|
+
/* @__PURE__ */ jsx(LockDuplicateDelete, {
|
|
26290
|
+
controller,
|
|
26291
|
+
mutable,
|
|
26292
|
+
locked,
|
|
26293
|
+
ids: [shape.id]
|
|
26294
|
+
})
|
|
26295
|
+
]
|
|
26296
|
+
})
|
|
26297
|
+
] });
|
|
26298
|
+
}
|
|
26299
|
+
/** One corner: drags the board point directly under the pointer, keeping the opposite corner fixed — independent width/height, no proportional lock. */
|
|
26300
|
+
function ShapeResizeHandle({ controller, shape, corner, x, y }) {
|
|
26301
|
+
const [dragging, setDragging] = useState(false);
|
|
26302
|
+
const fixedCorner = () => {
|
|
26303
|
+
return {
|
|
26304
|
+
x: corner === "nw" || corner === "sw" ? shape.x + shape.width : shape.x,
|
|
26305
|
+
y: corner === "nw" || corner === "ne" ? shape.y - shape.height : shape.y
|
|
26306
|
+
};
|
|
26307
|
+
};
|
|
26308
|
+
const resizeTo = (boardX, boardY) => {
|
|
26309
|
+
const fixed = fixedCorner();
|
|
26310
|
+
const width = Math.max(SHAPE_MIN_SIZE, Math.abs(boardX - fixed.x));
|
|
26311
|
+
const height = Math.max(SHAPE_MIN_SIZE, Math.abs(boardY - fixed.y));
|
|
26312
|
+
const nextX = Math.min(boardX, fixed.x);
|
|
26313
|
+
const nextY = Math.max(boardY, fixed.y);
|
|
26314
|
+
controller.content.update(shape.id, {
|
|
26315
|
+
x: nextX,
|
|
26316
|
+
y: nextY,
|
|
26317
|
+
width,
|
|
26318
|
+
height
|
|
26319
|
+
});
|
|
26320
|
+
};
|
|
26321
|
+
const nudge = (dx, dy) => {
|
|
26322
|
+
resizeTo((corner === "ne" || corner === "se" ? shape.x + shape.width : shape.x) + dx, (corner === "nw" || corner === "ne" ? shape.y : shape.y - shape.height) + dy);
|
|
26323
|
+
};
|
|
26324
|
+
return /* @__PURE__ */ jsx("span", {
|
|
26325
|
+
"aria-label": `Resize shape (${corner})`,
|
|
26326
|
+
role: "slider",
|
|
26327
|
+
"aria-valuemin": SHAPE_MIN_SIZE,
|
|
26328
|
+
"aria-valuemax": 9999,
|
|
26329
|
+
"aria-valuenow": Math.round(shape.width),
|
|
26330
|
+
tabIndex: 0,
|
|
26331
|
+
className: "scrawl-board__focus-handle",
|
|
26332
|
+
"data-orientation": corner === "nw" || corner === "se" ? "nwse" : "nesw",
|
|
26333
|
+
style: {
|
|
26334
|
+
left: x,
|
|
26335
|
+
top: y
|
|
26336
|
+
},
|
|
26337
|
+
onKeyDown: (event) => {
|
|
26338
|
+
if (event.key === "ArrowUp") {
|
|
26339
|
+
event.preventDefault();
|
|
26340
|
+
nudge(0, .5);
|
|
26341
|
+
} else if (event.key === "ArrowDown") {
|
|
26342
|
+
event.preventDefault();
|
|
26343
|
+
nudge(0, -.5);
|
|
26344
|
+
} else if (event.key === "ArrowRight") {
|
|
26345
|
+
event.preventDefault();
|
|
26346
|
+
nudge(.5, 0);
|
|
26347
|
+
} else if (event.key === "ArrowLeft") {
|
|
26348
|
+
event.preventDefault();
|
|
26349
|
+
nudge(-.5, 0);
|
|
26350
|
+
}
|
|
26351
|
+
},
|
|
26352
|
+
onPointerDown: (event) => {
|
|
26353
|
+
event.stopPropagation();
|
|
26354
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
26355
|
+
setDragging(true);
|
|
26356
|
+
},
|
|
26357
|
+
onPointerMove: (event) => {
|
|
26358
|
+
if (!dragging) return;
|
|
26359
|
+
const board = controller.view.screenToBoard({
|
|
26360
|
+
x: event.clientX,
|
|
26361
|
+
y: event.clientY
|
|
26362
|
+
});
|
|
26363
|
+
resizeTo(board.x, board.y);
|
|
26364
|
+
},
|
|
26365
|
+
onPointerUp: (event) => {
|
|
26366
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
26367
|
+
setDragging(false);
|
|
26368
|
+
}
|
|
26369
|
+
});
|
|
26370
|
+
}
|
|
26371
|
+
function LockDuplicateDelete({ controller, mutable, locked, ids }) {
|
|
26372
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
26373
|
+
className: "scrawl-board__style-group",
|
|
26374
|
+
children: [
|
|
26375
|
+
/* @__PURE__ */ jsx("button", {
|
|
26376
|
+
"aria-label": locked ? "Unlock" : "Lock",
|
|
26377
|
+
disabled: !mutable,
|
|
26378
|
+
onClick: () => {
|
|
26379
|
+
for (const id of ids) controller.content.update(id, locked ? { locked: false } : { locked: true });
|
|
26380
|
+
},
|
|
26381
|
+
"data-tooltip": locked ? "Unlock" : "Lock",
|
|
26382
|
+
type: "button",
|
|
26383
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: locked ? "lock" : "unlock" })
|
|
26384
|
+
}),
|
|
26385
|
+
/* @__PURE__ */ jsx("button", {
|
|
26386
|
+
"aria-label": "Duplicate",
|
|
26387
|
+
disabled: !mutable || locked,
|
|
26388
|
+
onClick: () => {
|
|
26389
|
+
const newIds = controller.content.duplicate(ids);
|
|
26390
|
+
if (newIds.length) controller.content.select(newIds);
|
|
26391
|
+
},
|
|
26392
|
+
"data-tooltip": "Duplicate",
|
|
26393
|
+
type: "button",
|
|
26394
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "duplicate" })
|
|
26395
|
+
}),
|
|
26396
|
+
/* @__PURE__ */ jsx("button", {
|
|
26397
|
+
"aria-label": "Delete",
|
|
26398
|
+
disabled: !mutable || locked,
|
|
26399
|
+
onClick: () => controller.content.remove(ids),
|
|
26400
|
+
"data-tooltip": "Delete",
|
|
26401
|
+
type: "button",
|
|
26402
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "delete" })
|
|
26403
|
+
})
|
|
26404
|
+
]
|
|
26405
|
+
});
|
|
26406
|
+
}
|
|
26407
|
+
//#endregion
|
|
26408
|
+
//#region src/ui/dialogs/inline-editing.tsx
|
|
26409
|
+
/** Inline note, text, and table-cell editors — driven entirely by public controller events and commands. */
|
|
26410
|
+
function InlineEditors({ controller, renderPortal }) {
|
|
26411
|
+
const [editor, setEditor] = useState(null);
|
|
26412
|
+
const [snapshot, setSnapshot] = useState(() => controller.getSnapshot());
|
|
26413
|
+
useEffect(() => controller.subscribe(() => setSnapshot(controller.getSnapshot())), [controller]);
|
|
26414
|
+
const mutable = snapshot.status === "ready" && !snapshot.readOnly;
|
|
26415
|
+
const editedTable = editor?.kind === "table-cell" ? controller.query.get(editor.id) : void 0;
|
|
26416
|
+
const locked = editedTable?.type === "table" && Boolean(editedTable.locked);
|
|
26417
|
+
const anchorRef = useRef(null);
|
|
26418
|
+
const textAreaRef = useRef(null);
|
|
26419
|
+
const measureRef = useRef(null);
|
|
26420
|
+
const coveredRow = editor?.kind === "table-cell" ? editor.row : void 0;
|
|
26421
|
+
const coveredCol = editor?.kind === "table-cell" ? editor.col : void 0;
|
|
26422
|
+
const coveredId = editor && editor.kind !== "note" ? editor.id : null;
|
|
26423
|
+
useEffect(() => {
|
|
26424
|
+
if (!coveredId) return;
|
|
26425
|
+
controller.view.setInlineEditing(coveredRow === void 0 ? { id: coveredId } : {
|
|
26426
|
+
id: coveredId,
|
|
26427
|
+
row: coveredRow,
|
|
26428
|
+
col: coveredCol
|
|
26429
|
+
});
|
|
26430
|
+
return () => {
|
|
26431
|
+
if (controller.getSnapshot().status !== "disposed") controller.view.setInlineEditing(null);
|
|
26432
|
+
};
|
|
26433
|
+
}, [
|
|
26434
|
+
controller,
|
|
26435
|
+
coveredId,
|
|
26436
|
+
coveredRow,
|
|
26437
|
+
coveredCol
|
|
26438
|
+
]);
|
|
26439
|
+
const textValue = editor?.kind === "text" ? editor.text : null;
|
|
26440
|
+
useLayoutEffect(() => {
|
|
26441
|
+
const el = textAreaRef.current;
|
|
26442
|
+
if (textValue === null || !el) return;
|
|
26443
|
+
const cs = getComputedStyle(el);
|
|
26444
|
+
measureRef.current ??= document.createElement("canvas").getContext("2d");
|
|
26445
|
+
const measure = measureRef.current;
|
|
26446
|
+
if (measure) {
|
|
26447
|
+
measure.font = `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`;
|
|
26448
|
+
const widest = Math.max(...textValue.split("\n").map((line) => measure.measureText(line || " ").width));
|
|
26449
|
+
const chrome = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight) + parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth);
|
|
26450
|
+
const cap = Math.min(window.innerWidth * .6, 640);
|
|
26451
|
+
el.style.width = `${Math.ceil(Math.min(cap, widest + chrome + 4))}px`;
|
|
26452
|
+
}
|
|
26453
|
+
el.style.height = "auto";
|
|
26454
|
+
el.style.height = `${el.scrollHeight + parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth)}px`;
|
|
26455
|
+
}, [textValue, editor?.kind === "text" ? editor.fontSizePx : null]);
|
|
26456
|
+
/** Portal children are positioned relative to the Board root, not the viewport. */
|
|
26457
|
+
const rootOffset = () => {
|
|
26458
|
+
const rootRect = (anchorRef.current?.closest("[data-scrawl-root]"))?.getBoundingClientRect();
|
|
26459
|
+
return {
|
|
26460
|
+
left: rootRect?.left ?? 0,
|
|
26461
|
+
top: rootRect?.top ?? 0
|
|
26462
|
+
};
|
|
26463
|
+
};
|
|
26464
|
+
useEffect(() => controller.on("edit-request", (event) => {
|
|
26465
|
+
const offset = rootOffset();
|
|
26466
|
+
if (event.kind === "text" && event.id === null) {
|
|
26467
|
+
const newId = controller.content.add({
|
|
26468
|
+
type: "text",
|
|
26469
|
+
x: event.boardX,
|
|
26470
|
+
y: event.boardY,
|
|
26471
|
+
text: "",
|
|
26472
|
+
color: event.color,
|
|
26473
|
+
fontSize: TEXT_DEFAULT_SIZE
|
|
26474
|
+
});
|
|
26475
|
+
controller.content.select([newId]);
|
|
26476
|
+
setEditor({
|
|
26477
|
+
...event,
|
|
26478
|
+
id: newId,
|
|
26479
|
+
left: event.screenRect.left - offset.left,
|
|
26480
|
+
top: event.screenRect.top - offset.top
|
|
26481
|
+
});
|
|
26482
|
+
return;
|
|
26483
|
+
}
|
|
26484
|
+
setEditor({
|
|
26485
|
+
...event,
|
|
26486
|
+
left: event.screenRect.left - offset.left,
|
|
26487
|
+
top: event.screenRect.top - offset.top
|
|
26488
|
+
});
|
|
26489
|
+
}), [controller]);
|
|
26490
|
+
const commitNote = () => {
|
|
26491
|
+
if (editor?.kind === "note") controller.content.update(editor.id, { text: editor.text });
|
|
26492
|
+
setEditor(null);
|
|
26493
|
+
};
|
|
26494
|
+
const commitText = () => {
|
|
26495
|
+
if (editor?.kind === "text" && editor.id !== null) {
|
|
26496
|
+
const text = editor.text;
|
|
26497
|
+
const existing = controller.query.get(editor.id);
|
|
26498
|
+
if (!text.trim()) controller.content.remove([editor.id]);
|
|
26499
|
+
else if (existing?.type !== "text" || existing.text !== text) controller.content.update(editor.id, { text });
|
|
26500
|
+
}
|
|
26501
|
+
setEditor(null);
|
|
26502
|
+
};
|
|
26503
|
+
const commitTableCell = () => {
|
|
26504
|
+
if (editor?.kind === "table-cell") controller.content.table.commitCell(editor.id, editor.row, editor.col, editor.text);
|
|
26505
|
+
setEditor(null);
|
|
26506
|
+
};
|
|
26507
|
+
useEffect(() => controller.on("page-changing", () => {
|
|
26508
|
+
if (editor?.kind === "note") commitNote();
|
|
26509
|
+
else if (editor?.kind === "text") commitText();
|
|
26510
|
+
else if (editor?.kind === "table-cell") commitTableCell();
|
|
26511
|
+
}));
|
|
26512
|
+
const navigateTableCell = (dRow, dCol) => {
|
|
26513
|
+
if (editor?.kind !== "table-cell") return;
|
|
26514
|
+
controller.content.table.commitCell(editor.id, editor.row, editor.col, editor.text);
|
|
26515
|
+
const table = controller.query.get(editor.id);
|
|
26516
|
+
if (!table || table.type !== "table") {
|
|
26517
|
+
setEditor(null);
|
|
26518
|
+
return;
|
|
26519
|
+
}
|
|
26520
|
+
let nextCol = editor.col + dCol;
|
|
26521
|
+
let nextRow = editor.row + dRow;
|
|
26522
|
+
if (nextCol >= table.cols) {
|
|
26523
|
+
nextCol = 0;
|
|
26524
|
+
nextRow += 1;
|
|
26525
|
+
} else if (nextCol < 0) {
|
|
26526
|
+
nextCol = table.cols - 1;
|
|
26527
|
+
nextRow -= 1;
|
|
26528
|
+
}
|
|
26529
|
+
if (nextRow >= table.rows) nextRow = 0;
|
|
26530
|
+
if (nextRow < 0) nextRow = table.rows - 1;
|
|
26531
|
+
let leftBoard = table.x;
|
|
26532
|
+
for (let c = 0; c < nextCol; c++) leftBoard += table.colWidths[c] ?? 8;
|
|
26533
|
+
let topBoard = table.y;
|
|
26534
|
+
for (let r = 0; r < nextRow; r++) topBoard -= table.rowHeights[r] ?? 3.6;
|
|
26535
|
+
const colWidth = table.colWidths[nextCol] ?? 8;
|
|
26536
|
+
const rowHeight = table.rowHeights[nextRow] ?? 3.6;
|
|
26537
|
+
const topLeft = controller.view.boardToScreen({
|
|
26538
|
+
x: leftBoard,
|
|
26539
|
+
y: topBoard
|
|
26540
|
+
});
|
|
26541
|
+
const bottomRight = controller.view.boardToScreen({
|
|
26542
|
+
x: leftBoard + colWidth,
|
|
26543
|
+
y: topBoard - rowHeight
|
|
26544
|
+
});
|
|
26545
|
+
const offset = rootOffset();
|
|
26546
|
+
setEditor({
|
|
26547
|
+
kind: "table-cell",
|
|
26548
|
+
id: editor.id,
|
|
26549
|
+
row: nextRow,
|
|
26550
|
+
col: nextCol,
|
|
26551
|
+
text: table.cells[`${nextRow},${nextCol}`] ?? "",
|
|
26552
|
+
fontSizePx: editor.fontSizePx,
|
|
26553
|
+
screenRect: {
|
|
26554
|
+
left: topLeft.x,
|
|
26555
|
+
top: topLeft.y,
|
|
26556
|
+
width: Math.max(40, bottomRight.x - topLeft.x),
|
|
26557
|
+
height: Math.max(24, bottomRight.y - topLeft.y)
|
|
26558
|
+
},
|
|
26559
|
+
left: topLeft.x - offset.left,
|
|
26560
|
+
top: topLeft.y - offset.top
|
|
26561
|
+
});
|
|
26562
|
+
};
|
|
26563
|
+
const focusEditor = useCallback((el) => {
|
|
26564
|
+
textAreaRef.current = el;
|
|
26565
|
+
if (!el) return;
|
|
26566
|
+
const doFocus = () => {
|
|
26567
|
+
try {
|
|
26568
|
+
el.focus({ preventScroll: true });
|
|
26569
|
+
el.setSelectionRange(el.value.length, el.value.length);
|
|
26570
|
+
} catch {}
|
|
26571
|
+
};
|
|
26572
|
+
doFocus();
|
|
26573
|
+
requestAnimationFrame(doFocus);
|
|
26574
|
+
}, []);
|
|
26575
|
+
if (!editor) return /* @__PURE__ */ jsx("div", {
|
|
26576
|
+
ref: anchorRef,
|
|
26577
|
+
style: { display: "none" }
|
|
26578
|
+
});
|
|
26579
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
26580
|
+
ref: anchorRef,
|
|
26581
|
+
style: { display: "none" }
|
|
26582
|
+
}), renderPortal(editor.kind === "note" ? /* @__PURE__ */ jsx("textarea", {
|
|
26583
|
+
"aria-label": "Edit note",
|
|
26584
|
+
autoFocus: true,
|
|
26585
|
+
className: "scrawl-board__inline-editor scrawl-board__inline-editor--note",
|
|
26586
|
+
onBlur: commitNote,
|
|
26587
|
+
onChange: (event) => setEditor({
|
|
26588
|
+
...editor,
|
|
26589
|
+
text: event.currentTarget.value
|
|
26590
|
+
}),
|
|
26591
|
+
onKeyDown: (event) => {
|
|
26592
|
+
event.stopPropagation();
|
|
26593
|
+
if (event.key === "Escape") commitNote();
|
|
26594
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) commitNote();
|
|
26595
|
+
},
|
|
26596
|
+
ref: focusEditor,
|
|
26597
|
+
style: {
|
|
26598
|
+
left: editor.left,
|
|
26599
|
+
top: editor.top,
|
|
26600
|
+
width: editor.screenRect.width,
|
|
26601
|
+
height: editor.screenRect.height,
|
|
26602
|
+
backgroundColor: editor.color,
|
|
26603
|
+
fontSize: Math.max(12, editor.screenRect.width * 42 / 512),
|
|
26604
|
+
lineHeight: 1.3,
|
|
26605
|
+
padding: editor.screenRect.width * 40 / 512
|
|
26606
|
+
},
|
|
26607
|
+
value: editor.text
|
|
26608
|
+
}) : editor.kind === "text" ? /* @__PURE__ */ jsx("textarea", {
|
|
26609
|
+
"aria-label": "Edit text",
|
|
26610
|
+
autoFocus: true,
|
|
26611
|
+
className: "scrawl-board__inline-editor scrawl-board__inline-editor--text",
|
|
26612
|
+
onBlur: commitText,
|
|
26613
|
+
onChange: (event) => setEditor({
|
|
26614
|
+
...editor,
|
|
26615
|
+
text: event.currentTarget.value
|
|
26616
|
+
}),
|
|
26617
|
+
onKeyDown: (event) => {
|
|
26618
|
+
event.stopPropagation();
|
|
26619
|
+
if (event.key === "Escape") commitText();
|
|
26620
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) commitText();
|
|
26621
|
+
},
|
|
26622
|
+
ref: focusEditor,
|
|
26623
|
+
rows: Math.max(1, editor.text.split("\n").length),
|
|
26624
|
+
style: {
|
|
26625
|
+
left: editor.left,
|
|
26626
|
+
top: editor.top,
|
|
26627
|
+
color: editor.color,
|
|
26628
|
+
fontSize: Math.max(12, editor.fontSizePx)
|
|
26629
|
+
},
|
|
26630
|
+
value: editor.text
|
|
26631
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
26632
|
+
className: "scrawl-board__inline-editor--table-cell",
|
|
26633
|
+
style: {
|
|
26634
|
+
left: editor.left,
|
|
26635
|
+
top: editor.top,
|
|
26636
|
+
width: editor.screenRect.width,
|
|
26637
|
+
minHeight: editor.screenRect.height
|
|
26638
|
+
},
|
|
26639
|
+
children: [/* @__PURE__ */ jsx("textarea", {
|
|
26640
|
+
"aria-label": `Edit table cell ${editor.row + 1}, ${editor.col + 1}`,
|
|
26641
|
+
autoFocus: true,
|
|
26642
|
+
className: "scrawl-board__inline-editor",
|
|
26643
|
+
onBlur: commitTableCell,
|
|
26644
|
+
onChange: (event) => setEditor({
|
|
26645
|
+
...editor,
|
|
26646
|
+
text: event.currentTarget.value
|
|
26647
|
+
}),
|
|
26648
|
+
onKeyDown: (event) => {
|
|
26649
|
+
event.stopPropagation();
|
|
26650
|
+
if (event.key === "Tab") {
|
|
26651
|
+
event.preventDefault();
|
|
26652
|
+
navigateTableCell(0, event.shiftKey ? -1 : 1);
|
|
26653
|
+
} else if (event.key === "Enter" && !event.shiftKey) {
|
|
26654
|
+
event.preventDefault();
|
|
26655
|
+
navigateTableCell(1, 0);
|
|
26656
|
+
} else if (event.key === "Escape") commitTableCell();
|
|
26657
|
+
},
|
|
26658
|
+
ref: focusEditor,
|
|
26659
|
+
style: { fontSize: Math.max(11, editor.fontSizePx) },
|
|
26660
|
+
value: editor.text
|
|
26661
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
26662
|
+
"aria-label": "Selected object",
|
|
26663
|
+
className: "scrawl-board__inline-editor-actions",
|
|
26664
|
+
role: "toolbar",
|
|
26665
|
+
children: [
|
|
26666
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
26667
|
+
"Cell (",
|
|
26668
|
+
editor.row + 1,
|
|
26669
|
+
", ",
|
|
26670
|
+
editor.col + 1,
|
|
26671
|
+
")"
|
|
26672
|
+
] }),
|
|
26673
|
+
/* @__PURE__ */ jsx("button", {
|
|
26674
|
+
"aria-label": "Add row",
|
|
26675
|
+
onMouseDown: (event) => {
|
|
26676
|
+
event.preventDefault();
|
|
26677
|
+
controller.content.table.addRow(editor.id);
|
|
26678
|
+
},
|
|
26679
|
+
"data-tooltip": "Add row",
|
|
26680
|
+
type: "button",
|
|
26681
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "addRow" })
|
|
26028
26682
|
}),
|
|
26029
|
-
/* @__PURE__ */ jsx(
|
|
26030
|
-
label: "
|
|
26031
|
-
|
|
26032
|
-
|
|
26033
|
-
|
|
26034
|
-
|
|
26035
|
-
|
|
26683
|
+
/* @__PURE__ */ jsx("button", {
|
|
26684
|
+
"aria-label": "Add column",
|
|
26685
|
+
onMouseDown: (event) => {
|
|
26686
|
+
event.preventDefault();
|
|
26687
|
+
controller.content.table.addCol(editor.id);
|
|
26688
|
+
},
|
|
26689
|
+
"data-tooltip": "Add column",
|
|
26690
|
+
type: "button",
|
|
26691
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "addColumn" })
|
|
26692
|
+
}),
|
|
26693
|
+
/* @__PURE__ */ jsx("button", {
|
|
26694
|
+
"aria-label": "Delete row",
|
|
26695
|
+
onMouseDown: (event) => {
|
|
26696
|
+
event.preventDefault();
|
|
26697
|
+
controller.content.table.deleteRow(editor.id, editor.row);
|
|
26698
|
+
setEditor(null);
|
|
26699
|
+
},
|
|
26700
|
+
"data-tooltip": "Delete row",
|
|
26701
|
+
type: "button",
|
|
26702
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "deleteRow" })
|
|
26703
|
+
}),
|
|
26704
|
+
/* @__PURE__ */ jsx("span", {
|
|
26705
|
+
"aria-hidden": "true",
|
|
26706
|
+
className: "scrawl-board__toolbar-divider"
|
|
26036
26707
|
}),
|
|
26037
26708
|
/* @__PURE__ */ jsx(LockDuplicateDelete, {
|
|
26038
26709
|
controller,
|
|
26039
|
-
|
|
26710
|
+
ids: [editor.id],
|
|
26040
26711
|
locked,
|
|
26041
|
-
|
|
26712
|
+
mutable
|
|
26042
26713
|
})
|
|
26043
26714
|
]
|
|
26044
|
-
})
|
|
26045
|
-
] });
|
|
26046
|
-
}
|
|
26047
|
-
/** One corner: drags the board point directly under the pointer, keeping the opposite corner fixed — independent width/height, no proportional lock. */
|
|
26048
|
-
function ShapeResizeHandle({ controller, shape, corner, x, y }) {
|
|
26049
|
-
const [dragging, setDragging] = useState(false);
|
|
26050
|
-
const fixedCorner = () => {
|
|
26051
|
-
return {
|
|
26052
|
-
x: corner === "nw" || corner === "sw" ? shape.x + shape.width : shape.x,
|
|
26053
|
-
y: corner === "nw" || corner === "ne" ? shape.y - shape.height : shape.y
|
|
26054
|
-
};
|
|
26055
|
-
};
|
|
26056
|
-
const resizeTo = (boardX, boardY) => {
|
|
26057
|
-
const fixed = fixedCorner();
|
|
26058
|
-
const width = Math.max(SHAPE_MIN_SIZE, Math.abs(boardX - fixed.x));
|
|
26059
|
-
const height = Math.max(SHAPE_MIN_SIZE, Math.abs(boardY - fixed.y));
|
|
26060
|
-
const nextX = Math.min(boardX, fixed.x);
|
|
26061
|
-
const nextY = Math.max(boardY, fixed.y);
|
|
26062
|
-
controller.content.update(shape.id, {
|
|
26063
|
-
x: nextX,
|
|
26064
|
-
y: nextY,
|
|
26065
|
-
width,
|
|
26066
|
-
height
|
|
26067
|
-
});
|
|
26068
|
-
};
|
|
26069
|
-
const nudge = (dx, dy) => {
|
|
26070
|
-
resizeTo((corner === "ne" || corner === "se" ? shape.x + shape.width : shape.x) + dx, (corner === "nw" || corner === "ne" ? shape.y : shape.y - shape.height) + dy);
|
|
26071
|
-
};
|
|
26072
|
-
return /* @__PURE__ */ jsx("span", {
|
|
26073
|
-
"aria-label": `Resize shape (${corner})`,
|
|
26074
|
-
role: "slider",
|
|
26075
|
-
"aria-valuemin": SHAPE_MIN_SIZE,
|
|
26076
|
-
"aria-valuemax": 9999,
|
|
26077
|
-
"aria-valuenow": Math.round(shape.width),
|
|
26078
|
-
tabIndex: 0,
|
|
26079
|
-
className: "scrawl-board__focus-handle",
|
|
26080
|
-
"data-orientation": corner === "nw" || corner === "se" ? "nwse" : "nesw",
|
|
26081
|
-
style: {
|
|
26082
|
-
left: x,
|
|
26083
|
-
top: y
|
|
26084
|
-
},
|
|
26085
|
-
onKeyDown: (event) => {
|
|
26086
|
-
if (event.key === "ArrowUp") {
|
|
26087
|
-
event.preventDefault();
|
|
26088
|
-
nudge(0, .5);
|
|
26089
|
-
} else if (event.key === "ArrowDown") {
|
|
26090
|
-
event.preventDefault();
|
|
26091
|
-
nudge(0, -.5);
|
|
26092
|
-
} else if (event.key === "ArrowRight") {
|
|
26093
|
-
event.preventDefault();
|
|
26094
|
-
nudge(.5, 0);
|
|
26095
|
-
} else if (event.key === "ArrowLeft") {
|
|
26096
|
-
event.preventDefault();
|
|
26097
|
-
nudge(-.5, 0);
|
|
26098
|
-
}
|
|
26099
|
-
},
|
|
26100
|
-
onPointerDown: (event) => {
|
|
26101
|
-
event.stopPropagation();
|
|
26102
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
26103
|
-
setDragging(true);
|
|
26104
|
-
},
|
|
26105
|
-
onPointerMove: (event) => {
|
|
26106
|
-
if (!dragging) return;
|
|
26107
|
-
const board = controller.view.screenToBoard({
|
|
26108
|
-
x: event.clientX,
|
|
26109
|
-
y: event.clientY
|
|
26110
|
-
});
|
|
26111
|
-
resizeTo(board.x, board.y);
|
|
26112
|
-
},
|
|
26113
|
-
onPointerUp: (event) => {
|
|
26114
|
-
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
26115
|
-
setDragging(false);
|
|
26116
|
-
}
|
|
26117
|
-
});
|
|
26118
|
-
}
|
|
26119
|
-
function LockDuplicateDelete({ controller, mutable, locked, ids }) {
|
|
26120
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
26121
|
-
className: "scrawl-board__style-group",
|
|
26122
|
-
children: [
|
|
26123
|
-
/* @__PURE__ */ jsx("button", {
|
|
26124
|
-
"aria-label": locked ? "Unlock" : "Lock",
|
|
26125
|
-
disabled: !mutable,
|
|
26126
|
-
onClick: () => {
|
|
26127
|
-
for (const id of ids) controller.content.update(id, locked ? { locked: false } : { locked: true });
|
|
26128
|
-
},
|
|
26129
|
-
"data-tooltip": locked ? "Unlock" : "Lock",
|
|
26130
|
-
type: "button",
|
|
26131
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: locked ? "lock" : "unlock" })
|
|
26132
|
-
}),
|
|
26133
|
-
/* @__PURE__ */ jsx("button", {
|
|
26134
|
-
"aria-label": "Duplicate",
|
|
26135
|
-
disabled: !mutable || locked,
|
|
26136
|
-
onClick: () => {
|
|
26137
|
-
const newIds = controller.content.duplicate(ids);
|
|
26138
|
-
if (newIds.length) controller.content.select(newIds);
|
|
26139
|
-
},
|
|
26140
|
-
"data-tooltip": "Duplicate",
|
|
26141
|
-
type: "button",
|
|
26142
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "duplicate" })
|
|
26143
|
-
}),
|
|
26144
|
-
/* @__PURE__ */ jsx("button", {
|
|
26145
|
-
"aria-label": "Delete",
|
|
26146
|
-
disabled: !mutable || locked,
|
|
26147
|
-
onClick: () => controller.content.remove(ids),
|
|
26148
|
-
"data-tooltip": "Delete",
|
|
26149
|
-
type: "button",
|
|
26150
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "delete" })
|
|
26151
|
-
})
|
|
26152
|
-
]
|
|
26153
|
-
});
|
|
26715
|
+
})]
|
|
26716
|
+
}))] });
|
|
26154
26717
|
}
|
|
26155
26718
|
//#endregion
|
|
26156
26719
|
//#region src/ui/toolbar/board-tooltips.tsx
|
|
@@ -26212,7 +26775,7 @@ function BoardTooltips({ renderPortal }) {
|
|
|
26212
26775
|
document.addEventListener("focusin", show);
|
|
26213
26776
|
document.addEventListener("focusout", leave);
|
|
26214
26777
|
document.addEventListener("pointerdown", hide);
|
|
26215
|
-
document.addEventListener("keydown", key);
|
|
26778
|
+
document.addEventListener("keydown", key, true);
|
|
26216
26779
|
window.addEventListener("scroll", hide, true);
|
|
26217
26780
|
window.addEventListener("resize", hide);
|
|
26218
26781
|
return () => {
|
|
@@ -26222,11 +26785,22 @@ function BoardTooltips({ renderPortal }) {
|
|
|
26222
26785
|
document.removeEventListener("focusin", show);
|
|
26223
26786
|
document.removeEventListener("focusout", leave);
|
|
26224
26787
|
document.removeEventListener("pointerdown", hide);
|
|
26225
|
-
document.removeEventListener("keydown", key);
|
|
26788
|
+
document.removeEventListener("keydown", key, true);
|
|
26226
26789
|
window.removeEventListener("scroll", hide, true);
|
|
26227
26790
|
window.removeEventListener("resize", hide);
|
|
26228
26791
|
};
|
|
26229
26792
|
}, []);
|
|
26793
|
+
useEffect(() => {
|
|
26794
|
+
if (!active) return;
|
|
26795
|
+
const observer = new MutationObserver(() => {
|
|
26796
|
+
if (!active.element.isConnected) setActive(null);
|
|
26797
|
+
});
|
|
26798
|
+
observer.observe(document.body, {
|
|
26799
|
+
childList: true,
|
|
26800
|
+
subtree: true
|
|
26801
|
+
});
|
|
26802
|
+
return () => observer.disconnect();
|
|
26803
|
+
}, [active]);
|
|
26230
26804
|
useLayoutEffect(() => {
|
|
26231
26805
|
if (!active || !tooltip.current) return;
|
|
26232
26806
|
const box = active.element.getBoundingClientRect();
|
|
@@ -26745,9 +27319,18 @@ var INKS = [
|
|
|
26745
27319
|
["Forest", "#315d46"],
|
|
26746
27320
|
["Oxblood", "#773f3b"]
|
|
26747
27321
|
];
|
|
26748
|
-
|
|
27322
|
+
var ACTIVITY_KIND = {
|
|
27323
|
+
create: "added",
|
|
27324
|
+
update: "updated",
|
|
27325
|
+
delete: "removed",
|
|
27326
|
+
comment: "commented",
|
|
27327
|
+
share: "shared",
|
|
27328
|
+
export: "exported"
|
|
27329
|
+
};
|
|
27330
|
+
function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons, commentAuthor, onShare, activity, commentFeed, permissions, menuItems }) {
|
|
26749
27331
|
const [welcomeDismissed, setWelcomeDismissed] = useState(false);
|
|
26750
27332
|
const [panel, setPanel] = useState(null);
|
|
27333
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
26751
27334
|
const [pagesOpen, setPagesOpen] = useState(false);
|
|
26752
27335
|
const [moreOpen, setMoreOpen] = useState(false);
|
|
26753
27336
|
const [query, setQuery] = useState("");
|
|
@@ -26768,8 +27351,17 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
26768
27351
|
const enabled = (region) => regions?.[region] !== false;
|
|
26769
27352
|
const mutable = snapshot.status === "ready" && !snapshot.readOnly;
|
|
26770
27353
|
useEffect(() => {
|
|
26771
|
-
if (
|
|
26772
|
-
}, [
|
|
27354
|
+
if (searchOpen) searchRef.current?.focus();
|
|
27355
|
+
}, [searchOpen]);
|
|
27356
|
+
useEffect(() => {
|
|
27357
|
+
if (!searchOpen) return;
|
|
27358
|
+
const dismiss = (event) => {
|
|
27359
|
+
if (searchRef.current?.value) return;
|
|
27360
|
+
if (!event.target.closest?.(".scrawl-board__actions")) setSearchOpen(false);
|
|
27361
|
+
};
|
|
27362
|
+
document.addEventListener("pointerdown", dismiss);
|
|
27363
|
+
return () => document.removeEventListener("pointerdown", dismiss);
|
|
27364
|
+
}, [searchOpen]);
|
|
26773
27365
|
useEffect(() => {
|
|
26774
27366
|
const root = chromeRef.current;
|
|
26775
27367
|
if (!root) return;
|
|
@@ -26866,10 +27458,90 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
26866
27458
|
root.removeEventListener("drop", drop);
|
|
26867
27459
|
};
|
|
26868
27460
|
}, [imagesEnabled]);
|
|
27461
|
+
const [localActivity, setLocalActivity] = useState([]);
|
|
27462
|
+
const [localActivityUnread, setLocalActivityUnread] = useState(0);
|
|
27463
|
+
const hostActivity = activity?.entries;
|
|
27464
|
+
const actorName = commentAuthor?.name ?? "You";
|
|
27465
|
+
useEffect(() => {
|
|
27466
|
+
if (hostActivity) return;
|
|
27467
|
+
let sequence = 0;
|
|
27468
|
+
return controller.on("audit", (payload) => {
|
|
27469
|
+
const event = payload;
|
|
27470
|
+
queueMicrotask(() => {
|
|
27471
|
+
const selected = controller.getSnapshot().selection[0];
|
|
27472
|
+
const box = selected && event.action !== "delete" ? controller.query.bounds(selected) : void 0;
|
|
27473
|
+
const entry = {
|
|
27474
|
+
id: `session-${Date.now()}-${sequence++}`,
|
|
27475
|
+
kind: ACTIVITY_KIND[event.action] ?? "updated",
|
|
27476
|
+
title: event.summary.charAt(0).toUpperCase() + event.summary.slice(1),
|
|
27477
|
+
actorName,
|
|
27478
|
+
at: Date.now(),
|
|
27479
|
+
...box ? { location: {
|
|
27480
|
+
x: (box.minX + box.maxX) / 2,
|
|
27481
|
+
y: (box.minY + box.maxY) / 2
|
|
27482
|
+
} } : {}
|
|
27483
|
+
};
|
|
27484
|
+
setLocalActivity((list) => [entry, ...list].slice(0, 200));
|
|
27485
|
+
setLocalActivityUnread((count) => count + 1);
|
|
27486
|
+
});
|
|
27487
|
+
});
|
|
27488
|
+
}, [
|
|
27489
|
+
controller,
|
|
27490
|
+
hostActivity,
|
|
27491
|
+
actorName
|
|
27492
|
+
]);
|
|
27493
|
+
const activityEntries = hostActivity ?? localActivity;
|
|
27494
|
+
const activityUnread = activity?.unreadCount ?? (hostActivity ? 0 : localActivityUnread);
|
|
27495
|
+
const commentsUnread = commentFeed?.unreadCount ?? 0;
|
|
27496
|
+
const markActivityRead = () => {
|
|
27497
|
+
setLocalActivityUnread(0);
|
|
27498
|
+
if (activityUnread > 0) activity?.onMarkAllRead?.();
|
|
27499
|
+
};
|
|
27500
|
+
const markCommentsRead = () => {
|
|
27501
|
+
if (commentsUnread > 0) commentFeed?.onMarkAllRead?.();
|
|
27502
|
+
};
|
|
27503
|
+
const showComments = enabled("comments");
|
|
27504
|
+
const showActivity = enabled("activity");
|
|
27505
|
+
const [jumpPin, setJumpPin] = useState(null);
|
|
27506
|
+
const jumpTo = (location) => {
|
|
27507
|
+
controller.view.centerOn(location);
|
|
27508
|
+
setJumpPin({
|
|
27509
|
+
location,
|
|
27510
|
+
arrival: Date.now()
|
|
27511
|
+
});
|
|
27512
|
+
};
|
|
27513
|
+
const moreItems = [
|
|
27514
|
+
...enabled("import") && mutable ? [{
|
|
27515
|
+
id: "scrawl:import",
|
|
27516
|
+
label: "Import board JSON",
|
|
27517
|
+
icon: /* @__PURE__ */ jsx(ToolIcon, { tool: "import" })
|
|
27518
|
+
}] : [],
|
|
27519
|
+
...permissions?.canManage ? [{
|
|
27520
|
+
id: "scrawl:permissions",
|
|
27521
|
+
label: "Manage permissions",
|
|
27522
|
+
icon: /* @__PURE__ */ jsx(ToolIcon, { tool: "shield" })
|
|
27523
|
+
}] : [],
|
|
27524
|
+
...menuItems ?? []
|
|
27525
|
+
];
|
|
27526
|
+
const selectMoreItem = (id) => {
|
|
27527
|
+
if (id === "scrawl:import") importRef.current?.click();
|
|
27528
|
+
else if (id === "scrawl:permissions") {
|
|
27529
|
+
const more = chromeRef.current?.querySelector("[aria-label=\"More board actions\"]");
|
|
27530
|
+
if (more) open("permissions", more);
|
|
27531
|
+
} else menuItems?.find((item) => item.id === id)?.onSelect();
|
|
27532
|
+
};
|
|
26869
27533
|
const open = (next, opener) => {
|
|
26870
27534
|
setMoreOpen(false);
|
|
27535
|
+
if (!searchRef.current?.value) setSearchOpen(false);
|
|
26871
27536
|
openerRef.current = opener;
|
|
26872
27537
|
setPanel(next);
|
|
27538
|
+
if (next === "comments") markCommentsRead();
|
|
27539
|
+
if (next === "activity") markActivityRead();
|
|
27540
|
+
};
|
|
27541
|
+
/** A header button opens its own panel, or closes it if it's already open. */
|
|
27542
|
+
const toggle = (next, opener) => {
|
|
27543
|
+
if (panel === next) close();
|
|
27544
|
+
else open(next, opener);
|
|
26873
27545
|
};
|
|
26874
27546
|
const close = useCallback(() => {
|
|
26875
27547
|
setPanel(null);
|
|
@@ -26914,12 +27586,9 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
26914
27586
|
return;
|
|
26915
27587
|
}
|
|
26916
27588
|
if (modifier && event.key.toLowerCase() === "f" && enabled("search")) {
|
|
26917
|
-
|
|
26918
|
-
|
|
26919
|
-
|
|
26920
|
-
event.stopPropagation();
|
|
26921
|
-
open("search", opener);
|
|
26922
|
-
}
|
|
27589
|
+
event.preventDefault();
|
|
27590
|
+
event.stopPropagation();
|
|
27591
|
+
setSearchOpen(true);
|
|
26923
27592
|
return;
|
|
26924
27593
|
}
|
|
26925
27594
|
if (editing) return;
|
|
@@ -26981,14 +27650,149 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
26981
27650
|
ref: chromeRef,
|
|
26982
27651
|
style,
|
|
26983
27652
|
children: [
|
|
26984
|
-
|
|
26985
|
-
|
|
26986
|
-
|
|
26987
|
-
|
|
26988
|
-
|
|
26989
|
-
|
|
26990
|
-
|
|
26991
|
-
|
|
27653
|
+
/* @__PURE__ */ jsxs("div", {
|
|
27654
|
+
className: "scrawl-board__topbar",
|
|
27655
|
+
children: [enabled("pages") && /* @__PURE__ */ jsx("div", {
|
|
27656
|
+
className: "scrawl-board__navigator",
|
|
27657
|
+
children: /* @__PURE__ */ jsx(NotebookNavigation, {
|
|
27658
|
+
controller,
|
|
27659
|
+
snapshot
|
|
27660
|
+
})
|
|
27661
|
+
}), renderSlot(slots?.topBar, (enabled("search") || enabled("export") || showComments || showActivity || moreItems.length > 0 || onShare) && /* @__PURE__ */ jsxs("div", {
|
|
27662
|
+
className: "scrawl-board__header-actions",
|
|
27663
|
+
children: [
|
|
27664
|
+
/* @__PURE__ */ jsxs("div", {
|
|
27665
|
+
"aria-label": "Board actions",
|
|
27666
|
+
className: "scrawl-board__actions",
|
|
27667
|
+
role: "toolbar",
|
|
27668
|
+
children: [
|
|
27669
|
+
enabled("search") && /* @__PURE__ */ jsxs("div", {
|
|
27670
|
+
className: "scrawl-board__search",
|
|
27671
|
+
"data-open": searchOpen ? "" : void 0,
|
|
27672
|
+
"data-scrawl-search": "",
|
|
27673
|
+
children: [
|
|
27674
|
+
/* @__PURE__ */ jsx("button", {
|
|
27675
|
+
"aria-label": "Search Board",
|
|
27676
|
+
"aria-expanded": searchOpen,
|
|
27677
|
+
"data-tooltip": "Search",
|
|
27678
|
+
type: "button",
|
|
27679
|
+
onClick: () => {
|
|
27680
|
+
if (searchOpen && !query) setSearchOpen(false);
|
|
27681
|
+
else setSearchOpen(true);
|
|
27682
|
+
searchRef.current?.focus();
|
|
27683
|
+
},
|
|
27684
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "search" })
|
|
27685
|
+
}),
|
|
27686
|
+
/* @__PURE__ */ jsx("input", {
|
|
27687
|
+
"aria-label": "Search Board",
|
|
27688
|
+
className: "scrawl-board__search-input",
|
|
27689
|
+
placeholder: "Search this board…",
|
|
27690
|
+
ref: searchRef,
|
|
27691
|
+
tabIndex: searchOpen ? void 0 : -1,
|
|
27692
|
+
type: "search",
|
|
27693
|
+
value: query,
|
|
27694
|
+
onChange: (event) => setQuery(event.currentTarget.value),
|
|
27695
|
+
onKeyDown: (event) => {
|
|
27696
|
+
event.stopPropagation();
|
|
27697
|
+
if (event.key !== "Escape") return;
|
|
27698
|
+
event.preventDefault();
|
|
27699
|
+
setQuery("");
|
|
27700
|
+
setSearchOpen(false);
|
|
27701
|
+
chromeRef.current?.querySelector("[aria-label=\"Search Board\"]")?.focus();
|
|
27702
|
+
}
|
|
27703
|
+
}),
|
|
27704
|
+
searchOpen && query && /* @__PURE__ */ jsx("div", {
|
|
27705
|
+
"aria-live": "polite",
|
|
27706
|
+
className: "scrawl-board__search-results",
|
|
27707
|
+
"data-scrawl-search-results": "",
|
|
27708
|
+
children: results.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No matching Board content." }) : results.map((result) => /* @__PURE__ */ jsxs("button", {
|
|
27709
|
+
type: "button",
|
|
27710
|
+
onClick: () => {
|
|
27711
|
+
if (result.kind === "comment") controller.comments.open(result.id);
|
|
27712
|
+
else controller.content.select([result.id]);
|
|
27713
|
+
controller.view.centerOn({
|
|
27714
|
+
x: result.x,
|
|
27715
|
+
y: result.y
|
|
27716
|
+
});
|
|
27717
|
+
setAnnouncement(`${result.title} selected`);
|
|
27718
|
+
setQuery("");
|
|
27719
|
+
setSearchOpen(false);
|
|
27720
|
+
},
|
|
27721
|
+
children: [/* @__PURE__ */ jsx("strong", { children: result.title }), /* @__PURE__ */ jsx("span", { children: result.snippet })]
|
|
27722
|
+
}, `${result.kind}:${result.id}`))
|
|
27723
|
+
})
|
|
27724
|
+
]
|
|
27725
|
+
}),
|
|
27726
|
+
enabled("export") && /* @__PURE__ */ jsx("button", {
|
|
27727
|
+
"aria-label": "Export Board",
|
|
27728
|
+
"aria-expanded": panel === "export",
|
|
27729
|
+
onClick: (event) => toggle("export", event.currentTarget),
|
|
27730
|
+
"data-tooltip": "Export",
|
|
27731
|
+
type: "button",
|
|
27732
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "export" })
|
|
27733
|
+
}),
|
|
27734
|
+
showComments && /* @__PURE__ */ jsxs("button", {
|
|
27735
|
+
"aria-expanded": panel === "comments",
|
|
27736
|
+
"aria-label": commentsUnread > 0 ? `Comments (${commentsUnread} unread)` : "Comments",
|
|
27737
|
+
"data-tooltip": "Comments",
|
|
27738
|
+
type: "button",
|
|
27739
|
+
onClick: (event) => toggle("comments", event.currentTarget),
|
|
27740
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "comment" }), commentsUnread > 0 && /* @__PURE__ */ jsx("span", {
|
|
27741
|
+
"aria-hidden": "true",
|
|
27742
|
+
className: "scrawl-board__badge",
|
|
27743
|
+
children: commentsUnread > 99 ? "99+" : commentsUnread
|
|
27744
|
+
})]
|
|
27745
|
+
}),
|
|
27746
|
+
showActivity && /* @__PURE__ */ jsxs("button", {
|
|
27747
|
+
"aria-expanded": panel === "activity",
|
|
27748
|
+
"aria-label": activityUnread > 0 ? `Recent activity (${activityUnread} unread)` : "Recent activity",
|
|
27749
|
+
"data-tooltip": "Recent activity",
|
|
27750
|
+
type: "button",
|
|
27751
|
+
onClick: (event) => toggle("activity", event.currentTarget),
|
|
27752
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "activity" }), activityUnread > 0 && /* @__PURE__ */ jsx("span", {
|
|
27753
|
+
"aria-hidden": "true",
|
|
27754
|
+
className: "scrawl-board__badge",
|
|
27755
|
+
children: activityUnread > 99 ? "99+" : activityUnread
|
|
27756
|
+
})]
|
|
27757
|
+
}),
|
|
27758
|
+
moreItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
27759
|
+
"aria-hidden": "true",
|
|
27760
|
+
className: "scrawl-board__actions-divider"
|
|
27761
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
27762
|
+
"aria-expanded": panel === "more",
|
|
27763
|
+
"aria-haspopup": "menu",
|
|
27764
|
+
"aria-label": "More board actions",
|
|
27765
|
+
"data-tooltip": "More",
|
|
27766
|
+
type: "button",
|
|
27767
|
+
onClick: (event) => toggle("more", event.currentTarget),
|
|
27768
|
+
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "moreVertical" })
|
|
27769
|
+
})] })
|
|
27770
|
+
]
|
|
27771
|
+
}),
|
|
27772
|
+
onShare && /* @__PURE__ */ jsxs("button", {
|
|
27773
|
+
className: "scrawl-board__share",
|
|
27774
|
+
onClick: onShare,
|
|
27775
|
+
type: "button",
|
|
27776
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "share" }), /* @__PURE__ */ jsx("span", { children: "Share" })]
|
|
27777
|
+
}),
|
|
27778
|
+
enabled("import") && /* @__PURE__ */ jsx("input", {
|
|
27779
|
+
accept: "application/json,.json",
|
|
27780
|
+
"aria-label": "Choose Board JSON",
|
|
27781
|
+
className: "scrawl-board__file-input",
|
|
27782
|
+
onChange: (event) => {
|
|
27783
|
+
importDocument(event.currentTarget.files?.[0]);
|
|
27784
|
+
event.currentTarget.value = "";
|
|
27785
|
+
},
|
|
27786
|
+
ref: importRef,
|
|
27787
|
+
type: "file"
|
|
27788
|
+
}),
|
|
27789
|
+
panel === "more" && /* @__PURE__ */ jsx(BoardMoreMenu, {
|
|
27790
|
+
items: moreItems,
|
|
27791
|
+
onClose: close,
|
|
27792
|
+
onSelect: selectMoreItem
|
|
27793
|
+
})
|
|
27794
|
+
]
|
|
27795
|
+
}))]
|
|
26992
27796
|
}),
|
|
26993
27797
|
enabled("pages") && /* @__PURE__ */ jsx(PagesPanel, {
|
|
26994
27798
|
controller,
|
|
@@ -27090,45 +27894,29 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
27090
27894
|
})]
|
|
27091
27895
|
})]
|
|
27092
27896
|
}),
|
|
27093
|
-
|
|
27094
|
-
|
|
27095
|
-
|
|
27096
|
-
|
|
27097
|
-
|
|
27098
|
-
|
|
27099
|
-
|
|
27100
|
-
|
|
27101
|
-
|
|
27102
|
-
|
|
27103
|
-
|
|
27104
|
-
|
|
27105
|
-
|
|
27106
|
-
|
|
27107
|
-
|
|
27108
|
-
|
|
27109
|
-
|
|
27110
|
-
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
onChange: (event) => {
|
|
27117
|
-
importDocument(event.currentTarget.files?.[0]);
|
|
27118
|
-
event.currentTarget.value = "";
|
|
27119
|
-
},
|
|
27120
|
-
ref: importRef,
|
|
27121
|
-
type: "file"
|
|
27122
|
-
})] }),
|
|
27123
|
-
enabled("export") && /* @__PURE__ */ jsx("button", {
|
|
27124
|
-
"aria-label": "Export Board",
|
|
27125
|
-
onClick: (event) => open("export", event.currentTarget),
|
|
27126
|
-
"data-tooltip": "Export",
|
|
27127
|
-
type: "button",
|
|
27128
|
-
children: /* @__PURE__ */ jsx(ToolIcon, { tool: "export" })
|
|
27129
|
-
})
|
|
27130
|
-
]
|
|
27131
|
-
})),
|
|
27897
|
+
panel === "comments" && /* @__PURE__ */ jsx(CommentsDrawer, {
|
|
27898
|
+
comments: controller.comments.list(),
|
|
27899
|
+
controller,
|
|
27900
|
+
onClose: close,
|
|
27901
|
+
onMarkAllRead: markCommentsRead,
|
|
27902
|
+
unread: commentsUnread
|
|
27903
|
+
}),
|
|
27904
|
+
panel === "activity" && /* @__PURE__ */ jsx(ActivityDrawer, {
|
|
27905
|
+
entries: activityEntries,
|
|
27906
|
+
onClose: close,
|
|
27907
|
+
onJump: jumpTo,
|
|
27908
|
+
onMarkAllRead: markActivityRead,
|
|
27909
|
+
unread: activityUnread
|
|
27910
|
+
}),
|
|
27911
|
+
panel === "permissions" && permissions && /* @__PURE__ */ jsx(PermissionsDrawer, {
|
|
27912
|
+
onClose: close,
|
|
27913
|
+
permissions
|
|
27914
|
+
}),
|
|
27915
|
+
jumpPin && /* @__PURE__ */ jsx(JumpPin, {
|
|
27916
|
+
arrival: jumpPin.arrival,
|
|
27917
|
+
controller,
|
|
27918
|
+
location: jumpPin.location
|
|
27919
|
+
}),
|
|
27132
27920
|
renderSlot(slots?.toolbar, (enabled("tools") || enabled("style") || enabled("history") || enabled("pages")) && /* @__PURE__ */ jsxs("div", {
|
|
27133
27921
|
"aria-label": "Board tools",
|
|
27134
27922
|
className: "scrawl-board__tools",
|
|
@@ -27334,47 +28122,16 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
27334
28122
|
}, color))
|
|
27335
28123
|
})
|
|
27336
28124
|
})),
|
|
27337
|
-
panel === "search" && renderPortal(/* @__PURE__ */ jsx(ChromePanel, {
|
|
27338
|
-
anchor: "actions",
|
|
27339
|
-
label: "Search Board",
|
|
27340
|
-
onClose: close,
|
|
27341
|
-
slot: slots?.dialogs,
|
|
27342
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
27343
|
-
"data-scrawl-search": "",
|
|
27344
|
-
children: [/* @__PURE__ */ jsxs("label", { children: ["Search Board", /* @__PURE__ */ jsx("input", {
|
|
27345
|
-
onChange: (event) => setQuery(event.currentTarget.value),
|
|
27346
|
-
ref: searchRef,
|
|
27347
|
-
type: "search",
|
|
27348
|
-
value: query
|
|
27349
|
-
})] }), /* @__PURE__ */ jsx("div", {
|
|
27350
|
-
"aria-live": "polite",
|
|
27351
|
-
"data-scrawl-search-results": "",
|
|
27352
|
-
children: query && results.length === 0 ? /* @__PURE__ */ jsx("p", { children: "No matching Board content." }) : results.map((result) => /* @__PURE__ */ jsxs("button", {
|
|
27353
|
-
onClick: () => {
|
|
27354
|
-
if (result.kind === "comment") controller.comments.open(result.id);
|
|
27355
|
-
else controller.content.select([result.id]);
|
|
27356
|
-
controller.view.centerOn({
|
|
27357
|
-
x: result.x,
|
|
27358
|
-
y: result.y
|
|
27359
|
-
});
|
|
27360
|
-
setAnnouncement(`${result.title} selected`);
|
|
27361
|
-
close();
|
|
27362
|
-
},
|
|
27363
|
-
type: "button",
|
|
27364
|
-
children: [/* @__PURE__ */ jsx("strong", { children: result.title }), /* @__PURE__ */ jsx("span", { children: result.snippet })]
|
|
27365
|
-
}, `${result.kind}:${result.id}`))
|
|
27366
|
-
})]
|
|
27367
|
-
})
|
|
27368
|
-
})),
|
|
27369
28125
|
panel === "export" && renderPortal(/* @__PURE__ */ jsx(ChromePanel, {
|
|
27370
28126
|
anchor: "actions",
|
|
28127
|
+
headless: true,
|
|
27371
28128
|
label: "Export Board",
|
|
27372
28129
|
onClose: close,
|
|
27373
28130
|
slot: slots?.dialogs,
|
|
27374
28131
|
children: /* @__PURE__ */ jsxs("div", {
|
|
27375
28132
|
className: "scrawl-board__export-options",
|
|
27376
28133
|
children: [
|
|
27377
|
-
/* @__PURE__ */
|
|
28134
|
+
/* @__PURE__ */ jsxs("button", {
|
|
27378
28135
|
"aria-label": "Export PNG",
|
|
27379
28136
|
onClick: () => {
|
|
27380
28137
|
controller.export.png().then((blob) => blob && download(blob, `${snapshot.documentId}.png`));
|
|
@@ -27382,9 +28139,9 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
27382
28139
|
close();
|
|
27383
28140
|
},
|
|
27384
28141
|
type: "button",
|
|
27385
|
-
children: "PNG
|
|
28142
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "image" }), /* @__PURE__ */ jsx("span", { children: "Export as PNG" })]
|
|
27386
28143
|
}),
|
|
27387
|
-
/* @__PURE__ */
|
|
28144
|
+
/* @__PURE__ */ jsxs("button", {
|
|
27388
28145
|
"aria-label": "Export SVG",
|
|
27389
28146
|
onClick: () => {
|
|
27390
28147
|
download(new Blob([controller.export.svg()], { type: "image/svg+xml" }), `${snapshot.documentId}.svg`);
|
|
@@ -27392,9 +28149,9 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
27392
28149
|
close();
|
|
27393
28150
|
},
|
|
27394
28151
|
type: "button",
|
|
27395
|
-
children: "SVG
|
|
28152
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "shape:triangle" }), /* @__PURE__ */ jsx("span", { children: "Export as SVG" })]
|
|
27396
28153
|
}),
|
|
27397
|
-
/* @__PURE__ */
|
|
28154
|
+
/* @__PURE__ */ jsxs("button", {
|
|
27398
28155
|
"aria-label": "Export JSON",
|
|
27399
28156
|
onClick: () => {
|
|
27400
28157
|
download(new Blob([JSON.stringify(controller.export.json(), null, 2)], { type: "application/json" }), `${snapshot.documentId}.json`);
|
|
@@ -27402,7 +28159,7 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
27402
28159
|
close();
|
|
27403
28160
|
},
|
|
27404
28161
|
type: "button",
|
|
27405
|
-
children: "
|
|
28162
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { tool: "page" }), /* @__PURE__ */ jsx("span", { children: "Export as JSON" })]
|
|
27406
28163
|
})
|
|
27407
28164
|
]
|
|
27408
28165
|
})
|
|
@@ -27719,7 +28476,7 @@ function ScrawlProvider({ controller, children, preset = "light", theme, portalC
|
|
|
27719
28476
|
* field except `canvas` (pass `canvas={null}` for a headless board, or an
|
|
27720
28477
|
* existing `<canvas>` element to control its identity yourself).
|
|
27721
28478
|
*/
|
|
27722
|
-
function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }) {
|
|
28479
|
+
function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, onShare, activity, commentFeed, permissions, menuItems, ...options }) {
|
|
27723
28480
|
const [runtime, setRuntime] = useState(null);
|
|
27724
28481
|
const runtimeRef = useRef(null);
|
|
27725
28482
|
const ownedCanvasRef = useRef(null);
|
|
@@ -27816,8 +28573,13 @@ function Scrawl({ children, preset, theme, portalContainer, className, style, on
|
|
|
27816
28573
|
className,
|
|
27817
28574
|
style,
|
|
27818
28575
|
children: [runtime.canvas && /* @__PURE__ */ jsx(ScrawlCanvas, { element: runtime.canvas }), children ?? /* @__PURE__ */ jsx(ScrawlDefaultUI, {
|
|
28576
|
+
activity,
|
|
27819
28577
|
commentAuthor: options.identity,
|
|
27820
|
-
|
|
28578
|
+
commentFeed,
|
|
28579
|
+
icons,
|
|
28580
|
+
menuItems,
|
|
28581
|
+
onShare,
|
|
28582
|
+
permissions
|
|
27821
28583
|
})]
|
|
27822
28584
|
});
|
|
27823
28585
|
}
|
|
@@ -27854,12 +28616,17 @@ function ScrawlCanvas({ element, className, style, "aria-label": ariaLabel = "Sc
|
|
|
27854
28616
|
style
|
|
27855
28617
|
});
|
|
27856
28618
|
}
|
|
27857
|
-
function ScrawlDefaultUI({ className, style, regions, slots, icons, commentAuthor }) {
|
|
28619
|
+
function ScrawlDefaultUI({ className, style, regions, slots, icons, commentAuthor, onShare, activity, commentFeed, permissions, menuItems }) {
|
|
27858
28620
|
return /* @__PURE__ */ jsx(DefaultBoardChrome, {
|
|
28621
|
+
activity,
|
|
27859
28622
|
className,
|
|
27860
28623
|
commentAuthor,
|
|
28624
|
+
commentFeed,
|
|
27861
28625
|
controller: useScrawlController(),
|
|
27862
28626
|
icons,
|
|
28627
|
+
menuItems,
|
|
28628
|
+
onShare,
|
|
28629
|
+
permissions,
|
|
27863
28630
|
regions,
|
|
27864
28631
|
renderPortal: (children) => /* @__PURE__ */ jsx(ScrawlPortal, { children }),
|
|
27865
28632
|
slots,
|