@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.
Files changed (158) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/Game/AnimationManager.js.map +1 -1
  3. package/dist/Game/Event.js.map +1 -1
  4. package/dist/Game/Map.d.ts +9 -1
  5. package/dist/Game/Map.js +63 -5
  6. package/dist/Game/Map.js.map +1 -1
  7. package/dist/Game/Object.d.ts +17 -9
  8. package/dist/Game/Object.js +1 -12
  9. package/dist/Game/Object.js.map +1 -1
  10. package/dist/Game/Player.js.map +1 -1
  11. package/dist/Gui/Gui.d.ts +17 -4
  12. package/dist/Gui/Gui.js +64 -34
  13. package/dist/Gui/Gui.js.map +1 -1
  14. package/dist/Gui/Gui.spec.d.ts +1 -0
  15. package/dist/Gui/NotificationManager.js.map +1 -1
  16. package/dist/Resource.js +1 -1
  17. package/dist/Resource.js.map +1 -1
  18. package/dist/RpgClient.d.ts +35 -2
  19. package/dist/RpgClientEngine.d.ts +41 -5
  20. package/dist/RpgClientEngine.js +50 -5
  21. package/dist/RpgClientEngine.js.map +1 -1
  22. package/dist/Sound.js.map +1 -1
  23. package/dist/_virtual/{_@oxc-project_runtime@0.128.0 → _@oxc-project_runtime@0.130.0}/helpers/decorate.js +1 -1
  24. package/dist/_virtual/{_@oxc-project_runtime@0.128.0 → _@oxc-project_runtime@0.130.0}/helpers/decorateMetadata.js +1 -1
  25. package/dist/components/animations/animation.ce.js.map +1 -1
  26. package/dist/components/animations/hit.ce.js.map +1 -1
  27. package/dist/components/animations/index.js.map +1 -1
  28. package/dist/components/character.ce.js +259 -5
  29. package/dist/components/character.ce.js.map +1 -1
  30. package/dist/components/dynamics/bar.ce.js +96 -0
  31. package/dist/components/dynamics/bar.ce.js.map +1 -0
  32. package/dist/components/dynamics/image.ce.js +23 -0
  33. package/dist/components/dynamics/image.ce.js.map +1 -0
  34. package/dist/components/dynamics/parse-value.d.ts +3 -0
  35. package/dist/components/dynamics/parse-value.js +51 -35
  36. package/dist/components/dynamics/parse-value.js.map +1 -1
  37. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  38. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  39. package/dist/components/dynamics/shape-utils.js +73 -0
  40. package/dist/components/dynamics/shape-utils.js.map +1 -0
  41. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  42. package/dist/components/dynamics/shape.ce.js +83 -0
  43. package/dist/components/dynamics/shape.ce.js.map +1 -0
  44. package/dist/components/dynamics/text.ce.js +28 -41
  45. package/dist/components/dynamics/text.ce.js.map +1 -1
  46. package/dist/components/gui/box.ce.js.map +1 -1
  47. package/dist/components/gui/dialogbox/index.ce.js +3 -3
  48. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  49. package/dist/components/gui/gameover.ce.js +1 -1
  50. package/dist/components/gui/gameover.ce.js.map +1 -1
  51. package/dist/components/gui/hud/hud.ce.js +1 -1
  52. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  53. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  54. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  55. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  56. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  57. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  58. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  59. package/dist/components/gui/mobile/index.js.map +1 -1
  60. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  61. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  62. package/dist/components/gui/save-load.ce.js.map +1 -1
  63. package/dist/components/gui/shop/shop.ce.js +1 -1
  64. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  65. package/dist/components/gui/title-screen.ce.js +2 -2
  66. package/dist/components/gui/title-screen.ce.js.map +1 -1
  67. package/dist/components/player-components-utils.d.ts +67 -0
  68. package/dist/components/player-components-utils.js +162 -0
  69. package/dist/components/player-components-utils.js.map +1 -0
  70. package/dist/components/player-components-utils.spec.d.ts +1 -0
  71. package/dist/components/player-components.ce.js +188 -0
  72. package/dist/components/player-components.ce.js.map +1 -0
  73. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  74. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  75. package/dist/components/scenes/canvas.ce.js +147 -4
  76. package/dist/components/scenes/canvas.ce.js.map +1 -1
  77. package/dist/components/scenes/draw-map.ce.js +2 -8
  78. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  79. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  80. package/dist/core/inject.js +1 -1
  81. package/dist/core/inject.js.map +1 -1
  82. package/dist/core/setup.js +1 -1
  83. package/dist/core/setup.js.map +1 -1
  84. package/dist/decorators/spritesheet.d.ts +1 -0
  85. package/dist/decorators/spritesheet.js +11 -0
  86. package/dist/decorators/spritesheet.js.map +1 -0
  87. package/dist/index.d.ts +1 -0
  88. package/dist/index.js +3 -2
  89. package/dist/module.js +4 -1
  90. package/dist/module.js.map +1 -1
  91. package/dist/node_modules/.pnpm/{@signe_di@2.10.0 → @signe_di@3.0.1}/node_modules/@signe/di/dist/index.js +1 -1
  92. package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
  93. package/dist/node_modules/.pnpm/{@signe_reactive@2.9.2 → @signe_reactive@3.0.1}/node_modules/@signe/reactive/dist/index.js +15 -3
  94. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
  95. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  96. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
  97. package/dist/node_modules/.pnpm/{@signe_room@2.10.0 → @signe_room@3.0.1}/node_modules/@signe/room/dist/index.js +124 -39
  98. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
  99. 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
  100. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  101. package/dist/node_modules/.pnpm/{@signe_sync@2.10.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/index.js +36 -13
  102. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
  103. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
  104. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
  105. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
  106. package/dist/presets/animation.js.map +1 -1
  107. package/dist/presets/faceset.js.map +1 -1
  108. package/dist/presets/icon.js.map +1 -1
  109. package/dist/presets/index.js.map +1 -1
  110. package/dist/presets/lpc.js.map +1 -1
  111. package/dist/presets/rmspritesheet.js.map +1 -1
  112. package/dist/services/AbstractSocket.js.map +1 -1
  113. package/dist/services/keyboardControls.js.map +1 -1
  114. package/dist/services/loadMap.d.ts +6 -0
  115. package/dist/services/loadMap.js +1 -1
  116. package/dist/services/loadMap.js.map +1 -1
  117. package/dist/services/mmorpg.js +7 -3
  118. package/dist/services/mmorpg.js.map +1 -1
  119. package/dist/services/save.js.map +1 -1
  120. package/dist/services/standalone.js +1 -1
  121. package/dist/services/standalone.js.map +1 -1
  122. package/dist/utils/getEntityProp.js.map +1 -1
  123. package/package.json +10 -10
  124. package/src/Game/Map.ts +91 -2
  125. package/src/Game/Object.ts +22 -35
  126. package/src/Gui/Gui.spec.ts +273 -0
  127. package/src/Gui/Gui.ts +105 -50
  128. package/src/Resource.ts +1 -2
  129. package/src/RpgClient.ts +36 -2
  130. package/src/RpgClientEngine.ts +74 -11
  131. package/src/components/character.ce +318 -9
  132. package/src/components/dynamics/bar.ce +87 -0
  133. package/src/components/dynamics/image.ce +20 -0
  134. package/src/components/dynamics/parse-value.spec.ts +41 -0
  135. package/src/components/dynamics/parse-value.ts +102 -37
  136. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  137. package/src/components/dynamics/shape-utils.ts +61 -0
  138. package/src/components/dynamics/shape.ce +89 -0
  139. package/src/components/dynamics/text.ce +34 -149
  140. package/src/components/player-components-utils.spec.ts +109 -0
  141. package/src/components/player-components-utils.ts +205 -0
  142. package/src/components/player-components.ce +221 -0
  143. package/src/components/scenes/canvas.ce +165 -6
  144. package/src/components/scenes/draw-map.ce +2 -15
  145. package/src/components/scenes/event-layer.ce +1 -2
  146. package/src/core/setup.ts +2 -2
  147. package/src/decorators/spritesheet.ts +8 -0
  148. package/src/index.ts +1 -0
  149. package/src/module.ts +5 -1
  150. package/src/services/loadMap.ts +2 -0
  151. package/src/services/mmorpg.ts +8 -2
  152. package/dist/node_modules/.pnpm/@signe_di@2.10.0/node_modules/@signe/di/dist/index.js.map +0 -1
  153. package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js +0 -45
  154. package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  155. package/dist/node_modules/.pnpm/@signe_reactive@2.9.2/node_modules/@signe/reactive/dist/index.js.map +0 -1
  156. package/dist/node_modules/.pnpm/@signe_room@2.10.0/node_modules/@signe/room/dist/index.js.map +0 -1
  157. package/dist/node_modules/.pnpm/@signe_sync@2.10.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  158. 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
- while ((match = pattern.exec(value)) !== null) {
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
- // Try to access the property from the object
41
- // Support nested properties like {param.maxHp}
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={parseDynamicValue(component.value, object)} ...getComponentStyle(component) />
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 { parseDynamicValue } from "./parse-value";
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
- // Map fontSize to size (shortcut property)
106
- // fontSize can be number or string (with dynamic references)
107
- if (style.fontSize !== undefined) {
108
- const fontSizeValue = parseNumericStyleValue(style.fontSize, object);
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
- // Map fontFamily (shortcut property)
115
- if (style.fontFamily !== undefined) {
116
- if (typeof style.fontFamily === 'string' && style.fontFamily.includes('{')) {
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
- // Build style object for PixiJS Text properties
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
- if (typeof style.fontStyle === 'string' && style.fontStyle.includes('{')) {
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
- if (typeof style.fontWeight === 'string' && style.fontWeight.includes('{')) {
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
- if (typeof style.stroke === 'string' && style.stroke.includes('{')) {
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
- if (typeof style.align === 'string' && style.align.includes('{')) {
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
- // Only add style prop if there are style properties
177
- if (Object.keys(textStyle).length > 0) {
178
- result.style = textStyle;
179
- }
51
+ return textStyle;
52
+ };
180
53
 
181
- return result;
182
- }
183
- </script>
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>