@zq-silk/yui 2.15.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/assets/client/app/catalog.js +40 -14
- package/dist/web/assets/client/app/main.js +33 -9
- package/dist/web/assets/client/app/manage.js +18 -0
- package/dist/web/assets/client/app/selection.js +4 -1
- package/dist/web/assets/client/app/taskView.js +22 -5
- package/dist/web/assets/client/i18n/en.js +10 -3
- package/dist/web/assets/client/i18n/zh-CN.js +10 -3
- package/dist/web/assets/client/index.js +4 -0
- package/dist/web/assets/client/layout/manageShell.js +30 -8
- package/dist/web/assets/client/layout/shortcuts.js +7 -3
- package/dist/web/assets/client/layout/sidebarNav.js +147 -0
- package/dist/web/assets/client/layout/sizing.js +5 -2
- package/dist/web/assets/client/layout/workspace.js +120 -21
- package/dist/web/assets/client/lib/motion.js +59 -0
- package/dist/web/assets/client/lib/prefs.js +29 -2
- package/dist/web/assets/client/lib/theme.js +11 -4
- package/dist/web/assets/client/ui/primitives.js +27 -0
- package/dist/web/assets/client/views/overview.js +8 -5
- package/dist/web/assets/client/views/task/page.js +10 -10
- package/dist/web/assets/shared/brand.js +22 -0
- package/dist/web/assets/shared/geometry.js +7 -1
- package/dist/web/assets/shared/icons.js +4 -0
- package/dist/web/assets/shared/motion.js +74 -0
- package/dist/web/assets/shell/appRail.js +14 -0
- package/dist/web/assets/shell/dialogs.js +1 -0
- package/dist/web/assets/shell/dock.js +19 -6
- package/dist/web/assets/shell/index.js +6 -2
- package/dist/web/assets/shell/login.js +9 -8
- package/dist/web/assets/shell/manage.js +32 -20
- package/dist/web/assets/shell/settingsContent.js +1 -1
- package/dist/web/assets/shell/sidebar.js +16 -23
- package/dist/web/assets/styles/base.js +1 -1
- package/dist/web/assets/styles/index.js +2 -0
- package/dist/web/assets/styles/layout/responsive.js +63 -19
- package/dist/web/assets/styles/layout/workspace.js +53 -16
- package/dist/web/assets/styles/motion.js +51 -0
- package/dist/web/assets/styles/tokens.js +5 -3
- package/dist/web/assets/styles/ui/controls.js +1 -1
- package/dist/web/assets/styles/ui/forms.js +1 -1
- package/dist/web/assets/styles/ui/surfaces.js +1 -2
- package/dist/web/assets/styles/views/conversation.js +1 -1
- package/dist/web/assets/styles/views/dock.js +22 -2
- package/dist/web/assets/styles/views/login.js +16 -13
- package/dist/web/assets/styles/views/manage.js +25 -42
- package/dist/web/assets/styles/views/page.js +2 -3
- package/dist/web/assets/styles/views/records.js +1 -1
- package/dist/web/assets/styles/views/settings.js +1 -1
- package/dist/web/assets/styles/views/sidebar.js +25 -20
- package/dist/web/assets/styles/views/task.js +9 -15
- package/dist/web/assets/styles/views/terminal.js +1 -1
- package/docs/release-workflow.md +22 -0
- package/docs/release-workflow.zh-CN.md +15 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ export const TASK_PAGE_SCRIPT = String.raw `
|
|
|
4
4
|
// observation arrives separately from the Context snapshot, so every fact it
|
|
5
5
|
// feeds lives in a [data-slot] that updateObservation() redraws in place.
|
|
6
6
|
import { h, icon, clear } from "/assets/js/lib/dom.js";
|
|
7
|
-
import { badge, chip, timeTag } from "/assets/js/ui/primitives.js";
|
|
7
|
+
import { badge, chip, operatorToggle, timeTag, topBar } from "/assets/js/ui/primitives.js";
|
|
8
8
|
import { label, statusBadge } from "/assets/js/domain/vocab.js";
|
|
9
9
|
import { totalOf } from "/assets/js/domain/context.js";
|
|
10
10
|
import { renderOverviewPanel } from "/assets/js/views/task/overview.js";
|
|
@@ -29,8 +29,8 @@ export function renderTaskDetail(container, data, t, locale, ctx) {
|
|
|
29
29
|
container.dataset.jobsLocale = locale;
|
|
30
30
|
data.viewState.coreKey = data.core.coreCursor;
|
|
31
31
|
const page = h("div.page.task-page");
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
container.append(taskBar(task, t, ctx));
|
|
33
|
+
page.append(h("div.task-hero", null, h("h1.task-title", null, task.title), taskMeta(task, t, locale)),
|
|
34
34
|
h("div.tabs-wrap", null, taskTabs(data.core, t, ctx)));
|
|
35
35
|
const panels = {};
|
|
36
36
|
TABS.forEach(function (tab) {
|
|
@@ -74,15 +74,15 @@ export function selectTab(container, tab) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function taskBar(task, t, ctx) {
|
|
77
|
-
return
|
|
78
|
-
h("button.icon-btn.back-btn", { type: "button", "aria-label": t("actions.back"), title: t("actions.back"), onclick: ctx.onBack }, icon("back")),
|
|
77
|
+
return topBar(t, ctx.revealSidebar,
|
|
79
78
|
h("nav.crumbs", { "aria-label": t("crumbs.label") },
|
|
80
|
-
h("button.crumb", { type: "button", onclick: ctx.onBack }, t("
|
|
79
|
+
h("button.crumb", { type: "button", title: t("overview.title"), onclick: ctx.onBack }, t("manage.workbench")),
|
|
81
80
|
h("span.crumb-sep", { "aria-hidden": "true" }, "/"),
|
|
82
|
-
h("code.crumb-current",
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
h("code.crumb-current", { title: task.title }, task.id)),
|
|
82
|
+
[dockToggle("discussion", "chat", t("dock.discussion") + " · D", t, ctx),
|
|
83
|
+
dockToggle("session", "terminal", t("dock.session"), t, ctx),
|
|
84
|
+
h("span.task-bar-sep", { "aria-hidden": "true" }),
|
|
85
|
+
operatorToggle(t, ctx.toggleOperator)]);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function dockToggle(mode, iconName, title, t, ctx) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { THEMES } from "./themes.js";
|
|
2
|
+
// The Yui mark: a capital Y with an accent dot on a tile (64×64 grid). One
|
|
3
|
+
// geometry source for the in-page mark and the favicon. The in-page mark is
|
|
4
|
+
// drawn with theme tokens via CSS (tile = ink, Y = canvas, dot = accent), so it
|
|
5
|
+
// follows a theme switch; the favicon cannot read page CSS, so each theme gets
|
|
6
|
+
// a pre-coloured copy that the theme script swaps in.
|
|
7
|
+
const Y_PATH = "M17 17L30 33L43 17M30 33V49";
|
|
8
|
+
const DOT = { cx: 49.5, cy: 46, r: 4.6 };
|
|
9
|
+
// Inline glyph for `.brand-mark`; the tile itself is the element's background.
|
|
10
|
+
export function brandGlyphSvg() {
|
|
11
|
+
return `<svg class="brand-glyph" viewBox="0 0 64 64" aria-hidden="true" focusable="false">`
|
|
12
|
+
+ `<path d="${Y_PATH}" fill="none" stroke="currentColor" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
|
13
|
+
+ `<circle class="brand-dot" cx="${DOT.cx}" cy="${DOT.cy}" r="${DOT.r}"/></svg>`;
|
|
14
|
+
}
|
|
15
|
+
function brandIconSvg(tile, glyph, dot) {
|
|
16
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">`
|
|
17
|
+
+ `<rect width="64" height="64" rx="16" fill="${tile}"/>`
|
|
18
|
+
+ `<path d="${Y_PATH}" fill="none" stroke="${glyph}" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
|
19
|
+
+ `<circle cx="${DOT.cx}" cy="${DOT.cy}" r="${DOT.r}" fill="${dot}"/></svg>`;
|
|
20
|
+
}
|
|
21
|
+
// Favicon data URIs keyed by theme id (img-src allows data:).
|
|
22
|
+
export const BRAND_ICONS = Object.freeze(Object.fromEntries(THEMES.map(theme => [theme.id, "data:image/svg+xml," + encodeURIComponent(brandIconSvg(theme.tokens.ink, theme.tokens.canvas, theme.tokens.accent))])));
|
|
@@ -9,11 +9,17 @@ export const BREAKPOINTS = Object.freeze({
|
|
|
9
9
|
// ≤ small: phone layout for page content.
|
|
10
10
|
small: 620
|
|
11
11
|
});
|
|
12
|
+
// The global navigation rail (Workbench / Models / Settings) on every page.
|
|
13
|
+
export const RAIL_WIDTH = 60;
|
|
12
14
|
export const SIDEBAR = Object.freeze({ min: 240, max: 560, compact: 264, step: 16 });
|
|
13
15
|
export const DOCK = Object.freeze({ min: 320, max: 760, initial: 440, step: 24 });
|
|
14
16
|
// The task column never shrinks below this while a divider is dragged.
|
|
15
17
|
export const CENTER_MIN = 420;
|
|
16
18
|
export const DIVIDER_WIDTH = 9;
|
|
19
|
+
// Between narrow and this width an open dock and the default task list cannot
|
|
20
|
+
// both fit beside a minimum task column, so the task list steps aside while
|
|
21
|
+
// the dock is open (closing the dock brings it back).
|
|
22
|
+
export const DOCK_FOCUS_MAX = RAIL_WIDTH + SIDEBAR.compact + DIVIDER_WIDTH + DOCK.initial + CENTER_MIN - 1;
|
|
17
23
|
export const LAYOUT_GEOMETRY = Object.freeze({
|
|
18
|
-
narrow: BREAKPOINTS.narrow, sidebar: SIDEBAR, dock: DOCK, centerMin: CENTER_MIN, divider: DIVIDER_WIDTH
|
|
24
|
+
narrow: BREAKPOINTS.narrow, focusMax: DOCK_FOCUS_MAX, rail: RAIL_WIDTH, sidebar: SIDEBAR, dock: DOCK, centerMin: CENTER_MIN, divider: DIVIDER_WIDTH
|
|
19
25
|
});
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
export const ICON_PATHS = Object.freeze({
|
|
5
5
|
refresh: ["M20 11a8 8 0 0 0-14.3-4.9L4 8", "M4 4v4h4", "M4 13a8 8 0 0 0 14.3 4.9L20 16", "M20 20v-4h-4"],
|
|
6
6
|
search: ["M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14z", "M20 20l-4-4"],
|
|
7
|
+
textSearch: ["M4 6h14", "M4 11h6", "M4 16h4", "M15.5 19a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z", "M21 21l-3-3"],
|
|
7
8
|
terminal: ["M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z", "M7 10l3 2-3 2", "M12 15h5"],
|
|
8
9
|
send: ["M4 12l16-8-6 16-2.5-6.5z", "M11.5 13.5L20 4"],
|
|
9
10
|
swap: ["M7 7h13", "M16 3l4 4-4 4", "M17 17H4", "M8 13l-4 4 4 4"],
|
|
10
11
|
close: ["M6 6l12 12", "M18 6L6 18"],
|
|
12
|
+
workbench: ["M4 4h7v7H4z", "M13 4h7v4h-7z", "M13 10h7v10h-7z", "M4 13h7v7H4z"],
|
|
13
|
+
sidebar: ["M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z", "M9 5v14", "M5.5 9h1", "M5.5 12h1"],
|
|
14
|
+
menu: ["M4 7h16", "M4 12h16", "M4 17h16"],
|
|
11
15
|
back: ["M19 12H5", "M11 6l-6 6 6 6"],
|
|
12
16
|
dock: ["M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z", "M14 5v14", "M17 9h1", "M17 12h1"],
|
|
13
17
|
settings: ["M4 7h10", "M18 7h2", "M16 5v4", "M4 17h4", "M12 17h8", "M10 15v4"],
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// The motion system: every duration, easing, keyframe and stacking layer in
|
|
2
|
+
// the web UI comes from here. The stylesheets read the generated CSS custom
|
|
3
|
+
// properties (tokens.ts) and keyframes (styles/motion.ts); client scripts read
|
|
4
|
+
// the same values as JSON (client/lib/motion.ts). Changing a number below
|
|
5
|
+
// retimes the whole interface in one place.
|
|
6
|
+
//
|
|
7
|
+
// The curves follow well-known open-source component libraries: `sheet` is
|
|
8
|
+
// the iOS / Vaul drawer curve, `standard` a Material/Radix-style ease-out for
|
|
9
|
+
// small UI, and `exit` an accelerate curve so things leave quicker than they
|
|
10
|
+
// arrive.
|
|
11
|
+
export const DURATIONS = Object.freeze({
|
|
12
|
+
// Hover, press and colour feedback.
|
|
13
|
+
fast: 120,
|
|
14
|
+
// Popovers, menus, dialogs, content swaps.
|
|
15
|
+
base: 200,
|
|
16
|
+
// Exits are a little quicker than entries.
|
|
17
|
+
exit: 150,
|
|
18
|
+
// Column resizes (sidebar collapse, dock open) via View Transitions.
|
|
19
|
+
layout: 260,
|
|
20
|
+
// Drawers and sheets.
|
|
21
|
+
sheet: 420,
|
|
22
|
+
sheetOut: 300,
|
|
23
|
+
// Cross-document page transitions.
|
|
24
|
+
page: 220
|
|
25
|
+
});
|
|
26
|
+
export const EASINGS = Object.freeze({
|
|
27
|
+
standard: "cubic-bezier(.2,.8,.2,1)",
|
|
28
|
+
sheet: "cubic-bezier(.32,.72,0,1)",
|
|
29
|
+
exit: "cubic-bezier(.4,0,1,1)"
|
|
30
|
+
});
|
|
31
|
+
// Stacking layers, lowest to highest.
|
|
32
|
+
export const LAYERS = Object.freeze({
|
|
33
|
+
raised: 1,
|
|
34
|
+
sticky: 10,
|
|
35
|
+
header: 20,
|
|
36
|
+
popover: 30,
|
|
37
|
+
sheet: 60,
|
|
38
|
+
scrim: 70,
|
|
39
|
+
drawer: 71,
|
|
40
|
+
railOver: 72,
|
|
41
|
+
toast: 90,
|
|
42
|
+
skip: 100
|
|
43
|
+
});
|
|
44
|
+
// Keyframe presets. Each is emitted as a CSS @keyframes rule named
|
|
45
|
+
// `m-<name>` and handed to the Web Animations API by the client.
|
|
46
|
+
export const PRESETS = Object.freeze({
|
|
47
|
+
fade: { frames: [{ opacity: 0 }, { opacity: 1 }], duration: "base", easing: "standard" },
|
|
48
|
+
rise: { frames: [{ opacity: 0, transform: "translate3d(0,6px,0)" }, { opacity: 1, transform: "none" }], duration: "base", easing: "standard" },
|
|
49
|
+
pop: { frames: [{ opacity: 0, transform: "scale(.96)" }, { opacity: 1, transform: "none" }], duration: "base", easing: "standard" },
|
|
50
|
+
sheetUp: { frames: [{ transform: "translate3d(0,100%,0)" }, { transform: "translate3d(0,0,0)" }], duration: "sheet", easing: "sheet" },
|
|
51
|
+
sheetDown: { frames: [{ transform: "translate3d(0,0,0)" }, { transform: "translate3d(0,100%,0)" }], duration: "sheetOut", easing: "sheet" }
|
|
52
|
+
});
|
|
53
|
+
const kebab = (name) => name.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
|
|
54
|
+
// CSS custom properties: --t-<duration>, --ease-<easing>, --z-<layer>.
|
|
55
|
+
// `--t-med` and `--ease` stay as aliases for the existing stylesheets.
|
|
56
|
+
export function motionTokenStyles() {
|
|
57
|
+
const t = Object.entries(DURATIONS).map(([k, v]) => `--t-${kebab(k)}:${v}ms`);
|
|
58
|
+
const e = Object.entries(EASINGS).map(([k, v]) => `--ease-${kebab(k)}:${v}`);
|
|
59
|
+
const z = Object.entries(LAYERS).map(([k, v]) => `--z-${kebab(k)}:${v}`);
|
|
60
|
+
return [...t, "--t-med:var(--t-base)", ...e, "--ease:var(--ease-standard)", ...z].join(";") + ";";
|
|
61
|
+
}
|
|
62
|
+
function frameCss(frame) {
|
|
63
|
+
return Object.entries(frame).map(([k, v]) => `${kebab(k)}:${v}`).join(";");
|
|
64
|
+
}
|
|
65
|
+
export function motionKeyframes() {
|
|
66
|
+
const presets = Object.entries(PRESETS).map(([name, p]) => {
|
|
67
|
+
const last = p.frames.length - 1;
|
|
68
|
+
const stops = p.frames.map((f, i) => `${last ? Math.round((i / last) * 100) : 0}%{${frameCss(f)}}`).join("");
|
|
69
|
+
return `@keyframes m-${kebab(name)}{${stops}}`;
|
|
70
|
+
});
|
|
71
|
+
return [...presets, "@keyframes m-spin{to{transform:rotate(360deg)}}", "@keyframes m-blink{50%{opacity:.35}}"].join("\n");
|
|
72
|
+
}
|
|
73
|
+
// The JSON the client motion helper reads.
|
|
74
|
+
export const MOTION = Object.freeze({ durations: DURATIONS, easings: EASINGS, presets: PRESETS });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { brandGlyphSvg } from "../shared/brand.js";
|
|
2
|
+
import { iconSvg } from "../shared/icons.js";
|
|
3
|
+
const item = (area, href, icon, key, label, current, id) => `<a${id ? ` id="${id}"` : ""} class="rail-item" href="${href}" data-area="${area}"${area === current ? ` aria-current="page"` : ""}${area === "workbench" && current === "workbench" ? "" : ` data-nav-area="${area}"`} title="${label}" data-i18n-title="${key}">${iconSvg(icon)}<span data-i18n="${key}">${label}</span></a>`;
|
|
4
|
+
export function appRail(current) {
|
|
5
|
+
const brandNav = current === "workbench" ? "" : ` data-nav-area="workbench"`;
|
|
6
|
+
return `<nav class="app-rail" aria-label="Main navigation" data-i18n-aria-label="manage.nav">
|
|
7
|
+
<a class="rail-brand" href="/"${brandNav} title="Yui"><span class="brand-mark" aria-hidden="true">${brandGlyphSvg()}</span><span class="sr-only">Yui</span></a>
|
|
8
|
+
<div class="rail-items">
|
|
9
|
+
${item("workbench", "/", "workbench", "manage.workbench", "Workbench", current, current === "workbench" ? "rail-workbench" : undefined)}
|
|
10
|
+
${item("models", "/models", "layers", "models.title", "Models", current)}
|
|
11
|
+
${item("settings", "/settings", "settings", "settings.title", "Settings", current, current === "workbench" ? "settings-open" : undefined)}
|
|
12
|
+
</div>
|
|
13
|
+
</nav>`;
|
|
14
|
+
}
|
|
@@ -27,6 +27,7 @@ export const DIALOGS_HTML = ` <dialog id="settings-dialog" class="dialog settin
|
|
|
27
27
|
<dt><kbd>R</kbd></dt><dd data-i18n="shortcut.refresh">Refresh</dd>
|
|
28
28
|
<dt><kbd>D</kbd></dt><dd data-i18n="shortcut.dock">Show or hide the session panel</dd>
|
|
29
29
|
<dt><kbd>O</kbd></dt><dd data-i18n="shortcut.operator">Operator session</dd>
|
|
30
|
+
<dt><kbd>[</kbd></dt><dd data-i18n="shortcut.sidebar">Show or hide the task list</dd>
|
|
30
31
|
<dt><kbd>1</kbd>–<kbd>${TASK_TABS.length}</kbd></dt><dd data-i18n="shortcut.tabs">Switch task sections</dd>
|
|
31
32
|
<dt><kbd>Esc</kbd></dt><dd data-i18n="shortcut.escape">Close panel / leave task</dd>
|
|
32
33
|
</dl>
|
|
@@ -7,17 +7,30 @@ import { iconSvg } from "../shared/icons.js";
|
|
|
7
7
|
export const DOCK_HTML = ` <div id="dock-divider" class="dock-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="${DOCK.min}" aria-valuemax="${DOCK.max}" aria-valuenow="${DOCK.initial}" aria-label="Resize session panel" data-i18n-aria-label="dock.resize" hidden><span aria-hidden="true"></span></div>
|
|
8
8
|
<aside id="dock" class="dock" aria-label="Session" data-i18n-aria-label="dock.label" hidden>
|
|
9
9
|
<header class="dock-head">
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
<details id="dock-target" class="dock-target">
|
|
11
|
+
<summary class="dock-target-btn" aria-label="Conversation target" data-i18n-aria-label="dock.target">
|
|
12
|
+
<span id="dock-target-icon" class="dock-target-icon" aria-hidden="true">${iconSvg("broadcast")}</span>
|
|
13
|
+
<span id="dock-target-label" class="dock-target-label">Operator</span>${iconSvg("down")}
|
|
14
|
+
</summary>
|
|
15
|
+
<div class="dock-target-menu">
|
|
16
|
+
<div id="dock-target-options" class="dock-target-options" role="group" aria-label="Conversation target" data-i18n-aria-label="dock.target"></div>
|
|
17
|
+
<hr>
|
|
18
|
+
<button id="global-input-open" class="dock-target-item" type="button">${iconSvg("send")}<span data-i18n="global.openRole">Message another Role…</span></button>
|
|
19
|
+
</div>
|
|
20
|
+
</details>
|
|
16
21
|
<div class="dock-actions">
|
|
17
22
|
<button id="dock-swap" class="icon-btn" type="button" aria-label="Swap with task details" data-i18n-aria-label="dock.swap" title="Swap with task details">${iconSvg("swap")}</button>
|
|
18
23
|
<button id="dock-close" class="icon-btn" type="button" aria-label="Hide session panel" data-i18n-aria-label="dock.hide" title="Hide · D">${iconSvg("close")}</button>
|
|
19
24
|
</div>
|
|
20
25
|
</header>
|
|
26
|
+
<div class="dock-tabs">
|
|
27
|
+
<div class="seg seg-sm" role="tablist" aria-label="Session view" data-i18n-aria-label="dock.label">
|
|
28
|
+
<button id="dock-tab-conversation" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-conversation"><span data-i18n="conversation.title">Conversation</span></button>
|
|
29
|
+
<button id="dock-tab-discussion" class="seg-btn" type="button" role="tab" aria-selected="true" aria-controls="dock-discussion">${iconSvg("chat")}<span data-i18n="dock.discussion">Discussion</span></button>
|
|
30
|
+
<button id="dock-tab-session" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-session">${iconSvg("terminal")}<span data-i18n="dock.session">Live session</span></button>
|
|
31
|
+
<button id="dock-tab-preview" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-preview" disabled><span data-i18n="preview.title">Preview</span></button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
21
34
|
<section id="dock-conversation" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-conversation" hidden></section>
|
|
22
35
|
<section id="dock-discussion" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-discussion"></section>
|
|
23
36
|
<section id="dock-preview" class="dock-pane dock-preview" role="tabpanel" aria-labelledby="dock-tab-preview" hidden></section>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { BRAND_ICONS } from "../shared/brand.js";
|
|
1
2
|
import { DEFAULT_DARK_THEME } from "../shared/themes.js";
|
|
2
3
|
import { STYLESHEETS } from "../styles/index.js";
|
|
3
4
|
import { DIALOGS_HTML } from "./dialogs.js";
|
|
4
5
|
import { DOCK_HTML } from "./dock.js";
|
|
5
6
|
import { SIDEBAR_HTML } from "./sidebar.js";
|
|
7
|
+
import { appRail } from "./appRail.js";
|
|
6
8
|
// Static application frame. Everything inside #detail, #task-list and the dock
|
|
7
9
|
// panes is rendered by the client modules; the shell only fixes the regions
|
|
8
10
|
// and the controls whose identity must survive re-renders. Stylesheets are
|
|
@@ -17,8 +19,8 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
17
19
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
18
20
|
<meta name="color-scheme" content="dark light">
|
|
19
21
|
<meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
|
|
22
|
+
<link rel="icon" type="image/svg+xml" href="${BRAND_ICONS[DEFAULT_DARK_THEME.id]}">
|
|
20
23
|
<script src="/assets/theme.js"></script>
|
|
21
|
-
<link rel="icon" href="data:,">
|
|
22
24
|
<title>Yui</title>
|
|
23
25
|
<link rel="preload" href="/assets/fonts/inter-500.woff2" as="font" type="font/woff2" crossorigin>
|
|
24
26
|
<link rel="preload" href="/assets/fonts/inter-600.woff2" as="font" type="font/woff2" crossorigin>
|
|
@@ -28,7 +30,9 @@ ${STYLESHEET_LINKS}
|
|
|
28
30
|
<body>
|
|
29
31
|
<a class="skip-link" href="#center" data-i18n="a11y.skip">Skip to task details</a>
|
|
30
32
|
<div class="app">
|
|
33
|
+
${appRail("workbench")}
|
|
31
34
|
${SIDEBAR_HTML}
|
|
35
|
+
<div id="sidebar-scrim" class="sidebar-scrim"></div>
|
|
32
36
|
<main id="center" class="center" tabindex="-1">
|
|
33
37
|
<div id="detail" class="detail"></div>
|
|
34
38
|
</main>
|
|
@@ -36,6 +40,6 @@ ${DOCK_HTML}
|
|
|
36
40
|
</div>
|
|
37
41
|
${DIALOGS_HTML}
|
|
38
42
|
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
39
|
-
<script type="module" src="/assets/app.js"></script>
|
|
43
|
+
<script type="module" src="/assets/app.js" blocking="render"></script>
|
|
40
44
|
</body>
|
|
41
45
|
</html>`;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { DEFAULT_DARK_THEME } from "../shared/themes.js";
|
|
2
|
+
import { BRAND_ICONS, brandGlyphSvg } from "../shared/brand.js";
|
|
2
3
|
import { iconSvg } from "../shared/icons.js";
|
|
3
4
|
// The only document served without a session. It carries no page token and
|
|
4
5
|
// no Task data; the client module posts the password as same-origin JSON and
|
|
5
6
|
// the server answers with a session cookie. CSP form-action 'none' means the
|
|
6
7
|
// form can never submit natively (so a password cannot end up in a URL).
|
|
7
|
-
const SHEETS = ["fonts", "tokens", "base", "ui/controls", "ui/forms", "views/login"];
|
|
8
|
+
const SHEETS = ["fonts", "tokens", "base", "motion", "ui/controls", "ui/forms", "views/login"];
|
|
8
9
|
export const LOGIN_HTML = `<!doctype html>
|
|
9
10
|
<html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
|
|
10
11
|
<head>
|
|
11
12
|
<meta charset="utf-8">
|
|
12
13
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
13
14
|
<meta name="color-scheme" content="dark light">
|
|
15
|
+
<link rel="icon" type="image/svg+xml" href="${BRAND_ICONS[DEFAULT_DARK_THEME.id]}">
|
|
14
16
|
<script src="/assets/theme.js"></script>
|
|
15
|
-
<link rel="icon" href="data:,">
|
|
16
17
|
<title>Sign in · Yui</title>
|
|
17
18
|
<link rel="preload" href="/assets/fonts/inter-500.woff2" as="font" type="font/woff2" crossorigin>
|
|
18
19
|
<link rel="preload" href="/assets/fonts/inter-600.woff2" as="font" type="font/woff2" crossorigin>
|
|
@@ -20,13 +21,13 @@ ${SHEETS.map(name => ` <link rel="stylesheet" href="/assets/css/${name}.css">`)
|
|
|
20
21
|
</head>
|
|
21
22
|
<body class="login-body">
|
|
22
23
|
<main class="login-shell">
|
|
23
|
-
<div class="login-top">
|
|
24
|
-
<label class="login-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
|
|
25
|
-
</div>
|
|
26
24
|
<section class="login-card" aria-labelledby="login-title">
|
|
27
|
-
<div class="login-
|
|
28
|
-
<
|
|
29
|
-
|
|
25
|
+
<div class="login-top">
|
|
26
|
+
<div class="login-brand">
|
|
27
|
+
<span class="brand-mark" aria-hidden="true">${brandGlyphSvg()}</span>
|
|
28
|
+
<span class="brand-name">Yui</span>
|
|
29
|
+
</div>
|
|
30
|
+
<label class="login-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
|
|
30
31
|
</div>
|
|
31
32
|
<header class="login-heading">
|
|
32
33
|
<h1 id="login-title" data-i18n="login.heading">Sign in to Yui</h1>
|
|
@@ -1,51 +1,63 @@
|
|
|
1
1
|
import { STYLESHEETS } from "../styles/index.js";
|
|
2
2
|
import { DEFAULT_DARK_THEME } from "../shared/themes.js";
|
|
3
|
+
import { BRAND_ICONS } from "../shared/brand.js";
|
|
3
4
|
import { iconSvg } from "../shared/icons.js";
|
|
5
|
+
import { appRail } from "./appRail.js";
|
|
4
6
|
import { MANAGE_SECTIONS } from "../shared/manageNav.js";
|
|
5
|
-
// One frame and entry point for /models/* and /settings/*. The
|
|
6
|
-
//
|
|
7
|
-
// the
|
|
7
|
+
// One frame and entry point for /models/* and /settings/*. The frame is the
|
|
8
|
+
// workbench's own: the global rail, a collapsible list (here the current
|
|
9
|
+
// area's sections), and the center column under the shared top bar. Narrow
|
|
10
|
+
// screens open the rail and the list together as a drawer, exactly as on the
|
|
11
|
+
// workbench. The shared router changes sections without unloading their
|
|
12
|
+
// controllers or drafts. Leaving the management document still uses the
|
|
13
|
+
// browser's unsaved-draft prompt.
|
|
8
14
|
const escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
9
15
|
const sectionLink = (item) => `<a class="manage-link" href="/${item.section === "connection" ? "settings" : item.page}/${item.section}" data-page="${item.page}" data-section="${item.section}"><span data-i18n="${item.key}">${escapeHtml(item.label)}</span><i class="manage-dirty" aria-hidden="true"></i></a>`;
|
|
10
|
-
const area = (name,
|
|
16
|
+
const area = (name, key, label) => `
|
|
11
17
|
<div class="manage-area" data-area-group="${name}">
|
|
12
|
-
<a class="manage-primary" href="/${name}" data-area="${name}">${iconSvg(icon)}<span data-i18n="${key}">${label}</span></a>
|
|
13
18
|
<div class="manage-sections" role="group" aria-label="${label}" data-i18n-aria-label="${key}">
|
|
14
19
|
${MANAGE_SECTIONS[name].map(sectionLink).join("\n ")}
|
|
15
20
|
</div>
|
|
16
21
|
</div>`;
|
|
17
|
-
const RAIL = `<aside class="manage-rail">
|
|
18
|
-
<header class="
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
</a>
|
|
22
|
-
<label class="manage-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
|
|
22
|
+
const RAIL = `<aside class="manage-rail sidebar" aria-label="Sections" data-i18n-aria-label="manage.sections">
|
|
23
|
+
<header class="side-head">
|
|
24
|
+
<h2 id="manage-area-title" class="side-title"></h2>
|
|
25
|
+
<button id="sidebar-collapse" class="icon-btn side-collapse" type="button" aria-label="Collapse sections" data-i18n-aria-label="manage.collapseSections" title="Collapse sections · [" data-i18n-title="manage.collapseSections">${iconSvg("sidebar")}</button>
|
|
23
26
|
</header>
|
|
24
|
-
<nav class="manage-nav" aria-label="
|
|
25
|
-
<div class="manage-area" data-area-group="workbench">
|
|
26
|
-
<a class="manage-primary" href="/" data-area="workbench">${iconSvg("inbox")}<span data-i18n="manage.workbench">Workbench</span></a>
|
|
27
|
-
</div>${area("models", "layers", "models.title", "Models")}${area("settings", "settings", "settings.title", "Settings")}
|
|
27
|
+
<nav class="manage-nav" aria-label="Sections" data-i18n-aria-label="manage.sections">${area("models", "models.title", "Models")}${area("settings", "settings.title", "Settings")}
|
|
28
28
|
</nav>
|
|
29
29
|
</aside>`;
|
|
30
|
+
const TOP_BAR = `<header class="top-bar">
|
|
31
|
+
<button class="icon-btn side-expand" type="button" data-sidebar-toggle="true" aria-expanded="false" aria-label="Show sections" data-i18n-aria-label="manage.showSections" title="Show sections · [" data-i18n-title="manage.showSections"><span class="i-sidebar">${iconSvg("sidebar")}</span><span class="i-menu">${iconSvg("menu")}</span></button>
|
|
32
|
+
<nav class="crumbs" aria-label="Location" data-i18n-aria-label="crumbs.label"><span id="manage-crumb-area" class="crumb crumb-static"></span><span class="crumb-sep" aria-hidden="true">/</span><span id="manage-crumb-section" class="crumb-current"></span></nav>
|
|
33
|
+
<span class="spacer"></span>
|
|
34
|
+
<label class="top-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
|
|
35
|
+
</header>`;
|
|
30
36
|
// One management document. `main` is the page's reading column markup.
|
|
31
37
|
export function manageDocument(options) {
|
|
32
38
|
return `<!doctype html>
|
|
33
39
|
<html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
|
|
34
40
|
<head>
|
|
35
|
-
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
41
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
36
42
|
<meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
|
|
43
|
+
<link rel="icon" type="image/svg+xml" href="${BRAND_ICONS[DEFAULT_DARK_THEME.id]}">
|
|
37
44
|
<script src="/assets/theme.js"></script>
|
|
38
|
-
<
|
|
45
|
+
<title>Yui · ${options.title}</title>
|
|
39
46
|
${STYLESHEETS.map(s => `<link rel="stylesheet" href="/assets/css/${s.name}.css">`).join("\n ")}
|
|
40
47
|
</head>
|
|
41
48
|
<body class="manage-body ${options.page}-body">
|
|
42
|
-
<div class="manage-app" data-page="${options.page}" hidden>
|
|
49
|
+
<div class="app manage-app" data-page="${options.page}" hidden>
|
|
50
|
+
${appRail(options.page === "models" ? "models" : "settings")}
|
|
43
51
|
${RAIL}
|
|
44
|
-
<
|
|
52
|
+
<div id="sidebar-scrim" class="sidebar-scrim"></div>
|
|
53
|
+
<main id="manage-scroll" class="center manage-center" tabindex="-1">
|
|
54
|
+
${TOP_BAR}
|
|
55
|
+
<div class="manage-main" id="manage-main">
|
|
45
56
|
${options.main}
|
|
57
|
+
</div>
|
|
46
58
|
</main>
|
|
47
59
|
</div>
|
|
48
|
-
<script type="module" src="/assets/js/app/manage.js"></script>
|
|
60
|
+
<script type="module" src="/assets/js/app/manage.js" blocking="render"></script>
|
|
49
61
|
</body></html>`;
|
|
50
62
|
}
|
|
51
63
|
// The common section heading: title and one explanatory line.
|
|
@@ -10,7 +10,7 @@ export const SETTINGS_MAIN = `
|
|
|
10
10
|
<fieldset class="field"><legend data-i18n="settings.theme">Theme</legend><div id="theme-options" class="theme-options"></div></fieldset>
|
|
11
11
|
<div class="settings-stack">
|
|
12
12
|
<label class="field"><span data-i18n="settings.language">Language</span><select id="settings-locale"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
|
|
13
|
-
<label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="
|
|
13
|
+
<label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="structured" data-i18n="settings.structured">Structured view</option><option value="native" data-i18n="settings.native">Native terminal</option></select></label>
|
|
14
14
|
<p data-i18n="settings.accessHelp">Preference for subsequent session selection where supported. It does not switch a live Session.</p>
|
|
15
15
|
<button id="reset-layout" type="button" class="btn" data-i18n="settings.resetLayout">Reset saved workspace layout</button>
|
|
16
16
|
<p id="browser-receipt" role="status"></p>
|
|
@@ -6,24 +6,27 @@ import { iconSvg } from "../shared/icons.js";
|
|
|
6
6
|
// The string keeps its indentation inside .app and has no trailing newline.
|
|
7
7
|
export const SIDEBAR_HTML = ` <aside class="sidebar" aria-label="Tasks" data-i18n-aria-label="sidebar.label">
|
|
8
8
|
<header class="side-head">
|
|
9
|
-
<
|
|
10
|
-
<span class="brand-mark" aria-hidden="true">結</span>
|
|
11
|
-
<span class="brand-name">Yui</span>
|
|
12
|
-
</div>
|
|
9
|
+
<h2 class="side-title" data-i18n="sidebar.label">Tasks</h2>
|
|
13
10
|
<span class="sync" id="sync-state" title="Last sync" data-i18n-title="sync.label">
|
|
14
11
|
<i aria-hidden="true"></i><time id="last-sync">—</time>
|
|
15
12
|
</span>
|
|
16
13
|
<button id="refresh" class="icon-btn" type="button" aria-label="Refresh" data-i18n-aria-label="actions.refresh" title="Refresh · R">${iconSvg("refresh")}</button>
|
|
14
|
+
<button id="sidebar-collapse" class="icon-btn side-collapse" type="button" aria-label="Collapse task list" data-i18n-aria-label="sidebar.collapse" title="Collapse task list · [" data-i18n-title="sidebar.collapse">${iconSvg("sidebar")}</button>
|
|
17
15
|
</header>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
<div class="side-tools">
|
|
17
|
+
<button id="create-task" class="btn btn-primary side-create" type="button">${iconSvg("plus")}<span data-i18n="create.title">New task</span></button>
|
|
18
|
+
<div class="side-search">
|
|
19
|
+
<label class="search-field">
|
|
20
|
+
${iconSvg("search")}
|
|
21
|
+
<span class="sr-only" data-i18n="search.label">Search tasks</span>
|
|
22
|
+
<input id="search" type="search" autocomplete="off" spellcheck="false" placeholder="Search tasks" data-i18n-placeholder="search.placeholder">
|
|
23
|
+
<kbd>/</kbd>
|
|
24
|
+
</label>
|
|
25
|
+
<button id="body-search-open" class="icon-btn side-search-body" type="button" aria-label="Search task contents" data-i18n-aria-label="bodySearch.title" title="Search task contents" data-i18n-title="bodySearch.title">${iconSvg("textSearch")}</button>
|
|
26
|
+
</div>
|
|
27
|
+
<div id="catalog-source" class="seg seg-sm source-seg" role="radiogroup" aria-label="Task category" data-i18n-aria-label="jobs.taskCategory"></div>
|
|
28
|
+
<div id="attention-bar" class="attention-bar" aria-live="polite"></div>
|
|
29
|
+
</div>
|
|
27
30
|
<div id="status-filters" class="status-tabs" role="group" aria-label="Filter by status" data-i18n-aria-label="filters.label"></div>
|
|
28
31
|
<div id="task-list" class="task-list" aria-label="Tasks" data-i18n-aria-label="sidebar.label" aria-live="polite"></div>
|
|
29
32
|
<div id="catalog-controls" class="pager" aria-live="polite">
|
|
@@ -32,15 +35,5 @@ export const SIDEBAR_HTML = ` <aside class="sidebar" aria-label="Tasks" data-
|
|
|
32
35
|
<button id="catalog-next" class="pager-btn" type="button" data-i18n="catalog.next" disabled>Next page</button>
|
|
33
36
|
<button id="catalog-attention-reset" class="pager-clear" type="button" data-i18n="catalog.clearAttention" hidden>Clear attention filter</button>
|
|
34
37
|
</div>
|
|
35
|
-
<footer class="side-foot">
|
|
36
|
-
<button id="operator-terminal" class="foot-btn" type="button" title="Operator session · O">
|
|
37
|
-
${iconSvg("terminal")}<span data-i18n="actions.operator">Operator</span><kbd>O</kbd>
|
|
38
|
-
</button>
|
|
39
|
-
<button id="global-input-open" class="foot-btn" type="button" title="Global input" data-i18n-title="global.open">
|
|
40
|
-
${iconSvg("broadcast")}<span data-i18n="global.open">Global input</span>
|
|
41
|
-
</button>
|
|
42
|
-
<a id="models-open" class="icon-btn" href="/models" aria-label="Models" data-i18n-aria-label="models.title" title="Models" data-i18n-title="models.title">${iconSvg("layers")}</a>
|
|
43
|
-
<button id="settings-open" class="icon-btn" type="button" aria-label="Settings" data-i18n-aria-label="settings.title" title="Settings">${iconSvg("settings")}</button>
|
|
44
|
-
</footer>
|
|
45
38
|
</aside>
|
|
46
39
|
<div id="sidebar-divider" class="sidebar-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="${SIDEBAR.min}" aria-valuemax="${SIDEBAR.max}" aria-valuenow="${SIDEBAR.compact}" aria-label="Resize task list" data-i18n-aria-label="sidebar.resize" title="Drag to resize · double-click to reset" data-i18n-title="sidebar.resizeHint"><span aria-hidden="true"></span></div>`;
|
|
@@ -28,7 +28,7 @@ input:disabled,select:disabled,textarea:disabled{opacity:.55}
|
|
|
28
28
|
::-webkit-scrollbar-track{background:transparent}
|
|
29
29
|
[hidden]{display:none!important}
|
|
30
30
|
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
31
|
-
.skip-link{position:fixed;left:12px;top:-60px;z-index:
|
|
31
|
+
.skip-link{position:fixed;left:12px;top:-60px;z-index:var(--z-skip);padding:9px 14px;border-radius:var(--r-sm);background:var(--accent);color:var(--accent-ink);text-decoration:none;font-weight:600;transition:top var(--t-fast)}
|
|
32
32
|
.skip-link:focus{top:12px}
|
|
33
33
|
.icon{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
|
|
34
34
|
.icon-sm{width:14px;height:14px}
|
|
@@ -3,6 +3,7 @@ import { BASE_STYLES } from "./base.js";
|
|
|
3
3
|
import { RESPONSIVE_STYLES } from "./layout/responsive.js";
|
|
4
4
|
import { WORKSPACE_STYLES } from "./layout/workspace.js";
|
|
5
5
|
import { MARKDOWN_STYLES } from "./markdown.js";
|
|
6
|
+
import { MOTION_STYLES } from "./motion.js";
|
|
6
7
|
import { TOKEN_STYLES } from "./tokens.js";
|
|
7
8
|
import { CONTROL_STYLES } from "./ui/controls.js";
|
|
8
9
|
import { FORM_STYLES } from "./ui/forms.js";
|
|
@@ -29,6 +30,7 @@ export const STYLESHEETS = Object.freeze([
|
|
|
29
30
|
{ name: "fonts", body: FONT_FACE_STYLES },
|
|
30
31
|
{ name: "tokens", body: TOKEN_STYLES },
|
|
31
32
|
{ name: "base", body: BASE_STYLES },
|
|
33
|
+
{ name: "motion", body: MOTION_STYLES },
|
|
32
34
|
{ name: "layout/workspace", body: WORKSPACE_STYLES },
|
|
33
35
|
{ name: "ui/controls", body: CONTROL_STYLES },
|
|
34
36
|
{ name: "ui/surfaces", body: SURFACE_STYLES },
|