diff --git a/frontend/src/features/admin/media/ManualInboxPanel.tsx b/frontend/src/features/admin/media/ManualInboxPanel.tsx index 803df34..caf0d5a 100644 --- a/frontend/src/features/admin/media/ManualInboxPanel.tsx +++ b/frontend/src/features/admin/media/ManualInboxPanel.tsx @@ -227,7 +227,7 @@ export function ManualInboxPanel({ onClose }: Readonly<{ onClose: () => void }>) <>

{t('admin.media.manualHint')}

-
+
{/* Шоу — первое решение в этом диалоге: остальные поля лишь помогают разложить файлы. */}
@@ -345,7 +345,7 @@ export function ManualInboxPanel({ onClose }: Readonly<{ onClose: () => void }>)
-
+
{isLoading &&

{t('common.loading')}

} {!isLoading && folders.length === 0 && (

{t('admin.media.manualEmpty')}

diff --git a/frontend/src/features/admin/media/MediaImportDialog.tsx b/frontend/src/features/admin/media/MediaImportDialog.tsx index a9156df..316efaf 100644 --- a/frontend/src/features/admin/media/MediaImportDialog.tsx +++ b/frontend/src/features/admin/media/MediaImportDialog.tsx @@ -11,7 +11,7 @@ import { ManualInboxPanel } from './ManualInboxPanel' import { MovieImportPanel } from './MovieImportPanel' import { UploadToShowPanel } from './UploadToShowPanel' -/** Способы пополнить библиотеку. «В шоу» появляется только когда файлы уже выбраны. */ +/** Способы пополнить библиотеку: фильмы пачкой, серии из manual/, загрузка файлов в шоу. */ type Tab = 'movies' | 'manual' | 'toShow' /** @@ -19,22 +19,17 @@ type Tab = 'movies' | 'manual' | 'toShow' * выбранных файлов в шоу. Раньше это были три кнопки и три окна — а выбор между ними делается * один раз и по одному признаку: что за контент кладём. */ -export function MediaImportDialog({ - files, - onClose, -}: Readonly<{ - /** Файлы, выбранные в проводнике до открытия окна: тогда сразу открывается вкладка «в шоу». */ - files: File[] | null - onClose: () => void -}>) { +export function MediaImportDialog({ onClose }: Readonly<{ onClose: () => void }>) { const { t } = useTranslation() - const [tab, setTab] = useState(files ? 'toShow' : 'movies') + const [tab, setTab] = useState('movies') - const tabs: Tab[] = files ? ['toShow', 'movies', 'manual'] : ['movies', 'manual'] + const tabs: Tab[] = ['movies', 'manual', 'toShow'] return ( !open && onClose()}> - + {/* Высота фиксирована: вкладки разного роста, и окно, прыгающее при переключении, + сбивает — то, на что метился курсор, уезжает из-под него. */} + {t('admin.media.importTitle')} {t('admin.media.importHint')} @@ -57,9 +52,11 @@ export function MediaImportDialog({ ))}
- {tab === 'movies' && } - {tab === 'manual' && } - {tab === 'toShow' && files && } +
+ {tab === 'movies' && } + {tab === 'manual' && } + {tab === 'toShow' && } +
) diff --git a/frontend/src/features/admin/media/MediaPanel.tsx b/frontend/src/features/admin/media/MediaPanel.tsx index 9dde719..3b8c055 100644 --- a/frontend/src/features/admin/media/MediaPanel.tsx +++ b/frontend/src/features/admin/media/MediaPanel.tsx @@ -41,12 +41,9 @@ export function MediaPanel() { const { t } = useTranslation() const queryClient = useQueryClient() const fileInput = useRef(null) - const fileInputShow = useRef(null) const [filter, setFilter] = useState('active') const [page, setPage] = useState(1) const { sort, toggle } = useTableSort('created', true) - // Файлы, выбранные для загрузки в шоу: с ними окно импорта открывается сразу на нужной вкладке. - const [filesForShow, setFilesForShow] = useState(null) const [importOpen, setImportOpen] = useState(false) const enqueue = useUploadStore((s) => s.enqueue) @@ -170,21 +167,6 @@ export function MediaPanel() { e.target.value = '' }} /> - { - const files = e.target.files - if (files && files.length > 0) { - setFilesForShow(Array.from(files)) - setImportOpen(true) - } - e.target.value = '' - }} - />
- {importOpen && ( - { - setImportOpen(false) - setFilesForShow(null) - }} - /> - )} + {importOpen && setImportOpen(false)} />}
diff --git a/frontend/src/features/admin/media/MovieImportPanel.tsx b/frontend/src/features/admin/media/MovieImportPanel.tsx index 2ffc1f8..4f98627 100644 --- a/frontend/src/features/admin/media/MovieImportPanel.tsx +++ b/frontend/src/features/admin/media/MovieImportPanel.tsx @@ -257,7 +257,7 @@ export function MovieImportPanel({ onClose }: Readonly<{ onClose: () => void }>) )} -
+
diff --git a/frontend/src/features/admin/media/UploadToShowPanel.tsx b/frontend/src/features/admin/media/UploadToShowPanel.tsx index de9154a..5a09241 100644 --- a/frontend/src/features/admin/media/UploadToShowPanel.tsx +++ b/frontend/src/features/admin/media/UploadToShowPanel.tsx @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query' -import { useMemo, useState } from 'react' +import { Upload } from 'lucide-react' +import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { qk } from '@/shared/api/query-keys' import { Badge } from '@/shared/ui/badge' @@ -13,12 +14,16 @@ import { compareParsed, formatSeasonEpisode, isValidRegex, parseEpisodeName } fr import { matchShowByName } from './match-show' import { useUploadStore } from './upload-store' -export function UploadToShowPanel({ - files, - onClose, -}: Readonly<{ files: File[]; onClose: () => void }>) { +/** + * Загрузка выбранных файлов в шоу: сезон и номера серий разбираются здесь же, а сама заливка идёт + * очередью с прогрессом. Файлы выбираются в самой вкладке — окно открывается кнопкой «Импорт», + * и требовать выбрать файлы **до** него значило бы прятать способ за проводником. + */ +export function UploadToShowPanel({ onClose }: Readonly<{ onClose: () => void }>) { const { t } = useTranslation() const enqueue = useUploadStore((s) => s.enqueue) + const [files, setFiles] = useState([]) + const fileInput = useRef(null) const [seasonStr, setSeasonStr] = useState('') const [regexStr, setRegexStr] = useState('') // Ручные правки привязки: имя файла → id шоу ('' — явно в библиотеку). Иначе берётся автоопределение. @@ -70,7 +75,31 @@ export function UploadToShowPanel({ <>

{t('admin.media.autoDetectHint')}

-
+
+ { + setFiles([...(e.target.files ?? [])]) + setOverrides({}) + e.target.value = '' + }} + /> + + + {files.length === 0 + ? t('admin.media.toShowEmpty') + : t('admin.media.toShowPicked', { count: files.length })} + +
+ +
@@ -117,7 +146,7 @@ export function UploadToShowPanel({ />
-
    +
      {previews.map((p) => { const label = formatSeasonEpisode(p.parsed) const current = assignment(p.name) @@ -148,7 +177,7 @@ export function UploadToShowPanel({ - diff --git a/frontend/src/shared/lib/locales/en.ts b/frontend/src/shared/lib/locales/en.ts index 7de41b7..78fb597 100644 --- a/frontend/src/shared/lib/locales/en.ts +++ b/frontend/src/shared/lib/locales/en.ts @@ -318,6 +318,9 @@ export const en = { manualImported: 'Files imported: {{count}}', uploadToShow: 'Upload to show', toShowTitle: 'Upload and add to show', + toShowPick: 'Pick files', + toShowEmpty: 'No files picked', + toShowPicked: 'Files picked: {{count}}', autoDetectHint: 'Each file is linked to the show whose original (or display) name appears in the release name, e.g. “The.Simpsons.S33E01…” → The Simpsons.', toShowLibrary: 'To library', diff --git a/frontend/src/shared/lib/locales/ru.ts b/frontend/src/shared/lib/locales/ru.ts index ba368bf..5026acf 100644 --- a/frontend/src/shared/lib/locales/ru.ts +++ b/frontend/src/shared/lib/locales/ru.ts @@ -283,7 +283,7 @@ export const ru = { importTitle: 'Импорт медиа', importHint: 'Три способа пополнить библиотеку: фильмы пачкой, серии из папки manual в шоу и загрузка выбранных файлов в шоу.', - importTabs: { movies: 'Фильмы', manual: 'Серии из manual', toShow: 'Выбранные файлы в шоу' }, + importTabs: { movies: 'Фильмы', manual: 'Серии из manual', toShow: 'Файлы в шоу' }, moviesButton: 'Фильмы', manualTitle: 'Ручной разбор папки manual', manualHint: @@ -315,6 +315,9 @@ export const ru = { manualImported: 'Импортировано файлов: {{count}}', uploadToShow: 'Загрузить в шоу', toShowTitle: 'Загрузить и добавить в шоу', + toShowPick: 'Выбрать файлы', + toShowEmpty: 'Файлы не выбраны', + toShowPicked: 'Выбрано файлов: {{count}}', autoDetectHint: 'Каждый файл привяжется к шоу, чьё оригинальное (или отображаемое) название есть в имени релиза, напр. «The.Simpsons.S33E01…» → The Simpsons.', toShowLibrary: 'В библиотеку',