diff --git a/src/components/manga/sourceConfiguration/CheckBoxPreference.tsx b/src/components/manga/sourceConfiguration/CheckBoxPreference.tsx deleted file mode 100644 index 043dcff5..00000000 --- a/src/components/manga/sourceConfiguration/CheckBoxPreference.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; -import React from 'react'; - -export default function CheckBoxPreference({ title, summary }: CheckBoxPreferenceProps) { - return ( - - - - ); -} diff --git a/src/components/manga/sourceConfiguration/EditTextPreference.tsx b/src/components/manga/sourceConfiguration/EditTextPreference.tsx new file mode 100644 index 00000000..38a6f053 --- /dev/null +++ b/src/components/manga/sourceConfiguration/EditTextPreference.tsx @@ -0,0 +1,80 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import React, { useState } from 'react'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogActions from '@mui/material/DialogActions'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; + +export default function EditTextPreference(props: EditTextPreferenceProps) { + const { + title, summary, dialogTitle, dialogMessage, currentValue, updateValue, + } = props; + + const [internalCurrentValue, setInternalCurrentValue] = useState(currentValue); + const [dialogOpen, setDialogOpen] = useState(false); + + const handleDialogCancel = () => { + setDialogOpen(false); + + // reset the dialog + setInternalCurrentValue(currentValue); + }; + + const handleDialogSubmit = () => { + setDialogOpen(false); + + updateValue(internalCurrentValue); + }; + + return ( + <> + setDialogOpen(true)} + > + + + + + {dialogTitle} + + + + {dialogMessage} + + setInternalCurrentValue(e.target.value)} + /> + + + + + + + + ); +} diff --git a/src/components/manga/sourceConfiguration/ListPreference.tsx b/src/components/manga/sourceConfiguration/ListPreference.tsx new file mode 100644 index 00000000..839e5a15 --- /dev/null +++ b/src/components/manga/sourceConfiguration/ListPreference.tsx @@ -0,0 +1,149 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import React, { useState, useEffect } from 'react'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import RadioGroup from '@mui/material/RadioGroup'; +import Radio from '@mui/material/Radio'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Button from '@mui/material/Button'; + +interface IListDialogProps{ + value: string + open: boolean + onClose: (arg0: string | null) => void + options: string[] +} + +function ListDialog(props: IListDialogProps) { + const { + value: valueProp, open, onClose, options, + } = props; + const [value, setValue] = React.useState(valueProp); + const radioGroupRef = React.useRef(null); + + React.useEffect(() => { + if (!open) { + setValue(valueProp); + } + }, [valueProp, open]); + + const handleEntering = () => { + if (radioGroupRef.current != null) { + radioGroupRef?.current.focus(); + } + }; + + const handleCancel = () => { + onClose(null); + }; + + const handleOk = () => { + onClose(value); + }; + + const handleChange = (event: any) => { + setValue(event.target.value); + }; + + return ( + + Phone Ringtone + + + {options.map((option) => ( + } + label={option} + /> + ))} + + + + + + + + ); +} + +export default function ListPreference(props: ListPreferenceProps) { + const { + title, summary, currentValue, updateValue, entryValues, entries, + } = props; + const [internalCurrentValue, setInternalCurrentValue] = useState(currentValue); + const [dialogOpen, setDialogOpen] = useState(false); + + useEffect(() => { + setInternalCurrentValue(currentValue); + }, [currentValue]); + + const findEntryOf = (value: string) => { + const idx = entryValues.indexOf(value); + return entries[idx]; + }; + + const findEntryValueOf = (value: string) => { + const idx = entries.indexOf(value); + return entryValues[idx]; + }; + + const getSummary = () => { + if (summary === '%s') { + return findEntryOf(currentValue); + } + return summary; + }; + + const handleDialogClose = (newValue: string | null) => { + if (newValue !== null) { + updateValue(findEntryValueOf(newValue)); + + // appear smooth + setInternalCurrentValue(newValue); + } + + setDialogOpen(false); + }; + + return ( + <> + setDialogOpen(true)} + > + + + + + ); +} diff --git a/src/components/manga/sourceConfiguration/TwoStatePreference.tsx b/src/components/manga/sourceConfiguration/TwoStatePreference.tsx new file mode 100644 index 00000000..674ad781 --- /dev/null +++ b/src/components/manga/sourceConfiguration/TwoStatePreference.tsx @@ -0,0 +1,59 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import React, { useState, useEffect } from 'react'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import Switch from '@mui/material/Switch'; +import Checkbox from '@mui/material/Checkbox'; + +function getTwoStateType(type: 'Checkbox' | 'Switch') { + if (type === 'Switch') { return Switch; } + return Checkbox; +} + +function TwoSatePreference(props: TwoStatePreferenceProps) { + const { + title, summary, currentValue, updateValue, type, + } = props; + const [internalCurrentValue, setInternalCurrentValue] = useState(currentValue); + + useEffect(() => { + setInternalCurrentValue(currentValue); + }, [currentValue]); + + return ( + + + + {React.createElement(getTwoStateType(type), + { + edge: 'end', + checked: internalCurrentValue, + onChange: () => { + updateValue(!currentValue); + + // appear smooth + setInternalCurrentValue(!currentValue); + }, + })} + + + ); +} + +export function CheckBoxPreference(props: CheckBoxPreferenceProps) { + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} +export function SwitchPreferenceCompat(props: SwitchPreferenceCompatProps) { + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} + +export default { CheckBoxPreference, SwitchPreferenceCompat }; diff --git a/src/screens/manga/SourceConfigure.tsx b/src/screens/manga/SourceConfigure.tsx index 7f72d7e0..90828a8e 100644 --- a/src/screens/manga/SourceConfigure.tsx +++ b/src/screens/manga/SourceConfigure.tsx @@ -9,13 +9,22 @@ import React, { useContext, useEffect, useState } from 'react'; import NavbarContext from 'context/NavbarContext'; import { useParams } from 'react-router-dom'; import client from 'util/client'; -import CheckBoxPreference from 'components/manga/sourceConfiguration/CheckBoxPreference'; +import { SwitchPreferenceCompat, CheckBoxPreference } from 'components/manga/sourceConfiguration/TwoStatePreference'; +import ListPreference from 'components/manga/sourceConfiguration/ListPreference'; +import EditTextPreference from 'components/manga/sourceConfiguration/EditTextPreference'; import List from '@mui/material/List'; +import cloneObject from 'util/cloneObject'; function getPrefComponent(type: string) { switch (type) { case 'CheckBoxPreference': return CheckBoxPreference; + case 'SwitchPreferenceCompat': + return SwitchPreferenceCompat; + case 'ListPreference': + return ListPreference; + case 'EditTextPreference': + return EditTextPreference; default: return CheckBoxPreference; } @@ -25,6 +34,9 @@ export default function SourceConfigure() { const [sourcePreferences, setSourcePreferences] = useState([]); const { setTitle, setAction } = useContext(NavbarContext); + const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack + const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack + useEffect(() => { setTitle('Source Configuration'); setAction(<>); }, []); const { sourceId } = useParams<{ sourceId: string }>(); @@ -33,14 +45,29 @@ export default function SourceConfigure() { client.get(`/api/v1/source/${sourceId}/preferences`) .then((response) => response.data) .then((data) => setSourcePreferences(data)); - }, []); + }, [updateTriggerHolder]); + + const updateValue = (position: number) => ( + (value: any) => { + client.post(`/api/v1/source/${sourceId}/preferences`, + JSON.stringify({ position, value: value.toString() })) + .then(() => triggerUpdate()); + } + ); return ( <> - {sourcePreferences.map( - (it) => React.createElement(getPrefComponent(it.type), it.props), + (it, index) => { + const props = cloneObject(it.props); + props.updateValue = updateValue(index); + props.key = index; + + // TypeScript is dumb in detecting extra props + // @ts-ignore + return React.createElement(getPrefComponent(it.type), props); + }, )} diff --git a/src/screens/manga/SourceMangas.tsx b/src/screens/manga/SourceMangas.tsx index 791f205e..dc878b46 100644 --- a/src/screens/manga/SourceMangas.tsx +++ b/src/screens/manga/SourceMangas.tsx @@ -24,7 +24,10 @@ export default function SourceMangas(props: { popular: boolean }) { const [lastPageNum, setLastPageNum] = useState(1); useEffect(() => { - setTitle('Source'); + setTitle('Source'); // title is deligated to `MangaGrid` but we set it here once + }, []); + + useEffect(() => { setAction( <> {isConfigurable && ( @@ -34,8 +37,6 @@ export default function SourceMangas(props: { popular: boolean }) { edge="end" color="inherit" size="large" - // TODO: enable source configuration - style={{ display: 'none' }} > diff --git a/src/typings.d.ts b/src/typings.d.ts index fe54e47f..2ad6f657 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -142,7 +142,7 @@ interface INavbarOverride { } type ReaderType = -'ContinuesVertical'| +'ContinuesVertical' | 'Webtoon' | 'SingleVertical' | 'SingleRTL' | @@ -150,7 +150,7 @@ type ReaderType = 'DoubleVertical' | 'DoubleRTL' | 'DoubleLTR' | -'ContinuesHorizontalLTR'| +'ContinuesHorizontalLTR' | 'ContinuesHorizontalRTL'; interface IReaderSettings{ @@ -200,11 +200,6 @@ interface IQueue { queue: IDownloadChapter[] } -interface SourcePreferences { - type: string - props: any -} - interface PreferenceProps { key: string title: string @@ -212,8 +207,29 @@ interface PreferenceProps { defaultValue: any currentValue: any defaultValueType: string + + // intetnal props + updateValue: any } -interface CheckBoxPreferenceProps extends PreferenceProps { +interface TwoStatePreferenceProps extends PreferenceProps { + // intetnal props + type: 'Switch' | 'Checkbox' +} +interface CheckBoxPreferenceProps extends PreferenceProps {} +interface SwitchPreferenceCompatProps extends PreferenceProps {} +interface ListPreferenceProps extends PreferenceProps { + entries: string[] + entryValues: string[] +} +interface EditTextPreferenceProps extends PreferenceProps { + dialogTitle: string + dialogMessage: string + text: string +} + +interface SourcePreferences { + type: string + props: any }