@mirasen/chessboard 1.1.5 → 1.2.1
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/CHANGELOG.md +45 -0
- package/README.md +61 -13
- package/dist/animation/render/fade.d.ts +2 -3
- package/dist/animation/render/fade.js +6 -6
- package/dist/animation/render/move.d.ts +2 -3
- package/dist/animation/render/move.js +6 -6
- package/dist/animation/render/plan.d.ts +3 -4
- package/dist/animation/render/plan.js +5 -5
- package/dist/animation/render/static.d.ts +2 -3
- package/dist/animation/render/static.js +4 -4
- package/dist/animation/render/types.d.ts +10 -4
- package/dist/extensions/build/index.d.ts +1 -2
- package/dist/extensions/build/index.js +1 -1
- package/dist/extensions/factory/main.js +11 -7
- package/dist/extensions/factory/runtime.js +24 -1
- package/dist/extensions/first-party/active-target/factory.js +18 -11
- package/dist/extensions/first-party/annotations/api.d.ts +16 -0
- package/dist/extensions/first-party/annotations/api.js +79 -0
- package/dist/extensions/first-party/annotations/committed.d.ts +3 -0
- package/dist/extensions/first-party/annotations/committed.js +11 -0
- package/dist/extensions/first-party/annotations/constants.d.ts +3 -0
- package/dist/extensions/first-party/annotations/constants.js +54 -0
- package/dist/extensions/first-party/annotations/denormalize.d.ts +4 -0
- package/dist/extensions/first-party/annotations/denormalize.js +22 -0
- package/dist/extensions/first-party/annotations/factory.d.ts +3 -0
- package/dist/extensions/first-party/annotations/factory.js +143 -0
- package/dist/extensions/first-party/annotations/interaction.d.ts +8 -0
- package/dist/extensions/first-party/annotations/interaction.js +129 -0
- package/dist/extensions/first-party/annotations/invalidation.d.ts +3 -0
- package/dist/extensions/first-party/annotations/invalidation.js +7 -0
- package/dist/extensions/first-party/annotations/normalize.d.ts +12 -0
- package/dist/extensions/first-party/annotations/normalize.js +54 -0
- package/dist/extensions/first-party/annotations/render/committed.d.ts +3 -0
- package/dist/extensions/first-party/annotations/render/committed.js +123 -0
- package/dist/extensions/first-party/annotations/render/helpers/arrows.d.ts +33 -0
- package/dist/extensions/first-party/annotations/render/helpers/arrows.js +93 -0
- package/dist/extensions/first-party/annotations/render/helpers/circles.d.ts +18 -0
- package/dist/extensions/first-party/annotations/render/helpers/circles.js +30 -0
- package/dist/extensions/first-party/annotations/render/preview.d.ts +3 -0
- package/dist/extensions/first-party/annotations/render/preview.js +96 -0
- package/dist/extensions/first-party/annotations/types/internal.d.ts +77 -0
- package/dist/extensions/first-party/annotations/types/internal.js +1 -0
- package/dist/extensions/first-party/annotations/types/main.d.ts +40 -0
- package/dist/extensions/first-party/annotations/types/main.js +6 -0
- package/dist/extensions/first-party/annotations/types/public.d.ts +48 -0
- package/dist/extensions/first-party/annotations/types/public.js +1 -0
- package/dist/extensions/first-party/annotations/update.d.ts +3 -0
- package/dist/extensions/first-party/annotations/update.js +45 -0
- package/dist/extensions/first-party/auto-promote/factory.js +6 -6
- package/dist/extensions/first-party/board-events/factory.js +6 -6
- package/dist/extensions/first-party/common/helpers.d.ts +2 -1
- package/dist/extensions/first-party/common/helpers.js +6 -5
- package/dist/extensions/first-party/common/types.d.ts +2 -0
- package/dist/extensions/first-party/last-move/factory.js +9 -9
- package/dist/extensions/first-party/legal-moves/factory.js +8 -8
- package/dist/extensions/first-party/main-renderer/animation/factory.d.ts +2 -2
- package/dist/extensions/first-party/main-renderer/animation/factory.js +4 -4
- package/dist/extensions/first-party/main-renderer/animation/render.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/animation/render.js +2 -2
- package/dist/extensions/first-party/main-renderer/animation/types.d.ts +3 -3
- package/dist/extensions/first-party/main-renderer/board/factory.js +2 -2
- package/dist/extensions/first-party/main-renderer/board/render.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/board/render.js +4 -4
- package/dist/extensions/first-party/main-renderer/board/types.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/coordinates/factory.js +2 -2
- package/dist/extensions/first-party/main-renderer/coordinates/render.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/coordinates/render.js +5 -5
- package/dist/extensions/first-party/main-renderer/coordinates/types.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/drag/factory.d.ts +2 -2
- package/dist/extensions/first-party/main-renderer/drag/factory.js +11 -8
- package/dist/extensions/first-party/main-renderer/drag/render.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/drag/render.js +7 -6
- package/dist/extensions/first-party/main-renderer/drag/types.d.ts +6 -5
- package/dist/extensions/first-party/main-renderer/drag/update.js +2 -2
- package/dist/extensions/first-party/main-renderer/factory.js +9 -5
- package/dist/extensions/first-party/main-renderer/piece-symbols.d.ts +19 -0
- package/dist/extensions/first-party/main-renderer/piece-symbols.js +45 -0
- package/dist/extensions/first-party/main-renderer/pieces/factory.d.ts +2 -2
- package/dist/extensions/first-party/main-renderer/pieces/factory.js +4 -4
- package/dist/extensions/first-party/main-renderer/pieces/render.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/pieces/render.js +8 -8
- package/dist/extensions/first-party/main-renderer/pieces/types.d.ts +4 -4
- package/dist/extensions/first-party/main-renderer/types/extension.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/types/extension.js +8 -1
- package/dist/extensions/first-party/main-renderer/types/instance.d.ts +2 -0
- package/dist/extensions/first-party/promotion/factory.js +10 -10
- package/dist/extensions/first-party/selected-square/factory.js +8 -8
- package/dist/extensions/first-party/watermark/factory.js +8 -8
- package/dist/extensions/types/basic/events.d.ts +32 -3
- package/dist/extensions/types/basic/events.js +2 -1
- package/dist/extensions/types/basic/mount.d.ts +5 -2
- package/dist/extensions/types/basic/mount.js +0 -2
- package/dist/extensions/types/basic/transient-visuals.d.ts +1 -1
- package/dist/extensions/types/context/animation.d.ts +3 -3
- package/dist/extensions/types/context/events.d.ts +2 -1
- package/dist/extensions/types/context/update.d.ts +6 -7
- package/dist/extensions/types/extension.d.ts +3 -0
- package/dist/extensions/types/main.d.ts +1 -0
- package/dist/extensions/types/surface/commands.d.ts +1 -1
- package/dist/extensions/types/surface/main.d.ts +6 -4
- package/dist/extensions/types/wrapper.d.ts +3 -1
- package/dist/extensions/types/wrapper.js +6 -2
- package/dist/extensions/update.js +6 -10
- package/dist/index.d.ts +1 -1
- package/dist/render/mount.js +3 -3
- package/dist/render/rendering/state.js +2 -2
- package/dist/render/svg/factory.js +40 -25
- package/dist/render/svg/helpers.d.ts +8 -4
- package/dist/render/svg/helpers.js +27 -12
- package/dist/render/svg/ids.d.ts +6 -0
- package/dist/render/svg/ids.js +11 -0
- package/dist/render/types.d.ts +4 -3
- package/dist/render/types.js +2 -1
- package/dist/runtime/factory/input.d.ts +3 -1
- package/dist/runtime/factory/input.js +11 -2
- package/dist/runtime/factory/main.js +10 -4
- package/dist/runtime/input/adapter/event.js +2 -2
- package/dist/runtime/input/adapter/pointer.d.ts +2 -2
- package/dist/runtime/input/controller/factory.d.ts +1 -1
- package/dist/runtime/input/controller/factory.js +35 -15
- package/dist/runtime/input/controller/helpers.d.ts +1 -1
- package/dist/runtime/input/controller/interaction.d.ts +3 -0
- package/dist/runtime/input/controller/interaction.js +14 -0
- package/dist/runtime/input/controller/pointer.d.ts +6 -6
- package/dist/runtime/input/controller/pointer.js +53 -27
- package/dist/runtime/input/controller/transient-visuals.d.ts +2 -3
- package/dist/runtime/input/controller/types.d.ts +6 -5
- package/dist/runtime/types/main.d.ts +1 -1
- package/dist/wrapper/factory.js +2 -7
- package/dist/wrapper/types.d.ts +3 -11
- package/package.json +1 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import assert from '@ktarmyshov/assert';
|
|
2
|
+
import { VISUAL_CONFIG } from '../constants.js';
|
|
3
|
+
import { arrowAnnotationKeyNormalized } from '../normalize.js';
|
|
4
|
+
import { createAnnotationArrowSvg, removeAnnotationArrowSvg, updateAnnotationArrowSvg } from './helpers/arrows.js';
|
|
5
|
+
import { createAnnotationCircleSvg, removeAnnotationCircleSvg, updateAnnotationCircleSvg } from './helpers/circles.js';
|
|
6
|
+
export function renderCommittedAnnotations(state, geometry) {
|
|
7
|
+
assert(state.slotRoots, 'Expected slotRoots to be initialized before rendering annotations');
|
|
8
|
+
renderCommittedCircles(state, geometry);
|
|
9
|
+
renderCommittedArrows(state, geometry);
|
|
10
|
+
}
|
|
11
|
+
function getSuppressedCircleKey(state) {
|
|
12
|
+
const gesture = state.activeDrawGesture;
|
|
13
|
+
const target = state.activeDrawPreviewTarget;
|
|
14
|
+
if (gesture === null || target === null)
|
|
15
|
+
return null;
|
|
16
|
+
if (target !== gesture.sourceSquare)
|
|
17
|
+
return null;
|
|
18
|
+
const existing = state.annotations.circles.get(target);
|
|
19
|
+
if (existing === undefined || existing.color !== gesture.color)
|
|
20
|
+
return null;
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
function getSuppressedArrowKey(state) {
|
|
24
|
+
const gesture = state.activeDrawGesture;
|
|
25
|
+
const target = state.activeDrawPreviewTarget;
|
|
26
|
+
if (gesture === null || target === null)
|
|
27
|
+
return null;
|
|
28
|
+
if (target === gesture.sourceSquare)
|
|
29
|
+
return null;
|
|
30
|
+
const key = arrowAnnotationKeyNormalized(gesture.sourceSquare, target);
|
|
31
|
+
const existing = state.annotations.arrows.get(key);
|
|
32
|
+
if (existing === undefined || existing.color !== gesture.color)
|
|
33
|
+
return null;
|
|
34
|
+
return key;
|
|
35
|
+
}
|
|
36
|
+
function renderCommittedCircles(state, geometry) {
|
|
37
|
+
const { committed } = VISUAL_CONFIG.circle;
|
|
38
|
+
const suppressedKey = getSuppressedCircleKey(state);
|
|
39
|
+
const seenKeys = new Set();
|
|
40
|
+
for (const [key, circle] of state.annotations.circles) {
|
|
41
|
+
if (key === suppressedKey)
|
|
42
|
+
continue;
|
|
43
|
+
seenKeys.add(key);
|
|
44
|
+
const params = {
|
|
45
|
+
parent: state.slotRoots.overPieces,
|
|
46
|
+
dataId: `annotation-circle-committed-${key}`,
|
|
47
|
+
square: circle.square,
|
|
48
|
+
color: circle.color,
|
|
49
|
+
radius: committed.radius,
|
|
50
|
+
strokeWidth: committed.strokeWidth,
|
|
51
|
+
opacity: committed.opacity,
|
|
52
|
+
geometry
|
|
53
|
+
};
|
|
54
|
+
const existing = state.svg.svgCircles.get(key);
|
|
55
|
+
if (existing) {
|
|
56
|
+
updateAnnotationCircleSvg(existing, params);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const rendered = createAnnotationCircleSvg(params);
|
|
60
|
+
state.svg.svgCircles.set(key, rendered);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const [key, rendered] of state.svg.svgCircles) {
|
|
64
|
+
if (!seenKeys.has(key)) {
|
|
65
|
+
removeAnnotationCircleSvg(rendered);
|
|
66
|
+
state.svg.svgCircles.delete(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function renderCommittedArrows(state, geometry) {
|
|
71
|
+
const { committed } = VISUAL_CONFIG.arrow;
|
|
72
|
+
const suppressedKey = getSuppressedArrowKey(state);
|
|
73
|
+
const seenKeys = new Set();
|
|
74
|
+
for (const [key, arrow] of state.annotations.arrows) {
|
|
75
|
+
if (key === suppressedKey)
|
|
76
|
+
continue;
|
|
77
|
+
seenKeys.add(key);
|
|
78
|
+
const fromRect = geometry.getSquareRect(arrow.from);
|
|
79
|
+
const toRect = geometry.getSquareRect(arrow.to);
|
|
80
|
+
const fromCx = fromRect.x + fromRect.width / 2;
|
|
81
|
+
const fromCy = fromRect.y + fromRect.height / 2;
|
|
82
|
+
const toCx = toRect.x + toRect.width / 2;
|
|
83
|
+
const toCy = toRect.y + toRect.height / 2;
|
|
84
|
+
const dx = toCx - fromCx;
|
|
85
|
+
const dy = toCy - fromCy;
|
|
86
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
87
|
+
if (dist === 0) {
|
|
88
|
+
seenKeys.delete(key);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const markerId = state.svgIds.makeId('annotations', `arrowhead-${key}`);
|
|
92
|
+
const markerHref = `url(${state.svgIds.makeHref('annotations', `arrowhead-${key}`)})`;
|
|
93
|
+
const params = {
|
|
94
|
+
defsRoot: state.slotRoots.defs,
|
|
95
|
+
visualRoot: state.slotRoots.overPieces,
|
|
96
|
+
markerDataId: `annotation-arrowhead-committed-${key}`,
|
|
97
|
+
markerPathDataId: `annotation-arrowhead-path-committed-${key}`,
|
|
98
|
+
lineDataId: `annotation-arrow-committed-${key}`,
|
|
99
|
+
markerId,
|
|
100
|
+
markerHref,
|
|
101
|
+
from: arrow.from,
|
|
102
|
+
to: arrow.to,
|
|
103
|
+
color: arrow.color,
|
|
104
|
+
opacity: committed.opacity,
|
|
105
|
+
visual: committed,
|
|
106
|
+
geometry
|
|
107
|
+
};
|
|
108
|
+
const existing = state.svg.svgArrows.get(key);
|
|
109
|
+
if (existing) {
|
|
110
|
+
updateAnnotationArrowSvg(existing, params);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const rendered = createAnnotationArrowSvg(params);
|
|
114
|
+
state.svg.svgArrows.set(key, rendered);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const [key, rendered] of state.svg.svgArrows) {
|
|
118
|
+
if (!seenKeys.has(key)) {
|
|
119
|
+
removeAnnotationArrowSvg(rendered);
|
|
120
|
+
state.svg.svgArrows.delete(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SceneRenderGeometry } from '../../../../../layout/geometry/types.js';
|
|
2
|
+
import type { Square } from '../../../../../state/board/types/internal.js';
|
|
3
|
+
import type { RenderedAnnotationArrowSvg } from '../../types/main.js';
|
|
4
|
+
export interface AnnotationArrowSvgVisualConfig {
|
|
5
|
+
readonly strokeWidth: number;
|
|
6
|
+
readonly startOffset: number;
|
|
7
|
+
readonly endOffset: number;
|
|
8
|
+
readonly opacity: number;
|
|
9
|
+
readonly markerWidth: number;
|
|
10
|
+
readonly markerHeight: number;
|
|
11
|
+
readonly markerRefX: number;
|
|
12
|
+
readonly markerRefY: number;
|
|
13
|
+
readonly markerViewBox: string;
|
|
14
|
+
readonly markerPathD: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AnnotationArrowSvgParams {
|
|
17
|
+
readonly defsRoot: SVGDefsElement;
|
|
18
|
+
readonly visualRoot: SVGGElement;
|
|
19
|
+
readonly markerDataId: string;
|
|
20
|
+
readonly markerPathDataId: string;
|
|
21
|
+
readonly lineDataId: string;
|
|
22
|
+
readonly markerId: string;
|
|
23
|
+
readonly markerHref: string;
|
|
24
|
+
readonly from: Square;
|
|
25
|
+
readonly to: Square;
|
|
26
|
+
readonly color: string;
|
|
27
|
+
readonly opacity: number;
|
|
28
|
+
readonly visual: AnnotationArrowSvgVisualConfig;
|
|
29
|
+
readonly geometry: SceneRenderGeometry;
|
|
30
|
+
}
|
|
31
|
+
export declare function createAnnotationArrowSvg(params: AnnotationArrowSvgParams): RenderedAnnotationArrowSvg;
|
|
32
|
+
export declare function updateAnnotationArrowSvg(rendered: RenderedAnnotationArrowSvg, params: AnnotationArrowSvgParams): void;
|
|
33
|
+
export declare function removeAnnotationArrowSvg(rendered: RenderedAnnotationArrowSvg): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createSvgElement, updateSvgElementAttributes } from '../../../../../render/svg/helpers.js';
|
|
2
|
+
function computeArrowGeometry(params) {
|
|
3
|
+
const { from, to, visual, geometry } = params;
|
|
4
|
+
const squareSize = geometry.squareSize;
|
|
5
|
+
const fromRect = geometry.getSquareRect(from);
|
|
6
|
+
const toRect = geometry.getSquareRect(to);
|
|
7
|
+
const fromCx = fromRect.x + fromRect.width / 2;
|
|
8
|
+
const fromCy = fromRect.y + fromRect.height / 2;
|
|
9
|
+
const toCx = toRect.x + toRect.width / 2;
|
|
10
|
+
const toCy = toRect.y + toRect.height / 2;
|
|
11
|
+
const dx = toCx - fromCx;
|
|
12
|
+
const dy = toCy - fromCy;
|
|
13
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
14
|
+
const ux = dx / dist;
|
|
15
|
+
const uy = dy / dist;
|
|
16
|
+
const absoluteStartOffset = squareSize * visual.startOffset;
|
|
17
|
+
const absoluteEndOffset = squareSize * visual.endOffset;
|
|
18
|
+
return {
|
|
19
|
+
x1: (fromCx + ux * absoluteStartOffset).toString(),
|
|
20
|
+
y1: (fromCy + uy * absoluteStartOffset).toString(),
|
|
21
|
+
x2: (toCx - ux * absoluteEndOffset).toString(),
|
|
22
|
+
y2: (toCy - uy * absoluteEndOffset).toString(),
|
|
23
|
+
strokeWidth: (squareSize * visual.strokeWidth).toString()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function createAnnotationArrowSvg(params) {
|
|
27
|
+
const { defsRoot, visualRoot, markerDataId, markerPathDataId, lineDataId, markerId, markerHref, color, opacity, visual } = params;
|
|
28
|
+
const geo = computeArrowGeometry(params);
|
|
29
|
+
const marker = createSvgElement(defsRoot, 'marker', {
|
|
30
|
+
'data-chessboard-id': markerDataId,
|
|
31
|
+
id: markerId,
|
|
32
|
+
markerUnits: 'strokeWidth',
|
|
33
|
+
markerWidth: visual.markerWidth.toString(),
|
|
34
|
+
markerHeight: visual.markerHeight.toString(),
|
|
35
|
+
refX: visual.markerRefX.toString(),
|
|
36
|
+
refY: visual.markerRefY.toString(),
|
|
37
|
+
orient: 'auto',
|
|
38
|
+
overflow: 'visible',
|
|
39
|
+
viewBox: visual.markerViewBox
|
|
40
|
+
});
|
|
41
|
+
const markerPath = createSvgElement(marker, 'path', {
|
|
42
|
+
'data-chessboard-id': markerPathDataId,
|
|
43
|
+
d: visual.markerPathD,
|
|
44
|
+
fill: color
|
|
45
|
+
});
|
|
46
|
+
const line = createSvgElement(visualRoot, 'line', {
|
|
47
|
+
'data-chessboard-id': lineDataId,
|
|
48
|
+
x1: geo.x1,
|
|
49
|
+
y1: geo.y1,
|
|
50
|
+
x2: geo.x2,
|
|
51
|
+
y2: geo.y2,
|
|
52
|
+
stroke: color,
|
|
53
|
+
'stroke-width': geo.strokeWidth,
|
|
54
|
+
'stroke-linecap': 'round',
|
|
55
|
+
opacity: opacity.toString(),
|
|
56
|
+
'marker-end': markerHref
|
|
57
|
+
});
|
|
58
|
+
return { line, marker, markerPath };
|
|
59
|
+
}
|
|
60
|
+
export function updateAnnotationArrowSvg(rendered, params) {
|
|
61
|
+
const { markerId, markerHref, color, opacity, visual } = params;
|
|
62
|
+
const geo = computeArrowGeometry(params);
|
|
63
|
+
updateSvgElementAttributes(rendered.marker, {
|
|
64
|
+
id: markerId,
|
|
65
|
+
markerUnits: 'strokeWidth',
|
|
66
|
+
markerWidth: visual.markerWidth.toString(),
|
|
67
|
+
markerHeight: visual.markerHeight.toString(),
|
|
68
|
+
refX: visual.markerRefX.toString(),
|
|
69
|
+
refY: visual.markerRefY.toString(),
|
|
70
|
+
orient: 'auto',
|
|
71
|
+
overflow: 'visible',
|
|
72
|
+
viewBox: visual.markerViewBox
|
|
73
|
+
});
|
|
74
|
+
updateSvgElementAttributes(rendered.markerPath, {
|
|
75
|
+
d: visual.markerPathD,
|
|
76
|
+
fill: color
|
|
77
|
+
});
|
|
78
|
+
updateSvgElementAttributes(rendered.line, {
|
|
79
|
+
x1: geo.x1,
|
|
80
|
+
y1: geo.y1,
|
|
81
|
+
x2: geo.x2,
|
|
82
|
+
y2: geo.y2,
|
|
83
|
+
stroke: color,
|
|
84
|
+
'stroke-width': geo.strokeWidth,
|
|
85
|
+
'stroke-linecap': 'round',
|
|
86
|
+
opacity: opacity.toString(),
|
|
87
|
+
'marker-end': markerHref
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function removeAnnotationArrowSvg(rendered) {
|
|
91
|
+
rendered.line.remove();
|
|
92
|
+
rendered.marker.remove();
|
|
93
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SceneRenderGeometry } from '../../../../../layout/geometry/types.js';
|
|
2
|
+
import type { Square } from '../../../../../state/board/types/internal.js';
|
|
3
|
+
import type { RenderedAnnotationCircleSvg } from '../../types/main.js';
|
|
4
|
+
export interface AnnotationCircleSvgAttributeParams {
|
|
5
|
+
readonly square: Square;
|
|
6
|
+
readonly color: string;
|
|
7
|
+
readonly radius: number;
|
|
8
|
+
readonly strokeWidth: number;
|
|
9
|
+
readonly opacity: number;
|
|
10
|
+
readonly geometry: SceneRenderGeometry;
|
|
11
|
+
}
|
|
12
|
+
export interface AnnotationCircleSvgCreateParams extends AnnotationCircleSvgAttributeParams {
|
|
13
|
+
readonly parent: SVGGElement;
|
|
14
|
+
readonly dataId: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function createAnnotationCircleSvg(params: AnnotationCircleSvgCreateParams): RenderedAnnotationCircleSvg;
|
|
17
|
+
export declare function updateAnnotationCircleSvg(rendered: RenderedAnnotationCircleSvg, params: AnnotationCircleSvgAttributeParams): void;
|
|
18
|
+
export declare function removeAnnotationCircleSvg(rendered: RenderedAnnotationCircleSvg): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createSvgElement, updateSvgElementAttributes } from '../../../../../render/svg/helpers.js';
|
|
2
|
+
function computeCircleAttributes(params) {
|
|
3
|
+
const { square, color, radius, strokeWidth, opacity, geometry } = params;
|
|
4
|
+
const squareSize = geometry.squareSize;
|
|
5
|
+
const rect = geometry.getSquareRect(square);
|
|
6
|
+
return {
|
|
7
|
+
cx: (rect.x + rect.width / 2).toString(),
|
|
8
|
+
cy: (rect.y + rect.height / 2).toString(),
|
|
9
|
+
r: (squareSize * radius).toString(),
|
|
10
|
+
fill: 'none',
|
|
11
|
+
stroke: color,
|
|
12
|
+
'stroke-width': (squareSize * strokeWidth).toString(),
|
|
13
|
+
opacity: opacity.toString()
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createAnnotationCircleSvg(params) {
|
|
17
|
+
const attributes = computeCircleAttributes(params);
|
|
18
|
+
const circle = createSvgElement(params.parent, 'circle', {
|
|
19
|
+
'data-chessboard-id': params.dataId,
|
|
20
|
+
...attributes
|
|
21
|
+
});
|
|
22
|
+
return { circle };
|
|
23
|
+
}
|
|
24
|
+
export function updateAnnotationCircleSvg(rendered, params) {
|
|
25
|
+
const attributes = computeCircleAttributes(params);
|
|
26
|
+
updateSvgElementAttributes(rendered.circle, attributes);
|
|
27
|
+
}
|
|
28
|
+
export function removeAnnotationCircleSvg(rendered) {
|
|
29
|
+
rendered.circle.remove();
|
|
30
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from '@ktarmyshov/assert';
|
|
2
|
+
import { VISUAL_CONFIG } from '../constants.js';
|
|
3
|
+
import { arrowAnnotationKeyNormalized } from '../normalize.js';
|
|
4
|
+
import { createAnnotationArrowSvg, removeAnnotationArrowSvg, updateAnnotationArrowSvg } from './helpers/arrows.js';
|
|
5
|
+
import { createAnnotationCircleSvg, removeAnnotationCircleSvg, updateAnnotationCircleSvg } from './helpers/circles.js';
|
|
6
|
+
export function renderPreviewAnnotations(state, geometry) {
|
|
7
|
+
assert(state.slotRoots, 'Expected slotRoots to be initialized before rendering preview');
|
|
8
|
+
const gesture = state.activeDrawGesture;
|
|
9
|
+
const target = state.activeDrawPreviewTarget;
|
|
10
|
+
// No preview needed: clear any existing preview DOM
|
|
11
|
+
if (gesture === null || target === null) {
|
|
12
|
+
clearPreviewCircle(state);
|
|
13
|
+
clearPreviewArrow(state);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const source = gesture.sourceSquare;
|
|
17
|
+
const color = gesture.color;
|
|
18
|
+
if (target === source) {
|
|
19
|
+
// Circle preview
|
|
20
|
+
clearPreviewArrow(state);
|
|
21
|
+
renderPreviewCircle(state, geometry, source, color);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Arrow preview
|
|
25
|
+
clearPreviewCircle(state);
|
|
26
|
+
renderPreviewArrowSvg(state, geometry, source, target, color);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function renderPreviewCircle(state, geometry, square, color) {
|
|
30
|
+
const { previewAdd, previewRemoveOpacity } = VISUAL_CONFIG.circle;
|
|
31
|
+
// Determine add vs remove style
|
|
32
|
+
const existingCircle = state.annotations.circles.get(square);
|
|
33
|
+
const isRemove = existingCircle !== undefined && existingCircle.color === color;
|
|
34
|
+
const opacity = isRemove ? previewRemoveOpacity : previewAdd.opacity;
|
|
35
|
+
assert(state.slotRoots, 'Expected slotRoots to be initialized before rendering preview');
|
|
36
|
+
const params = {
|
|
37
|
+
parent: state.slotRoots.drag,
|
|
38
|
+
dataId: 'annotation-circle-preview',
|
|
39
|
+
square,
|
|
40
|
+
color,
|
|
41
|
+
radius: previewAdd.radius,
|
|
42
|
+
strokeWidth: previewAdd.strokeWidth,
|
|
43
|
+
opacity,
|
|
44
|
+
geometry
|
|
45
|
+
};
|
|
46
|
+
if (state.previewSvg.circle) {
|
|
47
|
+
updateAnnotationCircleSvg(state.previewSvg.circle, params);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
state.previewSvg.circle = createAnnotationCircleSvg(params);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function renderPreviewArrowSvg(state, geometry, from, to, color) {
|
|
54
|
+
const { committed, previewAdd, previewRemoveOpacity } = VISUAL_CONFIG.arrow;
|
|
55
|
+
// Determine add vs remove style
|
|
56
|
+
const arrowKey = arrowAnnotationKeyNormalized(from, to);
|
|
57
|
+
const existingArrow = state.annotations.arrows.get(arrowKey);
|
|
58
|
+
const isRemove = existingArrow !== undefined && existingArrow.color === color;
|
|
59
|
+
const visual = isRemove ? committed : previewAdd;
|
|
60
|
+
const opacity = isRemove ? previewRemoveOpacity : previewAdd.opacity;
|
|
61
|
+
const markerId = state.svgIds.makeId('annotations', 'arrowhead-preview');
|
|
62
|
+
const markerHref = `url(${state.svgIds.makeHref('annotations', 'arrowhead-preview')})`;
|
|
63
|
+
const params = {
|
|
64
|
+
defsRoot: state.slotRoots.defs,
|
|
65
|
+
visualRoot: state.slotRoots.drag,
|
|
66
|
+
markerDataId: 'annotation-arrowhead-preview',
|
|
67
|
+
markerPathDataId: 'annotation-arrowhead-path-preview',
|
|
68
|
+
lineDataId: 'annotation-arrow-preview',
|
|
69
|
+
markerId,
|
|
70
|
+
markerHref,
|
|
71
|
+
from,
|
|
72
|
+
to,
|
|
73
|
+
color,
|
|
74
|
+
opacity,
|
|
75
|
+
visual,
|
|
76
|
+
geometry
|
|
77
|
+
};
|
|
78
|
+
if (state.previewSvg.arrow) {
|
|
79
|
+
updateAnnotationArrowSvg(state.previewSvg.arrow, params);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
state.previewSvg.arrow = createAnnotationArrowSvg(params);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function clearPreviewCircle(state) {
|
|
86
|
+
if (state.previewSvg.circle) {
|
|
87
|
+
removeAnnotationCircleSvg(state.previewSvg.circle);
|
|
88
|
+
state.previewSvg.circle = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function clearPreviewArrow(state) {
|
|
92
|
+
if (state.previewSvg.arrow) {
|
|
93
|
+
removeAnnotationArrowSvg(state.previewSvg.arrow);
|
|
94
|
+
state.previewSvg.arrow = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Square } from '../../../../state/board/types/internal.js';
|
|
2
|
+
import type { AnnotationsDrawModifier } from './public.js';
|
|
3
|
+
/** Annotation color as a CSS-compatible color string. */
|
|
4
|
+
export type AnnotationColor = string;
|
|
5
|
+
export type CircleAnnotationKey = Square;
|
|
6
|
+
export type ArrowAnnotationKey = number;
|
|
7
|
+
export interface CircleAnnotation {
|
|
8
|
+
readonly key: CircleAnnotationKey;
|
|
9
|
+
readonly square: Square;
|
|
10
|
+
readonly color: AnnotationColor;
|
|
11
|
+
}
|
|
12
|
+
export interface ArrowAnnotation {
|
|
13
|
+
readonly key: ArrowAnnotationKey;
|
|
14
|
+
readonly from: Square;
|
|
15
|
+
readonly to: Square;
|
|
16
|
+
readonly color: AnnotationColor;
|
|
17
|
+
}
|
|
18
|
+
export interface AnnotationModifierColorConfig {
|
|
19
|
+
readonly none: AnnotationColor;
|
|
20
|
+
readonly ctrl: AnnotationColor;
|
|
21
|
+
readonly shift: AnnotationColor;
|
|
22
|
+
readonly alt: AnnotationColor;
|
|
23
|
+
readonly meta: AnnotationColor;
|
|
24
|
+
}
|
|
25
|
+
export interface AnnotationsConfig {
|
|
26
|
+
clearOnCoreInteraction: boolean;
|
|
27
|
+
drawButton: number;
|
|
28
|
+
drawModifier: AnnotationsDrawModifier | null;
|
|
29
|
+
readonly colors: AnnotationModifierColorConfig;
|
|
30
|
+
}
|
|
31
|
+
export interface ActiveDrawGesture {
|
|
32
|
+
readonly sourceSquare: Square;
|
|
33
|
+
readonly color: AnnotationColor;
|
|
34
|
+
}
|
|
35
|
+
export interface VisualConfigCircleAnnotation {
|
|
36
|
+
strokeWidth: number;
|
|
37
|
+
radius: number;
|
|
38
|
+
opacity: number;
|
|
39
|
+
}
|
|
40
|
+
export interface VisualConfigArrowAnnotation {
|
|
41
|
+
strokeWidth: number;
|
|
42
|
+
startOffset: number;
|
|
43
|
+
endOffset: number;
|
|
44
|
+
opacity: number;
|
|
45
|
+
markerWidth: number;
|
|
46
|
+
markerHeight: number;
|
|
47
|
+
markerRefX: number;
|
|
48
|
+
markerRefY: number;
|
|
49
|
+
markerViewBox: string;
|
|
50
|
+
markerPathD: string;
|
|
51
|
+
}
|
|
52
|
+
export interface VisualConfigCircle {
|
|
53
|
+
readonly committed: VisualConfigCircleAnnotation;
|
|
54
|
+
readonly previewAdd: VisualConfigCircleAnnotation;
|
|
55
|
+
/**
|
|
56
|
+
* Remove preview reuses previewAdd definitions
|
|
57
|
+
* and overrides only opacity.
|
|
58
|
+
*/
|
|
59
|
+
readonly previewRemoveOpacity: number;
|
|
60
|
+
}
|
|
61
|
+
export interface VisualConfigArrow {
|
|
62
|
+
readonly committed: VisualConfigArrowAnnotation;
|
|
63
|
+
readonly previewAdd: VisualConfigArrowAnnotation;
|
|
64
|
+
/**
|
|
65
|
+
* Remove preview reuses committed arrow definitions
|
|
66
|
+
* and overrides only opacity.
|
|
67
|
+
*/
|
|
68
|
+
readonly previewRemoveOpacity: number;
|
|
69
|
+
}
|
|
70
|
+
export interface VisualConfig {
|
|
71
|
+
readonly circle: VisualConfigCircle;
|
|
72
|
+
readonly arrow: VisualConfigArrow;
|
|
73
|
+
}
|
|
74
|
+
export declare const enum DirtyLayer {
|
|
75
|
+
COMMITTED = 1,
|
|
76
|
+
PREVIEW = 2
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Square } from '../../../../state/board/types/internal.js';
|
|
2
|
+
import type { ExtensionDefinition, ExtensionInstance } from '../../../types/extension.js';
|
|
3
|
+
import type { ExtensionRuntimeSurface } from '../../../types/surface/main.js';
|
|
4
|
+
import type { ExtensionInternalBase } from '../../common/types.js';
|
|
5
|
+
import type { ActiveDrawGesture, AnnotationsConfig, ArrowAnnotation, ArrowAnnotationKey, CircleAnnotation, CircleAnnotationKey } from './internal.js';
|
|
6
|
+
import type { AnnotationsPublicAPI } from './public.js';
|
|
7
|
+
export declare const EXTENSION_SLOTS: readonly ["defs", "overPieces", "drag"];
|
|
8
|
+
export type ExtensionSlotsType = typeof EXTENSION_SLOTS;
|
|
9
|
+
export declare const EXTENSION_ID: "annotations";
|
|
10
|
+
export type AnnotationsDefinition = ExtensionDefinition<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, AnnotationsPublicAPI>;
|
|
11
|
+
export type AnnotationsInstance = ExtensionInstance<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, AnnotationsPublicAPI>;
|
|
12
|
+
export interface RenderedAnnotationCircleSvg {
|
|
13
|
+
readonly circle: SVGCircleElement;
|
|
14
|
+
}
|
|
15
|
+
export interface RenderedAnnotationArrowSvg {
|
|
16
|
+
readonly line: SVGLineElement;
|
|
17
|
+
readonly marker: SVGMarkerElement;
|
|
18
|
+
readonly markerPath: SVGPathElement;
|
|
19
|
+
}
|
|
20
|
+
export interface AnnotationsStateInternalSvg {
|
|
21
|
+
readonly svgCircles: Map<CircleAnnotationKey, RenderedAnnotationCircleSvg>;
|
|
22
|
+
readonly svgArrows: Map<ArrowAnnotationKey, RenderedAnnotationArrowSvg>;
|
|
23
|
+
}
|
|
24
|
+
export interface AnnotationsStateInternalAnnotations {
|
|
25
|
+
readonly circles: Map<CircleAnnotationKey, CircleAnnotation>;
|
|
26
|
+
readonly arrows: Map<ArrowAnnotationKey, ArrowAnnotation>;
|
|
27
|
+
}
|
|
28
|
+
export interface AnnotationsStateInternalPreviewSvg {
|
|
29
|
+
circle: RenderedAnnotationCircleSvg | null;
|
|
30
|
+
arrow: RenderedAnnotationArrowSvg | null;
|
|
31
|
+
}
|
|
32
|
+
export interface AnnotationsStateInternal extends ExtensionInternalBase<ExtensionSlotsType> {
|
|
33
|
+
readonly runtimeSurface: ExtensionRuntimeSurface;
|
|
34
|
+
readonly svg: AnnotationsStateInternalSvg;
|
|
35
|
+
readonly annotations: AnnotationsStateInternalAnnotations;
|
|
36
|
+
readonly config: AnnotationsConfig;
|
|
37
|
+
activeDrawGesture: ActiveDrawGesture | null;
|
|
38
|
+
activeDrawPreviewTarget: Square | null;
|
|
39
|
+
readonly previewSvg: AnnotationsStateInternalPreviewSvg;
|
|
40
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SquareString } from '../../../../state/board/types/input.js';
|
|
2
|
+
/** The annotation color resolved from user input modifiers. */
|
|
3
|
+
export type AnnotationColorPublic = string;
|
|
4
|
+
export interface CircleAnnotationPublic {
|
|
5
|
+
readonly square: SquareString;
|
|
6
|
+
readonly color: AnnotationColorPublic;
|
|
7
|
+
}
|
|
8
|
+
export interface ArrowAnnotationPublic {
|
|
9
|
+
readonly from: SquareString;
|
|
10
|
+
readonly to: SquareString;
|
|
11
|
+
readonly color: AnnotationColorPublic;
|
|
12
|
+
}
|
|
13
|
+
export interface AnnotationsInitOptionsConfigModifierColor {
|
|
14
|
+
none?: AnnotationColorPublic;
|
|
15
|
+
ctrl?: AnnotationColorPublic;
|
|
16
|
+
shift?: AnnotationColorPublic;
|
|
17
|
+
alt?: AnnotationColorPublic;
|
|
18
|
+
meta?: AnnotationColorPublic;
|
|
19
|
+
}
|
|
20
|
+
export type AnnotationsDrawModifier = Exclude<keyof AnnotationsInitOptionsConfigModifierColor, 'none'>;
|
|
21
|
+
export interface AnnotationsInitOptionsConfig {
|
|
22
|
+
clearOnCoreInteraction?: boolean;
|
|
23
|
+
drawButton?: number;
|
|
24
|
+
drawModifier?: AnnotationsDrawModifier | null;
|
|
25
|
+
colors?: AnnotationsInitOptionsConfigModifierColor;
|
|
26
|
+
}
|
|
27
|
+
export interface AnnotationsInitOptionsAnnotations {
|
|
28
|
+
circles?: CircleAnnotationPublic[];
|
|
29
|
+
arrows?: ArrowAnnotationPublic[];
|
|
30
|
+
}
|
|
31
|
+
export interface AnnotationsInitOptions {
|
|
32
|
+
config?: AnnotationsInitOptionsConfig;
|
|
33
|
+
annotations?: AnnotationsInitOptionsAnnotations;
|
|
34
|
+
}
|
|
35
|
+
export type CircleAnnotationValuePublic = Omit<CircleAnnotationPublic, 'square'>;
|
|
36
|
+
export type ArrowAnnotationValuePublic = Omit<ArrowAnnotationPublic, 'from' | 'to'>;
|
|
37
|
+
export interface AnnotationsPublicAPI {
|
|
38
|
+
getCircles(): CircleAnnotationPublic[];
|
|
39
|
+
getArrows(): ArrowAnnotationPublic[];
|
|
40
|
+
setCircles(circles: CircleAnnotationPublic[]): void;
|
|
41
|
+
setArrows(arrows: ArrowAnnotationPublic[]): void;
|
|
42
|
+
circle(square: SquareString, annotation: CircleAnnotationValuePublic | null): void;
|
|
43
|
+
arrow(from: SquareString, to: SquareString, annotation: ArrowAnnotationValuePublic | null): void;
|
|
44
|
+
clear(): void;
|
|
45
|
+
clearOnCoreInteraction: boolean;
|
|
46
|
+
drawButton: number;
|
|
47
|
+
drawModifier: AnnotationsDrawModifier | null;
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { isUpdateContextRenderable } from '../../types/context/update.js';
|
|
2
|
+
import { clearCommittedAnnotations, hasCommittedAnnotations } from './committed.js';
|
|
3
|
+
import { EXTENSION_ID } from './types/main.js';
|
|
4
|
+
export function annotationsOnUpdate(state, context) {
|
|
5
|
+
if (state.config.clearOnCoreInteraction &&
|
|
6
|
+
hasCommittedAnnotations(state) &&
|
|
7
|
+
context.mutation.hasMutation({
|
|
8
|
+
causes: ['runtime.interaction.completeCoreDragTo']
|
|
9
|
+
})) {
|
|
10
|
+
clearCommittedAnnotations(state);
|
|
11
|
+
context.invalidation.markDirty(1 /* DirtyLayer.COMMITTED */);
|
|
12
|
+
}
|
|
13
|
+
if (context.mutation.hasMutation({
|
|
14
|
+
causes: ['layout.refreshGeometry']
|
|
15
|
+
}) &&
|
|
16
|
+
isUpdateContextRenderable(context)) {
|
|
17
|
+
context.invalidation.markDirty(1 /* DirtyLayer.COMMITTED */);
|
|
18
|
+
if (state.activeDrawPreviewTarget !== null ||
|
|
19
|
+
state.previewSvg.circle !== null ||
|
|
20
|
+
state.previewSvg.arrow !== null) {
|
|
21
|
+
context.invalidation.markDirty(2 /* DirtyLayer.PREVIEW */);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Preview target tracking from ext:draw drag session
|
|
25
|
+
let nextPreviewTarget = null;
|
|
26
|
+
if (state.activeDrawGesture !== null) {
|
|
27
|
+
const dragSession = context.currentFrame.state.interaction.dragSession;
|
|
28
|
+
if (dragSession &&
|
|
29
|
+
dragSession.type === 'ext:draw' &&
|
|
30
|
+
'owner' in dragSession &&
|
|
31
|
+
dragSession.owner === EXTENSION_ID) {
|
|
32
|
+
nextPreviewTarget = dragSession.targetSquare;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (nextPreviewTarget !== state.activeDrawPreviewTarget) {
|
|
36
|
+
state.activeDrawPreviewTarget = nextPreviewTarget;
|
|
37
|
+
context.invalidation.markDirty(2 /* DirtyLayer.PREVIEW */);
|
|
38
|
+
context.invalidation.markDirty(1 /* DirtyLayer.COMMITTED */);
|
|
39
|
+
}
|
|
40
|
+
else if (nextPreviewTarget === null &&
|
|
41
|
+
(state.previewSvg.circle !== null || state.previewSvg.arrow !== null)) {
|
|
42
|
+
context.invalidation.markDirty(2 /* DirtyLayer.PREVIEW */);
|
|
43
|
+
context.invalidation.markDirty(1 /* DirtyLayer.COMMITTED */);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -4,14 +4,14 @@ export function createAutoPromote() {
|
|
|
4
4
|
return {
|
|
5
5
|
id: EXTENSION_ID,
|
|
6
6
|
slots: EXTENSION_SLOTS,
|
|
7
|
-
createInstance() {
|
|
8
|
-
return createAutoPromoteInstance();
|
|
7
|
+
createInstance(options) {
|
|
8
|
+
return createAutoPromoteInstance(options);
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
function createAutoPromoteInternal() {
|
|
12
|
+
function createAutoPromoteInternal(options) {
|
|
13
13
|
return {
|
|
14
|
-
...extensionCreateInternalBase(),
|
|
14
|
+
...extensionCreateInternalBase(options),
|
|
15
15
|
toQueen: false
|
|
16
16
|
};
|
|
17
17
|
}
|
|
@@ -25,8 +25,8 @@ function createAutoPromoteInstancePublic(state) {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
function createAutoPromoteInstance() {
|
|
29
|
-
const internalState = createAutoPromoteInternal();
|
|
28
|
+
function createAutoPromoteInstance(options) {
|
|
29
|
+
const internalState = createAutoPromoteInternal(options);
|
|
30
30
|
const publicInterface = createAutoPromoteInstancePublic(internalState);
|
|
31
31
|
return {
|
|
32
32
|
id: EXTENSION_ID,
|