refactor(copy): 대상 교각의 같은 형식 필터와 같은 형식 모두 선택 버튼 제거

- "원본과 같은 형식만" 체크박스와 "같은 형식 모두 선택" 버튼 삭제, "선택 해제"는 유지
- 대상 칩에 원본과 형식이 다르면 "형식 다름" 표시
- copyCandidates와 selectedTargets에서 형식 필터 인자 제거, 테스트 갱신
- 테스트 타입 오류(optional 필드) 수정

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-27 17:01:32 +09:00
1 parent 5f3ea8d56f
commit fb09c693c4
4 files changed
+18 -33

No files matched your search

-2
View File
@@ -159,8 +159,6 @@
<section>
<h3>1 · 대상 교각</h3>
<div class="cp-tools">
<label class="chk"><input id="cp-same-type" type="checkbox" checked />원본과 같은 형식만</label>
<button id="cp-select-same" class="btn-o small" type="button">같은 형식 모두 선택</button>
<button id="cp-clear" class="btn-o small" type="button">선택 해제</button>
</div>
<div id="cp-targets" class="cp-chips"></div>
+5 -6
View File
@@ -16,17 +16,16 @@ export interface TargetCandidate {
selectable: boolean;
}
/** The piers offered as targets: every other pier, or only those of the source type. */
export function copyCandidates(piers: CopyCandidate[], sourceName: string, sameTypeOnly: boolean): TargetCandidate[] {
const source = piers.find((p) => p.name === sourceName);
/** The piers offered as targets: every other pier. The type is shown so a different type is visible. */
export function copyCandidates(piers: CopyCandidate[], sourceName: string): TargetCandidate[] {
return piers
.filter((p) => p.name !== sourceName && (!sameTypeOnly || p.type === source?.type))
.filter((p) => p.name !== sourceName)
.map((p) => ({ name: p.name, type: p.type, selectable: p.hasModel }));
}
/** The targets that get the change: ticked by the user, selectable, and visible under the current filter. */
export function selectedTargets(piers: CopyCandidate[], sourceName: string, sameTypeOnly: boolean, ticked: Set<string>): string[] {
return copyCandidates(piers, sourceName, sameTypeOnly)
export function selectedTargets(piers: CopyCandidate[], sourceName: string, ticked: Set<string>): string[] {
return copyCandidates(piers, sourceName)
.filter((c) => c.selectable && ticked.has(c.name))
.map((c) => c.name);
}
+4 -15
View File
@@ -640,7 +640,7 @@ function copyCandidateList(): CopyCandidate[] {
function selectedCopyTargets(): Pier[] {
const src = activePier();
if (!src) return [];
const names = new Set(selectedTargets(copyCandidateList(), src.input.name, $<HTMLInputElement>('cp-same-type').checked, copySelection));
const names = new Set(selectedTargets(copyCandidateList(), src.input.name, copySelection));
return state.piers.filter((p) => names.has(p.input.name));
}
@@ -685,8 +685,7 @@ function renderCopyTargets(): void {
const box = $('cp-targets');
box.replaceChildren();
const src = activePier();
const sameType = $<HTMLInputElement>('cp-same-type').checked;
for (const c of copyCandidates(copyCandidateList(), src?.input.name ?? '', sameType)) {
for (const c of copyCandidates(copyCandidateList(), src?.input.name ?? '')) {
const label = el('label', undefined, 'chk');
const check = el('input');
check.type = 'checkbox';
@@ -698,7 +697,8 @@ function renderCopyTargets(): void {
else copySelection.delete(c.name);
renderCopyPreview();
});
const text = c.selectable ? `${c.name} · ${c.type}` : `${c.name} · ${c.type} (모델 없음, 제외)`;
const otherType = src !== undefined && c.type !== src.input.type ? ' · 형식 다름' : '';
const text = c.selectable ? `${c.name} · ${c.type}${otherType}` : `${c.name} · ${c.type} (모델 없음, 제외)`;
label.append(check, document.createTextNode(text));
box.appendChild(label);
}
@@ -3076,17 +3076,6 @@ $<HTMLButtonElement>('member-regen').addEventListener('click', () => {
const pier = activePier();
if (pier) regenerateOne(pier.input.name);
});
$<HTMLInputElement>('cp-same-type').addEventListener('change', () => {
renderCopyTargets();
renderCopyPreview();
});
$<HTMLButtonElement>('cp-select-same').addEventListener('click', () => {
const src = activePier();
const sameType = $<HTMLInputElement>('cp-same-type').checked;
copySelection = new Set(copyCandidates(copyCandidateList(), src?.input.name ?? '', sameType).filter((c) => c.selectable).map((c) => c.name));
renderCopyTargets();
renderCopyPreview();
});
$<HTMLButtonElement>('cp-items-reset').addEventListener('click', () => {
const src = activePier();
copyItemChoice = new Set(src ? copyItemsWithOverrides(src) : []);
+9 -10
View File
@@ -12,32 +12,31 @@ const piers: CopyCandidate[] = [
describe('copyCandidates (대상 후보)', () => {
it('excludes the source pier', () => {
expect(copyCandidates(piers, 'P3', false).map((c) => c.name)).not.toContain('P3');
expect(copyCandidates(piers, 'P3').map((c) => c.name)).not.toContain('P3');
});
it('keeps only the source type when 같은 형식만 is on', () => {
expect(copyCandidates(piers, 'P3', true).map((c) => c.name)).toEqual(['P1', 'P2', 'P5']);
it('offers piers of every type, so a different type is visible', () => {
expect(copyCandidates(piers, 'P3').map((c) => c.type)).toEqual(['T형', 'T형', 'Y형', 'T형']);
});
it('marks a pier without a model as not selectable', () => {
const p5 = copyCandidates(piers, 'P3', false).find((c) => c.name === 'P5');
const p5 = copyCandidates(piers, 'P3').find((c) => c.name === 'P5');
expect(p5?.selectable).toBe(false);
});
});
describe('selectedTargets (선택한 대상만)', () => {
it('is empty when nothing is ticked', () => {
expect(selectedTargets(piers, 'P3', false, new Set())).toEqual([]);
expect(selectedTargets(piers, 'P3', new Set())).toEqual([]);
});
it('returns only ticked, selectable piers', () => {
const picked = new Set(['P1', 'P4', 'P5']);
expect(selectedTargets(piers, 'P3', false, picked)).toEqual(['P1', 'P4']);
expect(selectedTargets(piers, 'P3', picked)).toEqual(['P1', 'P4']);
});
it('ignores a tick on a pier hidden by the same-type filter', () => {
const picked = new Set(['P4']);
expect(selectedTargets(piers, 'P3', true, picked)).toEqual([]);
it('keeps a tick on a pier of another type', () => {
expect(selectedTargets(piers, 'P3', new Set(['P4']))).toEqual(['P4']);
});
});
@@ -82,7 +81,7 @@ describe('copyItemsWithOverrides (기준 교각에서 되돌리기가 표시된
});
it('selects the coping h1, h2 and Level 폭 items when those values changed', () => {
expect(copyItemsWithOverrides(source({ copingH1Mm: base.copingH1Mm + 1, levelWidthMm: base.levelWidthMm + 1 }))).toEqual(['coping-h1', 'level-width']);
expect(copyItemsWithOverrides(source({ copingH1Mm: (base.copingH1Mm ?? 0) + 1, levelWidthMm: (base.levelWidthMm ?? 0) + 1 }))).toEqual(['coping-h1', 'level-width']);
});
it('ignores a changed value that no copy item carries (groundEl)', () => {