Reverted to commit 85969433e6
This commit is contained in:
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* Cliente leve para instâncias FileBrowser (https://filebrowser.org).
|
||||
* Suporta NoAuth (POST /api/login vazio) e listagem de pastas via /api/resources.
|
||||
*/
|
||||
|
||||
export const SUPPORTED_EXT = ['glb', 'obj', 'stl', 'ifc'] as const;
|
||||
export type SupportedFolderExt = (typeof SUPPORTED_EXT)[number];
|
||||
|
||||
export interface FBItem {
|
||||
name: string;
|
||||
isDir: boolean;
|
||||
size: number;
|
||||
extension: string; // ".ifc" (com ponto) — normalizamos
|
||||
modified?: string;
|
||||
}
|
||||
|
||||
export interface FolderListing {
|
||||
origin: string;
|
||||
/** Caminho da pasta sempre começando e terminando com "/" (ex.: "/modelos/aco/") */
|
||||
path: string;
|
||||
token: string | null;
|
||||
items: FBItem[];
|
||||
}
|
||||
|
||||
/** Tenta obter um JWT de uma instância FileBrowser NoAuth. */
|
||||
export async function getFileBrowserToken(origin: string): Promise<string | null> {
|
||||
try {
|
||||
const r = await fetch(`${origin}/api/login`, { method: 'POST', mode: 'cors' });
|
||||
if (!r.ok) return null;
|
||||
const txt = (await r.text()).trim();
|
||||
if (txt && txt.split('.').length === 3) return txt;
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Garante "/" inicial e final. */
|
||||
function normalizeFolderPath(p: string): string {
|
||||
let path = p || '/';
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (!path.endsWith('/')) path = path + '/';
|
||||
return path;
|
||||
}
|
||||
|
||||
/** Lista o conteúdo de uma pasta a partir de uma URL completa OU de origin+path. */
|
||||
export async function listFolder(input: { folderUrl?: string; origin?: string; path?: string }): Promise<FolderListing> {
|
||||
let origin: string;
|
||||
let path: string;
|
||||
|
||||
if (input.folderUrl) {
|
||||
const u = new URL(input.folderUrl);
|
||||
origin = u.origin;
|
||||
path = normalizeFolderPath(decodeURIComponent(u.pathname));
|
||||
} else {
|
||||
if (!input.origin) throw new Error('origin obrigatório');
|
||||
origin = input.origin;
|
||||
path = normalizeFolderPath(input.path || '/');
|
||||
}
|
||||
|
||||
const token = await getFileBrowserToken(origin);
|
||||
const auth = token ? `?auth=${encodeURIComponent(token)}` : '';
|
||||
// FileBrowser API: GET /api/resources/<path>
|
||||
const apiUrl = `${origin}/api/resources${path}${auth}`;
|
||||
|
||||
const res = await fetch(apiUrl, { mode: 'cors' });
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Não foi possível listar a pasta (HTTP ${res.status}). ` +
|
||||
`Verifique se a URL é de uma instância FileBrowser e se a pasta é pública.`
|
||||
);
|
||||
}
|
||||
const ct = res.headers.get('content-type') || '';
|
||||
if (!ct.includes('application/json')) {
|
||||
throw new Error('Endpoint não retornou JSON — provavelmente não é um servidor FileBrowser.');
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
const rawItems: any[] = Array.isArray(json?.items) ? json.items : [];
|
||||
const items: FBItem[] = rawItems.map((it) => ({
|
||||
name: String(it.name ?? ''),
|
||||
isDir: Boolean(it.isDir),
|
||||
size: Number(it.size ?? 0),
|
||||
extension: String(it.extension ?? '').toLowerCase(),
|
||||
modified: it.modified,
|
||||
}));
|
||||
|
||||
return { origin, path, token, items };
|
||||
}
|
||||
|
||||
/** Monta a URL /api/raw para download direto de um arquivo. */
|
||||
export function buildRawUrl(origin: string, path: string, name: string, token: string | null): string {
|
||||
const folder = normalizeFolderPath(path);
|
||||
const auth = token ? `?auth=${encodeURIComponent(token)}` : '';
|
||||
return `${origin}/api/raw${folder}${encodeURIComponent(name)}${auth}`;
|
||||
}
|
||||
|
||||
/** True se o item for arquivo 3D suportado. */
|
||||
export function isSupportedModel(item: FBItem): boolean {
|
||||
if (item.isDir) return false;
|
||||
const ext = item.extension.replace(/^\./, '').toLowerCase();
|
||||
return (SUPPORTED_EXT as readonly string[]).includes(ext);
|
||||
}
|
||||
|
||||
/** Sobe um nível na hierarquia de pastas. Retorna "/" para a raiz. */
|
||||
export function parentPath(path: string): string {
|
||||
const norm = normalizeFolderPath(path);
|
||||
if (norm === '/') return '/';
|
||||
const trimmed = norm.slice(0, -1); // remove trailing /
|
||||
const idx = trimmed.lastIndexOf('/');
|
||||
return idx <= 0 ? '/' : trimmed.slice(0, idx + 1);
|
||||
}
|
||||
|
||||
/** Quebra "/a/b/c/" em [{name:'a',path:'/a/'},{name:'b',path:'/a/b/'},...] */
|
||||
export function breadcrumbs(path: string): { name: string; path: string }[] {
|
||||
const norm = normalizeFolderPath(path);
|
||||
if (norm === '/') return [];
|
||||
const parts = norm.split('/').filter(Boolean);
|
||||
const out: { name: string; path: string }[] = [];
|
||||
let acc = '';
|
||||
for (const p of parts) {
|
||||
acc += '/' + p;
|
||||
out.push({ name: p, path: acc + '/' });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function formatBytes(n: number): string {
|
||||
if (!n || n < 1024) return `${n} B`;
|
||||
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
||||
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
||||
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
||||
}
|
||||
Reference in New Issue
Block a user