| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765 |
- import * as React from "react";
- import { useContext, useEffect, useState } from "react";
- import {
- Alert,
- CardActions,
- CardContent,
- Chip,
- FormControl,
- Select,
- Stack,
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableRow,
- Tooltip,
- useMediaQuery,
- Typography,
- IconButton,
- Container,
- TextField,
- MenuItem,
- Card,
- Button,
- Dialog,
- DialogTitle,
- DialogContent,
- DialogActions,
- useTheme,
- } from "@mui/material";
- import EditIcon from "@mui/icons-material/Edit";
- import CloseIcon from "@mui/icons-material/Close";
- import PlayArrowIcon from "@mui/icons-material/PlayArrow";
- import { useLiveQuery } from "dexie-react-hooks";
- import { useTranslation } from "react-i18next";
- import { Info } from "@mui/icons-material";
- import { useOutletContext } from "react-router-dom";
- import userManager from "../app/UserManager";
- import { playSound, shortUrl, shuffle, sounds, validUrl } from "../app/utils";
- import session from "../app/Session";
- import routes from "./routes";
- import accountApi, { Permission, Role } from "../app/AccountApi";
- import { Pref, PrefGroup } from "./Pref";
- import { AccountContext } from "./App";
- import { Paragraph } from "./styles";
- import prefs, { THEME } from "../app/Prefs";
- import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons";
- import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./ReserveDialogs";
- import { UnauthorizedError } from "../app/errors";
- import { subscribeTopic } from "./SubscribeDialog";
- import notifier from "../app/Notifier";
- import { useIsLaunchedPWA, useNotificationPermissionListener } from "./hooks";
- 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) {
- await session.resetAndRedirect(routes.login);
- }
- }
- };
- const Preferences = () => (
- <Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}>
- <Stack spacing={3}>
- <Notifications />
- <Reservations />
- <Users />
- <Appearance />
- </Stack>
- </Container>
- );
- const Notifications = () => {
- const { t } = useTranslation();
- const isLaunchedPWA = useIsLaunchedPWA();
- const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
- return (
- <Card sx={{ p: 3 }} aria-label={t("prefs_notifications_title")}>
- <Typography variant="h5" sx={{ marginBottom: 2 }}>
- {t("prefs_notifications_title")}
- </Typography>
- <PrefGroup>
- <Sound />
- <MinPriority />
- <DeleteAfter />
- {!isLaunchedPWA && pushPossible && <WebPushEnabled />}
- </PrefGroup>
- </Card>
- );
- };
- 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 (
- <Pref labelId={labelId} title={t("prefs_notifications_sound_title")} description={description}>
- <div style={{ display: "flex", width: "100%" }}>
- <FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
- <Select value={sound} onChange={handleChange} aria-labelledby={labelId}>
- <MenuItem value="none">{t("prefs_notifications_sound_no_sound")}</MenuItem>
- {Object.entries(sounds).map((s) => (
- <MenuItem key={s[0]} value={s[0]}>
- {s[1].label}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
- <IconButton onClick={() => playSound(sound)} disabled={sound === "none"} aria-label={t("prefs_notifications_sound_play")}>
- <PlayArrowIcon />
- </IconButton>
- </div>
- </Pref>
- );
- };
- 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 (
- <Pref labelId={labelId} title={t("prefs_notifications_min_priority_title")} description={description}>
- <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
- <Select value={minPriority} onChange={handleChange} aria-labelledby={labelId}>
- <MenuItem value={1}>{t("prefs_notifications_min_priority_any")}</MenuItem>
- <MenuItem value={2}>{t("prefs_notifications_min_priority_low_and_higher")}</MenuItem>
- <MenuItem value={3}>{t("prefs_notifications_min_priority_default_and_higher")}</MenuItem>
- <MenuItem value={4}>{t("prefs_notifications_min_priority_high_and_higher")}</MenuItem>
- <MenuItem value={5}>{t("prefs_notifications_min_priority_max_only")}</MenuItem>
- </Select>
- </FormControl>
- </Pref>
- );
- };
- 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");
- default:
- return "";
- }
- })();
- return (
- <Pref labelId={labelId} title={t("prefs_notifications_delete_after_title")} description={description}>
- <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
- <Select value={deleteAfter} onChange={handleChange} aria-labelledby={labelId}>
- <MenuItem value={0}>{t("prefs_notifications_delete_after_never")}</MenuItem>
- <MenuItem value={10800}>{t("prefs_notifications_delete_after_three_hours")}</MenuItem>
- <MenuItem value={86400}>{t("prefs_notifications_delete_after_one_day")}</MenuItem>
- <MenuItem value={604800}>{t("prefs_notifications_delete_after_one_week")}</MenuItem>
- <MenuItem value={2592000}>{t("prefs_notifications_delete_after_one_month")}</MenuItem>
- </Select>
- </FormControl>
- </Pref>
- );
- };
- const Theme = () => {
- const { t } = useTranslation();
- const labelId = "prefTheme";
- const theme = useLiveQuery(async () => prefs.theme());
- const handleChange = async (ev) => {
- await prefs.setTheme(ev.target.value);
- };
- return (
- <Pref labelId={labelId} title={t("prefs_appearance_theme_title")}>
- <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
- <Select value={theme ?? THEME.SYSTEM} onChange={handleChange} aria-labelledby={labelId}>
- <MenuItem value={THEME.SYSTEM}>{t("prefs_appearance_theme_system")}</MenuItem>
- <MenuItem value={THEME.DARK}>{t("prefs_appearance_theme_dark")}</MenuItem>
- <MenuItem value={THEME.LIGHT}>{t("prefs_appearance_theme_light")}</MenuItem>
- </Select>
- </FormControl>
- </Pref>
- );
- };
- const WebPushEnabled = () => {
- const { t } = useTranslation();
- const labelId = "prefWebPushEnabled";
- const enabled = useLiveQuery(async () => prefs.webPushEnabled());
- const handleChange = async (ev) => {
- await prefs.setWebPushEnabled(ev.target.value);
- };
- return (
- <Pref
- labelId={labelId}
- title={t("prefs_notifications_web_push_title")}
- description={enabled ? t("prefs_notifications_web_push_enabled_description") : t("prefs_notifications_web_push_disabled_description")}
- >
- <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
- <Select value={enabled ?? false} onChange={handleChange} aria-labelledby={labelId}>
- <MenuItem value>{t("prefs_notifications_web_push_enabled", { server: shortUrl(config.base_url) })}</MenuItem>
- <MenuItem value={false}>{t("prefs_notifications_web_push_disabled")}</MenuItem>
- </Select>
- </FormControl>
- </Pref>
- );
- };
- 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 (
- <Card sx={{ padding: 1 }} aria-label={t("prefs_users_title")}>
- <CardContent sx={{ paddingBottom: 1 }}>
- <Typography variant="h5" sx={{ marginBottom: 2 }}>
- {t("prefs_users_title")}
- </Typography>
- <Paragraph>
- {t("prefs_users_description")}
- {session.exists() && <>{` ${t("prefs_users_description_no_sync")}`}</>}
- </Paragraph>
- {users?.length > 0 && <UserTable users={users} />}
- </CardContent>
- <CardActions>
- <Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button>
- <UserDialog
- key={`userAddDialog${dialogKey}`}
- open={dialogOpen}
- user={null}
- users={users}
- onCancel={handleDialogCancel}
- onSubmit={handleDialogSubmit}
- />
- </CardActions>
- </Card>
- );
- };
- 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 (
- <Table size="small" aria-label={t("prefs_users_table")}>
- <TableHead>
- <TableRow>
- <TableCell sx={{ paddingLeft: 0 }}>{t("prefs_users_table_user_header")}</TableCell>
- <TableCell>{t("prefs_users_table_base_url_header")}</TableCell>
- <TableCell />
- </TableRow>
- </TableHead>
- <TableBody>
- {props.users?.map((user) => (
- <TableRow key={user.baseUrl} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
- <TableCell component="th" scope="row" sx={{ paddingLeft: 0 }} aria-label={t("prefs_users_table_user_header")}>
- {user.username}
- </TableCell>
- <TableCell aria-label={t("prefs_users_table_base_url_header")}>{user.baseUrl}</TableCell>
- <TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
- {(!session.exists() || user.baseUrl !== config.base_url) && (
- <>
- <IconButton onClick={() => handleEditClick(user)} aria-label={t("prefs_users_edit_button")}>
- <EditIcon />
- </IconButton>
- <IconButton onClick={() => handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}>
- <CloseIcon />
- </IconButton>
- </>
- )}
- {session.exists() && user.baseUrl === config.base_url && (
- <Tooltip title={t("prefs_users_table_cannot_delete_or_edit")}>
- <span>
- <IconButton disabled>
- <EditIcon />
- </IconButton>
- <IconButton disabled>
- <CloseIcon />
- </IconButton>
- </span>
- </Tooltip>
- )}
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- <UserDialog
- key={`userEditDialog${dialogKey}`}
- open={dialogOpen}
- user={dialogUser}
- users={props.users}
- onCancel={handleDialogCancel}
- onSubmit={handleDialogSubmit}
- />
- </Table>
- );
- };
- const UserDialog = (props) => {
- const theme = useTheme();
- 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 baseUrlValid = baseUrl.length === 0 || validUrl(baseUrl);
- const baseUrlExists = props.users?.map((user) => user.baseUrl).includes(baseUrl);
- const baseUrlError = baseUrl.length > 0 && (!baseUrlValid || baseUrlExists);
- const addButtonEnabled = (() => {
- if (editMode) {
- return username.length > 0 && password.length > 0;
- }
- return validUrl(baseUrl) && !baseUrlExists && username.length > 0 && password.length > 0;
- })();
- const handleSubmit = async () => {
- props.onSubmit({
- baseUrl,
- username,
- password,
- });
- };
- useEffect(() => {
- if (editMode) {
- setBaseUrl(props.user.baseUrl);
- setUsername(props.user.username);
- setPassword(props.user.password);
- }
- }, [editMode, props.user]);
- return (
- <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
- <DialogTitle>{editMode ? t("prefs_users_dialog_title_edit") : t("prefs_users_dialog_title_add")}</DialogTitle>
- <DialogContent>
- {!editMode && (
- <TextField
- autoFocus
- margin="dense"
- id="baseUrl"
- label={t("prefs_users_dialog_base_url_label")}
- aria-label={t("prefs_users_dialog_base_url_label")}
- value={baseUrl}
- onChange={(ev) => setBaseUrl(ev.target.value)}
- type="url"
- fullWidth
- variant="standard"
- error={baseUrlError}
- helperText={
- baseUrl.length > 0 && !baseUrlValid
- ? t("prefs_users_dialog_base_url_invalid")
- : baseUrlExists
- ? t("prefs_users_dialog_base_url_exists")
- : ""
- }
- />
- )}
- <TextField
- autoFocus={editMode}
- margin="dense"
- id="username"
- label={t("prefs_users_dialog_username_label")}
- aria-label={t("prefs_users_dialog_username_label")}
- value={username}
- onChange={(ev) => setUsername(ev.target.value)}
- type="text"
- fullWidth
- variant="standard"
- />
- <TextField
- margin="dense"
- id="password"
- label={t("prefs_users_dialog_password_label")}
- aria-label={t("prefs_users_dialog_password_label")}
- type="password"
- value={password}
- onChange={(ev) => setPassword(ev.target.value)}
- fullWidth
- variant="standard"
- />
- </DialogContent>
- <DialogActions>
- <Button onClick={props.onCancel}>{t("common_cancel")}</Button>
- <Button onClick={handleSubmit} disabled={!addButtonEnabled}>
- {editMode ? t("common_save") : t("common_add")}
- </Button>
- </DialogActions>
- </Dialog>
- );
- };
- const Appearance = () => {
- const { t } = useTranslation();
- return (
- <Card sx={{ p: 3 }} aria-label={t("prefs_appearance_title")}>
- <Typography variant="h5" sx={{ marginBottom: 2 }}>
- {t("prefs_appearance_title")}
- </Typography>
- <PrefGroup>
- <Theme />
- <Language />
- </PrefGroup>
- </Card>
- );
- };
- const Language = () => {
- const { t, i18n } = useTranslation();
- const labelId = "prefLanguage";
- const lang = i18n.resolvedLanguage ?? "en";
- // Country flags are displayed using emoji. Emoji rendering is handled by platform fonts.
- // Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows.
- const randomFlags = shuffle([
- "🇬🇧",
- "🇺🇸",
- "🇪🇸",
- "🇫🇷",
- "🇧🇬",
- "🇨🇿",
- "🇩🇪",
- "🇵🇱",
- "🇺🇦",
- "🇨🇳",
- "🇮🇹",
- "🇭🇺",
- "🇧🇷",
- "🇳🇱",
- "🇮🇩",
- "🇯🇵",
- "🇷🇺",
- "🇹🇷",
- "🇫🇮",
- ]).slice(0, 3);
- const showFlags = !navigator.userAgent.includes("Windows");
- let title = t("prefs_appearance_language_title");
- if (showFlags) {
- title += ` ${randomFlags.join(" ")}`;
- }
- 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 (
- <Pref labelId={labelId} title={title}>
- <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
- <Select value={lang} onChange={handleChange} aria-labelledby={labelId}>
- <MenuItem value="en">English</MenuItem>
- <MenuItem value="ar">العربية</MenuItem>
- <MenuItem value="id">Bahasa Indonesia</MenuItem>
- <MenuItem value="bg">Български</MenuItem>
- <MenuItem value="cs">Čeština</MenuItem>
- <MenuItem value="zh_Hant">繁體中文</MenuItem>
- <MenuItem value="zh_Hans">简体中文</MenuItem>
- <MenuItem value="da">Dansk</MenuItem>
- <MenuItem value="de">Deutsch</MenuItem>
- <MenuItem value="et">Eesti</MenuItem>
- <MenuItem value="es">Español</MenuItem>
- <MenuItem value="fr">Français</MenuItem>
- <MenuItem value="gl">Galego</MenuItem>
- <MenuItem value="it">Italiano</MenuItem>
- <MenuItem value="hu">Magyar</MenuItem>
- <MenuItem value="ko">한국어</MenuItem>
- <MenuItem value="ja">日本語</MenuItem>
- <MenuItem value="nl">Nederlands</MenuItem>
- <MenuItem value="nb_NO">Norsk bokmål</MenuItem>
- <MenuItem value="uk">Українська</MenuItem>
- <MenuItem value="pt">Português</MenuItem>
- <MenuItem value="pt_BR">Português (Brasil)</MenuItem>
- <MenuItem value="pl">Polski</MenuItem>
- <MenuItem value="ru">Русский</MenuItem>
- <MenuItem value="ro">Română</MenuItem>
- <MenuItem value="sk">Slovenčina</MenuItem>
- <MenuItem value="fi">Suomi</MenuItem>
- <MenuItem value="sv">Svenska</MenuItem>
- <MenuItem value="tr">Türkçe</MenuItem>
- <MenuItem value="ta">தமிழ்</MenuItem>
- </Select>
- </FormControl>
- </Pref>
- );
- };
- const Reservations = () => {
- const { t } = useTranslation();
- const { account } = useContext(AccountContext);
- const [dialogKey, setDialogKey] = useState(0);
- const [dialogOpen, setDialogOpen] = useState(false);
- if (!config.enable_reservations || !session.exists() || !account) {
- return <></>;
- }
- const reservations = account.reservations || [];
- const limitReached = account.role === Role.USER && account.stats.reservations_remaining === 0;
- const handleAddClick = () => {
- setDialogKey((prev) => prev + 1);
- setDialogOpen(true);
- };
- return (
- <Card sx={{ padding: 1 }} aria-label={t("prefs_reservations_title")}>
- <CardContent sx={{ paddingBottom: 1 }}>
- <Typography variant="h5" sx={{ marginBottom: 2 }}>
- {t("prefs_reservations_title")}
- </Typography>
- <Paragraph>{t("prefs_reservations_description")}</Paragraph>
- {reservations.length > 0 && <ReservationsTable reservations={reservations} />}
- {limitReached && <Alert severity="info">{t("prefs_reservations_limit_reached")}</Alert>}
- </CardContent>
- <CardActions>
- <Button onClick={handleAddClick} disabled={limitReached}>
- {t("prefs_reservations_add_button")}
- </Button>
- <ReserveAddDialog
- key={`reservationAddDialog${dialogKey}`}
- open={dialogOpen}
- reservations={reservations}
- onClose={() => setDialogOpen(false)}
- />
- </CardActions>
- </Card>
- );
- };
- const ReservationsTable = (props) => {
- const { t } = useTranslation();
- const [dialogKey, setDialogKey] = useState(0);
- const [dialogReservation, setDialogReservation] = useState(null);
- const [editDialogOpen, setEditDialogOpen] = useState(false);
- const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
- const { subscriptions } = useOutletContext();
- const localSubscriptions =
- subscriptions?.length > 0
- ? Object.assign({}, ...subscriptions.filter((s) => s.baseUrl === config.base_url).map((s) => ({ [s.topic]: s })))
- : {};
- const handleEditClick = (reservation) => {
- setDialogKey((prev) => prev + 1);
- setDialogReservation(reservation);
- setEditDialogOpen(true);
- };
- const handleDeleteClick = async (reservation) => {
- setDialogKey((prev) => prev + 1);
- setDialogReservation(reservation);
- setDeleteDialogOpen(true);
- };
- const handleSubscribeClick = async (reservation) => {
- await subscribeTopic(config.base_url, reservation.topic, {});
- };
- return (
- <Table size="small" aria-label={t("prefs_reservations_table")}>
- <TableHead>
- <TableRow>
- <TableCell sx={{ paddingLeft: 0 }}>{t("prefs_reservations_table_topic_header")}</TableCell>
- <TableCell>{t("prefs_reservations_table_access_header")}</TableCell>
- <TableCell />
- </TableRow>
- </TableHead>
- <TableBody>
- {props.reservations.map((reservation) => (
- <TableRow key={reservation.topic} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
- <TableCell component="th" scope="row" sx={{ paddingLeft: 0 }} aria-label={t("prefs_reservations_table_topic_header")}>
- {reservation.topic}
- </TableCell>
- <TableCell aria-label={t("prefs_reservations_table_access_header")}>
- {reservation.everyone === Permission.READ_WRITE && (
- <>
- <PermissionReadWrite size="small" sx={{ verticalAlign: "bottom", mr: 1.5 }} />
- {t("prefs_reservations_table_everyone_read_write")}
- </>
- )}
- {reservation.everyone === Permission.READ_ONLY && (
- <>
- <PermissionRead size="small" sx={{ verticalAlign: "bottom", mr: 1.5 }} />
- {t("prefs_reservations_table_everyone_read_only")}
- </>
- )}
- {reservation.everyone === Permission.WRITE_ONLY && (
- <>
- <PermissionWrite size="small" sx={{ verticalAlign: "bottom", mr: 1.5 }} />
- {t("prefs_reservations_table_everyone_write_only")}
- </>
- )}
- {reservation.everyone === Permission.DENY_ALL && (
- <>
- <PermissionDenyAll size="small" sx={{ verticalAlign: "bottom", mr: 1.5 }} />
- {t("prefs_reservations_table_everyone_deny_all")}
- </>
- )}
- </TableCell>
- <TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
- {!localSubscriptions[reservation.topic] && (
- <Tooltip title={t("prefs_reservations_table_click_to_subscribe")}>
- <Chip
- icon={<Info />}
- onClick={() => handleSubscribeClick(reservation)}
- label={t("prefs_reservations_table_not_subscribed")}
- color="primary"
- variant="outlined"
- />
- </Tooltip>
- )}
- <IconButton onClick={() => handleEditClick(reservation)} aria-label={t("prefs_reservations_edit_button")}>
- <EditIcon />
- </IconButton>
- <IconButton onClick={() => handleDeleteClick(reservation)} aria-label={t("prefs_reservations_delete_button")}>
- <CloseIcon />
- </IconButton>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- <ReserveEditDialog
- key={`reservationEditDialog${dialogKey}`}
- open={editDialogOpen}
- reservation={dialogReservation}
- reservations={props.reservations}
- onClose={() => setEditDialogOpen(false)}
- />
- <ReserveDeleteDialog
- key={`reservationDeleteDialog${dialogKey}`}
- open={deleteDialogOpen}
- topic={dialogReservation?.topic}
- onClose={() => setDeleteDialogOpen(false)}
- />
- </Table>
- );
- };
- export default Preferences;
|