@mirasen/chessboard 1.1.4 → 1.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/CHANGELOG.md +43 -0
- package/README.md +7 -1
- package/assets/watermark/mirasen.svg +10 -0
- 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.d.ts +2 -0
- package/dist/extensions/first-party/watermark/factory.js +82 -0
- package/dist/extensions/first-party/watermark/types.d.ts +13 -0
- package/dist/extensions/first-party/watermark/types.js +2 -0
- 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 +5 -1
- package/dist/extensions/types/wrapper.js +10 -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 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { denormalizeSquare } from '../../../state/board/denormalize.js';
|
|
2
|
+
export function denormalizeCircles(circles) {
|
|
3
|
+
const result = [];
|
|
4
|
+
for (const circle of circles.values()) {
|
|
5
|
+
result.push({
|
|
6
|
+
square: denormalizeSquare(circle.square),
|
|
7
|
+
color: circle.color
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
export function denormalizeArrows(arrows) {
|
|
13
|
+
const result = [];
|
|
14
|
+
for (const arrow of arrows.values()) {
|
|
15
|
+
result.push({
|
|
16
|
+
from: denormalizeSquare(arrow.from),
|
|
17
|
+
to: denormalizeSquare(arrow.to),
|
|
18
|
+
color: arrow.color
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { cloneDeep } from 'es-toolkit';
|
|
2
|
+
import { extensionCreateInternalBase, extensionDestroyBase, extensionMountBase, extensionUnmountBase } from '../common/helpers.js';
|
|
3
|
+
import { annotationsClear, annotationsGetArrows, annotationsGetCircles, annotationsGetClearOnCoreInteraction, annotationsGetDrawButton, annotationsGetDrawModifier, annotationsSetArrow, annotationsSetArrows, annotationsSetCircle, annotationsSetCircles, annotationsSetClearOnCoreInteraction, annotationsSetDrawButton, annotationsSetDrawModifier } from './api.js';
|
|
4
|
+
import { cancelAnnotationsDrag, completeAnnotationsDrag, handleAnnotationsEvent } from './interaction.js';
|
|
5
|
+
import { normalizeAnnotationsConfig, normalizeInitialAnnotations } from './normalize.js';
|
|
6
|
+
import { renderCommittedAnnotations } from './render/committed.js';
|
|
7
|
+
import { renderPreviewAnnotations } from './render/preview.js';
|
|
8
|
+
import { EXTENSION_ID, EXTENSION_SLOTS } from './types/main.js';
|
|
9
|
+
import { annotationsOnUpdate } from './update.js';
|
|
10
|
+
export function createAnnotations(options = {}) {
|
|
11
|
+
const normalizedConfig = normalizeAnnotationsConfig(options.config);
|
|
12
|
+
const normalizedAnnotations = normalizeInitialAnnotations(options.annotations);
|
|
13
|
+
return {
|
|
14
|
+
id: EXTENSION_ID,
|
|
15
|
+
slots: EXTENSION_SLOTS,
|
|
16
|
+
createInstance(runtimeOptions) {
|
|
17
|
+
return createAnnotationsInstance(normalizedConfig, normalizedAnnotations, runtimeOptions);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createAnnotationsInternal(normalizedConfig, normalizedAnnotations, runtimeOptions) {
|
|
22
|
+
// Clone to ensure each instance has independent mutable state
|
|
23
|
+
const config = cloneDeep(normalizedConfig);
|
|
24
|
+
const annotations = {
|
|
25
|
+
circles: new Map(normalizedAnnotations.circles),
|
|
26
|
+
arrows: new Map(normalizedAnnotations.arrows)
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
...extensionCreateInternalBase(runtimeOptions),
|
|
30
|
+
runtimeSurface: runtimeOptions.runtimeSurface,
|
|
31
|
+
svg: {
|
|
32
|
+
svgCircles: new Map(),
|
|
33
|
+
svgArrows: new Map()
|
|
34
|
+
},
|
|
35
|
+
annotations,
|
|
36
|
+
config,
|
|
37
|
+
activeDrawGesture: null,
|
|
38
|
+
activeDrawPreviewTarget: null,
|
|
39
|
+
previewSvg: { circle: null, arrow: null }
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function extensionCleanSvg(state) {
|
|
43
|
+
state.svg.svgCircles.clear();
|
|
44
|
+
state.svg.svgArrows.clear();
|
|
45
|
+
if (state.previewSvg.arrow) {
|
|
46
|
+
state.previewSvg.arrow.marker.remove();
|
|
47
|
+
}
|
|
48
|
+
state.previewSvg.circle = null;
|
|
49
|
+
state.previewSvg.arrow = null;
|
|
50
|
+
}
|
|
51
|
+
function extensionClean(state) {
|
|
52
|
+
state.runtimeSurface.events.unsubscribeEvent('pointerdown');
|
|
53
|
+
state.runtimeSurface.events.unsubscribeEvent('contextmenu');
|
|
54
|
+
state.activeDrawGesture = null;
|
|
55
|
+
state.activeDrawPreviewTarget = null;
|
|
56
|
+
extensionCleanSvg(state);
|
|
57
|
+
}
|
|
58
|
+
function createAnnotationsPublicAPI(state) {
|
|
59
|
+
return {
|
|
60
|
+
getCircles() {
|
|
61
|
+
return annotationsGetCircles(state);
|
|
62
|
+
},
|
|
63
|
+
getArrows() {
|
|
64
|
+
return annotationsGetArrows(state);
|
|
65
|
+
},
|
|
66
|
+
setCircles(circles) {
|
|
67
|
+
annotationsSetCircles(state, circles);
|
|
68
|
+
},
|
|
69
|
+
setArrows(arrows) {
|
|
70
|
+
annotationsSetArrows(state, arrows);
|
|
71
|
+
},
|
|
72
|
+
circle(square, annotation) {
|
|
73
|
+
annotationsSetCircle(state, square, annotation);
|
|
74
|
+
},
|
|
75
|
+
arrow(from, to, annotation) {
|
|
76
|
+
annotationsSetArrow(state, from, to, annotation);
|
|
77
|
+
},
|
|
78
|
+
clear() {
|
|
79
|
+
annotationsClear(state);
|
|
80
|
+
},
|
|
81
|
+
get clearOnCoreInteraction() {
|
|
82
|
+
return annotationsGetClearOnCoreInteraction(state);
|
|
83
|
+
},
|
|
84
|
+
set clearOnCoreInteraction(value) {
|
|
85
|
+
annotationsSetClearOnCoreInteraction(state, value);
|
|
86
|
+
},
|
|
87
|
+
get drawButton() {
|
|
88
|
+
return annotationsGetDrawButton(state);
|
|
89
|
+
},
|
|
90
|
+
set drawButton(value) {
|
|
91
|
+
annotationsSetDrawButton(state, value);
|
|
92
|
+
},
|
|
93
|
+
get drawModifier() {
|
|
94
|
+
return annotationsGetDrawModifier(state);
|
|
95
|
+
},
|
|
96
|
+
set drawModifier(value) {
|
|
97
|
+
annotationsSetDrawModifier(state, value);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createAnnotationsInstance(normalizedConfig, normalizedAnnotations, runtimeOptions) {
|
|
102
|
+
const internalState = createAnnotationsInternal(normalizedConfig, normalizedAnnotations, runtimeOptions);
|
|
103
|
+
const publicAPI = createAnnotationsPublicAPI(internalState);
|
|
104
|
+
return {
|
|
105
|
+
id: EXTENSION_ID,
|
|
106
|
+
mount(env) {
|
|
107
|
+
extensionMountBase(internalState, env.slotRoots);
|
|
108
|
+
internalState.runtimeSurface.events.subscribeEvent('pointerdown');
|
|
109
|
+
internalState.runtimeSurface.events.subscribeEvent('contextmenu');
|
|
110
|
+
},
|
|
111
|
+
onUpdate(context) {
|
|
112
|
+
annotationsOnUpdate(internalState, context);
|
|
113
|
+
},
|
|
114
|
+
render(context) {
|
|
115
|
+
if (context.invalidation.dirtyLayers & 1 /* DirtyLayer.COMMITTED */) {
|
|
116
|
+
renderCommittedAnnotations(internalState, context.currentFrame.layout.geometry);
|
|
117
|
+
}
|
|
118
|
+
if (context.invalidation.dirtyLayers & 2 /* DirtyLayer.PREVIEW */) {
|
|
119
|
+
renderPreviewAnnotations(internalState, context.currentFrame.layout.geometry);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
onEvent(context) {
|
|
123
|
+
handleAnnotationsEvent(internalState, context);
|
|
124
|
+
},
|
|
125
|
+
completeDrag(session) {
|
|
126
|
+
completeAnnotationsDrag(internalState, session);
|
|
127
|
+
},
|
|
128
|
+
cancelDrag(session) {
|
|
129
|
+
cancelAnnotationsDrag(internalState, session);
|
|
130
|
+
},
|
|
131
|
+
unmount() {
|
|
132
|
+
extensionClean(internalState);
|
|
133
|
+
extensionUnmountBase(internalState);
|
|
134
|
+
},
|
|
135
|
+
destroy() {
|
|
136
|
+
extensionClean(internalState);
|
|
137
|
+
extensionDestroyBase(internalState);
|
|
138
|
+
},
|
|
139
|
+
getPublic() {
|
|
140
|
+
return publicAPI;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExtensionDragSessionSnapshot } from '../../types/basic/interaction.js';
|
|
2
|
+
import type { ExtensionOnEventContext } from '../../types/context/events.js';
|
|
3
|
+
import type { AnnotationColor, AnnotationsConfig } from './types/internal.js';
|
|
4
|
+
import type { AnnotationsStateInternal } from './types/main.js';
|
|
5
|
+
export declare function resolveAnnotationColor(config: AnnotationsConfig, rawEvent: PointerEvent): AnnotationColor;
|
|
6
|
+
export declare function handleAnnotationsEvent(state: AnnotationsStateInternal, context: ExtensionOnEventContext): void;
|
|
7
|
+
export declare function completeAnnotationsDrag(state: AnnotationsStateInternal, session: ExtensionDragSessionSnapshot): void;
|
|
8
|
+
export declare function cancelAnnotationsDrag(state: AnnotationsStateInternal, session: ExtensionDragSessionSnapshot): void;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import assert from '@ktarmyshov/assert';
|
|
2
|
+
import { clearCommittedAnnotations, hasCommittedAnnotations } from './committed.js';
|
|
3
|
+
import { markCommittedDirtyAndRequestRender } from './invalidation.js';
|
|
4
|
+
import { arrowAnnotationKeyNormalized } from './normalize.js';
|
|
5
|
+
const ANNOTATIONS_IDLE_CLEAR_DRAG_TYPE = 'ext:idle-clear';
|
|
6
|
+
const ANNOTATIONS_DRAW_DRAG_TYPE = 'ext:draw';
|
|
7
|
+
export function resolveAnnotationColor(config, rawEvent) {
|
|
8
|
+
if (config.drawModifier != null) {
|
|
9
|
+
return config.colors[config.drawModifier];
|
|
10
|
+
}
|
|
11
|
+
if (rawEvent.ctrlKey)
|
|
12
|
+
return config.colors.ctrl;
|
|
13
|
+
if (rawEvent.shiftKey)
|
|
14
|
+
return config.colors.shift;
|
|
15
|
+
if (rawEvent.altKey)
|
|
16
|
+
return config.colors.alt;
|
|
17
|
+
if (rawEvent.metaKey)
|
|
18
|
+
return config.colors.meta;
|
|
19
|
+
return config.colors.none;
|
|
20
|
+
}
|
|
21
|
+
export function handleAnnotationsEvent(state, context) {
|
|
22
|
+
assert(context.rawEvent.type === 'pointerdown' || context.rawEvent.type === 'contextmenu');
|
|
23
|
+
if (context.rawEvent.type === 'contextmenu') {
|
|
24
|
+
// Browser context menus are separate from the pointer drag lifecycle.
|
|
25
|
+
// The board owns secondary-button interaction, so suppress the browser menu.
|
|
26
|
+
context.rawEvent.preventDefault();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const rawEvent = context.rawEvent;
|
|
30
|
+
// The configured draw button owns annotation drawing gestures.
|
|
31
|
+
// If configured as primary, drawing intentionally takes precedence over idle-clear.
|
|
32
|
+
if (rawEvent.button === state.config.drawButton) {
|
|
33
|
+
const targetSquare = context.sceneEvent?.targetSquare;
|
|
34
|
+
if (targetSquare === undefined || targetSquare === null)
|
|
35
|
+
return;
|
|
36
|
+
state.activeDrawGesture = {
|
|
37
|
+
sourceSquare: targetSquare,
|
|
38
|
+
color: resolveAnnotationColor(state.config, rawEvent)
|
|
39
|
+
};
|
|
40
|
+
const success = state.runtimeSurface.commands.startDrag({
|
|
41
|
+
type: ANNOTATIONS_DRAW_DRAG_TYPE,
|
|
42
|
+
sourceSquare: targetSquare,
|
|
43
|
+
sourcePieceCode: null,
|
|
44
|
+
targetSquare: targetSquare
|
|
45
|
+
});
|
|
46
|
+
if (success) {
|
|
47
|
+
rawEvent.preventDefault();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
state.activeDrawGesture = null;
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Drawing uses the configured draw button above.
|
|
55
|
+
// The remaining primary-button path is reserved for idle-clear.
|
|
56
|
+
if (rawEvent.button !== 0)
|
|
57
|
+
return;
|
|
58
|
+
const targetSquare = context.sceneEvent?.targetSquare;
|
|
59
|
+
if (targetSquare === undefined || targetSquare === null)
|
|
60
|
+
return;
|
|
61
|
+
if (context.runtimeInteractionActionPreview !== null)
|
|
62
|
+
return;
|
|
63
|
+
if (!state.config.clearOnCoreInteraction)
|
|
64
|
+
return;
|
|
65
|
+
if (!hasCommittedAnnotations(state))
|
|
66
|
+
return;
|
|
67
|
+
const success = state.runtimeSurface.commands.startDrag({
|
|
68
|
+
type: ANNOTATIONS_IDLE_CLEAR_DRAG_TYPE,
|
|
69
|
+
sourceSquare: targetSquare,
|
|
70
|
+
sourcePieceCode: null,
|
|
71
|
+
targetSquare: targetSquare
|
|
72
|
+
});
|
|
73
|
+
if (success) {
|
|
74
|
+
rawEvent.preventDefault();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function completeAnnotationsDrag(state, session) {
|
|
78
|
+
if (session.type === ANNOTATIONS_DRAW_DRAG_TYPE) {
|
|
79
|
+
const gesture = state.activeDrawGesture;
|
|
80
|
+
state.activeDrawGesture = null;
|
|
81
|
+
state.activeDrawPreviewTarget = null;
|
|
82
|
+
if (!gesture)
|
|
83
|
+
return;
|
|
84
|
+
if (session.targetSquare === null)
|
|
85
|
+
return;
|
|
86
|
+
if (gesture.sourceSquare === session.targetSquare) {
|
|
87
|
+
commitCircleToggle(state, gesture.sourceSquare, gesture.color);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
commitArrowToggle(state, gesture.sourceSquare, session.targetSquare, gesture.color);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (session.type !== ANNOTATIONS_IDLE_CLEAR_DRAG_TYPE)
|
|
95
|
+
return;
|
|
96
|
+
if (session.targetSquare === null)
|
|
97
|
+
return;
|
|
98
|
+
if (!hasCommittedAnnotations(state))
|
|
99
|
+
return;
|
|
100
|
+
clearCommittedAnnotations(state);
|
|
101
|
+
markCommittedDirtyAndRequestRender(state);
|
|
102
|
+
}
|
|
103
|
+
export function cancelAnnotationsDrag(state, session) {
|
|
104
|
+
if (session.type === ANNOTATIONS_DRAW_DRAG_TYPE) {
|
|
105
|
+
state.activeDrawGesture = null;
|
|
106
|
+
state.activeDrawPreviewTarget = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function commitCircleToggle(state, square, color) {
|
|
110
|
+
const existing = state.annotations.circles.get(square);
|
|
111
|
+
if (existing && existing.color === color) {
|
|
112
|
+
state.annotations.circles.delete(square);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
state.annotations.circles.set(square, { key: square, square, color });
|
|
116
|
+
}
|
|
117
|
+
markCommittedDirtyAndRequestRender(state);
|
|
118
|
+
}
|
|
119
|
+
function commitArrowToggle(state, from, to, color) {
|
|
120
|
+
const key = arrowAnnotationKeyNormalized(from, to);
|
|
121
|
+
const existing = state.annotations.arrows.get(key);
|
|
122
|
+
if (existing && existing.color === color) {
|
|
123
|
+
state.annotations.arrows.delete(key);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
state.annotations.arrows.set(key, { key, from, to, color });
|
|
127
|
+
}
|
|
128
|
+
markCommittedDirtyAndRequestRender(state);
|
|
129
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function markDirtyAndRequestRender(state, layers) {
|
|
2
|
+
state.runtimeSurface.invalidation.markDirty(layers);
|
|
3
|
+
state.runtimeSurface.commands.requestRender({ state: true });
|
|
4
|
+
}
|
|
5
|
+
export function markCommittedDirtyAndRequestRender(state) {
|
|
6
|
+
markDirtyAndRequestRender(state, 1 /* DirtyLayer.COMMITTED */);
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SquareString } from '../../../state/board/types/input.js';
|
|
2
|
+
import type { Square } from '../../../state/board/types/internal.js';
|
|
3
|
+
import type { AnnotationsConfig, ArrowAnnotation, ArrowAnnotationKey, CircleAnnotation, CircleAnnotationKey } from './types/internal.js';
|
|
4
|
+
import type { AnnotationsStateInternalAnnotations } from './types/main.js';
|
|
5
|
+
import type { AnnotationsInitOptionsAnnotations, AnnotationsInitOptionsConfig, ArrowAnnotationPublic, CircleAnnotationPublic } from './types/public.js';
|
|
6
|
+
export declare function normalizeAnnotationsConfig(input?: AnnotationsInitOptionsConfig): AnnotationsConfig;
|
|
7
|
+
export declare function circleAnnotationKey(square: SquareString): CircleAnnotationKey;
|
|
8
|
+
export declare function normalizeCircleAnnotation(pub: CircleAnnotationPublic): CircleAnnotation;
|
|
9
|
+
export declare function arrowAnnotationKeyNormalized(from: Square, to: Square): ArrowAnnotationKey;
|
|
10
|
+
export declare function arrowAnnotationKey(from: SquareString, to: SquareString): ArrowAnnotationKey;
|
|
11
|
+
export declare function normalizeArrowAnnotation(pub: ArrowAnnotationPublic): ArrowAnnotation;
|
|
12
|
+
export declare function normalizeInitialAnnotations(input?: AnnotationsInitOptionsAnnotations): AnnotationsStateInternalAnnotations;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cloneDeep, toMerged } from 'es-toolkit';
|
|
2
|
+
import { normalizeSquare } from '../../../state/board/normalize.js';
|
|
3
|
+
import { DEFAULT_CONFIG } from './constants.js';
|
|
4
|
+
export function normalizeAnnotationsConfig(input) {
|
|
5
|
+
if (!input) {
|
|
6
|
+
return cloneDeep(DEFAULT_CONFIG);
|
|
7
|
+
}
|
|
8
|
+
return toMerged(DEFAULT_CONFIG, input);
|
|
9
|
+
}
|
|
10
|
+
export function circleAnnotationKey(square) {
|
|
11
|
+
return normalizeSquare(square);
|
|
12
|
+
}
|
|
13
|
+
export function normalizeCircleAnnotation(pub) {
|
|
14
|
+
const square = normalizeSquare(pub.square);
|
|
15
|
+
return {
|
|
16
|
+
key: square,
|
|
17
|
+
square,
|
|
18
|
+
color: pub.color
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function arrowAnnotationKeyNormalized(from, to) {
|
|
22
|
+
if (from === to) {
|
|
23
|
+
throw new Error("Invalid arrow: 'from' and 'to' must be different squares");
|
|
24
|
+
}
|
|
25
|
+
return from * 64 + to;
|
|
26
|
+
}
|
|
27
|
+
export function arrowAnnotationKey(from, to) {
|
|
28
|
+
const fromKey = normalizeSquare(from);
|
|
29
|
+
const toKey = normalizeSquare(to);
|
|
30
|
+
return arrowAnnotationKeyNormalized(fromKey, toKey);
|
|
31
|
+
}
|
|
32
|
+
export function normalizeArrowAnnotation(pub) {
|
|
33
|
+
const from = normalizeSquare(pub.from);
|
|
34
|
+
const to = normalizeSquare(pub.to);
|
|
35
|
+
const key = arrowAnnotationKeyNormalized(from, to);
|
|
36
|
+
return { key, from, to, color: pub.color };
|
|
37
|
+
}
|
|
38
|
+
export function normalizeInitialAnnotations(input) {
|
|
39
|
+
const circles = new Map();
|
|
40
|
+
const arrows = new Map();
|
|
41
|
+
if (input?.circles) {
|
|
42
|
+
for (const pub of input.circles) {
|
|
43
|
+
const circle = normalizeCircleAnnotation(pub);
|
|
44
|
+
circles.set(circle.key, circle);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (input?.arrows) {
|
|
48
|
+
for (const pub of input.arrows) {
|
|
49
|
+
const arrow = normalizeArrowAnnotation(pub);
|
|
50
|
+
arrows.set(arrow.key, arrow);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { circles, arrows };
|
|
54
|
+
}
|
|
@@ -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;
|