@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,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Color 파싱 / 정규화 유틸 — styling tool 들이 공통으로 사용.
|
|
4
|
+
*
|
|
5
|
+
* AI 가 자연어로 색상 표현하는 다양한 형태를 받아 표준 hex 또는 rgba 로 변환.
|
|
6
|
+
*
|
|
7
|
+
* 지원 입력:
|
|
8
|
+
* - hex 6자리: '#ff0000', '#FF0000'
|
|
9
|
+
* - hex 3자리: '#f00' (→ '#ff0000')
|
|
10
|
+
* - hex 8자리: '#ff0000ff' (alpha 포함)
|
|
11
|
+
* - rgb / rgba: 'rgb(255,0,0)', 'rgba(255,0,0,0.5)'
|
|
12
|
+
* - CSS named: 'red', 'transparent', ...
|
|
13
|
+
* - 시맨틱 alias: 'warm-red', 'forest-green', ...
|
|
14
|
+
*
|
|
15
|
+
* 출력 형태:
|
|
16
|
+
* - normalizeColor(): 표준 hex (#rrggbb), opacity 는 별개로 분리 반환
|
|
17
|
+
* - validateColor(): 유효성만 체크 (boolean + 에러 메시지)
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.parseColor = parseColor;
|
|
21
|
+
exports.validateColor = validateColor;
|
|
22
|
+
exports.normalizeColor = normalizeColor;
|
|
23
|
+
/** CSS named colors → hex (자주 쓰는 것만, 필요 시 확장) */
|
|
24
|
+
const NAMED_COLORS = {
|
|
25
|
+
// 무채
|
|
26
|
+
white: '#ffffff',
|
|
27
|
+
black: '#000000',
|
|
28
|
+
gray: '#808080',
|
|
29
|
+
grey: '#808080',
|
|
30
|
+
silver: '#c0c0c0',
|
|
31
|
+
// 기본 컬러
|
|
32
|
+
red: '#ff0000',
|
|
33
|
+
green: '#008000',
|
|
34
|
+
blue: '#0000ff',
|
|
35
|
+
yellow: '#ffff00',
|
|
36
|
+
cyan: '#00ffff',
|
|
37
|
+
magenta: '#ff00ff',
|
|
38
|
+
orange: '#ffa500',
|
|
39
|
+
purple: '#800080',
|
|
40
|
+
pink: '#ffc0cb',
|
|
41
|
+
brown: '#a52a2a',
|
|
42
|
+
// 자주 쓰는 변형
|
|
43
|
+
navy: '#000080',
|
|
44
|
+
teal: '#008080',
|
|
45
|
+
lime: '#00ff00',
|
|
46
|
+
olive: '#808000',
|
|
47
|
+
maroon: '#800000',
|
|
48
|
+
// 투명
|
|
49
|
+
transparent: 'transparent'
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* 시맨틱 컬러 alias — AI 가 자연어 표현 사용 가능.
|
|
53
|
+
* Material 3 기반 톤 (brightness 적당, saturation 풍부).
|
|
54
|
+
*/
|
|
55
|
+
const SEMANTIC_COLORS = {
|
|
56
|
+
// warm reds
|
|
57
|
+
'warm-red': '#e74c3c',
|
|
58
|
+
'soft-red': '#ef4444',
|
|
59
|
+
'deep-red': '#b91c1c',
|
|
60
|
+
'dark-red': '#7f1d1d',
|
|
61
|
+
// greens
|
|
62
|
+
'forest-green': '#228b22',
|
|
63
|
+
'mint-green': '#10b981',
|
|
64
|
+
'olive-green': '#65a30d',
|
|
65
|
+
'lime-green': '#84cc16',
|
|
66
|
+
// blues
|
|
67
|
+
'sky-blue': '#0ea5e9',
|
|
68
|
+
'navy-blue': '#1e40af',
|
|
69
|
+
'royal-blue': '#3b82f6',
|
|
70
|
+
'powder-blue': '#bfdbfe',
|
|
71
|
+
// yellows / oranges
|
|
72
|
+
'amber': '#f59e0b',
|
|
73
|
+
'sunset-orange': '#f97316',
|
|
74
|
+
'gold': '#eab308',
|
|
75
|
+
'mustard': '#ca8a04',
|
|
76
|
+
// purples
|
|
77
|
+
'violet': '#7c3aed',
|
|
78
|
+
'lavender': '#c4b5fd',
|
|
79
|
+
// grays
|
|
80
|
+
'slate': '#64748b',
|
|
81
|
+
'charcoal': '#374151',
|
|
82
|
+
'cool-gray': '#6b7280',
|
|
83
|
+
'warm-gray': '#78716c',
|
|
84
|
+
// 시맨틱 의도
|
|
85
|
+
success: '#22c55e',
|
|
86
|
+
warning: '#f59e0b',
|
|
87
|
+
error: '#ef4444',
|
|
88
|
+
info: '#3b82f6'
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* 색상 입력을 정규 hex + alpha 로 변환.
|
|
92
|
+
* 잘못된 입력은 null — 호출자가 에러 처리.
|
|
93
|
+
*/
|
|
94
|
+
function parseColor(input) {
|
|
95
|
+
if (typeof input !== 'string')
|
|
96
|
+
return null;
|
|
97
|
+
const s = input.trim().toLowerCase();
|
|
98
|
+
if (s.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
// transparent
|
|
101
|
+
if (s === 'transparent' || s === 'none') {
|
|
102
|
+
return { hex: 'transparent', alpha: 0 };
|
|
103
|
+
}
|
|
104
|
+
// hex
|
|
105
|
+
if (s.startsWith('#')) {
|
|
106
|
+
return parseHex(s);
|
|
107
|
+
}
|
|
108
|
+
// rgb / rgba
|
|
109
|
+
const rgbMatch = s.match(/^rgba?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/);
|
|
110
|
+
if (rgbMatch) {
|
|
111
|
+
const r = clampByte(rgbMatch[1]);
|
|
112
|
+
const g = clampByte(rgbMatch[2]);
|
|
113
|
+
const b = clampByte(rgbMatch[3]);
|
|
114
|
+
const a = rgbMatch[4] !== undefined ? clampUnit(rgbMatch[4]) : 1;
|
|
115
|
+
if (r === null || g === null || b === null || a === null)
|
|
116
|
+
return null;
|
|
117
|
+
return { hex: rgbToHex(r, g, b), alpha: a };
|
|
118
|
+
}
|
|
119
|
+
// CSS named
|
|
120
|
+
if (NAMED_COLORS[s]) {
|
|
121
|
+
return parseHex(NAMED_COLORS[s]);
|
|
122
|
+
}
|
|
123
|
+
// 시맨틱 alias
|
|
124
|
+
if (SEMANTIC_COLORS[s]) {
|
|
125
|
+
return parseHex(SEMANTIC_COLORS[s]);
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
function parseHex(s) {
|
|
130
|
+
const hex = s.startsWith('#') ? s.slice(1) : s;
|
|
131
|
+
if (hex === 'transparent')
|
|
132
|
+
return { hex: 'transparent', alpha: 0 };
|
|
133
|
+
if (!/^[0-9a-f]+$/i.test(hex))
|
|
134
|
+
return null;
|
|
135
|
+
if (hex.length === 3) {
|
|
136
|
+
// #abc → #aabbcc
|
|
137
|
+
const r = hex[0] + hex[0];
|
|
138
|
+
const g = hex[1] + hex[1];
|
|
139
|
+
const b = hex[2] + hex[2];
|
|
140
|
+
return { hex: `#${r}${g}${b}`.toLowerCase(), alpha: 1 };
|
|
141
|
+
}
|
|
142
|
+
if (hex.length === 4) {
|
|
143
|
+
// #abcd → #aabbccdd
|
|
144
|
+
const r = hex[0] + hex[0];
|
|
145
|
+
const g = hex[1] + hex[1];
|
|
146
|
+
const b = hex[2] + hex[2];
|
|
147
|
+
const a = parseInt(hex[3] + hex[3], 16) / 255;
|
|
148
|
+
return { hex: `#${r}${g}${b}`.toLowerCase(), alpha: round3(a) };
|
|
149
|
+
}
|
|
150
|
+
if (hex.length === 6) {
|
|
151
|
+
return { hex: `#${hex}`.toLowerCase(), alpha: 1 };
|
|
152
|
+
}
|
|
153
|
+
if (hex.length === 8) {
|
|
154
|
+
const rgb = hex.slice(0, 6);
|
|
155
|
+
const a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
156
|
+
return { hex: `#${rgb}`.toLowerCase(), alpha: round3(a) };
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function clampByte(s) {
|
|
161
|
+
const n = Number(s);
|
|
162
|
+
if (!Number.isFinite(n) || n < 0 || n > 255)
|
|
163
|
+
return null;
|
|
164
|
+
return Math.round(n);
|
|
165
|
+
}
|
|
166
|
+
function clampUnit(s) {
|
|
167
|
+
const n = Number(s);
|
|
168
|
+
if (!Number.isFinite(n) || n < 0 || n > 1)
|
|
169
|
+
return null;
|
|
170
|
+
return n;
|
|
171
|
+
}
|
|
172
|
+
function rgbToHex(r, g, b) {
|
|
173
|
+
const h = (n) => n.toString(16).padStart(2, '0');
|
|
174
|
+
return `#${h(r)}${h(g)}${h(b)}`;
|
|
175
|
+
}
|
|
176
|
+
function round3(n) {
|
|
177
|
+
return Math.round(n * 1000) / 1000;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 단순 검증 — 정규화 가능 여부만.
|
|
181
|
+
* 에러 메시지에 정정 제안 포함 가능.
|
|
182
|
+
*/
|
|
183
|
+
function validateColor(input) {
|
|
184
|
+
if (typeof input !== 'string') {
|
|
185
|
+
return { valid: false, error: 'Color must be a string.' };
|
|
186
|
+
}
|
|
187
|
+
const parsed = parseColor(input);
|
|
188
|
+
if (parsed)
|
|
189
|
+
return { valid: true };
|
|
190
|
+
// 비슷한 named color 추천
|
|
191
|
+
const lower = input.trim().toLowerCase();
|
|
192
|
+
const allKnown = [...Object.keys(NAMED_COLORS), ...Object.keys(SEMANTIC_COLORS)];
|
|
193
|
+
const close = allKnown.find(k => k.includes(lower) || lower.includes(k));
|
|
194
|
+
return {
|
|
195
|
+
valid: false,
|
|
196
|
+
error: `Invalid color: '${input}'. Use hex (#ff0000), rgb(255,0,0), or named (red, warm-red, ...).`,
|
|
197
|
+
suggestion: close
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 색상을 hex 문자열로 정규화 (저장용).
|
|
202
|
+
* 실패 시 null.
|
|
203
|
+
*/
|
|
204
|
+
function normalizeColor(input) {
|
|
205
|
+
const parsed = parseColor(input);
|
|
206
|
+
if (!parsed)
|
|
207
|
+
return null;
|
|
208
|
+
return parsed.hex;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=color-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-utils.js","sourceRoot":"","sources":["../../../server/service/styling/color-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Color 파싱 / 정규화 유틸 — styling tool 들이 공통으로 사용.\n *\n * AI 가 자연어로 색상 표현하는 다양한 형태를 받아 표준 hex 또는 rgba 로 변환.\n *\n * 지원 입력:\n * - hex 6자리: '#ff0000', '#FF0000'\n * - hex 3자리: '#f00' (→ '#ff0000')\n * - hex 8자리: '#ff0000ff' (alpha 포함)\n * - rgb / rgba: 'rgb(255,0,0)', 'rgba(255,0,0,0.5)'\n * - CSS named: 'red', 'transparent', ...\n * - 시맨틱 alias: 'warm-red', 'forest-green', ...\n *\n * 출력 형태:\n * - normalizeColor(): 표준 hex (#rrggbb), opacity 는 별개로 분리 반환\n * - validateColor(): 유효성만 체크 (boolean + 에러 메시지)\n */\n\n/** CSS named colors → hex (자주 쓰는 것만, 필요 시 확장) */\nconst NAMED_COLORS: Record<string, string> = {\n // 무채\n white: '#ffffff',\n black: '#000000',\n gray: '#808080',\n grey: '#808080',\n silver: '#c0c0c0',\n // 기본 컬러\n red: '#ff0000',\n green: '#008000',\n blue: '#0000ff',\n yellow: '#ffff00',\n cyan: '#00ffff',\n magenta: '#ff00ff',\n orange: '#ffa500',\n purple: '#800080',\n pink: '#ffc0cb',\n brown: '#a52a2a',\n // 자주 쓰는 변형\n navy: '#000080',\n teal: '#008080',\n lime: '#00ff00',\n olive: '#808000',\n maroon: '#800000',\n // 투명\n transparent: 'transparent'\n}\n\n/**\n * 시맨틱 컬러 alias — AI 가 자연어 표현 사용 가능.\n * Material 3 기반 톤 (brightness 적당, saturation 풍부).\n */\nconst SEMANTIC_COLORS: Record<string, string> = {\n // warm reds\n 'warm-red': '#e74c3c',\n 'soft-red': '#ef4444',\n 'deep-red': '#b91c1c',\n 'dark-red': '#7f1d1d',\n // greens\n 'forest-green': '#228b22',\n 'mint-green': '#10b981',\n 'olive-green': '#65a30d',\n 'lime-green': '#84cc16',\n // blues\n 'sky-blue': '#0ea5e9',\n 'navy-blue': '#1e40af',\n 'royal-blue': '#3b82f6',\n 'powder-blue': '#bfdbfe',\n // yellows / oranges\n 'amber': '#f59e0b',\n 'sunset-orange': '#f97316',\n 'gold': '#eab308',\n 'mustard': '#ca8a04',\n // purples\n 'violet': '#7c3aed',\n 'lavender': '#c4b5fd',\n // grays\n 'slate': '#64748b',\n 'charcoal': '#374151',\n 'cool-gray': '#6b7280',\n 'warm-gray': '#78716c',\n // 시맨틱 의도\n success: '#22c55e',\n warning: '#f59e0b',\n error: '#ef4444',\n info: '#3b82f6'\n}\n\n/** Parse 결과 — color 와 alpha 분리. alpha 는 0..1. */\nexport interface ParsedColor {\n /** 표준 hex (#rrggbb, 소문자) 또는 'transparent' */\n hex: string\n /** Alpha 채널 0..1. transparent 면 0, 명시 alpha 없으면 1. */\n alpha: number\n}\n\n/**\n * 색상 입력을 정규 hex + alpha 로 변환.\n * 잘못된 입력은 null — 호출자가 에러 처리.\n */\nexport function parseColor(input: string | null | undefined): ParsedColor | null {\n if (typeof input !== 'string') return null\n const s = input.trim().toLowerCase()\n if (s.length === 0) return null\n\n // transparent\n if (s === 'transparent' || s === 'none') {\n return { hex: 'transparent', alpha: 0 }\n }\n\n // hex\n if (s.startsWith('#')) {\n return parseHex(s)\n }\n\n // rgb / rgba\n const rgbMatch = s.match(/^rgba?\\s*\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/)\n if (rgbMatch) {\n const r = clampByte(rgbMatch[1])\n const g = clampByte(rgbMatch[2])\n const b = clampByte(rgbMatch[3])\n const a = rgbMatch[4] !== undefined ? clampUnit(rgbMatch[4]) : 1\n if (r === null || g === null || b === null || a === null) return null\n return { hex: rgbToHex(r, g, b), alpha: a }\n }\n\n // CSS named\n if (NAMED_COLORS[s]) {\n return parseHex(NAMED_COLORS[s])\n }\n\n // 시맨틱 alias\n if (SEMANTIC_COLORS[s]) {\n return parseHex(SEMANTIC_COLORS[s])\n }\n\n return null\n}\n\nfunction parseHex(s: string): ParsedColor | null {\n const hex = s.startsWith('#') ? s.slice(1) : s\n if (hex === 'transparent') return { hex: 'transparent', alpha: 0 }\n\n if (!/^[0-9a-f]+$/i.test(hex)) return null\n\n if (hex.length === 3) {\n // #abc → #aabbcc\n const r = hex[0] + hex[0]\n const g = hex[1] + hex[1]\n const b = hex[2] + hex[2]\n return { hex: `#${r}${g}${b}`.toLowerCase(), alpha: 1 }\n }\n if (hex.length === 4) {\n // #abcd → #aabbccdd\n const r = hex[0] + hex[0]\n const g = hex[1] + hex[1]\n const b = hex[2] + hex[2]\n const a = parseInt(hex[3] + hex[3], 16) / 255\n return { hex: `#${r}${g}${b}`.toLowerCase(), alpha: round3(a) }\n }\n if (hex.length === 6) {\n return { hex: `#${hex}`.toLowerCase(), alpha: 1 }\n }\n if (hex.length === 8) {\n const rgb = hex.slice(0, 6)\n const a = parseInt(hex.slice(6, 8), 16) / 255\n return { hex: `#${rgb}`.toLowerCase(), alpha: round3(a) }\n }\n return null\n}\n\nfunction clampByte(s: string): number | null {\n const n = Number(s)\n if (!Number.isFinite(n) || n < 0 || n > 255) return null\n return Math.round(n)\n}\n\nfunction clampUnit(s: string): number | null {\n const n = Number(s)\n if (!Number.isFinite(n) || n < 0 || n > 1) return null\n return n\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n const h = (n: number) => n.toString(16).padStart(2, '0')\n return `#${h(r)}${h(g)}${h(b)}`\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000\n}\n\n/**\n * 단순 검증 — 정규화 가능 여부만.\n * 에러 메시지에 정정 제안 포함 가능.\n */\nexport function validateColor(input: string | null | undefined): {\n valid: boolean\n error?: string\n suggestion?: string\n} {\n if (typeof input !== 'string') {\n return { valid: false, error: 'Color must be a string.' }\n }\n const parsed = parseColor(input)\n if (parsed) return { valid: true }\n\n // 비슷한 named color 추천\n const lower = input.trim().toLowerCase()\n const allKnown = [...Object.keys(NAMED_COLORS), ...Object.keys(SEMANTIC_COLORS)]\n const close = allKnown.find(k => k.includes(lower) || lower.includes(k))\n return {\n valid: false,\n error: `Invalid color: '${input}'. Use hex (#ff0000), rgb(255,0,0), or named (red, warm-red, ...).`,\n suggestion: close\n }\n}\n\n/**\n * 색상을 hex 문자열로 정규화 (저장용).\n * 실패 시 null.\n */\nexport function normalizeColor(input: string | null | undefined): string | null {\n const parsed = parseColor(input)\n if (!parsed) return null\n return parsed.hex\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* copyStyle — 한 컴포넌트의 styling 을 다른 컴포넌트들에 복사.
|
|
3
|
+
*
|
|
4
|
+
* 자주 쓰는 패턴: "MOTOR1 처럼 다른 모터들도 같게 만들어줘"
|
|
5
|
+
* AI: getComponentByRefid(MOTOR1) 또는 getComponentStyle(MOTOR1) → copyStyle(...)
|
|
6
|
+
*
|
|
7
|
+
* aspects 인자로 어느 styling 영역만 복사할지 선택 가능 — 부분 통일 (예: 색만 통일).
|
|
8
|
+
*
|
|
9
|
+
* 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components
|
|
10
|
+
* 에서 source 찾아 styling 추출 후 target 들에 patch 생성.
|
|
11
|
+
*/
|
|
12
|
+
import type { BoardEditOp } from '../types';
|
|
13
|
+
export interface CopyStyleInput {
|
|
14
|
+
sourceRefid: number;
|
|
15
|
+
targetRefids: number[];
|
|
16
|
+
aspects?: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity' | 'visibility'>;
|
|
17
|
+
}
|
|
18
|
+
export interface OpsResult {
|
|
19
|
+
ops: BoardEditOp[];
|
|
20
|
+
errors: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* input + components 배열 → BoardEditOp[].
|
|
24
|
+
*
|
|
25
|
+
* components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCopyStyleOps(input: CopyStyleInput, components: ReadonlyArray<any>): OpsResult;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCopyStyleOps = buildCopyStyleOps;
|
|
4
|
+
const ASPECT_FIELDS = {
|
|
5
|
+
fill: ['fillStyle'],
|
|
6
|
+
stroke: ['strokeStyle'],
|
|
7
|
+
shadow: ['shadow'],
|
|
8
|
+
material: ['material3d'],
|
|
9
|
+
font: [
|
|
10
|
+
'fontFamily',
|
|
11
|
+
'fontSize',
|
|
12
|
+
'fontWeight',
|
|
13
|
+
'fontStyle',
|
|
14
|
+
'fontColor',
|
|
15
|
+
'textAlign',
|
|
16
|
+
'textBaseline',
|
|
17
|
+
'lineHeight'
|
|
18
|
+
],
|
|
19
|
+
opacity: ['alpha'],
|
|
20
|
+
visibility: ['hidden']
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_ASPECTS = [
|
|
23
|
+
'fill',
|
|
24
|
+
'stroke',
|
|
25
|
+
'shadow',
|
|
26
|
+
'material',
|
|
27
|
+
'font',
|
|
28
|
+
'opacity'
|
|
29
|
+
// visibility 는 default 에서 제외 — 의도 없이 hidden 복사 위험
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* input + components 배열 → BoardEditOp[].
|
|
33
|
+
*
|
|
34
|
+
* components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.
|
|
35
|
+
*/
|
|
36
|
+
function buildCopyStyleOps(input, components) {
|
|
37
|
+
if (typeof input.sourceRefid !== 'number') {
|
|
38
|
+
return { ops: [], errors: ['sourceRefid (number) required'] };
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(input.targetRefids) || input.targetRefids.length === 0) {
|
|
41
|
+
return { ops: [], errors: ['targetRefids must be a non-empty array'] };
|
|
42
|
+
}
|
|
43
|
+
const targets = input.targetRefids.filter(n => typeof n === 'number' && Number.isFinite(n) && n !== input.sourceRefid);
|
|
44
|
+
if (targets.length === 0) {
|
|
45
|
+
return { ops: [], errors: ['No valid target refids (excluding source)'] };
|
|
46
|
+
}
|
|
47
|
+
const source = components.find(c => c && typeof c.refid === 'number' && c.refid === input.sourceRefid);
|
|
48
|
+
if (!source) {
|
|
49
|
+
return {
|
|
50
|
+
ops: [],
|
|
51
|
+
errors: [`Source component refid=${input.sourceRefid} not found`]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const aspects = input.aspects && input.aspects.length > 0 ? input.aspects : DEFAULT_ASPECTS;
|
|
55
|
+
const validAspects = Object.keys(ASPECT_FIELDS);
|
|
56
|
+
const unknown = aspects.filter(a => !validAspects.includes(a));
|
|
57
|
+
if (unknown.length > 0) {
|
|
58
|
+
return {
|
|
59
|
+
ops: [],
|
|
60
|
+
errors: [`Unknown aspect(s): ${unknown.join(', ')}. Valid: ${validAspects.join('/')}`]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// source 에서 aspect 별 필드 추출 → patch 합치기
|
|
64
|
+
const patch = {};
|
|
65
|
+
for (const aspect of aspects) {
|
|
66
|
+
const fields = ASPECT_FIELDS[aspect];
|
|
67
|
+
for (const field of fields) {
|
|
68
|
+
if (source[field] !== undefined) {
|
|
69
|
+
patch[field] = source[field];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (Object.keys(patch).length === 0) {
|
|
74
|
+
return { ops: [], errors: ['Source has no styling for the requested aspects'] };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
ops: targets.map(refid => ({ op: 'modify', refid, patch })),
|
|
78
|
+
errors: []
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=copy-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-tools.js","sourceRoot":"","sources":["../../../server/service/styling/copy-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * copyStyle — 한 컴포넌트의 styling 을 다른 컴포넌트들에 복사.\n *\n * 자주 쓰는 패턴: \"MOTOR1 처럼 다른 모터들도 같게 만들어줘\"\n * AI: getComponentByRefid(MOTOR1) 또는 getComponentStyle(MOTOR1) → copyStyle(...)\n *\n * aspects 인자로 어느 styling 영역만 복사할지 선택 가능 — 부분 통일 (예: 색만 통일).\n *\n * 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components\n * 에서 source 찾아 styling 추출 후 target 들에 patch 생성.\n */\nimport type { BoardEditOp } from '../types'\n\nexport interface CopyStyleInput {\n sourceRefid: number\n targetRefids: number[]\n aspects?: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity' | 'visibility'>\n}\n\nexport interface OpsResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\nconst ASPECT_FIELDS: Record<string, string[]> = {\n fill: ['fillStyle'],\n stroke: ['strokeStyle'],\n shadow: ['shadow'],\n material: ['material3d'],\n font: [\n 'fontFamily',\n 'fontSize',\n 'fontWeight',\n 'fontStyle',\n 'fontColor',\n 'textAlign',\n 'textBaseline',\n 'lineHeight'\n ],\n opacity: ['alpha'],\n visibility: ['hidden']\n}\n\nconst DEFAULT_ASPECTS: Array<keyof typeof ASPECT_FIELDS> = [\n 'fill',\n 'stroke',\n 'shadow',\n 'material',\n 'font',\n 'opacity'\n // visibility 는 default 에서 제외 — 의도 없이 hidden 복사 위험\n]\n\n/**\n * input + components 배열 → BoardEditOp[].\n *\n * components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.\n */\nexport function buildCopyStyleOps(\n input: CopyStyleInput,\n components: ReadonlyArray<any>\n): OpsResult {\n if (typeof input.sourceRefid !== 'number') {\n return { ops: [], errors: ['sourceRefid (number) required'] }\n }\n if (!Array.isArray(input.targetRefids) || input.targetRefids.length === 0) {\n return { ops: [], errors: ['targetRefids must be a non-empty array'] }\n }\n const targets = input.targetRefids.filter(\n n => typeof n === 'number' && Number.isFinite(n) && n !== input.sourceRefid\n )\n if (targets.length === 0) {\n return { ops: [], errors: ['No valid target refids (excluding source)'] }\n }\n\n const source = components.find(\n c => c && typeof c.refid === 'number' && c.refid === input.sourceRefid\n )\n if (!source) {\n return {\n ops: [],\n errors: [`Source component refid=${input.sourceRefid} not found`]\n }\n }\n\n const aspects = input.aspects && input.aspects.length > 0 ? input.aspects : DEFAULT_ASPECTS\n const validAspects = Object.keys(ASPECT_FIELDS)\n const unknown = aspects.filter(a => !validAspects.includes(a))\n if (unknown.length > 0) {\n return {\n ops: [],\n errors: [`Unknown aspect(s): ${unknown.join(', ')}. Valid: ${validAspects.join('/')}`]\n }\n }\n\n // source 에서 aspect 별 필드 추출 → patch 합치기\n const patch: any = {}\n for (const aspect of aspects) {\n const fields = ASPECT_FIELDS[aspect]\n for (const field of fields) {\n if (source[field] !== undefined) {\n patch[field] = source[field]\n }\n }\n }\n\n if (Object.keys(patch).length === 0) {\n return { ops: [], errors: ['Source has no styling for the requested aspects'] }\n }\n\n return {\n ops: targets.map(refid => ({ op: 'modify', refid, patch })),\n errors: []\n }\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setShadow / setOpacity / setVisible — 효과 / 가시성 / 투명도.
|
|
3
|
+
*
|
|
4
|
+
* setShadow: state.shadow (color/blur/offsetX/offsetY)
|
|
5
|
+
* setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)
|
|
6
|
+
* setVisible: state.hidden 토글 (true → 숨김)
|
|
7
|
+
*/
|
|
8
|
+
import type { BoardEditOp } from '../types';
|
|
9
|
+
export interface SetShadowInput {
|
|
10
|
+
refids: number[];
|
|
11
|
+
/** synthetic — false 면 blur=0 + color=transparent 매핑 (drawer 가 안 그림). 모델에 enabled 필드 없음. */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
/** state.shadow.color */
|
|
14
|
+
color?: string;
|
|
15
|
+
/** Blur radius (CSS box-shadow 의 blur-radius, Canvas 의 shadowBlur). state.shadow.blur. */
|
|
16
|
+
blur?: number;
|
|
17
|
+
/** Horizontal offset (CSS box-shadow 의 offset-x, Canvas 의 shadowOffsetX). state.shadow.offsetX. */
|
|
18
|
+
offsetX?: number;
|
|
19
|
+
/** Vertical offset (CSS box-shadow 의 offset-y, Canvas 의 shadowOffsetY). state.shadow.offsetY. */
|
|
20
|
+
offsetY?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface SetOpacityInput {
|
|
23
|
+
refids: number[];
|
|
24
|
+
/** state.alpha 의 canonical 이름. 0..1. */
|
|
25
|
+
alpha: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SetVisibleInput {
|
|
28
|
+
refids: number[];
|
|
29
|
+
visible: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface OpsResult {
|
|
32
|
+
ops: BoardEditOp[];
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* setShadow — CSS box-shadow / Canvas API 표준 이름 (color/offsetX/offsetY/blur) 그대로
|
|
37
|
+
* state.shadow 에 set. things-scene drawer 는 옛 이름 (left/top/blurSize) 도
|
|
38
|
+
* backwards-compat 으로 읽지만 (effect/shadow.ts:36-47), 새 모델은 canonical 만 사용.
|
|
39
|
+
*
|
|
40
|
+
* synthetic enabled=false → blur 0 + transparent 매핑 (drawer 가 안 그림).
|
|
41
|
+
* 모델에 enabled 필드는 없음.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildSetShadowOps(input: SetShadowInput): OpsResult;
|
|
44
|
+
export declare function buildSetOpacityOps(input: SetOpacityInput): OpsResult;
|
|
45
|
+
export declare function buildSetVisibleOps(input: SetVisibleInput): OpsResult;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSetShadowOps = buildSetShadowOps;
|
|
4
|
+
exports.buildSetOpacityOps = buildSetOpacityOps;
|
|
5
|
+
exports.buildSetVisibleOps = buildSetVisibleOps;
|
|
6
|
+
const color_utils_1 = require("./color-utils");
|
|
7
|
+
/**
|
|
8
|
+
* setShadow — CSS box-shadow / Canvas API 표준 이름 (color/offsetX/offsetY/blur) 그대로
|
|
9
|
+
* state.shadow 에 set. things-scene drawer 는 옛 이름 (left/top/blurSize) 도
|
|
10
|
+
* backwards-compat 으로 읽지만 (effect/shadow.ts:36-47), 새 모델은 canonical 만 사용.
|
|
11
|
+
*
|
|
12
|
+
* synthetic enabled=false → blur 0 + transparent 매핑 (drawer 가 안 그림).
|
|
13
|
+
* 모델에 enabled 필드는 없음.
|
|
14
|
+
*/
|
|
15
|
+
function buildSetShadowOps(input) {
|
|
16
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
17
|
+
return { ops: [], errors: ['refids must be a non-empty array'] };
|
|
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'] };
|
|
22
|
+
const noOp = input.enabled === undefined &&
|
|
23
|
+
input.color === undefined &&
|
|
24
|
+
input.blur === undefined &&
|
|
25
|
+
input.offsetX === undefined &&
|
|
26
|
+
input.offsetY === undefined;
|
|
27
|
+
if (noOp)
|
|
28
|
+
return { ops: [], errors: ['No shadow changes specified'] };
|
|
29
|
+
if (input.enabled === false) {
|
|
30
|
+
// synthetic — false 면 blur 0 + transparent 으로 변환 (drawer 가 안 그림)
|
|
31
|
+
const patch = { shadow: { blur: 0, color: 'transparent' } };
|
|
32
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] };
|
|
33
|
+
}
|
|
34
|
+
if (input.color !== undefined) {
|
|
35
|
+
const v = (0, color_utils_1.validateColor)(input.color);
|
|
36
|
+
if (!v.valid)
|
|
37
|
+
return { ops: [], errors: [v.error ?? 'Invalid shadow color'] };
|
|
38
|
+
}
|
|
39
|
+
if (input.blur !== undefined && (input.blur < 0 || !Number.isFinite(input.blur))) {
|
|
40
|
+
return { ops: [], errors: [`Invalid blur: ${input.blur}`] };
|
|
41
|
+
}
|
|
42
|
+
if (input.offsetX !== undefined && !Number.isFinite(input.offsetX)) {
|
|
43
|
+
return { ops: [], errors: [`Invalid offsetX: ${input.offsetX}`] };
|
|
44
|
+
}
|
|
45
|
+
if (input.offsetY !== undefined && !Number.isFinite(input.offsetY)) {
|
|
46
|
+
return { ops: [], errors: [`Invalid offsetY: ${input.offsetY}`] };
|
|
47
|
+
}
|
|
48
|
+
// CSS / Canvas 표준 canonical 이름 — rename 없음
|
|
49
|
+
const sh = {};
|
|
50
|
+
// validateColor 가 통과한 색만 도달 — normalizeColor 가 여기서 null 반환 안 함
|
|
51
|
+
if (input.color !== undefined)
|
|
52
|
+
sh.color = (0, color_utils_1.normalizeColor)(input.color);
|
|
53
|
+
if (input.blur !== undefined)
|
|
54
|
+
sh.blur = input.blur;
|
|
55
|
+
if (input.offsetX !== undefined)
|
|
56
|
+
sh.offsetX = input.offsetX;
|
|
57
|
+
if (input.offsetY !== undefined)
|
|
58
|
+
sh.offsetY = input.offsetY;
|
|
59
|
+
const patch = { shadow: sh };
|
|
60
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] };
|
|
61
|
+
}
|
|
62
|
+
function buildSetOpacityOps(input) {
|
|
63
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
64
|
+
return { ops: [], errors: ['refids must be a non-empty array'] };
|
|
65
|
+
}
|
|
66
|
+
if (typeof input.alpha !== 'number' || input.alpha < 0 || input.alpha > 1) {
|
|
67
|
+
return { ops: [], errors: [`alpha must be 0..1: ${input.alpha}`] };
|
|
68
|
+
}
|
|
69
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n));
|
|
70
|
+
if (refids.length === 0)
|
|
71
|
+
return { ops: [], errors: ['No valid numeric refids'] };
|
|
72
|
+
// canonical 이름 그대로
|
|
73
|
+
const patch = { alpha: input.alpha };
|
|
74
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] };
|
|
75
|
+
}
|
|
76
|
+
function buildSetVisibleOps(input) {
|
|
77
|
+
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
78
|
+
return { ops: [], errors: ['refids must be a non-empty array'] };
|
|
79
|
+
}
|
|
80
|
+
if (typeof input.visible !== 'boolean') {
|
|
81
|
+
return { ops: [], errors: ['visible must be a boolean'] };
|
|
82
|
+
}
|
|
83
|
+
const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n));
|
|
84
|
+
if (refids.length === 0)
|
|
85
|
+
return { ops: [], errors: ['No valid numeric refids'] };
|
|
86
|
+
// hidden 은 visible 의 inverse
|
|
87
|
+
const patch = { hidden: !input.visible };
|
|
88
|
+
return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=effect-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect-tools.js","sourceRoot":"","sources":["../../../server/service/styling/effect-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setShadow / setOpacity / setVisible — 효과 / 가시성 / 투명도.\n *\n * setShadow: state.shadow (color/blur/offsetX/offsetY)\n * setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)\n * setVisible: state.hidden 토글 (true → 숨김)\n */\nimport type { BoardEditOp } from '../types'\nimport { normalizeColor, validateColor } from './color-utils'\n\nexport interface SetShadowInput {\n refids: number[]\n /** synthetic — false 면 blur=0 + color=transparent 매핑 (drawer 가 안 그림). 모델에 enabled 필드 없음. */\n enabled?: boolean\n /** state.shadow.color */\n color?: string\n /** Blur radius (CSS box-shadow 의 blur-radius, Canvas 의 shadowBlur). state.shadow.blur. */\n blur?: number\n /** Horizontal offset (CSS box-shadow 의 offset-x, Canvas 의 shadowOffsetX). state.shadow.offsetX. */\n offsetX?: number\n /** Vertical offset (CSS box-shadow 의 offset-y, Canvas 의 shadowOffsetY). state.shadow.offsetY. */\n offsetY?: number\n}\n\nexport interface SetOpacityInput {\n refids: number[]\n /** state.alpha 의 canonical 이름. 0..1. */\n alpha: number\n}\n\nexport interface SetVisibleInput {\n refids: number[]\n visible: boolean\n}\n\nexport interface OpsResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\n/**\n * setShadow — CSS box-shadow / Canvas API 표준 이름 (color/offsetX/offsetY/blur) 그대로\n * state.shadow 에 set. things-scene drawer 는 옛 이름 (left/top/blurSize) 도\n * backwards-compat 으로 읽지만 (effect/shadow.ts:36-47), 새 모델은 canonical 만 사용.\n *\n * synthetic enabled=false → blur 0 + transparent 매핑 (drawer 가 안 그림).\n * 모델에 enabled 필드는 없음.\n */\nexport function buildSetShadowOps(input: SetShadowInput): 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.enabled === undefined &&\n input.color === undefined &&\n input.blur === undefined &&\n input.offsetX === undefined &&\n input.offsetY === undefined\n if (noOp) return { ops: [], errors: ['No shadow changes specified'] }\n\n if (input.enabled === false) {\n // synthetic — false 면 blur 0 + transparent 으로 변환 (drawer 가 안 그림)\n const patch = { shadow: { blur: 0, color: 'transparent' } }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n }\n\n if (input.color !== undefined) {\n const v = validateColor(input.color)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid shadow color'] }\n }\n if (input.blur !== undefined && (input.blur < 0 || !Number.isFinite(input.blur))) {\n return { ops: [], errors: [`Invalid blur: ${input.blur}`] }\n }\n if (input.offsetX !== undefined && !Number.isFinite(input.offsetX)) {\n return { ops: [], errors: [`Invalid offsetX: ${input.offsetX}`] }\n }\n if (input.offsetY !== undefined && !Number.isFinite(input.offsetY)) {\n return { ops: [], errors: [`Invalid offsetY: ${input.offsetY}`] }\n }\n\n // CSS / Canvas 표준 canonical 이름 — rename 없음\n const sh: Record<string, number | string> = {}\n // validateColor 가 통과한 색만 도달 — normalizeColor 가 여기서 null 반환 안 함\n if (input.color !== undefined) sh.color = normalizeColor(input.color)!\n if (input.blur !== undefined) sh.blur = input.blur\n if (input.offsetX !== undefined) sh.offsetX = input.offsetX\n if (input.offsetY !== undefined) sh.offsetY = input.offsetY\n\n const patch = { shadow: sh }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n\nexport function buildSetOpacityOps(input: SetOpacityInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n if (typeof input.alpha !== 'number' || input.alpha < 0 || input.alpha > 1) {\n return { ops: [], errors: [`alpha must be 0..1: ${input.alpha}`] }\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 // canonical 이름 그대로\n const patch = { alpha: input.alpha }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n\nexport function buildSetVisibleOps(input: SetVisibleInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n if (typeof input.visible !== 'boolean') {\n return { ops: [], errors: ['visible must be a boolean'] }\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 // hidden 은 visible 의 inverse\n const patch = { hidden: !input.visible }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setFill — 컴포넌트 배경 (state.fillStyle) 변경.
|
|
3
|
+
*
|
|
4
|
+
* 4 modes 지원: none / solid / gradient / pattern.
|
|
5
|
+
*
|
|
6
|
+
* 출력 정책 — refid 당 1 op:
|
|
7
|
+
* - solid: fillStyle = '#rrggbb' or 'rgba(...)' (string)
|
|
8
|
+
* - gradient: fillStyle = { type:'gradient', gradientType, colorStops, rotation, ... }
|
|
9
|
+
* - pattern: fillStyle = { type:'pattern', image, ... }
|
|
10
|
+
* - none: fillStyle = { type:'none' }
|
|
11
|
+
* - mode 미지정 + color 만: 부분 update (string 또는 object 병합)
|
|
12
|
+
*
|
|
13
|
+
* things-scene 실제 데이터 형식 (drawer/fill.ts 검증):
|
|
14
|
+
* - gradient: { type, gradientType, colorStops: [{position, color}], rotation (라디안) }
|
|
15
|
+
* - rotation 은 라디안 — `Math.cos(fRotation)` 직접 사용
|
|
16
|
+
*
|
|
17
|
+
* 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture
|
|
18
|
+
* 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.
|
|
19
|
+
*/
|
|
20
|
+
import type { BoardEditOp } from '../types';
|
|
21
|
+
export interface SetFillInput {
|
|
22
|
+
refids: number[];
|
|
23
|
+
mode?: 'none' | 'solid' | 'gradient' | 'pattern';
|
|
24
|
+
color?: string;
|
|
25
|
+
gradientType?: 'linear' | 'radial';
|
|
26
|
+
/** colorStops — things-scene 의 canonical 필드명 그대로. position 0..1, color hex/rgb/named. */
|
|
27
|
+
colorStops?: Array<{
|
|
28
|
+
color: string;
|
|
29
|
+
position: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** 회전 — 입력은 degrees (사용자/AI 친화), 내부 변환 후 things-scene 의 라디안으로 저장. */
|
|
32
|
+
rotation?: number;
|
|
33
|
+
image?: string;
|
|
34
|
+
/** pattern: 영역에 맞춰 비율 조정 (true) vs 원본 크기 사용 (false). */
|
|
35
|
+
fitPattern?: boolean;
|
|
36
|
+
/** pattern: 반복 안 함 (true) — 1회만 그림. */
|
|
37
|
+
noRepeat?: boolean;
|
|
38
|
+
/** pattern: 정렬. fitPattern=false 일 때만 적용. */
|
|
39
|
+
align?: 'center' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom' | 'left' | 'right' | 'top' | 'bottom';
|
|
40
|
+
/** pattern: 영역 내 오프셋 (fitPattern=false 일 때). */
|
|
41
|
+
offsetX?: number;
|
|
42
|
+
offsetY?: number;
|
|
43
|
+
/** pattern: 패턴 크기 (기본은 이미지 원본 크기). */
|
|
44
|
+
patternWidth?: number;
|
|
45
|
+
patternHeight?: number;
|
|
46
|
+
opacity?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface SetFillResult {
|
|
49
|
+
ops: BoardEditOp[];
|
|
50
|
+
errors: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* setFill 입력 검증 + BoardEditOp[] 생성.
|
|
54
|
+
*
|
|
55
|
+
* 기본 정책:
|
|
56
|
+
* - refids 비어있거나 mode 도 색도 없으면 에러
|
|
57
|
+
* - 색상 입력은 normalizeColor 통과해야 적용
|
|
58
|
+
* - mode='gradient' 인데 stops 없으면 자동 생성 (color → 어두운 변형)
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildSetFillOps(input: SetFillInput): SetFillResult;
|