// Typed wrappers around the Rust p4 commands. import { invoke } from "@tauri-apps/api/core"; export interface P4Info { serverAddress?: string; serverVersion?: string; userName?: string; clientName?: string; clientRoot?: string; serverUptime?: string; serverDate?: string; [k: string]: unknown; } export interface OpenedFile { depotFile?: string; clientFile?: string; rev?: string; haveRev?: string; action?: string; // edit | add | delete | branch | integrate | move/add | move/delete type?: string; // text | binary | ... change?: string; // "default" or a number user?: string; client?: string; [k: string]: unknown; } export interface Client { client?: string; Root?: string; Host?: string; Stream?: string; [k: string]: unknown; } export interface Change { change?: string; time?: string; user?: string; client?: string; status?: string; desc?: string; [k: string]: unknown; } export const p4 = { clients: (port: string, user: string) => invoke("p4_clients", { port, user }), login: (port: string, user: string, password: string, client: string) => invoke("p4_login", { port, user, password, client }), restore: (port: string, user: string, client: string) => invoke("p4_restore", { port, user, client }), info: () => invoke("p4_info"), opened: (scope = "") => invoke("p4_opened", { scope }), dirs: (path = "") => invoke("p4_dirs", { path }), changes: () => invoke("p4_changes"), history: (path: string) => invoke("p4_history", { path }), disconnect: () => invoke("p4_disconnect"), sync: (scope = "") => invoke("p4_sync", { scope }), diff: (file: string) => invoke("p4_diff", { file }), revert: (files: string[]) => invoke("p4_revert", { files }), submit: (description: string, files: string[]) => invoke("p4_submit", { description, files }), commit: (description: string, files: string[]) => invoke("p4_commit", { description, files }), submitChange: (change: string) => invoke("p4_submit_change", { change }), add: (files: string[]) => invoke("p4_add", { files }), edit: (files: string[]) => invoke("p4_edit", { files }), del: (files: string[]) => invoke("p4_delete", { files }), reconcile: (path: string) => invoke("p4_reconcile", { path }), scan: (scope = "") => invoke("p4_scan", { scope }), describe: (change: string) => invoke("p4_describe", { change }), undoChange: (change: string) => invoke("p4_undo_change", { change }), openInExplorer: (target: string) => invoke("open_in_explorer", { target }), findUproject: (scope: string) => invoke("find_uproject", { scope }), launchFile: (path: string) => invoke("launch_file", { path }), openInVscode: (depot: string) => invoke("open_in_vscode", { depot }), readDepot: async (depot: string): Promise => { const r: unknown = await invoke("read_depot", { depot }); if (r instanceof ArrayBuffer) return r; if (ArrayBuffer.isView(r)) return (r as ArrayBufferView).buffer as ArrayBuffer; if (Array.isArray(r)) return new Uint8Array(r as number[]).buffer; return r as ArrayBuffer; }, switchClient: (client: string) => invoke("p4_switch_client", { client }), }; export interface Dir { dir?: string; [k: string]: unknown } // depot path -> short leaf name (last segment) export function leaf(path?: string): string { if (!path) return ""; const p = path.replace(/\/+$/, ""); return p.slice(p.lastIndexOf("/") + 1); } // a submitted changelist with indexed file fields (depotFile0, action0, ...) export interface Describe extends Change { [k: string]: unknown } export function describeFiles(d: Describe): { depotFile: string; action: string; rev: string }[] { const out: { depotFile: string; action: string; rev: string }[] = []; for (let i = 0; ; i++) { const dp = d[`depotFile${i}`] as string | undefined; if (!dp) break; out.push({ depotFile: dp, action: (d[`action${i}`] as string) || "edit", rev: (d[`rev${i}`] as string) || "" }); } return out; } // --- session persistence (server/user/workspace — никогда не пароль) --- export interface Session { server: string; user: string; client: string } const SESSION_KEY = "exd-session"; export function saveSession(s: Session) { try { localStorage.setItem(SESSION_KEY, JSON.stringify(s)); } catch {} } export function loadSession(): Session | null { try { const r = localStorage.getItem(SESSION_KEY); return r ? JSON.parse(r) : null; } catch { return null; } } export function clearSession() { try { localStorage.removeItem(SESSION_KEY); } catch {} } // --- working-folder scope persistence (per workspace) --- export function saveScope(client: string, path: string) { try { localStorage.setItem("exd-scope-" + client, path); } catch {} } export function loadScope(client: string): string { try { return client ? (localStorage.getItem("exd-scope-" + client) || "") : ""; } catch { return ""; } } // format a unix-epoch string as a short local datetime export function fmtTime(t?: string): string { if (!t) return ""; const n = Number(t); if (!n) return ""; const d = new Date(n * 1000); return d.toLocaleString(undefined, { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }); } // action -> single-char status + css class export function statusOf(action?: string): { ch: string; cls: string } { const a = (action || "").toLowerCase(); if (a.includes("add")) return { ch: "+", cls: "st-a" }; if (a.includes("delete")) return { ch: "−", cls: "st-d" }; return { ch: "±", cls: "st-e" }; // edit / integrate / branch / move } // depot or client path -> { name, dir } export function splitPath(p?: string): { name: string; dir: string } { if (!p) return { name: "(unknown)", dir: "" }; const norm = p.replace(/\\/g, "/"); const i = norm.lastIndexOf("/"); return { name: norm.slice(i + 1), dir: norm.slice(0, i + 1) }; } // classify by extension for the preview panel export type Kind = "model" | "uasset" | "image" | "code"; export function kindOf(name: string): Kind { const n = name.toLowerCase(); if (/\.(fbx|obj|gltf|glb|stl|ply|3mf|dae)$/.test(n)) return "model"; if (/\.(uasset|umap)$/.test(n)) return "uasset"; if (/\.(png|jpe?g|webp|gif|bmp|svg)$/.test(n)) return "image"; return "code"; }