Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_6' into sub_laptop_5
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
"""D01 예정노선 등록 API · 대시보드 「받은 예정노선」 (요구 345 · 요구서 3-1 · 2-2)."""
|
||||
"""D01 예정노선 등록 API · 대시보드 「받은 예정노선」 (요구 345 · 요구서 3-1 · 2-2).
|
||||
|
||||
화면은 이름 · 시작 연도 · 사업 지역만 보냄(요구서 389 3장 1 · 3) — 끝 연도 = 시작 · 임도 종류 없음
|
||||
= 설계속도 작업임도 20 · 측점 없음(DB 칸 그대로).
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
@@ -21,7 +25,7 @@ class CreatePlanRequest(BaseModel):
|
||||
project_year: int = Field(..., ge=2000, le=2100)
|
||||
project_year_end: int | None = Field(default=None, ge=2000, le=2100)
|
||||
region: str = Field(..., min_length=1, max_length=100)
|
||||
road_type: str = Field(..., pattern="^(main|fire|work)$")
|
||||
road_type: str | None = Field(default=None, pattern="^(main|fire|work)$")
|
||||
design_speed_kph: int | None = None
|
||||
start_chainage_m: float | None = Field(default=None, ge=0)
|
||||
end_chainage_m: float | None = Field(default=None, ge=0)
|
||||
|
||||
@@ -1,51 +1,25 @@
|
||||
/* =============================================================================
|
||||
* D01_PlanRegister_UI_Page.ts
|
||||
* D01 예정노선 등록(요구서 345 3-1 · PLAN 529-1) — B02 등록 화면 꼴 · 도면 칸 없음.
|
||||
* 칸 = 사업연도 시작 · 끝 · 사업지역 · 종류 「노선 계획」(고정) · 임도 종류(B02 값) · 설계속도
|
||||
* (종류별 고를 수 있는 값 · 기본 = 가장 낮은 값 = B05 resolve_design_speed) · 프로젝트명(B02 이름
|
||||
* 조합기 · 연도 자리 = 「시작~끝」) · 노선 시작 · 종료 누가거리.
|
||||
* D01 예정노선 등록(요구서 345 3-1 · 389 3장 1~3 · PLAN 560-1) — B02 등록 화면 꼴 · 도면 칸 없음.
|
||||
* 칸 = 사업연도(시작) · 사업지역 · 프로젝트명(B02 이름 조합기 · 「<시작 연도> <사업 지역> 노선 계획」)
|
||||
* · 사업연도 끝 · 임도 종류 · 설계속도 · 측점은 안 받음(끝 연도 = D03 [저장] · 나머지 = 서버 기본).
|
||||
* [등록] = POST /api/d01/project → 현재 프로젝트 · D02 로.
|
||||
* ========================================================================== */
|
||||
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import {
|
||||
createButton,
|
||||
createCard,
|
||||
createInputField,
|
||||
createSelectField,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { createButton, createCard, createInputField } from "@ui/ui_template_elements";
|
||||
import { createFormGrid, createGeneralLayout } from "@ui/ui_template_general_layout";
|
||||
import { createHelpButton } from "@ui/ui_template_help";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { t } from "@ui/ui_template_locale";
|
||||
import { stationLabel } from "../common_util/common_util_station";
|
||||
import { createGroupProject } from "../A00_Common/group_workflow";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { createNameComposer } from "../B02_ProjRegister/B02_ProjRegister_UI_Name";
|
||||
import { SPEED_CHOICES } from "../B05_Profile/logic/B05_Profile_Grade_Criteria";
|
||||
|
||||
/** B02 임도 종류 → 설계속도 기준 등급(B05 grade_class_of 와 같음) */
|
||||
const GRADE_CLASS: Record<string, string> = { main: "trunk", fire: "fire", work: "work" };
|
||||
|
||||
/** 측점 간격 — D03 `PLAN_STATION_INTERVAL_M` 와 같음 */
|
||||
const STATION_INTERVAL_M = 20;
|
||||
|
||||
const numberOrNull = (text: string): number | null =>
|
||||
text.trim() === "" ? null : Number.parseFloat(text);
|
||||
|
||||
export function renderD01PlanRegister(root: HTMLElement): void {
|
||||
const year = new Date().getFullYear();
|
||||
const yearStart = createInputField({
|
||||
label: t("DE_Reg_YearStart"),
|
||||
type: "number",
|
||||
value: String(year),
|
||||
min: 2000,
|
||||
max: 2100,
|
||||
});
|
||||
const yearEnd = createInputField({
|
||||
label: t("DE_Reg_YearEnd"),
|
||||
type: "number",
|
||||
value: String(year),
|
||||
value: String(new Date().getFullYear()),
|
||||
min: 2000,
|
||||
max: 2100,
|
||||
});
|
||||
@@ -54,75 +28,29 @@ export function renderD01PlanRegister(root: HTMLElement): void {
|
||||
placeholder: t("B02_Proj_Field_Region_Placeholder"),
|
||||
required: true,
|
||||
});
|
||||
const kind = createInputField({ label: t("DE_Reg_Kind"), value: t("DE_Reg_KindPlan") });
|
||||
kind.input.readOnly = true;
|
||||
const roadType = createSelectField({
|
||||
label: t("B02_Proj_Field_RoadType"),
|
||||
options: [
|
||||
{ value: "main", text: t("B02_Proj_RoadType_Main") },
|
||||
{ value: "fire", text: t("B02_Proj_RoadType_Fire") },
|
||||
{ value: "work", text: t("B02_Proj_RoadType_Work") },
|
||||
],
|
||||
});
|
||||
const speed = createSelectField({ label: t("DE_Reg_DesignSpeed"), options: [] });
|
||||
const name = createInputField({ label: t("DE_Reg_Name"), required: true });
|
||||
const startM = createInputField({ label: t("DE_Reg_StartM"), type: "number", min: 0 });
|
||||
const endM = createInputField({ label: t("DE_Reg_EndM"), type: "number", min: 0 });
|
||||
// 누가거리 칸 옆 즉시 측점 표기(저장값 · 서버 검사는 m 그대로)
|
||||
for (const field of [startM, endM]) {
|
||||
const shown = createText("", "muted");
|
||||
shown.dataset.station = "";
|
||||
field.root.insertBefore(shown, field.root.lastElementChild);
|
||||
field.input.addEventListener("input", () => {
|
||||
const value = numberOrNull(field.input.value);
|
||||
shown.textContent =
|
||||
value !== null && Number.isFinite(value) && value >= 0
|
||||
? `= ${stationLabel(value, STATION_INTERVAL_M, 1)}`
|
||||
: "";
|
||||
});
|
||||
}
|
||||
|
||||
// 설계속도 = 종류별 고를 수 있는 값 · 기본 = 가장 낮은 값(20)
|
||||
const syncSpeeds = (): void => {
|
||||
const choices = SPEED_CHOICES[GRADE_CLASS[roadType.select.value]] ?? [20];
|
||||
speed.setOptions(
|
||||
choices.map((kph) => ({ value: String(kph), text: `${kph} km/h` })),
|
||||
String(Math.min(...choices)),
|
||||
);
|
||||
};
|
||||
|
||||
// 이름 = B02 조합기 그대로(손대기 전 = 자동 · 고친 뒤 = 조각만 갈아 끼움)
|
||||
const composer = createNameComposer();
|
||||
const years = (): string => {
|
||||
const [a, b] = [yearStart.input.value.trim(), yearEnd.input.value.trim()];
|
||||
return b && b !== a ? `${a}~${b}` : a;
|
||||
};
|
||||
const syncName = (): void => {
|
||||
name.input.value = composer.update({
|
||||
year: years(),
|
||||
year: yearStart.input.value.trim(),
|
||||
region: region.input.value,
|
||||
type: roadType.select.selectedOptions[0]?.textContent ?? "",
|
||||
type: t("DE_Reg_KindPlan"),
|
||||
});
|
||||
};
|
||||
name.input.addEventListener("input", () => composer.edit(name.input.value));
|
||||
for (const field of [yearStart, yearEnd, region]) field.input.addEventListener("input", syncName);
|
||||
roadType.select.addEventListener("change", () => (syncSpeeds(), syncName()));
|
||||
syncSpeeds();
|
||||
for (const field of [yearStart, region]) field.input.addEventListener("input", syncName);
|
||||
syncName();
|
||||
|
||||
const submit = (): void => {
|
||||
const fields = [yearStart, yearEnd, region, name, startM, endM];
|
||||
const fields = [yearStart, region, name];
|
||||
fields.forEach((field) => field.setError());
|
||||
const y0 = Number.parseInt(yearStart.input.value, 10);
|
||||
const y1 = Number.parseInt(yearEnd.input.value, 10);
|
||||
const [s, e] = [numberOrNull(startM.input.value), numberOrNull(endM.input.value)];
|
||||
const errors: Array<[(typeof fields)[number], string | false]> = [
|
||||
[yearStart, !(y0 >= 2000 && y0 <= 2100) && t("Common_Validation_NumberRange")],
|
||||
[yearEnd, !(y1 >= y0 && y1 <= 2100) && t("DE_Reg_YearOrder")],
|
||||
[region, !region.input.value.trim() && t("B02_Proj_Error_Required")],
|
||||
[name, !name.input.value.trim() && t("B02_Proj_Error_Required")],
|
||||
[startM, s !== null && !(s >= 0) && t("Common_Validation_NumberRange")],
|
||||
[endM, e !== null && s !== null && !(e > s) && t("DE_Reg_ChainOrder")],
|
||||
];
|
||||
const bad = errors.filter(([, message]) => message);
|
||||
bad.forEach(([field, message]) => field.setError(message as string));
|
||||
@@ -132,20 +60,13 @@ export function renderD01PlanRegister(root: HTMLElement): void {
|
||||
{
|
||||
project_name: name.input.value.trim(),
|
||||
project_year: y0,
|
||||
project_year_end: y1,
|
||||
region: region.input.value.trim(),
|
||||
road_type: roadType.select.value,
|
||||
design_speed_kph: Number(speed.select.value),
|
||||
start_chainage_m: s,
|
||||
end_chainage_m: e,
|
||||
},
|
||||
ROUTES.D02_PLAN_BASE,
|
||||
);
|
||||
};
|
||||
|
||||
const grid = createFormGrid(
|
||||
[yearStart, yearEnd, region, kind, roadType, speed, name, startM, endM].map((f) => f.root),
|
||||
);
|
||||
const grid = createFormGrid([yearStart, region, name].map((f) => f.root));
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "ui-general-block__actions ui-general-block__actions--end";
|
||||
actions.append(
|
||||
|
||||
@@ -15,6 +15,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -250,10 +251,12 @@ async def import_route_files(
|
||||
async def _new_road_project(assignee: dict[str, Any], session: dict[str, Any]) -> str:
|
||||
"""예정노선 이름 · 지역 · 임도 종류 · 그 연도로 임도 프로젝트(B02 등록과 같은 함수)."""
|
||||
body = _parsed(assignee["route_json"]) or {}
|
||||
# 「2026 홍천 노선 계획」 꼴 = 앞 연도를 그 연도로 갈아 끼움(연도 두 번 안 붙음) · 그 밖 = 뒤에 연도
|
||||
name, dated = re.subn(r"^\d{4}(?:~\d{4})?(?=\s)", str(assignee["year"]), assignee["name"])
|
||||
created = await create_project(
|
||||
user_id=int(session["user_id"]),
|
||||
company_id=int(session["company_id"]),
|
||||
name=f"{assignee['name']} {assignee['year']}",
|
||||
name=name if dated else f"{assignee['name']} {assignee['year']}",
|
||||
region=assignee["region"] or "",
|
||||
road_type=assignee["road_type"] or "work",
|
||||
project_year=int(assignee["year"]),
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* 좌 = 구간 목록 · 탐색 설정(기본 · 고급 접힘) · [해석] · 진행률 · [끊기]
|
||||
* 가운데 = 평면 지도(셀별 최소 안전율 · 임계 체적) · [3D 보기](지표 + 노선면 + 임계 활동면)
|
||||
* 우 = 구간 결과(3D 최소 · 2D 최소(같은 구간 E03 측점 · 3D 와 같은 조건 안전율 최소) · 3D/2D 비 · 체적 · 방법)
|
||||
* 계산 = 서버 단독(POST …/stab/analyze3d · 뒤에서) · 화면은 값 · 그림만. 탐색 설정 = sessionStorage.
|
||||
* 계산 = 서버 단독(POST …/stab/analyze3d · 뒤에서) · 화면은 값 · 그림만. 탐색 설정 = sessionStorage
|
||||
* · 없으면 저장된 결과의 설정 · 결과 열쇠 = 구간 + 조건(건기 · 우기 따로 남음).
|
||||
* ========================================================================== */
|
||||
|
||||
import "./E04_Stab3D_UI_Style.css";
|
||||
@@ -44,6 +45,7 @@ interface SavedSummary {
|
||||
f_min: number;
|
||||
volume: number;
|
||||
method: string;
|
||||
condition: string;
|
||||
stale: boolean;
|
||||
}
|
||||
|
||||
@@ -67,7 +69,10 @@ interface Progress {
|
||||
}
|
||||
|
||||
const cacheKey = (projectId: string): string => `e04_settings:${projectId}`;
|
||||
const sectionKey = (start: number, end: number): string => `${start.toFixed(2)}_${end.toFixed(2)}`;
|
||||
const CONDITIONS = ["dry", "wet"] as const;
|
||||
const rangeKey = (start: number, end: number): string => `${start.toFixed(2)}_${end.toFixed(2)}`;
|
||||
const sectionKey = (start: number, end: number, condition: string): string =>
|
||||
`${rangeKey(start, end)}_${condition}`;
|
||||
|
||||
async function api<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
@@ -218,16 +223,11 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
||||
const analyzeButton = createButton({
|
||||
label: t("E04_Analyze"),
|
||||
variant: "filled",
|
||||
onClick: () => void analyze(false),
|
||||
onClick: () => void analyze(),
|
||||
});
|
||||
const reanalyzeButton = createButton({
|
||||
label: t("E04_Reanalyze"),
|
||||
variant: "ghost",
|
||||
onClick: () => void analyze(true),
|
||||
});
|
||||
analyzeButton.disabled = reanalyzeButton.disabled = true;
|
||||
analyzeButton.disabled = true;
|
||||
const actions = el("div", { className: "ui-sidebar-actions" });
|
||||
actions.append(reanalyzeButton, analyzeButton);
|
||||
actions.append(analyzeButton);
|
||||
const readOnly = note(t("E04_ReadOnly"), "e04-hint");
|
||||
readOnly.hidden = true;
|
||||
panel.append(
|
||||
@@ -252,40 +252,46 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
||||
let shownKey = "";
|
||||
settings.onChange(() => {
|
||||
remember();
|
||||
const { start_m, end_m } = settings.read();
|
||||
const { start_m, end_m, condition } = settings.read();
|
||||
map.setRange(start_m, end_m);
|
||||
paintSections();
|
||||
if (shownKey !== sectionKey(start_m, end_m)) void openResult();
|
||||
if (shownKey !== sectionKey(start_m, end_m, condition)) void openResult();
|
||||
});
|
||||
|
||||
function paintButtons(): void {
|
||||
const editable = Boolean(overview?.editable);
|
||||
analyzeButton.disabled = reanalyzeButton.disabled = !editable || running || !overview;
|
||||
analyzeButton.disabled = !editable || running || !overview;
|
||||
readOnly.hidden = !overview || editable;
|
||||
}
|
||||
|
||||
function paintSections(): void {
|
||||
if (!overview) return;
|
||||
const { start_m, end_m } = settings.read();
|
||||
const { start_m, end_m, condition } = settings.read();
|
||||
const savedOf = new Map(overview.saved.map((s) => [s.key, s]));
|
||||
const custom = overview.saved.filter(
|
||||
(s) => !overview!.sections.some((x) => sectionKey(x.start_m, x.end_m) === s.key),
|
||||
);
|
||||
const items = [
|
||||
...overview.sections,
|
||||
...custom.map((s) => ({ start_m: s.start_m, end_m: s.end_m })),
|
||||
];
|
||||
const items = new Map(overview.sections.map((x) => [rangeKey(x.start_m, x.end_m), x]));
|
||||
for (const s of overview.saved) {
|
||||
const key = rangeKey(s.start_m, s.end_m);
|
||||
if (!items.has(key)) items.set(key, { start_m: s.start_m, end_m: s.end_m });
|
||||
}
|
||||
sectionList.replaceChildren(
|
||||
...items.map((section) => {
|
||||
const key = sectionKey(section.start_m, section.end_m);
|
||||
const saved = savedOf.get(key);
|
||||
...[...items].map(([key, section]) => {
|
||||
const byCondition = CONDITIONS.map((c) => savedOf.get(`${key}_${c}`));
|
||||
const saved = savedOf.get(`${key}_${condition}`);
|
||||
const row = createListRow({
|
||||
label: `${section.start_m.toFixed(0)} – ${section.end_m.toFixed(0)} m`,
|
||||
trailing: saved
|
||||
? `Fs ${saved.f_min.toFixed(2)}${saved.stale ? ` · ${t("E04_Stale")}` : ""}`
|
||||
: "",
|
||||
trailing: byCondition
|
||||
.flatMap((x) =>
|
||||
x
|
||||
? [
|
||||
`${t(`E04_Short_${x.condition}` as LocaleKey)} ${x.f_min.toFixed(2)}${
|
||||
x.stale ? ` ${t("E04_Stale")}` : ""
|
||||
}`,
|
||||
]
|
||||
: [],
|
||||
)
|
||||
.join(" · "),
|
||||
stripe: saved ? stepColor(FS_STEPS, saved.f_min) : "transparent",
|
||||
active: key === sectionKey(start_m, end_m),
|
||||
active: key === rangeKey(start_m, end_m),
|
||||
onClick: () => settings.setRange(section.start_m, section.end_m),
|
||||
});
|
||||
row.classList.add("e04-section");
|
||||
@@ -364,15 +370,19 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
||||
resultBody.replaceChildren(list, ...notes);
|
||||
}
|
||||
|
||||
const currentKey = (): string => {
|
||||
const { start_m, end_m, condition } = settings.read();
|
||||
return sectionKey(start_m, end_m, condition);
|
||||
};
|
||||
|
||||
async function openResult(): Promise<void> {
|
||||
const { start_m, end_m } = settings.read();
|
||||
const key = sectionKey(start_m, end_m);
|
||||
const key = currentKey();
|
||||
shownKey = key;
|
||||
const saved = overview?.saved.some((s) => s.key === key);
|
||||
result = saved
|
||||
? await api<Result3d>(`/projects/${projectId}/stab/result3d?key=${key}`).catch(() => null)
|
||||
: null;
|
||||
if (key !== sectionKey(settings.read().start_m, settings.read().end_m)) return; // 그새 바뀜
|
||||
if (key !== currentKey()) return; // 그새 바뀜
|
||||
map.setResult(result);
|
||||
if (mode === "3d") view3d.show(result, overview?.line ?? [], layer);
|
||||
paintResult();
|
||||
@@ -405,13 +415,13 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
||||
}
|
||||
}
|
||||
|
||||
async function analyze(force: boolean): Promise<void> {
|
||||
async function analyze(): Promise<void> {
|
||||
const values = settings.read();
|
||||
const { start_m, end_m, condition, ...search } = values;
|
||||
try {
|
||||
const body = await api<{ status: string }>(`/projects/${projectId}/stab/analyze3d`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ start_m, end_m, condition, search, force }),
|
||||
body: JSON.stringify({ start_m, end_m, condition, search }),
|
||||
});
|
||||
if (body.status === "cached") {
|
||||
showToast(t("E04_Cached"), "info");
|
||||
@@ -446,7 +456,7 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
||||
...ov.defaults,
|
||||
start_m: first?.start_m ?? 0,
|
||||
end_m: first?.end_m ?? 100,
|
||||
condition: "dry",
|
||||
condition: ov.saved[0]?.condition ?? "dry",
|
||||
},
|
||||
);
|
||||
map.setRoute(ov.line, ov.stations);
|
||||
@@ -455,6 +465,12 @@ export function renderE04Stab3D(root: HTMLElement): void {
|
||||
paintSections();
|
||||
stations2d = await fetch2d(projectId);
|
||||
await openResult();
|
||||
// 캐시가 없으면 저장된 결과의 탐색 설정을 되살림
|
||||
const opened = result as Result3d | null; // openResult 가 바꿈(흐름 좁힘 풀기)
|
||||
if (!cached && opened?.settings) {
|
||||
settings.write(opened.settings as Partial<Settings>);
|
||||
remember();
|
||||
}
|
||||
const p = await api<Progress>(`/projects/${projectId}/stab/progress3d`).catch(() => null);
|
||||
if (p?.running) void poll();
|
||||
})();
|
||||
|
||||
@@ -98,8 +98,8 @@ export function buildSettingsCards() {
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
const range = { start_m: read().start_m, end_m: read().end_m };
|
||||
write({ ...read(), ...defaults, ...range, condition: "dry" });
|
||||
const { start_m, end_m, condition } = read(); // 구간 · 조건은 그대로
|
||||
write({ ...read(), ...defaults, start_m, end_m, condition });
|
||||
changed();
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -124,6 +124,8 @@ def test_d01_plan_created(monkeypatch):
|
||||
assert seen["design_speed_kph"] == 40
|
||||
asyncio.run(d01.post_plan_project(None, _plan(road_type="work", design_speed_kph=40), ADMIN))
|
||||
assert seen["design_speed_kph"] == 20 # 작업임도 = 20 만
|
||||
asyncio.run(d01.post_plan_project(None, _plan(road_type=None), ADMIN))
|
||||
assert seen["road_type"] is None and seen["design_speed_kph"] == 20 # 560-1 임도 종류 안 받음
|
||||
with pytest.raises(HTTPException) as e:
|
||||
asyncio.run(d01.post_plan_project(None, _plan(project_year_end=2025), FOREST))
|
||||
assert e.value.status_code == 400
|
||||
|
||||
@@ -264,6 +264,20 @@ def test_new_mode_creates_road_project(env, monkeypatch):
|
||||
assert made["company_id"] == 5 and len(env["rows"]) == 5
|
||||
|
||||
|
||||
def test_new_mode_name_year_once(env, monkeypatch):
|
||||
"""560-1 — 「<시작 연도> <지역> 노선 계획」 이름 = 앞 연도를 그 연도로(두 번 안 붙음)."""
|
||||
made = {}
|
||||
|
||||
async def create_project(**kw):
|
||||
made.update(kw)
|
||||
return {"project_id": ROAD_ID}
|
||||
|
||||
monkeypatch.setattr(link, "create_project", create_project)
|
||||
_use(monkeypatch, _assignee(name="2025 홍천 노선 계획"))
|
||||
_link(mode="new", project_id=None)
|
||||
assert made["name"] == "2026 홍천 노선 계획"
|
||||
|
||||
|
||||
def _stage2_writes(log):
|
||||
return [
|
||||
args
|
||||
|
||||
@@ -16,13 +16,6 @@ def _no_hex(path: str) -> None:
|
||||
assert not re.search(r"#[0-9a-fA-F]{3,8}\b", css), f"{path}: 날 색"
|
||||
|
||||
|
||||
def test_d01_station_label_is_text_part():
|
||||
ts = _src("D01_PlanRegister/D01_PlanRegister_UI_Page.ts")
|
||||
assert "style.cssText" not in ts
|
||||
assert 'createText("", "muted")' in ts
|
||||
assert 'dataset.station = ""' in ts # 시험 · 촬영이 잡음
|
||||
|
||||
|
||||
def test_d02_parts():
|
||||
ts = _src("D02_PlanBase/D02_PlanBase_UI_Page.ts")
|
||||
css = _src("D02_PlanBase/D02_PlanBase_UI_Style.css")
|
||||
|
||||
@@ -39,7 +39,6 @@ export const ui_locales_e04 = {
|
||||
E04_Condition_wet: ["우기 (지표까지 포화)", "Wet (saturated to surface)"],
|
||||
E04_Defaults: ["기본값", "Defaults"],
|
||||
E04_Analyze: ["해석", "Analyze"],
|
||||
E04_Reanalyze: ["다시 해석", "Re-analyze"],
|
||||
E04_Cancel: ["끊기", "Stop"],
|
||||
E04_Running: ["해석 중", "Analyzing"],
|
||||
E04_Cancelled: ["해석을 끊었습니다.", "Analysis stopped."],
|
||||
@@ -48,6 +47,8 @@ export const ui_locales_e04 = {
|
||||
E04_ReadOnly: ["보기만 허용된 프로젝트입니다.", "View-only project."],
|
||||
E04_Saved: ["결과 있음", "Saved"],
|
||||
E04_Stale: ["옛 결과", "Outdated"],
|
||||
E04_Short_dry: ["건기", "Dry"],
|
||||
E04_Short_wet: ["우기", "Wet"],
|
||||
|
||||
/* --- 가운데 지도 · 3D --- */
|
||||
E04_View_Plan: ["평면 지도", "Plan map"],
|
||||
|
||||
Reference in New Issue
Block a user