chore(web-platform): release V0.6.1 工程质量优化
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled

This commit is contained in:
2026-08-28 15:38:10 +08:00
parent f4b415c54f
commit 60d3a6d68c
135 changed files with 13552 additions and 2882 deletions
+87 -39
View File
@@ -1,57 +1,105 @@
import type {ProjectManifest} from './types';
import type { ProjectManifest } from './types';
const TEXT_EXTENSIONS=/\.(?:xml|urdf|txt|obj|mtl|csv|json|yaml|yml)$/i;
const decoder=new TextDecoder('utf-8',{fatal:false});
const encoder=new TextEncoder();
const TEXT_EXTENSIONS = /\.(?:xml|urdf|txt|obj|mtl|csv|json|yaml|yml)$/i;
const decoder = new TextDecoder('utf-8', { fatal: false });
const encoder = new TextEncoder();
export function isEditableSource(path:string):boolean{return TEXT_EXTENSIONS.test(path);}
export function editableSourcePaths(manifest:ProjectManifest):string[]{
return manifest.files.filter(file=>isEditableSource(file.path)).map(file=>file.path).sort((a,b)=>a.localeCompare(b));
export function isEditableSource(path: string): boolean {
return TEXT_EXTENSIONS.test(path);
}
export function readCachedText(manifest:ProjectManifest,path:string):string{
const file=manifest.files.find(candidate=>candidate.path===path);
if(!file)throw new Error(`缓存中找不到文件:${path}`);
if(!isEditableSource(path))throw new Error(`不支持编辑二进制文件:${path}`);
export function editableSourcePaths(manifest: ProjectManifest): string[] {
return manifest.files
.filter((file) => isEditableSource(file.path))
.map((file) => file.path)
.sort((a, b) => a.localeCompare(b));
}
export function readCachedText(manifest: ProjectManifest, path: string): string {
const file = manifest.files.find((candidate) => candidate.path === path);
if (!file) throw new Error(`缓存中找不到文件:${path}`);
if (!isEditableSource(path)) throw new Error(`不支持编辑二进制文件:${path}`);
return decoder.decode(file.data);
}
/** 返回只更新浏览器会话内存的新工程清单,不接触用户本地文件系统。 */
export function mergeCachedFiles(manifest:ProjectManifest,additional:ProjectManifest['files']):ProjectManifest{
if(!additional.length)return manifest;
const byPath=new Map(manifest.files.map(file=>[file.path,file]));
for(const file of additional)byPath.set(file.path,file);
const files=Array.from(byPath.values());
return {...manifest,files,totalBytes:files.reduce((total,item)=>total+item.size,0)};
export function mergeCachedFiles(
manifest: ProjectManifest,
additional: ProjectManifest['files'],
): ProjectManifest {
if (!additional.length) return manifest;
const byPath = new Map(manifest.files.map((file) => [file.path, file]));
for (const file of additional) byPath.set(file.path, file);
const files = Array.from(byPath.values());
return { ...manifest, files, totalBytes: files.reduce((total, item) => total + item.size, 0) };
}
export function upsertCachedMjcf(manifest:ProjectManifest,path:string,text:string):ProjectManifest{
const data=encoder.encode(text),index=manifest.files.findIndex(candidate=>candidate.path===path);
const files=manifest.files.slice();
const file={path,data,size:data.byteLength,source:'file' as const,mimeType:'application/xml'};
if(index<0)files.push(file);else files[index]={...files[index],...file};
const entries=manifest.entries.some(entry=>entry.path===path)?manifest.entries:[...manifest.entries,{path,format:'mjcf' as const,label:`${path} (MJCF 缓存)`}];
return {...manifest,files,entries,totalBytes:files.reduce((total,item)=>total+item.size,0)};
export function upsertCachedMjcf(
manifest: ProjectManifest,
path: string,
text: string,
): ProjectManifest {
const data = encoder.encode(text),
index = manifest.files.findIndex((candidate) => candidate.path === path);
const files = manifest.files.slice();
const file = {
path,
data,
size: data.byteLength,
source: 'file' as const,
mimeType: 'application/xml',
};
if (index < 0) files.push(file);
else files[index] = { ...files[index], ...file };
const entries = manifest.entries.some((entry) => entry.path === path)
? manifest.entries
: [...manifest.entries, { path, format: 'mjcf' as const, label: `${path} (MJCF 缓存)` }];
return {
...manifest,
files,
entries,
totalBytes: files.reduce((total, item) => total + item.size, 0),
};
}
export function updateCachedText(manifest:ProjectManifest,path:string,text:string):ProjectManifest{
const index=manifest.files.findIndex(candidate=>candidate.path===path);
if(index<0)throw new Error(`缓存中找不到文件:${path}`);
if(!isEditableSource(path))throw new Error(`不支持编辑二进制文件:${path}`);
const data=encoder.encode(text),files=manifest.files.slice();
files[index]={...files[index],data,size:data.byteLength,mimeType:files[index].mimeType||'text/plain'};
return {...manifest,files,totalBytes:files.reduce((total,file)=>total+file.size,0)};
export function updateCachedText(
manifest: ProjectManifest,
path: string,
text: string,
): ProjectManifest {
const index = manifest.files.findIndex((candidate) => candidate.path === path);
if (index < 0) throw new Error(`缓存中找不到文件:${path}`);
if (!isEditableSource(path)) throw new Error(`不支持编辑二进制文件:${path}`);
const data = encoder.encode(text),
files = manifest.files.slice();
files[index] = {
...files[index],
data,
size: data.byteLength,
mimeType: files[index].mimeType || 'text/plain',
};
return { ...manifest, files, totalBytes: files.reduce((total, file) => total + file.size, 0) };
}
export function downloadBytes(data:Uint8Array,fileName:string,mimeType='application/xml'):void{
const blob=new Blob([data as BlobPart],{type:`${mimeType};charset=utf-8`});
const url=URL.createObjectURL(blob),anchor=document.createElement('a');
anchor.href=url;anchor.download=fileName;anchor.style.display='none';document.body.append(anchor);anchor.click();anchor.remove();
setTimeout(()=>URL.revokeObjectURL(url),0);
export function downloadBytes(
data: Uint8Array,
fileName: string,
mimeType = 'application/xml',
): void {
const blob = new Blob([data as BlobPart], { type: `${mimeType};charset=utf-8` });
const url = URL.createObjectURL(blob),
anchor = document.createElement('a');
anchor.href = url;
anchor.download = fileName;
anchor.style.display = 'none';
document.body.append(anchor);
anchor.click();
anchor.remove();
setTimeout(() => URL.revokeObjectURL(url), 0);
}
export function exportedFileName(projectName:string,extension:'urdf'|'xml'):string{
const stem=projectName.replace(/\.(?:zip|xml|urdf)$/i,'').replace(/[^\p{L}\p{N}._-]+/gu,'_')||'model';
export function exportedFileName(projectName: string, extension: 'urdf' | 'xml'): string {
const stem =
projectName.replace(/\.(?:zip|xml|urdf)$/i, '').replace(/[^\p{L}\p{N}._-]+/gu, '_') || 'model';
return `${stem}.${extension}`;
}