Files
Aislo/A00_Common/map/map_layer_bar.ts
T
eomsangdonandClaude Opus 5.5 cd0f68946b fix(부품): 묶음 단추 켠 수 원형 숫자 걷음(요구 551)
- createPickMenu 여럿 = 아이콘 + ▾ · 켠 표시는 단추 눌림(하나라도 켜면) 그대로
- 배지 count accent 덧 · 메뉴 css 배지 줄 걷음 · 견본 · 겹 줄 머리 글 맞춤
- 시험 634_1 고침 — 배지 대신 「배지 없음 · 눌림 = 켠 줄 있음」을 잡음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:49:26 +09:00

134 lines
5.0 KiB
TypeScript

/* =============================================================================
* map_layer_bar.ts
* 공용 지도 겹 줄(PLAN 569-2 · 586-2 · 624-2 · 634-2 · 요구 530 · 546) — 겹 단추 한 줄(공용 지도 도구줄 `createMapTools`):
* 단독 단추(표 묶음 없음) | 묶음 단추(여럿 고르기 메뉴 · 하나라도 켜면 눌림 · 골라도 안 닫힘) | 그래픽 카드 단추(GL ↔ 옛 캔버스).
* 보일 겹 · 묶음 · 호버제목 · 처음 켬 · 취향 열쇠 = 표(`map_layer_list`) · 누르면 켬끔 뒤집고 취향에 적고 `onToggle`.
* 막힘 = 지형을 아직 안 받음(`ready: false`) · 조각만 쓰는 겹은 옛 캔버스에서 · 페이지 까닭(`why`) — 「제목 — 까닭」.
* 받기 · 그리기 · 범례는 페이지 몫 — 켬끔 값(`on`)은 같은 객체를 같이 씀.
* 쓰는 법 = `createMapLayerBar({ page: "d03", glOn, onToggle, onGl })` → `{ root, bar, on, toggle, save, sync }`.
* ========================================================================== */
import { createIconButton } from "@ui/ui_template_button";
import { createPickMenu, type PickMenu } from "@ui/ui_template_context_menu";
import { createMapTools, mapToolTip } from "@ui/ui_template_map_tools";
import { writeByKey } from "../b_page_state";
import { tileOnly } from "./map_forest_layers";
import {
LAYER_GROUPS,
MAP_LAYER_PAGES,
pageLayers,
readLayerPref,
type LayerGroup,
type MapLayerKey,
type MapPage,
} from "./map_layer_list";
/** 그래픽 카드 단추 호버(표 밖 · 그룹1 끝 단독). */
const GL_TITLE = "그래픽 카드";
export interface MapLayerBar {
/** 도구줄 감싼 것(자리는 페이지 CSS · 한 행 = `createMapToolRow`). */
root: HTMLElement;
/** 도구줄 자체(폭 재기). */
bar: HTMLElement;
/** 켬끔 — 페이지가 바꾸면 `save` · 받기 실패로 끄는 것은 안 적음. */
on: Record<MapLayerKey, boolean>;
/** 뒤집고 적고 `onToggle`. */
toggle: (key: MapLayerKey) => void;
save: () => void;
/** 단추 · 메뉴 줄의 눌림 · 막힘 · 글을 켬끔 값에 맞춤 · `why` = 페이지 막힘 까닭(겹마다). */
sync: (state: {
ready: boolean;
glOn: boolean;
why?: Partial<Record<MapLayerKey, string>>;
}) => void;
}
export function createMapLayerBar(opts: {
page: MapPage;
glOn: boolean;
onToggle: (key: MapLayerKey) => void;
/** 그래픽 카드 단추 누름. */
onGl: () => void;
}): MapLayerBar {
const { page } = opts;
const on = readLayerPref(page);
const save = (): void => writeByKey(MAP_LAYER_PAGES[page].pref, JSON.stringify(on));
const toggle = (key: MapLayerKey): void => {
on[key] = !on[key];
save();
opts.onToggle(key);
};
const defs = pageLayers(page);
const buttons = new Map<MapLayerKey, HTMLButtonElement>();
const menus = new Map<LayerGroup, PickMenu>();
const singles: HTMLElement[] = [];
for (const def of defs) {
if (!def.group) {
const button = createIconButton({
icon: def.icon,
title: def.title,
size: 32,
pressed: on[def.key],
onClick: () => toggle(def.key),
});
button.dataset.layer = def.key;
button.dataset.tip = def.title;
buttons.set(def.key, button);
singles.push(button);
continue;
}
if (menus.has(def.group)) continue;
const group = def.group;
const menu = createPickMenu({
multi: true,
icon: LAYER_GROUPS[group].icon,
title: LAYER_GROUPS[group].name,
items: defs
.filter((one) => one.group === group)
.map((one) => ({ value: one.key, label: one.title, icon: one.icon })),
onPick: (key) => key && toggle(key as MapLayerKey),
});
menu.root.dataset.layerGroup = group;
menus.set(group, menu);
}
for (const def of defs)
if (def.group) menus.get(def.group)!.setItem(def.key, { checked: on[def.key] });
const glButton = createIconButton({
icon: "gauge",
title: GL_TITLE,
size: 32,
pressed: opts.glOn,
onClick: opts.onGl,
});
glButton.dataset.layer = "gl";
const tools = createMapTools({
label: "지도 겹",
groups: [singles, [...menus.values()].map((menu) => menu.root), [glButton]],
});
function sync({ ready, glOn, why = {} }: Parameters<MapLayerBar["sync"]>[0]): void {
for (const def of defs) {
const reason = !ready
? "지형 받기 뒤 보임"
: (why[def.key] ??
(tileOnly(def.key) && !glOn ? "옛 그림(그래픽 카드 끔)엔 안 보임" : null));
const button = buttons.get(def.key);
if (button) {
button.setAttribute("aria-pressed", String(on[def.key]));
button.disabled = !!reason;
mapToolTip(button, reason);
continue;
}
menus.get(def.group!)!.setItem(def.key, {
checked: on[def.key],
disabled: !!reason,
title: reason ? `${def.title} — ${reason}` : def.title,
});
}
glButton.setAttribute("aria-pressed", String(glOn));
}
return { root: tools.root, bar: tools.bar, on, toggle, save, sync };
}