@neta-art/cohub 12.1.0 → 12.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 +8 -5
- package/dist/board/editor/editor.d.ts +1 -0
- package/dist/board/editor/editor.js +4 -0
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +3 -0
- package/dist/board/export/node/index.js +13 -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 +16 -0
- package/dist/board/model/asset-key.js +92 -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 +7 -5
- package/dist/board/model/index.js +9 -7
- package/dist/board/model/items/create.js +1 -1
- 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/text-metrics.d.ts +8 -3
- package/dist/board/model/text-metrics.js +13 -2
- package/dist/board/model/themes.d.ts +488 -0
- package/dist/board/model/themes.js +495 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +6 -4
- package/dist/board/render/index.js +5 -3
- package/dist/board/render/lod.d.ts +22 -0
- package/dist/board/render/lod.js +27 -0
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/far-plate.d.ts +11 -1
- package/dist/board/render/renderers/far-plate.js +27 -8
- package/dist/board/render/renderers/file-card-renderer.js +3 -5
- package/dist/board/render/renderers/image-card-renderer.js +32 -6
- package/dist/board/render/renderers/model-card-renderer.js +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +7 -8
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/style.d.ts +7 -2
- package/dist/board/render/style.js +5 -1
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-fit.js +8 -12
- package/dist/board/render/text-measurement.d.ts +15 -1
- package/dist/board/render/text-measurement.js +113 -20
- 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/stage/assets.d.ts +15 -4
- package/dist/board/stage/assets.js +155 -89
- package/dist/board/stage/index.d.ts +5 -5
- package/dist/board/stage/index.js +4 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/scene.d.ts +11 -1
- package/dist/board/stage/scene.js +213 -138
- package/dist/board/stage/stage.d.ts +12 -1
- package/dist/board/stage/stage.js +97 -24
- package/dist/board/stage/texture-pool.d.ts +8 -1
- package/dist/board/stage/texture-pool.js +37 -1
- package/dist/chunks/app-realtime.js +1 -1
- package/dist/chunks/{transport.js → http-error.js} +37 -1
- package/dist/chunks/http.js +30 -10
- package/dist/http.js +1 -1
- package/dist/index.js +139 -131
- 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/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +123 -1
- package/dist/protocol/dist/index.d.ts +2 -2
- package/dist/websocket.js +951 -1
- package/package.json +3 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
- package/dist/chunks/websocket.js +0 -988
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
|
+
import { fitTextToLines } from "../text-fit.js";
|
|
2
3
|
import { syncTextResolution } from "../text-resolution.js";
|
|
3
4
|
import { positionShell } from "./base-card-renderer.js";
|
|
4
5
|
import { drawFarPlate } from "./far-plate.js";
|
|
5
6
|
import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
|
|
6
7
|
//#region src/board/render/renderers/custom-card-renderer.ts
|
|
7
8
|
const partsByContainer = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
const LABEL_LINE = 16;
|
|
8
10
|
function sync(container, item, context) {
|
|
9
11
|
const parts = partsByContainer.get(container);
|
|
10
12
|
if (!parts) return;
|
|
@@ -39,9 +41,8 @@ function sync(container, item, context) {
|
|
|
39
41
|
alpha: .8
|
|
40
42
|
});
|
|
41
43
|
parts.label.visible = !ready || Boolean(error);
|
|
42
|
-
parts.label.text = message;
|
|
43
44
|
parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
|
|
44
|
-
parts.label.
|
|
45
|
+
fitTextToLines(parts.label, message, Math.max(1, Math.floor((height - 16) / LABEL_LINE)), Math.max(40, width - 16));
|
|
45
46
|
parts.label.position.set(width / 2, height / 2);
|
|
46
47
|
}
|
|
47
48
|
syncTextResolution(parts.label, context.textResolution);
|
|
@@ -59,9 +60,8 @@ const customCardRenderer = {
|
|
|
59
60
|
style: {
|
|
60
61
|
fontFamily: BOARD_MONO_FONT_STACK,
|
|
61
62
|
fontSize: 12,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
breakWords: true
|
|
63
|
+
lineHeight: LABEL_LINE,
|
|
64
|
+
align: "center"
|
|
65
65
|
},
|
|
66
66
|
resolution: context.textResolution,
|
|
67
67
|
roundPixels: true
|
|
@@ -2,6 +2,12 @@ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
|
|
|
2
2
|
import "../../../protocol/dist/index.js";
|
|
3
3
|
import { Graphics } from "pixi.js";
|
|
4
4
|
//#region src/board/render/renderers/far-plate.d.ts
|
|
5
|
+
type LocalRect = {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
5
11
|
type FarPlateStyle = {
|
|
6
12
|
fill: number;
|
|
7
13
|
fillAlpha?: number;
|
|
@@ -9,6 +15,10 @@ type FarPlateStyle = {
|
|
|
9
15
|
accentAlpha?: number;
|
|
10
16
|
};
|
|
11
17
|
declare function drawFarPlate(graphics: Graphics, frame: BoardFrame, style: FarPlateStyle): void;
|
|
18
|
+
declare function drawFarBars(graphics: Graphics, frame: BoardFrame, bars: readonly LocalRect[], style: {
|
|
19
|
+
color: number;
|
|
20
|
+
alpha: number;
|
|
21
|
+
}): void;
|
|
12
22
|
declare function farStrokeSamples<T>(points: readonly T[]): readonly T[];
|
|
13
23
|
declare function drawFarStroke(graphics: Graphics, points: ReadonlyArray<{
|
|
14
24
|
x: number;
|
|
@@ -19,4 +29,4 @@ declare function drawFarStroke(graphics: Graphics, points: ReadonlyArray<{
|
|
|
19
29
|
alpha?: number;
|
|
20
30
|
}): void;
|
|
21
31
|
//#endregion
|
|
22
|
-
export { FarPlateStyle, drawFarPlate, drawFarStroke, farStrokeSamples };
|
|
32
|
+
export { FarPlateStyle, drawFarBars, drawFarPlate, drawFarStroke, farStrokeSamples };
|
|
@@ -4,11 +4,11 @@ const FAR_STROKE_MAX_POINTS = 24;
|
|
|
4
4
|
const ACCENT_RATIO = .14;
|
|
5
5
|
const ACCENT_MIN = 2;
|
|
6
6
|
const ACCENT_MAX = 10;
|
|
7
|
-
function
|
|
8
|
-
const x = frame.x +
|
|
9
|
-
const y = frame.y +
|
|
10
|
-
const width = Math.max(0,
|
|
11
|
-
const height = Math.max(0,
|
|
7
|
+
function traceLocal(graphics, frame, rect) {
|
|
8
|
+
const x = frame.x + rect.x;
|
|
9
|
+
const y = frame.y + rect.y;
|
|
10
|
+
const width = Math.max(0, rect.width);
|
|
11
|
+
const height = Math.max(0, rect.height);
|
|
12
12
|
if (!frame.rotation) {
|
|
13
13
|
graphics.rect(x, y, width, height);
|
|
14
14
|
return;
|
|
@@ -34,18 +34,37 @@ function traceFrame(graphics, frame, inset = 0, heightOverride) {
|
|
|
34
34
|
}
|
|
35
35
|
function drawFarPlate(graphics, frame, style) {
|
|
36
36
|
if (frame.width <= 0 || frame.height <= 0) return;
|
|
37
|
-
|
|
37
|
+
traceLocal(graphics, frame, {
|
|
38
|
+
x: 0,
|
|
39
|
+
y: 0,
|
|
40
|
+
width: frame.width,
|
|
41
|
+
height: frame.height
|
|
42
|
+
});
|
|
38
43
|
graphics.fill({
|
|
39
44
|
color: style.fill,
|
|
40
45
|
alpha: style.fillAlpha ?? 1
|
|
41
46
|
});
|
|
42
47
|
if (style.accent === void 0) return;
|
|
43
|
-
|
|
48
|
+
const band = Math.min(ACCENT_MAX, Math.max(ACCENT_MIN, frame.height * ACCENT_RATIO));
|
|
49
|
+
traceLocal(graphics, frame, {
|
|
50
|
+
x: 0,
|
|
51
|
+
y: 0,
|
|
52
|
+
width: frame.width,
|
|
53
|
+
height: band
|
|
54
|
+
});
|
|
44
55
|
graphics.fill({
|
|
45
56
|
color: style.accent,
|
|
46
57
|
alpha: style.accentAlpha ?? .9
|
|
47
58
|
});
|
|
48
59
|
}
|
|
60
|
+
function drawFarBars(graphics, frame, bars, style) {
|
|
61
|
+
if (bars.length === 0) return;
|
|
62
|
+
for (const bar of bars) traceLocal(graphics, frame, bar);
|
|
63
|
+
graphics.fill({
|
|
64
|
+
color: style.color,
|
|
65
|
+
alpha: style.alpha
|
|
66
|
+
});
|
|
67
|
+
}
|
|
49
68
|
function farStrokeSamples(points) {
|
|
50
69
|
if (points.length <= FAR_STROKE_MAX_POINTS) return points;
|
|
51
70
|
const step = Math.ceil(points.length / FAR_STROKE_MAX_POINTS);
|
|
@@ -70,4 +89,4 @@ function drawFarStroke(graphics, points, style) {
|
|
|
70
89
|
});
|
|
71
90
|
}
|
|
72
91
|
//#endregion
|
|
73
|
-
export { drawFarPlate, drawFarStroke, farStrokeSamples };
|
|
92
|
+
export { drawFarBars, drawFarPlate, drawFarStroke, farStrokeSamples };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
2
|
import { fileCategory, fileStem } from "../../model/file-snapshot.js";
|
|
3
3
|
import { fileCategoryAccent, fileMetaLine, filePreviewKind, fileTypeLabel } from "../../model/file-preview.js";
|
|
4
|
+
import { boardCardDetailed } from "../lod.js";
|
|
4
5
|
import { fitTextToLines } from "../text-fit.js";
|
|
5
|
-
import {
|
|
6
|
+
import { syncTextResolution } from "../text-resolution.js";
|
|
6
7
|
import { createCardTag, syncCardTag, zoomCardTag } from "../card-tag.js";
|
|
7
8
|
import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
|
|
8
9
|
import { positionShell } from "./base-card-renderer.js";
|
|
@@ -146,6 +147,7 @@ function syncContent(parts, item, context) {
|
|
|
146
147
|
context.palette.text,
|
|
147
148
|
context.palette.muted,
|
|
148
149
|
context.style.font,
|
|
150
|
+
context.themeKey,
|
|
149
151
|
accent
|
|
150
152
|
].join("|");
|
|
151
153
|
if (textSig === parts.textSig) return;
|
|
@@ -203,8 +205,6 @@ const fileCardRenderer = {
|
|
|
203
205
|
fontFamily: BOARD_FONT_STACK,
|
|
204
206
|
fontSize: TITLE_SIZE,
|
|
205
207
|
fontWeight: "600",
|
|
206
|
-
wordWrap: true,
|
|
207
|
-
breakWords: true,
|
|
208
208
|
lineHeight: TITLE_LINE
|
|
209
209
|
},
|
|
210
210
|
resolution,
|
|
@@ -217,8 +217,6 @@ const fileCardRenderer = {
|
|
|
217
217
|
fontFamily: BOARD_FONT_STACK,
|
|
218
218
|
fontSize: EXCERPT_SIZE,
|
|
219
219
|
fontWeight: "400",
|
|
220
|
-
wordWrap: true,
|
|
221
|
-
breakWords: true,
|
|
222
220
|
lineHeight: EXCERPT_LINE
|
|
223
221
|
},
|
|
224
222
|
resolution,
|
|
@@ -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
140
|
fill: context.palette.hover,
|
|
115
|
-
fillAlpha:
|
|
141
|
+
fillAlpha: FAR_ALPHA
|
|
116
142
|
});
|
|
117
143
|
},
|
|
118
144
|
destroy: (container) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isBoardModelItem } from "../../model/media.js";
|
|
2
2
|
import { fileStem } from "../../model/file-snapshot.js";
|
|
3
3
|
import { fileMetaLine } from "../../model/file-preview.js";
|
|
4
|
-
import { boardCardDetailed } from "../
|
|
4
|
+
import { boardCardDetailed } from "../lod.js";
|
|
5
5
|
import { createCardTag, syncCardTag, traceCube, zoomCardTag } from "../card-tag.js";
|
|
6
6
|
import { cardElevation, containRect, drawCardSurface } from "../surface.js";
|
|
7
7
|
import { positionShell } from "./base-card-renderer.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
|
|
2
1
|
import { worldPoint } from "../../model/geometry.js";
|
|
2
|
+
import { layoutShapeLabel } from "../../model/text-metrics.js";
|
|
3
3
|
import { itemAlpha, itemColor } from "../palette.js";
|
|
4
|
-
import {
|
|
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,7 +1,8 @@
|
|
|
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";
|
|
@@ -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";
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { getMediaResourceTitle } from "../../model/media.js";
|
|
2
|
-
import { boardCardDetailed } from "../
|
|
2
|
+
import { boardCardDetailed } from "../lod.js";
|
|
3
3
|
import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
|
|
4
4
|
import { cardElevation, containRect, drawCardSurface, fillCardPicture } from "../surface.js";
|
|
5
5
|
import { positionShell } from "./base-card-renderer.js";
|
|
@@ -122,6 +122,7 @@ 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
128
|
fill: context.palette.surface,
|
|
@@ -24,6 +24,11 @@ type BoardSelectionStyle = {
|
|
|
24
24
|
handleSize: number;
|
|
25
25
|
};
|
|
26
26
|
declare function defaultBoardRenderStyle(mode: "dark" | "light"): BoardRenderStyle;
|
|
27
|
-
|
|
27
|
+
/** Theme overrides of the default style; a shadow without positive alpha is none. */
|
|
28
|
+
type BoardRenderStyleInput = Partial<Omit<BoardRenderStyle, "shadow" | "selection">> & {
|
|
29
|
+
shadow?: Partial<BoardShadowStyle> | null;
|
|
30
|
+
selection?: Partial<BoardSelectionStyle>;
|
|
31
|
+
};
|
|
32
|
+
declare function resolveBoardRenderStyle(mode: "dark" | "light", style?: BoardRenderStyleInput): BoardRenderStyle;
|
|
28
33
|
//#endregion
|
|
29
|
-
export { BoardRenderStyle, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
|
|
34
|
+
export { BoardRenderStyle, BoardRenderStyleInput, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
|
|
@@ -40,6 +40,10 @@ function defaultBoardRenderStyle(mode) {
|
|
|
40
40
|
function resolveBoardRenderStyle(mode, style) {
|
|
41
41
|
const base = defaultBoardRenderStyle(mode);
|
|
42
42
|
if (!style) return base;
|
|
43
|
+
const shadow = style.shadow === null ? null : {
|
|
44
|
+
...SHADOW[mode],
|
|
45
|
+
...style.shadow
|
|
46
|
+
};
|
|
43
47
|
return {
|
|
44
48
|
...base,
|
|
45
49
|
...style,
|
|
@@ -47,7 +51,7 @@ function resolveBoardRenderStyle(mode, style) {
|
|
|
47
51
|
...base.selection,
|
|
48
52
|
...style.selection
|
|
49
53
|
},
|
|
50
|
-
shadow:
|
|
54
|
+
shadow: shadow && shadow.alpha > 0 ? shadow : null
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
//#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[];
|