refactor(E04): 보기 고르기 · 구간 줄 · 결과 값 목록 · 범례 판 · 진행 막대 · 띠 알림을 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,18 @@
|
|||||||
import "./E04_Stab3D_UI_Style.css";
|
import "./E04_Stab3D_UI_Style.css";
|
||||||
import { API_BASE_URL, CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend";
|
import { API_BASE_URL, CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend";
|
||||||
import { attachCollapsible, createPanelCard } from "@ui/ui_template_collapsible";
|
import { attachCollapsible, createPanelCard } from "@ui/ui_template_collapsible";
|
||||||
|
import { createBanner } from "@ui/ui_template_banner";
|
||||||
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
||||||
|
import { createEmpty } from "@ui/ui_template_empty";
|
||||||
|
import { createFloatPanel } from "@ui/ui_template_float_panel";
|
||||||
|
import { createKv, type KvRow } from "@ui/ui_template_kv";
|
||||||
|
import { createLegend } from "@ui/ui_template_flow_legend";
|
||||||
|
import { createListRow } from "@ui/ui_template_list_row";
|
||||||
import { t, type LocaleKey } from "@ui/ui_template_locale";
|
import { t, type LocaleKey } from "@ui/ui_template_locale";
|
||||||
|
import { createProgressBar } from "@ui/ui_template_progress";
|
||||||
|
import { createSegment } from "@ui/ui_template_segment";
|
||||||
import { createSidePanels } from "@ui/ui_template_side_panels";
|
import { createSidePanels } from "@ui/ui_template_side_panels";
|
||||||
|
import { createText } from "@ui/ui_template_text";
|
||||||
import { mountGroupStep } from "../A00_Common/group_workflow";
|
import { mountGroupStep } from "../A00_Common/group_workflow";
|
||||||
import { createPlanMap, type MapLayer, type StationPoint } from "./E04_Stab3D_UI_Map";
|
import { createPlanMap, type MapLayer, type StationPoint } from "./E04_Stab3D_UI_Map";
|
||||||
import { createView3D } from "./E04_Stab3D_UI_View3D";
|
import { createView3D } from "./E04_Stab3D_UI_View3D";
|
||||||
@@ -86,18 +95,36 @@ async function fetch2d(projectId: string): Promise<Station2d[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 안내 글 한 줄 — 걸이 이름(`e04-hint` 같은)만 얹음 */
|
||||||
|
function note(text: string, hook: string): HTMLElement {
|
||||||
|
const node = createText(text, "muted");
|
||||||
|
node.classList.add(hook);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyBox(text: string): HTMLElement {
|
||||||
|
const node = createEmpty({ text, small: true });
|
||||||
|
node.classList.add("e04-empty");
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 지도 범례 — 떠 있는 판(머리 = 제목) 안에 범례 부품 */
|
||||||
function legend(layer: MapLayer): HTMLElement {
|
function legend(layer: MapLayer): HTMLElement {
|
||||||
const box = el("div", { className: "e04-legend" });
|
|
||||||
box.append(el("strong", { text: t(layer === "fs" ? "E04_Legend_FS" : "E04_Legend_Volume") }));
|
|
||||||
const rows = layer === "fs" ? legendRows(FS_STEPS, 1) : legendRows(VOLUME_STEPS, 0);
|
const rows = layer === "fs" ? legendRows(FS_STEPS, 1) : legendRows(VOLUME_STEPS, 0);
|
||||||
for (const row of [...rows, { color: stepColor(FS_STEPS, null), label: t("E04_NoData") }]) {
|
const panel = createFloatPanel({
|
||||||
const item = el("span", { className: "e04-legend__row" });
|
title: t(layer === "fs" ? "E04_Legend_FS" : "E04_Legend_Volume"),
|
||||||
const swatch = el("i", { className: "e04-legend__swatch" });
|
});
|
||||||
swatch.style.background = row.color;
|
panel.root.classList.add("e04-legend");
|
||||||
item.append(swatch, el("span", { text: row.label }));
|
panel.body.append(
|
||||||
box.append(item);
|
createLegend(
|
||||||
}
|
[...rows, { color: stepColor(FS_STEPS, null), label: t("E04_NoData") }].map((row) => ({
|
||||||
return box;
|
label: row.label,
|
||||||
|
color: row.color,
|
||||||
|
})),
|
||||||
|
{ vertical: true },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return panel.root;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderE04Stab3D(root: HTMLElement): void {
|
export function renderE04Stab3D(root: HTMLElement): void {
|
||||||
@@ -105,8 +132,7 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
const panel = el("div", { className: "ui-sidebar-panel e04-panel" });
|
const panel = el("div", { className: "ui-sidebar-panel e04-panel" });
|
||||||
const main = el("div", { className: "e04-page" });
|
const main = el("div", { className: "e04-page" });
|
||||||
mountGroupStep(root, ROUTES.E04_STAB_3D, { mainContent: main, leftPanel: panel });
|
mountGroupStep(root, ROUTES.E04_STAB_3D, { mainContent: main, leftPanel: panel });
|
||||||
if (!projectId)
|
if (!projectId) return void main.append(emptyBox(t("E04_NoProject")));
|
||||||
return void main.append(el("p", { className: "e04-empty", text: t("E04_NoProject") }));
|
|
||||||
|
|
||||||
// ---- 가운데 ----
|
// ---- 가운데 ----
|
||||||
const map = createPlanMap();
|
const map = createPlanMap();
|
||||||
@@ -115,19 +141,14 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
let mode: "plan" | "3d" = "plan";
|
let mode: "plan" | "3d" = "plan";
|
||||||
let layer: MapLayer = "fs";
|
let layer: MapLayer = "fs";
|
||||||
const toggle = (labels: [LocaleKey, string][], onPick: (value: string) => void) => {
|
const toggle = (labels: [LocaleKey, string][], onPick: (value: string) => void) => {
|
||||||
const group = el("div", { className: "e04-toggle" });
|
const group = createSegment({
|
||||||
const buttons = labels.map(([key, value], i) => {
|
options: labels.map(([key, value]) => ({ value, label: t(key) })),
|
||||||
const button = createButton({ label: t(key), variant: "ghost", size: "sm" });
|
value: labels[0][1],
|
||||||
button.dataset.value = value;
|
size: 24,
|
||||||
button.classList.toggle("is-on", i === 0);
|
onChange: onPick,
|
||||||
button.addEventListener("click", () => {
|
|
||||||
buttons.forEach((b) => b.classList.toggle("is-on", b === button));
|
|
||||||
onPick(value);
|
|
||||||
});
|
|
||||||
return button;
|
|
||||||
});
|
});
|
||||||
group.append(...buttons);
|
group.root.classList.add("e04-toggle");
|
||||||
return group;
|
return group.root;
|
||||||
};
|
};
|
||||||
const legendSlot = el("div", { className: "e04-legend-slot" });
|
const legendSlot = el("div", { className: "e04-legend-slot" });
|
||||||
const paintLegend = (): void => legendSlot.replaceChildren(legend(layer));
|
const paintLegend = (): void => legendSlot.replaceChildren(legend(layer));
|
||||||
@@ -183,8 +204,8 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
const sectionCard = createPanelCard(t("E04_Card_Sections"), false);
|
const sectionCard = createPanelCard(t("E04_Card_Sections"), false);
|
||||||
const settings = buildSettingsCards();
|
const settings = buildSettingsCards();
|
||||||
sectionCard.append(sectionList, settings.rangeRow);
|
sectionCard.append(sectionList, settings.rangeRow);
|
||||||
const progressBar = el("progress", { className: "e04-progress", attrs: { max: "100" } });
|
const progressBar = createProgressBar();
|
||||||
const progressText = el("p", { className: "e04-progress__text" });
|
const progressText = note("", "e04-progress__text");
|
||||||
const cancelButton = createButton({
|
const cancelButton = createButton({
|
||||||
label: t("E04_Cancel"),
|
label: t("E04_Cancel"),
|
||||||
variant: "danger-ghost",
|
variant: "danger-ghost",
|
||||||
@@ -192,7 +213,7 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
onClick: () => void api(`/projects/${projectId}/stab/analyze3d/cancel`, { method: "POST" }),
|
onClick: () => void api(`/projects/${projectId}/stab/analyze3d/cancel`, { method: "POST" }),
|
||||||
});
|
});
|
||||||
const progressBox = el("div", { className: "e04-progress-box" });
|
const progressBox = el("div", { className: "e04-progress-box" });
|
||||||
progressBox.append(progressBar, progressText, cancelButton);
|
progressBox.append(progressBar.root, progressText, cancelButton);
|
||||||
progressBox.hidden = true;
|
progressBox.hidden = true;
|
||||||
const analyzeButton = createButton({
|
const analyzeButton = createButton({
|
||||||
label: t("E04_Analyze"),
|
label: t("E04_Analyze"),
|
||||||
@@ -207,10 +228,10 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
analyzeButton.disabled = reanalyzeButton.disabled = true;
|
analyzeButton.disabled = reanalyzeButton.disabled = true;
|
||||||
const actions = el("div", { className: "ui-sidebar-actions" });
|
const actions = el("div", { className: "ui-sidebar-actions" });
|
||||||
actions.append(reanalyzeButton, analyzeButton);
|
actions.append(reanalyzeButton, analyzeButton);
|
||||||
const readOnly = el("p", { className: "e04-hint", text: t("E04_ReadOnly") });
|
const readOnly = note(t("E04_ReadOnly"), "e04-hint");
|
||||||
readOnly.hidden = true;
|
readOnly.hidden = true;
|
||||||
panel.append(
|
panel.append(
|
||||||
el("p", { className: "e04-hint", text: t("E04_Hint") }),
|
note(t("E04_Hint"), "e04-hint"),
|
||||||
readOnly,
|
readOnly,
|
||||||
sectionCard,
|
sectionCard,
|
||||||
settings.basicCard,
|
settings.basicCard,
|
||||||
@@ -258,31 +279,25 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
...items.map((section) => {
|
...items.map((section) => {
|
||||||
const key = sectionKey(section.start_m, section.end_m);
|
const key = sectionKey(section.start_m, section.end_m);
|
||||||
const saved = savedOf.get(key);
|
const saved = savedOf.get(key);
|
||||||
const button = el("button", {
|
const row = createListRow({
|
||||||
className: `e04-section${key === sectionKey(start_m, end_m) ? " is-active" : ""}`,
|
label: `${section.start_m.toFixed(0)} – ${section.end_m.toFixed(0)} m`,
|
||||||
attrs: { type: "button", "data-key": key },
|
trailing: saved
|
||||||
|
? `Fs ${saved.f_min.toFixed(2)}${saved.stale ? ` · ${t("E04_Stale")}` : ""}`
|
||||||
|
: "",
|
||||||
|
stripe: saved ? stepColor(FS_STEPS, saved.f_min) : "transparent",
|
||||||
|
active: key === sectionKey(start_m, end_m),
|
||||||
|
onClick: () => settings.setRange(section.start_m, section.end_m),
|
||||||
});
|
});
|
||||||
const chip = el("i", { className: "e04-section__chip" });
|
row.classList.add("e04-section");
|
||||||
chip.style.background = saved ? stepColor(FS_STEPS, saved.f_min) : "transparent";
|
row.dataset.key = key;
|
||||||
button.append(
|
return row;
|
||||||
chip,
|
|
||||||
el("span", { text: `${section.start_m.toFixed(0)} – ${section.end_m.toFixed(0)} m` }),
|
|
||||||
el("span", {
|
|
||||||
className: "e04-section__value",
|
|
||||||
text: saved
|
|
||||||
? `Fs ${saved.f_min.toFixed(2)}${saved.stale ? ` · ${t("E04_Stale")}` : ""}`
|
|
||||||
: "",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
button.addEventListener("click", () => settings.setRange(section.start_m, section.end_m));
|
|
||||||
return button;
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function paintResult(): void {
|
function paintResult(): void {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
resultBody.replaceChildren(el("p", { className: "e04-empty", text: t("E04_Result_None") }));
|
resultBody.replaceChildren(emptyBox(t("E04_Result_None")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const s = result.summary;
|
const s = result.summary;
|
||||||
@@ -319,20 +334,33 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
["E04_Result_Trials", s.trials.toLocaleString()],
|
["E04_Result_Trials", s.trials.toLocaleString()],
|
||||||
["E04_Result_Time", `${result.elapsed_s.toFixed(1)} s`],
|
["E04_Result_Time", `${result.elapsed_s.toFixed(1)} s`],
|
||||||
];
|
];
|
||||||
const list = el("dl", { className: "e04-result__list" });
|
const kv: KvRow[] = rows.map(([key, value, color]) => {
|
||||||
for (const [key, value, color] of rows) {
|
const cell = document.createDocumentFragment();
|
||||||
const dd = el("dd", { text: value, attrs: { "data-field": key } });
|
|
||||||
if (key === "E04_Result_Verdict") dd.classList.add(`is-${s.verdict}`);
|
|
||||||
if (color) {
|
if (color) {
|
||||||
const chip = el("i", { className: "e04-section__chip" });
|
const chip = el("i", { className: "e04-section__chip" });
|
||||||
chip.style.background = color;
|
chip.style.background = color;
|
||||||
dd.prepend(chip);
|
cell.append(chip);
|
||||||
}
|
}
|
||||||
list.append(el("dt", { text: t(key) }), dd);
|
cell.append(value);
|
||||||
}
|
return {
|
||||||
|
label: t(key),
|
||||||
|
value: cell,
|
||||||
|
...(key === "E04_Result_Verdict" ? { ok: s.verdict === "ok" } : {}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const list = createKv(kv);
|
||||||
|
list.classList.add("e04-result__list");
|
||||||
|
list.querySelectorAll("dd").forEach((dd, i) => {
|
||||||
|
dd.setAttribute("data-field", rows[i][0]);
|
||||||
|
if (rows[i][0] === "E04_Result_Verdict") dd.classList.add(`is-${s.verdict}`);
|
||||||
|
});
|
||||||
const notes: HTMLElement[] = [];
|
const notes: HTMLElement[] = [];
|
||||||
if (result.stale) notes.push(el("p", { className: "e04-warn", text: t("E04_Result_Stale") }));
|
if (result.stale) {
|
||||||
notes.push(el("p", { className: "e04-note", text: t("E04_Result_Note") }));
|
const warn = createBanner({ tone: "warning", text: t("E04_Result_Stale") });
|
||||||
|
warn.classList.add("e04-warn");
|
||||||
|
notes.push(warn);
|
||||||
|
}
|
||||||
|
notes.push(note(t("E04_Result_Note"), "e04-note"));
|
||||||
resultBody.replaceChildren(list, ...notes);
|
resultBody.replaceChildren(list, ...notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,8 +390,8 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
if (!p) break;
|
if (!p) break;
|
||||||
running = p.running;
|
running = p.running;
|
||||||
progressBox.hidden = !p.running;
|
progressBox.hidden = !p.running;
|
||||||
progressBar.value = p.percent;
|
progressBar.set(p.percent / 100);
|
||||||
progressText.textContent = `${t("E04_Running")} ${p.percent}% · ${p.message ?? ""}`;
|
progressText.replaceChildren(`${t("E04_Running")} ${p.percent}% · ${p.message ?? ""}`);
|
||||||
paintButtons();
|
paintButtons();
|
||||||
if (!p.running) {
|
if (!p.running) {
|
||||||
if (p.error) showToast(p.error, "error");
|
if (p.error) showToast(p.error, "error");
|
||||||
@@ -404,12 +432,7 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
|||||||
try {
|
try {
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
main.replaceChildren(
|
main.replaceChildren(emptyBox(error instanceof Error ? error.message : String(error)));
|
||||||
el("p", {
|
|
||||||
className: "e04-empty",
|
|
||||||
text: error instanceof Error ? error.message : String(error),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ov = overview!;
|
const ov = overview!;
|
||||||
|
|||||||
@@ -29,17 +29,6 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: var(--spacing-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-toggle .ui-btn.is-on {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: var(--color-primary-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-stage {
|
.e04-stage {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@@ -99,23 +88,10 @@
|
|||||||
right: var(--spacing-8);
|
right: var(--spacing-8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 판 · 범례 · 목록 줄 · 값 목록 · 띠 알림은 부품 — 여기는 자리만 */
|
||||||
.e04-view3d__tools {
|
.e04-view3d__tools {
|
||||||
position: absolute;
|
|
||||||
top: var(--spacing-8);
|
top: var(--spacing-8);
|
||||||
left: var(--spacing-8);
|
left: var(--spacing-8);
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-12);
|
|
||||||
padding: var(--spacing-4) var(--spacing-8);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: var(--color-surface);
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-view3d__toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-legend-slot {
|
.e04-legend-slot {
|
||||||
@@ -125,30 +101,16 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-legend {
|
.e04-legend-slot .e04-legend {
|
||||||
display: flex;
|
position: static;
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
padding: var(--spacing-8);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: var(--color-surface);
|
|
||||||
color: var(--color-text);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-legend__row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-legend__swatch,
|
|
||||||
.e04-section__chip {
|
.e04-section__chip {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
flex: none;
|
flex: none;
|
||||||
|
margin-right: var(--spacing-4);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
@@ -156,10 +118,7 @@
|
|||||||
/* 좌측 */
|
/* 좌측 */
|
||||||
.e04-hint,
|
.e04-hint,
|
||||||
.e04-note {
|
.e04-note {
|
||||||
margin: 0 0 var(--spacing-8);
|
margin-bottom: var(--spacing-8);
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-sections {
|
.e04-sections {
|
||||||
@@ -170,35 +129,6 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-section {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr auto;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-8);
|
|
||||||
padding: var(--spacing-4) var(--spacing-8);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--color-text);
|
|
||||||
font: inherit;
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-section:hover {
|
|
||||||
background: var(--color-surface-raised);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-section.is-active {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-section__value {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-range .ui-field,
|
.e04-range .ui-field,
|
||||||
.e04-advanced .ui-field {
|
.e04-advanced .ui-field {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -229,58 +159,15 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-progress {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-progress__text {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 우측 */
|
/* 우측 */
|
||||||
.e04-result {
|
.e04-result {
|
||||||
padding: var(--spacing-12);
|
padding: var(--spacing-12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-result__list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
gap: var(--spacing-8) var(--spacing-12);
|
|
||||||
margin: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-result__list dt {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-result__list dd {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-8);
|
|
||||||
margin: 0;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-weight: 600;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-result__list dd.is-ok {
|
|
||||||
color: var(--color-success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-result__list dd.is-ng {
|
|
||||||
color: var(--color-danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.e04-warn {
|
.e04-warn {
|
||||||
margin: var(--spacing-12) 0 0;
|
margin-top: var(--spacing-12);
|
||||||
color: var(--color-danger);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.e04-empty {
|
.e04-empty {
|
||||||
margin: var(--spacing-16);
|
margin: var(--spacing-16);
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
||||||
|
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||||
import { createTerrainCompass } from "@ui/ui_template_compass";
|
import { createTerrainCompass } from "@ui/ui_template_compass";
|
||||||
|
import { createFloatPanel } from "@ui/ui_template_float_panel";
|
||||||
import { el } from "@ui/ui_template_elements";
|
import { el } from "@ui/ui_template_elements";
|
||||||
import { t } from "@ui/ui_template_locale";
|
import { t } from "@ui/ui_template_locale";
|
||||||
import {
|
import {
|
||||||
@@ -46,13 +48,13 @@ export function createView3D(): View3D {
|
|||||||
const canvas = el("canvas", { className: "e04-view3d__canvas" });
|
const canvas = el("canvas", { className: "e04-view3d__canvas" });
|
||||||
const compass = createTerrainCompass({ sizePx: 88, className: "e04-view3d__compass" });
|
const compass = createTerrainCompass({ sizePx: 88, className: "e04-view3d__compass" });
|
||||||
// [임계 활동면] 켬 = 활동체를 걷어낸 지표 + 잘린 활동면(Scoops3D 꼴) · 끔 = 원래 지표
|
// [임계 활동면] 켬 = 활동체를 걷어낸 지표 + 잘린 활동면(Scoops3D 꼴) · 끔 = 원래 지표
|
||||||
const slipToggle = el("input", { attrs: { type: "checkbox" } });
|
const slipBox = createCheckbox(t("E04_SlipSurface"), { checked: true });
|
||||||
slipToggle.checked = true;
|
const slipToggle = slipBox.input;
|
||||||
const tools = el("div", { className: "e04-view3d__tools" });
|
slipBox.root.classList.add("e04-view3d__toggle");
|
||||||
const label = el("label", { className: "e04-view3d__toggle" });
|
const tools = createFloatPanel({ popover: true });
|
||||||
label.append(slipToggle, el("span", { text: t("E04_SlipSurface") }));
|
tools.root.classList.add("e04-view3d__tools");
|
||||||
tools.append(label);
|
tools.body.append(slipBox.root);
|
||||||
root.append(canvas, compass.root, tools);
|
root.append(canvas, compass.root, tools.root);
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
||||||
renderer.setPixelRatio(window.devicePixelRatio || 1);
|
renderer.setPixelRatio(window.devicePixelRatio || 1);
|
||||||
|
|||||||
@@ -105,3 +105,31 @@ def test_e03_parts():
|
|||||||
".e03-view3d {", ".e03-swatch-none", ".e03-list table", ".e03-list th {",
|
".e03-view3d {", ".e03-swatch-none", ".e03-list table", ".e03-list th {",
|
||||||
): # fmt: skip
|
): # fmt: skip
|
||||||
assert dead not in css, dead
|
assert dead not in css, dead
|
||||||
|
|
||||||
|
|
||||||
|
def test_e04_parts():
|
||||||
|
page = _src("E04_Stab3D/E04_Stab3D_UI_Page.ts")
|
||||||
|
view = _src("E04_Stab3D/E04_Stab3D_UI_View3D.ts")
|
||||||
|
css = _src("E04_Stab3D/E04_Stab3D_UI_Style.css")
|
||||||
|
assert 'el("progress"' not in page and "createProgressBar()" in page
|
||||||
|
for part in (
|
||||||
|
"createSegment(",
|
||||||
|
"createListRow(",
|
||||||
|
"createKv(",
|
||||||
|
"createFloatPanel(",
|
||||||
|
"createBanner(",
|
||||||
|
):
|
||||||
|
assert part in page, part
|
||||||
|
assert 'el("input"' not in view and "createCheckbox(" in view
|
||||||
|
assert (
|
||||||
|
'classList.add("e04-toggle")' in page
|
||||||
|
) # 촬영 장면이 `.e04-toggle button[data-value]` 로 잡음
|
||||||
|
assert 'row.classList.add("e04-section")' in page
|
||||||
|
for dead in (
|
||||||
|
".e04-toggle .ui-btn",
|
||||||
|
".e04-legend__row",
|
||||||
|
".e04-legend__swatch",
|
||||||
|
".e04-result__list",
|
||||||
|
".e04-section:hover",
|
||||||
|
):
|
||||||
|
assert dead not in css, dead
|
||||||
|
|||||||
Reference in New Issue
Block a user