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%
10.7 KB · 104 lines javascript
Raw Blame History
1/**2 * ─────────────────────────────────────────────3 *  SPB Drive — Personal Cloud Drive4 * ─────────────────────────────────────────────5 *  Author  : Simon-Pierre Boucher6 *  Contact : contact@spboucher.ai7 *  File    : src/web/assets/js/icons.js8 *  Purpose : Crisp custom SVG icon set — file families + UI glyphs9 *  License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213const S = 'fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"';14const filePath = '<path d="M6 2.5h7.5L19 8v13a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 5 21V4A1.5 1.5 0 0 1 6.5 2.5Z"/><path d="M13.5 2.5V8H19"/>';1516/** File-family icons, color-coded per family. */17const FAMILY = {18  folder: { color: 'var(--folder-blue)', svg: `<path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h4.6l2 2.4h8.4A1.5 1.5 0 0 1 21 8.9V18a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18Z" fill="currentColor" stroke="none"/>` },19  image: { color: '#22d3aa', svg: `${filePath}<circle cx="9.4" cy="11.5" r="1.4"/><path d="m6.5 18.5 3.4-3.7 2.3 2.2 2.6-3.2 2.7 4.7"/>` },20  video: { color: '#b48cff', svg: `${filePath}<path d="m10 11.5 4.5 2.7-4.5 2.7Z" fill="currentColor"/>` },21  audio: { color: '#ff7ab8', svg: `${filePath}<path d="M9.5 17.5v-5l5-1.4v5"/><circle cx="8.2" cy="17.6" r="1.3"/><circle cx="13.2" cy="16.2" r="1.3"/>` },22  pdf: { color: '#ff5d5d', svg: `${filePath}<path d="M7.5 17.5v-5h1.6a1.5 1.5 0 0 1 0 3H7.5m5-3v5m0-5h1.3a1.7 2 0 0 1 0 4h-.3m3.5 1v-5h2.5m-2.5 2.6h2"/>` },23  doc: { color: '#4f8cff', svg: `${filePath}<path d="M8 12.5h8M8 15.5h8M8 18.5h5"/>` },24  sheet: { color: '#7bd88f', svg: `${filePath}<path d="M8 12h8v7H8Zm0 2.3h8m-8 2.3h8M11 12v7"/>` },25  slides: { color: '#ff9f5a', svg: `${filePath}<rect x="7.5" y="12" width="9" height="5.5" rx="0.8"/><path d="M12 17.5v2"/>` },26  code: { color: '#ffd166', svg: `${filePath}<path d="m9.5 12.5-2.4 2.7 2.4 2.7m5-5.4 2.4 2.7-2.4 2.7"/>` },27  text: { color: '#8b93a3', svg: `${filePath}<path d="M8 12.5h8M8 15.5h8M8 18.5h4"/>` },28  archive: { color: '#ffb454', svg: `${filePath}<path d="M11 5h2m-2 2.5h2M11 10h2m-1.5 3h1a1 1 0 0 1 1 1v2.2a1.5 1.5 0 1 1-2 0V14a1 1 0 0 1 1-1Z"/>` },29  font: { color: '#e6e9ef', svg: `${filePath}<path d="m8.5 18.5 3-8 3 8m-5-2.3h4"/>` },30  mail: { color: '#4f8cff', svg: `${filePath}<rect x="7" y="12" width="10" height="7" rx="1"/><path d="m7.5 12.8 4.5 3.4 4.5-3.4"/>` },31  book: { color: '#22d3aa', svg: `${filePath}<path d="M8 19a2 2 0 0 1 2-2h6V11H10a2 2 0 0 0-2 2Z"/>` },32  cube: { color: '#b48cff', svg: `${filePath}<path d="m12 11 4 2v4.5l-4 2-4-2V13Zm-4 2 4 2 4-2m-4 2v4.5"/>` },33  file: { color: '#8b93a3', svg: filePath },34};3536/** Render a file-family icon (class type-icon for sizing). */37export function fileIcon(family, cls = '') {38  const f = FAMILY[family] ?? FAMILY.file;39  return `<svg class="type-icon ${cls}" viewBox="0 0 24 24" ${S} style="color:${f.color}" aria-hidden="true">${f.svg}</svg>`;40}4142/** Folder icon honoring per-folder color + emoji override. */43export function folderIcon(node, cls = '') {44  if (node?.emoji) return `<span class="${cls}" style="font-size:1.15em;line-height:1" aria-hidden="true">${node.emoji}</span>`;45  const color = node?.color ? `var(--folder-${node.color}, ${node.color})` : 'var(--folder-blue)';46  return `<svg class="type-icon ${cls}" viewBox="0 0 24 24" style="color:${color}" aria-hidden="true">${FAMILY.folder.svg}</svg>`;47}4849export function nodeIcon(node, cls = '') {50  return node.type === 'folder' ? folderIcon(node, cls) : fileIcon(node.icon, cls);51}5253/** UI glyphs. */54export const UI = {55  chevron: `<svg viewBox="0 0 24 24" ${S}><path d="m9 6 6 6-6 6"/></svg>`,56  chevronL: `<svg viewBox="0 0 24 24" ${S}><path d="m15 6-6 6 6 6"/></svg>`,57  star: `<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="m12 3 2.7 5.6 6.1.8-4.5 4.3 1.1 6-5.4-2.9-5.4 2.9 1.1-6L3.2 9.4l6.1-.8Z"/></svg>`,58  starO: `<svg viewBox="0 0 24 24" ${S}><path d="m12 3 2.7 5.6 6.1.8-4.5 4.3 1.1 6-5.4-2.9-5.4 2.9 1.1-6L3.2 9.4l6.1-.8Z"/></svg>`,59  download: `<svg viewBox="0 0 24 24" ${S}><path d="M12 4v12m0 0 5-5m-5 5-5-5M4 19h16"/></svg>`,60  share: `<svg viewBox="0 0 24 24" ${S}><circle cx="6" cy="12" r="2.5"/><circle cx="17" cy="6" r="2.5"/><circle cx="17" cy="18" r="2.5"/><path d="m8.3 10.8 6.4-3.6m-6.4 6 6.4 3.6"/></svg>`,61  trash: `<svg viewBox="0 0 24 24" ${S}><path d="M4 7h16m-2 0v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7m3 0V4.5A1.5 1.5 0 0 1 10.5 3h3A1.5 1.5 0 0 1 15 4.5V7m-5 4v6m4-6v6"/></svg>`,62  restore: `<svg viewBox="0 0 24 24" ${S}><path d="M4 10a8 8 0 1 1 2.3 6.3M4 10V5m0 5h5"/></svg>`,63  rename: `<svg viewBox="0 0 24 24" ${S}><path d="m14 5 5 5L8 21H3v-5Zm-3 3 5 5"/></svg>`,64  copy: `<svg viewBox="0 0 24 24" ${S}><rect x="9" y="9" width="12" height="12" rx="1.5"/><path d="M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"/></svg>`,65  move: `<svg viewBox="0 0 24 24" ${S}><path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h4.6l2 2.4h8.4A1.5 1.5 0 0 1 21 8.9V18a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18Z"/><path d="M12 10.5v6m0 0 2.5-2.5M12 16.5 9.5 14"/></svg>`,66  info: `<svg viewBox="0 0 24 24" ${S}><circle cx="12" cy="12" r="9"/><path d="M12 11v5m0-8v.5"/></svg>`,67  close: `<svg viewBox="0 0 24 24" ${S}><path d="m6 6 12 12M18 6 6 18"/></svg>`,68  plus: `<svg viewBox="0 0 24 24" ${S}><path d="M12 5v14M5 12h14"/></svg>`,69  grid: `<svg viewBox="0 0 24 24" ${S}><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>`,70  listv: `<svg viewBox="0 0 24 24" ${S}><path d="M8 6h13M8 12h13M8 18h13M3.5 6h.5m-.5 6h.5m-.5 6h.5"/></svg>`,71  sun: `<svg viewBox="0 0 24 24" ${S}><circle cx="12" cy="12" r="4.5"/><path d="M12 2.5v2m0 15v2m9.5-9.5h-2m-15 0h-2m16-6.7-1.4 1.4M6.9 17.1l-1.4 1.4m0-13.4 1.4 1.4m10.2 10.2 1.4 1.4"/></svg>`,72  moon: `<svg viewBox="0 0 24 24" ${S}><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5Z"/></svg>`,73  gear: `<svg viewBox="0 0 24 24" ${S}><circle cx="12" cy="12" r="3"/><path d="M12 2.8 13.5 5h2.6l.9 2.4 2.3 1.3-.4 2.6 1.6 2-1.6 2 .4 2.6-2.3 1.3-.9 2.4h-2.6L12 21.2 10.5 19H7.9L7 16.6l-2.3-1.3.4-2.6-1.6-2 1.6-2L4.7 6l2.3-1.3L7.9 5h2.6Z"/></svg>`,74  menu: `<svg viewBox="0 0 24 24" ${S}><path d="M4 7h16M4 12h16M4 17h16"/></svg>`,75  upload: `<svg viewBox="0 0 24 24" ${S}><path d="M12 16V4m0 0 5 5m-5-5-5 5M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg>`,76  search: `<svg viewBox="0 0 24 24" ${S}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>`,77  clock: `<svg viewBox="0 0 24 24" ${S}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg>`,78  tag: `<svg viewBox="0 0 24 24" ${S}><path d="m3.5 12.5 8-8H20v8.5l-8 8a1.4 1.4 0 0 1-2 0l-6.5-6.5a1.4 1.4 0 0 1 0-2Z"/><circle cx="15.5" cy="8.5" r="1.2"/></svg>`,79  activity: `<svg viewBox="0 0 24 24" ${S}><path d="M3 12h4l2.5-7 5 14 2.5-7h4"/></svg>`,80  link: `<svg viewBox="0 0 24 24" ${S}><path d="M10 14a4.5 4.5 0 0 0 6.4.4l3-3a4.5 4.5 0 1 0-6.4-6.3l-1.3 1.2"/><path d="M14 10a4.5 4.5 0 0 0-6.4-.4l-3 3a4.5 4.5 0 1 0 6.4 6.3l1.3-1.2"/></svg>`,81  check: `<svg viewBox="0 0 24 24" ${S}><path d="m5 13 4.5 4.5L19 7"/></svg>`,82  dots: `<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg>`,83  duplicate: `<svg viewBox="0 0 24 24" ${S}><rect x="8" y="8" width="12" height="12" rx="1.5"/><path d="M16 4H5.5A1.5 1.5 0 0 0 4 5.5V16"/></svg>`,84  qr: `<svg viewBox="0 0 24 24" ${S}><rect x="4" y="4" width="6" height="6"/><rect x="14" y="4" width="6" height="6"/><rect x="4" y="14" width="6" height="6"/><path d="M14 14h2.5v2.5H14Zm3.5 3.5H20V20h-2.5Zm0-3.5H20m-6 6h2.5"/></svg>`,85  eye: `<svg viewBox="0 0 24 24" ${S}><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="2.8"/></svg>`,86  zoomIn: `<svg viewBox="0 0 24 24" ${S}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5M8 11h6m-3-3v6"/></svg>`,87  zoomOut: `<svg viewBox="0 0 24 24" ${S}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5M8 11h6"/></svg>`,88  rotate: `<svg viewBox="0 0 24 24" ${S}><path d="M20 11a8 8 0 1 0-2 5.3M20 5v6h-6"/></svg>`,89  expand: `<svg viewBox="0 0 24 24" ${S}><path d="M4 9V4h5m11 5V4h-5M4 15v5h5m11-5v5h-5"/></svg>`,90  folderNew: `<svg viewBox="0 0 24 24" ${S}><path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h4.6l2 2.4h8.4A1.5 1.5 0 0 1 21 8.9V18a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18Z"/><path d="M12 10.5v6m-3-3h6"/></svg>`,91  logout: `<svg viewBox="0 0 24 24" ${S}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4m7 13 5-4-5-4m5 4H9"/></svg>`,92  edit: `<svg viewBox="0 0 24 24" ${S}><path d="M12 20h8M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4Z"/></svg>`,93  history: `<svg viewBox="0 0 24 24" ${S}><path d="M4 10a8 8 0 1 1 2.3 6.3M4 10V5m0 5h5"/><path d="M12 8.5V12l2.5 1.7"/></svg>`,94  inbox: `<svg viewBox="0 0 24 24" ${S}><path d="M3 13h5l1.5 2.5h5L16 13h5"/><path d="M5 5h14a1.5 1.5 0 0 1 1.5 1.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V6.5A1.5 1.5 0 0 1 5 5Z"/><path d="M12 8v3m0 0 2-2m-2 2-2-2"/></svg>`,95  storage: `<svg viewBox="0 0 24 24" ${S}><ellipse cx="12" cy="5.5" rx="7.5" ry="2.8"/><path d="M4.5 5.5V12c0 1.5 3.4 2.8 7.5 2.8s7.5-1.3 7.5-2.8V5.5"/><path d="M4.5 12v6.5c0 1.5 3.4 2.8 7.5 2.8s7.5-1.3 7.5-2.8V12"/></svg>`,96};9798/** Empty-state illustrations (custom minimal SVG art). */99export const EMPTY_ART = {100  folder: `<svg viewBox="0 0 160 120" fill="none"><rect x="30" y="34" width="100" height="66" rx="8" stroke="var(--border)" stroke-width="2.5" fill="var(--surface)"/><path d="M30 44a8 8 0 0 1 8-8h22l8 9h54a8 8 0 0 1 8 8" stroke="var(--accent)" stroke-width="2.5"/><circle cx="80" cy="72" r="13" stroke="var(--muted)" stroke-width="2.5" stroke-dasharray="4 5"/><path d="M80 66v12m-6-6h12" stroke="var(--muted)" stroke-width="2.5" stroke-linecap="round"/></svg>`,101  trash: `<svg viewBox="0 0 160 120" fill="none"><path d="M55 40h50l-4 58a8 8 0 0 1-8 7H67a8 8 0 0 1-8-7Z" stroke="var(--border)" stroke-width="2.5" fill="var(--surface)"/><path d="M48 40h64M70 40v-6a6 6 0 0 1 6-6h8a6 6 0 0 1 6 6v6" stroke="var(--accent-2)" stroke-width="2.5" stroke-linecap="round"/><path d="m70 56 20 32m0-32-20 32" stroke="var(--muted)" stroke-width="2.5" stroke-linecap="round" opacity="0.5"/></svg>`,102  search: `<svg viewBox="0 0 160 120" fill="none"><circle cx="72" cy="56" r="26" stroke="var(--accent)" stroke-width="2.5" fill="var(--surface)"/><path d="m92 76 18 18" stroke="var(--accent)" stroke-width="2.5" stroke-linecap="round"/><path d="M62 56h20m-16-8h12m-14 16h16" stroke="var(--muted)" stroke-width="2.5" stroke-linecap="round" opacity="0.6"/></svg>`,103};104