- 서버 확인 길(my-machines/draft) = 화면이 정한 표지로 그 식 한 줄 셈 기록(요소 · 표 · 식 문서) 돌려줌 - 화면은 기록을 계산기 캐시에 합침 → 서버 왕복 전 0 원 깜빡임 없음(미리 셈 값과 같음) - 기계 창 목록 열 이름 「구분」 · 폭 조정 - 시험 test_827_1 ⑦ 셈 기록 확인 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
531 lines
19 KiB
TypeScript
531 lines
19 KiB
TypeScript
/* =============================================================================
|
|
* line_stack_machine.ts
|
|
* 줄 쌓기 기계 창(PLAN 827-1 · 요구 810 · 811) — 기계 줄 이름 단추 · 옵션 가로 [조회]가 띄움.
|
|
* 왼쪽 = 기계 찾기 — 맨 위 「지금 이 줄」 · 이 단가산출 초안 · 개인 층 내 기계 · M01 기계(줄마다 시간당)
|
|
* 오른쪽 = 공통 기계 양식(`ui_template_machine_form` · 읽기 전용) — 지금 줄 = 이 단가산출 사본에 박힌 값
|
|
* (원본 지금 값과 다르면 「새 값 있음」) · 고른 줄 = 그 기계 지금 값
|
|
* [이 기계로 바꾸기] = `pick`(부른 쪽 `repick` · 가져온 식 823-1 규칙 그대로)
|
|
* [고쳐 쓰기](개인 층 · 쓰기 권한 · `ctx.canEdit`) = 양식 편집 → [확인] = 서버 확인(쓰기 없음) → `draft`
|
|
* (부른 쪽이 문서 칸 `내기계초안` 에 · 디스크는 단가산출 [저장] 때 서버) · 그 밖 = 까닭 한 줄
|
|
* M01 기계에서 시작 = 새 내 기계(본뜬 = 그 키) · 내 기계 = 그 기계 고침 · 초안 = 그 초안 고침 · 즉시 저장 없음
|
|
* ========================================================================== */
|
|
|
|
import { createButton } from "@ui/ui_template_button";
|
|
import { createDataTable } from "@ui/ui_template_data_table";
|
|
import { createInputField, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
|
|
import { createMachineForm, HOURLY_KEY, type MachineValues } from "@ui/ui_template_machine_form";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import { createText } from "@ui/ui_template_text";
|
|
import { isDraftNo, newTag, type DraftItem } from "../common_util/common_util_line_stack_draft";
|
|
import type { DocRecord } from "../common_util/common_util_formula_doc_cache";
|
|
import type { Picked } from "../common_util/common_util_line_stack_pick";
|
|
import { call, type Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
|
|
import {
|
|
checkDraft,
|
|
fetchMyMachineLists,
|
|
listMyMachines,
|
|
machineArg,
|
|
machineRows,
|
|
readMyMachine,
|
|
type MachineBrief,
|
|
type MyHourly,
|
|
type MyMachineRow,
|
|
} from "../M02_MasterTemplete/M02_MyMachine_Api";
|
|
import { bodyOfForm, hourlyText, valuesOf } from "../M02_MasterTemplete/M02_MyMachine_Form";
|
|
import { mt } from "../M02_MasterTemplete/M02_MyMachine_Text";
|
|
|
|
/** 사본 줄(서버 `copy_row`) — 키 · 값 · 방식(내 기계) · 시간당(직접) */
|
|
interface CopyRowData {
|
|
키: string;
|
|
원문번호?: string;
|
|
이름?: string;
|
|
규격?: string;
|
|
값?: MachineValues;
|
|
방식?: string;
|
|
시간당?: Record<string, number>;
|
|
}
|
|
export interface CopyAnswer {
|
|
사본: CopyRowData | null;
|
|
지금: CopyRowData | null;
|
|
새값: boolean;
|
|
}
|
|
|
|
export interface MachineCtx {
|
|
/** 층 · 주인 — null = M01 단가산출 기초(M01 기계만) */
|
|
scope: { layer: Layer; owner: string | null } | null;
|
|
/** [고쳐 쓰기] 됨(개인 층 SK · 쓰기 권한) */
|
|
canEdit: boolean;
|
|
/** 못 고칠 까닭(M01 · 회사 · 마스터 층) */
|
|
why: string;
|
|
/** 이 SK 키(쓰는 곳 셈에서 뺌) */
|
|
me?: string;
|
|
/** 이 SK 사본에 박힌 기계 줄(키 있는 M02 SK) */
|
|
copyRow?: (no: string) => Promise<CopyAnswer>;
|
|
}
|
|
|
|
export interface MachineWindowOptions {
|
|
ctx: MachineCtx;
|
|
drafts: DraftItem[];
|
|
/** 지금 줄 기계 입력 글(따옴표 뗌 · 원문번호 · 개인 EQP… · 개인 EQP초안-N) · 없음 = null */
|
|
now: string | null;
|
|
query: string;
|
|
/** 없으면 보기만(옵션 가로 조회) */
|
|
pick?: (p: Picked) => void;
|
|
/** 고쳐 쓰기 [확인] — 초안 · 줄 연결 · 그 식 셈 기록(계산기 캐시 · 즉시 셈) */
|
|
draft?: (d: DraftItem, p: Picked, record: DocRecord) => void;
|
|
}
|
|
|
|
type Kind = "now" | "draft" | "mine" | "m01";
|
|
interface Cand {
|
|
kind: Kind;
|
|
/** 기계 입력 글 */
|
|
no: string;
|
|
이름: string;
|
|
규격: string;
|
|
계: number | null;
|
|
구분: string;
|
|
draft?: DraftItem;
|
|
mine?: MyMachineRow;
|
|
/** M01 키(EQ…) */
|
|
key?: string;
|
|
}
|
|
interface Shown {
|
|
values: MachineValues;
|
|
name: string;
|
|
spec: string;
|
|
lines: HTMLElement[];
|
|
logic?: string | null;
|
|
/** [고쳐 쓰기] 대상 */
|
|
target: { 표지?: string; 키?: string; 본뜬?: string };
|
|
/** 내 기계 = 쓰는 곳 셈 키 */
|
|
mineKey?: string;
|
|
}
|
|
|
|
const kindOf = (no: string): Kind =>
|
|
isDraftNo(no) ? "draft" : no.startsWith("개인 ") ? "mine" : "m01";
|
|
|
|
/** 사본 · 원본 줄 → 양식 값 — 직접 = 시간당 · 바로(셈 꼴) = 계수 셋 · 빈 연료 · 조종원 0 뺌 */
|
|
function formValues(row: CopyRowData): MachineValues {
|
|
if (row.방식 === "직접") {
|
|
const v = row.시간당 ?? row.값 ?? {};
|
|
return { [HOURLY_KEY]: { 노무비: v.노무비, 재료비: v.재료비, 경비: v.경비 } };
|
|
}
|
|
const v: MachineValues = { ...(row.값 ?? {}) };
|
|
if (row.방식 === "바로") {
|
|
for (const k of ["상각비계수", "정비비계수", "관리비계수"]) delete v[k];
|
|
for (const k of ["주연료", "잡재료율", "조종원"]) if (v[k] === 0) delete v[k];
|
|
for (const k of ["주연료종류", "조종원직종"]) if (v[k] === "") delete v[k];
|
|
}
|
|
return v;
|
|
}
|
|
/** 내 기계 목록 줄 → 양식 값(직접 = 서버 시간당 노 · 재 · 경) */
|
|
const mineValues = (m: MyMachineRow): MachineValues =>
|
|
valuesOf({
|
|
방식: m.방식,
|
|
값: m.값,
|
|
시간당: {
|
|
노무비: m.시간당.노무비 ?? 0,
|
|
재료비: m.시간당.재료비 ?? 0,
|
|
경비: m.시간당.경비 ?? 0,
|
|
},
|
|
});
|
|
const hourOf = (h: MyHourly | undefined): number | null => (h?.ok ? (h.계 ?? null) : null);
|
|
const same = (a: Cand | undefined, b: Cand | null): boolean =>
|
|
!!a && !!b && a.kind === b.kind && a.no === b.no && a.key === b.key;
|
|
|
|
export function openMachineWindow(o: MachineWindowOptions): void {
|
|
const { ctx } = o;
|
|
const owner = ctx.scope?.owner ?? null;
|
|
const personal = ctx.scope?.layer === "personal";
|
|
let mines: MyMachineRow[] = [];
|
|
let m01: { ref: string; 이름: string; 규격?: string | null; 구분?: string | null }[] = [];
|
|
const briefs = new Map<string, MachineBrief>();
|
|
let cands: Cand[] = [];
|
|
let sel: Cand | null = null;
|
|
let editing = false;
|
|
let formDirty = (): boolean => false;
|
|
let seq = 0;
|
|
let showSeq = 0;
|
|
let timer = 0;
|
|
let close = (): void => {};
|
|
|
|
const search = createInputField({ type: "search", placeholder: mt("Find") });
|
|
search.input.value = o.query;
|
|
const hint = createText("");
|
|
const right = el("div", { className: "lstk-mwin__form", attrs: { "data-mwin-form": "" } });
|
|
const table = createDataTable({
|
|
caption: mt("Win_Title"),
|
|
columns: [
|
|
{ key: "품명", label: mt("Name"), width: 150 },
|
|
{ key: "규격", label: mt("Spec"), width: 80 },
|
|
{ key: "금액", label: mt("Hourly"), kind: "number", decimals: 0, width: 84 },
|
|
{ key: "구분", label: mt("Win_Kind"), width: 110 },
|
|
],
|
|
rows: [],
|
|
rowClass: (i) => (same(cands[i], sel) ? "is-title" : null),
|
|
onRowClick: (i) => {
|
|
if (cands[i]) void choose(cands[i]);
|
|
},
|
|
});
|
|
|
|
/* --- 목록 --- */
|
|
const nowCand = (): Cand | null => {
|
|
if (!o.now) return null;
|
|
const d = o.drafts.find((x) => `개인 ${x.표지}` === o.now);
|
|
const m = mines.find((x) => x.원문번호 === o.now);
|
|
const b = [...briefs.values()].find((x) => x.원문번호 === o.now);
|
|
const name = d?.줄.이름 ?? m?.이름 ?? b?.이름 ?? o.now;
|
|
return {
|
|
kind: "now",
|
|
no: o.now,
|
|
이름: name,
|
|
규격: d?.줄.규격 ?? m?.규격 ?? b?.규격 ?? "",
|
|
계: d ? hourOf(d.시간당) : m ? hourOf(m.시간당) : hourOf(b?.시간당),
|
|
구분: mt("Win_Now"),
|
|
};
|
|
};
|
|
const draw = (): void => {
|
|
const words = search.input.value.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
const hit = (t: string): boolean => words.every((w) => t.toLowerCase().includes(w));
|
|
const now = nowCand();
|
|
cands = [
|
|
...(now ? [now] : []),
|
|
...o.drafts
|
|
.filter((d) => hit(`${d.줄.이름} ${d.줄.규격}`))
|
|
.map((d): Cand => ({
|
|
kind: "draft",
|
|
no: `개인 ${d.표지}`,
|
|
이름: d.줄.이름,
|
|
규격: d.줄.규격,
|
|
계: hourOf(d.시간당),
|
|
구분: mt("Win_Draft"),
|
|
draft: d,
|
|
})),
|
|
...mines
|
|
.filter((r) => hit(`${r.이름} ${r.규격} ${r.키}`))
|
|
.map((r): Cand => ({
|
|
kind: "mine",
|
|
no: r.원문번호,
|
|
이름: r.이름,
|
|
규격: r.규격,
|
|
계: hourOf(r.시간당),
|
|
구분: `${mt("Pick_Group")} · ${mt(r.방식)}`,
|
|
mine: r,
|
|
})),
|
|
...m01.map((x): Cand => ({
|
|
kind: "m01",
|
|
no: briefs.get(x.ref)?.원문번호 ?? "",
|
|
이름: x.이름,
|
|
규격: x.규격 ?? "",
|
|
계: hourOf(briefs.get(x.ref)?.시간당),
|
|
구분: [mt("Win_M01"), x.구분].filter(Boolean).join(" · "),
|
|
key: x.ref,
|
|
})),
|
|
];
|
|
hint.textContent = `${cands.length}`;
|
|
table.setRows(cands.map((c) => ({ 품명: c.이름, 규격: c.규격, 금액: c.계, 구분: c.구분 })));
|
|
void fill();
|
|
};
|
|
/** M01 줄 시간당 — 보이는 앞 40 줄만 뒤에서(색인 메모 · 없는 것만) */
|
|
const fill = async (): Promise<void> => {
|
|
const want = cands
|
|
.filter((c) => c.kind === "m01" && c.key && !briefs.has(c.key))
|
|
.slice(0, 40)
|
|
.map((c) => c.key!);
|
|
if (!want.length) return;
|
|
const mine = seq;
|
|
const got = await machineRows(want).catch(() => []);
|
|
for (const b of got) briefs.set(b.키, b);
|
|
if (mine === seq && got.length) draw();
|
|
};
|
|
const find = async (): Promise<void> => {
|
|
const mine = ++seq;
|
|
const q = search.input.value.trim();
|
|
const got = await call<{ items: typeof m01 }>(
|
|
`/logic-lib/elements?group=${encodeURIComponent("기계")}&q=${encodeURIComponent(q)}&limit=200`,
|
|
).catch(() => ({ items: [] }));
|
|
if (mine !== seq) return;
|
|
m01 = got.items;
|
|
draw();
|
|
};
|
|
search.input.addEventListener("input", () => {
|
|
draw();
|
|
window.clearTimeout(timer);
|
|
timer = window.setTimeout(() => void find(), 250);
|
|
});
|
|
|
|
/* --- 오른쪽 --- */
|
|
const briefOf = async (no: string, key?: string): Promise<MachineBrief | undefined> => {
|
|
const at = key ?? no;
|
|
const have = key ? briefs.get(key) : [...briefs.values()].find((b) => b.원문번호 === no);
|
|
if (have) return have;
|
|
const [got] = await machineRows([at]).catch(() => []);
|
|
if (got) briefs.set(got.키, got);
|
|
return got;
|
|
};
|
|
|
|
/** 고른 줄 → 양식 값 · 글 · 바꿀 연결 · 고칠 대상 */
|
|
const resolve = async (c: Cand): Promise<Shown> => {
|
|
const k = c.kind === "now" ? kindOf(c.no) : c.kind;
|
|
const lines: HTMLElement[] = [];
|
|
if (k === "draft") {
|
|
const d = c.draft ?? o.drafts.find((x) => `개인 ${x.표지}` === c.no);
|
|
if (!d) throw new Error(mt("Hourly_None"));
|
|
lines.push(createText(mt("Win_Draft"), "muted"));
|
|
if (d.시간당)
|
|
lines.push(createText(`${mt("Server_Hourly")} ${hourlyText(d.시간당 as MyHourly)}`));
|
|
const target = {
|
|
표지: d.표지,
|
|
...(d.키 ? { 키: d.키 } : {}),
|
|
...(d.본뜬 ? { 본뜬: d.본뜬 } : {}),
|
|
};
|
|
return {
|
|
values: valuesOf({
|
|
방식: d.줄.방식 as never,
|
|
값: (d.줄.값 ?? {}) as MachineValues,
|
|
시간당: d.줄.시간당 as never,
|
|
}),
|
|
name: d.줄.이름,
|
|
spec: d.줄.규격,
|
|
lines,
|
|
logic: d.시간당?.로직,
|
|
target,
|
|
mineKey: d.키,
|
|
};
|
|
}
|
|
// 지금 줄 = 이 단가산출 사본 값(있으면)
|
|
const copy = c.kind === "now" && ctx.copyRow ? await ctx.copyRow(c.no).catch(() => null) : null;
|
|
const row = copy?.사본 ?? null;
|
|
if (row) {
|
|
lines.push(createText(mt("Win_Copy"), "muted"));
|
|
if (copy?.새값) lines.push(createText(mt("Win_Newer"), "warning"));
|
|
}
|
|
if (k === "mine") {
|
|
const m = c.mine ?? mines.find((r) => r.원문번호 === c.no);
|
|
const key = m?.키 ?? c.no.replace(/^개인 /, "");
|
|
const was = o.drafts.find((d) => d.키 === key); // 이미 고친 초안 = 그 초안을 다시 고침
|
|
if (!row && m) lines.push(createText(`${mt("Server_Hourly")} ${hourlyText(m.시간당)}`));
|
|
return {
|
|
values: row ? formValues(row) : m ? mineValues(m) : {},
|
|
name: row?.이름 ?? m?.이름 ?? c.이름,
|
|
spec: row?.규격 ?? m?.규격 ?? c.규격,
|
|
lines,
|
|
logic: m?.시간당.로직,
|
|
target: { 키: key, ...(was ? { 표지: was.표지 } : {}) },
|
|
mineKey: key,
|
|
};
|
|
}
|
|
const b = await briefOf(c.no, c.key);
|
|
if (!row && b) lines.push(createText(`${mt("Win_Live")} · ${hourlyText(b.시간당)}`));
|
|
if (!row && !b) throw new Error(mt("Hourly_None"));
|
|
return {
|
|
values: row ? formValues(row) : { ...(b?.값 ?? {}) },
|
|
name: row?.이름 ?? b?.이름 ?? c.이름,
|
|
spec: row?.규격 ?? b?.규격 ?? c.규격,
|
|
lines,
|
|
logic: b?.시간당.ok ? b.시간당.로직 : null,
|
|
target: { 본뜬: row?.키 ?? b?.키 },
|
|
};
|
|
};
|
|
|
|
const pickedOf = (c: Cand, s: Shown): Picked | null => {
|
|
if (!s.logic) return null;
|
|
const no = c.kind === "mine" && c.mine ? machineArg(c.mine.원문번호) : `'${c.no}'`;
|
|
return { 키: s.logic, 이름: s.name, 입력: { 기계: no }, ...(s.spec ? { 규격: s.spec } : {}) };
|
|
};
|
|
|
|
const choose = async (c: Cand): Promise<void> => {
|
|
if (editing && formDirty() && !(await showConfirmDialog(mt("Leave_Ask")))) return;
|
|
editing = false;
|
|
sel = c;
|
|
table.setRows(cands.map((x) => ({ 품명: x.이름, 규격: x.규격, 금액: x.계, 구분: x.구분 })));
|
|
await show(c);
|
|
};
|
|
|
|
async function show(c: Cand, edit = false, blank = false): Promise<void> {
|
|
const mine = ++showSeq;
|
|
right.replaceChildren(createText(mt("Server_Hourly"), "muted"));
|
|
let s: Shown;
|
|
try {
|
|
s = blank ? { values: {}, name: "", spec: "", lines: [], target: {} } : await resolve(c);
|
|
} catch (error) {
|
|
if (mine === showSeq)
|
|
right.replaceChildren(
|
|
createText(error instanceof Error ? error.message : mt("Failed"), "error"),
|
|
);
|
|
return;
|
|
}
|
|
const lists = await fetchMyMachineLists();
|
|
if (mine !== showSeq) return;
|
|
editing = edit;
|
|
const notice = el("div", { className: "lstk-mwin__notice" });
|
|
const form = createMachineForm({
|
|
name: s.name,
|
|
spec: s.spec,
|
|
values: s.values,
|
|
coefMode: "direct",
|
|
allowHourly: true,
|
|
fuels: lists.fuels,
|
|
jobs: lists.jobs,
|
|
power: lists.power,
|
|
hour: lists.hour,
|
|
readOnly: !edit,
|
|
onChange: () => notice.replaceChildren(),
|
|
});
|
|
formDirty = () => edit && form.state().changed;
|
|
const bar: HTMLElement[] = [];
|
|
if (!edit) {
|
|
const p = pickedOf(c, s);
|
|
if (o.pick && c.kind !== "now") {
|
|
const b = createButton({
|
|
label: mt("Win_Swap"),
|
|
variant: "filled",
|
|
size: 28,
|
|
disabled: !p,
|
|
onClick: () => {
|
|
if (!p) return;
|
|
close();
|
|
o.pick!(p);
|
|
},
|
|
});
|
|
b.dataset.mwinSwap = "";
|
|
if (!p) b.title = mt("Pick_Direct");
|
|
bar.push(b);
|
|
}
|
|
if (o.draft && ctx.canEdit) {
|
|
const b = createButton({
|
|
label: mt("Win_Edit"),
|
|
variant: c.kind === "now" ? "filled" : "ghost",
|
|
size: 28,
|
|
iconStart: "pencil",
|
|
onClick: () => void show(c, true),
|
|
});
|
|
b.dataset.mwinEdit = "";
|
|
bar.push(b);
|
|
} else if (o.draft && ctx.why) bar.push(createText(ctx.why, "muted"));
|
|
} else {
|
|
const ok = createButton({ label: mt("Win_Ok"), variant: "filled", size: 28 });
|
|
ok.dataset.mwinOk = "";
|
|
ok.addEventListener("click", () => void confirm(form.state(), s, notice, ok));
|
|
bar.push(
|
|
ok,
|
|
createButton({
|
|
label: mt("Win_Cancel"),
|
|
variant: "ghost",
|
|
size: 28,
|
|
onClick: () => void (blank ? choose(c) : show(c)),
|
|
}),
|
|
);
|
|
}
|
|
const extra: HTMLElement[] = [];
|
|
if (edit) extra.push(createText(mt("Win_Edit_Note"), "muted"));
|
|
if (edit && s.target.키) {
|
|
const uses = el("div");
|
|
extra.push(uses);
|
|
void readMyMachine(owner, s.target.키)
|
|
.then((one) => {
|
|
const n = (one.쓰는곳 ?? []).filter((u) => u.키 !== ctx.me).length;
|
|
uses.replaceChildren(createText(mt("Win_Uses").replace("{value}", String(n)), "warning"));
|
|
})
|
|
.catch(() => undefined);
|
|
}
|
|
right.replaceChildren(
|
|
el("div", {
|
|
className: "ui-toolbar",
|
|
children: [el("strong", { text: `${s.name} ${s.spec}`.trim() || mt("New") }), ...bar],
|
|
}),
|
|
...s.lines,
|
|
...extra,
|
|
notice,
|
|
form.root,
|
|
);
|
|
right.dataset.mwinMode = edit ? "edit" : "view";
|
|
}
|
|
|
|
/** [확인] — 서버 확인(쓰기 없음) → 초안 · 줄 연결 */
|
|
async function confirm(
|
|
state: ReturnType<ReturnType<typeof createMachineForm>["state"]>,
|
|
s: Shown,
|
|
notice: HTMLElement,
|
|
btn: HTMLButtonElement,
|
|
): Promise<void> {
|
|
const { body, errors } = bodyOfForm(state);
|
|
notice.replaceChildren(...errors.map((e) => createText(e, "error")));
|
|
if (!body) return;
|
|
btn.disabled = true;
|
|
try {
|
|
const tag = s.target.표지 ?? newTag();
|
|
const got = await checkDraft(owner, body, { ...s.target, 표지: tag });
|
|
if (!got.시간당.ok || !got.시간당.로직)
|
|
throw new Error(hourlyText(got.시간당) || mt("Pick_Direct"));
|
|
const item: DraftItem = {
|
|
표지: tag,
|
|
...(s.target.키 ? { 키: s.target.키 } : {}),
|
|
...(!s.target.키 && s.target.본뜬 ? { 본뜬: s.target.본뜬 } : {}),
|
|
줄: body,
|
|
시간당: got.시간당,
|
|
};
|
|
const p: Picked = {
|
|
키: got.시간당.로직,
|
|
이름: body.이름,
|
|
입력: { 기계: `'개인 ${tag}'` },
|
|
...(body.규격 ? { 규격: body.규격 } : {}),
|
|
};
|
|
formDirty = () => false;
|
|
close();
|
|
o.draft!(item, p, got.연결);
|
|
} catch (error) {
|
|
notice.replaceChildren(
|
|
createText(error instanceof Error ? error.message : mt("Failed"), "error"),
|
|
);
|
|
} finally {
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
|
|
const blankBtn = createButton({
|
|
label: mt("Win_Blank"),
|
|
variant: "ghost",
|
|
onClick: () => {
|
|
const c: Cand = { kind: "draft", no: "", 이름: "", 규격: "", 계: null, 구분: "" };
|
|
sel = null;
|
|
void show(c, true, true);
|
|
},
|
|
});
|
|
blankBtn.dataset.mwinBlank = "";
|
|
({ close } = openModal({
|
|
title: mt("Win_Title"),
|
|
closeLabel: mt("Close"),
|
|
width: "1100px",
|
|
wide: true,
|
|
...(o.draft && ctx.canEdit ? { actions: [blankBtn] } : {}),
|
|
beforeClose: async () => !formDirty() || showConfirmDialog(mt("Leave_Ask")),
|
|
mount: (body) =>
|
|
body.append(
|
|
el("div", {
|
|
className: "lstk-mwin",
|
|
attrs: { "data-machine-window": "" },
|
|
children: [
|
|
el("div", {
|
|
className: "lstk-mwin__list",
|
|
children: [
|
|
el("div", { className: "ui-toolbar", children: [search.root, hint] }),
|
|
table.root,
|
|
],
|
|
}),
|
|
right,
|
|
],
|
|
}),
|
|
),
|
|
}));
|
|
right.replaceChildren(createText(mt("Win_Hint"), "muted"));
|
|
|
|
void (async () => {
|
|
if (personal) mines = await listMyMachines(owner).catch(() => []);
|
|
if (o.now && kindOf(o.now) === "m01") await briefOf(o.now);
|
|
await find();
|
|
const first = cands[0];
|
|
if (first && first.kind === "now") await choose(first);
|
|
})().catch((error) => showToast(error instanceof Error ? error.message : mt("Failed"), "error"));
|
|
}
|