feat(spreadsheet): 알약 줄 끝 [연결 모두 해제] — 물음 한 번 · 묶음 명령 한 번(81-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -70,6 +70,20 @@ export function linkCommand(
|
||||
return commands.length === 1 ? commands[0] : { 종류: "묶음", 명령: commands };
|
||||
}
|
||||
|
||||
/** 알약 연결을 전부 풀 명령(81-4) — 연결 있는 시트마다 연결을 뺀 `시트통째` · 시트가 둘 이상이면 `묶음`
|
||||
* (되돌리기 한 번) · 풀 연결이 없으면 null */
|
||||
export function clearLinksCommand(book: Workbook): Command | null {
|
||||
const commands: Command[] = book.시트
|
||||
.filter((s) => s.연결?.length)
|
||||
.map((s) => {
|
||||
const copy = structuredClone(s);
|
||||
delete copy.연결;
|
||||
return { 종류: "시트통째", 시트: copy };
|
||||
});
|
||||
if (!commands.length) return null;
|
||||
return commands.length === 1 ? commands[0] : { 종류: "묶음", 명령: commands };
|
||||
}
|
||||
|
||||
/** 면적 문서에 알약 키를 잇기 · 풀기(`칸` null) — 활성 시트에 · 새 문서를 돌려줌(받은 문서는 그대로 ·
|
||||
* 번호 등 계약 밖 칸도 그대로) · 칸 주소가 틀리면 null · 같은 칸 다른 알약은 함께 둠(`replace` 면 뺌). 일위대가 [저장] 이 면적 문서를 함께 쓸 때 부름 */
|
||||
export function linkAreaCell<T extends Workbook>(
|
||||
|
||||
@@ -89,6 +89,13 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 알약 줄 끝 [연결 모두 해제] — 점선 알약과 같은 결 · 오른쪽 끝으로 밀어 알약과 떨어뜨림 */
|
||||
.ss-link--clear {
|
||||
margin-left: auto;
|
||||
border-style: dashed;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 연결 칸 왼아래 세모(초록 오류 세모는 왼위 · 빨강 메모는 오른위) */
|
||||
.ss-link-dot {
|
||||
position: absolute;
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
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 { inRange, parseA1, parseRange, quoteSheet, toA1 } from "./spreadsheet_address";
|
||||
import { applyCommand } from "./spreadsheet_commands";
|
||||
import { askShare } from "./spreadsheet_link_ask";
|
||||
import { linkHues } from "./spreadsheet_link_color";
|
||||
import { linkCommand, linksAt } from "./spreadsheet_link_set";
|
||||
import { clearLinksCommand, linkCommand, linksAt } from "./spreadsheet_link_set";
|
||||
import type { slotOf } from "./spreadsheet_editor";
|
||||
import { shiftSpan } from "./spreadsheet_refshift";
|
||||
import { selectCell } from "./spreadsheet_selection";
|
||||
@@ -73,6 +73,9 @@ const TEXT = {
|
||||
remove: "목록에서 빼기",
|
||||
removed: "목록에서 뺌 — 연결도 풀림",
|
||||
add: "+ 자재 추가",
|
||||
clearAll: "연결 모두 해제",
|
||||
clearTip: "이은 알약 연결을 한꺼번에 풂 — 알약 목록은 그대로 · 되돌리기 한 번",
|
||||
clearAsk: "알약 연결 {n}개를 모두 풀까?",
|
||||
search: "자재 이름 · 규격 · 코드",
|
||||
searching: "찾는 중…",
|
||||
none: "맞는 자재 없음",
|
||||
@@ -190,6 +193,16 @@ export function mountLinks(
|
||||
back();
|
||||
}
|
||||
|
||||
/** 알약 줄 끝 [연결 모두 해제](81-4) — 물음 한 번 · 연결만 빠짐(목록 그대로) · 시트 여럿이어도 되돌리기 한 번 */
|
||||
async function clearAll(): Promise<void> {
|
||||
const n = ctx.book.시트.reduce((sum, s) => sum + (s.연결?.length ?? 0), 0);
|
||||
const command = clearLinksCommand(ctx.book);
|
||||
if (ctx.readOnly || !command) return;
|
||||
if (await showConfirmDialog(TEXT.clearAsk.replace("{n}", String(n)), TEXT.clearAll))
|
||||
ctx.dispatch(command);
|
||||
back();
|
||||
}
|
||||
|
||||
/** 그 칸으로 가서 눈에 띄게 깜빡임(굵은 테두리 + 채움 · 테마 색 · 끝나면 스스로 사라짐) */
|
||||
function goTo(sheet: Sheet, a1: string): void {
|
||||
const p = parseA1(a1);
|
||||
@@ -353,6 +366,15 @@ export function mountLinks(
|
||||
more.addEventListener("click", () => openPick(more));
|
||||
pills.push(more);
|
||||
}
|
||||
if (!ctx.readOnly && map.size) {
|
||||
const clear = el("button", {
|
||||
className: "ss-link ss-link--clear",
|
||||
text: TEXT.clearAll,
|
||||
attrs: { type: "button", title: TEXT.clearTip },
|
||||
});
|
||||
clear.addEventListener("click", () => void clearAll());
|
||||
pills.push(clear);
|
||||
}
|
||||
bar.replaceChildren(...pills);
|
||||
if (fresh)
|
||||
bar.querySelector(".is-here")?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { applyCommand } from "../../../A00_Common/spreadsheet/spreadsheet_commands";
|
||||
import { linkHues } from "../../../A00_Common/spreadsheet/spreadsheet_link_color";
|
||||
import {
|
||||
clearLinksCommand,
|
||||
linkAreaCell,
|
||||
linkCommand,
|
||||
linksAt,
|
||||
@@ -33,6 +34,11 @@ const book = (): Workbook =>
|
||||
],
|
||||
활성: "s1",
|
||||
}) as unknown as Workbook;
|
||||
const b0 = (): Workbook => {
|
||||
const x = book();
|
||||
x.시트.forEach((t) => delete t.연결);
|
||||
return x;
|
||||
};
|
||||
const links = (b: Workbook) => b.시트.map((s) => s.연결 ?? null);
|
||||
const run = (b: Workbook, c: Command | null | false) => {
|
||||
if (!c) return null;
|
||||
@@ -129,6 +135,22 @@ eq("틀린 주소", linkAreaCell(b, "철근|ton", "9C"), null);
|
||||
eq("풀기 · 시트 넘어", links(linkAreaCell(b, "치기|㎥", null)!), [links(book())[0], null]);
|
||||
eq("바뀔 것 없음 = 사본", JSON.stringify(linkAreaCell(b, "깔기|㎡", "B6")), before);
|
||||
|
||||
// 연결 모두 해제(81-4) — 연결 있는 시트마다 시트통째 · 둘 넘으면 묶음 · 되돌리기 한 번 · 없으면 null
|
||||
b = book();
|
||||
const wholeBefore = JSON.stringify(b);
|
||||
const clearAll = clearLinksCommand(b);
|
||||
eq("모두 해제 = 묶음", clearAll && clearAll.종류, "묶음");
|
||||
undo = run(b, clearAll);
|
||||
eq("모두 해제 뒤 연결 0", links(b), [null, null]);
|
||||
eq("칸 값은 그대로", b.시트[0].칸, { B1: { 값: 12.5 } });
|
||||
if (undo) applyCommand(b, undo);
|
||||
eq("되돌리기 한 번에 전부 돌아옴", JSON.stringify(b), wholeBefore);
|
||||
eq("연결 없으면 null", clearLinksCommand(b0()), null);
|
||||
b = book();
|
||||
b.시트[1].연결 = undefined;
|
||||
const one = clearLinksCommand(b);
|
||||
eq("시트 하나만 있으면 시트통째 하나", one && one.종류, "시트통째");
|
||||
|
||||
// linkHues — 키로 정해짐 · 차례 무관 · 24 개까지 서로 다름 · 12 개까지 30° 바퀴 · 15° 눈금
|
||||
const keys = Array.from({ length: 24 }, (_, i) => `알약${i}|㎡`);
|
||||
const h1 = linkHues(keys);
|
||||
|
||||
@@ -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"] == 29
|
||||
assert result["pass"] == 35
|
||||
|
||||
|
||||
def test_손잡이_setLink_몸():
|
||||
@@ -90,3 +90,13 @@ def test_알약_끌어_놓기_색():
|
||||
"--ss-link-l: 31%;" in css and css.count("--ss-link-l: 66%;") == 2
|
||||
) # 밝음 · 어두움(설정 · 시스템)
|
||||
assert "ss-link-flash" in css # 반짝임 그대로
|
||||
|
||||
|
||||
def test_연결_모두_해제_단추는_알약_줄_끝_편집_가능일_때만():
|
||||
src = (ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_links.ts").read_text(encoding="utf-8")
|
||||
assert "if (!ctx.readOnly && map.size) {" in src # 읽기 전용 · 연결 0 = 단추 없음
|
||||
assert 'className: "ss-link ss-link--clear"' in src
|
||||
body = src.split("async function clearAll")[1].split("function goTo")[0]
|
||||
assert "showConfirmDialog(TEXT.clearAsk.replace" in body # 물음 한 번
|
||||
assert "ctx.dispatch(command)" in body # 묶음 명령 한 번 = 되돌리기 한 번
|
||||
assert 'clearAsk: "알약 연결 {n}개를 모두 풀까?"' in src
|
||||
|
||||
Reference in New Issue
Block a user