SPB Git

spb/drive Public

SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.

JavaScript 82.7% CSS 10.6% Nunjucks 3.6% Shell 1.8% SQL 1.3%
4.2 KB · 87 lines javascript
Raw Blame History
1/**2 * ─────────────────────────────────────────────3 *  SPB Drive — Personal Cloud Drive4 * ─────────────────────────────────────────────5 *  Author  : Simon-Pierre Boucher6 *  Contact : contact@spboucher.ai7 *  File    : src/preview/router.mjs8 *  Purpose : MIME/extension → preview strategy dispatcher9 *  License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213import { OFFICE_EXTS } from './office.mjs';14import { archiveKind } from './archive.mjs';15import { EXT_LANG } from './code.mjs';1617const IMAGE_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'bmp', 'ico']);18const VIDEO_EXTS = new Set(['mp4', 'webm', 'mov', 'mkv', 'avi', 'm4v', 'mpg', 'mpeg', 'wmv', 'flv', 'ts']);19const AUDIO_EXTS = new Set(['mp3', 'wav', 'flac', 'ogg', 'oga', 'm4a', 'aac', 'opus', 'wma', 'aiff', 'aif']);20const FONT_EXTS = new Set(['ttf', 'otf', 'woff', 'woff2']);21const STRUCTURED_EXTS = new Set(['json', 'yaml', 'yml', 'toml', 'xml', 'plist']);22const MODEL3D_EXTS = new Set(['stl', 'glb', 'gltf', 'obj']);2324/** Lowercased extension without the dot ('' if none). */25export function extOf(name) {26  const base = String(name).toLowerCase();27  const special = base.endsWith('.tar.gz') ? 'tar.gz' : null;28  if (special) return special;29  const dot = base.lastIndexOf('.');30  return dot > 0 ? base.slice(dot + 1) : '';31}3233/**34 * Decide how a file previews.35 * @param {{name: string, mime?: string, size?: number}} node36 * @returns {{strategy: string, ext: string}}37 */38export function previewStrategy(node) {39  const name = node.name ?? '';40  const mime = node.mime ?? '';41  const ext = extOf(name);4243  if (ext === 'svg' || mime === 'image/svg+xml') return { strategy: 'svg', ext };44  if (ext === 'heic' || ext === 'heif') return { strategy: 'heic', ext };45  if (IMAGE_EXTS.has(ext) || mime.startsWith('image/')) return { strategy: 'image', ext };46  if (VIDEO_EXTS.has(ext) || mime.startsWith('video/')) return { strategy: 'video', ext };47  if (AUDIO_EXTS.has(ext) || mime.startsWith('audio/')) return { strategy: 'audio', ext };48  if (ext === 'pdf' || mime === 'application/pdf') return { strategy: 'pdf', ext };49  if (OFFICE_EXTS.has(ext)) return { strategy: 'office', ext };50  if (ext === 'md' || ext === 'markdown') return { strategy: 'markdown', ext };51  if (ext === 'csv' || ext === 'tsv') return { strategy: 'csv', ext };52  if (ext === 'ipynb') return { strategy: 'notebook', ext };53  if (STRUCTURED_EXTS.has(ext)) return { strategy: 'structured', ext };54  if (archiveKind(name)) return { strategy: 'archive', ext };55  if (FONT_EXTS.has(ext)) return { strategy: 'font', ext };56  if (ext === 'eml') return { strategy: 'email', ext };57  if (ext === 'epub') return { strategy: 'epub', ext };58  if (MODEL3D_EXTS.has(ext)) return { strategy: 'model3d', ext };59  if (ext === 'html' || ext === 'htm') return { strategy: 'code', ext };60  if (EXT_LANG[ext] || mime.startsWith('text/')) return { strategy: 'code', ext };61  return { strategy: 'fallback', ext };62}6364/** Which thumbnail pipeline (if any) applies to a file. */65export function thumbKind(node) {66  const { strategy } = previewStrategy(node);67  if (['image', 'heic'].includes(strategy)) return 'image';68  if (strategy === 'video') return 'video';69  if (strategy === 'pdf') return 'pdf';70  if (strategy === 'office') return 'office'; // via converted PDF when cached71  return null;72}7374/** Icon family for the crisp per-extension SVG icon set. */75export function iconFamily(node) {76  if (node.type === 'folder') return 'folder';77  const { strategy, ext } = previewStrategy(node);78  const map = {79    image: 'image', svg: 'image', heic: 'image', video: 'video', audio: 'audio',80    pdf: 'pdf', office: /x?ls|ods/.test(ext) ? 'sheet' : /pptx?|odp/.test(ext) ? 'slides' : 'doc',81    markdown: 'text', csv: 'sheet', notebook: 'code', structured: 'code',82    archive: 'archive', font: 'font', email: 'mail', epub: 'book',83    model3d: 'cube', code: 'code', fallback: 'file',84  };85  return map[strategy] ?? 'file';86}87