260710_0
This commit is contained in:
@@ -544,6 +544,14 @@ export const ui_locales = {
|
||||
B03_File_Error_Size: ["파일 크기 제한을 초과했습니다.", "File size limit exceeded."],
|
||||
B03_File_Upload_Success: ["입력 파일 업로드를 완료했습니다.", "Input files uploaded."],
|
||||
B03_File_Upload_Failed: ["파일 업로드에 실패했습니다.", "File upload failed."],
|
||||
B03_File_Analysis_InProgress: [
|
||||
"WF1 분석이 백그라운드에서 진행 중입니다. 완료되면 자동으로 이동합니다.",
|
||||
"WF1 analysis is running in the background. You will move automatically when it completes.",
|
||||
],
|
||||
B03_File_Analysis_StillRunning: [
|
||||
"분석이 계속 진행 중입니다. 잠시 후 다시 확인하세요.",
|
||||
"Analysis is still running. Check again shortly.",
|
||||
],
|
||||
B03_File_Result_Path: ["저장 경로", "Stored path"],
|
||||
B03_File_Group_Required: ["필수 파일", "Required files"],
|
||||
B03_File_Group_Optional: ["선택 파일", "Optional files"],
|
||||
@@ -574,6 +582,7 @@ export const ui_locales = {
|
||||
B03_File_Status_Completed: ["완료", "Completed"],
|
||||
B03_File_Status_Failed: ["실패", "Failed"],
|
||||
B03_File_Status_Detected: ["중단된 업로드 감지", "Paused upload detected"],
|
||||
B03_File_Restore_State: ["저장된 업로드/분석 상태 복구", "Restored upload/analysis state"],
|
||||
B03_File_Resume_Button: ["업로드 재개", "Resume upload"],
|
||||
B03_File_New_Button: ["새 파일로 시작", "Start new file"],
|
||||
B03_File_ServiceWorker_Ready: [
|
||||
@@ -594,6 +603,20 @@ export const ui_locales = {
|
||||
B04_Surface_Field_Force: ["기존 결과 무시하고 재계산", "Force recompute"],
|
||||
B04_Surface_Btn_Analyze: ["지표면 분석 실행", "Run Surface Analysis"],
|
||||
B04_Surface_Btn_Refresh: ["목록 새로고침", "Refresh"],
|
||||
B04_Surface_InputFiles: ["입력 포인트클라우드", "Input point cloud"],
|
||||
B04_Surface_InputFiles_Empty: [
|
||||
"분석 가능한 LAS/LAZ 파일이 없습니다.",
|
||||
"No LAS/LAZ file is available.",
|
||||
],
|
||||
B04_Surface_Input_FileName: ["파일명", "File name"],
|
||||
B04_Surface_Input_Crs: ["좌표계", "CRS"],
|
||||
B04_Surface_Input_Size: ["크기(MB)", "Size (MB)"],
|
||||
B04_Surface_PointCloud_Title: ["포인트클라우드 미리보기", "Point cloud preview"],
|
||||
B04_Surface_Status_Unknown: ["상태 미확인", "Unknown"],
|
||||
B04_Surface_GroundStats_Title: ["지면 필터 통계", "Ground filter stats"],
|
||||
B04_Surface_GroundStats_Empty: ["표시할 지면 통계가 없습니다.", "No ground stats to display."],
|
||||
B04_Surface_GroundStats_Filter: ["필터", "Filter"],
|
||||
B04_Surface_GroundStats_SourcePoints: ["지면 포인트", "Ground points"],
|
||||
B04_Surface_Result_Title: ["생성된 지표면 모델", "Generated Surface Models"],
|
||||
B04_Surface_Result_Empty: [
|
||||
"아직 생성된 지표면 모델이 없습니다.",
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
.ui-workflow-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - var(--wf-header-height));
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.ui-workflow-layout__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-16);
|
||||
min-height: var(--wf-header-height);
|
||||
padding: var(--spacing-8) var(--spacing-24);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.ui-workflow-layout__menu-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-buttons);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-24);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__title {
|
||||
font-size: var(--text-subheading);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__steps {
|
||||
display: flex;
|
||||
gap: var(--spacing-8);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__step {
|
||||
flex: 0 0 auto;
|
||||
padding: var(--spacing-4) var(--spacing-16);
|
||||
border-radius: var(--radius-pills);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.ui-workflow-layout__step.is-active {
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.ui-workflow-layout__body {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 280px minmax(0, 1fr);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__panel {
|
||||
z-index: var(--z-dropdown);
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid var(--color-border);
|
||||
background: var(--color-surface);
|
||||
transition:
|
||||
margin-left var(--transition-base),
|
||||
box-shadow var(--transition-base);
|
||||
}
|
||||
|
||||
.ui-workflow-layout:not(.is-menu-open) .ui-workflow-layout__panel {
|
||||
margin-left: -280px;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__main {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.ui-workflow-layout__title-wrap {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.ui-workflow-layout__body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__panel {
|
||||
position: absolute;
|
||||
inset: 0 auto 0 0;
|
||||
width: 280px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.ui-workflow-layout:not(.is-menu-open) .ui-workflow-layout__panel {
|
||||
margin-left: -280px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ui-workflow-layout__main {
|
||||
min-height: calc(100vh - var(--wf-header-height));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import "./ui_template_workflow_layout.css";
|
||||
|
||||
export interface WorkflowLayoutOptions {
|
||||
title: string;
|
||||
steps: string[];
|
||||
activeStep: number;
|
||||
leftPanel: HTMLElement;
|
||||
mainContent: HTMLElement;
|
||||
onMenuToggle?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export interface WorkflowLayoutHandle {
|
||||
root: HTMLElement;
|
||||
setMenuOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
function createStepBar(steps: readonly string[], activeStep: number): HTMLElement {
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "ui-workflow-layout__steps";
|
||||
steps.forEach((step, index) => {
|
||||
const item = document.createElement("span");
|
||||
item.className = "ui-workflow-layout__step";
|
||||
if (index === activeStep) item.classList.add("is-active");
|
||||
item.textContent = step;
|
||||
bar.append(item);
|
||||
});
|
||||
return bar;
|
||||
}
|
||||
|
||||
export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLayoutHandle {
|
||||
const root = document.createElement("div");
|
||||
root.className = "ui-workflow-layout";
|
||||
|
||||
const header = document.createElement("header");
|
||||
header.className = "ui-workflow-layout__header";
|
||||
|
||||
const menuButton = document.createElement("button");
|
||||
menuButton.type = "button";
|
||||
menuButton.className = "ui-workflow-layout__menu-button";
|
||||
menuButton.setAttribute("aria-label", "Toggle workflow menu");
|
||||
menuButton.textContent = "☰";
|
||||
|
||||
const titleWrap = document.createElement("div");
|
||||
titleWrap.className = "ui-workflow-layout__title-wrap";
|
||||
const title = document.createElement("h2");
|
||||
title.className = "ui-workflow-layout__title";
|
||||
title.textContent = options.title;
|
||||
titleWrap.append(title, createStepBar(options.steps, options.activeStep));
|
||||
|
||||
header.append(menuButton, titleWrap);
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.className = "ui-workflow-layout__body";
|
||||
|
||||
const aside = document.createElement("aside");
|
||||
aside.className = "ui-workflow-layout__panel";
|
||||
aside.append(options.leftPanel);
|
||||
|
||||
const main = document.createElement("main");
|
||||
main.className = "ui-workflow-layout__main";
|
||||
main.append(options.mainContent);
|
||||
|
||||
body.append(aside, main);
|
||||
root.append(header, body);
|
||||
|
||||
function setMenuOpen(isOpen: boolean): void {
|
||||
root.classList.toggle("is-menu-open", isOpen);
|
||||
menuButton.setAttribute("aria-expanded", String(isOpen));
|
||||
options.onMenuToggle?.(isOpen);
|
||||
}
|
||||
|
||||
menuButton.addEventListener("click", () => setMenuOpen(!root.classList.contains("is-menu-open")));
|
||||
main.addEventListener("click", () => {
|
||||
if (root.classList.contains("is-menu-open")) setMenuOpen(false);
|
||||
});
|
||||
setMenuOpen(true);
|
||||
|
||||
return { root, setMenuOpen };
|
||||
}
|
||||
Reference in New Issue
Block a user