From 66048c55334450f34ff80b682266546caf83b6d7 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 26 Sep 2021 16:52:37 +0200 Subject: [PATCH] feat: dark mode (#32) --- web/public/index.html | 2 +- web/src/App.tsx | 17 +- web/src/components/EmptyListState.tsx | 6 +- web/src/components/FilterActionList.tsx | 6 +- web/src/components/debug.tsx | 4 +- web/src/components/empty/EmptySimple.tsx | 10 +- web/src/components/headings/TitleSubtitle.tsx | 4 +- web/src/components/inputs/PasswordField.tsx | 12 +- web/src/components/inputs/SwitchGroup.tsx | 26 +- web/src/components/inputs/TextField.tsx | 14 +- web/src/components/inputs/TextFieldWide.tsx | 8 +- .../components/inputs/wide/NumberField.tsx | 10 +- .../components/inputs/wide/PasswordField.tsx | 4 +- .../inputs/wide/RadioFieldsetWide.tsx | 18 +- web/src/components/modals/Delete.tsx | 25 +- web/src/components/notifications/Toast.tsx | 13 +- web/src/components/panels/SlideOver.tsx | 140 ++++ web/src/components/panels/index.ts | 1 + web/src/forms/filters/FilterAddForm.tsx | 71 +- web/src/forms/index.ts | 10 +- .../forms/settings/DownloadClientForms.tsx | 617 ++++++++++++++++++ .../{IndexerAddForm.tsx => IndexerForms.tsx} | 158 ++++- web/src/forms/settings/IndexerUpdateForm.tsx | 288 -------- web/src/forms/settings/IrcForms.tsx | 347 ++++++++++ web/src/forms/settings/IrcNetworkAddForm.tsx | 254 ------- .../forms/settings/IrcNetworkUpdateForm.tsx | 297 --------- .../downloadclient/DownloadClientAddForm.tsx | 245 ------- .../DownloadClientUpdateForm.tsx | 279 -------- .../forms/settings/downloadclient/shared.tsx | 122 ---- web/src/screens/Base.tsx | 46 +- web/src/screens/Dashboard.tsx | 8 +- web/src/screens/Logs.tsx | 2 +- web/src/screens/Settings.tsx | 13 +- web/src/screens/auth/login.tsx | 26 +- web/src/screens/auth/logout.tsx | 6 +- web/src/screens/filters/details.tsx | 100 ++- .../filters/inputs/DownloadClientSelect.tsx | 20 +- .../screens/filters/inputs/MultiSelect.tsx | 31 +- .../screens/filters/inputs/NumberField.tsx | 9 +- web/src/screens/filters/inputs/Select.tsx | 14 +- web/src/screens/filters/inputs/Switch.tsx | 6 +- .../screens/filters/inputs/SwitchGroup.tsx | 10 +- web/src/screens/filters/inputs/TextField.tsx | 6 +- web/src/screens/filters/list.tsx | 40 +- web/src/screens/settings/Application.tsx | 28 +- web/src/screens/settings/DownloadClient.tsx | 109 ++-- web/src/screens/settings/Indexer.tsx | 129 ++-- web/src/screens/settings/Irc.tsx | 115 ++-- web/tailwind.config.js | 2 +- 49 files changed, 1736 insertions(+), 1992 deletions(-) create mode 100644 web/src/components/panels/SlideOver.tsx create mode 100644 web/src/components/panels/index.ts create mode 100644 web/src/forms/settings/DownloadClientForms.tsx rename web/src/forms/settings/{IndexerAddForm.tsx => IndexerForms.tsx} (70%) delete mode 100644 web/src/forms/settings/IndexerUpdateForm.tsx create mode 100644 web/src/forms/settings/IrcForms.tsx delete mode 100644 web/src/forms/settings/IrcNetworkAddForm.tsx delete mode 100644 web/src/forms/settings/IrcNetworkUpdateForm.tsx delete mode 100644 web/src/forms/settings/downloadclient/DownloadClientAddForm.tsx delete mode 100644 web/src/forms/settings/downloadclient/DownloadClientUpdateForm.tsx delete mode 100644 web/src/forms/settings/downloadclient/shared.tsx diff --git a/web/public/index.html b/web/public/index.html index 16c0fbf..dfcf9ab 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -26,7 +26,7 @@ - +
diff --git a/web/src/App.tsx b/web/src/App.tsx index 5ce99a0..7c60bb6 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,12 +1,11 @@ -import React from "react"; -import {QueryClient, QueryClientProvider} from "react-query"; -import {BrowserRouter as Router, Route, Switch} from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "react-query"; +import { BrowserRouter as Router, Route } from "react-router-dom"; import Login from "./screens/auth/login"; import Logout from "./screens/auth/logout"; import Base from "./screens/Base"; -import {ReactQueryDevtools} from "react-query/devtools"; +import { ReactQueryDevtools } from "react-query/devtools"; import Layout from "./components/Layout"; -import {baseUrl} from "./utils/utils"; +import { baseUrl } from "./utils/utils"; function Protected() { return ( @@ -22,11 +21,11 @@ function App() { return ( - - - + + + - + ) }; diff --git a/web/src/components/EmptyListState.tsx b/web/src/components/EmptyListState.tsx index 9eeb686..65d9b8b 100644 --- a/web/src/components/EmptyListState.tsx +++ b/web/src/components/EmptyListState.tsx @@ -1,5 +1,3 @@ -import React from "react"; - interface props { text: string; buttonText?: string; @@ -9,11 +7,11 @@ interface props { export function EmptyListState({ text, buttonText, buttonOnClick }: props) { return (
-

{text}

+

{text}

{buttonText && buttonOnClick && ( diff --git a/web/src/components/debug.tsx b/web/src/components/debug.tsx index 1bc059a..9a59580 100644 --- a/web/src/components/debug.tsx +++ b/web/src/components/debug.tsx @@ -1,5 +1,3 @@ -import React from "react"; - const DEBUG = ({ values }: any) => { if (process.env.NODE_ENV !== "development") { return null; @@ -7,7 +5,7 @@ const DEBUG = ({ values }: any) => { return (
-
{JSON.stringify(values, 0 as any, 2)}
+
{JSON.stringify(values, 0 as any, 2)}
); }; diff --git a/web/src/components/empty/EmptySimple.tsx b/web/src/components/empty/EmptySimple.tsx index e344034..75679c7 100644 --- a/web/src/components/empty/EmptySimple.tsx +++ b/web/src/components/empty/EmptySimple.tsx @@ -1,4 +1,4 @@ -import {PlusIcon} from "@heroicons/react/solid"; +import { PlusIcon } from "@heroicons/react/solid"; interface props { title: string; @@ -7,15 +7,15 @@ interface props { buttonAction: any; } -const EmptySimple = ({ title, subtitle, buttonText, buttonAction}: props) => ( +const EmptySimple = ({ title, subtitle, buttonText, buttonAction }: props) => (
-

{title}

-

{subtitle}

+

{title}

+

{subtitle}

+
+
+
+ + {children !== undefined && children(values)} + + + +
+
+ {type === "UPDATE" && ( + + )} +
+ + + +
+
+
+ + + + ) + }} + + + + + + + + + + ) +} + +export default SlideOver; diff --git a/web/src/components/panels/index.ts b/web/src/components/panels/index.ts new file mode 100644 index 0000000..4f639f7 --- /dev/null +++ b/web/src/components/panels/index.ts @@ -0,0 +1 @@ +export { default as SlideOver } from "./SlideOver"; \ No newline at end of file diff --git a/web/src/forms/filters/FilterAddForm.tsx b/web/src/forms/filters/FilterAddForm.tsx index 8c08a02..6deb607 100644 --- a/web/src/forms/filters/FilterAddForm.tsx +++ b/web/src/forms/filters/FilterAddForm.tsx @@ -1,20 +1,17 @@ -import React, {Fragment, useEffect} from "react"; -import {useMutation} from "react-query"; -import {Filter} from "../../domain/interfaces"; -import {queryClient} from "../../App"; -import {XIcon} from "@heroicons/react/solid"; -import {Dialog, Transition} from "@headlessui/react"; -import {Field, Form} from "react-final-form"; +import { Fragment, useEffect } from "react"; +import { useMutation } from "react-query"; +import { Filter } from "../../domain/interfaces"; +import { queryClient } from "../../App"; +import { XIcon } from "@heroicons/react/solid"; +import { Dialog, Transition } from "@headlessui/react"; +import { Field, Form } from "react-final-form"; import DEBUG from "../../components/debug"; import APIClient from "../../api/APIClient"; import { toast } from 'react-hot-toast' import Toast from '../../components/notifications/Toast'; - -const required = (value: any) => (value ? undefined : 'Required') - -function FilterAddForm({isOpen, toggle}: any) { +function FilterAddForm({ isOpen, toggle }: any) { const mutation = useMutation((filter: Filter) => APIClient.filters.create(filter), { onSuccess: () => { queryClient.invalidateQueries('filter'); @@ -32,11 +29,21 @@ function FilterAddForm({isOpen, toggle}: any) { mutation.mutate(data) } + const validate = (values: any) => { + const errors = {} as any; + + if (!values.name) { + errors.name = "Required"; + } + + return errors; + } + return (
- +
-
+
- {({handleSubmit, values}) => { + {({ handleSubmit, values }) => { return ( - +
- {/* Header */} -
+
- Create - filter -

+ Create filter +

Add new filter.

- {/* Divider container */}
- - {({input, meta}) => ( + + {({ input, meta }) => (
{meta.touched && meta.error && - {meta.error}} + {meta.error}}
)}
@@ -122,24 +125,24 @@ function FilterAddForm({isOpen, toggle}: any) {
+ className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
- + ) }} diff --git a/web/src/forms/index.ts b/web/src/forms/index.ts index b27d312..934da4a 100644 --- a/web/src/forms/index.ts +++ b/web/src/forms/index.ts @@ -2,10 +2,6 @@ export { default as FilterAddForm } from "./filters/FilterAddForm"; export { default as FilterActionAddForm } from "./filters/FilterActionAddForm"; export { default as FilterActionUpdateForm } from "./filters/FilterActionUpdateForm"; -export { default as DownloadClientAddForm } from "./settings/downloadclient/DownloadClientAddForm"; -export { default as DownloadClientUpdateForm } from "./settings/downloadclient/DownloadClientUpdateForm"; - -export { default as IndexerAddForm } from "./settings/IndexerAddForm"; -export { default as IndexerUpdateForm } from "./settings/IndexerUpdateForm"; - -export { default as IrcNetworkAddForm } from "./settings/IrcNetworkAddForm"; +export { DownloadClientAddForm, DownloadClientUpdateForm } from "./settings/DownloadClientForms"; +export { IndexerAddForm, IndexerUpdateForm } from "./settings/IndexerForms"; +export { IrcNetworkAddForm, IrcNetworkUpdateForm } from "./settings/IrcForms"; diff --git a/web/src/forms/settings/DownloadClientForms.tsx b/web/src/forms/settings/DownloadClientForms.tsx new file mode 100644 index 0000000..ae9649a --- /dev/null +++ b/web/src/forms/settings/DownloadClientForms.tsx @@ -0,0 +1,617 @@ +import { Fragment, useRef, useState } from "react"; +import { useMutation } from "react-query"; +import { + DOWNLOAD_CLIENT_TYPES, + DownloadClient, +} from "../../domain/interfaces"; +import { Dialog, Transition } from "@headlessui/react"; +import { XIcon } from "@heroicons/react/solid"; +import { classNames } from "../../styles/utils"; +import { Form, useField } from "react-final-form"; +import DEBUG from "../../components/debug"; +import { SwitchGroup, TextFieldWide } from "../../components/inputs"; +import { queryClient } from "../../App"; +import APIClient from "../../api/APIClient"; +import { sleep } from "../../utils/utils"; +import { DownloadClientTypeOptions } from "../../domain/constants"; +import { NumberFieldWide, PasswordFieldWide, RadioFieldsetWide } from "../../components/inputs/wide"; + +import { toast } from 'react-hot-toast' +import Toast from '../../components/notifications/Toast'; +import { useToggle } from "../../hooks/hooks"; +import { DeleteModal } from "../../components/modals"; + +function FormFieldsDefault() { + return ( + + + + + +
+ +
+ + + +
+ ); +} + +function FormFieldsArr() { + const { input } = useField("settings.basic.auth"); + return ( + + + + + +
+ +
+ + {input.value === true && ( + + + + + )} +
+ ); +} + +export const componentMap: any = { + DELUGE_V1: , + DELUGE_V2: , + QBITTORRENT: , + RADARR: , + SONARR: , + LIDARR: , +}; + + +function FormFieldsRulesBasic() { + const { input: enabled } = useField("settings.rules.enabled"); + + return ( +
+ +
+ Rules +

+ Manage max downloads. +

+
+ +
+ +
+ + {enabled.value === true && ( + + + + )} +
+ ); +} + +function FormFieldsRules() { + const { input } = useField("settings.rules.ignore_slow_torrents"); + const { input: enabled } = useField("settings.rules.enabled"); + + return ( +
+ +
+ Rules +

+ Manage max downloads etc. +

+
+ +
+ +
+ + {enabled.value === true && ( + + +
+ +
+ + {input.value === true && ( + + + + )} +
+ )} +
+ ); +} + +export const rulesComponentMap: any = { + DELUGE_V1: , + DELUGE_V2: , + QBITTORRENT: , +}; + +export function DownloadClientAddForm({ isOpen, toggle }: any) { + const [isTesting, setIsTesting] = useState(false); + const [isSuccessfulTest, setIsSuccessfulTest] = useState(false); + const [isErrorTest, setIsErrorTest] = useState(false); + + const mutation = useMutation( + (client: DownloadClient) => APIClient.download_clients.create(client), + { + onSuccess: () => { + queryClient.invalidateQueries(["downloadClients"]); + toast.custom((t) => ) + + toggle(); + }, + onError: () => { + toast.custom((t) => ) + } + } + ); + + const testClientMutation = useMutation( + (client: DownloadClient) => APIClient.download_clients.test(client), + { + onMutate: () => { + setIsTesting(true); + setIsErrorTest(false); + setIsSuccessfulTest(false); + }, + onSuccess: () => { + sleep(1000) + .then(() => { + setIsTesting(false); + setIsSuccessfulTest(true); + }) + .then(() => { + sleep(2500).then(() => { + setIsSuccessfulTest(false); + }); + }); + }, + onError: (error) => { + console.log('not added') + setIsTesting(false); + setIsErrorTest(true); + sleep(2500).then(() => { + setIsErrorTest(false); + }); + }, + } + ); + + const onSubmit = (data: any) => { + mutation.mutate(data); + }; + + const testClient = (data: any) => { + testClientMutation.mutate(data); + }; + + return ( + + +
+ + +
+ +
+
+ {({ handleSubmit, values }) => { + return ( + +
+
+
+
+ + Add client + +

+ Add download client. +

+
+
+ +
+
+
+ +
+ + +
+ +
+ + + +
{componentMap[values.type]}
+
+
+ + {rulesComponentMap[values.type]} + +
+
+ + + +
+
+ + + + ); + }} + +
+
+
+
+
+
+ ); +} + +export function DownloadClientUpdateForm({ client, isOpen, toggle }: any) { + const [isTesting, setIsTesting] = useState(false); + const [isSuccessfulTest, setIsSuccessfulTest] = useState(false); + const [isErrorTest, setIsErrorTest] = useState(false); + const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false); + + const mutation = useMutation( + (client: DownloadClient) => APIClient.download_clients.update(client), + { + onSuccess: () => { + queryClient.invalidateQueries(["downloadClients"]); + toast.custom((t) => ) + toggle(); + }, + } + ); + + const deleteMutation = useMutation( + (clientID: number) => APIClient.download_clients.delete(clientID), + { + onSuccess: () => { + queryClient.invalidateQueries(); + toast.custom((t) => ) + toggleDeleteModal(); + }, + } + ); + + const testClientMutation = useMutation( + (client: DownloadClient) => APIClient.download_clients.test(client), + { + onMutate: () => { + setIsTesting(true); + setIsErrorTest(false); + setIsSuccessfulTest(false); + }, + onSuccess: () => { + sleep(1000) + .then(() => { + setIsTesting(false); + setIsSuccessfulTest(true); + }) + .then(() => { + sleep(2500).then(() => { + setIsSuccessfulTest(false); + }); + }); + }, + onError: (error) => { + setIsTesting(false); + setIsErrorTest(true); + sleep(2500).then(() => { + setIsErrorTest(false); + }); + }, + } + ); + + const onSubmit = (data: any) => { + mutation.mutate(data); + }; + + const cancelButtonRef = useRef(null); + const cancelModalButtonRef = useRef(null); + + const deleteAction = () => { + deleteMutation.mutate(client.id); + }; + + const testClient = (data: any) => { + testClientMutation.mutate(data); + }; + + return ( + + + +
+ + +
+ +
+
+ {({ handleSubmit, values }) => { + return ( + +
+
+
+
+ + Edit client + +

+ Edit download client settings. +

+
+
+ +
+
+
+ +
+ + +
+ +
+ + + +
{componentMap[values.type]}
+
+
+ + {rulesComponentMap[values.type]} + +
+
+ +
+ + + + +
+
+
+ + + ); + }} + +
+
+
+
+
+
+ ); +} diff --git a/web/src/forms/settings/IndexerAddForm.tsx b/web/src/forms/settings/IndexerForms.tsx similarity index 70% rename from web/src/forms/settings/IndexerAddForm.tsx rename to web/src/forms/settings/IndexerForms.tsx index 9469b8b..c5e82bd 100644 --- a/web/src/forms/settings/IndexerAddForm.tsx +++ b/web/src/forms/settings/IndexerForms.tsx @@ -13,12 +13,14 @@ import APIClient from "../../api/APIClient"; import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide"; import { toast } from 'react-hot-toast' import Toast from '../../components/notifications/Toast'; -interface props { +import { SlideOver } from "../../components/panels"; + +interface AddProps { isOpen: boolean; toggle: any; } -function IndexerAddForm({ isOpen, toggle }: props) { +export function IndexerAddForm({ isOpen, toggle }: AddProps) { const { data } = useQuery('indexerSchema', APIClient.indexers.getSchema, { enabled: isOpen, @@ -40,7 +42,7 @@ function IndexerAddForm({ isOpen, toggle }: props) { const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), { onSuccess: (data) => { - console.log("irc mutation: ", data); + // console.log("irc mutation: ", data); // queryClient.invalidateQueries(['indexer']); // sleep(1500) @@ -97,13 +99,14 @@ function IndexerAddForm({ isOpen, toggle }: props) { switch (f.type) { case "text": return ( - + ) case "secret": return ( ) } + return null })}