@things-factory/board-ai 10.0.0-beta.108
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/PLAN-styling-tools.md +1253 -0
- package/client/components/board-ai-chat.test.ts +149 -0
- package/client/components/board-ai-chat.ts +2574 -0
- package/client/components/chat-echo-dedup.test.ts +116 -0
- package/client/components/chat-echo-dedup.ts +89 -0
- package/client/components/chat-input-builder.ts +47 -0
- package/client/components/default-catalog-entries.test.ts +59 -0
- package/client/components/default-catalog-entries.ts +100 -0
- package/client/components/default-slash-templates.test.ts +55 -0
- package/client/components/default-slash-templates.ts +129 -0
- package/client/components/markdown.test.ts +518 -0
- package/client/components/markdown.ts +508 -0
- package/client/components/mention-popup-helpers.ts +433 -0
- package/client/components/mention-popup.test.ts +588 -0
- package/client/components/mention-popup.ts +238 -0
- package/client/index.ts +11 -0
- package/client/tsconfig.json +13 -0
- package/client/utils/board-edit-patch.ts +374 -0
- package/config/config.development.js +43 -0
- package/config/config.production.js +15 -0
- package/dist-client/client/components/board-ai-chat.d.ts +277 -0
- package/dist-client/client/components/board-ai-chat.js +2444 -0
- package/dist-client/client/components/board-ai-chat.js.map +1 -0
- package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
- package/dist-client/client/components/board-ai-chat.test.js +138 -0
- package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
- package/dist-client/client/components/chat-echo-dedup.js +58 -0
- package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
- package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
- package/dist-client/client/components/chat-input-builder.d.ts +39 -0
- package/dist-client/client/components/chat-input-builder.js +26 -0
- package/dist-client/client/components/chat-input-builder.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
- package/dist-client/client/components/default-catalog-entries.js +86 -0
- package/dist-client/client/components/default-catalog-entries.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.test.d.ts +1 -0
- package/dist-client/client/components/default-catalog-entries.test.js +52 -0
- package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.d.ts +17 -0
- package/dist-client/client/components/default-slash-templates.js +113 -0
- package/dist-client/client/components/default-slash-templates.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
- package/dist-client/client/components/default-slash-templates.test.js +51 -0
- package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
- package/dist-client/client/components/markdown.d.ts +60 -0
- package/dist-client/client/components/markdown.js +422 -0
- package/dist-client/client/components/markdown.js.map +1 -0
- package/dist-client/client/components/markdown.test.d.ts +1 -0
- package/dist-client/client/components/markdown.test.js +418 -0
- package/dist-client/client/components/markdown.test.js.map +1 -0
- package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
- package/dist-client/client/components/mention-popup-helpers.js +330 -0
- package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
- package/dist-client/client/components/mention-popup.d.ts +35 -0
- package/dist-client/client/components/mention-popup.js +223 -0
- package/dist-client/client/components/mention-popup.js.map +1 -0
- package/dist-client/client/components/mention-popup.test.d.ts +1 -0
- package/dist-client/client/components/mention-popup.test.js +474 -0
- package/dist-client/client/components/mention-popup.test.js.map +1 -0
- package/dist-client/client/index.d.ts +11 -0
- package/dist-client/client/index.js +12 -0
- package/dist-client/client/index.js.map +1 -0
- package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
- package/dist-client/client/utils/board-edit-patch.js +284 -0
- package/dist-client/client/utils/board-edit-patch.js.map +1 -0
- package/dist-client/server/service/agentic-loop.d.ts +87 -0
- package/dist-client/server/service/agentic-loop.js +276 -0
- package/dist-client/server/service/agentic-loop.js.map +1 -0
- package/dist-client/server/service/assistant.d.ts +84 -0
- package/dist-client/server/service/assistant.js +2220 -0
- package/dist-client/server/service/assistant.js.map +1 -0
- package/dist-client/server/service/catalog-resolver.d.ts +18 -0
- package/dist-client/server/service/catalog-resolver.js +93 -0
- package/dist-client/server/service/catalog-resolver.js.map +1 -0
- package/dist-client/server/service/component-tree.d.ts +68 -0
- package/dist-client/server/service/component-tree.js +68 -0
- package/dist-client/server/service/component-tree.js.map +1 -0
- package/dist-client/server/service/mention-resolver.d.ts +96 -0
- package/dist-client/server/service/mention-resolver.js +172 -0
- package/dist-client/server/service/mention-resolver.js.map +1 -0
- package/dist-client/server/service/prefab-registry.d.ts +73 -0
- package/dist-client/server/service/prefab-registry.js +252 -0
- package/dist-client/server/service/prefab-registry.js.map +1 -0
- package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
- package/dist-client/server/service/styling/clear-tools.js +50 -0
- package/dist-client/server/service/styling/clear-tools.js.map +1 -0
- package/dist-client/server/service/styling/color-utils.d.ts +43 -0
- package/dist-client/server/service/styling/color-utils.js +205 -0
- package/dist-client/server/service/styling/color-utils.js.map +1 -0
- package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
- package/dist-client/server/service/styling/copy-tools.js +78 -0
- package/dist-client/server/service/styling/copy-tools.js.map +1 -0
- package/dist-client/server/service/styling/effect-tools.d.ts +45 -0
- package/dist-client/server/service/styling/effect-tools.js +85 -0
- package/dist-client/server/service/styling/effect-tools.js.map +1 -0
- package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
- package/dist-client/server/service/styling/fill-tools.js +226 -0
- package/dist-client/server/service/styling/fill-tools.js.map +1 -0
- package/dist-client/server/service/styling/material-tools.d.ts +32 -0
- package/dist-client/server/service/styling/material-tools.js +88 -0
- package/dist-client/server/service/styling/material-tools.js.map +1 -0
- package/dist-client/server/service/styling/read-tools.d.ts +34 -0
- package/dist-client/server/service/styling/read-tools.js +42 -0
- package/dist-client/server/service/styling/read-tools.js.map +1 -0
- package/dist-client/server/service/styling/registry.d.ts +91 -0
- package/dist-client/server/service/styling/registry.js +345 -0
- package/dist-client/server/service/styling/registry.js.map +1 -0
- package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-client/server/service/styling/stroke-tools.js +94 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
- package/dist-client/server/service/styling/text-tools.d.ts +32 -0
- package/dist-client/server/service/styling/text-tools.js +101 -0
- package/dist-client/server/service/styling/text-tools.js.map +1 -0
- package/dist-client/server/service/types.d.ts +265 -0
- package/dist-client/server/service/types.js +2 -0
- package/dist-client/server/service/types.js.map +1 -0
- package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-client/server/service/validation/board-model-schema.js +402 -0
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
- package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
- package/dist-client/server/service/validation/tool-validation.js +193 -0
- package/dist-client/server/service/validation/tool-validation.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/index.d.ts +23 -0
- package/dist-server/index.js +27 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/agentic-loop.d.ts +87 -0
- package/dist-server/service/agentic-loop.js +280 -0
- package/dist-server/service/agentic-loop.js.map +1 -0
- package/dist-server/service/apply-patch.d.ts +47 -0
- package/dist-server/service/apply-patch.js +369 -0
- package/dist-server/service/apply-patch.js.map +1 -0
- package/dist-server/service/assistant.d.ts +84 -0
- package/dist-server/service/assistant.js +2237 -0
- package/dist-server/service/assistant.js.map +1 -0
- package/dist-server/service/board-ai-resolver.d.ts +54 -0
- package/dist-server/service/board-ai-resolver.js +409 -0
- package/dist-server/service/board-ai-resolver.js.map +1 -0
- package/dist-server/service/catalog-resolver.d.ts +18 -0
- package/dist-server/service/catalog-resolver.js +98 -0
- package/dist-server/service/catalog-resolver.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
- package/dist-server/service/chat-message/chat-message-publish.js +27 -0
- package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
- package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
- package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-message.d.ts +56 -0
- package/dist-server/service/chat-message/chat-message.js +149 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -0
- package/dist-server/service/chat-message/index.d.ts +5 -0
- package/dist-server/service/chat-message/index.js +12 -0
- package/dist-server/service/chat-message/index.js.map +1 -0
- package/dist-server/service/chat-session/chat-session.d.ts +31 -0
- package/dist-server/service/chat-session/chat-session.js +131 -0
- package/dist-server/service/chat-session/chat-session.js.map +1 -0
- package/dist-server/service/chat-session/index.d.ts +3 -0
- package/dist-server/service/chat-session/index.js +7 -0
- package/dist-server/service/chat-session/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
- package/dist-server/service/chat-session-participant/index.d.ts +4 -0
- package/dist-server/service/chat-session-participant/index.js +10 -0
- package/dist-server/service/chat-session-participant/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
- package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
- package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
- package/dist-server/service/chat-session-participant/presence.js +22 -0
- package/dist-server/service/chat-session-participant/presence.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +20 -0
- package/dist-server/service/chat-session-resolver.js +356 -0
- package/dist-server/service/chat-session-resolver.js.map +1 -0
- package/dist-server/service/component-tree.d.ts +68 -0
- package/dist-server/service/component-tree.js +73 -0
- package/dist-server/service/component-tree.js.map +1 -0
- package/dist-server/service/index.d.ts +20 -0
- package/dist-server/service/index.js +34 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/mention-marker.d.ts +78 -0
- package/dist-server/service/mention-marker.js +162 -0
- package/dist-server/service/mention-marker.js.map +1 -0
- package/dist-server/service/mention-resolver.d.ts +96 -0
- package/dist-server/service/mention-resolver.js +177 -0
- package/dist-server/service/mention-resolver.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.d.ts +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
- package/dist-server/service/patch-entry/index.d.ts +4 -0
- package/dist-server/service/patch-entry/index.js +12 -0
- package/dist-server/service/patch-entry/index.js.map +1 -0
- package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
- package/dist-server/service/patch-entry/patch-entry.js +96 -0
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
- package/dist-server/service/prefab-registry.d.ts +73 -0
- package/dist-server/service/prefab-registry.js +258 -0
- package/dist-server/service/prefab-registry.js.map +1 -0
- package/dist-server/service/styling/clear-tools.d.ts +15 -0
- package/dist-server/service/styling/clear-tools.js +53 -0
- package/dist-server/service/styling/clear-tools.js.map +1 -0
- package/dist-server/service/styling/color-utils.d.ts +43 -0
- package/dist-server/service/styling/color-utils.js +210 -0
- package/dist-server/service/styling/color-utils.js.map +1 -0
- package/dist-server/service/styling/copy-tools.d.ts +27 -0
- package/dist-server/service/styling/copy-tools.js +81 -0
- package/dist-server/service/styling/copy-tools.js.map +1 -0
- package/dist-server/service/styling/effect-tools.d.ts +45 -0
- package/dist-server/service/styling/effect-tools.js +90 -0
- package/dist-server/service/styling/effect-tools.js.map +1 -0
- package/dist-server/service/styling/fill-tools.d.ts +60 -0
- package/dist-server/service/styling/fill-tools.js +229 -0
- package/dist-server/service/styling/fill-tools.js.map +1 -0
- package/dist-server/service/styling/material-tools.d.ts +32 -0
- package/dist-server/service/styling/material-tools.js +92 -0
- package/dist-server/service/styling/material-tools.js.map +1 -0
- package/dist-server/service/styling/read-tools.d.ts +34 -0
- package/dist-server/service/styling/read-tools.js +45 -0
- package/dist-server/service/styling/read-tools.js.map +1 -0
- package/dist-server/service/styling/registry.d.ts +91 -0
- package/dist-server/service/styling/registry.js +352 -0
- package/dist-server/service/styling/registry.js.map +1 -0
- package/dist-server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-server/service/styling/stroke-tools.js +97 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -0
- package/dist-server/service/styling/text-tools.d.ts +32 -0
- package/dist-server/service/styling/text-tools.js +105 -0
- package/dist-server/service/styling/text-tools.js.map +1 -0
- package/dist-server/service/subscribe-auth.d.ts +14 -0
- package/dist-server/service/subscribe-auth.js +33 -0
- package/dist-server/service/subscribe-auth.js.map +1 -0
- package/dist-server/service/types.d.ts +265 -0
- package/dist-server/service/types.js +3 -0
- package/dist-server/service/types.js.map +1 -0
- package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-server/service/validation/board-model-schema.js +409 -0
- package/dist-server/service/validation/board-model-schema.js.map +1 -0
- package/dist-server/service/validation/tool-validation.d.ts +31 -0
- package/dist-server/service/validation/tool-validation.js +196 -0
- package/dist-server/service/validation/tool-validation.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +48 -0
- package/server/index.ts +23 -0
- package/server/service/agentic-loop.test.ts +705 -0
- package/server/service/agentic-loop.ts +431 -0
- package/server/service/apply-patch-drift.test.ts +829 -0
- package/server/service/apply-patch.test.ts +757 -0
- package/server/service/apply-patch.ts +411 -0
- package/server/service/assistant-integration.test.ts +922 -0
- package/server/service/assistant-llm-smoke.test.ts +296 -0
- package/server/service/assistant.test.ts +2034 -0
- package/server/service/assistant.ts +2378 -0
- package/server/service/board-ai-resolver.ts +407 -0
- package/server/service/catalog-resolver.test.ts +137 -0
- package/server/service/catalog-resolver.ts +102 -0
- package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
- package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
- package/server/service/chat-message/chat-message-broadcast.ts +27 -0
- package/server/service/chat-message/chat-message-publish.ts +22 -0
- package/server/service/chat-message/chat-message-subscription.ts +41 -0
- package/server/service/chat-message/chat-message.ts +169 -0
- package/server/service/chat-message/index.ts +12 -0
- package/server/service/chat-session/chat-session.ts +130 -0
- package/server/service/chat-session/index.ts +5 -0
- package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
- package/server/service/chat-session-participant/index.ts +6 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
- package/server/service/chat-session-participant/invite-notification.ts +48 -0
- package/server/service/chat-session-participant/presence.test.ts +39 -0
- package/server/service/chat-session-participant/presence.ts +21 -0
- package/server/service/chat-session-resolver.ts +312 -0
- package/server/service/component-tree.test.ts +248 -0
- package/server/service/component-tree.ts +135 -0
- package/server/service/index.ts +32 -0
- package/server/service/mention-marker.extract.test.ts +61 -0
- package/server/service/mention-marker.test.ts +228 -0
- package/server/service/mention-marker.ts +184 -0
- package/server/service/mention-resolver.test.ts +273 -0
- package/server/service/mention-resolver.ts +264 -0
- package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
- package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
- package/server/service/patch-entry/board-patch-subscription.ts +50 -0
- package/server/service/patch-entry/index.ts +12 -0
- package/server/service/patch-entry/patch-entry.ts +89 -0
- package/server/service/prefab-registry.test.ts +172 -0
- package/server/service/prefab-registry.ts +332 -0
- package/server/service/styling/clear-tools.test.ts +164 -0
- package/server/service/styling/clear-tools.ts +69 -0
- package/server/service/styling/color-utils.test.ts +130 -0
- package/server/service/styling/color-utils.ts +226 -0
- package/server/service/styling/copy-tools.test.ts +152 -0
- package/server/service/styling/copy-tools.ts +115 -0
- package/server/service/styling/effect-tools.test.ts +277 -0
- package/server/service/styling/effect-tools.ts +124 -0
- package/server/service/styling/fill-tools.test.ts +331 -0
- package/server/service/styling/fill-tools.ts +293 -0
- package/server/service/styling/material-tools.test.ts +294 -0
- package/server/service/styling/material-tools.ts +114 -0
- package/server/service/styling/read-tools.test.ts +78 -0
- package/server/service/styling/read-tools.ts +67 -0
- package/server/service/styling/registry.test.ts +203 -0
- package/server/service/styling/registry.ts +423 -0
- package/server/service/styling/stroke-tools.test.ts +245 -0
- package/server/service/styling/stroke-tools.ts +145 -0
- package/server/service/styling/text-tools.test.ts +253 -0
- package/server/service/styling/text-tools.ts +131 -0
- package/server/service/styling/tier1-tools.test.ts +300 -0
- package/server/service/subscribe-auth.test.ts +57 -0
- package/server/service/subscribe-auth.ts +34 -0
- package/server/service/types.ts +235 -0
- package/server/service/validation/board-model-schema.test.ts +778 -0
- package/server/service/validation/board-model-schema.ts +446 -0
- package/server/service/validation/tool-validation.ts +228 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +49 -0
- package/translations/ja.json +48 -0
- package/translations/ko.json +49 -0
- package/translations/ms.json +48 -0
- package/translations/zh.json +48 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* material-tools — setMaterial / setShadowProperties (3D PBR 재질).
|
|
3
|
+
*
|
|
4
|
+
* preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).
|
|
5
|
+
* 출력 patch.material3d 는 things-scene 의 Material3D 와 1:1 매핑.
|
|
6
|
+
*/
|
|
7
|
+
import { buildSetMaterialOps, buildSetShadowPropertiesOps } from './material-tools'
|
|
8
|
+
|
|
9
|
+
describe('buildSetMaterialOps — 입력 검증', () => {
|
|
10
|
+
test('refids 비어있으면 에러', () => {
|
|
11
|
+
const r = buildSetMaterialOps({ refids: [], preset: 'metal' })
|
|
12
|
+
expect(r.ops).toEqual([])
|
|
13
|
+
expect(r.errors[0]).toMatch(/non-empty array/)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('비-숫자 refids 필터링', () => {
|
|
17
|
+
const r = buildSetMaterialOps({
|
|
18
|
+
refids: [1, NaN, 'x' as any, 2],
|
|
19
|
+
preset: 'metal'
|
|
20
|
+
})
|
|
21
|
+
expect(r.ops).toHaveLength(2)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('아무 인자도 없으면 에러', () => {
|
|
25
|
+
const r = buildSetMaterialOps({ refids: [1] })
|
|
26
|
+
expect(r.ops).toEqual([])
|
|
27
|
+
expect(r.errors[0]).toMatch(/No material changes/)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('알 수 없는 preset → 에러 + 유효 목록 제시', () => {
|
|
31
|
+
const r = buildSetMaterialOps({ refids: [1], preset: 'silk' as any })
|
|
32
|
+
expect(r.ops).toEqual([])
|
|
33
|
+
expect(r.errors[0]).toMatch(/Unknown preset/)
|
|
34
|
+
expect(r.errors[0]).toMatch(/metal/)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('알 수 없는 side → 에러', () => {
|
|
38
|
+
const r = buildSetMaterialOps({ refids: [1], side: 'inside' as any })
|
|
39
|
+
expect(r.ops).toEqual([])
|
|
40
|
+
expect(r.errors[0]).toMatch(/Unknown side/)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('잘못된 emissive 색상 → 에러', () => {
|
|
44
|
+
const r = buildSetMaterialOps({
|
|
45
|
+
refids: [1],
|
|
46
|
+
preset: 'metal',
|
|
47
|
+
emissive: 'glowy' as any
|
|
48
|
+
})
|
|
49
|
+
expect(r.ops).toEqual([])
|
|
50
|
+
expect(r.errors[0]).toMatch(/Invalid/)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('buildSetMaterialOps — 범위 검증', () => {
|
|
55
|
+
test.each([
|
|
56
|
+
['metalness', { metalness: 1.5 }],
|
|
57
|
+
['metalness', { metalness: -0.1 }],
|
|
58
|
+
['roughness', { roughness: 1.5 }],
|
|
59
|
+
['roughness', { roughness: -0.1 }],
|
|
60
|
+
['opacity', { opacity: 1.5 }],
|
|
61
|
+
['opacity', { opacity: -0.1 }],
|
|
62
|
+
['emissiveIntensity', { emissiveIntensity: 5.5 }],
|
|
63
|
+
['emissiveIntensity', { emissiveIntensity: -0.1 }],
|
|
64
|
+
['envMapIntensity', { envMapIntensity: 3.5 }],
|
|
65
|
+
['envMapIntensity', { envMapIntensity: -0.1 }]
|
|
66
|
+
])('%s 범위 밖 (%o) → 에러', (field, override) => {
|
|
67
|
+
const r = buildSetMaterialOps({ refids: [1], ...(override as any) })
|
|
68
|
+
expect(r.ops).toEqual([])
|
|
69
|
+
expect(r.errors[0]).toMatch(new RegExp(field))
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('경계값 0 / 1 / 5 / 3 통과', () => {
|
|
73
|
+
expect(buildSetMaterialOps({ refids: [1], metalness: 0 }).ops).toHaveLength(1)
|
|
74
|
+
expect(buildSetMaterialOps({ refids: [1], metalness: 1 }).ops).toHaveLength(1)
|
|
75
|
+
expect(buildSetMaterialOps({ refids: [1], roughness: 0 }).ops).toHaveLength(1)
|
|
76
|
+
expect(buildSetMaterialOps({ refids: [1], roughness: 1 }).ops).toHaveLength(1)
|
|
77
|
+
expect(buildSetMaterialOps({ refids: [1], opacity: 0 }).ops).toHaveLength(1)
|
|
78
|
+
expect(buildSetMaterialOps({ refids: [1], opacity: 1 }).ops).toHaveLength(1)
|
|
79
|
+
expect(buildSetMaterialOps({ refids: [1], emissiveIntensity: 0 }).ops).toHaveLength(1)
|
|
80
|
+
expect(buildSetMaterialOps({ refids: [1], emissiveIntensity: 5 }).ops).toHaveLength(1)
|
|
81
|
+
expect(buildSetMaterialOps({ refids: [1], envMapIntensity: 0 }).ops).toHaveLength(1)
|
|
82
|
+
expect(buildSetMaterialOps({ refids: [1], envMapIntensity: 3 }).ops).toHaveLength(1)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('buildSetMaterialOps — preset 8종', () => {
|
|
87
|
+
test.each(['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'])(
|
|
88
|
+
'preset %s → patch.material3d.preset',
|
|
89
|
+
preset => {
|
|
90
|
+
const r = buildSetMaterialOps({ refids: [1], preset: preset as any })
|
|
91
|
+
expect(r.ops).toHaveLength(1)
|
|
92
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({ preset })
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('buildSetMaterialOps — preset + 속성 override', () => {
|
|
98
|
+
test('preset metal + metalness/roughness override', () => {
|
|
99
|
+
const r = buildSetMaterialOps({
|
|
100
|
+
refids: [1],
|
|
101
|
+
preset: 'metal',
|
|
102
|
+
metalness: 0.9,
|
|
103
|
+
roughness: 0.2
|
|
104
|
+
})
|
|
105
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
106
|
+
preset: 'metal',
|
|
107
|
+
metalness: 0.9,
|
|
108
|
+
roughness: 0.2
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('preset 없이 속성만 — partial update', () => {
|
|
113
|
+
// resolveMaterial3d 가 default 위 override 적용. preset 없어도 OK.
|
|
114
|
+
const r = buildSetMaterialOps({ refids: [1], roughness: 0.5 })
|
|
115
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({ roughness: 0.5 })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('emissive 색상 정규화', () => {
|
|
119
|
+
const r = buildSetMaterialOps({
|
|
120
|
+
refids: [1],
|
|
121
|
+
preset: 'plastic',
|
|
122
|
+
emissive: 'red',
|
|
123
|
+
emissiveIntensity: 1.5
|
|
124
|
+
})
|
|
125
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
126
|
+
preset: 'plastic',
|
|
127
|
+
emissive: '#ff0000',
|
|
128
|
+
emissiveIntensity: 1.5
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('side 통과 — double/front/back', () => {
|
|
133
|
+
expect(
|
|
134
|
+
(buildSetMaterialOps({ refids: [1], side: 'double' }).ops[0] as any).patch.material3d
|
|
135
|
+
).toEqual({ side: 'double' })
|
|
136
|
+
expect(
|
|
137
|
+
(buildSetMaterialOps({ refids: [1], side: 'front' }).ops[0] as any).patch.material3d
|
|
138
|
+
).toEqual({ side: 'front' })
|
|
139
|
+
expect(
|
|
140
|
+
(buildSetMaterialOps({ refids: [1], side: 'back' }).ops[0] as any).patch.material3d
|
|
141
|
+
).toEqual({ side: 'back' })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('opacity (3D-only) 와 envMapIntensity 통과', () => {
|
|
145
|
+
const r = buildSetMaterialOps({
|
|
146
|
+
refids: [1],
|
|
147
|
+
preset: 'glass',
|
|
148
|
+
opacity: 0.4,
|
|
149
|
+
envMapIntensity: 2
|
|
150
|
+
})
|
|
151
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
152
|
+
preset: 'glass',
|
|
153
|
+
opacity: 0.4,
|
|
154
|
+
envMapIntensity: 2
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('전체 필드 한 번에', () => {
|
|
159
|
+
const r = buildSetMaterialOps({
|
|
160
|
+
refids: [1],
|
|
161
|
+
preset: 'custom',
|
|
162
|
+
metalness: 0.5,
|
|
163
|
+
roughness: 0.3,
|
|
164
|
+
emissive: 'navy',
|
|
165
|
+
emissiveIntensity: 2,
|
|
166
|
+
opacity: 0.8,
|
|
167
|
+
envMapIntensity: 1.5,
|
|
168
|
+
side: 'double'
|
|
169
|
+
})
|
|
170
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
171
|
+
preset: 'custom',
|
|
172
|
+
metalness: 0.5,
|
|
173
|
+
roughness: 0.3,
|
|
174
|
+
emissive: '#000080',
|
|
175
|
+
emissiveIntensity: 2,
|
|
176
|
+
opacity: 0.8,
|
|
177
|
+
envMapIntensity: 1.5,
|
|
178
|
+
side: 'double'
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('buildSetMaterialOps — multi-target', () => {
|
|
184
|
+
test('동일 material 가 여러 refid 에 분배', () => {
|
|
185
|
+
const r = buildSetMaterialOps({
|
|
186
|
+
refids: [10, 20, 30],
|
|
187
|
+
preset: 'metal'
|
|
188
|
+
})
|
|
189
|
+
expect(r.ops).toHaveLength(3)
|
|
190
|
+
expect(r.ops.every(o => (o as any).patch.material3d.preset === 'metal')).toBe(true)
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe('buildSetShadowPropertiesOps — 3D 그림자 동작', () => {
|
|
195
|
+
test('refids 비어있으면 에러', () => {
|
|
196
|
+
const r = buildSetShadowPropertiesOps({ refids: [], castShadow: true })
|
|
197
|
+
expect(r.ops).toEqual([])
|
|
198
|
+
expect(r.errors[0]).toMatch(/non-empty array/)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('두 인자 모두 없으면 에러', () => {
|
|
202
|
+
const r = buildSetShadowPropertiesOps({ refids: [1] })
|
|
203
|
+
expect(r.ops).toEqual([])
|
|
204
|
+
expect(r.errors[0]).toMatch(/Specify castShadow/)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('castShadow 만', () => {
|
|
208
|
+
const r = buildSetShadowPropertiesOps({ refids: [1], castShadow: true })
|
|
209
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({ castShadow: true })
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('receiveShadow 만', () => {
|
|
213
|
+
const r = buildSetShadowPropertiesOps({ refids: [1], receiveShadow: false })
|
|
214
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({ receiveShadow: false })
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('둘 다', () => {
|
|
218
|
+
const r = buildSetShadowPropertiesOps({
|
|
219
|
+
refids: [1],
|
|
220
|
+
castShadow: true,
|
|
221
|
+
receiveShadow: true
|
|
222
|
+
})
|
|
223
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
224
|
+
castShadow: true,
|
|
225
|
+
receiveShadow: true
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('multi-target', () => {
|
|
230
|
+
const r = buildSetShadowPropertiesOps({
|
|
231
|
+
refids: [1, 2, 3],
|
|
232
|
+
castShadow: false
|
|
233
|
+
})
|
|
234
|
+
expect(r.ops).toHaveLength(3)
|
|
235
|
+
expect(
|
|
236
|
+
r.ops.every(o => (o as any).patch.material3d.castShadow === false)
|
|
237
|
+
).toBe(true)
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe('material-tools — 시나리오', () => {
|
|
242
|
+
test('금속 외관 — preset metal + 거울처럼 매끄럽게', () => {
|
|
243
|
+
const r = buildSetMaterialOps({
|
|
244
|
+
refids: [1, 2],
|
|
245
|
+
preset: 'metal',
|
|
246
|
+
metalness: 0.95,
|
|
247
|
+
roughness: 0.1
|
|
248
|
+
})
|
|
249
|
+
expect(r.ops).toHaveLength(2)
|
|
250
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
251
|
+
preset: 'metal',
|
|
252
|
+
metalness: 0.95,
|
|
253
|
+
roughness: 0.1
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('네온 발광 — emissive 강조', () => {
|
|
258
|
+
const r = buildSetMaterialOps({
|
|
259
|
+
refids: [42],
|
|
260
|
+
preset: 'plastic',
|
|
261
|
+
emissive: '#00ffff',
|
|
262
|
+
emissiveIntensity: 3
|
|
263
|
+
})
|
|
264
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
265
|
+
preset: 'plastic',
|
|
266
|
+
emissive: '#00ffff',
|
|
267
|
+
emissiveIntensity: 3
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('투명 유리', () => {
|
|
272
|
+
const r = buildSetMaterialOps({
|
|
273
|
+
refids: [1],
|
|
274
|
+
preset: 'glass',
|
|
275
|
+
opacity: 0.3
|
|
276
|
+
})
|
|
277
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
278
|
+
preset: 'glass',
|
|
279
|
+
opacity: 0.3
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test('대형 컨테이너 — 그림자 받기만', () => {
|
|
284
|
+
const r = buildSetShadowPropertiesOps({
|
|
285
|
+
refids: [1],
|
|
286
|
+
castShadow: false,
|
|
287
|
+
receiveShadow: true
|
|
288
|
+
})
|
|
289
|
+
expect((r.ops[0] as any).patch.material3d).toEqual({
|
|
290
|
+
castShadow: false,
|
|
291
|
+
receiveShadow: true
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setMaterial / setShadowProperties — 3D PBR 재질 + 그림자 동작.
|
|
3
|
+
*
|
|
4
|
+
* Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.
|
|
5
|
+
* preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).
|
|
6
|
+
*/
|
|
7
|
+
import type { BoardEditOp } from '../types'
|
|
8
|
+
import { normalizeColor, validateColor } from './color-utils'
|
|
9
|
+
|
|
10
|
+
const MATERIAL_PRESETS = ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'] as const
|
|
11
|
+
const SIDE_VALUES = ['double', 'front', 'back'] as const
|
|
12
|
+
|
|
13
|
+
export interface SetMaterialInput {
|
|
14
|
+
refids: number[]
|
|
15
|
+
preset?: typeof MATERIAL_PRESETS[number]
|
|
16
|
+
metalness?: number // 0..1
|
|
17
|
+
roughness?: number // 0..1
|
|
18
|
+
emissive?: string
|
|
19
|
+
emissiveIntensity?: number // 0..5
|
|
20
|
+
opacity?: number // 0..1 (3D-only)
|
|
21
|
+
envMapIntensity?: number // 0..3
|
|
22
|
+
side?: typeof SIDE_VALUES[number]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SetShadowPropertiesInput {
|
|
26
|
+
refids: number[]
|
|
27
|
+
castShadow?: boolean
|
|
28
|
+
receiveShadow?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OpsResult {
|
|
32
|
+
ops: BoardEditOp[]
|
|
33
|
+
errors: string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildSetMaterialOps(input: SetMaterialInput): OpsResult {
|
|
37
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
38
|
+
return { ops: [], errors: ['refids must be a non-empty array'] }
|
|
39
|
+
}
|
|
40
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))
|
|
41
|
+
if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }
|
|
42
|
+
|
|
43
|
+
const noOp = !(
|
|
44
|
+
input.preset ||
|
|
45
|
+
input.metalness !== undefined ||
|
|
46
|
+
input.roughness !== undefined ||
|
|
47
|
+
input.emissive !== undefined ||
|
|
48
|
+
input.emissiveIntensity !== undefined ||
|
|
49
|
+
input.opacity !== undefined ||
|
|
50
|
+
input.envMapIntensity !== undefined ||
|
|
51
|
+
input.side !== undefined
|
|
52
|
+
)
|
|
53
|
+
if (noOp) return { ops: [], errors: ['No material changes specified'] }
|
|
54
|
+
|
|
55
|
+
if (input.preset && !MATERIAL_PRESETS.includes(input.preset)) {
|
|
56
|
+
return {
|
|
57
|
+
ops: [],
|
|
58
|
+
errors: [`Unknown preset: '${input.preset}'. Valid: ${MATERIAL_PRESETS.join('/')}`]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (input.metalness !== undefined && (input.metalness < 0 || input.metalness > 1)) {
|
|
62
|
+
return { ops: [], errors: [`metalness out of [0,1]: ${input.metalness}`] }
|
|
63
|
+
}
|
|
64
|
+
if (input.roughness !== undefined && (input.roughness < 0 || input.roughness > 1)) {
|
|
65
|
+
return { ops: [], errors: [`roughness out of [0,1]: ${input.roughness}`] }
|
|
66
|
+
}
|
|
67
|
+
if (input.opacity !== undefined && (input.opacity < 0 || input.opacity > 1)) {
|
|
68
|
+
return { ops: [], errors: [`opacity out of [0,1]: ${input.opacity}`] }
|
|
69
|
+
}
|
|
70
|
+
if (input.emissiveIntensity !== undefined && (input.emissiveIntensity < 0 || input.emissiveIntensity > 5)) {
|
|
71
|
+
return { ops: [], errors: [`emissiveIntensity out of [0,5]: ${input.emissiveIntensity}`] }
|
|
72
|
+
}
|
|
73
|
+
if (input.envMapIntensity !== undefined && (input.envMapIntensity < 0 || input.envMapIntensity > 3)) {
|
|
74
|
+
return { ops: [], errors: [`envMapIntensity out of [0,3]: ${input.envMapIntensity}`] }
|
|
75
|
+
}
|
|
76
|
+
if (input.emissive !== undefined) {
|
|
77
|
+
const v = validateColor(input.emissive)
|
|
78
|
+
if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid emissive color'] }
|
|
79
|
+
}
|
|
80
|
+
if (input.side && !SIDE_VALUES.includes(input.side)) {
|
|
81
|
+
return { ops: [], errors: [`Unknown side: '${input.side}'. Valid: ${SIDE_VALUES.join('/')}`] }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const m: any = {}
|
|
85
|
+
if (input.preset) m.preset = input.preset
|
|
86
|
+
if (input.metalness !== undefined) m.metalness = input.metalness
|
|
87
|
+
if (input.roughness !== undefined) m.roughness = input.roughness
|
|
88
|
+
if (input.emissive !== undefined) m.emissive = normalizeColor(input.emissive)
|
|
89
|
+
if (input.emissiveIntensity !== undefined) m.emissiveIntensity = input.emissiveIntensity
|
|
90
|
+
if (input.opacity !== undefined) m.opacity = input.opacity
|
|
91
|
+
if (input.envMapIntensity !== undefined) m.envMapIntensity = input.envMapIntensity
|
|
92
|
+
if (input.side) m.side = input.side
|
|
93
|
+
|
|
94
|
+
const patch = { material3d: m }
|
|
95
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function buildSetShadowPropertiesOps(input: SetShadowPropertiesInput): OpsResult {
|
|
99
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
100
|
+
return { ops: [], errors: ['refids must be a non-empty array'] }
|
|
101
|
+
}
|
|
102
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))
|
|
103
|
+
if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }
|
|
104
|
+
|
|
105
|
+
const noOp = input.castShadow === undefined && input.receiveShadow === undefined
|
|
106
|
+
if (noOp) return { ops: [], errors: ['Specify castShadow and/or receiveShadow'] }
|
|
107
|
+
|
|
108
|
+
const m: any = {}
|
|
109
|
+
if (input.castShadow !== undefined) m.castShadow = input.castShadow
|
|
110
|
+
if (input.receiveShadow !== undefined) m.receiveShadow = input.receiveShadow
|
|
111
|
+
|
|
112
|
+
const patch = { material3d: m }
|
|
113
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }
|
|
114
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* read-tools — getComponentStyle / buildComponentStyleSummary.
|
|
3
|
+
*/
|
|
4
|
+
import { buildComponentStyleSummary } from './read-tools'
|
|
5
|
+
|
|
6
|
+
describe('buildComponentStyleSummary', () => {
|
|
7
|
+
test('full summary — 모든 aspect', () => {
|
|
8
|
+
const c = {
|
|
9
|
+
refid: 35,
|
|
10
|
+
type: 'rect',
|
|
11
|
+
fillStyle: { type: 'solid', color: '#ff0000' },
|
|
12
|
+
strokeStyle: { color: '#000', width: 2 },
|
|
13
|
+
shadow: { blur: 4, color: 'rgba(0,0,0,0.3)' },
|
|
14
|
+
material3d: { preset: 'metal' },
|
|
15
|
+
fontFamily: 'Inter',
|
|
16
|
+
fontSize: 14,
|
|
17
|
+
alpha: 0.8,
|
|
18
|
+
hidden: false,
|
|
19
|
+
text: 'Hello'
|
|
20
|
+
}
|
|
21
|
+
const s = buildComponentStyleSummary(c)
|
|
22
|
+
expect(s.refid).toBe(35)
|
|
23
|
+
expect(s.fillStyle).toEqual({ type: 'solid', color: '#ff0000' })
|
|
24
|
+
expect(s.strokeStyle).toEqual({ color: '#000', width: 2 })
|
|
25
|
+
expect(s.shadow).toEqual({ blur: 4, color: 'rgba(0,0,0,0.3)' })
|
|
26
|
+
expect(s.material3d).toEqual({ preset: 'metal' })
|
|
27
|
+
expect(s.alpha).toBe(0.8)
|
|
28
|
+
expect(s.hidden).toBe(false)
|
|
29
|
+
expect(s.text).toBe('Hello')
|
|
30
|
+
expect(s.font).toEqual({ fontFamily: 'Inter', fontSize: 14 })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('aspects 부분 — fill 만', () => {
|
|
34
|
+
const c = {
|
|
35
|
+
refid: 1,
|
|
36
|
+
fillStyle: { type: 'solid', color: '#abc' },
|
|
37
|
+
strokeStyle: { width: 1 },
|
|
38
|
+
alpha: 0.5
|
|
39
|
+
}
|
|
40
|
+
const s = buildComponentStyleSummary(c, ['fill'])
|
|
41
|
+
expect(s.fillStyle).toBeDefined()
|
|
42
|
+
expect(s.strokeStyle).toBeUndefined()
|
|
43
|
+
expect(s.alpha).toBeUndefined()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('aspects 다중 부분', () => {
|
|
47
|
+
const c = {
|
|
48
|
+
refid: 1,
|
|
49
|
+
fillStyle: { color: 'red' },
|
|
50
|
+
shadow: { blur: 2 },
|
|
51
|
+
hidden: true,
|
|
52
|
+
alpha: 0.7
|
|
53
|
+
}
|
|
54
|
+
const s = buildComponentStyleSummary(c, ['fill', 'shadow', 'visibility'])
|
|
55
|
+
expect(s.fillStyle).toBeDefined()
|
|
56
|
+
expect(s.shadow).toBeDefined()
|
|
57
|
+
expect(s.hidden).toBe(true)
|
|
58
|
+
expect(s.alpha).toBeUndefined() // opacity 미요청
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('컴포넌트가 styling 없으면 — refid 만', () => {
|
|
62
|
+
const c = { refid: 99, type: 'rect' }
|
|
63
|
+
const s = buildComponentStyleSummary(c)
|
|
64
|
+
expect(s).toEqual({ refid: 99 })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('font 부분 — 일부만 set', () => {
|
|
68
|
+
const c = { refid: 1, fontSize: 16, fontColor: '#0080ff' }
|
|
69
|
+
const s = buildComponentStyleSummary(c, ['font'])
|
|
70
|
+
expect(s.font).toEqual({ fontSize: 16, fontColor: '#0080ff' })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('font 모두 비어있으면 font 키 자체 없음', () => {
|
|
74
|
+
const c = { refid: 1, type: 'rect' }
|
|
75
|
+
const s = buildComponentStyleSummary(c, ['font'])
|
|
76
|
+
expect(s.font).toBeUndefined()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getComponentStyle — 컴포넌트의 현재 styling 상태 조회 (read tool).
|
|
3
|
+
*
|
|
4
|
+
* AI 가 변경 전 현재 상태 파악용 — "이미 빨간 모터를 더 빨갛게" 같은 모호 명령
|
|
5
|
+
* 정확 처리. iteration loop 의 핵심 기반.
|
|
6
|
+
*
|
|
7
|
+
* 반환 형태: { fillStyle, strokeStyle, shadow, material3d, font, alpha, hidden, text }
|
|
8
|
+
* - aspects 인자로 부분 조회 가능 (토큰 절감)
|
|
9
|
+
*/
|
|
10
|
+
export interface GetComponentStyleInput {
|
|
11
|
+
refid: number
|
|
12
|
+
aspects?: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity' | 'visibility' | 'text'>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ComponentStyleSummary {
|
|
16
|
+
refid: number
|
|
17
|
+
fillStyle?: any
|
|
18
|
+
strokeStyle?: any
|
|
19
|
+
shadow?: any
|
|
20
|
+
material3d?: any
|
|
21
|
+
font?: {
|
|
22
|
+
fontFamily?: string
|
|
23
|
+
fontSize?: number
|
|
24
|
+
fontWeight?: string
|
|
25
|
+
fontStyle?: string
|
|
26
|
+
fontColor?: string
|
|
27
|
+
textAlign?: string
|
|
28
|
+
textBaseline?: string
|
|
29
|
+
lineHeight?: number
|
|
30
|
+
}
|
|
31
|
+
alpha?: number
|
|
32
|
+
hidden?: boolean
|
|
33
|
+
text?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildComponentStyleSummary(
|
|
37
|
+
component: any,
|
|
38
|
+
aspects?: GetComponentStyleInput['aspects']
|
|
39
|
+
): ComponentStyleSummary {
|
|
40
|
+
const all = !aspects || aspects.length === 0
|
|
41
|
+
const want = (a: string) => all || aspects!.includes(a as any)
|
|
42
|
+
|
|
43
|
+
const out: ComponentStyleSummary = { refid: component.refid }
|
|
44
|
+
|
|
45
|
+
if (want('fill') && component.fillStyle !== undefined) out.fillStyle = component.fillStyle
|
|
46
|
+
if (want('stroke') && component.strokeStyle !== undefined) out.strokeStyle = component.strokeStyle
|
|
47
|
+
if (want('shadow') && component.shadow !== undefined) out.shadow = component.shadow
|
|
48
|
+
if (want('material') && component.material3d !== undefined) out.material3d = component.material3d
|
|
49
|
+
if (want('opacity') && typeof component.alpha === 'number') out.alpha = component.alpha
|
|
50
|
+
if (want('visibility') && component.hidden !== undefined) out.hidden = component.hidden
|
|
51
|
+
if (want('text') && typeof component.text === 'string') out.text = component.text
|
|
52
|
+
|
|
53
|
+
if (want('font')) {
|
|
54
|
+
const font: any = {}
|
|
55
|
+
if (component.fontFamily) font.fontFamily = component.fontFamily
|
|
56
|
+
if (component.fontSize !== undefined) font.fontSize = component.fontSize
|
|
57
|
+
if (component.fontWeight !== undefined) font.fontWeight = component.fontWeight
|
|
58
|
+
if (component.fontStyle) font.fontStyle = component.fontStyle
|
|
59
|
+
if (component.fontColor) font.fontColor = component.fontColor
|
|
60
|
+
if (component.textAlign) font.textAlign = component.textAlign
|
|
61
|
+
if (component.textBaseline) font.textBaseline = component.textBaseline
|
|
62
|
+
if (component.lineHeight !== undefined) font.lineHeight = component.lineHeight
|
|
63
|
+
if (Object.keys(font).length > 0) out.font = font
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return out
|
|
67
|
+
}
|