@rpgjs/client 5.0.0-beta.7 → 5.0.0-beta.9
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 +27 -0
- package/dist/Game/AnimationManager.js.map +1 -1
- package/dist/Game/Event.js.map +1 -1
- package/dist/Game/Map.d.ts +9 -1
- package/dist/Game/Map.js +63 -5
- package/dist/Game/Map.js.map +1 -1
- package/dist/Game/Object.d.ts +17 -9
- package/dist/Game/Object.js +1 -12
- package/dist/Game/Object.js.map +1 -1
- package/dist/Game/Player.js.map +1 -1
- package/dist/Gui/Gui.d.ts +17 -4
- package/dist/Gui/Gui.js +64 -34
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/Gui/Gui.spec.d.ts +1 -0
- package/dist/Gui/NotificationManager.js.map +1 -1
- package/dist/Resource.js +1 -1
- package/dist/Resource.js.map +1 -1
- package/dist/RpgClient.d.ts +35 -2
- package/dist/RpgClientEngine.d.ts +41 -5
- package/dist/RpgClientEngine.js +50 -5
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/Sound.js.map +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.128.0 → _@oxc-project_runtime@0.130.0}/helpers/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.128.0 → _@oxc-project_runtime@0.130.0}/helpers/decorateMetadata.js +1 -1
- package/dist/components/animations/animation.ce.js.map +1 -1
- package/dist/components/animations/hit.ce.js.map +1 -1
- package/dist/components/animations/index.js.map +1 -1
- package/dist/components/character.ce.js +259 -5
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/dynamics/bar.ce.js +96 -0
- package/dist/components/dynamics/bar.ce.js.map +1 -0
- package/dist/components/dynamics/image.ce.js +23 -0
- package/dist/components/dynamics/image.ce.js.map +1 -0
- package/dist/components/dynamics/parse-value.d.ts +3 -0
- package/dist/components/dynamics/parse-value.js +51 -35
- package/dist/components/dynamics/parse-value.js.map +1 -1
- package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
- package/dist/components/dynamics/shape-utils.d.ts +16 -0
- package/dist/components/dynamics/shape-utils.js +73 -0
- package/dist/components/dynamics/shape-utils.js.map +1 -0
- package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
- package/dist/components/dynamics/shape.ce.js +83 -0
- package/dist/components/dynamics/shape.ce.js.map +1 -0
- package/dist/components/dynamics/text.ce.js +28 -41
- package/dist/components/dynamics/text.ce.js.map +1 -1
- package/dist/components/gui/box.ce.js.map +1 -1
- package/dist/components/gui/dialogbox/index.ce.js +3 -3
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/gameover.ce.js +1 -1
- package/dist/components/gui/gameover.ce.js.map +1 -1
- package/dist/components/gui/hud/hud.ce.js +1 -1
- package/dist/components/gui/hud/hud.ce.js.map +1 -1
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
- package/dist/components/gui/mobile/index.js.map +1 -1
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
- package/dist/components/gui/notification/notification.ce.js.map +1 -1
- package/dist/components/gui/save-load.ce.js.map +1 -1
- package/dist/components/gui/shop/shop.ce.js +1 -1
- package/dist/components/gui/shop/shop.ce.js.map +1 -1
- package/dist/components/gui/title-screen.ce.js +2 -2
- package/dist/components/gui/title-screen.ce.js.map +1 -1
- package/dist/components/player-components-utils.d.ts +67 -0
- package/dist/components/player-components-utils.js +162 -0
- package/dist/components/player-components-utils.js.map +1 -0
- package/dist/components/player-components-utils.spec.d.ts +1 -0
- package/dist/components/player-components.ce.js +188 -0
- package/dist/components/player-components.ce.js.map +1 -0
- package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
- package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
- package/dist/components/scenes/canvas.ce.js +147 -4
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +2 -8
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/components/scenes/event-layer.ce.js.map +1 -1
- package/dist/core/inject.js +1 -1
- package/dist/core/inject.js.map +1 -1
- package/dist/core/setup.js +1 -1
- package/dist/core/setup.js.map +1 -1
- package/dist/decorators/spritesheet.d.ts +1 -0
- package/dist/decorators/spritesheet.js +11 -0
- package/dist/decorators/spritesheet.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/dist/module.js +4 -1
- package/dist/module.js.map +1 -1
- package/dist/node_modules/.pnpm/{@signe_di@2.10.0 → @signe_di@3.0.1}/node_modules/@signe/di/dist/index.js +1 -1
- package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/{@signe_reactive@2.9.2 → @signe_reactive@3.0.1}/node_modules/@signe/reactive/dist/index.js +15 -3
- package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
- package/dist/node_modules/.pnpm/{@signe_room@2.10.0 → @signe_room@3.0.1}/node_modules/@signe/room/dist/index.js +124 -39
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/{@signe_sync@2.10.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/client/index.js +1 -1
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
- package/dist/node_modules/.pnpm/{@signe_sync@2.10.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/index.js +36 -13
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
- package/dist/presets/animation.js.map +1 -1
- package/dist/presets/faceset.js.map +1 -1
- package/dist/presets/icon.js.map +1 -1
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/lpc.js.map +1 -1
- package/dist/presets/rmspritesheet.js.map +1 -1
- package/dist/services/AbstractSocket.js.map +1 -1
- package/dist/services/keyboardControls.js.map +1 -1
- package/dist/services/loadMap.d.ts +6 -0
- package/dist/services/loadMap.js +1 -1
- package/dist/services/loadMap.js.map +1 -1
- package/dist/services/mmorpg.js +7 -3
- package/dist/services/mmorpg.js.map +1 -1
- package/dist/services/save.js.map +1 -1
- package/dist/services/standalone.js +1 -1
- package/dist/services/standalone.js.map +1 -1
- package/dist/utils/getEntityProp.js.map +1 -1
- package/package.json +10 -10
- package/src/Game/Map.ts +91 -2
- package/src/Game/Object.ts +22 -35
- package/src/Gui/Gui.spec.ts +273 -0
- package/src/Gui/Gui.ts +105 -50
- package/src/Resource.ts +1 -2
- package/src/RpgClient.ts +36 -2
- package/src/RpgClientEngine.ts +74 -11
- package/src/components/character.ce +318 -9
- package/src/components/dynamics/bar.ce +87 -0
- package/src/components/dynamics/image.ce +20 -0
- package/src/components/dynamics/parse-value.spec.ts +41 -0
- package/src/components/dynamics/parse-value.ts +102 -37
- package/src/components/dynamics/shape-utils.spec.ts +46 -0
- package/src/components/dynamics/shape-utils.ts +61 -0
- package/src/components/dynamics/shape.ce +89 -0
- package/src/components/dynamics/text.ce +34 -149
- package/src/components/player-components-utils.spec.ts +109 -0
- package/src/components/player-components-utils.ts +205 -0
- package/src/components/player-components.ce +221 -0
- package/src/components/scenes/canvas.ce +165 -6
- package/src/components/scenes/draw-map.ce +2 -15
- package/src/components/scenes/event-layer.ce +1 -2
- package/src/core/setup.ts +2 -2
- package/src/decorators/spritesheet.ts +8 -0
- package/src/index.ts +1 -0
- package/src/module.ts +5 -1
- package/src/services/loadMap.ts +2 -0
- package/src/services/mmorpg.ts +8 -2
- package/dist/node_modules/.pnpm/@signe_di@2.10.0/node_modules/@signe/di/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js +0 -45
- package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_reactive@2.9.2/node_modules/@signe/reactive/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_room@2.10.0/node_modules/@signe/room/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.10.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.10.0/node_modules/@signe/sync/dist/index.js.map +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { signal } from "canvasengine";
|
|
3
|
+
import { resolveDynamicProps, resolveDynamicValue } from "./parse-value";
|
|
4
|
+
|
|
5
|
+
describe("dynamic component values", () => {
|
|
6
|
+
test("resolves player properties and keeps bar placeholders for the bar renderer", () => {
|
|
7
|
+
const object = {
|
|
8
|
+
name: signal("Alex"),
|
|
9
|
+
hpSignal: signal(100),
|
|
10
|
+
_param: signal({ maxHp: 120 })
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(resolveDynamicValue("HP: {hp}/{param.maxHp} {name} {$current}", object)).toBe("HP: 100/120 Alex {$current}");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("keeps resolved props reactive", () => {
|
|
17
|
+
const object = {
|
|
18
|
+
name: signal("Alex"),
|
|
19
|
+
hpSignal: signal(100),
|
|
20
|
+
_param: signal({ maxHp: 120 })
|
|
21
|
+
};
|
|
22
|
+
const props: any = resolveDynamicProps({
|
|
23
|
+
value: "HP: {hp} {name}",
|
|
24
|
+
text: "{$current}/{$max} {name}",
|
|
25
|
+
style: {
|
|
26
|
+
width: "{hp}"
|
|
27
|
+
}
|
|
28
|
+
}, object);
|
|
29
|
+
|
|
30
|
+
expect(props.value()).toBe("HP: 100 Alex");
|
|
31
|
+
expect(props.text()).toBe("{$current}/{$max} Alex");
|
|
32
|
+
expect(props.style()).toEqual({ width: "100" });
|
|
33
|
+
|
|
34
|
+
object.hpSignal.set(10);
|
|
35
|
+
object.name.set("Sam");
|
|
36
|
+
|
|
37
|
+
expect(props.value()).toBe("HP: 10 Sam");
|
|
38
|
+
expect(props.text()).toBe("{$current}/{$max} Sam");
|
|
39
|
+
expect(props.style()).toEqual({ width: "10" });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -6,17 +6,114 @@ interface MatchResult {
|
|
|
6
6
|
index: number;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
const readSignal = (value: any) => typeof value === 'function' ? value() : value;
|
|
10
|
+
const DYNAMIC_VALUE_PATTERN = /\{([^}]+)\}/g;
|
|
11
|
+
|
|
12
|
+
const hasDynamicValue = (value: any) => {
|
|
13
|
+
value = readSignal(value);
|
|
14
|
+
if (typeof value !== 'string') return false;
|
|
15
|
+
DYNAMIC_VALUE_PATTERN.lastIndex = 0;
|
|
16
|
+
return DYNAMIC_VALUE_PATTERN.test(value);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const resolveDynamicSnapshot = (value: any, object?: any): any => {
|
|
20
|
+
value = readSignal(value);
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return value.map((entry) => resolveDynamicSnapshot(entry, object));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (value && typeof value === 'object') {
|
|
27
|
+
return Object.fromEntries(
|
|
28
|
+
Object.entries(value).map(([key, entry]) => [key, resolveDynamicSnapshot(entry, object)])
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return resolveDynamicValue(value, object);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const getDynamicValue = (property: string, object?: any) => {
|
|
36
|
+
try {
|
|
37
|
+
const propertyPath = property.split('.');
|
|
38
|
+
let currentValue = object;
|
|
39
|
+
|
|
40
|
+
for (let j = 0; j < propertyPath.length; j++) {
|
|
41
|
+
let prop = propertyPath[j];
|
|
42
|
+
|
|
43
|
+
currentValue = readSignal(currentValue);
|
|
44
|
+
|
|
45
|
+
if (j === 0) {
|
|
46
|
+
if (prop === 'hp') prop = 'hpSignal';
|
|
47
|
+
if (prop === 'sp') prop = 'spSignal';
|
|
48
|
+
if (prop === 'param') prop = '_param';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (currentValue && typeof currentValue === 'object' && prop in currentValue) {
|
|
52
|
+
currentValue = currentValue[prop];
|
|
53
|
+
} else {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return readSignal(currentValue);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const resolveDynamicValue = (value: any, object?: any): any => {
|
|
65
|
+
value = readSignal(value);
|
|
66
|
+
|
|
67
|
+
if (typeof value !== 'string') {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return value.replace(DYNAMIC_VALUE_PATTERN, (fullMatch, property) => {
|
|
72
|
+
const propertyValue = getDynamicValue(property, object);
|
|
73
|
+
if (propertyValue == null && property.startsWith('$')) return fullMatch;
|
|
74
|
+
return propertyValue != null ? String(propertyValue) : '';
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const resolveDynamicProp = (value: any, object?: any): any => {
|
|
79
|
+
if (Array.isArray(value) || (value && typeof value === 'object' && typeof value !== 'function')) {
|
|
80
|
+
return computed(() => resolveDynamicSnapshot(value, object));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof value === 'function' || hasDynamicValue(value)) {
|
|
84
|
+
return computed(() => resolveDynamicValue(value, object));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return value;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const resolveDynamicProps = (props: any, object?: any): any => {
|
|
91
|
+
props = readSignal(props);
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(props)) {
|
|
94
|
+
return computed(() => resolveDynamicSnapshot(props, object));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (props && typeof props === 'object') {
|
|
98
|
+
return Object.fromEntries(
|
|
99
|
+
Object.entries(props).map(([key, value]) => [key, resolveDynamicProp(value, object)])
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return resolveDynamicProp(props, object);
|
|
104
|
+
};
|
|
105
|
+
|
|
9
106
|
export const parseDynamicValue = (value: any, object?: any) => {
|
|
10
107
|
if (typeof value !== 'string') {
|
|
11
108
|
return computed(() => String(value ?? ''));
|
|
12
109
|
}
|
|
13
110
|
|
|
14
111
|
// Find all dynamic references like {propertyName}
|
|
15
|
-
const pattern = /\{([^}]+)\}/g;
|
|
16
112
|
const matches: MatchResult[] = [];
|
|
17
113
|
let match;
|
|
18
|
-
|
|
19
|
-
|
|
114
|
+
|
|
115
|
+
DYNAMIC_VALUE_PATTERN.lastIndex = 0;
|
|
116
|
+
while ((match = DYNAMIC_VALUE_PATTERN.exec(value)) !== null) {
|
|
20
117
|
matches.push({
|
|
21
118
|
property: match[1],
|
|
22
119
|
fullMatch: match[0],
|
|
@@ -37,40 +134,8 @@ export const parseDynamicValue = (value: any, object?: any) => {
|
|
|
37
134
|
for (let i = matches.length - 1; i >= 0; i--) {
|
|
38
135
|
const { property, fullMatch } = matches[i];
|
|
39
136
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let propertyValue = '';
|
|
43
|
-
try {
|
|
44
|
-
const propertyPath = property.split('.');
|
|
45
|
-
let currentValue = object;
|
|
46
|
-
|
|
47
|
-
for (let j = 0; j < propertyPath.length; j++) {
|
|
48
|
-
const prop = propertyPath[j];
|
|
49
|
-
|
|
50
|
-
// Check if currentValue is a signal (function) and call it
|
|
51
|
-
if (typeof currentValue === 'function') {
|
|
52
|
-
currentValue = currentValue();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Access the property
|
|
56
|
-
if (currentValue && typeof currentValue === 'object' && prop in currentValue) {
|
|
57
|
-
currentValue = currentValue[prop];
|
|
58
|
-
} else {
|
|
59
|
-
currentValue = undefined;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// If the final value is a signal, call it
|
|
65
|
-
if (typeof currentValue === 'function') {
|
|
66
|
-
currentValue = currentValue();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
propertyValue = currentValue != null ? String(currentValue) : '';
|
|
70
|
-
} catch (error) {
|
|
71
|
-
// If property doesn't exist or can't be accessed, use empty string
|
|
72
|
-
propertyValue = '';
|
|
73
|
-
}
|
|
137
|
+
const currentValue = getDynamicValue(property, object);
|
|
138
|
+
const propertyValue = currentValue != null ? String(currentValue) : property.startsWith('$') ? fullMatch : '';
|
|
74
139
|
|
|
75
140
|
result = result.replace(fullMatch, propertyValue);
|
|
76
141
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { getShapeBox, getShapePointBounds, translatePolygonPoints } from "./shape-utils";
|
|
3
|
+
|
|
4
|
+
describe("shape utilities", () => {
|
|
5
|
+
test("normalizes rectangles and circles around their rendered bounds", () => {
|
|
6
|
+
expect(getShapeBox({ type: "rect", width: 32, height: 32 })).toEqual({
|
|
7
|
+
width: 32,
|
|
8
|
+
height: 32,
|
|
9
|
+
offsetX: 0,
|
|
10
|
+
offsetY: 0
|
|
11
|
+
});
|
|
12
|
+
expect(getShapeBox({ type: "circle", radius: 8 })).toEqual({
|
|
13
|
+
width: 16,
|
|
14
|
+
height: 16,
|
|
15
|
+
offsetX: 0,
|
|
16
|
+
offsetY: 0
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("normalizes line bounds with negative coordinates", () => {
|
|
21
|
+
expect(getShapeBox({ type: "line", x1: -4, y1: 6, x2: 12, y2: -2 })).toEqual({
|
|
22
|
+
width: 16,
|
|
23
|
+
height: 8,
|
|
24
|
+
offsetX: 4,
|
|
25
|
+
offsetY: 2
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("normalizes polygon bounds and translated points", () => {
|
|
30
|
+
const box = getShapeBox({ type: "polygon", points: [-5, 2, 15, 2, 5, 12] });
|
|
31
|
+
|
|
32
|
+
expect(getShapePointBounds([-5, 2, 15, 2, 5, 12])).toEqual({
|
|
33
|
+
minX: -5,
|
|
34
|
+
minY: 2,
|
|
35
|
+
maxX: 15,
|
|
36
|
+
maxY: 12
|
|
37
|
+
});
|
|
38
|
+
expect(box).toEqual({
|
|
39
|
+
width: 20,
|
|
40
|
+
height: 10,
|
|
41
|
+
offsetX: 5,
|
|
42
|
+
offsetY: -2
|
|
43
|
+
});
|
|
44
|
+
expect(translatePolygonPoints([-5, 2, 15, 2, 5, 12], box)).toEqual([0, 0, 20, 0, 10, 10]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const defaultToNumber = (value: any, fallback = 0) => {
|
|
2
|
+
const num = typeof value === 'number' ? value : parseFloat(value);
|
|
3
|
+
return Number.isFinite(num) ? num : fallback;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function getShapePointBounds(points: any[] = [], toNumber = defaultToNumber) {
|
|
7
|
+
if (!Array.isArray(points) || points.length < 2) {
|
|
8
|
+
return { minX: 0, minY: 0, maxX: 1, maxY: 1 };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let minX = Infinity;
|
|
12
|
+
let minY = Infinity;
|
|
13
|
+
let maxX = -Infinity;
|
|
14
|
+
let maxY = -Infinity;
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
17
|
+
const x = toNumber(points[i], 0);
|
|
18
|
+
const y = toNumber(points[i + 1], 0);
|
|
19
|
+
minX = Math.min(minX, x);
|
|
20
|
+
minY = Math.min(minY, y);
|
|
21
|
+
maxX = Math.max(maxX, x);
|
|
22
|
+
maxY = Math.max(maxY, y);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { minX, minY, maxX, maxY };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getShapeBox(cfg: any, toNumber = defaultToNumber) {
|
|
29
|
+
if (cfg.type === 'circle') {
|
|
30
|
+
return { width: cfg.radius * 2, height: cfg.radius * 2, offsetX: 0, offsetY: 0 };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (cfg.type === 'line') {
|
|
34
|
+
const minX = Math.min(cfg.x1, cfg.x2);
|
|
35
|
+
const minY = Math.min(cfg.y1, cfg.y2);
|
|
36
|
+
const maxX = Math.max(cfg.x1, cfg.x2);
|
|
37
|
+
const maxY = Math.max(cfg.y1, cfg.y2);
|
|
38
|
+
return {
|
|
39
|
+
width: Math.max(1, maxX - minX),
|
|
40
|
+
height: Math.max(1, maxY - minY),
|
|
41
|
+
offsetX: -minX,
|
|
42
|
+
offsetY: -minY
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (cfg.type === 'polygon') {
|
|
47
|
+
const bounds = getShapePointBounds(cfg.points, toNumber);
|
|
48
|
+
return {
|
|
49
|
+
width: Math.max(1, bounds.maxX - bounds.minX),
|
|
50
|
+
height: Math.max(1, bounds.maxY - bounds.minY),
|
|
51
|
+
offsetX: -bounds.minX,
|
|
52
|
+
offsetY: -bounds.minY
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { width: cfg.width, height: cfg.height, offsetX: 0, offsetY: 0 };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function translatePolygonPoints(points: any[] = [], box: { offsetX: number; offsetY: number }, toNumber = defaultToNumber) {
|
|
60
|
+
return points.map((point, index) => toNumber(point, 0) + (index % 2 === 0 ? box.offsetX : box.offsetY));
|
|
61
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<Container width={shapeWidth} height={shapeHeight} minWidth={shapeWidth} minHeight={shapeHeight}>
|
|
2
|
+
<Graphics width={shapeWidth} height={shapeHeight} draw={drawShape} />
|
|
3
|
+
</Container>
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
import { computed } from "canvasengine";
|
|
7
|
+
import { resolveDynamicValue } from "./parse-value";
|
|
8
|
+
import { getShapeBox, translatePolygonPoints } from "./shape-utils";
|
|
9
|
+
|
|
10
|
+
const props = defineProps();
|
|
11
|
+
const { object } = props;
|
|
12
|
+
|
|
13
|
+
const read = (prop, fallback) => prop ? prop() : fallback;
|
|
14
|
+
|
|
15
|
+
const toNumber = (value, fallback = 0) => {
|
|
16
|
+
const resolved = resolveDynamicValue(value, object);
|
|
17
|
+
const num = typeof resolved === 'number' ? resolved : parseFloat(resolved);
|
|
18
|
+
return Number.isFinite(num) ? num : fallback;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const toColor = (value, fallback) => {
|
|
22
|
+
const resolved = resolveDynamicValue(value, object);
|
|
23
|
+
if (typeof resolved === 'number') return resolved;
|
|
24
|
+
if (typeof resolved === 'string' && resolved.startsWith('#')) {
|
|
25
|
+
return parseInt(resolved.slice(1), 16);
|
|
26
|
+
}
|
|
27
|
+
return resolved ?? fallback;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const config = computed(() => ({
|
|
31
|
+
type: read(props.type, 'rectangle'),
|
|
32
|
+
fill: toColor(read(props.fill, '#ffffff'), 0xffffff),
|
|
33
|
+
radius: toNumber(read(props.radius, 8), 8),
|
|
34
|
+
width: toNumber(read(props.width, 16), 16),
|
|
35
|
+
height: toNumber(read(props.height, 16), 16),
|
|
36
|
+
x1: toNumber(read(props.x1, 0), 0),
|
|
37
|
+
y1: toNumber(read(props.y1, 0), 0),
|
|
38
|
+
x2: toNumber(read(props.x2, 16), 16),
|
|
39
|
+
y2: toNumber(read(props.y2, 0), 0),
|
|
40
|
+
opacity: Math.max(0, Math.min(1, toNumber(read(props.opacity, 1), 1))),
|
|
41
|
+
points: read(props.points, []),
|
|
42
|
+
line: read(props.line, null)
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
const shapeBox = computed(() => getShapeBox(config(), toNumber));
|
|
46
|
+
|
|
47
|
+
const shapeWidth = computed(() => shapeBox().width);
|
|
48
|
+
const shapeHeight = computed(() => shapeBox().height);
|
|
49
|
+
|
|
50
|
+
const drawShape = (g) => {
|
|
51
|
+
const cfg = config();
|
|
52
|
+
const box = shapeBox();
|
|
53
|
+
|
|
54
|
+
if (cfg.type === 'circle') {
|
|
55
|
+
g.circle(cfg.radius, cfg.radius, cfg.radius);
|
|
56
|
+
} else if (cfg.type === 'ellipse') {
|
|
57
|
+
g.ellipse(box.width / 2, box.height / 2, box.width / 2, box.height / 2);
|
|
58
|
+
} else if (cfg.type === 'line') {
|
|
59
|
+
g.moveTo(cfg.x1 + box.offsetX, cfg.y1 + box.offsetY);
|
|
60
|
+
g.lineTo(cfg.x2 + box.offsetX, cfg.y2 + box.offsetY);
|
|
61
|
+
} else if (cfg.type === 'polygon' && Array.isArray(cfg.points)) {
|
|
62
|
+
g.poly(translatePolygonPoints(cfg.points, box, toNumber));
|
|
63
|
+
} else if (cfg.type === 'rounded-rectangle') {
|
|
64
|
+
g.roundRect(0, 0, box.width, box.height, toNumber(read(props.radius, 4), 4));
|
|
65
|
+
} else {
|
|
66
|
+
g.rect(0, 0, box.width, box.height);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (cfg.type === 'line') {
|
|
70
|
+
const line = cfg.line ?? {};
|
|
71
|
+
g.stroke({
|
|
72
|
+
color: toColor(line.color, cfg.fill),
|
|
73
|
+
width: toNumber(line.width, 1),
|
|
74
|
+
alpha: toNumber(line.alpha, cfg.opacity)
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
g.fill({ color: cfg.fill, alpha: cfg.opacity });
|
|
80
|
+
|
|
81
|
+
if (cfg.line) {
|
|
82
|
+
g.stroke({
|
|
83
|
+
color: toColor(cfg.line.color, cfg.fill),
|
|
84
|
+
width: toNumber(cfg.line.width, 1),
|
|
85
|
+
alpha: toNumber(cfg.line.alpha, cfg.opacity)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
</script>
|
|
@@ -1,157 +1,38 @@
|
|
|
1
|
-
<Text text={
|
|
1
|
+
<Text text={textValue} color={textColor} size={textSize} fontFamily={textFontFamily} style={textStyle} />
|
|
2
2
|
|
|
3
3
|
<script>
|
|
4
4
|
import { computed } from "canvasengine";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveDynamicValue } from "./parse-value";
|
|
6
6
|
|
|
7
|
-
const { object } = defineProps();
|
|
8
|
-
const component = object._component;
|
|
7
|
+
const { object, value, style } = defineProps();
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
* Parses a numeric style value that can be a number or a string
|
|
12
|
-
*
|
|
13
|
-
* If the value is a string, it may contain dynamic references like {hp}
|
|
14
|
-
* which need to be parsed using parseDynamicValue. If it's a number,
|
|
15
|
-
* it's returned as-is wrapped in a computed.
|
|
16
|
-
*
|
|
17
|
-
* @param value - Numeric value (number or string)
|
|
18
|
-
* @param object - Object to resolve dynamic references from
|
|
19
|
-
* @returns Computed signal with the numeric value
|
|
20
|
-
*/
|
|
21
|
-
const parseNumericStyleValue = (value, object) => {
|
|
22
|
-
if (value === undefined || value === null) {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (typeof value === 'number') {
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (typeof value === 'string') {
|
|
31
|
-
// Check if it contains dynamic references
|
|
32
|
-
if (value.includes('{')) {
|
|
33
|
-
// Parse dynamic value and convert to number
|
|
34
|
-
const parsed = parseDynamicValue(value, object);
|
|
35
|
-
return computed(() => {
|
|
36
|
-
const str = parsed();
|
|
37
|
-
const num = parseFloat(str);
|
|
38
|
-
return isNaN(num) ? 0 : num;
|
|
39
|
-
});
|
|
40
|
-
} else {
|
|
41
|
-
// Simple string number, convert directly
|
|
42
|
-
const num = parseFloat(value);
|
|
43
|
-
return isNaN(num) ? undefined : num;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return value;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Maps component style properties to Canvas Engine Text component props
|
|
52
|
-
*
|
|
53
|
-
* Converts TextComponentOptions from the server to the format expected
|
|
54
|
-
* by the Canvas Engine Text component. Supports all text styling properties
|
|
55
|
-
* including fill, fontSize, fontFamily, fontStyle, fontWeight, stroke,
|
|
56
|
-
* opacity, wordWrap, and align. Also supports dynamic values (number | string)
|
|
57
|
-
* for numeric properties like fontSize and opacity.
|
|
58
|
-
*
|
|
59
|
-
* @param component - Component definition with style property
|
|
60
|
-
* @returns Object with Text component props
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* // Component with style
|
|
65
|
-
* const component = {
|
|
66
|
-
* style: {
|
|
67
|
-
* fill: '#000000',
|
|
68
|
-
* fontSize: 20,
|
|
69
|
-
* fontFamily: 'Arial',
|
|
70
|
-
* fontWeight: 'bold'
|
|
71
|
-
* }
|
|
72
|
-
* };
|
|
73
|
-
*
|
|
74
|
-
* const props = getComponentStyle(component);
|
|
75
|
-
* // Returns: { color: '#000000', size: 20, fontFamily: 'Arial', style: { fontWeight: 'bold' } }
|
|
76
|
-
*
|
|
77
|
-
* // Component with dynamic fontSize
|
|
78
|
-
* const component2 = {
|
|
79
|
-
* style: {
|
|
80
|
-
* fill: '#000000',
|
|
81
|
-
* fontSize: '{hp}', // Will be resolved from object.hp
|
|
82
|
-
* opacity: '0.8'
|
|
83
|
-
* }
|
|
84
|
-
* };
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
const getComponentStyle = (component) => {
|
|
88
|
-
if (!component.style) {
|
|
89
|
-
return {};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const style = component.style;
|
|
93
|
-
const result = {};
|
|
94
|
-
|
|
95
|
-
// Map fill to color (shortcut property)
|
|
96
|
-
// fill can be a string (hex color) or a dynamic string
|
|
97
|
-
if (style.fill !== undefined) {
|
|
98
|
-
if (typeof style.fill === 'string' && style.fill.includes('{')) {
|
|
99
|
-
result.color = parseDynamicValue(style.fill, object);
|
|
100
|
-
} else {
|
|
101
|
-
result.color = style.fill;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
9
|
+
const read = (prop, fallback) => prop ? prop() : fallback;
|
|
104
10
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
if (fontSizeValue !== undefined) {
|
|
110
|
-
result.size = fontSizeValue;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
11
|
+
const parseNumericStyleValue = (value, object) => {
|
|
12
|
+
value = resolveDynamicValue(value, object);
|
|
13
|
+
if (value === undefined || value === null) return undefined;
|
|
14
|
+
if (typeof value === 'number') return value;
|
|
113
15
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
result.fontFamily = parseDynamicValue(style.fontFamily, object);
|
|
118
|
-
} else {
|
|
119
|
-
result.fontFamily = style.fontFamily;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
16
|
+
const num = parseFloat(value);
|
|
17
|
+
return isNaN(num) ? undefined : num;
|
|
18
|
+
};
|
|
122
19
|
|
|
123
|
-
|
|
20
|
+
const getTextStyle = (style) => {
|
|
21
|
+
if (!style) return {};
|
|
124
22
|
const textStyle = {};
|
|
125
23
|
|
|
126
|
-
// Font style properties
|
|
127
24
|
if (style.fontStyle !== undefined) {
|
|
128
|
-
|
|
129
|
-
textStyle.fontStyle = parseDynamicValue(style.fontStyle, object);
|
|
130
|
-
} else {
|
|
131
|
-
textStyle.fontStyle = style.fontStyle;
|
|
132
|
-
}
|
|
25
|
+
textStyle.fontStyle = resolveDynamicValue(style.fontStyle, object);
|
|
133
26
|
}
|
|
134
27
|
|
|
135
28
|
if (style.fontWeight !== undefined) {
|
|
136
|
-
|
|
137
|
-
textStyle.fontWeight = parseDynamicValue(style.fontWeight, object);
|
|
138
|
-
} else if (typeof style.fontWeight === 'number') {
|
|
139
|
-
textStyle.fontWeight = style.fontWeight;
|
|
140
|
-
} else {
|
|
141
|
-
textStyle.fontWeight = style.fontWeight;
|
|
142
|
-
}
|
|
29
|
+
textStyle.fontWeight = resolveDynamicValue(style.fontWeight, object);
|
|
143
30
|
}
|
|
144
31
|
|
|
145
|
-
// Stroke properties
|
|
146
32
|
if (style.stroke !== undefined) {
|
|
147
|
-
|
|
148
|
-
textStyle.stroke = parseDynamicValue(style.stroke, object);
|
|
149
|
-
} else {
|
|
150
|
-
textStyle.stroke = style.stroke;
|
|
151
|
-
}
|
|
33
|
+
textStyle.stroke = resolveDynamicValue(style.stroke, object);
|
|
152
34
|
}
|
|
153
35
|
|
|
154
|
-
// Opacity (can be number or string)
|
|
155
36
|
if (style.opacity !== undefined) {
|
|
156
37
|
const opacityValue = parseNumericStyleValue(style.opacity, object);
|
|
157
38
|
if (opacityValue !== undefined) {
|
|
@@ -159,25 +40,29 @@ const getComponentStyle = (component) => {
|
|
|
159
40
|
}
|
|
160
41
|
}
|
|
161
42
|
|
|
162
|
-
// Word wrap
|
|
163
43
|
if (style.wordWrap !== undefined) {
|
|
164
44
|
textStyle.wordWrap = style.wordWrap;
|
|
165
45
|
}
|
|
166
46
|
|
|
167
|
-
// Text alignment
|
|
168
47
|
if (style.align !== undefined) {
|
|
169
|
-
|
|
170
|
-
textStyle.align = parseDynamicValue(style.align, object);
|
|
171
|
-
} else {
|
|
172
|
-
textStyle.align = style.align;
|
|
173
|
-
}
|
|
48
|
+
textStyle.align = resolveDynamicValue(style.align, object);
|
|
174
49
|
}
|
|
175
50
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
result.style = textStyle;
|
|
179
|
-
}
|
|
51
|
+
return textStyle;
|
|
52
|
+
};
|
|
180
53
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
54
|
+
const textValue = computed(() => String(resolveDynamicValue(read(value, ''), object) ?? ''));
|
|
55
|
+
const textColor = computed(() => {
|
|
56
|
+
const currentStyle = read(style, {});
|
|
57
|
+
return currentStyle.fill !== undefined ? resolveDynamicValue(currentStyle.fill, object) : undefined;
|
|
58
|
+
});
|
|
59
|
+
const textSize = computed(() => {
|
|
60
|
+
const currentStyle = read(style, {});
|
|
61
|
+
return currentStyle.fontSize !== undefined ? parseNumericStyleValue(currentStyle.fontSize, object) : undefined;
|
|
62
|
+
});
|
|
63
|
+
const textFontFamily = computed(() => {
|
|
64
|
+
const currentStyle = read(style, {});
|
|
65
|
+
return currentStyle.fontFamily !== undefined ? resolveDynamicValue(currentStyle.fontFamily, object) : undefined;
|
|
66
|
+
});
|
|
67
|
+
const textStyle = computed(() => getTextStyle(read(style, {})));
|
|
68
|
+
</script>
|