@things-factory/board-ai 10.0.0-beta.81 → 10.0.0-beta.83
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/dist-client/server/service/styling/effect-tools.d.ts +5 -4
- package/dist-client/server/service/styling/effect-tools.js +12 -4
- package/dist-client/server/service/styling/effect-tools.js.map +1 -1
- package/dist-client/server/service/styling/stroke-tools.js +17 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -1
- package/dist-client/server/service/validation/board-model-schema.d.ts +258 -258
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/styling/effect-tools.d.ts +5 -4
- package/dist-server/service/styling/effect-tools.js +12 -4
- package/dist-server/service/styling/effect-tools.js.map +1 -1
- package/dist-server/service/styling/stroke-tools.js +17 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +258 -258
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/styling/effect-tools.test.ts +18 -0
- package/server/service/styling/effect-tools.ts +14 -6
- package/server/service/styling/stroke-tools.test.ts +14 -0
- package/server/service/styling/stroke-tools.ts +18 -0
|
@@ -33,11 +33,12 @@ export interface OpsResult {
|
|
|
33
33
|
errors: string[];
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* setShadow —
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
39
|
*
|
|
40
|
-
*
|
|
40
|
+
* synthetic enabled=false → blur 0 + transparent 매핑 (drawer 가 안 그림).
|
|
41
|
+
* 모델에 enabled 필드는 없음.
|
|
41
42
|
*/
|
|
42
43
|
export declare function buildSetShadowOps(input: SetShadowInput): OpsResult;
|
|
43
44
|
export declare function buildSetOpacityOps(input: SetOpacityInput): OpsResult;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { normalizeColor, validateColor } from './color-utils';
|
|
2
2
|
/**
|
|
3
|
-
* setShadow —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* setShadow — CSS box-shadow / Canvas API 표준 이름 (color/offsetX/offsetY/blur) 그대로
|
|
4
|
+
* state.shadow 에 set. things-scene drawer 는 옛 이름 (left/top/blurSize) 도
|
|
5
|
+
* backwards-compat 으로 읽지만 (effect/shadow.ts:36-47), 새 모델은 canonical 만 사용.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* synthetic enabled=false → blur 0 + transparent 매핑 (drawer 가 안 그림).
|
|
8
|
+
* 모델에 enabled 필드는 없음.
|
|
8
9
|
*/
|
|
9
10
|
export function buildSetShadowOps(input) {
|
|
10
11
|
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
@@ -33,8 +34,15 @@ export function buildSetShadowOps(input) {
|
|
|
33
34
|
if (input.blur !== undefined && (input.blur < 0 || !Number.isFinite(input.blur))) {
|
|
34
35
|
return { ops: [], errors: [`Invalid blur: ${input.blur}`] };
|
|
35
36
|
}
|
|
37
|
+
if (input.offsetX !== undefined && !Number.isFinite(input.offsetX)) {
|
|
38
|
+
return { ops: [], errors: [`Invalid offsetX: ${input.offsetX}`] };
|
|
39
|
+
}
|
|
40
|
+
if (input.offsetY !== undefined && !Number.isFinite(input.offsetY)) {
|
|
41
|
+
return { ops: [], errors: [`Invalid offsetY: ${input.offsetY}`] };
|
|
42
|
+
}
|
|
36
43
|
// CSS / Canvas 표준 canonical 이름 — rename 없음
|
|
37
44
|
const sh = {};
|
|
45
|
+
// validateColor 가 통과한 색만 도달 — normalizeColor 가 여기서 null 반환 안 함
|
|
38
46
|
if (input.color !== undefined)
|
|
39
47
|
sh.color = normalizeColor(input.color);
|
|
40
48
|
if (input.blur !== undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/effect-tools.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAgC7D
|
|
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"]}
|
|
@@ -15,6 +15,11 @@ const VALID_DASH = new Set([
|
|
|
15
15
|
'long-dash-dot',
|
|
16
16
|
'long-dash-dot-dot'
|
|
17
17
|
]);
|
|
18
|
+
// Canvas CanvasRenderingContext2D 가 허용하는 lineCap / lineJoin 만.
|
|
19
|
+
// 잘못된 값은 Canvas 가 silent ignore → AI 가 'flat' / 'cap' 등을 보내도
|
|
20
|
+
// 효과 없음. 명시 검증으로 ops 미실행 + 명확한 에러.
|
|
21
|
+
const VALID_LINE_CAP = new Set(['butt', 'round', 'square']);
|
|
22
|
+
const VALID_LINE_JOIN = new Set(['miter', 'round', 'bevel']);
|
|
18
23
|
export function buildSetStrokeOps(input) {
|
|
19
24
|
if (!Array.isArray(input.refids) || input.refids.length === 0) {
|
|
20
25
|
return { ops: [], errors: ['refids must be a non-empty array of numbers'] };
|
|
@@ -40,6 +45,18 @@ export function buildSetStrokeOps(input) {
|
|
|
40
45
|
if (input.lineWidth !== undefined && (input.lineWidth < 0 || !Number.isFinite(input.lineWidth))) {
|
|
41
46
|
return { ops: [], errors: [`Invalid lineWidth: ${input.lineWidth}`] };
|
|
42
47
|
}
|
|
48
|
+
if (input.lineCap !== undefined && !VALID_LINE_CAP.has(input.lineCap)) {
|
|
49
|
+
return {
|
|
50
|
+
ops: [],
|
|
51
|
+
errors: [`Unknown lineCap: '${input.lineCap}'. Valid: ${[...VALID_LINE_CAP].join(' / ')}.`]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (input.lineJoin !== undefined && !VALID_LINE_JOIN.has(input.lineJoin)) {
|
|
55
|
+
return {
|
|
56
|
+
ops: [],
|
|
57
|
+
errors: [`Unknown lineJoin: '${input.lineJoin}'. Valid: ${[...VALID_LINE_JOIN].join(' / ')}.`]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
43
60
|
// dash 정규화 — alias → 실제 preset → 검증
|
|
44
61
|
let dashValue;
|
|
45
62
|
if (input.dash !== undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stroke-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/stroke-tools.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAiC7D,iDAAiD;AACjD,MAAM,YAAY,GAA2B;IAC3C,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,UAAU;CACpB,CAAA;AAED,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,OAAO;IACP,WAAW;IACX,YAAY;IACZ,MAAM;IACN,UAAU;IACV,WAAW;IACX,eAAe;IACf,mBAAmB;CACpB,CAAC,CAAA;AAEF,MAAM,UAAU,iBAAiB,CAAC,KAAqB;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,6CAA6C,CAAC,EAAE,CAAA;IAC7E,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACpF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,yBAAyB,CAAC,EAAE,CAAA;IAEhF,MAAM,IAAI,GACR,KAAK,CAAC,OAAO,KAAK,SAAS;QAC3B,KAAK,CAAC,WAAW,KAAK,SAAS;QAC/B,KAAK,CAAC,SAAS,KAAK,SAAS;QAC7B,KAAK,CAAC,IAAI,KAAK,SAAS;QACxB,KAAK,CAAC,OAAO,KAAK,SAAS;QAC3B,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAA;IAC9B,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,6BAA6B,CAAC,EAAE,CAAA;IAC7D,CAAC;IAED,KAAK;IACL,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QAC1C,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,qBAAqB,CAAC,EAAE,CAAA;IAC9E,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,sBAAsB,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,CAAA;IACvE,CAAC;IAED,oCAAoC;IACpC,IAAI,SAA6B,CAAA;IACjC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC9B,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,GAAG,CAAA;QACzC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,OAAO;gBACL,GAAG,EAAE,EAAE;gBACP,MAAM,EAAE;oBACN,kBAAkB,KAAK,CAAC,IAAI,aAAa,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,qCAAqC;iBAC1G;aACF,CAAA;QACH,CAAC;QACD,SAAS,GAAG,QAAQ,CAAA;IACtB,CAAC;IAED,2FAA2F;IAC3F,MAAM,KAAK,GAAQ,EAAE,CAAA;IACrB,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IAC1F,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACpE,IAAI,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,CAAA;IACvD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;IAC9D,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;IAEjE,qFAAqF;IACrF,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC5B,KAAK,CAAC,SAAS,GAAG,CAAC,CAAA;IACrB,CAAC;IAED,MAAM,GAAG,GAAkB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAChF,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["/**\n * setStroke — 컴포넌트 테두리 / 라인 스타일.\n *\n * **things-scene 의 실제 데이터 형식 (drawer/stroke.ts 검증):**\n * - `state.strokeStyle: string` (색상 — Canvas API 의 strokeStyle 에 직접 대입되는 CSS color)\n * - `state.lineWidth: number` (top-level)\n * - `state.lineDash: string` (top-level — preset 이름. drawer 가 lineWidth 기반으로 배열 변환)\n * valid: 'solid' | 'round-dot' | 'square-dot' | 'dash' | 'dash-dot' | 'long-dash' | 'long-dash-dot' | 'long-dash-dot-dot'\n * - `state.lineCap: 'butt' | 'round' | 'square'` (top-level)\n * - `state.lineJoin: 'miter' | 'round' | 'bevel'` (top-level)\n *\n * 인터페이스의 `StrokeStyle` 객체 구조는 stale. 사용처는 모두 top-level 직접 접근.\n *\n * 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:\n * AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.\n */\nimport type { BoardEditOp } from '../types'\nimport { normalizeColor, validateColor } from './color-utils'\n\nexport interface SetStrokeInput {\n refids: number[]\n /** synthetic — false 면 lineWidth=0 으로 매핑 (drawer 가 안 그림). 모델에 enabled 필드는 안 박힘. */\n enabled?: boolean\n /** state.strokeStyle 의 canonical 이름. CSS color string. */\n strokeStyle?: string\n /** state.lineWidth 의 canonical 이름. pixels. */\n lineWidth?: number\n /** preset 이름. AI 친화 alias 자동 매핑 — 'dashed' / 'dotted' / 'dashdot' 도 가능. */\n dash?:\n | 'solid'\n | 'dashed'\n | 'dotted'\n | 'dashdot'\n | 'round-dot'\n | 'square-dot'\n | 'dash'\n | 'dash-dot'\n | 'long-dash'\n | 'long-dash-dot'\n | 'long-dash-dot-dot'\n | string\n lineCap?: 'butt' | 'round' | 'square'\n lineJoin?: 'miter' | 'round' | 'bevel'\n}\n\nexport interface SetStrokeResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\n/** AI 친화 alias → things-scene 의 실제 preset 이름. */\nconst DASH_ALIASES: Record<string, string> = {\n dashed: 'dash',\n dotted: 'square-dot',\n dashdot: 'dash-dot'\n}\n\nconst VALID_DASH = new Set([\n 'solid',\n 'round-dot',\n 'square-dot',\n 'dash',\n 'dash-dot',\n 'long-dash',\n 'long-dash-dot',\n 'long-dash-dot-dot'\n])\n\nexport function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult {\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) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp =\n input.enabled === undefined &&\n input.strokeStyle === undefined &&\n input.lineWidth === undefined &&\n input.dash === undefined &&\n input.lineCap === undefined &&\n input.lineJoin === undefined\n if (noOp) {\n return { ops: [], errors: ['No stroke changes specified'] }\n }\n\n // 검증\n if (input.strokeStyle !== undefined) {\n const v = validateColor(input.strokeStyle)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid strokeStyle'] }\n }\n if (input.lineWidth !== undefined && (input.lineWidth < 0 || !Number.isFinite(input.lineWidth))) {\n return { ops: [], errors: [`Invalid lineWidth: ${input.lineWidth}`] }\n }\n\n // dash 정규화 — alias → 실제 preset → 검증\n let dashValue: string | undefined\n if (input.dash !== undefined) {\n const raw = String(input.dash)\n const resolved = DASH_ALIASES[raw] ?? raw\n if (!VALID_DASH.has(resolved)) {\n return {\n ops: [],\n errors: [\n `Unknown dash: '${input.dash}'. Valid: ${[...VALID_DASH].join(' / ')} (or alias: dashed/dotted/dashdot).`\n ]\n }\n }\n dashValue = resolved\n }\n\n // patch 구성 — canonical 이름 그대로. 매핑 X. (color 만 normalizeColor 통과 — 입력 정규화는 mapping 이 아닌 처리)\n const patch: any = {}\n if (input.strokeStyle !== undefined) patch.strokeStyle = normalizeColor(input.strokeStyle)\n if (input.lineWidth !== undefined) patch.lineWidth = input.lineWidth\n if (dashValue !== undefined) patch.lineDash = dashValue\n if (input.lineCap !== undefined) patch.lineCap = input.lineCap\n if (input.lineJoin !== undefined) patch.lineJoin = input.lineJoin\n\n // synthetic enabled — false 면 lineWidth 0 으로 변환 (drawer 가 안 그림 — drawer/stroke.ts:9)\n if (input.enabled === false) {\n patch.lineWidth = 0\n }\n\n const ops: BoardEditOp[] = refids.map(refid => ({ op: 'modify', refid, patch }))\n return { ops, errors: [] }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stroke-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/stroke-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setStroke — 컴포넌트 테두리 / 라인 스타일.\n *\n * **things-scene 의 실제 데이터 형식 (drawer/stroke.ts 검증):**\n * - `state.strokeStyle: string` (색상 — Canvas API 의 strokeStyle 에 직접 대입되는 CSS color)\n * - `state.lineWidth: number` (top-level)\n * - `state.lineDash: string` (top-level — preset 이름. drawer 가 lineWidth 기반으로 배열 변환)\n * valid: 'solid' | 'round-dot' | 'square-dot' | 'dash' | 'dash-dot' | 'long-dash' | 'long-dash-dot' | 'long-dash-dot-dot'\n * - `state.lineCap: 'butt' | 'round' | 'square'` (top-level)\n * - `state.lineJoin: 'miter' | 'round' | 'bevel'` (top-level)\n *\n * 인터페이스의 `StrokeStyle` 객체 구조는 stale. 사용처는 모두 top-level 직접 접근.\n *\n * 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:\n * AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.\n */\nimport type { BoardEditOp } from '../types'\nimport { normalizeColor, validateColor } from './color-utils'\n\nexport interface SetStrokeInput {\n refids: number[]\n /** synthetic — false 면 lineWidth=0 으로 매핑 (drawer 가 안 그림). 모델에 enabled 필드는 안 박힘. */\n enabled?: boolean\n /** state.strokeStyle 의 canonical 이름. CSS color string. */\n strokeStyle?: string\n /** state.lineWidth 의 canonical 이름. pixels. */\n lineWidth?: number\n /** preset 이름. AI 친화 alias 자동 매핑 — 'dashed' / 'dotted' / 'dashdot' 도 가능. */\n dash?:\n | 'solid'\n | 'dashed'\n | 'dotted'\n | 'dashdot'\n | 'round-dot'\n | 'square-dot'\n | 'dash'\n | 'dash-dot'\n | 'long-dash'\n | 'long-dash-dot'\n | 'long-dash-dot-dot'\n | string\n lineCap?: 'butt' | 'round' | 'square'\n lineJoin?: 'miter' | 'round' | 'bevel'\n}\n\nexport interface SetStrokeResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\n/** AI 친화 alias → things-scene 의 실제 preset 이름. */\nconst DASH_ALIASES: Record<string, string> = {\n dashed: 'dash',\n dotted: 'square-dot',\n dashdot: 'dash-dot'\n}\n\nconst VALID_DASH = new Set([\n 'solid',\n 'round-dot',\n 'square-dot',\n 'dash',\n 'dash-dot',\n 'long-dash',\n 'long-dash-dot',\n 'long-dash-dot-dot'\n])\n\n// Canvas CanvasRenderingContext2D 가 허용하는 lineCap / lineJoin 만.\n// 잘못된 값은 Canvas 가 silent ignore → AI 가 'flat' / 'cap' 등을 보내도\n// 효과 없음. 명시 검증으로 ops 미실행 + 명확한 에러.\nconst VALID_LINE_CAP = new Set(['butt', 'round', 'square'])\nconst VALID_LINE_JOIN = new Set(['miter', 'round', 'bevel'])\n\nexport function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult {\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) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp =\n input.enabled === undefined &&\n input.strokeStyle === undefined &&\n input.lineWidth === undefined &&\n input.dash === undefined &&\n input.lineCap === undefined &&\n input.lineJoin === undefined\n if (noOp) {\n return { ops: [], errors: ['No stroke changes specified'] }\n }\n\n // 검증\n if (input.strokeStyle !== undefined) {\n const v = validateColor(input.strokeStyle)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid strokeStyle'] }\n }\n if (input.lineWidth !== undefined && (input.lineWidth < 0 || !Number.isFinite(input.lineWidth))) {\n return { ops: [], errors: [`Invalid lineWidth: ${input.lineWidth}`] }\n }\n if (input.lineCap !== undefined && !VALID_LINE_CAP.has(input.lineCap)) {\n return {\n ops: [],\n errors: [`Unknown lineCap: '${input.lineCap}'. Valid: ${[...VALID_LINE_CAP].join(' / ')}.`]\n }\n }\n if (input.lineJoin !== undefined && !VALID_LINE_JOIN.has(input.lineJoin)) {\n return {\n ops: [],\n errors: [`Unknown lineJoin: '${input.lineJoin}'. Valid: ${[...VALID_LINE_JOIN].join(' / ')}.`]\n }\n }\n\n // dash 정규화 — alias → 실제 preset → 검증\n let dashValue: string | undefined\n if (input.dash !== undefined) {\n const raw = String(input.dash)\n const resolved = DASH_ALIASES[raw] ?? raw\n if (!VALID_DASH.has(resolved)) {\n return {\n ops: [],\n errors: [\n `Unknown dash: '${input.dash}'. Valid: ${[...VALID_DASH].join(' / ')} (or alias: dashed/dotted/dashdot).`\n ]\n }\n }\n dashValue = resolved\n }\n\n // patch 구성 — canonical 이름 그대로. 매핑 X. (color 만 normalizeColor 통과 — 입력 정규화는 mapping 이 아닌 처리)\n const patch: any = {}\n if (input.strokeStyle !== undefined) patch.strokeStyle = normalizeColor(input.strokeStyle)\n if (input.lineWidth !== undefined) patch.lineWidth = input.lineWidth\n if (dashValue !== undefined) patch.lineDash = dashValue\n if (input.lineCap !== undefined) patch.lineCap = input.lineCap\n if (input.lineJoin !== undefined) patch.lineJoin = input.lineJoin\n\n // synthetic enabled — false 면 lineWidth 0 으로 변환 (drawer 가 안 그림 — drawer/stroke.ts:9)\n if (input.enabled === false) {\n patch.lineWidth = 0\n }\n\n const ops: BoardEditOp[] = refids.map(refid => ({ op: 'modify', refid, patch }))\n return { ops, errors: [] }\n}\n"]}
|