Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { KeyboardEvent, MouseEvent } from 'react';
import cx from 'classnames';

import { LinkState } from '@/core/types/utilities/unrecognized-utility';

import CrossReference from './CrossReference';
import ProviderName from './ProviderName';
import VideoMetadata from './VideoMetadata';
Expand All @@ -16,29 +14,31 @@ type Props = {
};

const linkStateClassMap = {
[LinkState.PreInit]: 'opacity-65 cursor-wait',
[LinkState.Init]: '',
[LinkState.Searching]: 'animate-pulse cursor-wait',
[LinkState.Ready]: 'cursor-pointer',
[LinkState.Submitting]: 'cursor-progress',
[LinkState.Submitted]: '',
'pre-init': 'opacity-65 cursor-wait',
init: '',
searching: 'animate-pulse cursor-wait',
ready: 'cursor-pointer',
submitting: 'cursor-progress',
submitted: '',
fetching: 'animate-pulse cursor-wait',
} as const;

const selectionDisabledStates = [
LinkState.PreInit,
LinkState.Searching,
LinkState.Submitting,
'pre-init',
'searching',
'submitting',
'fetching',
];

const UnrecognizedVideo = (props: Props) => {
const LinkCard = (props: Props) => {
const { link, selected, toggleSelect } = props;

let border = 'border-panel-border';
if (link.state === LinkState.Submitted) {
if (link.state === 'submitted') {
border = 'border-panel-text-important';
} else if ([LinkState.Searching, LinkState.Submitting].includes(link.state)) {
} else if (['searching', 'submitting'].includes(link.state)) {
border = 'border-panel-text-primary';
} else if (link.state === LinkState.Ready) {
} else if (link.state === 'ready') {
border = 'border-panel-text-warning';
} else if (selected) {
border = 'border-panel-text-primary';
Expand Down Expand Up @@ -69,7 +69,7 @@ const UnrecognizedVideo = (props: Props) => {
border,
selected && 'bg-panel-background-selected-row!',
!selected && linkStateClassMap[link.state],
[LinkState.Ready, LinkState.Submitting, LinkState.Submitted].includes(link.state) && 'bg-panel-background-alt',
['ready', 'submitting', 'submitted'].includes(link.state) && 'bg-panel-background-alt',
)}
onMouseDown={handleMouseDown}
onClick={handleSelect}
Expand Down Expand Up @@ -105,4 +105,4 @@ const UnrecognizedVideo = (props: Props) => {
);
};

export default UnrecognizedVideo;
export default LinkCard;
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { mdiMagnify, mdiPencil, mdiSelectAll, mdiSelection, mdiSelectionRemove, mdiTrayPlus } from '@mdi/js';

import MenuButton from '@/components/Utilities/Unrecognized/MenuButton';
import { LinkState } from '@/core/types/utilities/unrecognized-utility';

import type { ManualLinkType } from '@/core/types/utilities/unrecognized-utility';

Expand Down Expand Up @@ -32,7 +31,7 @@ const Menu = (props: Props) => {
name="Search for Release Info"
keybinding="S"
disabled={!selectedLinks.length
|| !selectedLinks.some(link => [LinkState.Ready, LinkState.Init].includes(link.state))}
|| !selectedLinks.some(link => ['ready', 'init', 'fetching'].includes(link.state))}
/>

<MenuButton
Expand All @@ -50,7 +49,7 @@ const Menu = (props: Props) => {
/>

{(selectedLinks.length > 0
&& !selectedLinks.some(link => [LinkState.Searching, LinkState.Submitting].includes(link.state))) && (
&& !selectedLinks.some(link => ['searching', 'submitting'].includes(link.state))) && (
<MenuButton
onClick={removeLinks}
icon={mdiSelectionRemove}
Expand All @@ -59,7 +58,7 @@ const Menu = (props: Props) => {
/>
)}

{selectedLinks.some(link => link.state === LinkState.Ready) && (
{selectedLinks.some(link => link.state === 'ready') && (
<MenuButton
onClick={addLinksToSubmitQueue}
icon={mdiTrayPlus}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import { LinkState } from '@/core/types/utilities/unrecognized-utility';

import type { ManualLinkType } from '@/core/types/utilities/unrecognized-utility';

type Props = {
link: ManualLinkType;
};

const linkStateAttributes = {
[LinkState.PreInit]: {
'pre-init': {
className: 'opacity-65',
text: 'Initializing metadata...',
},
[LinkState.Init]: {
init: {
className: 'text-panel-text-warning',
text: 'Waiting for user action',
},
[LinkState.Searching]: {
searching: {
className: 'text-panel-text-primary',
text: 'Searching for a match...',
},
[LinkState.Ready]: {
ready: {
className: 'text-panel-text-important',
text: 'Ready for submission',
},
[LinkState.Submitting]: {
submitting: {
className: 'text-panel-text-primary',
text: 'Submitting match...',
},
[LinkState.Submitted]: {
submitted: {
className: 'text-panel-text-important',
text: 'Completed',
},
fetching: {
className: 'text-panel-text-primary',
text: 'Retrieving existing release info...',
},
} as const;

const ProviderName = ({ link }: Props) => {
const name = link.release.ProviderName;
const editedByUser = name.startsWith('User+') || name.endsWith('+User');
const providerName = name !== 'User' && ![LinkState.Searching].includes(link.state)
const providerName = name !== 'User' && link.state !== 'searching'
? name
.replace(/^User\+/, '')
.replace(/\+User$/, '')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { countBy } from 'lodash';

import { LinkState } from '@/core/types/utilities/unrecognized-utility';

import type { ManualLinkType } from '@/core/types/utilities/unrecognized-utility';
import type { LinkStateType, ManualLinkType } from '@/core/types/utilities/unrecognized-utility';

const TitleOptions = ({ links, selectedCount }: { links: ManualLinkType[], selectedCount: number }) => {
const countByStatus = countBy(links, link => link.state) as Record<LinkState, number>;
const submittedCount = countByStatus[LinkState.Submitted] ?? 0;
const pendingCount = (countByStatus[LinkState.Submitting] ?? 0) + (countByStatus[LinkState.Ready] ?? 0);
const countByStatus = countBy(links, link => link.state) as Record<LinkStateType, number>;
const submittedCount = countByStatus.submitted ?? 0;
const pendingCount = (countByStatus.submitting ?? 0) + (countByStatus.ready ?? 0);

return (
<div className="flex text-lg font-semibold">
Expand Down
8 changes: 8 additions & 0 deletions src/core/react-query/release-info/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ export const usePreviewReleaseInfoByProviderIdMutation = () =>
id: 'release-info',
},
});

export const useReleaseInfoByFileIdMutation = () =>
useMutation<ReleaseInfoType | null, unknown, number>({
mutationFn: fileId => axios.get(`ReleaseInfo/File/${fileId}`),
scope: {
id: 'release-info',
},
});
5 changes: 3 additions & 2 deletions src/core/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ import UnsupportedPage from '@/pages/unsupported/UnsupportedPage';
import DuplicateFilesLinkedTab from '@/pages/utilities/DuplicateFilesTabs/DuplicateFilesLinkedTab';
import DuplicateFilesUnrecognizedTab from '@/pages/utilities/DuplicateFilesTabs/DuplicateFilesUnrecognizedTab';
import FileSearch from '@/pages/utilities/FileSearch';
import LinkFilesWithProviders from '@/pages/utilities/LinkFilesWithProviders';
import MissingEpisodes from '@/pages/utilities/MissingEpisodes';
import ReleaseManagement from '@/pages/utilities/ReleaseManagement';
import Renamer from '@/pages/utilities/Renamer';
import SeriesWithoutFilesUtility from '@/pages/utilities/SeriesWithoutFilesUtility';
import IgnoredFilesTab from '@/pages/utilities/UnrecognizedUtilityTabs/IgnoredFilesTab';
import LinkFilesTab from '@/pages/utilities/UnrecognizedUtilityTabs/LinkFilesTab';
import LinkFilesWithProvidersTab from '@/pages/utilities/UnrecognizedUtilityTabs/LinkFilesWithProvidersTab';
import ManuallyLinkedTab from '@/pages/utilities/UnrecognizedUtilityTabs/ManuallyLinkedTab';
import UnrecognizedTab from '@/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab';

Expand Down Expand Up @@ -93,7 +93,7 @@ const router = sentryCreateBrowserRouter(
<Route path="unrecognized" element={<Navigate to="files" replace />} />
<Route path="unrecognized/files" element={<UnrecognizedTab />} />
<Route path="unrecognized/files/link" element={<LinkFilesTab />} />
<Route path="unrecognized/files/link-with-providers" element={<LinkFilesWithProvidersTab />} />

<Route path="unrecognized/manually-linked-files" element={<ManuallyLinkedTab />} />
<Route path="unrecognized/ignored-files" element={<IgnoredFilesTab />} />
<Route path="release-management/:seriesId" element={<ReleaseManagementSeriesDetail />} />
Expand All @@ -105,6 +105,7 @@ const router = sentryCreateBrowserRouter(
<Route path="series-without-files" element={<SeriesWithoutFilesUtility />} />
<Route path="file-search" element={<FileSearch />} />
<Route path="renamer" element={<Renamer />} />
<Route path="link-with-providers" element={<LinkFilesWithProviders />} />
</Route>
<Route path="log" element={<LogsPage />} />
<Route path="collection">
Expand Down
11 changes: 2 additions & 9 deletions src/core/types/utilities/unrecognized-utility.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type { FileType, ReleaseInfoType } from '@/core/types/api/file';

export enum LinkState {
PreInit = 'pre-init',
Init = 'init',
Searching = 'searching',
Ready = 'ready',
Submitting = 'submitting',
Submitted = 'submitted',
}
export type LinkStateType = 'pre-init' | 'init' | 'searching' | 'ready' | 'submitting' | 'submitted' | 'fetching';

export type ManualLinkProviderType = {
id: string;
Expand All @@ -20,5 +13,5 @@ export type ManualLinkType = {
providers: ManualLinkProviderType[];
release: ReleaseInfoType;
metadata?: string;
state: LinkState;
state: LinkStateType;
};
90 changes: 90 additions & 0 deletions src/core/utilities/releaseInfoHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { produce } from 'immer';

import { ReleaseSource } from '@/core/types/api/file';

import type { FileType, ReleaseInfoType } from '@/core/types/api/file';
import type { ManualLinkProviderType, ManualLinkType } from '@/core/types/utilities/unrecognized-utility';

let lastLinkId = 0;
const generateLinkId = () => {
if (lastLinkId === Number.MAX_SAFE_INTEGER) {
lastLinkId = 0;
}
lastLinkId += 1;
return lastLinkId;
};

export const mergeReleaseInfo = (incoming: ReleaseInfoType, original: ReleaseInfoType): ReleaseInfoType =>
produce(incoming, (draft) => {
if (draft.Source === ReleaseSource.Unknown && original.Source !== ReleaseSource.Unknown) {
draft.Source = original.Source;
}

if (draft.Version < 1) draft.Version = 1;

draft.FileSize ??= original.FileSize;
draft.OriginalFilename ??= original.OriginalFilename;
draft.IsChaptered ??= original.IsChaptered;
draft.IsCensored ??= original.IsCensored;
draft.IsCreditless ??= original.IsCreditless;
draft.Group ??= original.Group;

if (draft.ProviderName !== 'User' && !/\+User\b/.test(draft.ProviderName)) {
draft.ProviderName += '+User';
}
});

const createLinksFromFiles = (files: FileType[], providers: ManualLinkProviderType[]) => {
const sortedFiles = files.toSorted((fileA, fileB) => {
let locationA = (fileA.Locations.find(loc => loc.IsAccessible) ?? fileA.Locations[0])?.RelativePath ?? '';
let locationB = (fileB.Locations.find(loc => loc.IsAccessible) ?? fileB.Locations[0])?.RelativePath ?? '';
if (locationA.startsWith('dot')) locationA = `.${locationA.substring(3)}`;
if (locationB.startsWith('dot')) locationB = `.${locationB.substring(3)}`;
return locationA.localeCompare(locationB, 'en-US', {
numeric: true,
ignorePunctuation: true,
sensitivity: 'base',
});
});

const newLinks: Record<number, ManualLinkType> = {};
sortedFiles.forEach((file) => {
const now = new Date().toISOString();
const release: ReleaseInfoType = {
OriginalFilename: file.Locations?.[0]?.RelativePath.split(/[/\\]/g).pop(),
ProviderName: 'User',
Version: 1,
Source: ReleaseSource.Unknown,
CrossReferences: [],
FileSize: file.Size,
Hashes: file.Hashes,
IsCorrupted: false,
Released: file.MediaInfo?.Encoded?.slice(0, 10) ?? file.Created?.slice(0, 10),
Created: now,
Updated: now,
};

const linkId = generateLinkId();
if (file.Imported) {
newLinks[linkId] = {
id: linkId,
file,
providers: [],
state: 'fetching',
release,
};
} else {
newLinks[linkId] = {
id: linkId,
file,
providers,
state: 'pre-init',
release,
};
}
});

return newLinks;
};

export default createLinksFromFiles;
Loading