Foinse foilsithe
Íosluchtaigh foinse ZIP
CH-J Tools
Brabhsáil comhadlanna agus comhaid chun feidhmchlár áirithe a scaoileadh.
CH-J / Server Manager
1704 Comhaid
0.1.2-beta
Suimigh: · Tógtha:
1
/**2
* Frameless windows on Windows/Linux. Electron 0.1.0 used `titleBarStyle: hidden` with a3
* native `titleBarOverlay` (min/max/close drawn by Chromium in the top-right 40 px).4
* Tauri has no overlay, so the same area gets three equivalent buttons here, and the5
* existing drag regions keep working through a Core command.6
*/7
import { windowControls } from "../core/window";8
import { call, subscribe } from "../transport";9
import { bootInfo } from "./boot";11
const CSS = `12
chj-window-controls { position: fixed !important; top: 0 !important; right: 0 !important; height: 40px !important; z-index: 2147483647 !important; display: flex !important; }13
chj-window-controls button { width: 46px; height: 40px; border: 0; margin: 0; padding: 0; display: grid; place-items: center; color: #d9eaff; background: transparent; cursor: default; -webkit-app-region: no-drag; }14
chj-window-controls button:hover { background: rgba(217, 234, 255, .12); }15
chj-window-controls button.close:hover { background: #c42b1c; color: #fff; }16
chj-window-controls svg { width: 10px; height: 10px; stroke: currentColor; fill: none; stroke-width: 1.1; shape-rendering: crispEdges; }17
`;19
const ICONS: Record<string, string> = {20
minimize: '<svg viewBox="0 0 10 10"><path d="M0 5.5h10"/></svg>',21
toggleMaximize: '<svg viewBox="0 0 10 10"><rect x="0.5" y="0.5" width="9" height="9"/></svg>',22
close: '<svg viewBox="0 0 10 10"><path d="M0 0l10 10M10 0L0 10"/></svg>'23
};25
function isDragTarget(target: EventTarget | null, selectors: string[]): boolean {26
if (!(target instanceof Element)) return false;27
if (target.closest("button, a, input, select, textarea, [contenteditable], chj-window-controls")) return false;28
return selectors.some((selector) => Boolean(target.closest(selector)));29
}31
let controlsEnabled = true;32
let controlsHost: HTMLElement | null = null;34
/** An embedded module has no window of its own: the controls and dragging return with a separate window. */35
export function setWindowControlsEnabled(enabled: boolean): void {36
controlsEnabled = enabled;37
if (!controlsHost) return;38
if (enabled) { if (!controlsHost.isConnected) document.body.append(controlsHost); }39
else controlsHost.remove();40
}42
export function installWindowChrome(platform: string, dragSelectors: string[], dragRoots: () => Array<Document | ShadowRoot>): void {43
// This handshake runs on WKWebView, WebView2 and WebKitGTK, including native macOS chrome.44
window.addEventListener("chj:query-dirty", (event) => {45
let dirty = true;46
try {47
const page = window as Window & { __CHJ_HAS_UNSAVED_CHANGES__?: () => boolean; isDirty?: () => boolean };48
// NGINX uses a classic script with a global isDirty(), including disconnected drafts.49
const query = page.__CHJ_HAS_UNSAVED_CHANGES__ || (bootInfo().moduleId === "chj.nginx-manager" ? page.isDirty : undefined);50
dirty = query ? Boolean(query()) : false;51
} catch { /* A failed dirty query preserves the draft. */ }52
void call("window_control", "window:control", { action: "closeQueryReply", queryId: (event as CustomEvent<number>).detail, dirty }).catch(() => {});53
});54
if (platform !== "win32" && platform !== "linux") return;55
const mount = () => {56
if (document.querySelector("chj-window-controls")) return;57
const host = document.createElement("chj-window-controls");58
const shadow = host.attachShadow({ mode: "closed" });59
const css = CSS.replace(/chj-window-controls /g, ":host ").replace(/chj-window-controls \{/g, ":host {");60
// A constructed stylesheet is not inline, so module pages with a strict style-src keep their window controls.61
if ("CSSStyleSheet" in globalThis && "replaceSync" in CSSStyleSheet.prototype) {62
const sheet = new CSSStyleSheet();63
sheet.replaceSync(css);64
shadow.adoptedStyleSheets = [sheet];65
} else {66
const style = document.createElement("style");67
style.textContent = css;68
shadow.append(style);69
}70
let language = document.documentElement.lang || "en";71
let maximized = false;72
const buttons = new Map<string, HTMLButtonElement>();73
const translate = () => {74
const catalogs = globalThis.__CHJ_I18N__?.catalogs?.chrome || {};75
for (const [action, button] of buttons) {76
const key = `chrome.${action === "toggleMaximize" ? (maximized ? "restore" : "maximize") : action}`;77
const label = catalogs[language]?.[key] || catalogs[language.split("-")[0]]?.[key] || catalogs.en?.[key] || key;78
button.setAttribute("aria-label", label);79
button.title = label;80
if (action === "toggleMaximize") button.setAttribute("aria-pressed", String(maximized));81
}82
};83
const updateMaximized = () => { void windowControls.isMaximized().then((value) => { maximized = value; translate(); }).catch(() => {}); };84
subscribe<string>("module:ui:languageChanged", (value) => { language = value; translate(); });85
new MutationObserver(() => { language = document.documentElement.lang || language; translate(); }).observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] });86
window.addEventListener("resize", updateMaximized);87
for (const action of ["minimize", "toggleMaximize", "close"] as const) {88
const button = document.createElement("button");89
button.type = "button";90
button.className = action === "close" ? "close" : action;91
buttons.set(action, button);92
button.innerHTML = ICONS[action];93
button.addEventListener("click", () => { void windowControls.perform(action).then(updateMaximized).catch(() => {}); });94
shadow.append(button);95
}96
translate();97
updateMaximized();98
const outer = document.createElement("style");99
outer.textContent = "chj-window-controls { position: fixed; top: 0; right: 0; height: 40px; z-index: 2147483647; display: flex; }";100
document.head.append(outer);101
controlsHost = host;102
setWindowControlsEnabled(controlsEnabled);103
const handler = (event: Event) => {104
const mouse = event as MouseEvent;105
if (mouse.button !== 0 || !controlsEnabled) return;106
const path = mouse.composedPath();107
const target = (path[0] as EventTarget) || mouse.target;108
if (!isDragTarget(target, dragSelectors)) return;109
if (mouse.detail === 2) void windowControls.perform("toggleMaximize").catch(() => {});110
else void windowControls.perform("startDrag").catch(() => {});111
};112
for (const root of dragRoots()) root.addEventListener("mousedown", handler);113
};114
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", mount, { once: true });115
else mount();116
}SHA-256
47ede93033bf715e3aa50ba4788019311fef44efb561e72a146c4d8b04349d95CH-J Proprietary Software License 1.14 · Tá Foinse ar fáil faoin CH-J Proprietary Software License 1.14. Ní chuireann a infhaighteacht a athrú ar na téarmaí ceadúnais nó cearta breise a dheonú.
Cartlann SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f