0.2.3 — built-in code editor, .uasset previews, new icon

- Built-in code editor: edit working-copy source files in place (Edit → textarea
  → Save) before committing, no external IDE needed. Backend write_depot opens
  the file for edit, clears read-only, writes UTF-8; confined to the client root.
- .uasset previews: pull the embedded Unreal thumbnail (PNG) server-side for both
  working copy and history; on-demand real 3D via a headless Unreal FBX export
  (commandlet mode, no editor window), temp file deleted right after — nothing
  cached on disk. Plugin content paths mapped to their mount point.
- New app icon (brand mark on a theme tile) + theme-aware in-app logo.
- Connection pill / Settings show the actual connected server (P4PORT), not the
  server's internal self-reported address.
- Fix: the "select all" checkbox no longer renders as a giant empty square when
  not everything is selected (size pinned).
- IDE picker chooses which installed editor opens code; AI replies in UI language.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-07 22:55:04 +03:00
parent 6c16d4a4f4
commit 0da836b0ac
61 changed files with 528 additions and 65 deletions

View File

@ -14,7 +14,7 @@ import { t } from "./i18n";
type Api = { toggleWire: (b: boolean) => void; toggleGrid: (b: boolean) => void; reset: () => void };
export default function ModelViewer({ depot, name, spec }: { depot: string; name: string; spec?: string }) {
export default function ModelViewer({ depot, name, spec, bytes, forceExt }: { depot: string; name: string; spec?: string; bytes?: ArrayBuffer; forceExt?: string }) {
const mount = useRef<HTMLDivElement>(null);
const api = useRef<Api | null>(null);
const [status, setStatus] = useState<"loading" | "ok" | "error">("loading");
@ -69,7 +69,7 @@ export default function ModelViewer({ depot, name, spec }: { depot: string; name
let gridHelper: THREE.GridHelper | null = null;
let home = { pos: new THREE.Vector3(3, 2, 3), tgt: new THREE.Vector3() };
const ext = (name.toLowerCase().split(".").pop() || "");
const ext = (forceExt || name.toLowerCase().split(".").pop() || "");
function collectStats(obj: THREE.Object3D) {
let tris = 0, verts = 0, meshes = 0; const mats = new Set<THREE.Material>();
@ -138,7 +138,8 @@ export default function ModelViewer({ depot, name, spec }: { depot: string; name
(async () => {
try {
const buf = spec ? await p4.printDepot(spec) : await p4.readDepot(depot);
// pre-supplied bytes (e.g. an FBX exported from Unreal) skip the p4 fetch
const buf = bytes ? bytes : spec ? await p4.printDepot(spec) : await p4.readDepot(depot);
if (disposed) return;
const manager = new THREE.LoadingManager();
if (ext === "glb" || ext === "gltf") new GLTFLoader(manager).parse(buf, "", (g) => !disposed && place(g.scene), fail);
@ -173,7 +174,7 @@ export default function ModelViewer({ depot, name, spec }: { depot: string; name
api.current = null;
};
// eslint-disable-next-line
}, [depot, name, spec]);
}, [depot, name, spec, bytes, forceExt]);
const fmt = (n: number) => n.toLocaleString("ru");
return (