@neta-art/cohub 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -2
- package/dist/{board.d.ts → board/animation.d.ts} +4 -2
- package/dist/{chunks/board.js → board/animation.js} +5 -44
- package/dist/board/codec.d.ts +25 -0
- package/dist/board/codec.js +252 -0
- package/dist/board/core/bindings.d.ts +45 -0
- package/dist/board/core/bindings.js +162 -0
- package/dist/board/core/draw-geometry.d.ts +31 -0
- package/dist/board/core/draw-geometry.js +183 -0
- package/dist/board/core/export-assets.d.ts +14 -0
- package/dist/board/core/export-assets.js +22 -0
- package/dist/board/core/export-plan.d.ts +84 -0
- package/dist/board/core/export-plan.js +128 -0
- package/dist/board/core/file-preview.d.ts +176 -0
- package/dist/board/core/file-preview.js +271 -0
- package/dist/board/core/palette.d.ts +42 -0
- package/dist/board/core/palette.js +138 -0
- package/dist/board/core/shape-definition.d.ts +35 -0
- package/dist/board/core/shape-definition.js +64 -0
- package/dist/board/core/shape-types.d.ts +144 -0
- package/dist/board/core/shape-types.js +27 -0
- package/dist/board/core/text-metrics.d.ts +18 -0
- package/dist/board/core/text-metrics.js +43 -0
- package/dist/board/export/index.d.ts +56 -0
- package/dist/board/export/index.js +86 -0
- package/dist/board/export/scene.d.ts +35 -0
- package/dist/board/export/scene.js +69 -0
- package/dist/board/geometry.d.ts +131 -0
- package/dist/board/geometry.js +468 -0
- package/dist/board/headless/index.d.ts +69 -0
- package/dist/board/headless/index.js +150 -0
- package/dist/board/image-key.d.ts +19 -0
- package/dist/board/image-key.js +34 -0
- package/dist/board/index.d.ts +16 -0
- package/dist/board/index.js +16 -0
- package/dist/board/render/index.d.ts +6 -0
- package/dist/board/render/index.js +6 -0
- package/dist/board/render/palette.d.ts +5 -0
- package/dist/board/render/palette.js +30 -0
- package/dist/board/render/renderers/arrow-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/arrow-card-renderer.js +157 -0
- package/dist/board/render/renderers/base-card-renderer.d.ts +46 -0
- package/dist/board/render/renderers/base-card-renderer.js +153 -0
- package/dist/board/render/renderers/board-renderer-registry.d.ts +85 -0
- package/dist/board/render/renderers/board-renderer-registry.js +43 -0
- package/dist/board/render/renderers/draw-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/draw-card-renderer.js +82 -0
- package/dist/board/render/renderers/far-plate.d.ts +39 -0
- package/dist/board/render/renderers/far-plate.js +92 -0
- package/dist/board/render/renderers/file-card-renderer.d.ts +33 -0
- package/dist/board/render/renderers/file-card-renderer.js +371 -0
- package/dist/board/render/renderers/frame-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/frame-card-renderer.js +99 -0
- package/dist/board/render/renderers/geo-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/geo-card-renderer.js +128 -0
- package/dist/board/render/renderers/image-card-renderer.d.ts +8 -0
- package/dist/board/render/renderers/image-card-renderer.js +138 -0
- package/dist/board/render/renderers/note-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/note-card-renderer.js +112 -0
- package/dist/board/render/renderers/text-card-renderer.d.ts +7 -0
- package/dist/board/render/renderers/text-card-renderer.js +94 -0
- package/dist/board/render/renderers/unknown-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/unknown-card-renderer.js +102 -0
- package/dist/board/render/renderers/video-card-renderer.d.ts +7 -0
- package/dist/board/render/renderers/video-card-renderer.js +146 -0
- package/dist/board/render/text-measurement.d.ts +30 -0
- package/dist/board/render/text-measurement.js +57 -0
- package/dist/board/render/text-resolution.d.ts +16 -0
- package/dist/board/render/text-resolution.js +43 -0
- package/dist/board/render/themes/board-theme-registry.d.ts +20 -0
- package/dist/board/render/themes/board-theme-registry.js +13 -0
- package/dist/board/render/themes/clean-theme.d.ts +5 -0
- package/dist/board/render/themes/clean-theme.js +102 -0
- package/dist/chunks/http.d.ts +10 -40
- package/dist/chunks/http.js +26 -4
- package/dist/chunks/transport.js +4 -2
- package/dist/chunks/websocket.d.ts +832 -21
- package/dist/chunks/websocket.js +19 -2
- package/dist/http.d.ts +2 -3
- package/dist/index.d.ts +100 -5
- package/dist/index.js +391 -1
- package/dist/protocol/dist/board-constants.d.ts +23 -0
- package/dist/protocol/dist/board-constants.js +60 -0
- package/dist/protocol/dist/board-document.d.ts +1124 -0
- package/dist/protocol/dist/board-document.js +331 -0
- package/dist/protocol/dist/board.d.ts +186 -0
- package/dist/protocol/dist/board.js +207 -0
- package/dist/protocol/dist/index.d.ts +5 -0
- package/dist/protocol/dist/index.js +5 -0
- package/dist/protocol/dist/provenance.js +12 -0
- package/dist/protocol/dist/realtime/board-awareness.d.ts +1 -0
- package/dist/protocol/dist/realtime/board-awareness.js +117 -0
- package/dist/protocol/dist/realtime/types.d.ts +2 -0
- package/package.json +39 -4
- package/dist/board.js +0 -2
- package/dist/chunks/board.d.ts +0 -2589
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
+
import { pickBoardColor } from "../../core/palette.js";
|
|
3
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
4
|
+
import { drawFarPlate } from "./far-plate.js";
|
|
5
|
+
import { createLabel, positionShell } from "./base-card-renderer.js";
|
|
6
|
+
import { Container, Graphics } from "pixi.js";
|
|
7
|
+
//#region src/board/render/renderers/frame-card-renderer.ts
|
|
8
|
+
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
function sync(container, item, context) {
|
|
10
|
+
const parts = partsByContainer.get(container);
|
|
11
|
+
if (!parts || item.type !== "frame") return;
|
|
12
|
+
const { frame, label, color, locked } = item;
|
|
13
|
+
positionShell(parts.root, item);
|
|
14
|
+
syncTextResolution(parts.label, parts, context.zoom);
|
|
15
|
+
const selected = context.selectedIds.has(item.id);
|
|
16
|
+
const hovered = context.hoveredId === item.id;
|
|
17
|
+
const palette = pickBoardColor(context.colors, color, context.colorScheme);
|
|
18
|
+
const stroke = selected ? context.palette.brand : palette.stroke;
|
|
19
|
+
const alpha = selected ? 1 : hovered ? .85 : .55;
|
|
20
|
+
const visualSig = [
|
|
21
|
+
frame.width,
|
|
22
|
+
frame.height,
|
|
23
|
+
selected,
|
|
24
|
+
hovered,
|
|
25
|
+
palette.fill,
|
|
26
|
+
stroke
|
|
27
|
+
].join("|");
|
|
28
|
+
if (visualSig !== parts.visualSig) {
|
|
29
|
+
parts.visualSig = visualSig;
|
|
30
|
+
parts.box.clear();
|
|
31
|
+
parts.box.roundRect(0, 0, frame.width, frame.height, 4).fill({
|
|
32
|
+
color: palette.fill,
|
|
33
|
+
alpha: .04
|
|
34
|
+
});
|
|
35
|
+
parts.box.roundRect(0, 0, frame.width, frame.height, 4).stroke({
|
|
36
|
+
color: stroke,
|
|
37
|
+
width: selected ? 2 : 1.5,
|
|
38
|
+
alpha
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const title = locked ? `🔒 ${label || "Frame"}` : label || "Frame";
|
|
42
|
+
const textSig = [title, palette.stroke].join("|");
|
|
43
|
+
if (textSig !== parts.textSig) {
|
|
44
|
+
parts.textSig = textSig;
|
|
45
|
+
parts.label.text = title;
|
|
46
|
+
parts.label.style.fill = palette.stroke;
|
|
47
|
+
}
|
|
48
|
+
parts.label.position.set(4, -18);
|
|
49
|
+
}
|
|
50
|
+
const frameCardRenderer = {
|
|
51
|
+
id: "frame-card",
|
|
52
|
+
canRender: (item) => item.type === "frame",
|
|
53
|
+
create: (item, context) => {
|
|
54
|
+
const root = new Container();
|
|
55
|
+
const box = new Graphics();
|
|
56
|
+
const label = createLabel("", {
|
|
57
|
+
fill: context.palette.muted,
|
|
58
|
+
fontFamily: BOARD_FONT_STACK,
|
|
59
|
+
fontSize: 12,
|
|
60
|
+
fontWeight: "600"
|
|
61
|
+
});
|
|
62
|
+
root.addChild(box, label);
|
|
63
|
+
partsByContainer.set(root, {
|
|
64
|
+
root,
|
|
65
|
+
box,
|
|
66
|
+
label,
|
|
67
|
+
visualSig: "",
|
|
68
|
+
textSig: "",
|
|
69
|
+
resolution: label.resolution
|
|
70
|
+
});
|
|
71
|
+
sync(root, item, context);
|
|
72
|
+
return root;
|
|
73
|
+
},
|
|
74
|
+
update: (container, item, context) => {
|
|
75
|
+
sync(container, item, context);
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* Far LOD: a frame is a background region, so it draws as a faint wash with no
|
|
79
|
+
* label.
|
|
80
|
+
*
|
|
81
|
+
* Batching it is what keeps it *behind* its contents. As a live container it
|
|
82
|
+
* would be drawn above the whole far layer no matter where the document places
|
|
83
|
+
* it, so a frame would hide every card inside it.
|
|
84
|
+
*/
|
|
85
|
+
renderFar: (graphics, item, context) => {
|
|
86
|
+
if (item.type !== "frame") return;
|
|
87
|
+
const palette = pickBoardColor(context.colors, item.color, context.colorScheme);
|
|
88
|
+
drawFarPlate(graphics, item.frame, {
|
|
89
|
+
fill: palette.fill,
|
|
90
|
+
fillAlpha: .07
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
destroy: (container) => {
|
|
94
|
+
partsByContainer.delete(container);
|
|
95
|
+
container.destroy({ children: true });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
//#endregion
|
|
99
|
+
export { frameCardRenderer };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
+
import { pickBoardColor } from "../../core/palette.js";
|
|
3
|
+
import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
|
|
4
|
+
import { drawFarPlate } from "./far-plate.js";
|
|
5
|
+
import { positionShell } from "./base-card-renderer.js";
|
|
6
|
+
import { Container, Graphics, Text } from "pixi.js";
|
|
7
|
+
//#region src/board/render/renderers/geo-card-renderer.ts
|
|
8
|
+
const RADIUS = 8;
|
|
9
|
+
const LABEL_PADDING = 8;
|
|
10
|
+
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
function traceOutline(graphics, geo, width, height) {
|
|
12
|
+
switch (geo) {
|
|
13
|
+
case "ellipse":
|
|
14
|
+
graphics.ellipse(width / 2, height / 2, width / 2, height / 2);
|
|
15
|
+
return;
|
|
16
|
+
case "diamond":
|
|
17
|
+
graphics.moveTo(width / 2, 0).lineTo(width, height / 2).lineTo(width / 2, height).lineTo(0, height / 2).closePath();
|
|
18
|
+
return;
|
|
19
|
+
case "triangle":
|
|
20
|
+
graphics.moveTo(width / 2, 0).lineTo(width, height).lineTo(0, height).closePath();
|
|
21
|
+
return;
|
|
22
|
+
case "rounded":
|
|
23
|
+
graphics.roundRect(0, 0, width, height, RADIUS);
|
|
24
|
+
return;
|
|
25
|
+
default: graphics.rect(0, 0, width, height);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function sync(container, item, context) {
|
|
29
|
+
const parts = partsByContainer.get(container);
|
|
30
|
+
if (!parts) return;
|
|
31
|
+
positionShell(parts.root, item);
|
|
32
|
+
const { width, height } = item.frame;
|
|
33
|
+
const selected = context.selectedIds.has(item.id);
|
|
34
|
+
const hovered = context.hoveredId === item.id;
|
|
35
|
+
const color = pickBoardColor(context.colors, item.color, context.colorScheme);
|
|
36
|
+
syncTextResolution(parts.label, parts, context.zoom);
|
|
37
|
+
const resizing = context.resizingIds.has(item.id);
|
|
38
|
+
const visualSig = [
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
selected,
|
|
42
|
+
hovered,
|
|
43
|
+
item.geo,
|
|
44
|
+
item.fillOpacity,
|
|
45
|
+
color.fill,
|
|
46
|
+
color.stroke,
|
|
47
|
+
context.palette.muted
|
|
48
|
+
].join("|");
|
|
49
|
+
if (visualSig !== parts.visualSig) {
|
|
50
|
+
parts.visualSig = visualSig;
|
|
51
|
+
parts.shape.clear();
|
|
52
|
+
traceOutline(parts.shape, item.geo, width, height);
|
|
53
|
+
if (item.fillOpacity > 0) parts.shape.fill({
|
|
54
|
+
color: color.fill,
|
|
55
|
+
alpha: item.fillOpacity
|
|
56
|
+
});
|
|
57
|
+
traceOutline(parts.shape, item.geo, width, height);
|
|
58
|
+
parts.shape.stroke({
|
|
59
|
+
color: selected ? color.stroke : hovered ? context.palette.muted : color.stroke,
|
|
60
|
+
width: selected ? 2.5 : 1.75,
|
|
61
|
+
alpha: selected ? 1 : hovered ? .95 : .85
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const textSig = [item.text, color.label].join("|");
|
|
65
|
+
if (textSig !== parts.textSig) {
|
|
66
|
+
parts.textSig = textSig;
|
|
67
|
+
parts.label.text = item.text;
|
|
68
|
+
parts.label.visible = item.text.length > 0;
|
|
69
|
+
parts.label.style.fill = color.label;
|
|
70
|
+
}
|
|
71
|
+
syncTextWrapWidth(parts.label, parts, Math.max(1, width - LABEL_PADDING * 2), resizing);
|
|
72
|
+
parts.label.position.set(width / 2, height / 2);
|
|
73
|
+
}
|
|
74
|
+
const geoCardRenderer = {
|
|
75
|
+
id: "geo-card",
|
|
76
|
+
canRender: (item) => item.type === "geo",
|
|
77
|
+
create: (item, context) => {
|
|
78
|
+
const root = new Container();
|
|
79
|
+
const shape = new Graphics();
|
|
80
|
+
const resolution = textResolutionForZoom(context.zoom);
|
|
81
|
+
const label = new Text({
|
|
82
|
+
text: "",
|
|
83
|
+
style: {
|
|
84
|
+
fill: 16777215,
|
|
85
|
+
fontFamily: BOARD_FONT_STACK,
|
|
86
|
+
fontSize: 14,
|
|
87
|
+
fontWeight: "500",
|
|
88
|
+
align: "center",
|
|
89
|
+
wordWrap: true,
|
|
90
|
+
lineHeight: 20
|
|
91
|
+
},
|
|
92
|
+
resolution,
|
|
93
|
+
roundPixels: true
|
|
94
|
+
});
|
|
95
|
+
label.anchor.set(.5);
|
|
96
|
+
root.addChild(shape, label);
|
|
97
|
+
partsByContainer.set(root, {
|
|
98
|
+
root,
|
|
99
|
+
shape,
|
|
100
|
+
label,
|
|
101
|
+
visualSig: "",
|
|
102
|
+
textSig: "",
|
|
103
|
+
wrapWidth: 0,
|
|
104
|
+
resolution
|
|
105
|
+
});
|
|
106
|
+
if (item.type === "geo") sync(root, item, context);
|
|
107
|
+
return root;
|
|
108
|
+
},
|
|
109
|
+
update: (container, item, context) => {
|
|
110
|
+
if (item.type === "geo") sync(container, item, context);
|
|
111
|
+
},
|
|
112
|
+
renderFar: (graphics, item, context) => {
|
|
113
|
+
if (item.type !== "geo") return;
|
|
114
|
+
const color = pickBoardColor(context.colors, item.color, context.colorScheme);
|
|
115
|
+
drawFarPlate(graphics, item.frame, {
|
|
116
|
+
fill: color.fill,
|
|
117
|
+
fillAlpha: Math.max(.18, item.fillOpacity),
|
|
118
|
+
accent: color.stroke,
|
|
119
|
+
accentAlpha: .9
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
destroy: (container) => {
|
|
123
|
+
partsByContainer.get(container)?.root.destroy({ children: true });
|
|
124
|
+
partsByContainer.delete(container);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
export { geoCardRenderer };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BoardImageItem, BoardItem } from "../../../protocol/dist/board-document.js";
|
|
2
|
+
import { BoardCardRenderer } from "./board-renderer-registry.js";
|
|
3
|
+
//#region src/board/render/renderers/image-card-renderer.d.ts
|
|
4
|
+
declare const imageCardRenderer: BoardCardRenderer;
|
|
5
|
+
/** Helper kept for type narrowing in tests. */
|
|
6
|
+
declare function isImageItem(item: BoardItem): item is BoardImageItem;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { imageCardRenderer, isImageItem };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { drawFarPlate } from "./far-plate.js";
|
|
2
|
+
import { positionShell } from "./base-card-renderer.js";
|
|
3
|
+
import { Container, Graphics, Sprite, Texture } from "pixi.js";
|
|
4
|
+
//#region src/board/render/renderers/image-card-renderer.ts
|
|
5
|
+
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const RADIUS = 2;
|
|
7
|
+
/**
|
|
8
|
+
* Lay the sprite out inside the frame.
|
|
9
|
+
*
|
|
10
|
+
* The frame normally already matches the image's pixel aspect (see the editor's
|
|
11
|
+
* media size adoption), so this fills it exactly. Until that correction lands
|
|
12
|
+
* for a freshly dropped file, `contain` keeps the whole image visible rather
|
|
13
|
+
* than cropping it — the brief letterbox is self-correcting.
|
|
14
|
+
*/
|
|
15
|
+
function layoutContain(sprite, width, height) {
|
|
16
|
+
const texture = sprite.texture;
|
|
17
|
+
const tw = texture.width;
|
|
18
|
+
const th = texture.height;
|
|
19
|
+
if (!tw || !th) return;
|
|
20
|
+
const scale = Math.min(width / tw, height / th);
|
|
21
|
+
sprite.width = tw * scale;
|
|
22
|
+
sprite.height = th * scale;
|
|
23
|
+
sprite.x = (width - sprite.width) / 2;
|
|
24
|
+
sprite.y = (height - sprite.height) / 2;
|
|
25
|
+
}
|
|
26
|
+
function sync(container, item, context) {
|
|
27
|
+
const parts = partsByContainer.get(container);
|
|
28
|
+
if (!parts) return;
|
|
29
|
+
positionShell(parts.root, item);
|
|
30
|
+
const { width, height } = item.frame;
|
|
31
|
+
const selected = context.selectedIds.has(item.id);
|
|
32
|
+
const hovered = context.hoveredId === item.id;
|
|
33
|
+
const key = context.assetKey(item);
|
|
34
|
+
const texture = key ? context.getTexture(key) : null;
|
|
35
|
+
const failed = Boolean(key && !texture && context.hasError(key));
|
|
36
|
+
const texId = texture ? `${texture.width}x${texture.height}` : "none";
|
|
37
|
+
const sig = [
|
|
38
|
+
key ?? "",
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
selected,
|
|
42
|
+
hovered,
|
|
43
|
+
texId,
|
|
44
|
+
failed,
|
|
45
|
+
context.colorScheme
|
|
46
|
+
].join("|");
|
|
47
|
+
if (sig === parts.sig) return;
|
|
48
|
+
parts.sig = sig;
|
|
49
|
+
if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
|
|
50
|
+
parts.sprite.visible = Boolean(texture);
|
|
51
|
+
if (texture) layoutContain(parts.sprite, width, height);
|
|
52
|
+
parts.placeholder.clear();
|
|
53
|
+
if (!texture) {
|
|
54
|
+
parts.placeholder.roundRect(0, 0, width, height, RADIUS).fill({
|
|
55
|
+
color: context.palette.surface,
|
|
56
|
+
alpha: .85
|
|
57
|
+
}).roundRect(0, 0, width, height, RADIUS).stroke({
|
|
58
|
+
color: failed ? context.palette.border : context.palette.hover,
|
|
59
|
+
width: 1,
|
|
60
|
+
alpha: .9
|
|
61
|
+
});
|
|
62
|
+
const cx = width / 2;
|
|
63
|
+
const cy = height / 2;
|
|
64
|
+
if (failed) parts.placeholder.moveTo(cx - 10, cy - 10).lineTo(cx + 10, cy + 10).moveTo(cx + 10, cy - 10).lineTo(cx - 10, cy + 10).stroke({
|
|
65
|
+
color: context.palette.muted,
|
|
66
|
+
width: 1.5,
|
|
67
|
+
alpha: .8
|
|
68
|
+
});
|
|
69
|
+
else parts.placeholder.circle(cx, cy, 6).stroke({
|
|
70
|
+
color: context.palette.muted,
|
|
71
|
+
width: 1.25,
|
|
72
|
+
alpha: .55
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
parts.placeholder.visible = !texture;
|
|
76
|
+
if (selected || hovered) {
|
|
77
|
+
const hasPixels = Boolean(texture) && parts.sprite.width > 0;
|
|
78
|
+
const ox = hasPixels ? parts.sprite.x : 0;
|
|
79
|
+
const oy = hasPixels ? parts.sprite.y : 0;
|
|
80
|
+
const ow = hasPixels ? parts.sprite.width : width;
|
|
81
|
+
const oh = hasPixels ? parts.sprite.height : height;
|
|
82
|
+
parts.placeholder.visible = true;
|
|
83
|
+
parts.placeholder.roundRect(ox, oy, ow, oh, RADIUS).stroke({
|
|
84
|
+
color: selected ? context.palette.brand : context.palette.muted,
|
|
85
|
+
width: selected ? 2 : 1,
|
|
86
|
+
alpha: selected ? .95 : .7
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const imageCardRenderer = {
|
|
91
|
+
id: "image-card",
|
|
92
|
+
canRender: (item) => item.type === "image",
|
|
93
|
+
create: (item, context) => {
|
|
94
|
+
const root = new Container();
|
|
95
|
+
const sprite = new Sprite(Texture.EMPTY);
|
|
96
|
+
const placeholder = new Graphics();
|
|
97
|
+
const mask = new Graphics();
|
|
98
|
+
const maskBox = () => {
|
|
99
|
+
mask.clear();
|
|
100
|
+
if (item.type === "image") mask.roundRect(0, 0, item.frame.width, item.frame.height, RADIUS).fill({ color: 16777215 });
|
|
101
|
+
};
|
|
102
|
+
maskBox();
|
|
103
|
+
root.addChild(placeholder, sprite, mask);
|
|
104
|
+
sprite.mask = mask;
|
|
105
|
+
partsByContainer.set(root, {
|
|
106
|
+
root,
|
|
107
|
+
sprite,
|
|
108
|
+
placeholder,
|
|
109
|
+
sig: ""
|
|
110
|
+
});
|
|
111
|
+
if (item.type === "image") sync(root, item, context);
|
|
112
|
+
return root;
|
|
113
|
+
},
|
|
114
|
+
update: (container, item, context) => {
|
|
115
|
+
if (item.type !== "image") return;
|
|
116
|
+
const parts = partsByContainer.get(container);
|
|
117
|
+
if (!parts) return;
|
|
118
|
+
const mask = parts.root.children.find((child) => child !== parts.sprite && child !== parts.placeholder);
|
|
119
|
+
if (mask) mask.clear().roundRect(0, 0, item.frame.width, item.frame.height, RADIUS).fill({ color: 16777215 });
|
|
120
|
+
sync(container, item, context);
|
|
121
|
+
},
|
|
122
|
+
renderFar: (graphics, item, context) => {
|
|
123
|
+
drawFarPlate(graphics, item.frame, {
|
|
124
|
+
fill: context.palette.hover,
|
|
125
|
+
fillAlpha: .9
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
destroy: (container) => {
|
|
129
|
+
partsByContainer.get(container)?.root.destroy({ children: true });
|
|
130
|
+
partsByContainer.delete(container);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
/** Helper kept for type narrowing in tests. */
|
|
134
|
+
function isImageItem(item) {
|
|
135
|
+
return item.type === "image";
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { imageCardRenderer, isImageItem };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
+
import { pickBoardColor } from "../../core/palette.js";
|
|
3
|
+
import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
|
|
4
|
+
import { drawFarPlate } from "./far-plate.js";
|
|
5
|
+
import { positionShell } from "./base-card-renderer.js";
|
|
6
|
+
import { Container, Graphics, Text } from "pixi.js";
|
|
7
|
+
//#region src/board/render/renderers/note-card-renderer.ts
|
|
8
|
+
const RADIUS = 8;
|
|
9
|
+
const PADDING = 12;
|
|
10
|
+
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
function sync(container, item, context) {
|
|
12
|
+
const parts = partsByContainer.get(container);
|
|
13
|
+
if (!parts) return;
|
|
14
|
+
positionShell(parts.root, item);
|
|
15
|
+
const { width, height } = item.frame;
|
|
16
|
+
const selected = context.selectedIds.has(item.id);
|
|
17
|
+
const hovered = context.hoveredId === item.id;
|
|
18
|
+
const color = pickBoardColor(context.colors, item.color, context.colorScheme);
|
|
19
|
+
syncTextResolution(parts.body, parts, context.zoom);
|
|
20
|
+
const resizing = context.resizingIds.has(item.id);
|
|
21
|
+
const visualSig = [
|
|
22
|
+
width,
|
|
23
|
+
height,
|
|
24
|
+
selected,
|
|
25
|
+
hovered,
|
|
26
|
+
color.fill,
|
|
27
|
+
color.stroke,
|
|
28
|
+
context.palette.surface,
|
|
29
|
+
context.palette.muted,
|
|
30
|
+
context.palette.border
|
|
31
|
+
].join("|");
|
|
32
|
+
if (visualSig !== parts.visualSig) {
|
|
33
|
+
parts.visualSig = visualSig;
|
|
34
|
+
parts.background.clear();
|
|
35
|
+
parts.background.roundRect(0, 0, width, height, RADIUS).fill({
|
|
36
|
+
color: color.fill,
|
|
37
|
+
alpha: .16
|
|
38
|
+
}).roundRect(0, 0, width, height, RADIUS).fill({
|
|
39
|
+
color: context.palette.surface,
|
|
40
|
+
alpha: .6
|
|
41
|
+
}).rect(1, 1, width - 2, 4).fill({
|
|
42
|
+
color: color.stroke,
|
|
43
|
+
alpha: .95
|
|
44
|
+
});
|
|
45
|
+
parts.background.roundRect(0, 0, width, height, RADIUS).stroke({
|
|
46
|
+
color: selected ? color.stroke : hovered ? context.palette.muted : context.palette.border,
|
|
47
|
+
width: selected ? 2 : 1,
|
|
48
|
+
alpha: selected ? .95 : .8
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const textSig = [item.text, color.label].join("|");
|
|
52
|
+
if (textSig !== parts.textSig) {
|
|
53
|
+
parts.textSig = textSig;
|
|
54
|
+
parts.body.text = item.text;
|
|
55
|
+
parts.body.style.fill = color.label;
|
|
56
|
+
}
|
|
57
|
+
syncTextWrapWidth(parts.body, parts, Math.max(1, width - PADDING * 2), resizing);
|
|
58
|
+
parts.body.position.set(PADDING, 16);
|
|
59
|
+
}
|
|
60
|
+
const noteCardRenderer = {
|
|
61
|
+
id: "note-card",
|
|
62
|
+
canRender: (item) => item.type === "note",
|
|
63
|
+
create: (item, context) => {
|
|
64
|
+
const root = new Container();
|
|
65
|
+
const background = new Graphics();
|
|
66
|
+
const resolution = textResolutionForZoom(context.zoom);
|
|
67
|
+
const body = new Text({
|
|
68
|
+
text: "",
|
|
69
|
+
style: {
|
|
70
|
+
fill: 16777215,
|
|
71
|
+
fontFamily: BOARD_FONT_STACK,
|
|
72
|
+
fontSize: 14,
|
|
73
|
+
fontWeight: "500",
|
|
74
|
+
wordWrap: true,
|
|
75
|
+
lineHeight: 20
|
|
76
|
+
},
|
|
77
|
+
resolution,
|
|
78
|
+
roundPixels: true
|
|
79
|
+
});
|
|
80
|
+
root.addChild(background, body);
|
|
81
|
+
partsByContainer.set(root, {
|
|
82
|
+
root,
|
|
83
|
+
background,
|
|
84
|
+
body,
|
|
85
|
+
visualSig: "",
|
|
86
|
+
textSig: "",
|
|
87
|
+
wrapWidth: 0,
|
|
88
|
+
resolution
|
|
89
|
+
});
|
|
90
|
+
if (item.type === "note") sync(root, item, context);
|
|
91
|
+
return root;
|
|
92
|
+
},
|
|
93
|
+
update: (container, item, context) => {
|
|
94
|
+
if (item.type === "note") sync(container, item, context);
|
|
95
|
+
},
|
|
96
|
+
renderFar: (graphics, item, context) => {
|
|
97
|
+
if (item.type !== "note") return;
|
|
98
|
+
const color = pickBoardColor(context.colors, item.color, context.colorScheme);
|
|
99
|
+
drawFarPlate(graphics, item.frame, {
|
|
100
|
+
fill: color.fill,
|
|
101
|
+
fillAlpha: .28,
|
|
102
|
+
accent: color.stroke,
|
|
103
|
+
accentAlpha: .95
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
destroy: (container) => {
|
|
107
|
+
partsByContainer.get(container)?.root.destroy({ children: true });
|
|
108
|
+
partsByContainer.delete(container);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
export { noteCardRenderer };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BoardItem, BoardTextItem } from "../../../protocol/dist/board-document.js";
|
|
2
|
+
import { BoardCardRenderer } from "./board-renderer-registry.js";
|
|
3
|
+
//#region src/board/render/renderers/text-card-renderer.d.ts
|
|
4
|
+
declare const textCardRenderer: BoardCardRenderer;
|
|
5
|
+
declare function isTextItem(item: BoardItem): item is BoardTextItem;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { isTextItem, textCardRenderer };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
+
import { TEXT_FONT_SIZE, boardTextLineHeight, clampBoardTextFontSize, measureBoardText } from "../../core/text-metrics.js";
|
|
3
|
+
import { pickBoardColor } from "../../core/palette.js";
|
|
4
|
+
import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
|
|
5
|
+
import { drawFarPlate } from "./far-plate.js";
|
|
6
|
+
import { positionShell } from "./base-card-renderer.js";
|
|
7
|
+
import { Container, Text } from "pixi.js";
|
|
8
|
+
//#region src/board/render/renderers/text-card-renderer.ts
|
|
9
|
+
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
function sync(container, item, context) {
|
|
11
|
+
const parts = partsByContainer.get(container);
|
|
12
|
+
if (!parts) return;
|
|
13
|
+
positionShell(parts.root, item);
|
|
14
|
+
const fontSize = clampBoardTextFontSize(item.fontSize);
|
|
15
|
+
const measured = measureBoardText(item.text, fontSize);
|
|
16
|
+
const previewScale = Math.max(1e-4, item.frame.width / Math.max(1e-4, measured.width));
|
|
17
|
+
parts.body.scale.set(previewScale);
|
|
18
|
+
parts.body.position.set(item.frame.width / 2, item.frame.height / 2);
|
|
19
|
+
syncTextResolution(parts.body, parts, context.zoom * Math.max(1, previewScale));
|
|
20
|
+
const color = pickBoardColor(context.colors, item.color || "neutral", context.colorScheme);
|
|
21
|
+
const ink = item.color === "neutral" || !item.color ? context.palette.text : color.stroke;
|
|
22
|
+
const contentSig = [
|
|
23
|
+
item.text,
|
|
24
|
+
item.color,
|
|
25
|
+
fontSize,
|
|
26
|
+
context.colorScheme,
|
|
27
|
+
context.colors.brand.stroke,
|
|
28
|
+
context.palette.text
|
|
29
|
+
].join("|");
|
|
30
|
+
if (contentSig === parts.contentSig) return;
|
|
31
|
+
parts.contentSig = contentSig;
|
|
32
|
+
if (parts.body.text !== item.text) parts.body.text = item.text || "";
|
|
33
|
+
parts.body.style.fill = ink;
|
|
34
|
+
parts.body.style.fontSize = fontSize;
|
|
35
|
+
parts.body.style.lineHeight = boardTextLineHeight(fontSize);
|
|
36
|
+
}
|
|
37
|
+
const textCardRenderer = {
|
|
38
|
+
id: "text-card",
|
|
39
|
+
canRender: (item) => item.type === "text",
|
|
40
|
+
create: (item, context) => {
|
|
41
|
+
const root = new Container();
|
|
42
|
+
const resolution = textResolutionForZoom(context.zoom);
|
|
43
|
+
const color = pickBoardColor(context.colors, item.type === "text" ? item.color || "neutral" : "neutral", context.colorScheme);
|
|
44
|
+
const body = new Text({
|
|
45
|
+
text: "",
|
|
46
|
+
style: {
|
|
47
|
+
fill: item.type === "text" && (item.color === "neutral" || !item.color) ? context.palette.text : color.stroke,
|
|
48
|
+
fontFamily: BOARD_FONT_STACK,
|
|
49
|
+
fontSize: TEXT_FONT_SIZE,
|
|
50
|
+
fontWeight: "500",
|
|
51
|
+
lineHeight: boardTextLineHeight(TEXT_FONT_SIZE),
|
|
52
|
+
wordWrap: false
|
|
53
|
+
},
|
|
54
|
+
resolution,
|
|
55
|
+
roundPixels: true
|
|
56
|
+
});
|
|
57
|
+
body.anchor.set(.5);
|
|
58
|
+
root.addChild(body);
|
|
59
|
+
partsByContainer.set(root, {
|
|
60
|
+
root,
|
|
61
|
+
body,
|
|
62
|
+
resolution,
|
|
63
|
+
contentSig: ""
|
|
64
|
+
});
|
|
65
|
+
if (item.type === "text") sync(root, item, context);
|
|
66
|
+
return root;
|
|
67
|
+
},
|
|
68
|
+
update: (container, item, context) => {
|
|
69
|
+
if (item.type === "text") sync(container, item, context);
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* Far LOD: a low-alpha ink bar. Body text is well under a pixel tall at these
|
|
73
|
+
* zooms, so a rasterised glyph texture would only ever resolve to a smudge —
|
|
74
|
+
* this approximates that smudge for free and skips the texture entirely.
|
|
75
|
+
*/
|
|
76
|
+
renderFar: (graphics, item, context) => {
|
|
77
|
+
if (item.type !== "text") return;
|
|
78
|
+
const color = pickBoardColor(context.colors, item.color || "neutral", context.colorScheme);
|
|
79
|
+
const ink = item.color === "neutral" || !item.color ? context.palette.text : color.stroke;
|
|
80
|
+
drawFarPlate(graphics, item.frame, {
|
|
81
|
+
fill: ink,
|
|
82
|
+
fillAlpha: .35
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
destroy: (container) => {
|
|
86
|
+
partsByContainer.get(container)?.root.destroy({ children: true });
|
|
87
|
+
partsByContainer.delete(container);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
function isTextItem(item) {
|
|
91
|
+
return item.type === "text";
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { isTextItem, textCardRenderer };
|