Add upload to show functionality in MediaPanel: introduce file selection for uploading media files directly to a show, enhancing the upload store to support show associations. Update translations for new upload options and UI elements.

This commit is contained in:
Leonid Pershin
2026-07-25 08:16:05 +03:00
parent 4bebe64ff0
commit 2dd8bf9724
5 changed files with 311 additions and 6 deletions
@@ -1,5 +1,7 @@
import { create } from 'zustand'
import { queryClient } from '@/shared/api/query-client'
import { addEpisode } from '@/features/admin/shows/api'
import { toast } from '@/shared/ui/toast-store'
import { listMedia, uploadMedia } from './api'
export type UploadItem = {
@@ -14,16 +16,19 @@ type UploadStore = {
active: boolean
minimized: boolean
skipped: number
enqueue: (files: File[]) => Promise<void>
enqueue: (files: File[], options?: EnqueueOptions) => Promise<void>
cancel: (id: string) => void
cancelAll: () => void
toggleMinimize: () => void
dismiss: () => void
}
/** Доп-опции загрузки: привязка загружаемых файлов к шоу (добавляются сериями после аплоада). */
export type EnqueueOptions = { showId?: string }
// Очередь и флаг живут вне React — загрузка продолжается при любой навигации.
let counter = 0
const queue: { id: string; file: File }[] = []
const queue: { id: string; file: File; showId?: string }[] = []
const controllers = new Map<string, AbortController>()
let running = false
@@ -43,9 +48,23 @@ async function pump() {
controllers.set(job.id, controller)
patch(job.id, { status: 'uploading', percent: 0 })
try {
await uploadMedia(job.file, (percent) => patch(job.id, { percent }), controller.signal)
const created = await uploadMedia(
job.file,
(percent) => patch(job.id, { percent }),
controller.signal,
)
patch(job.id, { status: 'done', percent: 100 })
void queryClient.invalidateQueries({ queryKey: ['admin', 'media'] })
// Привязка к шоу: добавляем свежий ассет серией (порядок — как в очереди).
if (job.showId) {
try {
await addEpisode(job.showId, created.id)
void queryClient.invalidateQueries({ queryKey: ['admin', 'shows'] })
} catch {
toast.error(`${job.file.name}: не удалось добавить в шоу`)
}
}
} catch (error) {
// Отмена (AbortError) — тихо: элемент уже убран из списка. Прочее — помечаем ошибкой.
if (!(error instanceof DOMException && error.name === 'AbortError'))
@@ -65,7 +84,7 @@ export const useUploadStore = create<UploadStore>((set) => ({
minimized: false,
skipped: 0,
enqueue: async (files) => {
enqueue: async (files, options) => {
// Пропускаем дубликаты: уже в библиотеке (не проваленные) и уже в текущей очереди.
let existing = new Set<string>()
try {
@@ -90,7 +109,7 @@ export const useUploadStore = create<UploadStore>((set) => ({
const newItems: UploadItem[] = toAdd.map((file) => {
const id = `u${++counter}`
queue.push({ id, file })
queue.push({ id, file, showId: options?.showId })
return { id, name: file.name, percent: 0, status: 'queued' }
})