Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
This commit is contained in:
@@ -147,7 +147,7 @@ export function renderInputPanel(
|
||||
children: [...GROUPS, "다른 로직" as const, "변수" as const].map((k) =>
|
||||
createButton({
|
||||
label: k === "변수" ? t("Variable") : k === "다른 로직" ? t("Logic") : t(k),
|
||||
variant: k === kind ? "filled" : "pill",
|
||||
variant: k === kind ? "filled" : "ghost",
|
||||
size: "sm",
|
||||
onClick: () => k !== kind && on.change(setKind(lg, boxId, k)),
|
||||
}),
|
||||
@@ -193,7 +193,7 @@ function renderGroup(
|
||||
...(["fixed", "rule"] as const).map((m) =>
|
||||
createButton({
|
||||
label: m === "fixed" ? t("ModeFixed") : t("ModeRule"),
|
||||
variant: mode === m ? "filled" : "pill",
|
||||
variant: mode === m ? "filled" : "ghost",
|
||||
size: "sm",
|
||||
onClick: () => ((mode = m), drawChips(), draw()),
|
||||
}),
|
||||
|
||||
@@ -445,7 +445,7 @@ export function renderMaterial(
|
||||
...(["fixed", "choice", "rule"] as CfMaterialMode[]).map((m) =>
|
||||
createButton({
|
||||
label: m === "fixed" ? t("Fixed") : m === "choice" ? t("Choice") : t("Rule"),
|
||||
variant: mode === m ? "filled" : "pill",
|
||||
variant: mode === m ? "filled" : "ghost",
|
||||
size: "sm",
|
||||
onClick: () => ((mode = m), drawChips(), drawBody()),
|
||||
}),
|
||||
|
||||
@@ -92,18 +92,20 @@ export function buildPicker(opt: PickerOptions): { root: HTMLElement } {
|
||||
let seq = 0;
|
||||
|
||||
const buttons = tabs.map((tab) => {
|
||||
const b = el("button", {
|
||||
className: "m02-layers__btn",
|
||||
text: tab.label,
|
||||
attrs: { type: "button", "data-source": tab.id },
|
||||
});
|
||||
b.addEventListener("click", () => void show(tab));
|
||||
const b = createButton({ label: tab.label, variant: "ghost", onClick: () => void show(tab) });
|
||||
b.classList.add("m02-layers__btn");
|
||||
b.dataset.source = tab.id;
|
||||
return b;
|
||||
});
|
||||
|
||||
async function show(tab: Source): Promise<void> {
|
||||
const mine = ++seq;
|
||||
buttons.forEach((b) => b.classList.toggle("is-active", b.dataset.source === tab.id));
|
||||
buttons.forEach((b) => {
|
||||
const on = b.dataset.source === tab.id;
|
||||
b.classList.toggle("is-active", on);
|
||||
b.classList.toggle("ui-btn--filled", on);
|
||||
b.classList.toggle("ui-btn--ghost", !on);
|
||||
});
|
||||
people.root.hidden = tab.id !== "people";
|
||||
list.message();
|
||||
try {
|
||||
|
||||
@@ -42,28 +42,6 @@
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.m02-layers__btn {
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-buttons);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
font-size: var(--text-body-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-layers__btn:hover {
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.m02-layers__btn.is-active {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 층 단추 줄 오른쪽 끝 — 「N개 고름」 · 「+」(PLAN 30-2) */
|
||||
.m02-tree__tools {
|
||||
display: inline-flex;
|
||||
@@ -94,28 +72,6 @@
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
/* 층 단추 옆 「+」 — 나무 맨 위 폴더 · 항목 더하기(PLAN 29-6) */
|
||||
.m02-tree__add {
|
||||
min-width: 1.75rem;
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-buttons);
|
||||
background: none;
|
||||
color: var(--color-text-secondary);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-tree__add:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.m02-tree__add[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 남의 회사 「개인」 사람 나무 — 고른 사람 줄 아래 그 사람 라이브러리(PLAN 29-6) */
|
||||
.m02-people__panel {
|
||||
padding-left: var(--spacing-16);
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import "./M02_MasterTemplete_TreePanel.css";
|
||||
import { createInputField, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
|
||||
import {
|
||||
createButton,
|
||||
createInputField,
|
||||
el,
|
||||
showConfirmDialog,
|
||||
showToast,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { loadingError, showLoading } from "@ui/ui_template_loading";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import {
|
||||
@@ -141,13 +147,15 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
const root = el("div", { className: "m02-tree", children: [body] });
|
||||
search.root.classList.add("m02-tree__search");
|
||||
// 층 단추 옆 「+」 — 맨 위에 폴더 · 항목 더하기(뿌리 줄 오른쪽 클릭을 갈음)
|
||||
const add = el("button", {
|
||||
className: "m02-tree__add",
|
||||
text: "+",
|
||||
attrs: { type: "button", title: L("M02_TreeAddTop"), "aria-label": L("M02_TreeAddTop") },
|
||||
const add = createButton({
|
||||
label: "+",
|
||||
variant: "ghost",
|
||||
onClick: (event) => openTreeMenu(event, topMenu()),
|
||||
});
|
||||
add.classList.add("m02-tree__add");
|
||||
add.title = L("M02_TreeAddTop");
|
||||
add.setAttribute("aria-label", L("M02_TreeAddTop"));
|
||||
add.hidden = !opt.canEdit;
|
||||
add.addEventListener("click", (event) => openTreeMenu(event, topMenu()));
|
||||
// 「N개 고름」 · 「+」 — 층 단추 줄 오른쪽 끝(30-2 · 부르는 쪽이 붙임)
|
||||
const status = el("span", { className: "m02-tree__picked", attrs: { hidden: "" } });
|
||||
const tools = el("span", { className: "m02-tree__tools", children: [status, add] });
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 누르면 `go`(부르는 쪽이 해시로 다시 엶).
|
||||
* ========================================================================== */
|
||||
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import { createTreeRow } from "@ui/ui_template_tree";
|
||||
import type { SessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
@@ -67,15 +67,17 @@ export function createLayerBar(opt: LayerBarOptions): HTMLElement {
|
||||
: "M02_PickerPersonal",
|
||||
);
|
||||
const current = root === active.root && (root !== "personal" || people || !active.owner);
|
||||
const b = el("button", {
|
||||
className: `m02-layers__btn${current ? " is-active" : ""}`,
|
||||
text: readOnly ? `${name} · ${L("M02_RootReadOnly")}` : name,
|
||||
attrs: { type: "button", "data-root": root, "aria-pressed": String(current) },
|
||||
});
|
||||
b.addEventListener("click", () => {
|
||||
if (!current) opt.go(root, owner);
|
||||
else opt.reselect?.();
|
||||
const b = createButton({
|
||||
label: readOnly ? `${name} · ${L("M02_RootReadOnly")}` : name,
|
||||
variant: current ? "filled" : "ghost",
|
||||
onClick: () => {
|
||||
if (!current) opt.go(root, owner);
|
||||
else opt.reselect?.();
|
||||
},
|
||||
});
|
||||
b.classList.add("m02-layers__btn");
|
||||
b.dataset.root = root;
|
||||
b.setAttribute("aria-pressed", String(current));
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ def test_켜진_층_단추는_목록으로_복사_끌기():
|
||||
"""30-4 — 켜진 층 단추를 다시 누르면 그 층 목록(구조물 = 집계표 · 일위대가 단추 벌은 39-2 에서 지움) ·
|
||||
30-2 · 39-6 「〇〇로 복사…」 = 쓸 수 있는 층(지금 층 뺌 · 폴더 고름) · 끌어다 옮기기는 쓸 수 있는 층만."""
|
||||
roots = _text(M02 / "M02_MasterTemplete_TreePanel_Roots.ts")
|
||||
assert "if (!current) opt.go(root, owner);\n else opt.reselect?.();" in roots
|
||||
assert "if (!current) opt.go(root, owner);\n else opt.reselect?.();" in roots
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert 'layerBar("구조물조합", tree.add, () => void main.openTable(layer))' in page
|
||||
# 39-6 — 쓸 수 있는 층 전부(볼 회사 주인까지 · 지금 층 뺌) · 대상 폴더 고르는 창
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* 45-1 — M02 단추 = 공용 기본 단추(알약 꼴 아님) · 알약은 그대로 알약
|
||||
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
|
||||
* 나무에 item1(구조물 조합)이 있음
|
||||
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌)
|
||||
* 알약 꼴 = 모서리 반지름 ≥ 높이 / 2 - 1(캡슐) · 진짜 알약 = 알약 · 칩 · 이름표 클래스(.ss-link · *__pill · *-chip · .ui-tag)
|
||||
* 기대 수치:
|
||||
* - pillButtons = [](M02 안 보이는 단추 중 알약 꼴 0 · 머리 공용 메뉴는 M02 밖이라 뺌)
|
||||
* - layers = 층 단추가 ui-btn · 켜진 층은 filled · add = 「+」 가 ui-btn--ghost
|
||||
* - smRadius = 4(작은 단추 모서리 = 8 / 2) · buttons ≥ 20
|
||||
* - pillOn: [알약] 켬 → realPills > 0 · 모두 알약 꼴(realRound = realPills) → 끔으로 되돌림(pressedAfter = "false")
|
||||
*/
|
||||
(async () => {
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const until = async (fn, ms = 6000) => {
|
||||
for (let t = 0; t < ms; t += 150) {
|
||||
const v = fn();
|
||||
if (v) return v;
|
||||
await w(150);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const vis = (b) => b && !b.hidden && b.offsetParent !== null;
|
||||
const round = (e) => {
|
||||
const r = e.getBoundingClientRect();
|
||||
return parseFloat(getComputedStyle(e).borderTopLeftRadius) >= r.height / 2 - 1;
|
||||
};
|
||||
const REAL = ".ss-link, [class*='__pill'], [class*='-chip'], [class*='__chip'], .ui-tag";
|
||||
const scope = () =>
|
||||
[...document.querySelectorAll("button")].filter(
|
||||
(b) => vis(b) && !b.closest("header, .app-header") && !b.matches(REAL),
|
||||
);
|
||||
const out = {};
|
||||
const node = await until(() => document.querySelector('[data-node="i|item1"]'));
|
||||
if (!node) return JSON.stringify({ error: "item1 없음 — 기슭막이 폴더를 펼쳐 둘 것" });
|
||||
node.click();
|
||||
await until(() => document.querySelector(".m02-struct__card-tools .ui-btn"));
|
||||
await w(1500);
|
||||
const all = scope();
|
||||
out.buttons = all.length;
|
||||
out.pillButtons = all
|
||||
.filter((b) => round(b) && b.getBoundingClientRect().height > 0)
|
||||
.map((b) => `${b.innerText.trim().slice(0, 12)} | ${b.className}`);
|
||||
out.layers = [...document.querySelectorAll(".m02-layers__btn")].map(
|
||||
(b) => `${b.innerText.trim()}:${b.classList.contains("ui-btn--filled") ? "filled" : b.classList.contains("ui-btn--ghost") ? "ghost" : b.className}`,
|
||||
);
|
||||
out.add = document.querySelector(".m02-tree__add")?.className ?? null;
|
||||
const sm = all.find((b) => b.classList.contains("ui-btn--sm"));
|
||||
out.smRadius = sm ? parseFloat(getComputedStyle(sm).borderTopLeftRadius) : null;
|
||||
// [알약] 켬 → 진짜 알약은 알약 꼴 그대로 → 끔으로 되돌림
|
||||
const pillBtn = [...document.querySelectorAll(".m02-struct__card-tools button")].find(
|
||||
(b) => vis(b) && b.innerText.trim() === "알약",
|
||||
);
|
||||
if (pillBtn) {
|
||||
const before = pillBtn.getAttribute("aria-pressed");
|
||||
if (before !== "true") pillBtn.click();
|
||||
await w(600);
|
||||
const real = [...document.querySelectorAll(".ss-link")].filter(vis);
|
||||
out.pillOn = { realPills: real.length, realRound: real.filter(round).length };
|
||||
if (before !== "true") pillBtn.click();
|
||||
await w(300);
|
||||
out.pillOn.pressedAfter = pillBtn.getAttribute("aria-pressed");
|
||||
} else out.pillOn = "알약 단추 없음";
|
||||
return JSON.stringify(out);
|
||||
})()
|
||||
@@ -33,10 +33,11 @@ const BASE_CSS = `
|
||||
.ui-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.ui-btn__icon { display: inline-flex; width: 16px; height: 16px; }
|
||||
|
||||
/* 작은 크기 — 다섯 모양 모두에 먹음(padding · 글자만 바꿈) */
|
||||
/* 작은 크기 — 다섯 모양 모두에 먹음(padding · 글자 · 모서리) · 낮은 키에 8px 모서리면 알약 꼴이라 반으로 */
|
||||
.ui-btn--sm {
|
||||
padding: 2px var(--spacing-8);
|
||||
font-size: var(--text-caption);
|
||||
border-radius: calc(var(--radius-buttons) / 2);
|
||||
}
|
||||
|
||||
.ui-btn--filled {
|
||||
|
||||
Reference in New Issue
Block a user