@neta-art/cohub 12.1.1 → 12.3.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 +85 -8
- package/dist/board/editor/clipboard.js +1 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +8 -3
- package/dist/board/editor/editor.js +8 -2
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +93 -4
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +1 -1
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +2 -0
- package/dist/board/export/node/index.js +8 -1
- package/dist/board/export/node/scripts.js +1 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +24 -0
- package/dist/board/model/asset-key.js +104 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +9 -7
- package/dist/board/model/index.js +22 -20
- package/dist/board/model/items/create.js +3 -3
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/sampling.js +1 -1
- package/dist/board/model/text-metrics.d.ts +28 -3
- package/dist/board/model/text-metrics.js +36 -2
- package/dist/board/model/themes.d.ts +495 -0
- package/dist/board/model/themes.js +500 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +11 -7
- package/dist/board/render/index.js +9 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +30 -0
- package/dist/board/render/lod.js +33 -0
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +15 -3
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/audio-card-renderer.js +3 -2
- package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.d.ts +14 -1
- package/dist/board/render/renderers/far-plate.js +31 -8
- package/dist/board/render/renderers/file-card-renderer.js +49 -31
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +34 -8
- package/dist/board/render/renderers/model-card-renderer.js +6 -6
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +9 -10
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +5 -4
- package/dist/board/render/script-export.js +1 -1
- package/dist/board/render/script-host.js +3 -3
- package/dist/board/render/style.d.ts +8 -2
- package/dist/board/render/style.js +8 -2
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +6 -49
- package/dist/board/render/text-measurement.d.ts +25 -1
- package/dist/board/render/text-measurement.js +157 -19
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +28 -17
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +28 -5
- package/dist/board/stage/assets.js +293 -102
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +11 -7
- package/dist/board/stage/index.js +7 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +15 -1
- package/dist/board/stage/scene.js +248 -141
- package/dist/board/stage/stage.d.ts +28 -2
- package/dist/board/stage/stage.js +404 -112
- package/dist/board/stage/texture-pool.d.ts +10 -1
- package/dist/board/stage/texture-pool.js +72 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +202 -862
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +127 -1
- package/dist/protocol/dist/index.d.ts +3 -3
- package/package.json +4 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cardElevation, containRect, drawCardSurface, fillCardPicture } from "../surface.js";
|
|
2
2
|
import { positionShell } from "./base-card-renderer.js";
|
|
3
|
-
import { drawFarPlate } from "./far-plate.js";
|
|
3
|
+
import { drawFarPlate, farPictureTone } from "./far-plate.js";
|
|
4
4
|
import { Container, Graphics } from "pixi.js";
|
|
5
5
|
//#region src/board/render/renderers/image-card-renderer.ts
|
|
6
6
|
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
@@ -16,6 +16,8 @@ function cropRect(crop) {
|
|
|
16
16
|
} : null;
|
|
17
17
|
}
|
|
18
18
|
const PRINT_MARGIN = 6;
|
|
19
|
+
const FADE_MS = 160;
|
|
20
|
+
const FAR_ALPHA = .9;
|
|
19
21
|
function sync(container, item, context) {
|
|
20
22
|
const parts = partsByContainer.get(container);
|
|
21
23
|
if (!parts) return;
|
|
@@ -36,7 +38,15 @@ function sync(container, item, context) {
|
|
|
36
38
|
context.themeKey
|
|
37
39
|
].join("|");
|
|
38
40
|
if (sig === parts.sig) return;
|
|
41
|
+
if (texture && !parts.textured && parts.sig !== "" && !context.reducedMotion) {
|
|
42
|
+
parts.fadeFrom = performance.now();
|
|
43
|
+
parts.picture.alpha = 0;
|
|
44
|
+
} else if (!texture) {
|
|
45
|
+
parts.fadeFrom = null;
|
|
46
|
+
parts.picture.alpha = 1;
|
|
47
|
+
}
|
|
39
48
|
parts.sig = sig;
|
|
49
|
+
parts.textured = Boolean(texture);
|
|
40
50
|
const crop = cropRect(item.props.crop);
|
|
41
51
|
const framed = context.style.media === "framed";
|
|
42
52
|
const margin = framed ? Math.min(PRINT_MARGIN, width / 8, height / 8) : 0;
|
|
@@ -69,11 +79,13 @@ function sync(container, item, context) {
|
|
|
69
79
|
radius: 0
|
|
70
80
|
} : context.style, crop);
|
|
71
81
|
parts.placeholder.clear();
|
|
72
|
-
|
|
82
|
+
parts.clearSurface = !framed;
|
|
83
|
+
if (texture && !framed && parts.fadeFrom === null) return;
|
|
73
84
|
drawCardSurface(parts.placeholder, card, context.style, {
|
|
74
|
-
fill: context.palette.surface,
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
fill: failed || framed ? context.palette.surface : context.palette.hover,
|
|
86
|
+
fillAlpha: failed || framed ? 1 : FAR_ALPHA,
|
|
87
|
+
border: failed ? context.palette.border : null,
|
|
88
|
+
elevation: texture && !framed ? 0 : elevation
|
|
77
89
|
});
|
|
78
90
|
if (texture) return;
|
|
79
91
|
const cx = width / 2;
|
|
@@ -101,7 +113,10 @@ const imageCardRenderer = {
|
|
|
101
113
|
root,
|
|
102
114
|
placeholder,
|
|
103
115
|
picture,
|
|
104
|
-
sig: ""
|
|
116
|
+
sig: "",
|
|
117
|
+
textured: false,
|
|
118
|
+
fadeFrom: null,
|
|
119
|
+
clearSurface: false
|
|
105
120
|
});
|
|
106
121
|
if (item.type === "image") sync(root, item, context);
|
|
107
122
|
return root;
|
|
@@ -109,10 +124,21 @@ const imageCardRenderer = {
|
|
|
109
124
|
update: (container, item, context) => {
|
|
110
125
|
if (item.type === "image") sync(container, item, context);
|
|
111
126
|
},
|
|
127
|
+
tick: (container, clock) => {
|
|
128
|
+
const parts = partsByContainer.get(container);
|
|
129
|
+
if (parts?.fadeFrom == null) return null;
|
|
130
|
+
const progress = Math.min(1, Math.max(0, (clock - parts.fadeFrom) / FADE_MS));
|
|
131
|
+
parts.picture.alpha = 1 - (1 - progress) ** 3;
|
|
132
|
+
if (progress < 1) return 16;
|
|
133
|
+
parts.fadeFrom = null;
|
|
134
|
+
if (parts.clearSurface) parts.placeholder.clear();
|
|
135
|
+
return null;
|
|
136
|
+
},
|
|
137
|
+
lod: { kind: "media" },
|
|
112
138
|
renderFar: (graphics, item, context) => {
|
|
113
139
|
drawFarPlate(graphics, item.frame, {
|
|
114
|
-
fill: context.palette.hover,
|
|
115
|
-
fillAlpha:
|
|
140
|
+
fill: farPictureTone(item, context) ?? context.palette.hover,
|
|
141
|
+
fillAlpha: FAR_ALPHA
|
|
116
142
|
});
|
|
117
143
|
},
|
|
118
144
|
destroy: (container) => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fileStem } from "../../model/file-snapshot.js";
|
|
3
|
-
import { fileMetaLine } from "../../model/file-preview.js";
|
|
4
|
-
import { boardCardDetailed } from "../text-resolution.js";
|
|
1
|
+
import { boardCardDetailed } from "../lod.js";
|
|
5
2
|
import { createCardTag, syncCardTag, traceCube, zoomCardTag } from "../card-tag.js";
|
|
3
|
+
import { isBoardModelItem } from "../../model/media.js";
|
|
6
4
|
import { cardElevation, containRect, drawCardSurface } from "../surface.js";
|
|
7
5
|
import { positionShell } from "./base-card-renderer.js";
|
|
8
|
-
import { drawFarPlate } from "./far-plate.js";
|
|
6
|
+
import { drawFarPlate, farPictureTone } from "./far-plate.js";
|
|
9
7
|
import { drawModelKey } from "./media-key.js";
|
|
8
|
+
import { fileStem } from "../../model/file-snapshot.js";
|
|
9
|
+
import { fileMetaLine } from "../../model/file-preview.js";
|
|
10
10
|
import { Container, Graphics, Sprite, Texture } from "pixi.js";
|
|
11
11
|
//#region src/board/render/renderers/model-card-renderer.ts
|
|
12
12
|
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
@@ -120,7 +120,7 @@ const modelCardRenderer = {
|
|
|
120
120
|
},
|
|
121
121
|
renderFar: (graphics, item, context) => {
|
|
122
122
|
drawFarPlate(graphics, item.frame, {
|
|
123
|
-
fill: context.palette.surface,
|
|
123
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
124
124
|
fillAlpha: .96,
|
|
125
125
|
accent: context.palette.legendary,
|
|
126
126
|
accentAlpha: .45
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
1
|
import { worldPoint } from "../../model/geometry.js";
|
|
3
2
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
-
import {
|
|
3
|
+
import { layoutShapeLabel } from "../../model/text-metrics.js";
|
|
4
|
+
import { boardTextStyle, cssBaselineShift } from "../text-measurement.js";
|
|
5
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
5
6
|
import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
|
|
6
7
|
import { positionShell } from "./base-card-renderer.js";
|
|
7
8
|
import { drawFarPlate } from "./far-plate.js";
|
|
@@ -142,24 +143,34 @@ function sync(container, item, context) {
|
|
|
142
143
|
}
|
|
143
144
|
parts.label.alpha = itemAlpha(context, style.stroke);
|
|
144
145
|
const label = itemColor(context, style.stroke, "neutral", "label");
|
|
146
|
+
const layout = layoutShapeLabel(props, width);
|
|
147
|
+
const text = layout.lines.join("\n");
|
|
145
148
|
const textSig = [
|
|
146
|
-
|
|
149
|
+
text,
|
|
147
150
|
label,
|
|
148
151
|
props.fontSize,
|
|
149
|
-
props.align
|
|
152
|
+
props.align,
|
|
153
|
+
context.themeKey
|
|
150
154
|
].join("|");
|
|
151
155
|
if (textSig !== parts.textSig) {
|
|
152
156
|
parts.textSig = textSig;
|
|
153
|
-
parts.label.text =
|
|
154
|
-
parts.label.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
parts.label.text = text;
|
|
158
|
+
Object.assign(parts.label.style, {
|
|
159
|
+
...boardTextStyle({
|
|
160
|
+
font: "sans",
|
|
161
|
+
fontSize: props.fontSize,
|
|
162
|
+
fontWeight: 500
|
|
163
|
+
}),
|
|
164
|
+
fill: label,
|
|
165
|
+
lineHeight: layout.lineHeight,
|
|
166
|
+
align: props.align
|
|
167
|
+
});
|
|
159
168
|
}
|
|
160
|
-
|
|
169
|
+
parts.label.visible = props.text.length > 0 && context.editingId !== item.id;
|
|
161
170
|
syncTextResolution(parts.label, context.textResolution);
|
|
162
|
-
|
|
171
|
+
const anchor = props.align === "left" ? 0 : props.align === "right" ? 1 : .5;
|
|
172
|
+
parts.label.anchor.set(anchor, .5);
|
|
173
|
+
parts.label.position.set(8 + Math.max(0, width - 16) * anchor, height / 2 + cssBaselineShift(parts.label.style));
|
|
163
174
|
}
|
|
164
175
|
const shapeCardRenderer = {
|
|
165
176
|
id: "shape-card",
|
|
@@ -169,27 +180,17 @@ const shapeCardRenderer = {
|
|
|
169
180
|
const shape = new Graphics();
|
|
170
181
|
const label = new Text({
|
|
171
182
|
text: "",
|
|
172
|
-
style: {
|
|
173
|
-
fill: 16777215,
|
|
174
|
-
fontFamily: BOARD_FONT_STACK,
|
|
175
|
-
fontSize: 20,
|
|
176
|
-
fontWeight: "500",
|
|
177
|
-
align: "center",
|
|
178
|
-
wordWrap: true,
|
|
179
|
-
lineHeight: 28
|
|
180
|
-
},
|
|
183
|
+
style: { fill: 16777215 },
|
|
181
184
|
resolution: context.textResolution,
|
|
182
185
|
roundPixels: true
|
|
183
186
|
});
|
|
184
|
-
label.anchor.set(.5);
|
|
185
187
|
root.addChild(shape, label);
|
|
186
188
|
partsByContainer.set(root, {
|
|
187
189
|
root,
|
|
188
190
|
shape,
|
|
189
191
|
label,
|
|
190
192
|
visualSig: "",
|
|
191
|
-
textSig: ""
|
|
192
|
-
wrapWidth: 0
|
|
193
|
+
textSig: ""
|
|
193
194
|
});
|
|
194
195
|
if (item.type === "shape") sync(root, item, context);
|
|
195
196
|
return root;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
2
|
import { featuredTaskArtifact } from "../../../protocol/dist/board-task.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { boardCardDetailed } from "../lod.js";
|
|
4
|
+
import { fitTextToLines, fitTextToWidth } from "../text-fit.js";
|
|
5
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
5
6
|
import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
|
|
6
7
|
import { drawAudioWaveform } from "../audio-waveform.js";
|
|
7
8
|
import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
|
|
8
9
|
import { positionShell } from "./base-card-renderer.js";
|
|
9
|
-
import { drawFarPlate } from "./far-plate.js";
|
|
10
|
+
import { drawFarPlate, farPictureTone } from "./far-plate.js";
|
|
10
11
|
import { Container, FillGradient, Graphics, Sprite, Text } from "pixi.js";
|
|
11
12
|
//#region src/board/render/renderers/task-card-renderer.ts
|
|
12
13
|
const PADDING = 12;
|
|
@@ -226,7 +227,7 @@ function advance(parts, clock) {
|
|
|
226
227
|
const elapsed = formatBoardDuration(elapsedMs);
|
|
227
228
|
if (elapsed !== motion.elapsed) {
|
|
228
229
|
motion.elapsed = elapsed;
|
|
229
|
-
parts.body
|
|
230
|
+
fitTextToWidth(parts.body, `${motion.label} · ${elapsed}`, Math.max(24, motion.width - 24));
|
|
230
231
|
}
|
|
231
232
|
const untilNextSecond = SECOND - Math.max(0, elapsedMs) % SECOND;
|
|
232
233
|
next = next === null ? untilNextSecond : Math.min(next, untilNextSecond);
|
|
@@ -296,7 +297,8 @@ function sync(container, item, context) {
|
|
|
296
297
|
color,
|
|
297
298
|
context.palette.text,
|
|
298
299
|
context.palette.muted,
|
|
299
|
-
context.style.font
|
|
300
|
+
context.style.font,
|
|
301
|
+
context.themeKey
|
|
300
302
|
].join("|");
|
|
301
303
|
if (textSig !== parts.textSig) {
|
|
302
304
|
parts.textSig = textSig;
|
|
@@ -441,9 +443,7 @@ const taskCardRenderer = {
|
|
|
441
443
|
style: {
|
|
442
444
|
fontFamily,
|
|
443
445
|
fontSize,
|
|
444
|
-
fontWeight
|
|
445
|
-
wordWrap: true,
|
|
446
|
-
breakWords: true
|
|
446
|
+
fontWeight
|
|
447
447
|
},
|
|
448
448
|
resolution: context.textResolution,
|
|
449
449
|
roundPixels: true
|
|
@@ -452,7 +452,6 @@ const taskCardRenderer = {
|
|
|
452
452
|
const detail = text(BOARD_FONT_STACK, 10.5, "400");
|
|
453
453
|
const meta = text(BOARD_MONO_FONT_STACK, 10, "500");
|
|
454
454
|
const badge = text(BOARD_MONO_FONT_STACK, 10, "500");
|
|
455
|
-
badge.style.wordWrap = false;
|
|
456
455
|
badge.anchor.set(.5);
|
|
457
456
|
detail.anchor.set(.5, 0);
|
|
458
457
|
detail.style.align = "center";
|
|
@@ -513,7 +512,7 @@ const taskCardRenderer = {
|
|
|
513
512
|
const { status } = liveStatus(item, context);
|
|
514
513
|
const active = status === "failed" || status === "running" || status === "pending";
|
|
515
514
|
drawFarPlate(graphics, item.frame, {
|
|
516
|
-
fill: context.palette.surface,
|
|
515
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
517
516
|
fillAlpha: .82,
|
|
518
517
|
...active ? {
|
|
519
518
|
accent: statusColor(status, context),
|
|
@@ -1,25 +1,43 @@
|
|
|
1
|
-
import { layoutBoardText } from "../../../protocol/dist/board-layout.js";
|
|
2
|
-
import { boardFontFamily } from "../../model/text-metrics.js";
|
|
1
|
+
import { estimateTextWidth, layoutBoardText } from "../../../protocol/dist/board-layout.js";
|
|
3
2
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
-
import {
|
|
3
|
+
import { boardTextStyle, canvasFont, cssBaselineShift, layoutTextLines } from "../text-measurement.js";
|
|
4
|
+
import { boardTextSize } from "../lod.js";
|
|
5
5
|
import { syncTextResolution } from "../text-resolution.js";
|
|
6
6
|
import { positionShell } from "./base-card-renderer.js";
|
|
7
|
-
import {
|
|
7
|
+
import { drawFarBars } from "./far-plate.js";
|
|
8
8
|
import { Container, Graphics, Text } from "pixi.js";
|
|
9
9
|
//#region src/board/render/renderers/text-card-renderer.ts
|
|
10
10
|
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const ALIGN = {
|
|
12
|
+
left: 0,
|
|
13
|
+
center: .5,
|
|
14
|
+
right: 1
|
|
15
|
+
};
|
|
16
|
+
const FAR_MAX_LINES = 64;
|
|
17
|
+
function revealed(lines, reveal) {
|
|
18
|
+
if (reveal >= 1) return lines;
|
|
19
|
+
const glyphs = lines.map((line) => [...line]);
|
|
20
|
+
let budget = Math.round(glyphs.reduce((sum, line) => sum + line.length, 0) * Math.max(0, reveal));
|
|
21
|
+
const shown = [];
|
|
22
|
+
for (const line of glyphs) {
|
|
23
|
+
shown.push(line.slice(0, budget).join(""));
|
|
24
|
+
budget -= line.length;
|
|
25
|
+
if (budget <= 0) break;
|
|
26
|
+
}
|
|
27
|
+
return shown;
|
|
14
28
|
}
|
|
15
|
-
function syncCaret(parts, item, context) {
|
|
29
|
+
function syncCaret(parts, item, context, anchor) {
|
|
16
30
|
const { props } = item;
|
|
17
|
-
const visible = props.caret && props.reveal < 1 && Math.floor(context.time / 500) % 2 === 0;
|
|
31
|
+
const visible = parts.body.visible && props.caret && props.reveal < 1 && Math.floor(context.time / 500) % 2 === 0;
|
|
18
32
|
parts.caret.visible = visible;
|
|
19
|
-
if (!visible) return;
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
parts.
|
|
33
|
+
if (!visible || !parts.layout) return;
|
|
34
|
+
const scale = parts.body.scale.x;
|
|
35
|
+
const lastWidth = layoutTextLines(parts.lines.at(-1) ?? "", canvasFont(boardTextStyle(props))).width;
|
|
36
|
+
const x = parts.body.position.x + lastWidth * (1 - anchor) * scale + 2;
|
|
37
|
+
const y = Math.max(0, parts.lines.length - 1) * parts.layout.lineHeight * scale;
|
|
38
|
+
const width = Math.max(1, 1.5 / Math.max(.25, context.zoom));
|
|
39
|
+
const height = Math.max(12, parts.layout.lineHeight * scale - 2);
|
|
40
|
+
parts.caret.clear().rect(x, y + 1, width, height).fill({ color: itemColor(context, item.style.fill, "neutral") });
|
|
23
41
|
}
|
|
24
42
|
function sync(container, item, context) {
|
|
25
43
|
const parts = partsByContainer.get(container);
|
|
@@ -27,42 +45,63 @@ function sync(container, item, context) {
|
|
|
27
45
|
positionShell(parts.root, item);
|
|
28
46
|
const { props } = item;
|
|
29
47
|
const layout = layoutBoardText(props);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
parts.body.anchor.set(
|
|
34
|
-
parts.body.
|
|
48
|
+
const scale = Math.max(1e-4, item.frame.width / Math.max(1e-4, layout.width));
|
|
49
|
+
const anchor = ALIGN[props.align] ?? 0;
|
|
50
|
+
parts.body.scale.set(scale);
|
|
51
|
+
parts.body.anchor.set(anchor, 0);
|
|
52
|
+
parts.body.visible = context.editingId !== item.id;
|
|
35
53
|
parts.body.alpha = itemAlpha(context, item.style.fill);
|
|
36
54
|
parts.caret.alpha = parts.body.alpha;
|
|
37
55
|
const ink = itemColor(context, item.style.fill, "neutral");
|
|
38
|
-
const
|
|
39
|
-
const
|
|
56
|
+
const lines = revealed(layout.lines, props.reveal);
|
|
57
|
+
const text = lines.join("\n");
|
|
58
|
+
const style = boardTextStyle(props);
|
|
59
|
+
const sig = [
|
|
40
60
|
text,
|
|
41
61
|
ink,
|
|
42
|
-
props.fontSize,
|
|
43
|
-
props.fontWeight,
|
|
44
|
-
props.font,
|
|
45
62
|
props.align,
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
layout.lineHeight,
|
|
64
|
+
style.fontFamily,
|
|
65
|
+
style.fontSize,
|
|
66
|
+
style.fontWeight,
|
|
67
|
+
context.themeKey
|
|
48
68
|
].join("|");
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
parts.layout = layout;
|
|
70
|
+
parts.lines = lines;
|
|
71
|
+
if (sig !== parts.sig) {
|
|
72
|
+
parts.sig = sig;
|
|
52
73
|
Object.assign(parts.body.style, {
|
|
74
|
+
...style,
|
|
53
75
|
fill: ink,
|
|
54
|
-
fontSize: props.fontSize,
|
|
55
|
-
fontWeight: String(Math.round(props.fontWeight / 100) * 100),
|
|
56
|
-
fontFamily: boardFontFamily(props.font, BOARD_FONT_STACKS),
|
|
57
76
|
align: props.align,
|
|
58
77
|
lineHeight: layout.lineHeight,
|
|
59
|
-
wordWrap:
|
|
60
|
-
wordWrapWidth: props.width ?? 0,
|
|
61
|
-
breakWords: true
|
|
78
|
+
wordWrap: false
|
|
62
79
|
});
|
|
80
|
+
parts.body.text = text;
|
|
81
|
+
parts.baseline = cssBaselineShift(parts.body.style);
|
|
63
82
|
}
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
parts.body.position.set(item.frame.width * anchor, parts.baseline * scale);
|
|
84
|
+
syncTextResolution(parts.body, context.textResolution * scale);
|
|
85
|
+
syncCaret(parts, item, context, anchor);
|
|
86
|
+
}
|
|
87
|
+
function farBars({ frame, props }) {
|
|
88
|
+
const layout = layoutBoardText(props);
|
|
89
|
+
const scale = frame.width / Math.max(1e-4, layout.width);
|
|
90
|
+
const lines = layout.lines.slice(0, FAR_MAX_LINES);
|
|
91
|
+
const widths = lines.map((line) => estimateTextWidth(line.trim(), props.fontSize));
|
|
92
|
+
const widest = Math.max(1e-4, ...widths);
|
|
93
|
+
const step = layout.lineHeight * scale;
|
|
94
|
+
const height = step * .45;
|
|
95
|
+
return lines.flatMap((_, index) => {
|
|
96
|
+
const width = frame.width * (widths[index] ?? 0) / widest;
|
|
97
|
+
if (width <= 0) return [];
|
|
98
|
+
return [{
|
|
99
|
+
x: props.align === "center" ? (frame.width - width) / 2 : props.align === "right" ? frame.width - width : 0,
|
|
100
|
+
y: index * step + (step - height) / 2,
|
|
101
|
+
width,
|
|
102
|
+
height
|
|
103
|
+
}];
|
|
104
|
+
});
|
|
66
105
|
}
|
|
67
106
|
const textCardRenderer = {
|
|
68
107
|
id: "text-card",
|
|
@@ -71,7 +110,6 @@ const textCardRenderer = {
|
|
|
71
110
|
const root = new Container();
|
|
72
111
|
const body = new Text({
|
|
73
112
|
text: "",
|
|
74
|
-
style: { fontFamily: BOARD_FONT_STACKS.sans },
|
|
75
113
|
resolution: context.textResolution,
|
|
76
114
|
roundPixels: true
|
|
77
115
|
});
|
|
@@ -81,7 +119,10 @@ const textCardRenderer = {
|
|
|
81
119
|
root,
|
|
82
120
|
body,
|
|
83
121
|
caret,
|
|
84
|
-
|
|
122
|
+
layout: null,
|
|
123
|
+
lines: [],
|
|
124
|
+
sig: "",
|
|
125
|
+
baseline: 0
|
|
85
126
|
});
|
|
86
127
|
if (item.type === "text") sync(root, item, context);
|
|
87
128
|
return root;
|
|
@@ -93,11 +134,15 @@ const textCardRenderer = {
|
|
|
93
134
|
const parts = partsByContainer.get(container);
|
|
94
135
|
if (parts) syncTextResolution(parts.body, context.textResolution * parts.body.scale.x);
|
|
95
136
|
},
|
|
137
|
+
lod: {
|
|
138
|
+
kind: "text",
|
|
139
|
+
size: boardTextSize
|
|
140
|
+
},
|
|
96
141
|
renderFar: (graphics, item, context) => {
|
|
97
142
|
if (item.type !== "text") return;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
143
|
+
drawFarBars(graphics, item.frame, farBars(item), {
|
|
144
|
+
color: itemColor(context, item.style.fill, "neutral"),
|
|
145
|
+
alpha: .4 * itemAlpha(context, item.style.fill)
|
|
101
146
|
});
|
|
102
147
|
},
|
|
103
148
|
destroy: (container) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { boardCardDetailed } from "../text-resolution.js";
|
|
1
|
+
import { boardCardDetailed } from "../lod.js";
|
|
3
2
|
import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
|
|
3
|
+
import { getMediaResourceTitle } from "../../model/media.js";
|
|
4
4
|
import { cardElevation, containRect, drawCardSurface, fillCardPicture } from "../surface.js";
|
|
5
5
|
import { positionShell } from "./base-card-renderer.js";
|
|
6
|
-
import { drawFarPlate } from "./far-plate.js";
|
|
6
|
+
import { drawFarPlate, farPictureTone } from "./far-plate.js";
|
|
7
7
|
import { drawMediaPlayKey } from "./media-key.js";
|
|
8
8
|
import { Container, Graphics } from "pixi.js";
|
|
9
9
|
//#region src/board/render/renderers/video-card-renderer.ts
|
|
@@ -122,9 +122,10 @@ const videoCardRenderer = {
|
|
|
122
122
|
const tag = partsByContainer.get(container)?.tag;
|
|
123
123
|
if (tag) zoomCardTag(tag, zoom);
|
|
124
124
|
},
|
|
125
|
+
lod: { kind: "media" },
|
|
125
126
|
renderFar: (graphics, item, context) => {
|
|
126
127
|
drawFarPlate(graphics, item.frame, {
|
|
127
|
-
fill: context.palette.surface,
|
|
128
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
128
129
|
fillAlpha: .96,
|
|
129
130
|
accent: context.palette.brand,
|
|
130
131
|
accentAlpha: .75
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { composeBoardScriptItems } from "../model/script-scene.js";
|
|
2
1
|
import { buildBoardScene } from "../model/scene.js";
|
|
2
|
+
import { composeBoardScriptItems } from "../model/script-scene.js";
|
|
3
3
|
import { createBoardScriptHost } from "./script-host.js";
|
|
4
4
|
//#region src/board/render/script-export.ts
|
|
5
5
|
async function prepareBrowserBoardScripts(document, time, readModule, snapshots) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { boardScriptItems } from "../model/script-index.js";
|
|
2
|
-
import { boardScriptScope } from "../model/script-scene.js";
|
|
3
|
-
import { boardScriptHitVisible, resolveBoardScriptHit } from "../model/script-hit.js";
|
|
4
1
|
import { getBoardResolution } from "./text-resolution.js";
|
|
5
2
|
import { BoardScriptTexture } from "./script-texture.js";
|
|
6
3
|
import { scriptResolution } from "./script-resolution.js";
|
|
4
|
+
import { boardScriptItems } from "../model/script-index.js";
|
|
5
|
+
import { boardScriptScope } from "../model/script-scene.js";
|
|
6
|
+
import { boardScriptHitVisible, resolveBoardScriptHit } from "../model/script-hit.js";
|
|
7
7
|
import { createBoardScriptModuleReader, loadBoardScriptSources } from "../../protocol/dist/board-script-modules.js";
|
|
8
8
|
import { boardScriptFrameSource } from "./script-sandbox.js";
|
|
9
9
|
//#region src/board/render/script-host.ts
|
|
@@ -9,6 +9,7 @@ type BoardRenderStyle = {
|
|
|
9
9
|
icons: "line" | "pixel";
|
|
10
10
|
motion: "smooth" | "stepped";
|
|
11
11
|
font: string;
|
|
12
|
+
frameLabelSize: number;
|
|
12
13
|
};
|
|
13
14
|
type BoardShadowStyle = {
|
|
14
15
|
color: number;
|
|
@@ -24,6 +25,11 @@ type BoardSelectionStyle = {
|
|
|
24
25
|
handleSize: number;
|
|
25
26
|
};
|
|
26
27
|
declare function defaultBoardRenderStyle(mode: "dark" | "light"): BoardRenderStyle;
|
|
27
|
-
|
|
28
|
+
/** Theme overrides of the default style; a shadow without positive alpha is none. */
|
|
29
|
+
type BoardRenderStyleInput = Partial<Omit<BoardRenderStyle, "shadow" | "selection">> & {
|
|
30
|
+
shadow?: Partial<BoardShadowStyle> | null;
|
|
31
|
+
selection?: Partial<BoardSelectionStyle>;
|
|
32
|
+
};
|
|
33
|
+
declare function resolveBoardRenderStyle(mode: "dark" | "light", style?: BoardRenderStyleInput): BoardRenderStyle;
|
|
28
34
|
//#endregion
|
|
29
|
-
export { BoardRenderStyle, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
|
|
35
|
+
export { BoardRenderStyle, BoardRenderStyleInput, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
|
+
import "../model/text-metrics.js";
|
|
2
3
|
//#region src/board/render/style.ts
|
|
3
4
|
const BASE = {
|
|
4
5
|
corner: "round",
|
|
@@ -14,7 +15,8 @@ const BASE = {
|
|
|
14
15
|
progress: "shimmer",
|
|
15
16
|
icons: "line",
|
|
16
17
|
motion: "smooth",
|
|
17
|
-
font: BOARD_FONT_STACK
|
|
18
|
+
font: BOARD_FONT_STACK,
|
|
19
|
+
frameLabelSize: 12
|
|
18
20
|
};
|
|
19
21
|
const SHADOW = {
|
|
20
22
|
light: {
|
|
@@ -40,6 +42,10 @@ function defaultBoardRenderStyle(mode) {
|
|
|
40
42
|
function resolveBoardRenderStyle(mode, style) {
|
|
41
43
|
const base = defaultBoardRenderStyle(mode);
|
|
42
44
|
if (!style) return base;
|
|
45
|
+
const shadow = style.shadow === null ? null : {
|
|
46
|
+
...SHADOW[mode],
|
|
47
|
+
...style.shadow
|
|
48
|
+
};
|
|
43
49
|
return {
|
|
44
50
|
...base,
|
|
45
51
|
...style,
|
|
@@ -47,7 +53,7 @@ function resolveBoardRenderStyle(mode, style) {
|
|
|
47
53
|
...base.selection,
|
|
48
54
|
...style.selection
|
|
49
55
|
},
|
|
50
|
-
shadow:
|
|
56
|
+
shadow: shadow && shadow.alpha > 0 ? shadow : null
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
//#endregion
|
|
@@ -16,6 +16,11 @@ type CardCrop = {
|
|
|
16
16
|
w: number;
|
|
17
17
|
h: number;
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* The canvas renderer fills with a repeating pattern, so bilinear samples at the
|
|
21
|
+
* edge would blend in the opposite side. Mapping the picture half a texel inside
|
|
22
|
+
* its source keeps every sample within it on both renderers.
|
|
23
|
+
*/
|
|
19
24
|
declare function fillCardPicture(graphics: Graphics, texture: Texture, picture: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, crop?: CardCrop | null): Graphics;
|
|
20
25
|
declare function tracePixelRect(graphics: Graphics, x: number, y: number, width: number, height: number, step: number): Graphics;
|
|
21
26
|
declare function cardOutlinePoints(rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): WorldPoint[];
|
|
@@ -13,23 +13,33 @@ function traceCardOutline(graphics, rect, style, grow = 0) {
|
|
|
13
13
|
const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
|
|
14
14
|
return radius > 0 ? graphics.roundRect(x, y, width, height, radius) : graphics.rect(x, y, width, height);
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* The canvas renderer fills with a repeating pattern, so bilinear samples at the
|
|
18
|
+
* edge would blend in the opposite side. Mapping the picture half a texel inside
|
|
19
|
+
* its source keeps every sample within it on both renderers.
|
|
20
|
+
*/
|
|
16
21
|
function fillCardPicture(graphics, texture, picture, style, crop) {
|
|
17
22
|
const sampling = texture.source.style;
|
|
18
23
|
if (sampling.addressMode !== "mirror-repeat") {
|
|
19
24
|
sampling.addressMode = "mirror-repeat";
|
|
20
25
|
sampling.update();
|
|
21
26
|
}
|
|
22
|
-
const
|
|
27
|
+
const { x = 0, y = 0, w = 1, h = 1 } = crop ?? {};
|
|
28
|
+
const insetX = Math.min(.5 / texture.source.pixelWidth, w / 4);
|
|
29
|
+
const insetY = Math.min(.5 / texture.source.pixelHeight, h / 4);
|
|
30
|
+
const sw = w - insetX * 2;
|
|
31
|
+
const sh = h - insetY * 2;
|
|
32
|
+
const matrix = new Matrix(1 / sw, 0, 0, 1 / sh, -(x + insetX) / sw, -(y + insetY) / sh);
|
|
23
33
|
return traceCardOutline(graphics, picture, style).fill({
|
|
24
34
|
texture,
|
|
25
35
|
textureSpace: "local",
|
|
26
|
-
|
|
36
|
+
matrix
|
|
27
37
|
});
|
|
28
38
|
}
|
|
29
39
|
function tracePixelRect(graphics, x, y, width, height, step) {
|
|
30
40
|
const s = Math.min(step, width / 4, height / 4);
|
|
31
41
|
if (s <= 0) return graphics.rect(x, y, width, height);
|
|
32
|
-
return graphics.poly(pixelRectPoints(x, y, width, height, s).flatMap((point) => [point.x, point.y]));
|
|
42
|
+
return graphics.poly(pixelRectPoints(x, y, width, height, s).flatMap((point) => [point.x, point.y]), true);
|
|
33
43
|
}
|
|
34
44
|
function pixelRectPoints(x, y, width, height, s) {
|
|
35
45
|
const r = x + width;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BoardViewport } from "../model/geometry.js";
|
|
2
|
+
import { BoardScene, BoardSceneItem } from "../model/scene.js";
|
|
3
|
+
import { BoardRenderStyle } from "./style.js";
|
|
4
|
+
import { BoardTextStyle } from "./text-measurement.js";
|
|
5
|
+
//#region src/board/render/text-edit.d.ts
|
|
6
|
+
/** A content-box textarea at left/top, transformed by `rotate(rotation deg) scale(scale)` about its center. */
|
|
7
|
+
type BoardTextEditLayout = {
|
|
8
|
+
left: number;
|
|
9
|
+
top: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
padding: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
scale: number;
|
|
17
|
+
rotation: number;
|
|
18
|
+
font: BoardTextStyle;
|
|
19
|
+
lineHeight: number;
|
|
20
|
+
textAlign: "left" | "center" | "right";
|
|
21
|
+
whiteSpace: "pre" | "pre-wrap";
|
|
22
|
+
live: boolean;
|
|
23
|
+
};
|
|
24
|
+
type BoardTextEditInput = {
|
|
25
|
+
viewport: BoardViewport;
|
|
26
|
+
scene: BoardScene;
|
|
27
|
+
style?: Partial<Pick<BoardRenderStyle, "font" | "frameLabelSize">>;
|
|
28
|
+
draft: string;
|
|
29
|
+
};
|
|
30
|
+
declare function boardTextEditLayout(item: BoardSceneItem, { viewport, scene, style, draft }: BoardTextEditInput): BoardTextEditLayout | null;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { BoardTextEditInput, BoardTextEditLayout, boardTextEditLayout };
|