桌面应用图标替换为 B-brackets 设计(深藏青 + 白/蓝花括号)

- icon.svg 取自 opsx-icon-options/api/B-brackets.svg,全套光栅重生成:
  icon.icns(iconutil)、icon.ico(PNG-in-ICO 6 尺寸)、16-1024 PNG、Windows Square*/StoreLogo
- 替换 Tauri 脚手架默认五角星图标;GUI 重启实测 Dock 图标已生效
- 服务全量重启:server 8787(官网/文档 200)+ web 5173 + 桌面 GUI
This commit is contained in:
logan committed 2026-09-20 00:14:32 +08:00
1 parent 71a6d986a4
commit 39f4d5addc
161 files changed
+3289 -515

No files matched your search

+10 -7
View File
@@ -1,10 +1,10 @@
import { wsSmart } from "./wsSmart";
import type { HttpResult, HttpSpec, Transport, WsSession, WsSpec } from "@opsxapi/core";
import { browserTransport } from "./browser";
/**
* HTTP goes through Gin POST /v1/proxy/http.
* WS (MVP): falls back to browser WebSocket — custom headers require
* desktop/Gin tunnel later (Week 2).
* WS: no custom headers → browser WebSocket; with headers → Gin WS tunnel (wsSmart).
*/
export function ginProxyTransport(baseUrl: string): Transport {
const root = baseUrl.replace(/\/$/, "");
@@ -24,12 +24,15 @@ export function ginProxyTransport(baseUrl: string): Transport {
},
ws(spec: WsSpec): WsSession {
// TODO(week2): custom headers need desktop/Gin WS tunnel.
// For MVP, fall back to browser WebSocket (no custom headers).
console.warn(
"[opsxapi/transport] gin-proxy WS falls back to browser WebSocket; custom headers need desktop/Gin tunnel later",
const headers = spec.headers ?? {};
if (Object.keys(headers).length === 0) {
return browserTransport.ws(spec);
}
// Custom handshake headers → Gin WS tunnel
return wsSmart(
{ url: spec.url, headers, protocols: spec.protocols },
{ ginBase: root },
);
return browserTransport.ws(spec);
},
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
export { browserTransport } from "./browser";
export { ginProxyTransport } from "./ginProxy";
export { tauriNativeTransport } from "./tauriNative";
export { tauriNativeTransport, isTauriRuntime } from "./tauriNative";
export { detectTransport } from "./detect";
export type { TransportKind, DetectOptions } from "./detect";
+40 -6
View File
@@ -12,6 +12,17 @@ async function invoke<T>(cmd: string, args?: Record<string, unknown>): Promise<T
}
}
export function isTauriRuntime(): boolean {
if (typeof window === "undefined") return false;
return Boolean((window as unknown as { __TAURI_INTERNALS__?: unknown }).__TAURI_INTERNALS__);
}
type WsEventPayload = {
sessionId: string;
kind: string;
data: string;
};
export const tauriNativeTransport: Transport = {
async http(spec: HttpSpec): Promise<HttpResult> {
return invoke<HttpResult>("http_send", { spec });
@@ -21,20 +32,37 @@ export const tauriNativeTransport: Transport = {
const messageCbs: Array<(data: string) => void> = [];
const statusCbs: Array<(s: string) => void> = [];
let sessionId: string | null = null;
let unlisten: (() => void) | null = null;
let closed = false;
const emitStatus = (s: string) => statusCbs.forEach((cb) => cb(s));
void (async () => {
try {
emitStatus("connecting");
const { listen } = await import("@tauri-apps/api/event");
unlisten = await listen<WsEventPayload>("ws-event", (ev) => {
const p = ev.payload;
if (!sessionId || p.sessionId !== sessionId) return;
if (p.kind === "message") {
messageCbs.forEach((cb) => cb(p.data));
return;
}
if (p.kind === "status") {
emitStatus(p.data);
if (p.data === "closed" || p.data === "error") {
// keep listening until explicit close()
}
}
});
sessionId = await invoke<string>("ws_connect", { spec });
emitStatus("open");
// Full event listen pipeline is Week 2; stub keeps UI usable.
// open status also arrives via event; emit early for snappy UI
} catch (e) {
emitStatus("error");
messageCbs.forEach((cb) =>
cb(`[ws_connect error] ${e instanceof Error ? e.message : String(e)}`),
);
emitStatus("closed");
}
})();
@@ -57,11 +85,17 @@ export const tauriNativeTransport: Transport = {
});
},
close() {
if (!sessionId) return;
void invoke("ws_close", { sessionId }).finally(() => {
sessionId = null;
if (closed) return;
closed = true;
const id = sessionId;
sessionId = null;
unlisten?.();
unlisten = null;
if (!id) {
emitStatus("closed");
});
return;
}
void invoke("ws_close", { sessionId: id }).finally(() => emitStatus("closed"));
},
};
},