archiveShared.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. export const ARCHIVE_EXTENSIONS = [
  2. 'zip', 'zipx', '7z', 'rar', 'tar', 'gz', 'gzip', 'tgz', 'bz2', 'bzip2', 'tbz', 'tbz2',
  3. 'xz', 'txz', 'lzma', 'zst', 'tzst', 'cab', 'ar', 'cpio', 'iso', 'xar', 'lha', 'lzh',
  4. 'jar', 'war', 'ear', 'apk', 'cbz', 'cbr'
  5. ]
  6. export const ARCHIVE_PREVIEWABLE_EXTENSIONS = [
  7. 'doc', 'docx', 'docm', 'dot', 'dotx', 'dotm', 'xls', 'xlsx', 'xlsm', 'xlsb',
  8. 'xlt', 'xltx', 'xltm', 'csv', 'ods', 'fods', 'numbers', 'pptx', 'pptm',
  9. 'potx', 'potm', 'ppsx', 'ppsm', 'rtf', 'odt', 'odp', 'pdf', 'ofd', 'typ', 'typst', 'dxf', 'dwg', 'dwf', 'dwfx', 'xps',
  10. 'gltf', 'glb', 'obj', 'stl', 'ply', 'fbx', 'dae', '3ds',
  11. '3mf', 'amf', 'usd', 'usda', 'usdc', 'usdz', 'kmz', 'geojson', 'kml', 'gpx', 'shp', 'pcd', 'wrl', 'vrml', 'xyz', 'vtk',
  12. 'vtp', 'step', 'stp', 'iges', 'igs', 'ifc', '3dm', 'excalidraw', 'drawio', 'dio',
  13. 'epub', 'umd', 'gif', 'jpg', 'jpeg', 'bmp', 'tiff', 'tif', 'png', 'svg', 'webp', 'avif', 'ico', 'heic', 'heif', 'jxl', 'md',
  14. 'markdown', 'txt', 'json', 'jsonc', 'json5', 'ipynb', 'toml', 'proto', 'hcl', 'tex', 'gv', 'http', 'js', 'mjs', 'cjs', 'css', 'java', 'py', 'html', 'htm', 'jsx',
  15. 'ts', 'tsx', 'xml', 'log', 'vue', 'yaml', 'yml', 'ini', 'sh', 'bash', 'sql', 'go', 'rs',
  16. 'php', 'c', 'cpp', 'cc', 'h', 'hpp', 'cs', 'diff', 'react', 'rb', 'swift', 'kt', 'mp4', 'webm', 'm3u8', 'mp3', 'mpeg', 'wav', 'ogg',
  17. 'oga', 'opus', 'm4a', 'aac', 'flac', 'weba', 'midi', 'mid', 'eml', 'msg', 'mbox', 'olb', 'dra',
  18. 'ttf', 'otf', 'woff', 'woff2', 'psd', 'ai', 'eps', 'sqlite', 'wasm', 'parquet', 'avro', 'webarchive',
  19. ...ARCHIVE_EXTENSIONS
  20. ]
  21. export interface ArchiveEntryView {
  22. id: string;
  23. path: string;
  24. name: string;
  25. extension: string;
  26. size: number;
  27. lastModified?: number;
  28. depth: number;
  29. previewable: boolean;
  30. compressedFile: {
  31. name: string;
  32. size: number;
  33. lastModified?: number;
  34. extract(): Promise<File>;
  35. };
  36. }
  37. export const getExtension = (name: string) => {
  38. const clean = name.split(/[?#]/)[0] || name
  39. const dot = clean.lastIndexOf('.')
  40. return dot === -1 ? '' : clean.slice(dot + 1).toLowerCase()
  41. }
  42. export const isArchiveExtension = (extension: string) => ARCHIVE_EXTENSIONS.includes(extension.toLowerCase())
  43. export const isPreviewableArchiveEntry = (name: string) => {
  44. const extension = getExtension(name)
  45. return ARCHIVE_PREVIEWABLE_EXTENSIONS.includes(extension)
  46. }
  47. export const formatBytes = (value: number) => {
  48. if (!Number.isFinite(value) || value < 0) {
  49. return '-'
  50. }
  51. if (value < 1024) {
  52. return `${value} B`
  53. }
  54. const units = ['KB', 'MB', 'GB']
  55. let next = value / 1024
  56. for (const unit of units) {
  57. if (next < 1024 || unit === units[units.length - 1]) {
  58. return `${next.toFixed(next < 10 ? 1 : 0)} ${unit}`
  59. }
  60. next /= 1024
  61. }
  62. return `${value} B`
  63. }
  64. const isCompressedFile = (value: unknown): value is ArchiveEntryView['compressedFile'] => {
  65. return typeof value === 'object' &&
  66. value !== null &&
  67. 'extract' in value &&
  68. typeof value.extract === 'function'
  69. }
  70. export const flattenArchiveObject = (input: Record<string, unknown>, prefix = ''): ArchiveEntryView[] => {
  71. const entries: ArchiveEntryView[] = []
  72. Object.entries(input).forEach(([key, value]) => {
  73. const path = prefix ? `${prefix}/${key}` : key
  74. if (isCompressedFile(value)) {
  75. const name = value.name || key
  76. const extension = getExtension(name)
  77. entries.push({
  78. id: path,
  79. path,
  80. name,
  81. extension,
  82. size: value.size || 0,
  83. lastModified: value.lastModified,
  84. depth: path.split('/').length - 1,
  85. previewable: isPreviewableArchiveEntry(name),
  86. compressedFile: value
  87. })
  88. return
  89. }
  90. if (value && typeof value === 'object') {
  91. entries.push(...flattenArchiveObject(value as Record<string, unknown>, path))
  92. }
  93. })
  94. return entries
  95. }
  96. export const createArchiveCacheKey = (archiveName: string, archiveSize: number, entry: ArchiveEntryView) => {
  97. return [
  98. 'archive-entry',
  99. archiveName || 'archive',
  100. archiveSize,
  101. entry.path,
  102. entry.size,
  103. entry.lastModified || 0
  104. ].join(':')
  105. }