Skip to content

Commit

Permalink
Utitilies WIP again (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
harshithmohan authored Aug 13, 2022
1 parent 767e67b commit bf5e9a3
Show file tree
Hide file tree
Showing 10 changed files with 648 additions and 138 deletions.
10 changes: 9 additions & 1 deletion src/core/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -72,7 +76,11 @@ function Router(props: Props) {
<Route index element={<Navigate to="dashboard" />} />
<Route path="dashboard" element={<DashboardPage />} />
<Route path="import-folders" element={<ImportFoldersPage />} />
<Route path="utilities" element={<UtilitiesPage />} />
<Route path="utilities" element={<UtilitiesPage />}>
<Route index element={<Navigate to="unrecognized" replace />} />
<Route path="unrecognized" element={<UnrecognizedUtility />} />
<Route path="multiple-files" element={<MultipleFilesUtility />} />
</Route>
<Route path="actions" element={<ActionsPage />} />
<Route path="log" element={<LogsPage />} />
<Route path="collection">
Expand Down
11 changes: 11 additions & 0 deletions src/pages/utilities/MultipleFilesUtility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

function MultipleFilesUtility() {
return (
<div>
MFU
</div>
);
}

export default MultipleFilesUtility;
11 changes: 6 additions & 5 deletions src/pages/utilities/UnrecognizedUtility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -17,15 +18,15 @@ function UnrecognizedUtility() {
const renderTabContent = () => {
switch (activeTab) {
case 'unrecognized':
return (<UnrecognizedTab files={files} />);
return (<UnrecognizedTab />);
case 'avdump':
return (<UnrecognizedTab files={files} />);
return (<AVDumpTab />);
case 'manuallyLinked':
return (<UnrecognizedTab files={files} />);
return (<UnrecognizedTab />);
case 'ignoredFiles':
return (<UnrecognizedTab files={files} />);
return (<UnrecognizedTab />);
default:
return (<UnrecognizedTab files={files} />);
return (<UnrecognizedTab />);
}
};

Expand Down
252 changes: 252 additions & 0 deletions src/pages/utilities/UnrecognizedUtilityTabs/AVDumpTab..tsx
Original file line number Diff line number Diff line change
@@ -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 (
<React.Fragment>
Selected File Info
<div className="flex ml-2">
- <span className="text-highlight-2 ml-2">{selectedFile}/{markedItemsCount}</span>
</div>
</React.Fragment>
);
} 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 (
<TransitionDiv className="flex flex-col mt-2">
<div className="flex">
<div className="flex flex-col w-2/5">
<div className="font-semibold mb-1">Filename</div>
{selectedFileInfo.Locations[0].RelativePath}
</div>

<div className="flex flex-col w-1/5">
<div className="font-semibold mb-1">Size</div>
{prettyBytes(selectedFileInfo.Size, { binary: true })}
</div>

<div className="flex flex-col w-1/4">
<div className="font-semibold mb-1">Folder</div>
{importFolder}
</div>

<div className="flex flex-col w-40">
<div className="font-semibold mb-1">Import Date</div>
{moment(selectedFileInfo.Created).format('MMMM DD YYYY, HH:mm')}
</div>
</div>

<div className="flex mt-4">
<div className="flex flex-col w-2/5">
<div className="font-semibold mb-1">Hash</div>
{selectedFileInfo.Hashes.ED2K}
</div>

<div className="flex flex-col w-1/5">
<div className="font-semibold mb-1">MD5</div>
{selectedFileInfo.Hashes.MD5}
</div>

<div className="flex flex-col w-1/4">
<div className="font-semibold mb-1">SHA1</div>
{selectedFileInfo.Hashes.SHA1}
</div>

<div className="flex flex-col w-40">
<div className="font-semibold mb-1">CRC32</div>
{selectedFileInfo.Hashes.CRC32}
</div>
</div>
</TransitionDiv>
);
};

const renderPanelOptions = () => (
<div className="flex">
<Button onClick={() => changeSelectedFile('prev')}>
<Icon path={mdiChevronLeft} size={1} className="opacity-75 text-highlight-1" />
</Button>
<Button onClick={() => changeSelectedFile('next')} className="ml-2">
<Icon path={mdiChevronRight} size={1} className="opacity-75 text-highlight-1" />
</Button>
</div>
);

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) => (
<Button onClick={onClick} className="flex items-center ml-3 font-normal">
<Icon path={icon} size={1} className="mr-1"/>
{name}
</Button>
);

return (
<TransitionDiv className="flex grow">
{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')}
</>
)}
</TransitionDiv>
);
};

const updateSelectedSeries = (series: SeriesAniDBSearchResult) => setSelectedSeries(series);

return (
<TransitionDiv className="flex flex-col grow">

<div className="flex flex-col grow">
<div className="flex">
<div className={cx(['box-border flex grow bg-background-nav border border-background-border items-center rounded-md px-3 py-2', manualLink && 'pointer-events-none opacity-75'])}>
<Icon path={mdiMagnify} size={1} />
<input type="text" placeholder="Search..." className="ml-2 bg-background-nav border-b border-font-main" />
{renderOperations(markedItemsCount === 0)}
<div className="ml-auto text-highlight-2 font-semibold">{markedItemsCount} Files Selected</div>
</div>
{manualLink && (
<TransitionDiv className="flex pl-2.5 items-center">
<Button onClick={() => {
setManualLink(false);
setSelectedSeries({} as SeriesAniDBSearchResult);
}} className="px-3 py-2 bg-background-alt rounded-md border !border-background-border">Cancel</Button>
<Button onClick={() => {}} className="px-3 py-2 bg-highlight-1 rounded-md border !border-background-border ml-2">Save</Button>
</TransitionDiv>
)}
</div>
{manualLink ? (
<TransitionDiv className="flex mt-5 overflow-y-auto grow gap-x-4">
<SelectedFilesPanel files={files.data?.List.filter(item => markedItems[item.ID])!} selectedSeries={selectedSeries} />
{selectedSeries?.ID
? (<EpisodeLinkPanel selectedSeries={selectedSeries} setSeries={updateSelectedSeries} />)
: (<SeriesLinkPanel setSeries={updateSelectedSeries}/>)}
</TransitionDiv>
) : (
<FileListPanel markedItems={markedItems} setMarkedItems={changeMarkedItems} />
)}
</div>

<ShokoPanel title={fileInfoTitle()} className="!h-56 mt-4" options={renderPanelOptions()}>
{markedItemsCount === 0 ? (
<TransitionDiv className="flex items-center justify-center mt-2 font-semibold">
No File(s) Selected
</TransitionDiv>
) : renderFileInfo()}
</ShokoPanel>

</TransitionDiv>
);
}

export default AVDumpTab;
Original file line number Diff line number Diff line change
@@ -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 = () => (
<div className="flex gap-x-1 items-center">
<span>AniDB</span>|
<span className="text-highlight-2">{selectedSeries.ID} - {selectedSeries.Title}</span>
</div>
);

return (
<ShokoPanel title={renderTitle()} className="w-1/2">
{selectedSeries.ID}
</ShokoPanel>
);
}

export default EpisodeLinkPanel;
Loading

0 comments on commit bf5e9a3

Please sign in to comment.