feat(web): suggest tracks, my uploads and admin upload queue screens (M5.5)
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 3m4s
ci / ts / ts (packages + web) (push) Successful in 1m23s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 52s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m51s

- Suggest tracks: drag-and-drop of files and folders with relative
  paths, streaming SHA-256 in a Web Worker (hash-wasm), duplicate
  precheck, batch reservation and tus-js-client upload (8 MB chunks,
  4 in parallel, token refresh on 401, resume of failed files).
- My uploads: statuses, rejection reasons, withdraw, quota meter.
- Upload queue (admin): status tabs, possible-duplicate filter, batches,
  metadata and storage editing, bulk approve / reject with a reason,
  import retry, and the queue settings form.
- Sidebar section for moderators and admins; ru / en strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 17:46:18 +03:00
1 parent ca106e8eb1
commit a3f212d253
19 files changed
+2144 -14

No files matched your search

+12
View File
@@ -30,3 +30,15 @@ export function formatDuration(ms: number): string {
const s = String(total % 60).padStart(2, '0');
return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${s}` : `${m}:${s}`;
}
/** Размер файла в локали интерфейса: 734003 → «717 КБ», 2.5e9 → «2,3 ГБ». */
export function formatBytes(bytes: number, locale: string): string {
const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'] as const;
let v = Math.max(0, bytes);
let i = 0;
while (v >= 1024 && i < units.length - 1) {
v /= 1024;
i++;
}
return new Intl.NumberFormat(locale, { style: 'unit', unit: units[i], unitDisplay: 'short', maximumFractionDigits: v < 10 && i > 0 ? 1 : 0 }).format(v);
}
+6
View File
@@ -57,3 +57,9 @@ export { default as waveHappyIcon } from '@material-symbols/svg-400/rounded/sent
export { default as waveSpaIcon } from '@material-symbols/svg-400/rounded/spa.svg';
export { default as waveSunIcon } from '@material-symbols/svg-400/rounded/wb_sunny.svg';
export { default as waveWeekendIcon } from '@material-symbols/svg-400/rounded/weekend.svg';
export { default as uploadIcon } from '@material-symbols/svg-400/rounded/cloud_upload.svg';
export { default as uploadFillIcon } from '@material-symbols/svg-400/rounded/cloud_upload-fill.svg';
export { default as myUploadsIcon } from '@material-symbols/svg-400/rounded/library_add.svg';
export { default as myUploadsFillIcon } from '@material-symbols/svg-400/rounded/library_add-fill.svg';
export { default as inboxIcon } from '@material-symbols/svg-400/rounded/inbox.svg';
export { default as inboxFillIcon } from '@material-symbols/svg-400/rounded/inbox-fill.svg';