From 5a9491bd6964c1f314581ab27abe8c3fc062af99 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 22:28:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore(plan):=2041=EC=9E=A5=20=EB=A9=B4?= =?UTF-8?q?=EC=A0=81=EC=82=B0=EC=B6=9C=20=ED=91=9C=20=E2=86=92=20=EB=8F=84?= =?UTF-8?q?=EB=A9=B4=20=ED=91=9C=20=E2=80=94=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=20=EC=9B=90=EB=AC=B8=20=EB=8D=94=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FJqWQpoB1RE7hiaQ2wH8qV --- docs/raw/M02_구조물조합개편/사용자요구_1004_2.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md b/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md index 30d4b5a29..3651b8c87 100644 --- a/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md +++ b/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md @@ -19,3 +19,7 @@ 3. 면적산출 상세 - 알약지정은 현재 방법 + 그래그 & 드랍 방식을 추가 - 지정된 알약정보를 색상 자동지정 및 지정된 셀은 색상으로 구분을 할수 있게 반영, 반짝임은 유지 + +## 추가 기능 (10-4 밤 · 원문 그대로) + +추가기능 부여. 면적산출에서 특정 영역을 선정하고 도면영역으로 전달을 할 경우 도면에 테이블을 자동으로 넣고 셀의 내용을 싱크하는 기능을 부여. 만약 동기화가 어려운 경우 업데이트 버튼을 추가해도 무관, 복수개의 테이블이 넘어갈수도 잇으며 위치는 도면에서 사용자가 드래그하여 지정 From 3ad007683ced9f382af2180d4761b9b3d24cfbdc Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 22:29:26 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(spreadsheet):=20=EC=95=8C=EC=95=BD=20?= =?UTF-8?q?=EB=81=8C=EC=96=B4=20=EC=B9=B8=EC=97=90=20=EB=86=93=EA=B8=B0=20?= =?UTF-8?q?=C2=B7=20=EC=9D=B4=EC=9D=80=20=EC=95=8C=EC=95=BD=20=EC=83=89=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=E2=80=94=20=ED=82=A4=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=ED=95=B4=EC=A7=80=EB=8A=94=20=EC=83=89(24=20=EC=83=89=20=C2=B7?= =?UTF-8?q?=20=EB=91=90=20=ED=85=8C=EB=A7=88=20=EB=B0=9D=EA=B8=B0)=20?= =?UTF-8?q?=C2=B7=20=EC=9D=B4=EC=9D=80=20=EC=B9=B8=20=EB=B0=B0=EA=B2=BD=20?= =?UTF-8?q?=EA=B0=99=EC=9D=80=20=EC=83=89=20=EC=98=85=EA=B2=8C=20=C2=B7=20?= =?UTF-8?q?=EB=8B=A4=EB=A5=B8=20=EC=95=8C=EC=95=BD=20=EC=B9=B8=EC=97=90=20?= =?UTF-8?q?=EB=86=93=EC=9C=BC=EB=A9=B4=20=EB=B0=94=EA=BF=80=EC=A7=80=20?= =?UTF-8?q?=EB=AC=BC=EC=9D=8C=20=C2=B7=20=EB=B0=98=EC=A7=9D=EC=9E=84=20?= =?UTF-8?q?=EA=B7=B8=EB=8C=80=EB=A1=9C=20(PLAN=2040-3=20=E2=91=A1=20?= =?UTF-8?q?=E2=91=A2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4 --- .../spreadsheet/spreadsheet_link_color.ts | 38 +++++++ A00_Common/spreadsheet/spreadsheet_links.css | 55 ++++++++-- A00_Common/spreadsheet/spreadsheet_links.ts | 103 +++++++++++++++--- .../spreadsheet/spreadsheet_link_set_entry.ts | 24 ++++ resources/tester/test_spreadsheet_set_link.py | 18 ++- 5 files changed, 217 insertions(+), 21 deletions(-) create mode 100644 A00_Common/spreadsheet/spreadsheet_link_color.ts diff --git a/A00_Common/spreadsheet/spreadsheet_link_color.ts b/A00_Common/spreadsheet/spreadsheet_link_color.ts new file mode 100644 index 000000000..7e77b8a02 --- /dev/null +++ b/A00_Common/spreadsheet/spreadsheet_link_color.ts @@ -0,0 +1,38 @@ +/* ============================================================================= + * spreadsheet_link_color.ts (주인 D) + * 이은 알약 색 — 알약 키로 정해지는 색상(hue). 알약 · 이은 칸 배경 · 칸 세모가 같은 색(PLAN 40-3). + * 칸 24 개(30° 바퀴 12 + 15° 어긋난 바퀴 12) — 키 해시로 첫 자리 · 겹치면 다음 빈 자리(같은 문서 안에서 서로 다르게). + * 밝기 · 채도는 테마마다 CSS(`spreadsheet_links.css` `--ss-link-l`) · 여기는 색상만 · DOM 모름(시험이 Node 로 부름). + * ========================================================================== */ + +const SLOTS = 24; + +/** FNV-1a 32비트 — 키 글자로 늘 같은 수 */ +function hash(text: string): number { + let h = 0x811c9dc5; + for (let i = 0; i < text.length; i += 1) { + h ^= text.charCodeAt(i); + h = Math.imul(h, 0x01000193) >>> 0; + } + return h; +} + +/** 자리 → 색상(°) — 0~11 = 30° 간격 · 12~23 = 그 사이(15° 어긋남) */ +const hueOf = (slot: number): number => (slot % 12) * 30 + (slot >= 12 ? 15 : 0); + +/** 이은 알약 키들 → 키마다 색상(°) — 키 차례와 무관(정렬해 자리 배정) · 24 개 넘으면 다시 돎 */ +export function linkHues(keys: Iterable): Map { + const out = new Map(); + const used = new Set(); + for (const key of [...new Set(keys)].sort()) { + if (used.size >= SLOTS) used.clear(); + const start = hash(key) % 12; + // 첫 바퀴(30°)를 다 쓴 뒤에야 둘째 바퀴 — 적을 때 서로 30° 넘게 벌어짐 + let slot = start; + for (let i = 1; i < SLOTS && used.has(slot); i += 1) + slot = (i < 12 ? 0 : 12) + ((start + i) % 12); + used.add(slot); + out.set(key, hueOf(slot)); + } + return out; +} diff --git a/A00_Common/spreadsheet/spreadsheet_links.css b/A00_Common/spreadsheet/spreadsheet_links.css index b61fdf11e..edecb144f 100644 --- a/A00_Common/spreadsheet/spreadsheet_links.css +++ b/A00_Common/spreadsheet/spreadsheet_links.css @@ -33,14 +33,55 @@ color: var(--color-accent); } -/* 연결됨 — 흐림(연결 현황) */ -.ss-link.is-linked { - opacity: 0.5; - border-style: dashed; +/* 이은 알약 색(40-3) — 색상 = 알약 키(`--ss-link-h` · spreadsheet_link_color) · 밝기 · 옅기 = 테마 */ +.ss { + --ss-link-s: 70%; + --ss-link-l: 31%; + --ss-link-fill: 0.16; } -.ss-link.is-linked:hover { - opacity: 0.8; +:root[data-theme="dark"] .ss { + --ss-link-l: 66%; + --ss-link-fill: 0.26; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) .ss { + --ss-link-l: 66%; + --ss-link-fill: 0.26; + } +} + +/* 연결됨 — 알약 색 테두리 · 왼쪽 띠 · 옅은 배경(글은 본문 색 그대로 · 읽힘) */ +.ss-link.is-linked { + border-color: hsl(var(--ss-link-h, 0) var(--ss-link-s) var(--ss-link-l)); + background: hsl(var(--ss-link-h, 0) var(--ss-link-s) 50% / var(--ss-link-fill)); + box-shadow: inset 4px 0 0 hsl(var(--ss-link-h, 0) var(--ss-link-s) var(--ss-link-l)); +} + +.ss-link.is-linked code { + color: inherit; +} + +.ss-link[draggable="true"] { + cursor: grab; +} + +/* 이은 칸 — 알약 색 옅게(칸 글 위 · 누름은 칸으로 지나감) */ +.ss-link-cell { + position: absolute; + z-index: 2; + background: hsl(var(--ss-link-h, 0) var(--ss-link-s) 50% / var(--ss-link-fill)); + pointer-events: none; +} + +/* 알약을 끌 때 놓일 칸 */ +.ss-link-drop { + position: absolute; + z-index: 4; + box-sizing: border-box; + border: 2px dashed var(--color-accent); + pointer-events: none; } .ss-link--add { @@ -55,7 +96,7 @@ width: 0; height: 0; border-top: 6px solid transparent; - border-left: 6px solid var(--color-accent); + border-left: 6px solid hsl(var(--ss-link-h, 0) var(--ss-link-s) var(--ss-link-l)); pointer-events: none; } diff --git a/A00_Common/spreadsheet/spreadsheet_links.ts b/A00_Common/spreadsheet/spreadsheet_links.ts index 2890bef60..45e9a1572 100644 --- a/A00_Common/spreadsheet/spreadsheet_links.ts +++ b/A00_Common/spreadsheet/spreadsheet_links.ts @@ -1,8 +1,9 @@ /* ============================================================================= * spreadsheet_links.ts (주인 D) * 아래 알약 막대(표 · 탭 · 합계 줄 아래 맨 끝) — 면적산출(입력값) · 재료산출(자재) 알약을 칸에 연결(PLAN 13장). - * 칸을 고른 채 밝은 알약을 누르면 그 칸에 연결(알약 흐려짐 + 칸 주소 · 칸 왼아래 작은 세모) · - * 흐린 알약을 누르면 연결 칸으로 · 우클릭 [연결 풀기] · 자재 모드는 [자재 더하기] → 고르기 창 → 새 알약. + * 칸을 고른 채 밝은 알약을 누르면 그 칸에 연결 · 알약을 끌어 칸에 놓아도 연결(놓은 칸에 다른 알약이 있으면 + * 바꿀지 물음 · 40-3) · 이은 알약 = 키로 정해지는 색(`spreadsheet_link_color`) · 이은 칸 배경 같은 색 옅게 + + * 칸 왼아래 작은 세모 · 이은 알약을 누르면 연결 칸으로 · 우클릭 [연결 풀기] · 자재 모드는 [자재 더하기] → 고르기 창 → 새 알약. * 자재 모드 알약 목록 = 호스트(M02)가 `항목` 으로 줌(자재 정보 포함 · 저장은 마스터 자재 조합) — [자재 더하기] · * 우클릭 [목록에서 빼기] 때 `onItemsChange(목록)` 으로 알림(부른 쪽이 마스터에 적음 · 여기는 안 적음). * 저장 = 시트 `연결`(계약) · 바꿈은 `시트통째` 명령(되돌리기 짝) · 행열 넣기 · 지우기는 `applyLinked` 가 @@ -11,9 +12,10 @@ import "./spreadsheet_links.css"; import { createMapContextMenu } from "@ui/ui_template_context_menu"; -import { el, showToast } from "@ui/ui_template_elements"; +import { el, showConfirmDialog, showToast } from "@ui/ui_template_elements"; import { parseA1, quoteSheet, toA1 } from "./spreadsheet_address"; import { applyCommand } from "./spreadsheet_commands"; +import { linkHues } from "./spreadsheet_link_color"; import type { slotOf } from "./spreadsheet_editor"; import { shiftSpan } from "./spreadsheet_refshift"; import { selectCell } from "./spreadsheet_selection"; @@ -55,8 +57,10 @@ export interface Links extends PartHandle { } const TEXT = { - bar: "알약 — 칸을 고른 채 누르면 연결 · 흐린 알약은 누르면 연결 칸으로 · 우클릭 풀기", - free: "칸을 고른 채 누르면 이 칸에 연결", + bar: "알약 — 칸을 고른 채 누르거나 끌어 칸에 놓으면 연결 · 색 알약은 누르면 연결 칸으로 · 우클릭 풀기", + free: "칸을 고른 채 누르거나 끌어 칸에 놓으면 이 칸에 연결", + swap: "{cell} 은 이미 「{name}」 알약 칸 — 이 알약으로 바꿈?", + swapOk: "바꿈", linked: "누르면 연결 칸으로 · 우클릭 연결 풀기", unlink: "연결 풀기", remove: "목록에서 빼기", @@ -136,6 +140,9 @@ export function mountLinks( /** 알약 목록 — 호스트가 준 항목 + 더한 자재(자재 모드 · 바뀌면 호스트에 알림) */ const items: LinkItem[] = opts ? [...opts.항목] : []; let dots: HTMLElement[] = []; + /** 끄는 중인 알약 · 놓을 칸 표시 */ + let dragging: LinkItem | null = null; + const dropMark = el("div", { className: "ss-link-drop" }); let pick: HTMLElement | null = null; /** 키 → 연결(시트 · 줄) */ @@ -153,10 +160,11 @@ export function mountLinks( ctx.dispatch({ 종류: "시트통째", 시트: next }); } - function link(item: LinkItem): void { + /** 알약을 칸에 이음 — `at` 없으면 지금 고른 칸 */ + function link(item: LinkItem, at?: string): void { if (ctx.readOnly) return; const sheet = ctx.sheet(); - const 칸 = toA1(ctx.selection.활성.r, ctx.selection.활성.c); + const 칸 = at ?? toA1(ctx.selection.활성.r, ctx.selection.활성.c); const rest = (sheet.연결 ?? []).filter((l) => l.칸 !== 칸 && l.키 !== item.키); const one: CellLink = { 키: item.키, 이름: item.이름, 칸 }; if (item.단위) one.단위 = item.단위; @@ -184,7 +192,11 @@ export function mountLinks( const commit = (): void => opts?.onItemsChange?.(items.map((i) => ({ ...i }))); - function pill(item: LinkItem, hit: { sheet: Sheet; link: CellLink } | undefined) { + function pill( + item: LinkItem, + hit: { sheet: Sheet; link: CellLink } | undefined, + hue: number | undefined, + ) { const where = hit ? hit.sheet.id === ctx.selection.시트 ? hit.link.칸 @@ -199,7 +211,19 @@ export function mountLinks( ...(where ? [el("code", { text: where })] : []), ], }); - b.addEventListener("mousedown", (e) => e.preventDefault()); // 초점은 격자에 둠 + if (hue !== undefined) b.style.setProperty("--ss-link-h", String(hue)); + b.draggable = !ctx.readOnly; + b.addEventListener("dragstart", (e) => { + dragging = item; + e.dataTransfer?.setData("text/plain", item.키); + if (e.dataTransfer) e.dataTransfer.effectAllowed = "link"; + }); + b.addEventListener("dragend", () => { + dragging = null; + dropMark.remove(); + }); + // 초점은 격자에 둠 — 끌 수 있는 알약은 막으면 끌기가 안 돼 그대로(누른 뒤 `back` 이 격자로 돌림) + if (!b.draggable) b.addEventListener("mousedown", (e) => e.preventDefault()); b.addEventListener("click", () => (hit ? goTo(hit.sheet, hit.link.칸) : link(item))); const unlink = (): void => { if (hit) @@ -274,7 +298,8 @@ export function mountLinks( ? ofMaterial(link.자재) : { 키: link.키, 이름: link.이름 ?? link.키, 단위: link.단위 }, ); - const pills = shown.map((item) => pill(item, map.get(item.키))); + const hues = linkHues(map.keys()); + const pills = shown.map((item) => pill(item, map.get(item.키), hues.get(item.키))); if (opts.모드 === "자재" && opts.자재찾기 && !ctx.readOnly) { const more = el("button", { className: "ss-link ss-link--add", @@ -287,23 +312,67 @@ export function mountLinks( bar.replaceChildren(...pills); } - /** 칸 왼아래 작은 세모 — 칸이 든 판 층에 얹어 스크롤을 따라감 */ + /** 이은 칸 — 알약 색 옅은 배경 + 왼아래 작은 세모 · 칸이 든 판 층에 얹어 스크롤을 따라감 */ function placeDots(): void { for (const d of dots) d.remove(); dots = []; + const hues = linkHues(found().keys()); for (const l of ctx.sheet().연결 ?? []) { const p = parseA1(l.칸); if (!p) continue; const { layer, box } = slot(ctx, p.r, p.c); if (!box.w || !box.h) continue; // 숨긴 행열 + const tint = el("div", { className: "ss-link-cell", attrs: { "data-key": l.키 } }); + tint.style.cssText = `left:${box.x}px;top:${box.y}px;width:${box.w}px;height:${box.h}px`; const dot = el("div", { className: "ss-link-dot" }); dot.style.left = `${box.x}px`; dot.style.top = `${box.y + box.h - 7}px`; - layer.append(dot); - dots.push(dot); + for (const d of [tint, dot]) d.style.setProperty("--ss-link-h", String(hues.get(l.키) ?? 0)); + layer.append(tint, dot); + dots.push(tint, dot); } } + // ── 알약 끌어 칸에 놓기(40-3) ────────────────────────────────────────── + /** 끄는 알약 아래 칸(A1) — 머리 · 격자 밖이면 null */ + function cellAt(e: DragEvent): { r: number; c: number } | null { + const hit = ctx.grid.hitTest(e.clientX, e.clientY); + return hit && hit.r >= 0 && hit.c >= 0 ? { r: hit.r, c: hit.c } : null; + } + const onDragOver = (e: DragEvent): void => { + const at = dragging && !ctx.readOnly ? cellAt(e) : null; + if (!at) return void dropMark.remove(); + e.preventDefault(); // 놓을 수 있음 + if (e.dataTransfer) e.dataTransfer.dropEffect = "link"; + const { layer, box } = slot(ctx, at.r, at.c); + dropMark.style.cssText = `left:${box.x}px;top:${box.y}px;width:${box.w}px;height:${box.h}px`; + if (dropMark.parentElement !== layer) layer.append(dropMark); + }; + const onDragLeave = (e: DragEvent): void => { + if (!ctx.grid.root.contains(e.relatedTarget as Node | null)) dropMark.remove(); + }; + const onDrop = async (e: DragEvent): Promise => { + const item = dragging; + const at = item && !ctx.readOnly ? cellAt(e) : null; + dropMark.remove(); + dragging = null; + if (!item || !at) return; + e.preventDefault(); + const 칸 = toA1(at.r, at.c); + const other = (ctx.sheet().연결 ?? []).find((l) => l.칸 === 칸 && l.키 !== item.키); + const name = other?.이름 ?? other?.키 ?? ""; + if ( + other && + !(await showConfirmDialog( + TEXT.swap.replace("{cell}", 칸).replace("{name}", name), + TEXT.swapOk, + )) + ) + return back(); + ctx.select(selectCell(ctx.sheet(), at.r, at.c), true); + link(item, 칸); + }; + // ── 자재 고르기 창 ────────────────────────────────────────────────────── function closePick(): void { pick?.remove(); @@ -384,6 +453,10 @@ export function mountLinks( const onZoom = (): void => placeDots(); ctx.grid.root.addEventListener("aislo-grid-zoom", onZoom); + const onDropEvent = (e: DragEvent): void => void onDrop(e); + ctx.grid.root.addEventListener("dragover", onDragOver); + ctx.grid.root.addEventListener("dragleave", onDragLeave); + ctx.grid.root.addEventListener("drop", onDropEvent); return { root: opts ? bar : null, @@ -394,6 +467,10 @@ export function mountLinks( }, destroy() { ctx.grid.root.removeEventListener("aislo-grid-zoom", onZoom); + ctx.grid.root.removeEventListener("dragover", onDragOver); + ctx.grid.root.removeEventListener("dragleave", onDragLeave); + ctx.grid.root.removeEventListener("drop", onDropEvent); + dropMark.remove(); closePick(); for (const d of dots) d.remove(); menu.element.remove(); diff --git a/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts b/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts index 032bfb659..a2ba19fb1 100644 --- a/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts +++ b/resources/tester/spreadsheet/spreadsheet_link_set_entry.ts @@ -1,6 +1,7 @@ /* setLink 순수 길(`spreadsheet_link_set.ts`) 시험 진입 — vite --ssr 로 번들해 Node 로 돌림. * 결과 = JSON {pass, fail, failures} 한 줄(`test_spreadsheet_set_link.py` 가 읽음) */ import { applyCommand } from "../../../A00_Common/spreadsheet/spreadsheet_commands"; +import { linkHues } from "../../../A00_Common/spreadsheet/spreadsheet_link_color"; import { linkAreaCell, linkCommand } from "../../../A00_Common/spreadsheet/spreadsheet_link_set"; import type { Command, Workbook } from "../../../A00_Common/spreadsheet/spreadsheet_types"; @@ -100,4 +101,27 @@ eq("틀린 주소", linkAreaCell(b, "철근|ton", "9C"), null); eq("풀기 · 시트 넘어", links(linkAreaCell(b, "치기|㎥", null)!), [links(book())[0], null]); eq("바뀔 것 없음 = 사본", JSON.stringify(linkAreaCell(b, "깔기|㎡", "B6")), before); +// linkHues — 키로 정해짐 · 차례 무관 · 24 개까지 서로 다름 · 12 개까지 30° 바퀴 · 15° 눈금 +const keys = Array.from({ length: 24 }, (_, i) => `알약${i}|㎡`); +const h1 = linkHues(keys); +const h2 = linkHues([...keys].reverse()); +eq("차례 무관", [...h1.entries()].sort(), [...h2.entries()].sort()); +eq("24 개 서로 다름", new Set(h1.values()).size, 24); +eq( + "15° 눈금", + [...h1.values()].every((h) => h % 15 === 0 && h >= 0 && h < 360), + true, +); +const few = linkHues(keys.slice(0, 12)); +eq( + "12 개 = 30° 바퀴", + [...few.values()].every((h) => h % 30 === 0), + true, +); +eq( + "같은 키 같은 색", + linkHues(["철근|ton", "깔기|㎡"]).get("철근|ton"), + linkHues(["철근|ton"]).get("철근|ton"), +); + console.log(JSON.stringify({ pass, fail: failures.length, failures })); diff --git a/resources/tester/test_spreadsheet_set_link.py b/resources/tester/test_spreadsheet_set_link.py index 000430baf..554ca3aa9 100644 --- a/resources/tester/test_spreadsheet_set_link.py +++ b/resources/tester/test_spreadsheet_set_link.py @@ -55,7 +55,7 @@ def test_setLink_순수_길(): assert run.stdout.strip(), run.stderr result = json.loads(run.stdout) assert result["fail"] == 0, result["failures"] - assert result["pass"] == 19 + assert result["pass"] == 24 def test_손잡이_setLink_몸(): @@ -66,3 +66,19 @@ def test_손잡이_setLink_몸(): assert "linkCommand(ctx.book, ctx.sheet().id, key, cell, info)" in body assert "if (command) dispatch(command);" in body # 되돌리기 이력에 쌓임(setDoc 아님) assert "loadLinks();" in body + + +def test_알약_끌어_놓기_색(): + """40-3 ② ③ — 알약 끌어 칸에 놓기(다른 알약 칸이면 물음) · 이은 알약 · 칸 = 키 색 · 두 테마 밝기.""" + src = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_links.ts").read_text(encoding="utf-8") + assert "b.draggable = !ctx.readOnly;" in src and 'addEventListener("drop", onDropEvent)' in src + assert "showConfirmDialog(" in src and "link(item, 칸);" in src + assert 'if (!b.draggable) b.addEventListener("mousedown"' in src # 끌기 막지 않음 + assert "const hues = linkHues(found().keys());" in src and 'className: "ss-link-cell"' in src + css = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_links.css").read_text( + encoding="utf-8" + ) + assert ( + "--ss-link-l: 31%;" in css and css.count("--ss-link-l: 66%;") == 2 + ) # 밝음 · 어두움(설정 · 시스템) + assert "ss-link-flash" in css # 반짝임 그대로 From ad90e7226006fb4193c6449a3ad6b01ef57eb286 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 22:29:27 +0900 Subject: [PATCH 3/3] auto: 2026-10-04 22:29 (ESD_LAPTOP) --- docs/raw/M02_구조물조합개편/사용자요구_1004_2.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md b/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md index 30d4b5a29..3651b8c87 100644 --- a/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md +++ b/docs/raw/M02_구조물조합개편/사용자요구_1004_2.md @@ -19,3 +19,7 @@ 3. 면적산출 상세 - 알약지정은 현재 방법 + 그래그 & 드랍 방식을 추가 - 지정된 알약정보를 색상 자동지정 및 지정된 셀은 색상으로 구분을 할수 있게 반영, 반짝임은 유지 + +## 추가 기능 (10-4 밤 · 원문 그대로) + +추가기능 부여. 면적산출에서 특정 영역을 선정하고 도면영역으로 전달을 할 경우 도면에 테이블을 자동으로 넣고 셀의 내용을 싱크하는 기능을 부여. 만약 동기화가 어려운 경우 업데이트 버튼을 추가해도 무관, 복수개의 테이블이 넘어갈수도 잇으며 위치는 도면에서 사용자가 드래그하여 지정