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

@ -29,7 +29,6 @@
"@fortawesome/free-regular-svg-icons": "^6",
"@fortawesome/free-solid-svg-icons": "^6",
"@fortawesome/react-fontawesome": "^0.1",
"@mantine/dropzone": "^4",
"@mantine/modals": "^4",
"@mantine/notifications": "^4",
"@testing-library/jest-dom": "latest",
@ -53,6 +52,7 @@
"prettier": "^2",
"prettier-plugin-organize-imports": "^2",
"pretty-quick": "^3.1",
"react-dropzone": "^14",
"react-table": "^7",
"recharts": "^2.0.8",
"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 * from "./DropOverlay";
export * from "./FileBrowser";
export * from "./Selector";

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

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

Loading…
Cancel
Save