From 05e9919a7b61fd7a8de56fd82e853d9683a0fe5f Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Tue, 28 Jul 2026 03:21:18 +0300 Subject: [PATCH] Refactor media import components for improved layout and functionality Updated the ManualInboxPanel, MediaImportDialog, MovieImportPanel, and UploadToShowPanel to enhance their layout and responsiveness. Adjusted flex properties to ensure consistent sizing and overflow handling across components. Simplified the MediaImportDialog by removing unnecessary file handling logic, and improved localization strings for clarity. These changes aim to enhance user experience and maintain a cohesive design across the media import interface. --- .../features/admin/media/ManualInboxPanel.tsx | 4 +- .../admin/media/MediaImportDialog.tsx | 27 +++++------ .../src/features/admin/media/MediaPanel.tsx | 28 +----------- .../features/admin/media/MovieImportPanel.tsx | 2 +- .../admin/media/UploadToShowPanel.tsx | 45 +++++++++++++++---- frontend/src/shared/lib/locales/en.ts | 3 ++ frontend/src/shared/lib/locales/ru.ts | 5 ++- 7 files changed, 60 insertions(+), 54 deletions(-) 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: 'В библиотеку',