@neta-art/cohub 11.1.0 → 11.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.
Files changed (78) hide show
  1. package/dist/board/editor/context.d.ts +1 -0
  2. package/dist/board/editor/context.js +1 -0
  3. package/dist/board/editor/editor.d.ts +1 -0
  4. package/dist/board/editor/editor.js +7 -0
  5. package/dist/board/editor/interaction.d.ts +4 -0
  6. package/dist/board/editor/interaction.js +16 -7
  7. package/dist/board/editor/selection-transform.d.ts +1 -1
  8. package/dist/board/editor/selection-transform.js +13 -2
  9. package/dist/board/export/index.d.ts +2 -0
  10. package/dist/board/export/index.js +1 -0
  11. package/dist/board/export/scene.d.ts +2 -0
  12. package/dist/board/export/scene.js +4 -0
  13. package/dist/board/model/file-preview.d.ts +0 -1
  14. package/dist/board/model/file-preview.js +10 -1
  15. package/dist/board/model/index.d.ts +2 -1
  16. package/dist/board/model/index.js +2 -1
  17. package/dist/board/model/script-api.d.ts +69 -0
  18. package/dist/board/model/script-api.js +77 -0
  19. package/dist/board/render/audio-waveform.d.ts +1 -1
  20. package/dist/board/render/audio-waveform.js +8 -5
  21. package/dist/board/render/card-tag.d.ts +24 -0
  22. package/dist/board/render/card-tag.js +205 -0
  23. package/dist/board/render/index.d.ts +7 -4
  24. package/dist/board/render/index.js +7 -4
  25. package/dist/board/render/media-interaction.d.ts +2 -1
  26. package/dist/board/render/media-interaction.js +14 -6
  27. package/dist/board/render/palette.js +2 -2
  28. package/dist/board/render/renderers/arrow-card-renderer.js +20 -16
  29. package/dist/board/render/renderers/audio-card-renderer.d.ts +8 -1
  30. package/dist/board/render/renderers/audio-card-renderer.js +64 -60
  31. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -26
  32. package/dist/board/render/renderers/base-card-renderer.js +6 -92
  33. package/dist/board/render/renderers/board-renderer-registry.d.ts +8 -0
  34. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  35. package/dist/board/render/renderers/custom-card-renderer.js +4 -7
  36. package/dist/board/render/renderers/effect-card-renderer.js +0 -5
  37. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -11
  38. package/dist/board/render/renderers/file-card-renderer.js +43 -105
  39. package/dist/board/render/renderers/frame-card-renderer.js +14 -15
  40. package/dist/board/render/renderers/image-card-renderer.js +62 -67
  41. package/dist/board/render/renderers/media-key.d.ts +6 -0
  42. package/dist/board/render/renderers/media-key.js +39 -0
  43. package/dist/board/render/renderers/shape-card-renderer.js +6 -12
  44. package/dist/board/render/renderers/task-card-renderer.d.ts +1 -9
  45. package/dist/board/render/renderers/task-card-renderer.js +82 -56
  46. package/dist/board/render/renderers/unknown-card-renderer.js +17 -16
  47. package/dist/board/render/renderers/video-card-renderer.js +73 -80
  48. package/dist/board/render/stroke.d.ts +1 -1
  49. package/dist/board/render/stroke.js +5 -4
  50. package/dist/board/render/style.d.ts +29 -0
  51. package/dist/board/render/style.js +54 -0
  52. package/dist/board/render/surface.d.ts +34 -0
  53. package/dist/board/render/surface.js +132 -0
  54. package/dist/board/render/text-fit.d.ts +8 -0
  55. package/dist/board/render/text-fit.js +60 -0
  56. package/dist/board/render/text-resolution.d.ts +2 -1
  57. package/dist/board/render/text-resolution.js +4 -1
  58. package/dist/board/stage/index.d.ts +3 -2
  59. package/dist/board/stage/index.js +2 -1
  60. package/dist/board/stage/overlay.d.ts +41 -0
  61. package/dist/board/stage/overlay.js +182 -0
  62. package/dist/board/stage/scene.d.ts +2 -43
  63. package/dist/board/stage/scene.js +23 -131
  64. package/dist/board/stage/stage.d.ts +2 -0
  65. package/dist/board/stage/stage.js +99 -32
  66. package/dist/chunks/app-realtime.js +1697 -0
  67. package/dist/chunks/environment.d.ts +997 -3
  68. package/dist/chunks/http.d.ts +393 -3
  69. package/dist/chunks/http.js +40 -1
  70. package/dist/chunks/websocket.d.ts +4 -0
  71. package/dist/chunks/websocket.js +11 -2
  72. package/dist/http.d.ts +2 -2
  73. package/dist/index.d.ts +4 -323
  74. package/dist/index.js +58 -1660
  75. package/dist/protocol/dist/board-model.js +51 -51
  76. package/dist/protocol/dist/board-schema.d.ts +1 -2
  77. package/dist/protocol/dist/board-schema.js +7 -22
  78. package/package.json +2 -1
@@ -1,91 +1,94 @@
1
- import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
1
  import { getMediaResourceTitle } from "../../model/media.js";
3
- import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
2
+ import { boardCardDetailed } from "../text-resolution.js";
3
+ import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
4
+ import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
4
5
  import { positionShell } from "./base-card-renderer.js";
5
6
  import { drawFarPlate } from "./far-plate.js";
6
- import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
7
+ import { drawMediaPlayKey } from "./media-key.js";
8
+ import { Container, Graphics, Sprite, Texture } from "pixi.js";
7
9
  //#region src/board/render/renderers/video-card-renderer.ts
8
10
  const partsByContainer = /* @__PURE__ */ new WeakMap();
9
- const RADIUS = 4;
10
- function layoutContain(sprite, width, height) {
11
- const texture = sprite.texture;
12
- if (!texture.width || !texture.height) return;
13
- const scale = Math.min(width / texture.width, height / texture.height);
14
- sprite.width = texture.width * scale;
15
- sprite.height = texture.height * scale;
16
- sprite.x = (width - sprite.width) / 2;
17
- sprite.y = (height - sprite.height) / 2;
11
+ const FILM_RAIL = 10;
12
+ const FILM_HOLE = 4;
13
+ function videoTitle(item) {
14
+ return item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
18
15
  }
19
16
  function sync(container, item, context) {
20
17
  const parts = partsByContainer.get(container);
21
18
  if (!parts) return;
22
19
  positionShell(parts.root, item);
23
20
  const { width, height } = item.frame;
24
- const selected = context.selectedIds.has(item.id);
25
- const hovered = context.hoveredId === item.id;
26
- const resizing = context.resizingIds.has(item.id);
27
- const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
21
+ const title = videoTitle(item);
22
+ const durationMs = item.props.snapshot?.durationMs;
23
+ const duration = durationMs ? formatBoardDuration(durationMs) : "";
24
+ syncCardTag(parts.tag, {
25
+ glyph: "video",
26
+ text: duration ? `${title} · ${duration}` : title,
27
+ width
28
+ }, context);
28
29
  const key = context.assetKey(item);
29
30
  const texture = key ? context.getTexture(key) : null;
30
- const failed = Boolean(key && !texture && context.hasError(key));
31
- syncTextResolution(parts.label, parts, context.zoom);
32
- if (parts.label.text !== title) parts.label.text = title;
33
- syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), resizing);
34
- parts.label.position.set(8, Math.max(8, height - parts.label.height - 8));
31
+ const elevation = cardElevation(context, item.id);
35
32
  const sig = [
36
33
  key ?? "",
37
34
  texture ? `${texture.width}x${texture.height}` : "none",
38
35
  width,
39
36
  height,
40
- selected,
41
- hovered,
42
- failed,
43
- title,
44
- item.props.time ?? 0,
45
- parts.label.height,
46
- context.palette.surface,
47
- context.palette.brand,
48
- context.palette.muted,
49
- context.palette.border,
50
- context.palette.hover,
51
- context.palette.text
37
+ elevation,
38
+ boardCardDetailed(context.zoom),
39
+ context.themeKey
52
40
  ].join("|");
53
41
  if (sig === parts.sig) return;
54
42
  parts.sig = sig;
43
+ const framed = context.style.media === "framed";
44
+ const rect = {
45
+ x: 0,
46
+ y: 0,
47
+ width,
48
+ height
49
+ };
50
+ const rail = framed ? Math.min(FILM_RAIL, height / 6) : 0;
51
+ const screen = {
52
+ x: framed ? rail * .6 : 0,
53
+ y: rail,
54
+ width: width - (framed ? rail * 1.2 : 0),
55
+ height: height - rail * 2
56
+ };
57
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
58
+ fill: framed ? context.palette.text : context.palette.surface,
59
+ border: framed ? null : context.palette.border,
60
+ borderAlpha: .85,
61
+ elevation
62
+ });
63
+ if (framed) drawFilmRails(parts.plate, width, height, rail, context.palette.bg);
64
+ traceCardOutline(parts.mask.clear(), framed ? screen : rect, framed ? {
65
+ corner: "round",
66
+ radius: 1
67
+ } : context.style).fill({ color: 16777215 });
55
68
  if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
56
69
  parts.sprite.visible = Boolean(texture);
57
- if (texture) layoutContain(parts.sprite, width, height);
58
- parts.mask.clear().roundRect(0, 0, width, height, RADIUS).fill({ color: 16777215 });
59
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS).fill({
60
- color: context.palette.surface,
61
- alpha: .96
62
- });
63
- parts.chrome.clear();
64
70
  if (texture) {
65
- const bandHeight = Math.min(height, parts.label.height + 16);
66
- parts.chrome.rect(0, height - bandHeight, width, bandHeight).fill({
67
- color: context.palette.surface,
68
- alpha: .78
69
- });
71
+ const fitted = containRect(screen.width, screen.height, texture.width, texture.height);
72
+ parts.sprite.position.set(screen.x + fitted.x, screen.y + fitted.y);
73
+ parts.sprite.width = fitted.width;
74
+ parts.sprite.height = fitted.height;
70
75
  }
71
- const cx = width / 2;
72
- const cy = height / 2;
73
- const radius = Math.min(22, Math.min(width, height) * .18);
74
- parts.chrome.circle(cx, cy, radius).fill({
75
- color: selected ? context.palette.brand : context.palette.surface,
76
- alpha: texture ? .88 : .96
77
- });
78
- const triangle = radius * .55;
79
- parts.chrome.moveTo(cx - triangle * .35, cy - triangle * .6).lineTo(cx - triangle * .35, cy + triangle * .6).lineTo(cx + triangle * .7, cy).closePath().fill({
80
- color: context.palette.text,
81
- alpha: .94
82
- });
83
- parts.chrome.roundRect(0, 0, width, height, RADIUS).stroke({
84
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
85
- width: selected ? 2 : 1,
86
- alpha: selected ? .95 : .85
76
+ parts.chrome.clear();
77
+ if (boardCardDetailed(context.zoom)) drawMediaPlayKey(parts.chrome, screen.x + screen.width / 2, screen.y + screen.height / 2, Math.min(18, Math.min(screen.width, screen.height) * .16), context);
78
+ }
79
+ function drawFilmRails(graphics, width, height, rail, hole) {
80
+ const size = Math.min(FILM_HOLE, rail * .45);
81
+ const step = size * 2.5;
82
+ const count = Math.max(1, Math.floor((width - step) / step));
83
+ const start = (width - (count - 1) * step - size) / 2;
84
+ for (let index = 0; index < count; index += 1) {
85
+ const x = start + index * step;
86
+ graphics.rect(x, (rail - size) / 2, size, size).rect(x, height - rail + (rail - size) / 2, size, size);
87
+ }
88
+ graphics.fill({
89
+ color: hole,
90
+ alpha: .9
87
91
  });
88
- parts.label.style.fill = texture ? context.palette.text : context.palette.muted;
89
92
  }
90
93
  const videoCardRenderer = {
91
94
  id: "video-card",
@@ -96,20 +99,8 @@ const videoCardRenderer = {
96
99
  const sprite = new Sprite(Texture.EMPTY);
97
100
  const mask = new Graphics();
98
101
  const chrome = new Graphics();
99
- const resolution = textResolutionForZoom(context.zoom);
100
- const label = new Text({
101
- text: "",
102
- style: {
103
- fill: context.palette.muted,
104
- fontFamily: BOARD_FONT_STACK,
105
- fontSize: 12,
106
- fontWeight: "500",
107
- wordWrap: true
108
- },
109
- resolution,
110
- roundPixels: true
111
- });
112
- root.addChild(plate, sprite, mask, chrome, label);
102
+ const tag = createCardTag();
103
+ root.addChild(plate, sprite, mask, chrome, tag.root);
113
104
  sprite.mask = mask;
114
105
  partsByContainer.set(root, {
115
106
  root,
@@ -117,10 +108,8 @@ const videoCardRenderer = {
117
108
  sprite,
118
109
  mask,
119
110
  chrome,
120
- label,
121
- sig: "",
122
- wrapWidth: 0,
123
- resolution
111
+ tag,
112
+ sig: ""
124
113
  });
125
114
  if (item.type === "video") sync(root, item, context);
126
115
  return root;
@@ -128,6 +117,10 @@ const videoCardRenderer = {
128
117
  update: (container, item, context) => {
129
118
  if (item.type === "video") sync(container, item, context);
130
119
  },
120
+ zoom: (container, zoom) => {
121
+ const tag = partsByContainer.get(container)?.tag;
122
+ if (tag) zoomCardTag(tag, zoom);
123
+ },
131
124
  renderFar: (graphics, item, context) => {
132
125
  drawFarPlate(graphics, item.frame, {
133
126
  fill: context.palette.surface,
@@ -5,7 +5,7 @@ import { Graphics } from "pixi.js";
5
5
  //#region src/board/render/stroke.d.ts
6
6
  declare function dashPattern(dash: BoardDash | undefined, width: number): [number, number] | null;
7
7
  declare function trimPolyline(path: readonly WorldPoint[], trim: number | undefined): readonly WorldPoint[];
8
- declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean): void;
8
+ declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean, offset?: number): void;
9
9
  declare function traceArrowhead(graphics: Graphics, tip: WorldPoint, angle: number, size: number): void;
10
10
  declare function endAngle(path: readonly WorldPoint[], atStart: boolean): number | null;
11
11
  declare function roundRectPoints(width: number, height: number, radius: number): WorldPoint[];
@@ -31,7 +31,7 @@ function trimPolyline(path, trim) {
31
31
  }
32
32
  return out;
33
33
  }
34
- function tracePolyline(graphics, path, pattern, closed = false) {
34
+ function tracePolyline(graphics, path, pattern, closed = false, offset = 0) {
35
35
  const points = closed && path.length > 2 ? [...path, path[0]] : path;
36
36
  if (points.length < 2) return;
37
37
  if (!pattern) {
@@ -45,9 +45,10 @@ function tracePolyline(graphics, path, pattern, closed = false) {
45
45
  return;
46
46
  }
47
47
  const [dash, gap] = pattern;
48
- let penDown = true;
49
- let fresh = true;
50
- let remaining = dash;
48
+ const phase = Math.max(0, offset) % (dash + gap);
49
+ let penDown = phase < dash;
50
+ let fresh = penDown;
51
+ let remaining = penDown ? dash - phase : dash + gap - phase;
51
52
  for (let index = 1; index < points.length; index += 1) {
52
53
  const a = points[index - 1];
53
54
  const b = points[index];
@@ -0,0 +1,29 @@
1
+ //#region src/board/render/style.d.ts
2
+ type BoardRenderStyle = {
3
+ corner: "round" | "pixel";
4
+ radius: number;
5
+ shadow: BoardShadowStyle | null;
6
+ selection: BoardSelectionStyle;
7
+ media: "plain" | "framed";
8
+ progress: "shimmer" | "mosaic";
9
+ icons: "line" | "pixel";
10
+ motion: "smooth" | "stepped";
11
+ font: string;
12
+ };
13
+ type BoardShadowStyle = {
14
+ color: number;
15
+ alpha: number;
16
+ blur: number;
17
+ y: number;
18
+ };
19
+ type BoardSelectionStyle = {
20
+ width: number;
21
+ outset: number;
22
+ dash: number;
23
+ handle: "circle" | "square";
24
+ handleSize: number;
25
+ };
26
+ declare function defaultBoardRenderStyle(mode: "dark" | "light"): BoardRenderStyle;
27
+ declare function resolveBoardRenderStyle(mode: "dark" | "light", style?: Partial<BoardRenderStyle>): BoardRenderStyle;
28
+ //#endregion
29
+ export { BoardRenderStyle, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
@@ -0,0 +1,54 @@
1
+ import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
2
+ //#region src/board/render/style.ts
3
+ const BASE = {
4
+ corner: "round",
5
+ radius: 6,
6
+ selection: {
7
+ width: 1.5,
8
+ outset: 3,
9
+ dash: 0,
10
+ handle: "square",
11
+ handleSize: 8
12
+ },
13
+ media: "plain",
14
+ progress: "shimmer",
15
+ icons: "line",
16
+ motion: "smooth",
17
+ font: BOARD_FONT_STACK
18
+ };
19
+ const SHADOW = {
20
+ light: {
21
+ color: 1447704,
22
+ alpha: .06,
23
+ blur: 12,
24
+ y: 2
25
+ },
26
+ dark: {
27
+ color: 0,
28
+ alpha: .24,
29
+ blur: 12,
30
+ y: 2
31
+ }
32
+ };
33
+ function defaultBoardRenderStyle(mode) {
34
+ return {
35
+ ...BASE,
36
+ selection: { ...BASE.selection },
37
+ shadow: { ...SHADOW[mode] }
38
+ };
39
+ }
40
+ function resolveBoardRenderStyle(mode, style) {
41
+ const base = defaultBoardRenderStyle(mode);
42
+ if (!style) return base;
43
+ return {
44
+ ...base,
45
+ ...style,
46
+ selection: {
47
+ ...base.selection,
48
+ ...style.selection
49
+ },
50
+ shadow: style.shadow === void 0 ? base.shadow : style.shadow
51
+ };
52
+ }
53
+ //#endregion
54
+ export { defaultBoardRenderStyle, resolveBoardRenderStyle };
@@ -0,0 +1,34 @@
1
+ import { WorldPoint } from "../model/geometry.js";
2
+ import { BoardRenderStyle, BoardShadowStyle } from "./style.js";
3
+ import { Graphics } from "pixi.js";
4
+ //#region src/board/render/surface.d.ts
5
+ declare const PIXEL_STEP = 2;
6
+ type CardRect = {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ };
12
+ declare function traceCardOutline(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): Graphics;
13
+ declare function tracePixelRect(graphics: Graphics, x: number, y: number, width: number, height: number, step: number): Graphics;
14
+ declare function cardOutlinePoints(rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): WorldPoint[];
15
+ declare function drawCardShadow(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, shadow: BoardShadowStyle | null): void;
16
+ type CardSurface = {
17
+ fill: number;
18
+ fillAlpha?: number;
19
+ border?: number | null;
20
+ borderAlpha?: number;
21
+ elevation?: CardElevation;
22
+ };
23
+ type CardElevation = 0 | 1 | 2;
24
+ declare function cardShadow(style: BoardRenderStyle, elevation: CardElevation): BoardShadowStyle | null;
25
+ declare function drawCardSurface(graphics: Graphics, rect: CardRect, style: BoardRenderStyle, surface: CardSurface): Graphics;
26
+ declare function containRect(width: number, height: number, imageWidth: number, imageHeight: number): CardRect;
27
+ declare function cardElevation(context: {
28
+ liftedIds: ReadonlySet<string>;
29
+ }, id: string): CardElevation;
30
+ declare function isBoardCard(item: {
31
+ type: string;
32
+ }): boolean;
33
+ //#endregion
34
+ export { CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };
@@ -0,0 +1,132 @@
1
+ import { worldPoint } from "../model/geometry.js";
2
+ import { roundRectPoints } from "./stroke.js";
3
+ //#region src/board/render/surface.ts
4
+ const PIXEL_STEP = 2;
5
+ const SHADOW_LAYERS = 5;
6
+ function traceCardOutline(graphics, rect, style, grow = 0) {
7
+ const x = rect.x - grow;
8
+ const y = rect.y - grow;
9
+ const width = Math.max(0, rect.width + grow * 2);
10
+ const height = Math.max(0, rect.height + grow * 2);
11
+ if (style.corner === "pixel") return tracePixelRect(graphics, x, y, width, height, 2);
12
+ const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
13
+ return radius > 0 ? graphics.roundRect(x, y, width, height, radius) : graphics.rect(x, y, width, height);
14
+ }
15
+ function tracePixelRect(graphics, x, y, width, height, step) {
16
+ const s = Math.min(step, width / 4, height / 4);
17
+ if (s <= 0) return graphics.rect(x, y, width, height);
18
+ return graphics.poly(pixelRectPoints(x, y, width, height, s).flatMap((point) => [point.x, point.y]));
19
+ }
20
+ function pixelRectPoints(x, y, width, height, s) {
21
+ const r = x + width;
22
+ const b = y + height;
23
+ const p = worldPoint;
24
+ return [
25
+ p(x, y + s * 2),
26
+ p(x + s, y + s * 2),
27
+ p(x + s, y + s),
28
+ p(x + s * 2, y + s),
29
+ p(x + s * 2, y),
30
+ p(r - s * 2, y),
31
+ p(r - s * 2, y + s),
32
+ p(r - s, y + s),
33
+ p(r - s, y + s * 2),
34
+ p(r, y + s * 2),
35
+ p(r, b - s * 2),
36
+ p(r - s, b - s * 2),
37
+ p(r - s, b - s),
38
+ p(r - s * 2, b - s),
39
+ p(r - s * 2, b),
40
+ p(x + s * 2, b),
41
+ p(x + s * 2, b - s),
42
+ p(x + s, b - s),
43
+ p(x + s, b - s * 2),
44
+ p(x, b - s * 2)
45
+ ];
46
+ }
47
+ function cardOutlinePoints(rect, style, grow = 0) {
48
+ const x = rect.x - grow;
49
+ const y = rect.y - grow;
50
+ const width = Math.max(0, rect.width + grow * 2);
51
+ const height = Math.max(0, rect.height + grow * 2);
52
+ if (style.corner === "pixel") {
53
+ const s = Math.min(2, width / 4, height / 4);
54
+ const points = pixelRectPoints(x, y, width, height, Math.max(0, s));
55
+ return [...points, points[0]];
56
+ }
57
+ const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
58
+ return roundRectPoints(width, height, radius).map((point) => worldPoint(point.x + x, point.y + y));
59
+ }
60
+ function drawCardShadow(graphics, rect, style, shadow) {
61
+ if (!shadow || shadow.alpha <= 0 || shadow.blur <= 0) return;
62
+ const offset = {
63
+ ...rect,
64
+ y: rect.y + shadow.y
65
+ };
66
+ const reach = (index) => (1 - index / SHADOW_LAYERS) ** 2;
67
+ for (let index = SHADOW_LAYERS; index >= 1; index -= 1) {
68
+ const alpha = shadow.alpha * (reach(index - 1) - reach(index));
69
+ traceCardOutline(graphics, offset, style, shadow.blur * index / SHADOW_LAYERS).fill({
70
+ color: shadow.color,
71
+ alpha
72
+ });
73
+ traceCardOutline(graphics, rect, style).cut();
74
+ }
75
+ }
76
+ function cardShadow(style, elevation) {
77
+ const shadow = style.shadow;
78
+ if (!shadow || elevation === 0) return null;
79
+ if (elevation === 1) return shadow;
80
+ return {
81
+ ...shadow,
82
+ alpha: Math.min(1, shadow.alpha * 1.6),
83
+ blur: shadow.blur * 1.6,
84
+ y: shadow.y * 2 + 4
85
+ };
86
+ }
87
+ function drawCardSurface(graphics, rect, style, surface) {
88
+ drawCardShadow(graphics, rect, style, cardShadow(style, surface.elevation ?? 0));
89
+ traceCardOutline(graphics, rect, style).fill({
90
+ color: surface.fill,
91
+ alpha: surface.fillAlpha ?? 1
92
+ });
93
+ if (surface.border != null) traceCardOutline(graphics, rect, style).stroke({
94
+ color: surface.border,
95
+ width: 1,
96
+ alpha: surface.borderAlpha ?? 1,
97
+ alignment: 1
98
+ });
99
+ return graphics;
100
+ }
101
+ function containRect(width, height, imageWidth, imageHeight) {
102
+ if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
103
+ x: 0,
104
+ y: 0,
105
+ width: 0,
106
+ height: 0
107
+ };
108
+ const scale = Math.min(width / imageWidth, height / imageHeight);
109
+ return {
110
+ x: (width - imageWidth * scale) / 2,
111
+ y: (height - imageHeight * scale) / 2,
112
+ width: imageWidth * scale,
113
+ height: imageHeight * scale
114
+ };
115
+ }
116
+ const FREE_FORM_TYPES = /* @__PURE__ */ new Set([
117
+ "text",
118
+ "shape",
119
+ "draw",
120
+ "arrow",
121
+ "frame",
122
+ "effect",
123
+ "custom"
124
+ ]);
125
+ function cardElevation(context, id) {
126
+ return context.liftedIds.has(id) ? 2 : 1;
127
+ }
128
+ function isBoardCard(item) {
129
+ return !FREE_FORM_TYPES.has(item.type);
130
+ }
131
+ //#endregion
132
+ export { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };
@@ -0,0 +1,8 @@
1
+ import { Text } from "pixi.js";
2
+ //#region src/board/render/text-fit.d.ts
3
+ declare function ellipsizeWrappedLines(lines: string[], maxLines: number): string;
4
+ declare function fitLineWithEllipsis(line: string, wrapWidth: number, measureWidth: (value: string) => number): string;
5
+ declare function fitTextToLines(text: Text, value: string, maxLines: number, wrapWidth: number): void;
6
+ declare function fitTextToWidth(text: Text, value: string, width: number): void;
7
+ //#endregion
8
+ export { ellipsizeWrappedLines, fitLineWithEllipsis, fitTextToLines, fitTextToWidth };
@@ -0,0 +1,60 @@
1
+ import { CanvasTextMetrics } from "pixi.js";
2
+ //#region src/board/render/text-fit.ts
3
+ function ellipsizeWrappedLines(lines, maxLines) {
4
+ if (maxLines <= 0 || lines.length === 0) return "";
5
+ if (lines.length <= maxLines) return lines.join("\n");
6
+ const kept = lines.slice(0, maxLines);
7
+ const lastIndex = kept.length - 1;
8
+ const last = (kept[lastIndex] ?? "").replace(/\s+$/u, "");
9
+ kept[lastIndex] = last ? `${last}…` : "…";
10
+ return kept.join("\n");
11
+ }
12
+ function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
13
+ const trimmed = line.replace(/\s+$/u, "");
14
+ if (!trimmed) return "…";
15
+ const full = `${trimmed}…`;
16
+ if (measureWidth(full) <= wrapWidth) return full;
17
+ let lo = 0;
18
+ let hi = trimmed.length;
19
+ let best = "…";
20
+ while (lo <= hi) {
21
+ const mid = lo + hi >> 1;
22
+ const candidate = mid > 0 ? `${trimmed.slice(0, mid)}…` : "…";
23
+ if (measureWidth(candidate) <= wrapWidth) {
24
+ best = candidate;
25
+ lo = mid + 1;
26
+ } else hi = mid - 1;
27
+ }
28
+ return best;
29
+ }
30
+ function fitTextToLines(text, value, maxLines, wrapWidth) {
31
+ const width = Math.max(1, wrapWidth);
32
+ text.style.wordWrapWidth = width;
33
+ if (!value || maxLines <= 0) {
34
+ if (text.text !== "") text.text = "";
35
+ return;
36
+ }
37
+ const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
38
+ const sample = value.length > charCap ? value.slice(0, charCap) : value;
39
+ const metrics = CanvasTextMetrics.measureText(sample, text.style);
40
+ if (!(metrics.lines.length > maxLines || sample !== value)) {
41
+ if (text.text !== value) text.text = value;
42
+ return;
43
+ }
44
+ const keptCount = Math.min(maxLines, metrics.lines.length);
45
+ const kept = metrics.lines.slice(0, keptCount);
46
+ const lastIndex = kept.length - 1;
47
+ const probe = text.style.clone();
48
+ probe.wordWrap = false;
49
+ kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, probe).width);
50
+ const fitted = kept.join("\n");
51
+ if (text.text !== fitted) text.text = fitted;
52
+ }
53
+ function fitTextToWidth(text, value, width) {
54
+ text.style.wordWrap = false;
55
+ const measure = (candidate) => CanvasTextMetrics.measureText(candidate, text.style).width;
56
+ const fitted = !value || measure(value) <= width ? value : fitLineWithEllipsis(value, width, measure);
57
+ if (text.text !== fitted) text.text = fitted;
58
+ }
59
+ //#endregion
60
+ export { ellipsizeWrappedLines, fitLineWithEllipsis, fitTextToLines, fitTextToWidth };
@@ -2,6 +2,7 @@ import { Text } from "pixi.js";
2
2
  //#region src/board/render/text-resolution.d.ts
3
3
  declare function getBoardResolution(): number;
4
4
  declare function textZoomBucket(zoom: number): number;
5
+ declare function boardCardDetailed(zoom: number): boolean;
5
6
  /** Effective text resolution for a given camera zoom. */
6
7
  declare function textResolutionForZoom(zoom: number): number;
7
8
  /** Update a Pixi text texture only when zoom crosses a resolution bucket. */
@@ -13,4 +14,4 @@ declare function syncTextWrapWidth(text: Text, state: {
13
14
  wrapWidth: number;
14
15
  }, width: number, defer: boolean): void;
15
16
  //#endregion
16
- export { getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket };
17
+ export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket };
@@ -21,6 +21,9 @@ function textZoomBucket(zoom) {
21
21
  for (const bucket of TEXT_ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
22
22
  return TEXT_ZOOM_BUCKETS[TEXT_ZOOM_BUCKETS.length - 1] ?? 4;
23
23
  }
24
+ function boardCardDetailed(zoom) {
25
+ return textZoomBucket(zoom) >= 1;
26
+ }
24
27
  /** Effective text resolution for a given camera zoom. */
25
28
  function textResolutionForZoom(zoom) {
26
29
  const bucket = textZoomBucket(zoom);
@@ -40,4 +43,4 @@ function syncTextWrapWidth(text, state, width, defer) {
40
43
  state.wrapWidth = width;
41
44
  }
42
45
  //#endregion
43
- export { getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket };
46
+ export { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket };
@@ -2,7 +2,8 @@ import { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./l
2
2
  import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
3
3
  import { BoardAssetManager, BoardAssetManagerOptions, boardAssetKey, createBoardAssetManager } from "./assets.js";
4
4
  import { toBoardPointerEvent } from "./pointer.js";
5
- import { BoardLiveStroke, BoardScene, BoardSceneNode, SceneOverlayInput, SceneSyncInput, createBoardScene } from "./scene.js";
5
+ import { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene } from "./scene.js";
6
6
  import { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer } from "./views.js";
7
7
  import { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage } from "./stage.js";
8
- export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneOverlayInput, SceneSyncInput, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardScene, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
8
+ import { BoardOverlayInput, drawBoardOverlay, expandFrame } from "./overlay.js";
9
+ export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneSyncInput, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
@@ -3,6 +3,7 @@ import { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTextu
3
3
  import { boardAssetKey, createBoardAssetManager } from "./assets.js";
4
4
  import { toBoardPointerEvent } from "./pointer.js";
5
5
  import { createBoardScene } from "./scene.js";
6
+ import { drawBoardOverlay, expandFrame } from "./overlay.js";
6
7
  import { createBoardItemViewLayer } from "./views.js";
7
8
  import { mountBoardStage } from "./stage.js";
8
- export { DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardScene, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
9
+ export { DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
@@ -0,0 +1,41 @@
1
+ import { BoardFrame } from "../../protocol/dist/board-layout.js";
2
+ import { Rect, WorldPoint } from "../model/geometry.js";
3
+ import { BoardScene, BoardSceneItem } from "../model/scene.js";
4
+ import "../model/index.js";
5
+ import { BoardSelectionTransform, BoardTransformControl } from "../editor/selection-transform.js";
6
+ import "../editor/index.js";
7
+ import { BoardRenderStyle } from "../render/style.js";
8
+ import { BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
9
+ import "../render/index.js";
10
+ import { Graphics } from "pixi.js";
11
+ //#region src/board/stage/overlay.d.ts
12
+ type BoardOverlayInput = {
13
+ zoom: number;
14
+ pointerType: string;
15
+ palette: BoardRenderPalette;
16
+ style: BoardRenderStyle;
17
+ scene: BoardScene;
18
+ hovered: BoardSceneItem | null;
19
+ selected: readonly BoardSceneItem[];
20
+ march: number;
21
+ marquee: Rect | null;
22
+ transform: BoardSelectionTransform | null;
23
+ controls: boolean;
24
+ hoveredControl: BoardTransformControl | null;
25
+ rotationPointer: WorldPoint | null;
26
+ arrowEndpoints?: readonly WorldPoint[];
27
+ ports?: ReadonlyArray<{
28
+ x: number;
29
+ y: number;
30
+ radius: number;
31
+ }>;
32
+ hoveredPort?: {
33
+ x: number;
34
+ y: number;
35
+ } | null;
36
+ bindTarget?: BoardFrame | null;
37
+ };
38
+ declare function expandFrame(frame: BoardFrame, by: number): BoardFrame;
39
+ declare function drawBoardOverlay(graphics: Graphics, input: BoardOverlayInput): void;
40
+ //#endregion
41
+ export { BoardOverlayInput, drawBoardOverlay, expandFrame };