- 새 B06_Section_UI_Cross_Line_Cell.ts — 아래 띠 왼쪽 공용 칸(제목 · ◀▲↺▼▶ 한 벌 · 값 한 줄 말줄임 · 길게 누름 반복) · 숨은 버튼은 방향키 슬롯 뗌 - Design_Pick — 암 점선 위 판정 선(키 rock · ▲▼↺ · ◀▶ 막음 · 방향키 = 암만) · 떠 있는 선 조정창 · 우측 위 치수 라벨 걷음 · 안 고르면 암 칸 · 고르면 그 선(노면 · 사면 · 소단 · 중심점 · 교차점 · 암) - Center.ts bindAxisDrag(한 축 끌기 · 놓을 때 한 번 · click 한 번 막음) — 중심점 · 암 세로 끌기 같이 씀 - buildRockBoundaryControl = 공용 칸 「안 고름」 꼴(Cross_Design) · Card_Chrome 띠 왼쪽 = 공용 칸 · Detail_Pick 표 강조에 암 판정 선 뺌 - CSS(Cross.css 공용 칸 · Areas.css 암 고름 띠 · Controls.css 주석) · 시험 test_370_3 새로 · 137 · 139_6 · 179_1 · 193_1 · 194_1 · 318 · 356 · 370_2 고쳐 씀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
173 lines
6.3 KiB
TypeScript
173 lines
6.3 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_Line_Cell.ts
|
|
* 횡단 카드 아래 띠 왼쪽 **공용 칸**(370-3) — 고른 선의 제목 · 축에 맞는 버튼 · 값 한 줄.
|
|
*
|
|
* · 안 고르면 암 경계선 ▲▼↺ · 값(암 지반만 · 토사는 빈 칸 · 띠 높이 그대로)
|
|
* · 고르면 그 선(노면 · 사면 · 소단 · 중심점 · 교차점 · 암) — `_Cross_Design_Pick` 이 상태를 넣음
|
|
* · 버튼 다섯 칸(◀ ▲ ↺ ▼ ▶)은 한 벌 · 상태마다 보임 · 툴팁 · 동작만 바꿈 · 방향키는 보이는 슬롯만 찾음
|
|
* · ▲▼ 길게 누름 = 0.5 초 뒤 0.1 초마다 반복(암 · B05 종단 선 제어 꼴)
|
|
* ========================================================================== */
|
|
|
|
export type CellSlot = "left" | "up" | "reset" | "down" | "right";
|
|
const SLOTS: Array<[CellSlot, string]> = [
|
|
["left", "◀"],
|
|
["up", "▲"],
|
|
["reset", "↺"],
|
|
["down", "▼"],
|
|
["right", "▶"],
|
|
];
|
|
|
|
export interface CellButton {
|
|
title: string;
|
|
act: () => void;
|
|
/** 길게 누르면 반복. */
|
|
hold?: boolean;
|
|
}
|
|
export interface LineCellState {
|
|
title: string;
|
|
buttons: Partial<Record<CellSlot, CellButton>>;
|
|
value: string;
|
|
}
|
|
export interface LineCell {
|
|
root: HTMLElement;
|
|
/** 버튼 줄 — 방향키 묶음(`bindArrowKeys`)이 슬롯 버튼을 찾는 그릇. */
|
|
buttons: HTMLElement;
|
|
/** null = 빈 칸(자리는 남음). */
|
|
show: (state: LineCellState | null) => void;
|
|
/** 값 글만 바꿈(끄는 동안 미리 보기). */
|
|
setValue: (text: string) => void;
|
|
}
|
|
|
|
/**
|
|
* 길게 누르는 동안 같은 동작을 반복한다(B05 종단 선 제어 패턴 재사용). 0.5초 유지하면
|
|
* 0.1초 간격 반복이 시작된다. 버튼이 재빌드로 사라져도 반복이 끊기지 않도록
|
|
* 타이머·종료 감지를 버튼이 아니라 window 이벤트로 들고 있는다.
|
|
*/
|
|
function createHoldRepeater(): { start: (action: () => void) => void; stop: () => void } {
|
|
let delayTimer = 0;
|
|
let repeatTimer = 0;
|
|
function stop(): void {
|
|
window.clearTimeout(delayTimer);
|
|
window.clearInterval(repeatTimer);
|
|
delayTimer = 0;
|
|
repeatTimer = 0;
|
|
window.removeEventListener("pointerup", stop);
|
|
window.removeEventListener("pointercancel", stop);
|
|
window.removeEventListener("blur", stop);
|
|
}
|
|
function start(action: () => void): void {
|
|
stop();
|
|
delayTimer = window.setTimeout(() => {
|
|
repeatTimer = window.setInterval(action, 100);
|
|
}, 500);
|
|
window.addEventListener("pointerup", stop);
|
|
window.addEventListener("pointercancel", stop);
|
|
window.addEventListener("blur", stop);
|
|
}
|
|
return { start, stop };
|
|
}
|
|
|
|
/** 버튼 칸 표시(보임과 무관 · 시험이 찾음) — 슬롯 클래스(`b06-structure-panel__btn--*`)는 보일 때만. */
|
|
export const CELL_BTN = "b06-cross-card__cell-btn--";
|
|
const HOLD_HINT = "\n(길게 누르면 0.5초 뒤부터 연속 조정)";
|
|
|
|
export function buildLineCell(): LineCell {
|
|
const root = document.createElement("div");
|
|
root.className = "b06-design__seg b06-design__rockb b06-cross-card__linecell";
|
|
root.addEventListener("click", (event) => event.stopPropagation()); // 카드 고름 · 팬으로 안 번짐
|
|
const legend = document.createElement("span");
|
|
legend.className = "b06-design__seg-legend";
|
|
const group = document.createElement("div");
|
|
group.className = "b06-design__seg-buttons";
|
|
const readout = document.createElement("span");
|
|
readout.className = "b06-design__rockb-readout";
|
|
const repeater = createHoldRepeater();
|
|
let current: LineCellState | null = null;
|
|
const run = (slot: CellSlot) => current?.buttons[slot]?.act();
|
|
const buttons = SLOTS.map(([slot, text]) => {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = `b06-design__rockb-btn ${CELL_BTN}${slot}`;
|
|
if (slot === "reset") button.classList.add("is-reset");
|
|
button.textContent = text;
|
|
// 길게 누름 — 누르면 즉시 1회 · 반복 예약 · 이어지는 click 은 삼킴
|
|
let swallowClick = false;
|
|
button.addEventListener("pointerdown", (event) => {
|
|
if (!current?.buttons[slot]?.hold) return;
|
|
event.stopPropagation();
|
|
swallowClick = true;
|
|
run(slot);
|
|
repeater.start(() => run(slot));
|
|
});
|
|
button.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
if (swallowClick) {
|
|
swallowClick = false;
|
|
return;
|
|
}
|
|
run(slot);
|
|
});
|
|
group.append(button);
|
|
return [slot, button] as const;
|
|
});
|
|
group.append(readout);
|
|
root.append(legend, group);
|
|
const cell: LineCell = {
|
|
root,
|
|
buttons: group,
|
|
show: (state) => {
|
|
current = state;
|
|
root.classList.toggle("is-empty", !state);
|
|
legend.textContent = state?.title ?? "";
|
|
readout.textContent = state?.value ?? "";
|
|
readout.title = state?.value ?? "";
|
|
for (const [slot, button] of buttons) {
|
|
const spec = state?.buttons[slot];
|
|
button.hidden = !spec;
|
|
// 방향키 묶음은 슬롯 클래스로 찾음 — 숨은 버튼은 슬롯을 떼어 키를 안 삼킴(노면 ▲▼ = 종단도)
|
|
button.classList.toggle(`b06-structure-panel__btn--${slot}`, !!spec);
|
|
button.title = spec ? spec.title + (spec.hold ? HOLD_HINT : "") : "";
|
|
}
|
|
},
|
|
setValue: (text) => {
|
|
readout.textContent = text;
|
|
readout.title = text;
|
|
},
|
|
};
|
|
cell.show(null);
|
|
return cell;
|
|
}
|
|
|
|
/** 암 경계선 칸 상태(안 고름 · 암 고름 둘 다) — ▲ = 위로 · ▼ = 아래로 · ↺ = 기본. */
|
|
export interface RockCellSpec {
|
|
legend: string;
|
|
offsetM: number;
|
|
stepM: number;
|
|
upTitle: string;
|
|
downTitle: string;
|
|
resetTitle: string;
|
|
adjust: (deltaM: number) => void;
|
|
reset: () => void;
|
|
}
|
|
export const rockValueText = (offsetM: number): string =>
|
|
`${offsetM >= 0 ? "+" : ""}${offsetM.toFixed(1)}m`;
|
|
export function rockCellState(rock: RockCellSpec): LineCellState {
|
|
return {
|
|
title: rock.legend,
|
|
value: rockValueText(rock.offsetM),
|
|
buttons: {
|
|
up: {
|
|
title: `${rock.upTitle} (+${rock.stepM}m)`,
|
|
act: () => rock.adjust(rock.stepM),
|
|
hold: true,
|
|
},
|
|
down: {
|
|
title: `${rock.downTitle} (-${rock.stepM}m)`,
|
|
act: () => rock.adjust(-rock.stepM),
|
|
hold: true,
|
|
},
|
|
reset: { title: rock.resetTitle, act: rock.reset },
|
|
},
|
|
};
|
|
}
|