Improve the upload behavior

pull/1867/head
LASER-Yi 2 years ago
parent 182b125a9e
commit 4bb2cf65e6

@ -25,7 +25,6 @@
"@fortawesome/free-regular-svg-icons": "^6", "@fortawesome/free-regular-svg-icons": "^6",
"@fortawesome/free-solid-svg-icons": "^6", "@fortawesome/free-solid-svg-icons": "^6",
"@fortawesome/react-fontawesome": "^0.1", "@fortawesome/react-fontawesome": "^0.1",
"@mantine/dropzone": "^4",
"@mantine/modals": "^4", "@mantine/modals": "^4",
"@mantine/notifications": "^4", "@mantine/notifications": "^4",
"@testing-library/jest-dom": "latest", "@testing-library/jest-dom": "latest",
@ -49,6 +48,7 @@
"prettier": "^2", "prettier": "^2",
"prettier-plugin-organize-imports": "^2", "prettier-plugin-organize-imports": "^2",
"pretty-quick": "^3.1", "pretty-quick": "^3.1",
"react-dropzone": "^14.2.1",
"react-table": "^7", "react-table": "^7",
"recharts": "^2.0.8", "recharts": "^2.0.8",
"sass": "^1", "sass": "^1",
@ -2332,21 +2332,6 @@
"react-dom": ">=16.8.0" "react-dom": ">=16.8.0"
} }
}, },
"node_modules/@mantine/dropzone": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-4.2.7.tgz",
"integrity": "sha512-eQTVX5hClHNYR6UzNa4P559LsbfdqNHJUu/P7TiIvwIHqKRVjDRkuSZMciSpWqBueBfzrdCZQ32exb3l299Xfg==",
"dev": true,
"dependencies": {
"react-dropzone": "^11.4.2"
},
"peerDependencies": {
"@mantine/core": "4.2.7",
"@mantine/hooks": "4.2.7",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@mantine/hooks": { "node_modules/@mantine/hooks": {
"version": "4.2.7", "version": "4.2.7",
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-4.2.7.tgz", "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-4.2.7.tgz",
@ -5348,15 +5333,15 @@
} }
}, },
"node_modules/file-selector": { "node_modules/file-selector": {
"version": "0.4.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==", "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"tslib": "^2.0.3" "tslib": "^2.4.0"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 12"
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
@ -7163,20 +7148,20 @@
} }
}, },
"node_modules/react-dropzone": { "node_modules/react-dropzone": {
"version": "11.7.1", "version": "14.2.1",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.7.1.tgz", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.1.tgz",
"integrity": "sha512-zxCMwhfPy1olUEbw3FLNPLhAm/HnaYH5aELIEglRbqabizKAdHs0h+WuyOpmA+v1JXn0++fpQDdNfUagWt5hJQ==", "integrity": "sha512-jzX6wDtAjlfwZ+Fbg+G17EszWUkQVxhMTWMfAC9qSUq7II2pKglHA8aarbFKl0mLpRPDaNUcy+HD/Sf4gkf76Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"attr-accept": "^2.2.2", "attr-accept": "^2.2.2",
"file-selector": "^0.4.0", "file-selector": "^0.6.0",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
}, },
"engines": { "engines": {
"node": ">= 10.13" "node": ">= 10.13"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">= 16.8" "react": ">= 16.8 || 18.0.0"
} }
}, },
"node_modules/react-error-boundary": { "node_modules/react-error-boundary": {
@ -10230,15 +10215,6 @@
"react-textarea-autosize": "^8.3.2" "react-textarea-autosize": "^8.3.2"
} }
}, },
"@mantine/dropzone": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-4.2.7.tgz",
"integrity": "sha512-eQTVX5hClHNYR6UzNa4P559LsbfdqNHJUu/P7TiIvwIHqKRVjDRkuSZMciSpWqBueBfzrdCZQ32exb3l299Xfg==",
"dev": true,
"requires": {
"react-dropzone": "^11.4.2"
}
},
"@mantine/hooks": { "@mantine/hooks": {
"version": "4.2.7", "version": "4.2.7",
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-4.2.7.tgz", "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-4.2.7.tgz",
@ -12411,12 +12387,12 @@
} }
}, },
"file-selector": { "file-selector": {
"version": "0.4.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==", "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^2.0.3" "tslib": "^2.4.0"
} }
}, },
"fill-range": { "fill-range": {
@ -13727,13 +13703,13 @@
} }
}, },
"react-dropzone": { "react-dropzone": {
"version": "11.7.1", "version": "14.2.1",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.7.1.tgz", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.1.tgz",
"integrity": "sha512-zxCMwhfPy1olUEbw3FLNPLhAm/HnaYH5aELIEglRbqabizKAdHs0h+WuyOpmA+v1JXn0++fpQDdNfUagWt5hJQ==", "integrity": "sha512-jzX6wDtAjlfwZ+Fbg+G17EszWUkQVxhMTWMfAC9qSUq7II2pKglHA8aarbFKl0mLpRPDaNUcy+HD/Sf4gkf76Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"attr-accept": "^2.2.2", "attr-accept": "^2.2.2",
"file-selector": "^0.4.0", "file-selector": "^0.6.0",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
} }
}, },

@ -29,7 +29,6 @@
"@fortawesome/free-regular-svg-icons": "^6", "@fortawesome/free-regular-svg-icons": "^6",
"@fortawesome/free-solid-svg-icons": "^6", "@fortawesome/free-solid-svg-icons": "^6",
"@fortawesome/react-fontawesome": "^0.1", "@fortawesome/react-fontawesome": "^0.1",
"@mantine/dropzone": "^4",
"@mantine/modals": "^4", "@mantine/modals": "^4",
"@mantine/notifications": "^4", "@mantine/notifications": "^4",
"@testing-library/jest-dom": "latest", "@testing-library/jest-dom": "latest",
@ -53,6 +52,7 @@
"prettier": "^2", "prettier": "^2",
"prettier-plugin-organize-imports": "^2", "prettier-plugin-organize-imports": "^2",
"pretty-quick": "^3.1", "pretty-quick": "^3.1",
"react-dropzone": "^14",
"react-table": "^7", "react-table": "^7",
"recharts": "^2.0.8", "recharts": "^2.0.8",
"sass": "^1", "sass": "^1",

@ -0,0 +1,126 @@
import {
faArrowUp,
faFileCirclePlus,
faXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Box, createStyles, Overlay, Stack, Text } from "@mantine/core";
import clsx from "clsx";
import { FunctionComponent, useMemo } from "react";
import { DropzoneState } from "react-dropzone";
const useStyle = createStyles((theme) => {
return {
container: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
},
inner: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
margin: theme.spacing.md,
borderRadius: theme.radius.md,
borderWidth: "0.2rem",
borderStyle: "dashed",
borderColor: theme.colors.gray[7],
backgroundColor: theme.fn.rgba(theme.colors.gray[0], 0.4),
},
accepted: {
borderColor: theme.colors.brand[7],
backgroundColor: theme.fn.rgba(theme.colors.brand[0], 0.6),
},
rejected: {
borderColor: theme.colors.red[7],
backgroundColor: theme.fn.rgba(theme.colors.red[0], 0.9),
},
};
});
export interface DropOverlayProps {
state: DropzoneState;
zIndex?: number;
}
export const DropOverlay: FunctionComponent<DropOverlayProps> = ({
state,
children,
zIndex = 10,
}) => {
const {
getRootProps,
isDragActive,
isDragAccept: accepted,
isDragReject: rejected,
} = state;
const { classes } = useStyle();
const visible = isDragActive;
const icon = useMemo(() => {
if (accepted) {
return faArrowUp;
} else if (rejected) {
return faXmark;
} else {
return faFileCirclePlus;
}
}, [accepted, rejected]);
const title = useMemo(() => {
if (accepted) {
return "Release to Upload";
} else if (rejected) {
return "Cannot Upload Files";
} else {
return "Upload Subtitles";
}
}, [accepted, rejected]);
const subtitle = useMemo(() => {
if (accepted) {
return "";
} else if (rejected) {
return "Some files are invalid";
} else {
return "Drop to upload";
}
}, [accepted, rejected]);
return (
<Box sx={{ position: "relative" }} {...getRootProps()}>
{visible && (
<Box className={classes.container} style={{ zIndex }}>
<Stack
spacing="xs"
className={clsx(classes.inner, {
[classes.accepted]: accepted,
[classes.rejected]: rejected,
})}
style={{ zIndex: zIndex + 1 }}
>
<Box>
<FontAwesomeIcon icon={icon} size="3x" />
</Box>
<Text size="xl">{title}</Text>
<Text color="gray" size="sm">
{subtitle}
</Text>
</Stack>
<Overlay zIndex={zIndex}></Overlay>
</Box>
)}
{children}
</Box>
);
};

@ -1,78 +0,0 @@
import {
faArrowUp,
faFileCirclePlus,
faXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Box, Stack, Text } from "@mantine/core";
import {
Dropzone,
DropzoneProps,
DropzoneStatus,
FullScreenDropzone,
FullScreenDropzoneProps,
} from "@mantine/dropzone";
import { FunctionComponent, useMemo } from "react";
export type FileProps = Omit<DropzoneProps, "children"> & {
inner?: FileInnerComponent;
};
const File: FunctionComponent<FileProps> = ({
inner: Inner = FileInner,
...props
}) => {
return (
<Dropzone {...props}>
{(status) => <Inner status={status}></Inner>}
</Dropzone>
);
};
export type FileOverlayProps = Omit<FullScreenDropzoneProps, "children"> & {
inner?: FileInnerComponent;
};
export const FileOverlay: FunctionComponent<FileOverlayProps> = ({
inner: Inner = FileInner,
...props
}) => {
return (
<FullScreenDropzone {...props}>
{(status) => <Inner status={status}></Inner>}
</FullScreenDropzone>
);
};
export type FileInnerProps = {
status: DropzoneStatus;
};
type FileInnerComponent = FunctionComponent<FileInnerProps>;
const FileInner: FileInnerComponent = ({ status }) => {
const { accepted, rejected } = status;
const icon = useMemo(() => {
if (accepted) {
return faArrowUp;
} else if (rejected) {
return faXmark;
} else {
return faFileCirclePlus;
}
}, [accepted, rejected]);
return (
<Stack m="lg" align="center" spacing="xs" style={{ pointerEvents: "none" }}>
<Box mb="md">
<FontAwesomeIcon size="3x" icon={icon}></FontAwesomeIcon>
</Box>
<Text size="lg">Upload files here</Text>
<Text color="dimmed" size="sm">
Drag and drop, or click to select
</Text>
</Stack>
);
};
export default File;

@ -1,3 +1,4 @@
export { default as Action } from "./Action"; export { default as Action } from "./Action";
export * from "./DropOverlay";
export * from "./FileBrowser"; export * from "./FileBrowser";
export * from "./Selector"; export * from "./Selector";

@ -5,11 +5,10 @@ import {
useSeriesById, useSeriesById,
useSeriesModification, useSeriesModification,
} from "@/apis/hooks"; } from "@/apis/hooks";
import { Toolbox } from "@/components"; import { DropOverlay, Toolbox } from "@/components";
import { QueryOverlay } from "@/components/async"; import { QueryOverlay } from "@/components/async";
import { ItemEditModal } from "@/components/forms/ItemEditForm"; import { ItemEditModal } from "@/components/forms/ItemEditForm";
import { SeriesUploadModal } from "@/components/forms/SeriesUploadForm"; import { SeriesUploadModal } from "@/components/forms/SeriesUploadForm";
import File, { FileOverlay, FileProps } from "@/components/inputs/File";
import { SubtitleToolsModal } from "@/components/modals"; import { SubtitleToolsModal } from "@/components/modals";
import { useModals } from "@/modules/modals"; import { useModals } from "@/modules/modals";
import { notification, task, TaskGroup } from "@/modules/task"; import { notification, task, TaskGroup } from "@/modules/task";
@ -27,7 +26,8 @@ import {
import { Container, Group, Stack } from "@mantine/core"; import { Container, Group, Stack } from "@mantine/core";
import { useDocumentTitle } from "@mantine/hooks"; import { useDocumentTitle } from "@mantine/hooks";
import { showNotification } from "@mantine/notifications"; import { showNotification } from "@mantine/notifications";
import { FunctionComponent, useCallback, useMemo, useRef } from "react"; import { FunctionComponent, useCallback, useMemo } from "react";
import { FileRejection, useDropzone } from "react-dropzone";
import { Navigate, useParams } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import Table from "./table"; import Table from "./table";
@ -66,10 +66,18 @@ const SeriesEpisodesView: FunctionComponent = () => {
const hasTask = useIsAnyActionRunning(); const hasTask = useIsAnyActionRunning();
const dialogRef = useRef<VoidFunction>(null);
const onDrop = useCallback( const onDrop = useCallback(
(files: File[]) => { (files: File[], rejections: FileRejection[]) => {
if (series && profile) { if (series && profile) {
if (rejections.length > 0) {
showNotification(
notification.warn(
"Some files are rejected",
`${rejections.length} files are invalid`
)
);
}
modals.openContextModal(SeriesUploadModal, { modals.openContextModal(SeriesUploadModal, {
files, files,
series, series,
@ -86,11 +94,17 @@ const SeriesEpisodesView: FunctionComponent = () => {
[modals, profile, series] [modals, profile, series]
); );
const onReject = useCallback<Sure<FileProps["onReject"]>>((rejections) => { const dropzone = useDropzone({
showNotification( disabled: profile === undefined,
notification.warn("Cannot Upload Files", "Some files are invalid") noClick: true,
); onDrop,
}, []); });
// const onReject = useCallback<Sure<FileProps["onReject"]>>((rejections) => {
// showNotification(
// notification.warn("Cannot Upload Files", "Some files are invalid")
// );
// }, []);
useDocumentTitle(`${series?.title ?? "Unknown Series"} - Bazarr (Series)`); useDocumentTitle(`${series?.title ?? "Unknown Series"} - Bazarr (Series)`);
@ -101,118 +115,113 @@ const SeriesEpisodesView: FunctionComponent = () => {
return ( return (
<Container px={0} fluid> <Container px={0} fluid>
<QueryOverlay result={seriesQuery}> <QueryOverlay result={seriesQuery}>
{/* TODO: Still have some bugs. Handle it later */} <DropOverlay state={dropzone}>
<FileOverlay <Toolbox>
disabled={profile === undefined} <Group spacing="xs">
accept={[""]} <Toolbox.Button
onDrop={onDrop} icon={faSync}
></FileOverlay> disabled={!available || hasTask}
<div hidden> onClick={() => {
{/* A workaround to allow click to upload files */} if (series) {
<File task.create(series.title, TaskGroup.ScanDisk, action, {
disabled={profile === undefined} action: "scan-disk",
openRef={dialogRef} seriesid: id,
onDrop={onDrop} });
onReject={onReject} }
></File> }}
</div> >
<Toolbox> Scan Disk
<Group spacing="xs"> </Toolbox.Button>
<Toolbox.Button <Toolbox.Button
icon={faSync} icon={faSearch}
disabled={!available || hasTask} onClick={() => {
onClick={() => { if (series) {
if (series) { task.create(
task.create(series.title, TaskGroup.ScanDisk, action, { series.title,
action: "scan-disk", TaskGroup.SearchSubtitle,
seriesid: id, action,
}); {
} action: "search-missing",
}} seriesid: id,
> }
Scan Disk );
</Toolbox.Button> }
<Toolbox.Button }}
icon={faSearch} disabled={
onClick={() => { series === undefined ||
if (series) { series.episodeFileCount === 0 ||
task.create(series.title, TaskGroup.SearchSubtitle, action, { series.profileId === null ||
action: "search-missing", !available
seriesid: id,
});
} }
}} >
disabled={ Search
series === undefined || </Toolbox.Button>
series.episodeFileCount === 0 || </Group>
series.profileId === null || <Group spacing="xs">
!available <Toolbox.Button
} disabled={
> series === undefined ||
Search series.episodeFileCount === 0 ||
</Toolbox.Button> !available ||
</Group> hasTask
<Group spacing="xs">
<Toolbox.Button
disabled={
series === undefined ||
series.episodeFileCount === 0 ||
!available ||
hasTask
}
icon={faBriefcase}
onClick={() => {
if (episodes) {
modals.openContextModal(SubtitleToolsModal, {
payload: episodes,
});
} }
}} icon={faBriefcase}
> onClick={() => {
Tools if (episodes) {
</Toolbox.Button> modals.openContextModal(SubtitleToolsModal, {
<Toolbox.Button payload: episodes,
disabled={ });
series === undefined || }
series.episodeFileCount === 0 || }}
series.profileId === null || >
!available Tools
} </Toolbox.Button>
icon={faCloudUploadAlt} <Toolbox.Button
onClick={() => dialogRef.current?.()} disabled={
> series === undefined ||
Upload series.episodeFileCount === 0 ||
</Toolbox.Button> series.profileId === null ||
<Toolbox.Button !available
icon={faWrench}
disabled={hasTask}
onClick={() => {
if (series) {
modals.openContextModal(
ItemEditModal,
{
item: series,
mutation,
},
{ title: series.title }
);
} }
}} icon={faCloudUploadAlt}
> onClick={dropzone.open}
Edit Series >
</Toolbox.Button> Upload
</Group> </Toolbox.Button>
</Toolbox> <Toolbox.Button
<Stack> icon={faWrench}
<ItemOverview item={series ?? null} details={details}></ItemOverview> disabled={hasTask}
<QueryOverlay result={episodesQuery}> onClick={() => {
<Table if (series) {
episodes={episodes ?? null} modals.openContextModal(
profile={profile} ItemEditModal,
disabled={hasTask || !series || series.profileId === null} {
></Table> item: series,
</QueryOverlay> mutation,
</Stack> },
{ title: series.title }
);
}
}}
>
Edit Series
</Toolbox.Button>
</Group>
</Toolbox>
<Stack>
<ItemOverview
item={series ?? null}
details={details}
></ItemOverview>
<QueryOverlay result={episodesQuery}>
<Table
episodes={episodes ?? null}
profile={profile}
disabled={hasTask || !series || series.profileId === null}
></Table>
</QueryOverlay>
</Stack>
</DropOverlay>
</QueryOverlay> </QueryOverlay>
</Container> </Container>
); );

@ -8,15 +8,14 @@ import {
useMovieById, useMovieById,
useMovieModification, useMovieModification,
} from "@/apis/hooks/movies"; } from "@/apis/hooks/movies";
import { Action, Toolbox } from "@/components"; import { Action, DropOverlay, Toolbox } from "@/components";
import { QueryOverlay } from "@/components/async"; import { QueryOverlay } from "@/components/async";
import { ItemEditModal } from "@/components/forms/ItemEditForm"; import { ItemEditModal } from "@/components/forms/ItemEditForm";
import { MovieUploadModal } from "@/components/forms/MovieUploadForm"; import { MovieUploadModal } from "@/components/forms/MovieUploadForm";
import File, { FileOverlay } from "@/components/inputs/File";
import { MovieHistoryModal, SubtitleToolsModal } from "@/components/modals"; import { MovieHistoryModal, SubtitleToolsModal } from "@/components/modals";
import { MovieSearchModal } from "@/components/modals/ManualSearchModal"; import { MovieSearchModal } from "@/components/modals/ManualSearchModal";
import { useModals } from "@/modules/modals"; import { useModals } from "@/modules/modals";
import { task, TaskGroup } from "@/modules/task"; import { notification, task, TaskGroup } from "@/modules/task";
import ItemOverview from "@/pages/views/ItemOverview"; import ItemOverview from "@/pages/views/ItemOverview";
import { useLanguageProfileBy } from "@/utilities/languages"; import { useLanguageProfileBy } from "@/utilities/languages";
import { import {
@ -32,8 +31,10 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Container, Group, Menu, Stack } from "@mantine/core"; import { Container, Group, Menu, Stack } from "@mantine/core";
import { useDocumentTitle } from "@mantine/hooks"; import { useDocumentTitle } from "@mantine/hooks";
import { showNotification } from "@mantine/notifications";
import { isNumber } from "lodash"; import { isNumber } from "lodash";
import { FunctionComponent, useCallback, useRef } from "react"; import { FunctionComponent, useCallback } from "react";
import { FileRejection, useDropzone } from "react-dropzone";
import { Navigate, useParams } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import Table from "./table"; import Table from "./table";
@ -78,14 +79,28 @@ const MovieDetailView: FunctionComponent = () => {
[downloadAsync] [downloadAsync]
); );
const dialogRef = useRef<VoidFunction>(null);
const onDrop = useCallback( const onDrop = useCallback(
(files: File[]) => { (files: File[], rejections: FileRejection[]) => {
if (movie && profile) { if (movie && profile) {
if (rejections.length > 0) {
showNotification(
notification.warn(
"Some files are rejected",
`${rejections.length} files are invalid`
)
);
}
modals.openContextModal(MovieUploadModal, { modals.openContextModal(MovieUploadModal, {
files, files,
movie, movie,
}); });
} else {
showNotification(
notification.warn(
"Cannot Upload Files",
"movie or language profile is not ready"
)
);
} }
}, },
[modals, movie, profile] [modals, movie, profile]
@ -95,6 +110,12 @@ const MovieDetailView: FunctionComponent = () => {
useDocumentTitle(`${movie?.title ?? "Unknown Movie"} - Bazarr (Movies)`); useDocumentTitle(`${movie?.title ?? "Unknown Movie"} - Bazarr (Movies)`);
const dropzone = useDropzone({
disabled: profile === undefined,
onDrop,
noClick: true,
});
if (isNaN(id) || (isFetched && !movie)) { if (isNaN(id) || (isFetched && !movie)) {
return <Navigate to="/not-found"></Navigate>; return <Navigate to="/not-found"></Navigate>;
} }
@ -104,136 +125,135 @@ const MovieDetailView: FunctionComponent = () => {
return ( return (
<Container fluid px={0}> <Container fluid px={0}>
<QueryOverlay result={movieQuery}> <QueryOverlay result={movieQuery}>
<FileOverlay <DropOverlay state={dropzone}>
{/* <FileOverlay
disabled={profile === undefined} disabled={profile === undefined}
accept={[""]} accept={[""]}
onDrop={onDrop} onDrop={onDrop}
></FileOverlay> ></FileOverlay> */}
<div hidden> {/* <div hidden>
{/* A workaround to allow click to upload files */}
<File <File
disabled={profile === undefined} disabled={profile === undefined}
accept={[""]} accept={[""]}
openRef={dialogRef} openRef={dialogRef}
onDrop={onDrop} onDrop={onDrop}
></File> ></File>
</div> </div> */}
<Toolbox> <Toolbox>
<Group spacing="xs"> <Group spacing="xs">
<Toolbox.Button <Toolbox.Button
icon={faSync} icon={faSync}
disabled={hasTask} disabled={hasTask}
onClick={() => {
if (movie) {
task.create(movie.title, TaskGroup.ScanDisk, action, {
action: "scan-disk",
radarrid: id,
});
}
}}
>
Scan Disk
</Toolbox.Button>
<Toolbox.Button
icon={faSearch}
disabled={!isNumber(movie?.profileId)}
onClick={() => {
if (movie) {
task.create(movie.title, TaskGroup.SearchSubtitle, action, {
action: "search-missing",
radarrid: id,
});
}
}}
>
Search
</Toolbox.Button>
<Toolbox.Button
icon={faUser}
disabled={!isNumber(movie?.profileId) || hasTask}
onClick={() => {
if (movie) {
modals.openContextModal(MovieSearchModal, {
item: movie,
download,
query: useMoviesProvider,
});
}
}}
>
Manual
</Toolbox.Button>
</Group>
<Group spacing="xs">
<Toolbox.Button
disabled={!allowEdit || movie.profileId === null || hasTask}
icon={faCloudUploadAlt}
onClick={() => {
dialogRef.current?.();
}}
>
Upload
</Toolbox.Button>
<Toolbox.Button
icon={faWrench}
disabled={hasTask}
onClick={() => {
if (movie) {
modals.openContextModal(
ItemEditModal,
{
item: movie,
mutation,
},
{ title: movie.title }
);
}
}}
>
Edit Movie
</Toolbox.Button>
<Menu
control={
<Action
label="More Actions"
icon={faEllipsis}
disabled={hasTask}
/>
}
>
<Menu.Item
icon={<FontAwesomeIcon icon={faToolbox} />}
onClick={() => { onClick={() => {
if (movie) { if (movie) {
modals.openContextModal(SubtitleToolsModal, { task.create(movie.title, TaskGroup.ScanDisk, action, {
payload: [movie], action: "scan-disk",
radarrid: id,
}); });
} }
}} }}
> >
Tools Scan Disk
</Menu.Item> </Toolbox.Button>
<Menu.Item <Toolbox.Button
icon={<FontAwesomeIcon icon={faHistory} />} icon={faSearch}
disabled={!isNumber(movie?.profileId)}
onClick={() => { onClick={() => {
if (movie) { if (movie) {
modals.openContextModal(MovieHistoryModal, { movie }); task.create(movie.title, TaskGroup.SearchSubtitle, action, {
action: "search-missing",
radarrid: id,
});
} }
}} }}
> >
History Search
</Menu.Item> </Toolbox.Button>
</Menu> <Toolbox.Button
</Group> icon={faUser}
</Toolbox> disabled={!isNumber(movie?.profileId) || hasTask}
<Stack> onClick={() => {
<ItemOverview item={movie ?? null} details={[]}></ItemOverview> if (movie) {
<Table modals.openContextModal(MovieSearchModal, {
movie={movie ?? null} item: movie,
profile={profile} download,
disabled={hasTask} query: useMoviesProvider,
></Table> });
</Stack> }
}}
>
Manual
</Toolbox.Button>
</Group>
<Group spacing="xs">
<Toolbox.Button
disabled={!allowEdit || movie.profileId === null || hasTask}
icon={faCloudUploadAlt}
onClick={dropzone.open}
>
Upload
</Toolbox.Button>
<Toolbox.Button
icon={faWrench}
disabled={hasTask}
onClick={() => {
if (movie) {
modals.openContextModal(
ItemEditModal,
{
item: movie,
mutation,
},
{ title: movie.title }
);
}
}}
>
Edit Movie
</Toolbox.Button>
<Menu
control={
<Action
label="More Actions"
icon={faEllipsis}
disabled={hasTask}
/>
}
>
<Menu.Item
icon={<FontAwesomeIcon icon={faToolbox} />}
onClick={() => {
if (movie) {
modals.openContextModal(SubtitleToolsModal, {
payload: [movie],
});
}
}}
>
Tools
</Menu.Item>
<Menu.Item
icon={<FontAwesomeIcon icon={faHistory} />}
onClick={() => {
if (movie) {
modals.openContextModal(MovieHistoryModal, { movie });
}
}}
>
History
</Menu.Item>
</Menu>
</Group>
</Toolbox>
<Stack>
<ItemOverview item={movie ?? null} details={[]}></ItemOverview>
<Table
movie={movie ?? null}
profile={profile}
disabled={hasTask}
></Table>
</Stack>
</DropOverlay>
</QueryOverlay> </QueryOverlay>
</Container> </Container>
); );

Loading…
Cancel
Save