@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,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSetFillOps = buildSetFillOps;
|
|
4
|
+
const color_utils_1 = require("./color-utils");
|
|
5
|
+
/**
|
|
6
|
+
* setFill 입력 검증 + BoardEditOp[] 생성.
|
|
7
|
+
*
|
|
8
|
+
* 기본 정책:
|
|
9
|
+
* - refids 비어있거나 mode 도 색도 없으면 에러
|
|
10
|
+
* - 색상 입력은 normalizeColor 통과해야 적용
|
|
11
|
+
* - mode='gradient' 인데 stops 없으면 자동 생성 (color → 어두운 변형)
|
|
12
|
+
*/
|
|
13
|
+
function buildSetFillOps(input) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
const ops = [];
|
|
16
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
17
|
+
return { ops: [], errors: ['refids must be a non-empty array of numbers'] };
|
|
18
|
+
}
|
|
19
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n));
|
|
20
|
+
if (refids.length === 0) {
|
|
21
|
+
return { ops: [], errors: ['No valid numeric refids in input'] };
|
|
22
|
+
}
|
|
23
|
+
// mode 미지정 + 어떤 styling 인자도 없으면 무의미
|
|
24
|
+
if (!input.mode &&
|
|
25
|
+
!input.color &&
|
|
26
|
+
!input.colorStops &&
|
|
27
|
+
!input.image &&
|
|
28
|
+
typeof input.opacity !== 'number') {
|
|
29
|
+
return {
|
|
30
|
+
ops: [],
|
|
31
|
+
errors: ['No fill changes specified. Provide mode and/or color/opacity/etc.']
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// 색상 검증
|
|
35
|
+
if (input.color !== undefined) {
|
|
36
|
+
const v = (0, color_utils_1.validateColor)(input.color);
|
|
37
|
+
if (!v.valid) {
|
|
38
|
+
errors.push(v.error ?? `Invalid color: ${input.color}`);
|
|
39
|
+
return { ops: [], errors };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// gradient stops 검증
|
|
43
|
+
if (input.colorStops) {
|
|
44
|
+
for (const s of input.colorStops) {
|
|
45
|
+
if (typeof s.position !== 'number' || s.position < 0 || s.position > 1) {
|
|
46
|
+
errors.push(`Gradient stop position out of [0,1]: ${s.position}`);
|
|
47
|
+
return { ops: [], errors };
|
|
48
|
+
}
|
|
49
|
+
const v = (0, color_utils_1.validateColor)(s.color);
|
|
50
|
+
if (!v.valid) {
|
|
51
|
+
errors.push(`Invalid gradient stop color: ${s.color}`);
|
|
52
|
+
return { ops: [], errors };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// opacity 검증
|
|
57
|
+
if (typeof input.opacity === 'number') {
|
|
58
|
+
if (input.opacity < 0 || input.opacity > 1) {
|
|
59
|
+
errors.push(`Opacity out of [0,1]: ${input.opacity}`);
|
|
60
|
+
return { ops: [], errors };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// pattern — image 필수
|
|
64
|
+
if (input.mode === 'pattern' && !input.image) {
|
|
65
|
+
errors.push(`Mode 'pattern' requires 'image' (URL or asset ref)`);
|
|
66
|
+
return { ops: [], errors };
|
|
67
|
+
}
|
|
68
|
+
// 각 refid 별 patch 생성
|
|
69
|
+
const patch = buildFillPatch(input);
|
|
70
|
+
if (!patch) {
|
|
71
|
+
return { ops: [], errors: ['Failed to build fill patch'] };
|
|
72
|
+
}
|
|
73
|
+
// 단일 op — apply-patch 의 deep merge 가 자연스럽게 처리:
|
|
74
|
+
// - 새 fillStyle 이 string (solid) → 기존 object 통째 replace (typeof 매칭 X)
|
|
75
|
+
// - 새 fillStyle 이 object → 같은 key (colorStops 배열 등) 는 array replace 됨
|
|
76
|
+
// - 기존 부가 필드 (rotation 등) 가 새 patch 에 없으면 유지 — partial update 의도
|
|
77
|
+
// 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture trigger 해
|
|
78
|
+
// 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제 자체가 해결됐음.
|
|
79
|
+
for (const refid of refids) {
|
|
80
|
+
ops.push({ op: 'modify', refid, patch });
|
|
81
|
+
}
|
|
82
|
+
return { ops, errors };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* input → fillStyle patch 객체 생성.
|
|
86
|
+
*
|
|
87
|
+
* Mode 명시 시: fillStyle 통째 교체 → patch = { fillStyle: {...새 객체} }
|
|
88
|
+
* Mode 미지정 시: 기존 fillStyle 위에 색/opacity 만 병합 → patch = { fillStyle: { ...덮어쓸 필드 } }
|
|
89
|
+
* (apply-patch 의 deep-merge 가 처리. fillStyle 이 객체라면.)
|
|
90
|
+
*
|
|
91
|
+
* mode='none' 의 경우 fillStyle 을 falsy 로 — 빈 객체 또는 type:'none' 명시.
|
|
92
|
+
*/
|
|
93
|
+
function buildFillPatch(input) {
|
|
94
|
+
const { mode } = input;
|
|
95
|
+
if (mode === 'none') {
|
|
96
|
+
// fillStyle 제거 의도 — null 시맨틱 활용 (apply-patch 가 키 자체를 delete).
|
|
97
|
+
// drawer/fill.ts: fillStyle 없으면 falsy → 채우지 않음.
|
|
98
|
+
return { fillStyle: null };
|
|
99
|
+
}
|
|
100
|
+
if (mode === 'solid') {
|
|
101
|
+
// things-scene drawer/fill.ts:17 — fillStyle 이 string 이면 그대로 색상.
|
|
102
|
+
// 객체 형식 {type:'solid', color} 은 인터페이스에만 있는 stale.
|
|
103
|
+
// opacity 는 색상에 alpha 채널로 embedding (rgba).
|
|
104
|
+
const color = input.color ? (0, color_utils_1.normalizeColor)(input.color) : undefined;
|
|
105
|
+
if (!color)
|
|
106
|
+
return null;
|
|
107
|
+
let fillStyle = color;
|
|
108
|
+
if (typeof input.opacity === 'number' && input.opacity < 1) {
|
|
109
|
+
fillStyle = withAlpha(color, input.opacity);
|
|
110
|
+
}
|
|
111
|
+
return { fillStyle: fillStyle };
|
|
112
|
+
}
|
|
113
|
+
if (mode === 'gradient') {
|
|
114
|
+
let stops = input.colorStops;
|
|
115
|
+
// stops 미지정 시 — color 와 자동 생성 어두운 변형 (간단한 fallback)
|
|
116
|
+
if ((!stops || stops.length === 0) && input.color) {
|
|
117
|
+
const hex = (0, color_utils_1.normalizeColor)(input.color);
|
|
118
|
+
if (hex) {
|
|
119
|
+
stops = [
|
|
120
|
+
{ color: hex, position: 0 },
|
|
121
|
+
{ color: darken(hex, 0.4), position: 1 }
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!stops || stops.length < 2) {
|
|
126
|
+
// gradient 인데 stops 부족 — 호출자가 명시 안 하면 만들어 주지 않음
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
// things-scene 의 실제 필드: colorStops [{position, color}]
|
|
130
|
+
const colorStops = stops
|
|
131
|
+
.map(s => ({ position: s.position, color: (0, color_utils_1.normalizeColor)(s.color) }))
|
|
132
|
+
.filter(s => !!s.color);
|
|
133
|
+
if (colorStops.length < 2)
|
|
134
|
+
return null;
|
|
135
|
+
// rotation: 항상 명시 — drawer/fill.ts 가 default 없이 fillStyle.rotation 을 직접
|
|
136
|
+
// Math.cos / Math.sin 에 넘기므로 undefined 면 NaN → createLinearGradient 크래시.
|
|
137
|
+
// AI/사용자는 degrees 로 표현, 내부에서 라디안 변환. 미지정 시 0 default.
|
|
138
|
+
const rotationRad = typeof input.rotation === 'number' ? (input.rotation * Math.PI) / 180 : 0;
|
|
139
|
+
const fs = {
|
|
140
|
+
type: 'gradient',
|
|
141
|
+
gradientType: input.gradientType ?? 'linear',
|
|
142
|
+
colorStops,
|
|
143
|
+
rotation: rotationRad
|
|
144
|
+
};
|
|
145
|
+
if (typeof input.opacity === 'number')
|
|
146
|
+
fs.opacity = input.opacity;
|
|
147
|
+
return { fillStyle: fs };
|
|
148
|
+
}
|
|
149
|
+
if (mode === 'pattern') {
|
|
150
|
+
if (!input.image)
|
|
151
|
+
return null;
|
|
152
|
+
// things-scene 의 실제 pattern fillStyle (drawer/fill.ts:95~)
|
|
153
|
+
const fs = { type: 'pattern', image: input.image };
|
|
154
|
+
if (input.color) {
|
|
155
|
+
const c = (0, color_utils_1.normalizeColor)(input.color);
|
|
156
|
+
if (c)
|
|
157
|
+
fs.color = c; // 패턴 뒤 배경색 (drawer 가 사용)
|
|
158
|
+
}
|
|
159
|
+
if (input.fitPattern !== undefined)
|
|
160
|
+
fs.fitPattern = input.fitPattern;
|
|
161
|
+
if (input.noRepeat !== undefined)
|
|
162
|
+
fs.noRepeat = input.noRepeat;
|
|
163
|
+
if (input.align)
|
|
164
|
+
fs.align = input.align;
|
|
165
|
+
if (typeof input.offsetX === 'number')
|
|
166
|
+
fs.offsetX = input.offsetX;
|
|
167
|
+
if (typeof input.offsetY === 'number')
|
|
168
|
+
fs.offsetY = input.offsetY;
|
|
169
|
+
if (typeof input.patternWidth === 'number')
|
|
170
|
+
fs.width = input.patternWidth;
|
|
171
|
+
if (typeof input.patternHeight === 'number')
|
|
172
|
+
fs.height = input.patternHeight;
|
|
173
|
+
if (typeof input.opacity === 'number')
|
|
174
|
+
fs.opacity = input.opacity;
|
|
175
|
+
return { fillStyle: fs };
|
|
176
|
+
}
|
|
177
|
+
// mode 미지정 — 부분 update.
|
|
178
|
+
// things-scene 의 fillStyle 은 string (solid) 또는 object (gradient/pattern).
|
|
179
|
+
// 부분 update 에서 색만 들어오면 solid string 으로 통째 set (가장 흔한 케이스).
|
|
180
|
+
// (gradient 의 부분 update 는 mode='gradient' 명시 사용 — 모드 명시 시 full replace.)
|
|
181
|
+
if (input.color) {
|
|
182
|
+
const c = (0, color_utils_1.normalizeColor)(input.color);
|
|
183
|
+
if (!c)
|
|
184
|
+
return null;
|
|
185
|
+
const fillStyle = typeof input.opacity === 'number' && input.opacity < 1 ? withAlpha(c, input.opacity) : c;
|
|
186
|
+
return { fillStyle: fillStyle };
|
|
187
|
+
}
|
|
188
|
+
// color 없이 opacity 만 — 부분 update 어려움 (string 인지 object 인지 모름).
|
|
189
|
+
// 명확한 의도가 있으면 mode 명시 권장. 일단 무동작 (null).
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* hex / 명명 색 + alpha (0..1) → rgba(r,g,b,a) 문자열.
|
|
194
|
+
* Canvas API 의 fillStyle 은 rgba string 그대로 사용 가능.
|
|
195
|
+
*/
|
|
196
|
+
function withAlpha(hexOrName, alpha) {
|
|
197
|
+
// transparent
|
|
198
|
+
if (hexOrName === 'transparent')
|
|
199
|
+
return 'rgba(0,0,0,0)';
|
|
200
|
+
// hex 예상 (#rrggbb)
|
|
201
|
+
if (hexOrName.startsWith('#') && hexOrName.length === 7) {
|
|
202
|
+
const r = parseInt(hexOrName.slice(1, 3), 16);
|
|
203
|
+
const g = parseInt(hexOrName.slice(3, 5), 16);
|
|
204
|
+
const b = parseInt(hexOrName.slice(5, 7), 16);
|
|
205
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
206
|
+
}
|
|
207
|
+
// 다른 형식이면 색을 그대로 두고 alpha 무시 (nullable 안전)
|
|
208
|
+
return hexOrName;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 단순 hex 색상 어둡게 — gradient 자동 생성 시 fallback 두 번째 stop 용.
|
|
212
|
+
* lightness 기반 정확도 < HSL 변환이 정확하지만 의존성 줄이려 RGB linear blend 로 대체.
|
|
213
|
+
*/
|
|
214
|
+
function darken(hex, amount) {
|
|
215
|
+
const p = (0, color_utils_1.parseColor)(hex);
|
|
216
|
+
if (!p || p.hex === 'transparent')
|
|
217
|
+
return hex;
|
|
218
|
+
const h = p.hex.slice(1); // remove #
|
|
219
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
220
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
221
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
222
|
+
const factor = 1 - Math.max(0, Math.min(1, amount));
|
|
223
|
+
const r2 = Math.round(r * factor);
|
|
224
|
+
const g2 = Math.round(g * factor);
|
|
225
|
+
const b2 = Math.round(b * factor);
|
|
226
|
+
const toHex = (n) => n.toString(16).padStart(2, '0');
|
|
227
|
+
return `#${toHex(r2)}${toHex(g2)}${toHex(b2)}`;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=fill-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fill-tools.js","sourceRoot":"","sources":["../../../server/service/styling/fill-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setFill — 컴포넌트 배경 (state.fillStyle) 변경.\n *\n * 4 modes 지원: none / solid / gradient / pattern.\n *\n * 출력 정책 — refid 당 1 op:\n * - solid: fillStyle = '#rrggbb' or 'rgba(...)' (string)\n * - gradient: fillStyle = { type:'gradient', gradientType, colorStops, rotation, ... }\n * - pattern: fillStyle = { type:'pattern', image, ... }\n * - none: fillStyle = { type:'none' }\n * - mode 미지정 + color 만: 부분 update (string 또는 object 병합)\n *\n * things-scene 실제 데이터 형식 (drawer/fill.ts 검증):\n * - gradient: { type, gradientType, colorStops: [{position, color}], rotation (라디안) }\n * - rotation 은 라디안 — `Math.cos(fRotation)` 직접 사용\n *\n * 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture\n * 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.\n */\nimport type { BoardEditOp } from '../types'\nimport { parseColor, normalizeColor, validateColor } from './color-utils'\n\nexport interface SetFillInput {\n refids: number[]\n mode?: 'none' | 'solid' | 'gradient' | 'pattern'\n // solid\n color?: string\n // gradient\n gradientType?: 'linear' | 'radial'\n /** colorStops — things-scene 의 canonical 필드명 그대로. position 0..1, color hex/rgb/named. */\n colorStops?: Array<{ color: string; position: number }>\n /** 회전 — 입력은 degrees (사용자/AI 친화), 내부 변환 후 things-scene 의 라디안으로 저장. */\n rotation?: number\n // pattern (drawer/fill.ts:95~ 검증)\n image?: string\n /** pattern: 영역에 맞춰 비율 조정 (true) vs 원본 크기 사용 (false). */\n fitPattern?: boolean\n /** pattern: 반복 안 함 (true) — 1회만 그림. */\n noRepeat?: boolean\n /** pattern: 정렬. fitPattern=false 일 때만 적용. */\n align?:\n | 'center'\n | 'left-top'\n | 'left-bottom'\n | 'right-top'\n | 'right-bottom'\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n /** pattern: 영역 내 오프셋 (fitPattern=false 일 때). */\n offsetX?: number\n offsetY?: number\n /** pattern: 패턴 크기 (기본은 이미지 원본 크기). */\n patternWidth?: number\n patternHeight?: number\n // 공통\n opacity?: number // 0..1\n}\n\nexport interface SetFillResult {\n ops: BoardEditOp[]\n errors: string[] // refid 별 에러 (성공한 것은 op 만 발사)\n}\n\n/**\n * setFill 입력 검증 + BoardEditOp[] 생성.\n *\n * 기본 정책:\n * - refids 비어있거나 mode 도 색도 없으면 에러\n * - 색상 입력은 normalizeColor 통과해야 적용\n * - mode='gradient' 인데 stops 없으면 자동 생성 (color → 어두운 변형)\n */\nexport function buildSetFillOps(input: SetFillInput): SetFillResult {\n const errors: string[] = []\n const ops: BoardEditOp[] = []\n\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array of numbers'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) {\n return { ops: [], errors: ['No valid numeric refids in input'] }\n }\n\n // mode 미지정 + 어떤 styling 인자도 없으면 무의미\n if (\n !input.mode &&\n !input.color &&\n !input.colorStops &&\n !input.image &&\n typeof input.opacity !== 'number'\n ) {\n return {\n ops: [],\n errors: ['No fill changes specified. Provide mode and/or color/opacity/etc.']\n }\n }\n\n // 색상 검증\n if (input.color !== undefined) {\n const v = validateColor(input.color)\n if (!v.valid) {\n errors.push(v.error ?? `Invalid color: ${input.color}`)\n return { ops: [], errors }\n }\n }\n\n // gradient stops 검증\n if (input.colorStops) {\n for (const s of input.colorStops) {\n if (typeof s.position !== 'number' || s.position < 0 || s.position > 1) {\n errors.push(`Gradient stop position out of [0,1]: ${s.position}`)\n return { ops: [], errors }\n }\n const v = validateColor(s.color)\n if (!v.valid) {\n errors.push(`Invalid gradient stop color: ${s.color}`)\n return { ops: [], errors }\n }\n }\n }\n\n // opacity 검증\n if (typeof input.opacity === 'number') {\n if (input.opacity < 0 || input.opacity > 1) {\n errors.push(`Opacity out of [0,1]: ${input.opacity}`)\n return { ops: [], errors }\n }\n }\n\n // pattern — image 필수\n if (input.mode === 'pattern' && !input.image) {\n errors.push(`Mode 'pattern' requires 'image' (URL or asset ref)`)\n return { ops: [], errors }\n }\n\n // 각 refid 별 patch 생성\n const patch = buildFillPatch(input)\n if (!patch) {\n return { ops: [], errors: ['Failed to build fill patch'] }\n }\n\n // 단일 op — apply-patch 의 deep merge 가 자연스럽게 처리:\n // - 새 fillStyle 이 string (solid) → 기존 object 통째 replace (typeof 매칭 X)\n // - 새 fillStyle 이 object → 같은 key (colorStops 배열 등) 는 array replace 됨\n // - 기존 부가 필드 (rotation 등) 가 새 patch 에 없으면 유지 — partial update 의도\n // 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture trigger 해\n // 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제 자체가 해결됐음.\n for (const refid of refids) {\n ops.push({ op: 'modify', refid, patch })\n }\n return { ops, errors }\n}\n\n/**\n * input → fillStyle patch 객체 생성.\n *\n * Mode 명시 시: fillStyle 통째 교체 → patch = { fillStyle: {...새 객체} }\n * Mode 미지정 시: 기존 fillStyle 위에 색/opacity 만 병합 → patch = { fillStyle: { ...덮어쓸 필드 } }\n * (apply-patch 의 deep-merge 가 처리. fillStyle 이 객체라면.)\n *\n * mode='none' 의 경우 fillStyle 을 falsy 로 — 빈 객체 또는 type:'none' 명시.\n */\nfunction buildFillPatch(input: SetFillInput): { fillStyle: any } | null {\n const { mode } = input\n\n if (mode === 'none') {\n // fillStyle 제거 의도 — null 시맨틱 활용 (apply-patch 가 키 자체를 delete).\n // drawer/fill.ts: fillStyle 없으면 falsy → 채우지 않음.\n return { fillStyle: null as any }\n }\n\n if (mode === 'solid') {\n // things-scene drawer/fill.ts:17 — fillStyle 이 string 이면 그대로 색상.\n // 객체 형식 {type:'solid', color} 은 인터페이스에만 있는 stale.\n // opacity 는 색상에 alpha 채널로 embedding (rgba).\n const color = input.color ? normalizeColor(input.color) : undefined\n if (!color) return null\n let fillStyle: string = color\n if (typeof input.opacity === 'number' && input.opacity < 1) {\n fillStyle = withAlpha(color, input.opacity)\n }\n return { fillStyle: fillStyle as any }\n }\n\n if (mode === 'gradient') {\n let stops = input.colorStops\n // stops 미지정 시 — color 와 자동 생성 어두운 변형 (간단한 fallback)\n if ((!stops || stops.length === 0) && input.color) {\n const hex = normalizeColor(input.color)\n if (hex) {\n stops = [\n { color: hex, position: 0 },\n { color: darken(hex, 0.4), position: 1 }\n ]\n }\n }\n if (!stops || stops.length < 2) {\n // gradient 인데 stops 부족 — 호출자가 명시 안 하면 만들어 주지 않음\n return null\n }\n // things-scene 의 실제 필드: colorStops [{position, color}]\n const colorStops = stops\n .map(s => ({ position: s.position, color: normalizeColor(s.color)! }))\n .filter(s => !!s.color)\n if (colorStops.length < 2) return null\n // rotation: 항상 명시 — drawer/fill.ts 가 default 없이 fillStyle.rotation 을 직접\n // Math.cos / Math.sin 에 넘기므로 undefined 면 NaN → createLinearGradient 크래시.\n // AI/사용자는 degrees 로 표현, 내부에서 라디안 변환. 미지정 시 0 default.\n const rotationRad =\n typeof input.rotation === 'number' ? (input.rotation * Math.PI) / 180 : 0\n const fs: any = {\n type: 'gradient',\n gradientType: input.gradientType ?? 'linear',\n colorStops,\n rotation: rotationRad\n }\n if (typeof input.opacity === 'number') fs.opacity = input.opacity\n return { fillStyle: fs }\n }\n\n if (mode === 'pattern') {\n if (!input.image) return null\n // things-scene 의 실제 pattern fillStyle (drawer/fill.ts:95~)\n const fs: any = { type: 'pattern', image: input.image }\n if (input.color) {\n const c = normalizeColor(input.color)\n if (c) fs.color = c // 패턴 뒤 배경색 (drawer 가 사용)\n }\n if (input.fitPattern !== undefined) fs.fitPattern = input.fitPattern\n if (input.noRepeat !== undefined) fs.noRepeat = input.noRepeat\n if (input.align) fs.align = input.align\n if (typeof input.offsetX === 'number') fs.offsetX = input.offsetX\n if (typeof input.offsetY === 'number') fs.offsetY = input.offsetY\n if (typeof input.patternWidth === 'number') fs.width = input.patternWidth\n if (typeof input.patternHeight === 'number') fs.height = input.patternHeight\n if (typeof input.opacity === 'number') fs.opacity = input.opacity\n return { fillStyle: fs }\n }\n\n // mode 미지정 — 부분 update.\n // things-scene 의 fillStyle 은 string (solid) 또는 object (gradient/pattern).\n // 부분 update 에서 색만 들어오면 solid string 으로 통째 set (가장 흔한 케이스).\n // (gradient 의 부분 update 는 mode='gradient' 명시 사용 — 모드 명시 시 full replace.)\n if (input.color) {\n const c = normalizeColor(input.color)\n if (!c) return null\n const fillStyle =\n typeof input.opacity === 'number' && input.opacity < 1 ? withAlpha(c, input.opacity) : c\n return { fillStyle: fillStyle as any }\n }\n // color 없이 opacity 만 — 부분 update 어려움 (string 인지 object 인지 모름).\n // 명확한 의도가 있으면 mode 명시 권장. 일단 무동작 (null).\n return null\n}\n\n/**\n * hex / 명명 색 + alpha (0..1) → rgba(r,g,b,a) 문자열.\n * Canvas API 의 fillStyle 은 rgba string 그대로 사용 가능.\n */\nfunction withAlpha(hexOrName: string, alpha: number): string {\n // transparent\n if (hexOrName === 'transparent') return 'rgba(0,0,0,0)'\n // hex 예상 (#rrggbb)\n if (hexOrName.startsWith('#') && hexOrName.length === 7) {\n const r = parseInt(hexOrName.slice(1, 3), 16)\n const g = parseInt(hexOrName.slice(3, 5), 16)\n const b = parseInt(hexOrName.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${alpha})`\n }\n // 다른 형식이면 색을 그대로 두고 alpha 무시 (nullable 안전)\n return hexOrName\n}\n\n/**\n * 단순 hex 색상 어둡게 — gradient 자동 생성 시 fallback 두 번째 stop 용.\n * lightness 기반 정확도 < HSL 변환이 정확하지만 의존성 줄이려 RGB linear blend 로 대체.\n */\nfunction darken(hex: string, amount: number): string {\n const p = parseColor(hex)\n if (!p || p.hex === 'transparent') return hex\n const h = p.hex.slice(1) // remove #\n const r = parseInt(h.slice(0, 2), 16)\n const g = parseInt(h.slice(2, 4), 16)\n const b = parseInt(h.slice(4, 6), 16)\n const factor = 1 - Math.max(0, Math.min(1, amount))\n const r2 = Math.round(r * factor)\n const g2 = Math.round(g * factor)\n const b2 = Math.round(b * factor)\n const toHex = (n: number) => n.toString(16).padStart(2, '0')\n return `#${toHex(r2)}${toHex(g2)}${toHex(b2)}`\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
declare const MATERIAL_PRESETS: readonly ["default", "metal", "glass", "plastic", "wood", "ceramic", "rubber", "custom"];
|
|
9
|
+
declare const SIDE_VALUES: readonly ["double", "front", "back"];
|
|
10
|
+
export interface SetMaterialInput {
|
|
11
|
+
refids: number[];
|
|
12
|
+
preset?: typeof MATERIAL_PRESETS[number];
|
|
13
|
+
metalness?: number;
|
|
14
|
+
roughness?: number;
|
|
15
|
+
emissive?: string;
|
|
16
|
+
emissiveIntensity?: number;
|
|
17
|
+
opacity?: number;
|
|
18
|
+
envMapIntensity?: number;
|
|
19
|
+
side?: typeof SIDE_VALUES[number];
|
|
20
|
+
}
|
|
21
|
+
export interface SetShadowPropertiesInput {
|
|
22
|
+
refids: number[];
|
|
23
|
+
castShadow?: boolean;
|
|
24
|
+
receiveShadow?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface OpsResult {
|
|
27
|
+
ops: BoardEditOp[];
|
|
28
|
+
errors: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare function buildSetMaterialOps(input: SetMaterialInput): OpsResult;
|
|
31
|
+
export declare function buildSetShadowPropertiesOps(input: SetShadowPropertiesInput): OpsResult;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSetMaterialOps = buildSetMaterialOps;
|
|
4
|
+
exports.buildSetShadowPropertiesOps = buildSetShadowPropertiesOps;
|
|
5
|
+
const color_utils_1 = require("./color-utils");
|
|
6
|
+
const MATERIAL_PRESETS = ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'];
|
|
7
|
+
const SIDE_VALUES = ['double', 'front', 'back'];
|
|
8
|
+
function buildSetMaterialOps(input) {
|
|
9
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
10
|
+
return { ops: [], errors: ['refids must be a non-empty array'] };
|
|
11
|
+
}
|
|
12
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n));
|
|
13
|
+
if (refids.length === 0)
|
|
14
|
+
return { ops: [], errors: ['No valid numeric refids'] };
|
|
15
|
+
const noOp = !(input.preset ||
|
|
16
|
+
input.metalness !== undefined ||
|
|
17
|
+
input.roughness !== undefined ||
|
|
18
|
+
input.emissive !== undefined ||
|
|
19
|
+
input.emissiveIntensity !== undefined ||
|
|
20
|
+
input.opacity !== undefined ||
|
|
21
|
+
input.envMapIntensity !== undefined ||
|
|
22
|
+
input.side !== undefined);
|
|
23
|
+
if (noOp)
|
|
24
|
+
return { ops: [], errors: ['No material changes specified'] };
|
|
25
|
+
if (input.preset && !MATERIAL_PRESETS.includes(input.preset)) {
|
|
26
|
+
return {
|
|
27
|
+
ops: [],
|
|
28
|
+
errors: [`Unknown preset: '${input.preset}'. Valid: ${MATERIAL_PRESETS.join('/')}`]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (input.metalness !== undefined && (input.metalness < 0 || input.metalness > 1)) {
|
|
32
|
+
return { ops: [], errors: [`metalness out of [0,1]: ${input.metalness}`] };
|
|
33
|
+
}
|
|
34
|
+
if (input.roughness !== undefined && (input.roughness < 0 || input.roughness > 1)) {
|
|
35
|
+
return { ops: [], errors: [`roughness out of [0,1]: ${input.roughness}`] };
|
|
36
|
+
}
|
|
37
|
+
if (input.opacity !== undefined && (input.opacity < 0 || input.opacity > 1)) {
|
|
38
|
+
return { ops: [], errors: [`opacity out of [0,1]: ${input.opacity}`] };
|
|
39
|
+
}
|
|
40
|
+
if (input.emissiveIntensity !== undefined && (input.emissiveIntensity < 0 || input.emissiveIntensity > 5)) {
|
|
41
|
+
return { ops: [], errors: [`emissiveIntensity out of [0,5]: ${input.emissiveIntensity}`] };
|
|
42
|
+
}
|
|
43
|
+
if (input.envMapIntensity !== undefined && (input.envMapIntensity < 0 || input.envMapIntensity > 3)) {
|
|
44
|
+
return { ops: [], errors: [`envMapIntensity out of [0,3]: ${input.envMapIntensity}`] };
|
|
45
|
+
}
|
|
46
|
+
if (input.emissive !== undefined) {
|
|
47
|
+
const v = (0, color_utils_1.validateColor)(input.emissive);
|
|
48
|
+
if (!v.valid)
|
|
49
|
+
return { ops: [], errors: [v.error ?? 'Invalid emissive color'] };
|
|
50
|
+
}
|
|
51
|
+
if (input.side && !SIDE_VALUES.includes(input.side)) {
|
|
52
|
+
return { ops: [], errors: [`Unknown side: '${input.side}'. Valid: ${SIDE_VALUES.join('/')}`] };
|
|
53
|
+
}
|
|
54
|
+
const m = {};
|
|
55
|
+
if (input.preset)
|
|
56
|
+
m.preset = input.preset;
|
|
57
|
+
if (input.metalness !== undefined)
|
|
58
|
+
m.metalness = input.metalness;
|
|
59
|
+
if (input.roughness !== undefined)
|
|
60
|
+
m.roughness = input.roughness;
|
|
61
|
+
if (input.emissive !== undefined)
|
|
62
|
+
m.emissive = (0, color_utils_1.normalizeColor)(input.emissive);
|
|
63
|
+
if (input.emissiveIntensity !== undefined)
|
|
64
|
+
m.emissiveIntensity = input.emissiveIntensity;
|
|
65
|
+
if (input.opacity !== undefined)
|
|
66
|
+
m.opacity = input.opacity;
|
|
67
|
+
if (input.envMapIntensity !== undefined)
|
|
68
|
+
m.envMapIntensity = input.envMapIntensity;
|
|
69
|
+
if (input.side)
|
|
70
|
+
m.side = input.side;
|
|
71
|
+
const patch = { material3d: m };
|
|
72
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] };
|
|
73
|
+
}
|
|
74
|
+
function buildSetShadowPropertiesOps(input) {
|
|
75
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
76
|
+
return { ops: [], errors: ['refids must be a non-empty array'] };
|
|
77
|
+
}
|
|
78
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n));
|
|
79
|
+
if (refids.length === 0)
|
|
80
|
+
return { ops: [], errors: ['No valid numeric refids'] };
|
|
81
|
+
const noOp = input.castShadow === undefined && input.receiveShadow === undefined;
|
|
82
|
+
if (noOp)
|
|
83
|
+
return { ops: [], errors: ['Specify castShadow and/or receiveShadow'] };
|
|
84
|
+
const m = {};
|
|
85
|
+
if (input.castShadow !== undefined)
|
|
86
|
+
m.castShadow = input.castShadow;
|
|
87
|
+
if (input.receiveShadow !== undefined)
|
|
88
|
+
m.receiveShadow = input.receiveShadow;
|
|
89
|
+
const patch = { material3d: m };
|
|
90
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=material-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"material-tools.js","sourceRoot":"","sources":["../../../server/service/styling/material-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setMaterial / setShadowProperties — 3D PBR 재질 + 그림자 동작.\n *\n * Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.\n * preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).\n */\nimport type { BoardEditOp } from '../types'\nimport { normalizeColor, validateColor } from './color-utils'\n\nconst MATERIAL_PRESETS = ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'] as const\nconst SIDE_VALUES = ['double', 'front', 'back'] as const\n\nexport interface SetMaterialInput {\n refids: number[]\n preset?: typeof MATERIAL_PRESETS[number]\n metalness?: number // 0..1\n roughness?: number // 0..1\n emissive?: string\n emissiveIntensity?: number // 0..5\n opacity?: number // 0..1 (3D-only)\n envMapIntensity?: number // 0..3\n side?: typeof SIDE_VALUES[number]\n}\n\nexport interface SetShadowPropertiesInput {\n refids: number[]\n castShadow?: boolean\n receiveShadow?: boolean\n}\n\nexport interface OpsResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\nexport function buildSetMaterialOps(input: SetMaterialInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp = !(\n input.preset ||\n input.metalness !== undefined ||\n input.roughness !== undefined ||\n input.emissive !== undefined ||\n input.emissiveIntensity !== undefined ||\n input.opacity !== undefined ||\n input.envMapIntensity !== undefined ||\n input.side !== undefined\n )\n if (noOp) return { ops: [], errors: ['No material changes specified'] }\n\n if (input.preset && !MATERIAL_PRESETS.includes(input.preset)) {\n return {\n ops: [],\n errors: [`Unknown preset: '${input.preset}'. Valid: ${MATERIAL_PRESETS.join('/')}`]\n }\n }\n if (input.metalness !== undefined && (input.metalness < 0 || input.metalness > 1)) {\n return { ops: [], errors: [`metalness out of [0,1]: ${input.metalness}`] }\n }\n if (input.roughness !== undefined && (input.roughness < 0 || input.roughness > 1)) {\n return { ops: [], errors: [`roughness out of [0,1]: ${input.roughness}`] }\n }\n if (input.opacity !== undefined && (input.opacity < 0 || input.opacity > 1)) {\n return { ops: [], errors: [`opacity out of [0,1]: ${input.opacity}`] }\n }\n if (input.emissiveIntensity !== undefined && (input.emissiveIntensity < 0 || input.emissiveIntensity > 5)) {\n return { ops: [], errors: [`emissiveIntensity out of [0,5]: ${input.emissiveIntensity}`] }\n }\n if (input.envMapIntensity !== undefined && (input.envMapIntensity < 0 || input.envMapIntensity > 3)) {\n return { ops: [], errors: [`envMapIntensity out of [0,3]: ${input.envMapIntensity}`] }\n }\n if (input.emissive !== undefined) {\n const v = validateColor(input.emissive)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid emissive color'] }\n }\n if (input.side && !SIDE_VALUES.includes(input.side)) {\n return { ops: [], errors: [`Unknown side: '${input.side}'. Valid: ${SIDE_VALUES.join('/')}`] }\n }\n\n const m: any = {}\n if (input.preset) m.preset = input.preset\n if (input.metalness !== undefined) m.metalness = input.metalness\n if (input.roughness !== undefined) m.roughness = input.roughness\n if (input.emissive !== undefined) m.emissive = normalizeColor(input.emissive)\n if (input.emissiveIntensity !== undefined) m.emissiveIntensity = input.emissiveIntensity\n if (input.opacity !== undefined) m.opacity = input.opacity\n if (input.envMapIntensity !== undefined) m.envMapIntensity = input.envMapIntensity\n if (input.side) m.side = input.side\n\n const patch = { material3d: m }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n\nexport function buildSetShadowPropertiesOps(input: SetShadowPropertiesInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp = input.castShadow === undefined && input.receiveShadow === undefined\n if (noOp) return { ops: [], errors: ['Specify castShadow and/or receiveShadow'] }\n\n const m: any = {}\n if (input.castShadow !== undefined) m.castShadow = input.castShadow\n if (input.receiveShadow !== undefined) m.receiveShadow = input.receiveShadow\n\n const patch = { material3d: m }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
export interface ComponentStyleSummary {
|
|
15
|
+
refid: number;
|
|
16
|
+
fillStyle?: any;
|
|
17
|
+
strokeStyle?: any;
|
|
18
|
+
shadow?: any;
|
|
19
|
+
material3d?: any;
|
|
20
|
+
font?: {
|
|
21
|
+
fontFamily?: string;
|
|
22
|
+
fontSize?: number;
|
|
23
|
+
fontWeight?: string;
|
|
24
|
+
fontStyle?: string;
|
|
25
|
+
fontColor?: string;
|
|
26
|
+
textAlign?: string;
|
|
27
|
+
textBaseline?: string;
|
|
28
|
+
lineHeight?: number;
|
|
29
|
+
};
|
|
30
|
+
alpha?: number;
|
|
31
|
+
hidden?: boolean;
|
|
32
|
+
text?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function buildComponentStyleSummary(component: any, aspects?: GetComponentStyleInput['aspects']): ComponentStyleSummary;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildComponentStyleSummary = buildComponentStyleSummary;
|
|
4
|
+
function buildComponentStyleSummary(component, aspects) {
|
|
5
|
+
const all = !aspects || aspects.length === 0;
|
|
6
|
+
const want = (a) => all || aspects.includes(a);
|
|
7
|
+
const out = { refid: component.refid };
|
|
8
|
+
if (want('fill') && component.fillStyle !== undefined)
|
|
9
|
+
out.fillStyle = component.fillStyle;
|
|
10
|
+
if (want('stroke') && component.strokeStyle !== undefined)
|
|
11
|
+
out.strokeStyle = component.strokeStyle;
|
|
12
|
+
if (want('shadow') && component.shadow !== undefined)
|
|
13
|
+
out.shadow = component.shadow;
|
|
14
|
+
if (want('material') && component.material3d !== undefined)
|
|
15
|
+
out.material3d = component.material3d;
|
|
16
|
+
if (want('opacity') && typeof component.alpha === 'number')
|
|
17
|
+
out.alpha = component.alpha;
|
|
18
|
+
if (want('visibility') && component.hidden !== undefined)
|
|
19
|
+
out.hidden = component.hidden;
|
|
20
|
+
if (want('text') && typeof component.text === 'string')
|
|
21
|
+
out.text = component.text;
|
|
22
|
+
if (want('font')) {
|
|
23
|
+
const font = {};
|
|
24
|
+
if (component.fontFamily)
|
|
25
|
+
font.fontFamily = component.fontFamily;
|
|
26
|
+
if (component.fontSize !== undefined)
|
|
27
|
+
font.fontSize = component.fontSize;
|
|
28
|
+
if (component.fontWeight !== undefined)
|
|
29
|
+
font.fontWeight = component.fontWeight;
|
|
30
|
+
if (component.fontStyle)
|
|
31
|
+
font.fontStyle = component.fontStyle;
|
|
32
|
+
if (component.fontColor)
|
|
33
|
+
font.fontColor = component.fontColor;
|
|
34
|
+
if (component.textAlign)
|
|
35
|
+
font.textAlign = component.textAlign;
|
|
36
|
+
if (component.textBaseline)
|
|
37
|
+
font.textBaseline = component.textBaseline;
|
|
38
|
+
if (component.lineHeight !== undefined)
|
|
39
|
+
font.lineHeight = component.lineHeight;
|
|
40
|
+
if (Object.keys(font).length > 0)
|
|
41
|
+
out.font = font;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=read-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-tools.js","sourceRoot":"","sources":["../../../server/service/styling/read-tools.ts"],"names":[],"mappings":";;AAmCA,gEA+BC;AA/BD,SAAgB,0BAA0B,CACxC,SAAc,EACd,OAA2C;IAE3C,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,CAAA;IAC5C,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,IAAI,OAAQ,CAAC,QAAQ,CAAC,CAAQ,CAAC,CAAA;IAE9D,MAAM,GAAG,GAA0B,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,CAAA;IAE7D,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,SAAS,KAAK,SAAS;QAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;IAC1F,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAA;IAClG,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS;QAAE,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;IACnF,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS;QAAE,GAAG,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAA;IACjG,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,QAAQ;QAAE,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAA;IACvF,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS;QAAE,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;IACvF,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ;QAAE,GAAG,CAAC,IAAI,GAAG,SAAS,CAAC,IAAI,CAAA;IAEjF,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,GAAQ,EAAE,CAAA;QACpB,IAAI,SAAS,CAAC,UAAU;YAAE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAA;QAChE,IAAI,SAAS,CAAC,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAA;QACxE,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAA;QAC9E,IAAI,SAAS,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;QAC7D,IAAI,SAAS,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;QAC7D,IAAI,SAAS,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;QAC7D,IAAI,SAAS,CAAC,YAAY;YAAE,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC,YAAY,CAAA;QACtE,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAA;QAC9E,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;IACnD,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/**\n * getComponentStyle — 컴포넌트의 현재 styling 상태 조회 (read tool).\n *\n * AI 가 변경 전 현재 상태 파악용 — \"이미 빨간 모터를 더 빨갛게\" 같은 모호 명령\n * 정확 처리. iteration loop 의 핵심 기반.\n *\n * 반환 형태: { fillStyle, strokeStyle, shadow, material3d, font, alpha, hidden, text }\n * - aspects 인자로 부분 조회 가능 (토큰 절감)\n */\nexport interface GetComponentStyleInput {\n refid: number\n aspects?: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity' | 'visibility' | 'text'>\n}\n\nexport interface ComponentStyleSummary {\n refid: number\n fillStyle?: any\n strokeStyle?: any\n shadow?: any\n material3d?: any\n font?: {\n fontFamily?: string\n fontSize?: number\n fontWeight?: string\n fontStyle?: string\n fontColor?: string\n textAlign?: string\n textBaseline?: string\n lineHeight?: number\n }\n alpha?: number\n hidden?: boolean\n text?: string\n}\n\nexport function buildComponentStyleSummary(\n component: any,\n aspects?: GetComponentStyleInput['aspects']\n): ComponentStyleSummary {\n const all = !aspects || aspects.length === 0\n const want = (a: string) => all || aspects!.includes(a as any)\n\n const out: ComponentStyleSummary = { refid: component.refid }\n\n if (want('fill') && component.fillStyle !== undefined) out.fillStyle = component.fillStyle\n if (want('stroke') && component.strokeStyle !== undefined) out.strokeStyle = component.strokeStyle\n if (want('shadow') && component.shadow !== undefined) out.shadow = component.shadow\n if (want('material') && component.material3d !== undefined) out.material3d = component.material3d\n if (want('opacity') && typeof component.alpha === 'number') out.alpha = component.alpha\n if (want('visibility') && component.hidden !== undefined) out.hidden = component.hidden\n if (want('text') && typeof component.text === 'string') out.text = component.text\n\n if (want('font')) {\n const font: any = {}\n if (component.fontFamily) font.fontFamily = component.fontFamily\n if (component.fontSize !== undefined) font.fontSize = component.fontSize\n if (component.fontWeight !== undefined) font.fontWeight = component.fontWeight\n if (component.fontStyle) font.fontStyle = component.fontStyle\n if (component.fontColor) font.fontColor = component.fontColor\n if (component.textAlign) font.textAlign = component.textAlign\n if (component.textBaseline) font.textBaseline = component.textBaseline\n if (component.lineHeight !== undefined) font.lineHeight = component.lineHeight\n if (Object.keys(font).length > 0) out.font = font\n }\n\n return out\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styling tool registry — 선언형 단일 진실 (single source of truth).
|
|
3
|
+
*
|
|
4
|
+
* 동기:
|
|
5
|
+
* 기존 styling tool 추가 시 4-5곳 (WRITE_TOOL_NAMES set / tools 배열 정의 /
|
|
6
|
+
* dispatch case / validation case / system prompt 안내) 에 같은 정보가 산재해
|
|
7
|
+
* 동기화 누락 위험. 신규 tool 마다 4곳 boilerplate.
|
|
8
|
+
*
|
|
9
|
+
* 본 registry 가 한 곳에 모음 — 위 모든 사용처가 STYLING_TOOLS 에서 자동 도출.
|
|
10
|
+
*
|
|
11
|
+
* 마이그레이션:
|
|
12
|
+
* STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다
|
|
13
|
+
* 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.
|
|
14
|
+
*
|
|
15
|
+
* 비-styling write tool (addComponent / modifyBoard / alignComponents 등) 은
|
|
16
|
+
* 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.
|
|
17
|
+
*/
|
|
18
|
+
import type { BoardEditOp } from '../types';
|
|
19
|
+
export interface OpsResult {
|
|
20
|
+
ops: BoardEditOp[];
|
|
21
|
+
errors: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 한 styling tool 의 모든 메타데이터 + 빌더.
|
|
25
|
+
*
|
|
26
|
+
* description 과 themeNote 는 별도 — 자동 합성될 때 description 본문은 영문/기능
|
|
27
|
+
* 위주로 짧게, themeNote 는 한국어 ⚠ 정책 단문. 분리해 두면 미래에 i18n / 정책
|
|
28
|
+
* 일괄 변경 시 검색-치환이 쉽다.
|
|
29
|
+
*/
|
|
30
|
+
export interface StylingToolSpec {
|
|
31
|
+
/** AIToolCall.name 과 매칭. unique. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** 기능 설명 (영문 위주). */
|
|
34
|
+
description: string;
|
|
35
|
+
/**
|
|
36
|
+
* 선택 — ⚠ 정책 / 테마 가이드 단문. 빌드 시 description 끝에 자동 부착.
|
|
37
|
+
* 예: '⚠ 테마 요청 시 width/dash 건드리지 말 것.'
|
|
38
|
+
*/
|
|
39
|
+
themeNote?: string;
|
|
40
|
+
/** JSON Schema for parameters — LLM tool schema 에 그대로 노출. */
|
|
41
|
+
schema: any;
|
|
42
|
+
/**
|
|
43
|
+
* AIToolCall.arguments → OpsResult 변환.
|
|
44
|
+
* components 는 copyStyle 처럼 source lookup 필요한 tool 만 사용. 다른 tool 은
|
|
45
|
+
* 두 번째 인자 무시하면 됨.
|
|
46
|
+
*/
|
|
47
|
+
builder: (args: any, components: ReadonlyArray<any>) => OpsResult;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 등록된 styling tool 들. 한 tool 씩 점진 이전 중.
|
|
51
|
+
*
|
|
52
|
+
* 마이그레이션 순서: setFill → setStroke → setText/setFont → setShadow /
|
|
53
|
+
* setOpacity / setVisible → setMaterial / setShadowProperties → clearStyle
|
|
54
|
+
* → copyStyle. 각 이전 commit 은 별도 — bisect 로 회귀 추적 용이.
|
|
55
|
+
*
|
|
56
|
+
* spec 작성 규약:
|
|
57
|
+
* - description 은 영문 / 기능 설명
|
|
58
|
+
* - themeNote 는 ⚠ 시작 한국어 정책 단문 (테마 가이드 / 호출 금지 등)
|
|
59
|
+
* - schema 는 LLM 에 그대로 노출되는 JSON Schema
|
|
60
|
+
* - builder 는 builder 함수 직접 사용 (signature 호환). components 인자
|
|
61
|
+
* 안 쓰는 tool 도 그대로 통과.
|
|
62
|
+
*/
|
|
63
|
+
export declare const STYLING_TOOLS: StylingToolSpec[];
|
|
64
|
+
/**
|
|
65
|
+
* registry 에 등록된 모든 styling tool 이름 set.
|
|
66
|
+
* WRITE_TOOL_NAMES 합산용 — assistant.ts 가 isWriteTool 판정 시 styling 영역
|
|
67
|
+
* 부분을 본 set 에 위임.
|
|
68
|
+
*/
|
|
69
|
+
export declare function getStylingToolNames(registry?: ReadonlyArray<StylingToolSpec>): Set<string>;
|
|
70
|
+
/**
|
|
71
|
+
* LLM 에 노출할 tool 정의 배열.
|
|
72
|
+
* buildBoardEditTools 가 styling 부분을 본 함수 결과로 채운다.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildStylingToolDefinitions(registry?: ReadonlyArray<StylingToolSpec>): Array<{
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
parameters: any;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → BoardEditOp[] | null.
|
|
81
|
+
*
|
|
82
|
+
* 반환:
|
|
83
|
+
* - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)
|
|
84
|
+
* - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)
|
|
85
|
+
* - 정상 → BoardEditOp[]
|
|
86
|
+
*
|
|
87
|
+
* components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.
|
|
88
|
+
*/
|
|
89
|
+
export declare function dispatchStylingTool(name: string, args: any, components?: ReadonlyArray<any>, registry?: ReadonlyArray<StylingToolSpec>): BoardEditOp[] | null;
|
|
90
|
+
/** name 이 styling tool 로 등록되어 있는가. */
|
|
91
|
+
export declare function isStylingTool(name: string, registry?: ReadonlyArray<StylingToolSpec>): boolean;
|