diff --git a/src/core/router/index.tsx b/src/core/router/index.tsx index b9de58d2d..3bfe75c38 100644 --- a/src/core/router/index.tsx +++ b/src/core/router/index.tsx @@ -37,6 +37,10 @@ import GroupList from '../../pages/collection/GroupList'; import Group from '../../pages/collection/Group'; import FilterGroupList from '../../pages/collection/FilterGroupList'; +// Utilities +import UnrecognizedUtility from '../../pages/utilities/UnrecognizedUtility'; +import MultipleFilesUtility from '../../pages/utilities/MultipleFilesUtility'; + type Props = { history: BrowserHistory; }; @@ -72,7 +76,11 @@ function Router(props: Props) { } /> } /> } /> - } /> + }> + } /> + } /> + } /> + } /> } /> diff --git a/src/pages/utilities/MultipleFilesUtility.tsx b/src/pages/utilities/MultipleFilesUtility.tsx new file mode 100644 index 000000000..c5b53933e --- /dev/null +++ b/src/pages/utilities/MultipleFilesUtility.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function MultipleFilesUtility() { + return ( +
+ MFU +
+ ); +} + +export default MultipleFilesUtility; diff --git a/src/pages/utilities/UnrecognizedUtility.tsx b/src/pages/utilities/UnrecognizedUtility.tsx index f51be81b1..0fc570866 100644 --- a/src/pages/utilities/UnrecognizedUtility.tsx +++ b/src/pages/utilities/UnrecognizedUtility.tsx @@ -6,6 +6,7 @@ import { import cx from 'classnames'; import UnrecognizedTab from './UnrecognizedUtilityTabs/UnrecognizedTab'; +import AVDumpTab from './UnrecognizedUtilityTabs/AVDumpTab.'; import { useGetFileUnrecognizedQuery } from '../../core/rtkQuery/fileApi'; @@ -17,15 +18,15 @@ function UnrecognizedUtility() { const renderTabContent = () => { switch (activeTab) { case 'unrecognized': - return (); + return (); case 'avdump': - return (); + return (); case 'manuallyLinked': - return (); + return (); case 'ignoredFiles': - return (); + return (); default: - return (); + return (); } }; diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/AVDumpTab..tsx b/src/pages/utilities/UnrecognizedUtilityTabs/AVDumpTab..tsx new file mode 100644 index 000000000..2b1953662 --- /dev/null +++ b/src/pages/utilities/UnrecognizedUtilityTabs/AVDumpTab..tsx @@ -0,0 +1,252 @@ +import React, { useEffect, useState } from 'react'; +import prettyBytes from 'pretty-bytes'; +import moment from 'moment'; +import cx from 'classnames'; +import { countBy, find, forEach, pickBy } from 'lodash'; +import { Icon } from '@mdi/react'; +import { + mdiChevronLeft, mdiChevronRight, + mdiDatabaseSearchOutline, mdiDatabaseSyncOutline, + mdiDumpTruck, mdiMagnify, + mdiLinkVariantPlus, mdiMinusBoxOutline, + mdiEyeOffOutline, mdiCloseBoxOutline, +} from '@mdi/js'; + +import ShokoPanel from '../../../components/Panels/ShokoPanel'; +import Button from '../../../components/Input/Button'; +import TransitionDiv from '../../../components/TransitionDiv'; +import { useGetImportFoldersQuery } from '../../../core/rtkQuery/importFolderApi'; + +import SeriesLinkPanel from './Components/SeriesLinkPanel'; +import SelectedFilesPanel from './Components/SelectedFilesPanel'; +import EpisodeLinkPanel from './Components/EpisodeLinkPanel'; + +import type { SeriesAniDBSearchResult } from '../../../core/types/api/series'; + +import { + useGetFileUnrecognizedQuery, + useLazyPostFileRehashQuery, + useLazyPostFileRescanQuery, +} from '../../../core/rtkQuery/fileApi'; +import FileListPanel from './Components/FileListPanel'; +import type { ImportFolderType } from '../../../core/types/api/import-folder'; + +function AVDumpTab() { + const files = useGetFileUnrecognizedQuery({ pageSize: 0 }); + const importFolderQuery = useGetImportFoldersQuery(); + const importFolders = importFolderQuery?.data ?? [] as ImportFolderType[]; + const [fileRescanTrigger] = useLazyPostFileRescanQuery(); + const [fileRehashTrigger] = useLazyPostFileRehashQuery(); + + const [markedItems, setMarkedItems] = useState({} as { [key: number]: boolean }); + const [markedItemsCount, setMarkedItemsCount] = useState(0); + const [selectedFile, setSelectedFile] = useState(1); + const [manualLink, setManualLink] = useState(false); + const [selectedSeries, setSelectedSeries] = useState({} as SeriesAniDBSearchResult); + + useEffect(() => { + while (files.isLoading); + const newMarkedItems = {} as { [key: number]: boolean }; + forEach(files.data?.List, (file) => { + newMarkedItems[file.ID] = false; + }); + setMarkedItems(newMarkedItems); + }, []); + + const changeSelectedFile = (operation: string) => { + if (operation === 'prev') { + if (selectedFile > 1) { + setSelectedFile(selectedFile - 1); + } + } else { + if (selectedFile < markedItemsCount) { + setSelectedFile(selectedFile + 1); + } + } + }; + + const changeMarkedItems = (items: { [key: number]: boolean }) => { + setMarkedItems(items); + setMarkedItemsCount(countBy(items).true ?? 0); + if (selectedFile >= markedItemsCount) changeSelectedFile('prev'); + }; + + const fileInfoTitle = () => { + if (markedItemsCount > 0) { + return ( + + Selected File Info +
+ - {selectedFile}/{markedItemsCount} +
+
+ ); + } else { + return 'Selected File Info'; + } + }; + + const renderFileInfo = () => { + const selectedFiles = files.data?.List.filter(item => markedItems[item.ID])!; + const selectedFileInfo = selectedFiles[selectedFile - 1]; + + const importFolderId = selectedFileInfo.Locations[0].ImportFolderID; + const importFolder = find(importFolders, { ID: importFolderId })?.Path ?? ''; + + return ( + +
+
+
Filename
+ {selectedFileInfo.Locations[0].RelativePath} +
+ +
+
Size
+ {prettyBytes(selectedFileInfo.Size, { binary: true })} +
+ +
+
Folder
+ {importFolder} +
+ +
+
Import Date
+ {moment(selectedFileInfo.Created).format('MMMM DD YYYY, HH:mm')} +
+
+ +
+
+
Hash
+ {selectedFileInfo.Hashes.ED2K} +
+ +
+
MD5
+ {selectedFileInfo.Hashes.MD5} +
+ +
+
SHA1
+ {selectedFileInfo.Hashes.SHA1} +
+ +
+
CRC32
+ {selectedFileInfo.Hashes.CRC32} +
+
+
+ ); + }; + + const renderPanelOptions = () => ( +
+ + +
+ ); + + const rescanFiles = (selected = false) => { + if (selected) { + const fileIds = Object.keys(pickBy(markedItems, item => item)); + forEach(fileIds, fileId => fileRescanTrigger(parseInt(fileId))); + } else { + forEach(files.data?.List, file => fileRescanTrigger(file.ID)); + } + }; + + const rehashFiles = (selected = false) => { + if (selected) { + const fileIds = Object.keys(pickBy(markedItems, item => item)); + forEach(fileIds, fileId => fileRehashTrigger(parseInt(fileId))); + } else { + forEach(files.data?.List, file => fileRehashTrigger(file.ID)); + } + }; + + const renderOperations = (common = false) => { + const renderButton = (onClick: (...args: any) => void, icon: string, name: string) => ( + + ); + + return ( + + {common ? ( + <> + {renderButton(() => rescanFiles(), mdiDatabaseSearchOutline, 'Rescan All')} + {renderButton(() => rehashFiles(), mdiDatabaseSyncOutline, 'Rehash All')} + {renderButton(() => {}, mdiDumpTruck, 'AVDump All')} + + ) : ( + <> + {renderButton(() => setManualLink(!manualLink), mdiLinkVariantPlus, 'Manually Link')} + {renderButton(() => rescanFiles(true), mdiDatabaseSearchOutline, 'Rescan')} + {renderButton(() => rehashFiles(true), mdiDatabaseSyncOutline, 'Rehash')} + {renderButton(() => {}, mdiDumpTruck, 'AVDump')} + {renderButton(() => {}, mdiEyeOffOutline, 'Ignore')} + {renderButton(() => {}, mdiMinusBoxOutline, 'Delete')} + {renderButton(() => {}, mdiCloseBoxOutline, 'Cancel Selection')} + + )} + + ); + }; + + const updateSelectedSeries = (series: SeriesAniDBSearchResult) => setSelectedSeries(series); + + return ( + + +
+
+
+ + + {renderOperations(markedItemsCount === 0)} +
{markedItemsCount} Files Selected
+
+ {manualLink && ( + + + + + )} +
+ {manualLink ? ( + + markedItems[item.ID])!} selectedSeries={selectedSeries} /> + {selectedSeries?.ID + ? () + : ()} + + ) : ( + + )} +
+ + + {markedItemsCount === 0 ? ( + + No File(s) Selected + + ) : renderFileInfo()} + + +
+ ); +} + +export default AVDumpTab; diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/Components/EpisodeLinkPanel.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/Components/EpisodeLinkPanel.tsx new file mode 100644 index 000000000..c575b45bb --- /dev/null +++ b/src/pages/utilities/UnrecognizedUtilityTabs/Components/EpisodeLinkPanel.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import ShokoPanel from '../../../../components/Panels/ShokoPanel'; + +import type { SeriesAniDBSearchResult } from '../../../../core/types/api/series'; + +type Props = { + selectedSeries: SeriesAniDBSearchResult; + setSeries: (series: SeriesAniDBSearchResult) => void; +}; + +function EpisodeLinkPanel(props: Props) { + const { selectedSeries } = props; + + const renderTitle = () => ( +
+ AniDB| + {selectedSeries.ID} - {selectedSeries.Title} +
+ ); + + return ( + + {selectedSeries.ID} + + ); +} + +export default EpisodeLinkPanel; diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/Components/FileListPanel.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/Components/FileListPanel.tsx new file mode 100644 index 000000000..4f170cb7f --- /dev/null +++ b/src/pages/utilities/UnrecognizedUtilityTabs/Components/FileListPanel.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { countBy, find, forEach } from 'lodash'; +import prettyBytes from 'pretty-bytes'; +import moment from 'moment'; + +import TransitionDiv from '../../../../components/TransitionDiv'; +import Checkbox from '../../../../components/Input/Checkbox'; + +import { useGetFileUnrecognizedQuery } from '../../../../core/rtkQuery/fileApi'; +import { useGetImportFoldersQuery } from '../../../../core/rtkQuery/importFolderApi'; +import type { ImportFolderType } from '../../../../core/types/api/import-folder'; + +type Props = { + markedItems: { [key: number]: boolean }; + setMarkedItems: (items: { [key: number]: boolean }) => void; +}; + +function FileListPanel(props: Props) { + const { markedItems, setMarkedItems } = props; + + const files = useGetFileUnrecognizedQuery({ pageSize: 0 }); + const importFolderQuery = useGetImportFoldersQuery(); + const importFolders = importFolderQuery?.data ?? [] as ImportFolderType[]; + + const isSelectAllChecked = (countBy(markedItems).true ?? 0) === (files.data?.Total ?? 0); + + const handleInputChange = (event: any) => { + const { id, checked } = event.target; + setMarkedItems({ ...markedItems, [id]: checked }); + }; + + const renderRow = (Id: number, importFolder: string, filename: string, size: number, date: string) => ( + + +
+ +
+ + {importFolder} + {filename} + {prettyBytes(size, { binary: true })} + {moment(date).format('MMMM DD YYYY, HH:mm')} + + ); + + const rows: Array = []; + forEach(files.data?.List, (file) => { + const importFolderId = file.Locations[0].ImportFolderID; + const importFolder = find(importFolders, { ID: importFolderId })?.Name ?? ''; + rows.push(renderRow(file.ID, importFolder, file.Locations[0].RelativePath, file.Size, file.Created)); + }); + + const handleSelectAll = () => { + const tempMarkedItems: { [id: number]: boolean } = {}; + forEach(files.data?.List, file => tempMarkedItems[file.ID] = !isSelectAllChecked); + setMarkedItems(tempMarkedItems); + }; + + return ( + +
+ + + + + + + + +
+
+ +
+
Import FolderFilenameSizeDate +
+
+
+ + {rows} +
+
+
+ ); +} + +export default FileListPanel; diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/Components/SelectedFilesPanel.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/Components/SelectedFilesPanel.tsx new file mode 100644 index 000000000..5ebbec552 --- /dev/null +++ b/src/pages/utilities/UnrecognizedUtilityTabs/Components/SelectedFilesPanel.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { forEach } from 'lodash'; +import cx from 'classnames'; + +import ShokoPanel from '../../../../components/Panels/ShokoPanel'; + +import type { FileType } from '../../../../core/types/api/file'; +import type { SeriesAniDBSearchResult } from '../../../../core/types/api/series'; + +type Props = { + files: Array; + selectedSeries: SeriesAniDBSearchResult; +}; + +function SelectedFilesPanel(props: Props) { + const { files, selectedSeries } = props; + + const manualLinkFileRows: Array = []; + forEach(files, (file) => { + manualLinkFileRows.push( +
+ {file.Locations[0].RelativePath} +
, + ); + }); + + return ( + + {manualLinkFileRows} + + ); +} + +export default SelectedFilesPanel; diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/Components/SeriesLinkPanel.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/Components/SeriesLinkPanel.tsx new file mode 100644 index 000000000..2e8c1b7f6 --- /dev/null +++ b/src/pages/utilities/UnrecognizedUtilityTabs/Components/SeriesLinkPanel.tsx @@ -0,0 +1,99 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { debounce, forEach } from 'lodash'; +import { Icon } from '@mdi/react'; +import { mdiOpenInNew, mdiMagnify } from '@mdi/js'; + +import ShokoPanel from '../../../../components/Panels/ShokoPanel'; +import Input from '../../../../components/Input/Input'; + +import { useLazyGetSeriesAniDBSearchQuery } from '../../../../core/rtkQuery/seriesApi'; + +import type { SeriesAniDBSearchResult } from '../../../../core/types/api/series'; + +type Props = { + setSeries: (series: SeriesAniDBSearchResult) => void; +}; + +function SeriesLinkPanel(props: Props) { + const { setSeries } = props; + + const [searchText, setSearchText] = useState(''); + + const [searchTrigger, searchResults] = useLazyGetSeriesAniDBSearchQuery(); + + const debouncedSearch = useRef( + debounce( (query: string) => { + searchTrigger({ query, pageSize: 20 }).catch(() => {}); + }, 200), + ).current; + + const handleSearch = (query: string) => { + setSearchText(query); + if (query !== '') + debouncedSearch(query); + }; + + useEffect(() => { + return () => { + debouncedSearch.cancel(); + }; + }, [debouncedSearch]); + + const renderRow = (data: SeriesAniDBSearchResult) => ( + setSeries(data)} className="cursor-pointer"> + +
+ {data.ID} + { + e.stopPropagation(); + window.open(`https://anidb.net/anime/${data.ID}`, '_blank'); + }}> + + +
+ + {data.Title} + {data.EpisodeCount ?? '-'} + {data.Type} + + ); + + const rows: Array = []; + forEach(searchResults.data, (result) => { + rows.push(renderRow(result)); + }); + + return ( + +
+ handleSearch(e.target.value)} + placeholder="Enter Series Name or AniDB ID..." + className="grow" + startIcon={mdiMagnify} + /> +
+
+ + + + {/*TODO: Fix scrollbar*/} + + + + + + + + {rows} + +
AniDB IDSeries NameEPsType
+
+
+ ); +} + +export default SeriesLinkPanel; diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx index 447ac55a4..fd0dff274 100644 --- a/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx +++ b/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx @@ -1,42 +1,58 @@ -import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { useEffect, useState } from 'react'; import prettyBytes from 'pretty-bytes'; import moment from 'moment'; -import { countBy, find, forEach, pickBy } from 'lodash'; +import cx from 'classnames'; +import { countBy, find, forEach } from 'lodash'; import { Icon } from '@mdi/react'; import { mdiChevronLeft, mdiChevronRight, mdiDatabaseSearchOutline, mdiDatabaseSyncOutline, - mdiDumpTruck, mdiMagnify, - mdiLinkVariantPlus, mdiMinusBoxOutline, - mdiEyeOffOutline, mdiCloseBoxOutline, + mdiDumpTruck, mdiMagnify, mdiRestart, + mdiLinkVariantPlus, mdiMinusCircleOutline, + mdiEyeOffOutline, mdiCloseCircleOutline, } from '@mdi/js'; -import Events from '../../../core/events'; import ShokoPanel from '../../../components/Panels/ShokoPanel'; import Button from '../../../components/Input/Button'; -import Checkbox from '../../../components/Input/Checkbox'; import TransitionDiv from '../../../components/TransitionDiv'; import { useGetImportFoldersQuery } from '../../../core/rtkQuery/importFolderApi'; -import type { FileType } from '../../../core/types/api/file'; -import { ImportFolderType } from '../../../core/types/api/import-folder'; +import SeriesLinkPanel from './Components/SeriesLinkPanel'; +import SelectedFilesPanel from './Components/SelectedFilesPanel'; +import EpisodeLinkPanel from './Components/EpisodeLinkPanel'; -type Props = { - files: Array; -}; - -function UnrecognizedTab(props: Props) { - const { files } = props; - - const dispatch = useDispatch(); +import type { SeriesAniDBSearchResult } from '../../../core/types/api/series'; +import { + useGetFileUnrecognizedQuery, + useLazyPostFileRehashQuery, + useLazyPostFileRescanQuery, +} from '../../../core/rtkQuery/fileApi'; +import FileListPanel from './Components/FileListPanel'; +import type { ImportFolderType } from '../../../core/types/api/import-folder'; +import Input from '../../../components/Input/Input'; + +function UnrecognizedTab() { + const files = useGetFileUnrecognizedQuery({ pageSize: 0 }); const importFolderQuery = useGetImportFoldersQuery(); const importFolders = importFolderQuery?.data ?? [] as ImportFolderType[]; + const [fileRescanTrigger] = useLazyPostFileRescanQuery(); + const [fileRehashTrigger] = useLazyPostFileRehashQuery(); const [markedItems, setMarkedItems] = useState({} as { [key: number]: boolean }); const [markedItemsCount, setMarkedItemsCount] = useState(0); const [selectedFile, setSelectedFile] = useState(1); + const [manualLink, setManualLink] = useState(false); + const [selectedSeries, setSelectedSeries] = useState({} as SeriesAniDBSearchResult); + + useEffect(() => { + while (files.isLoading); + const newMarkedItems = {} as { [key: number]: boolean }; + forEach(files.data?.List, (file) => { + newMarkedItems[file.ID] = false; + }); + setMarkedItems(newMarkedItems); + }, []); const changeSelectedFile = (operation: string) => { if (operation === 'prev') { @@ -50,34 +66,12 @@ function UnrecognizedTab(props: Props) { } }; - const handleInputChange = (event: any) => { - const { id, checked } = event.target; - setMarkedItems({ ...markedItems, [id]: checked }); - setMarkedItemsCount(countBy({ ...markedItems, [id]: checked }).true ?? 0); - if (!checked && selectedFile >= markedItemsCount) changeSelectedFile('prev'); + const changeMarkedItems = (items: { [key: number]: boolean }) => { + setMarkedItems(items); + setMarkedItemsCount(countBy(items).true ?? 0); + if (selectedFile >= markedItemsCount) changeSelectedFile('prev'); }; - const renderRow = (Id: number, importFolder: string, filename: string, size: number, date: string) => ( - - -
- -
- - {importFolder} - {filename} - {prettyBytes(size, { binary: true })} - {moment(date).format('MMMM DD YYYY, HH:mm')} - - ); - - const rows: Array = []; - forEach(files, (file) => { - const importFolderId = file.Locations[0].ImportFolderID; - const importFolder = find(importFolders, { ID: importFolderId })?.Name ?? ''; - rows.push(renderRow(file.ID, importFolder, file.Locations[0].RelativePath, file.Size, file.Created)); - }); - const fileInfoTitle = () => { if (markedItemsCount > 0) { return ( @@ -94,14 +88,14 @@ function UnrecognizedTab(props: Props) { }; const renderFileInfo = () => { - const selectedFiles = files.filter(item => markedItems[item.ID]); + const selectedFiles = files.data?.List.filter(item => markedItems[item.ID])!; const selectedFileInfo = selectedFiles[selectedFile - 1]; const importFolderId = selectedFileInfo.Locations[0].ImportFolderID; const importFolder = find(importFolders, { ID: importFolderId })?.Path ?? ''; return ( -
+
Filename
@@ -118,7 +112,7 @@ function UnrecognizedTab(props: Props) { {importFolder}
-
+
Import Date
{moment(selectedFileInfo.Created).format('MMMM DD YYYY, HH:mm')}
@@ -140,128 +134,125 @@ function UnrecognizedTab(props: Props) { {selectedFileInfo.Hashes.SHA1}
-
+
CRC32
{selectedFileInfo.Hashes.CRC32}
-
+
); }; const renderPanelOptions = () => (
); const rescanFiles = (selected = false) => { if (selected) { - const fileIds = Object.keys(pickBy(markedItems, item => item)); - forEach(fileIds, fileId => dispatch({ type: Events.UTILITIES_RESCAN, payload: fileId })); + forEach(markedItems, (marked, fileId) => { + if (marked) fileRescanTrigger(parseInt(fileId)).catch(() => {}); + }); } else { - forEach(files, file => dispatch({ type: Events.UTILITIES_RESCAN, payload: file.ID })); + forEach(files.data?.List, file => fileRescanTrigger(file.ID)); } }; const rehashFiles = (selected = false) => { if (selected) { - const fileIds = Object.keys(pickBy(markedItems, item => item)); - forEach(fileIds, fileId => dispatch({ type: Events.UTILITIES_REHASH, payload: fileId })); + forEach(markedItems, (marked, fileId) => { + if (marked) fileRehashTrigger(parseInt(fileId)).catch(() => {}); + }); } else { - forEach(files, file => dispatch({ type: Events.UTILITIES_REHASH, payload: file.ID })); + forEach(files.data?.List, file => fileRehashTrigger(file.ID)); } }; - const renderCommonOperations = () => ( - - - - - - ); + const cancelSelection = () => { + const tempMarkedItems = markedItems; + forEach(tempMarkedItems, (_, key) => { + tempMarkedItems[key] = false; + }); + changeMarkedItems(tempMarkedItems); + }; - const renderOperations = () => ( - - - - - - - - - - ); + ); + + return ( + + {common ? ( + <> + {renderButton(() => files.refetch(), mdiRestart, 'Refresh')} + {renderButton(() => rescanFiles(), mdiDatabaseSearchOutline, 'Rescan All')} + {renderButton(() => rehashFiles(), mdiDatabaseSyncOutline, 'Rehash All')} + {renderButton(() => {}, mdiDumpTruck, 'AVDump All')} + + ) : ( + <> + {renderButton(() => setManualLink(!manualLink), mdiLinkVariantPlus, 'Manually Link')} + {renderButton(() => rescanFiles(true), mdiDatabaseSearchOutline, 'Rescan')} + {renderButton(() => rehashFiles(true), mdiDatabaseSyncOutline, 'Rehash')} + {renderButton(() => {}, mdiDumpTruck, 'AVDump')} + {renderButton(() => {}, mdiEyeOffOutline, 'Ignore')} + {renderButton(() => {}, mdiMinusCircleOutline, 'Delete')} + {renderButton(() => cancelSelection(), mdiCloseCircleOutline, 'Cancel Selection')} + + )} + + ); + }; + + const updateSelectedSeries = (series: SeriesAniDBSearchResult) => setSelectedSeries(series); return (
-
- - - {markedItemsCount > 0 ? renderOperations() : renderCommonOperations()} -
{markedItemsCount} Files Selected
-
-
- - - - {/*TODO: Select all checkbox*/} - - - - - - - - {rows} - -
- Import FolderFilenameSizeDate
+
+ {}} /> +
+ {renderOperations(markedItemsCount === 0)} +
{markedItemsCount} Files Selected
+
+ {manualLink && ( + + + + + )}
+ {manualLink ? ( + + markedItems[item.ID])!} selectedSeries={selectedSeries} /> + {selectedSeries?.ID + ? () + : ()} + + ) : ( + + )}
- + {markedItemsCount === 0 ? ( -
+ No File(s) Selected -
+ ) : renderFileInfo()}
diff --git a/src/pages/utilities/UtilitiesPage.tsx b/src/pages/utilities/UtilitiesPage.tsx index dba8e5247..200971914 100644 --- a/src/pages/utilities/UtilitiesPage.tsx +++ b/src/pages/utilities/UtilitiesPage.tsx @@ -1,11 +1,10 @@ import React from 'react'; - -import UnrecognizedUtility from './UnrecognizedUtility'; +import { Outlet } from 'react-router'; function UtilitiesPage() { return (
- +
); }