import * as React from 'react'; import {useEffect, useState} from 'react'; import { CardActions, CardContent, FormControl, Select, Stack, styled, Table, TableBody, TableCell, TableHead, TableRow, Tooltip, useMediaQuery } from "@mui/material"; import Typography from "@mui/material/Typography"; import prefs from "../app/Prefs"; import {Paragraph} from "./styles"; import EditIcon from '@mui/icons-material/Edit'; import CloseIcon from "@mui/icons-material/Close"; import IconButton from "@mui/material/IconButton"; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import Container from "@mui/material/Container"; import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; import Card from "@mui/material/Card"; import Button from "@mui/material/Button"; import {useLiveQuery} from "dexie-react-hooks"; import theme from "./theme"; 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 userManager from "../app/UserManager"; import {playSound, shuffle, sounds, validUrl} from "../app/utils"; import {useTranslation} from "react-i18next"; import session from "../app/Session"; import routes from "./routes"; import accountApi, {UnauthorizedError} from "../app/AccountApi"; import {Pref, PrefGroup} from "./Pref"; import {useOutletContext} from "react-router-dom"; import LockIcon from "@mui/icons-material/Lock"; import {Public, PublicOff} from "@mui/icons-material"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; const Preferences = () => { return ( ); }; const Notifications = () => { const { t } = useTranslation(); return ( {t("prefs_notifications_title")} ); }; const Sound = () => { const { t } = useTranslation(); const labelId = "prefSound"; const sound = useLiveQuery(async () => prefs.sound()); const handleChange = async (ev) => { await prefs.setSound(ev.target.value); await maybeUpdateAccountSettings({ notification: { sound: ev.target.value } }); } if (!sound) { return null; // While loading } let description; if (sound === "none") { description = t("prefs_notifications_sound_description_none"); } else { description = t("prefs_notifications_sound_description_some", { sound: sounds[sound].label }); } return (
playSound(sound)} disabled={sound === "none"} aria-label={t("prefs_notifications_sound_play")}>
) }; const MinPriority = () => { const { t } = useTranslation(); const labelId = "prefMinPriority"; const minPriority = useLiveQuery(async () => prefs.minPriority()); const handleChange = async (ev) => { await prefs.setMinPriority(ev.target.value); await maybeUpdateAccountSettings({ notification: { min_priority: ev.target.value } }); } if (!minPriority) { return null; // While loading } const priorities = { 1: t("priority_min"), 2: t("priority_low"), 3: t("priority_default"), 4: t("priority_high"), 5: t("priority_max") } let description; if (minPriority === 1) { description = t("prefs_notifications_min_priority_description_any"); } else if (minPriority === 5) { description = t("prefs_notifications_min_priority_description_max"); } else { description = t("prefs_notifications_min_priority_description_x_or_higher", { number: minPriority, name: priorities[minPriority] }); } return ( ) }; const DeleteAfter = () => { const { t } = useTranslation(); const labelId = "prefDeleteAfter"; const deleteAfter = useLiveQuery(async () => prefs.deleteAfter()); const handleChange = async (ev) => { await prefs.setDeleteAfter(ev.target.value); await maybeUpdateAccountSettings({ notification: { delete_after: ev.target.value } }); } if (deleteAfter === null || deleteAfter === undefined) { // !deleteAfter will not work with "0" return null; // While loading } const description = (() => { switch (deleteAfter) { case 0: return t("prefs_notifications_delete_after_never_description"); case 10800: return t("prefs_notifications_delete_after_three_hours_description"); case 86400: return t("prefs_notifications_delete_after_one_day_description"); case 604800: return t("prefs_notifications_delete_after_one_week_description"); case 2592000: return t("prefs_notifications_delete_after_one_month_description"); } })(); return ( ) }; const Users = () => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); const users = useLiveQuery(() => userManager.all()); const handleAddClick = () => { setDialogKey(prev => prev+1); setDialogOpen(true); }; const handleDialogCancel = () => { setDialogOpen(false); }; const handleDialogSubmit = async (user) => { setDialogOpen(false); try { await userManager.save(user); console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`); } catch (e) { console.log(`[Preferences] Error adding user.`, e); } }; return ( {t("prefs_users_title")} {t("prefs_users_description")} {session.exists() && <>{" " + t("prefs_users_description_no_sync")}} {users?.length > 0 && } ); }; const UserTable = (props) => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); const [dialogUser, setDialogUser] = useState(null); const handleEditClick = (user) => { setDialogKey(prev => prev+1); setDialogUser(user); setDialogOpen(true); }; const handleDialogCancel = () => { setDialogOpen(false); }; const handleDialogSubmit = async (user) => { setDialogOpen(false); try { await userManager.save(user); console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`); } catch (e) { console.log(`[Preferences] Error updating user.`, e); } }; const handleDeleteClick = async (user) => { try { await userManager.delete(user.baseUrl); console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`); } catch (e) { console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e); } }; return ( {t("prefs_users_table_user_header")} {t("prefs_users_table_base_url_header")} {props.users?.map(user => ( {user.username} {user.baseUrl} {(!session.exists() || user.baseUrl !== config.baseUrl) && <> handleEditClick(user)} aria-label={t("prefs_users_edit_button")}> handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}> } {session.exists() && user.baseUrl === config.baseUrl && } ))}
); }; const UserDialog = (props) => { const { t } = useTranslation(); const [baseUrl, setBaseUrl] = useState(""); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const editMode = props.user !== null; const addButtonEnabled = (() => { if (editMode) { return username.length > 0 && password.length > 0; } const baseUrlValid = validUrl(baseUrl); const baseUrlExists = props.users?.map(user => user.baseUrl).includes(baseUrl); return baseUrlValid && !baseUrlExists && username.length > 0 && password.length > 0; })(); const handleSubmit = async () => { props.onSubmit({ baseUrl: baseUrl, username: username, password: password }) }; useEffect(() => { if (editMode) { setBaseUrl(props.user.baseUrl); setUsername(props.user.username); setPassword(props.user.password); } }, [editMode, props.user]); return ( {editMode ? t("prefs_users_dialog_title_edit") : t("prefs_users_dialog_title_add")} {!editMode && setBaseUrl(ev.target.value)} type="url" fullWidth variant="standard" />} setUsername(ev.target.value)} type="text" fullWidth variant="standard" /> setPassword(ev.target.value)} fullWidth variant="standard" /> ); }; const Appearance = () => { const { t } = useTranslation(); return ( {t("prefs_appearance_title")} ); }; const Language = () => { const { t, i18n } = useTranslation(); const labelId = "prefLanguage"; const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇵🇱", "🇺🇦", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇳🇱", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3); const title = t("prefs_appearance_language_title") + " " + randomFlags.join(" "); const lang = i18n.language ?? "en"; const handleChange = async (ev) => { await i18n.changeLanguage(ev.target.value); await maybeUpdateAccountSettings({ language: ev.target.value }); }; // Remember: Flags are not languages. Don't put flags next to the language in the list. // Languages names from: https://www.omniglot.com/language/names.htm // Better: Sidebar in Wikipedia: https://en.wikipedia.org/wiki/Bokm%C3%A5l return ( ) }; const Access = () => { const { t } = useTranslation(); const { account } = useOutletContext(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); const handleAddClick = () => { setDialogKey(prev => prev+1); setDialogOpen(true); }; const handleDialogCancel = () => { setDialogOpen(false); }; const handleDialogSubmit = async (entry) => { setDialogOpen(false); try { await accountApi.addAccessEntry(); console.debug(`[Preferences] Added entry ${entry.topic}`); } catch (e) { console.log(`[Preferences] Error adding access entry.`, e); } }; if (!session.exists() || !account) { return <>; } return ( {t("prefs_access_title")} {t("prefs_access_description")} {account.access.length > 0 && } ); }; const AccessTable = (props) => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); const [dialogEntry, setDialogEntry] = useState(null); const handleEditClick = (entry) => { setDialogKey(prev => prev+1); setDialogEntry(entry); setDialogOpen(true); }; const handleDialogCancel = () => { setDialogOpen(false); }; const handleDialogSubmit = async (user) => { setDialogOpen(false); // FIXME }; const handleDeleteClick = async (user) => { // FIXME }; return ( {t("prefs_access_table_topic_header")} {t("prefs_access_table_access_header")} {props.entries.map(entry => ( {entry.topic} {t("prefs_access_table_perms_private")} handleEditClick(entry)} aria-label={t("prefs_access_edit_button")}> handleDeleteClick(entry)} aria-label={t("prefs_access_delete_button")}> ))}
); }; const AccessDialog = (props) => { const { t } = useTranslation(); const [topic, setTopic] = useState(""); const [access, setAccess] = useState("private"); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const editMode = props.entry !== null; const addButtonEnabled = (() => { // FIXME })(); const handleSubmit = async () => { props.onSubmit({ topic: topic, // FIXME }) }; useEffect(() => { if (editMode) { setTopic(props.topic); //setAccess(props.access); } }, [editMode, props]); return ( {editMode ? t("prefs_access_dialog_title_edit") : t("prefs_access_dialog_title_add")} {!editMode && setTopic(ev.target.value)} type="url" fullWidth variant="standard" />} ); }; const maybeUpdateAccountSettings = async (payload) => { if (!session.exists()) { return; } try { await accountApi.updateSettings(payload); } catch (e) { console.log(`[Preferences] Error updating account settings`, e); if ((e instanceof UnauthorizedError)) { session.resetAndRedirect(routes.login); } } }; export default Preferences;