Preferences.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. import * as React from 'react';
  2. import {useEffect, useState} from 'react';
  3. import {
  4. CardActions,
  5. CardContent,
  6. FormControl,
  7. Select,
  8. Stack,
  9. Table,
  10. TableBody,
  11. TableCell,
  12. TableHead,
  13. TableRow,
  14. useMediaQuery
  15. } from "@mui/material";
  16. import Typography from "@mui/material/Typography";
  17. import prefs from "../app/Prefs";
  18. import {Paragraph} from "./styles";
  19. import EditIcon from '@mui/icons-material/Edit';
  20. import CloseIcon from "@mui/icons-material/Close";
  21. import IconButton from "@mui/material/IconButton";
  22. import PlayArrowIcon from '@mui/icons-material/PlayArrow';
  23. import Container from "@mui/material/Container";
  24. import TextField from "@mui/material/TextField";
  25. import MenuItem from "@mui/material/MenuItem";
  26. import Card from "@mui/material/Card";
  27. import Button from "@mui/material/Button";
  28. import {useLiveQuery} from "dexie-react-hooks";
  29. import theme from "./theme";
  30. import Dialog from "@mui/material/Dialog";
  31. import DialogTitle from "@mui/material/DialogTitle";
  32. import DialogContent from "@mui/material/DialogContent";
  33. import DialogActions from "@mui/material/DialogActions";
  34. import userManager from "../app/UserManager";
  35. import {playSound, shuffle, sounds, validTopic, validUrl} from "../app/utils";
  36. import {useTranslation} from "react-i18next";
  37. const Preferences = () => {
  38. return (
  39. <Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}>
  40. <Stack spacing={3}>
  41. <Notifications/>
  42. <Appearance/>
  43. <Users/>
  44. <AccessControl/>
  45. </Stack>
  46. </Container>
  47. );
  48. };
  49. const Notifications = () => {
  50. const { t } = useTranslation();
  51. return (
  52. <Card sx={{p: 3}} aria-label={t("prefs_notifications_title")}>
  53. <Typography variant="h5" sx={{marginBottom: 2}}>
  54. {t("prefs_notifications_title")}
  55. </Typography>
  56. <PrefGroup>
  57. <Sound/>
  58. <MinPriority/>
  59. <DeleteAfter/>
  60. </PrefGroup>
  61. </Card>
  62. );
  63. };
  64. const Sound = () => {
  65. const { t } = useTranslation();
  66. const labelId = "prefSound";
  67. const sound = useLiveQuery(async () => prefs.sound());
  68. const handleChange = async (ev) => {
  69. await prefs.setSound(ev.target.value);
  70. }
  71. if (!sound) {
  72. return null; // While loading
  73. }
  74. let description;
  75. if (sound === "none") {
  76. description = t("prefs_notifications_sound_description_none");
  77. } else {
  78. description = t("prefs_notifications_sound_description_some", { sound: sounds[sound].label });
  79. }
  80. return (
  81. <Pref labelId={labelId} title={t("prefs_notifications_sound_title")} description={description}>
  82. <div style={{ display: 'flex', width: '100%' }}>
  83. <FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
  84. <Select value={sound} onChange={handleChange} aria-labelledby={labelId}>
  85. <MenuItem value={"none"}>{t("prefs_notifications_sound_no_sound")}</MenuItem>
  86. {Object.entries(sounds).map(s => <MenuItem key={s[0]} value={s[0]}>{s[1].label}</MenuItem>)}
  87. </Select>
  88. </FormControl>
  89. <IconButton onClick={() => playSound(sound)} disabled={sound === "none"} aria-label={t("prefs_notifications_sound_play")}>
  90. <PlayArrowIcon />
  91. </IconButton>
  92. </div>
  93. </Pref>
  94. )
  95. };
  96. const MinPriority = () => {
  97. const { t } = useTranslation();
  98. const labelId = "prefMinPriority";
  99. const minPriority = useLiveQuery(async () => prefs.minPriority());
  100. const handleChange = async (ev) => {
  101. await prefs.setMinPriority(ev.target.value);
  102. }
  103. if (!minPriority) {
  104. return null; // While loading
  105. }
  106. const priorities = {
  107. 1: t("priority_min"),
  108. 2: t("priority_low"),
  109. 3: t("priority_default"),
  110. 4: t("priority_high"),
  111. 5: t("priority_max")
  112. }
  113. let description;
  114. if (minPriority === 1) {
  115. description = t("prefs_notifications_min_priority_description_any");
  116. } else if (minPriority === 5) {
  117. description = t("prefs_notifications_min_priority_description_max");
  118. } else {
  119. description = t("prefs_notifications_min_priority_description_x_or_higher", {
  120. number: minPriority,
  121. name: priorities[minPriority]
  122. });
  123. }
  124. return (
  125. <Pref labelId={labelId} title={t("prefs_notifications_min_priority_title")} description={description}>
  126. <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
  127. <Select value={minPriority} onChange={handleChange} aria-labelledby={labelId}>
  128. <MenuItem value={1}>{t("prefs_notifications_min_priority_any")}</MenuItem>
  129. <MenuItem value={2}>{t("prefs_notifications_min_priority_low_and_higher")}</MenuItem>
  130. <MenuItem value={3}>{t("prefs_notifications_min_priority_default_and_higher")}</MenuItem>
  131. <MenuItem value={4}>{t("prefs_notifications_min_priority_high_and_higher")}</MenuItem>
  132. <MenuItem value={5}>{t("prefs_notifications_min_priority_max_only")}</MenuItem>
  133. </Select>
  134. </FormControl>
  135. </Pref>
  136. )
  137. };
  138. const DeleteAfter = () => {
  139. const { t } = useTranslation();
  140. const labelId = "prefDeleteAfter";
  141. const deleteAfter = useLiveQuery(async () => prefs.deleteAfter());
  142. const handleChange = async (ev) => {
  143. await prefs.setDeleteAfter(ev.target.value);
  144. }
  145. if (deleteAfter === null || deleteAfter === undefined) { // !deleteAfter will not work with "0"
  146. return null; // While loading
  147. }
  148. const description = (() => {
  149. switch (deleteAfter) {
  150. case 0: return t("prefs_notifications_delete_after_never_description");
  151. case 10800: return t("prefs_notifications_delete_after_three_hours_description");
  152. case 86400: return t("prefs_notifications_delete_after_one_day_description");
  153. case 604800: return t("prefs_notifications_delete_after_one_week_description");
  154. case 2592000: return t("prefs_notifications_delete_after_one_month_description");
  155. }
  156. })();
  157. return (
  158. <Pref labelId={labelId} title={t("prefs_notifications_delete_after_title")} description={description}>
  159. <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
  160. <Select value={deleteAfter} onChange={handleChange} aria-labelledby={labelId}>
  161. <MenuItem value={0}>{t("prefs_notifications_delete_after_never")}</MenuItem>
  162. <MenuItem value={10800}>{t("prefs_notifications_delete_after_three_hours")}</MenuItem>
  163. <MenuItem value={86400}>{t("prefs_notifications_delete_after_one_day")}</MenuItem>
  164. <MenuItem value={604800}>{t("prefs_notifications_delete_after_one_week")}</MenuItem>
  165. <MenuItem value={2592000}>{t("prefs_notifications_delete_after_one_month")}</MenuItem>
  166. </Select>
  167. </FormControl>
  168. </Pref>
  169. )
  170. };
  171. const PrefGroup = (props) => {
  172. return (
  173. <div role="table">
  174. {props.children}
  175. </div>
  176. )
  177. };
  178. const Pref = (props) => {
  179. return (
  180. <div
  181. role="row"
  182. style={{
  183. display: "flex",
  184. flexDirection: "row",
  185. marginTop: "10px",
  186. marginBottom: "20px",
  187. }}
  188. >
  189. <div
  190. role="cell"
  191. id={props.labelId}
  192. aria-label={props.title}
  193. style={{
  194. flex: '1 0 40%',
  195. display: 'flex',
  196. flexDirection: 'column',
  197. justifyContent: 'center',
  198. paddingRight: '30px'
  199. }}
  200. >
  201. <div><b>{props.title}</b></div>
  202. {props.description && <div><em>{props.description}</em></div>}
  203. </div>
  204. <div
  205. role="cell"
  206. style={{
  207. flex: '1 0 calc(60% - 50px)',
  208. display: 'flex',
  209. flexDirection: 'column',
  210. justifyContent: 'center'
  211. }}
  212. >
  213. {props.children}
  214. </div>
  215. </div>
  216. );
  217. };
  218. const Users = () => {
  219. const { t } = useTranslation();
  220. const [dialogKey, setDialogKey] = useState(0);
  221. const [dialogOpen, setDialogOpen] = useState(false);
  222. const users = useLiveQuery(() => userManager.all());
  223. const handleAddClick = () => {
  224. setDialogKey(prev => prev+1);
  225. setDialogOpen(true);
  226. };
  227. const handleDialogCancel = () => {
  228. setDialogOpen(false);
  229. };
  230. const handleDialogSubmit = async (user) => {
  231. setDialogOpen(false);
  232. try {
  233. await userManager.save(user);
  234. console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`);
  235. } catch (e) {
  236. console.log(`[Preferences] Error adding user.`, e);
  237. }
  238. };
  239. return (
  240. <Card sx={{ padding: 1 }} aria-label={t("prefs_users_title")}>
  241. <CardContent sx={{ paddingBottom: 1 }}>
  242. <Typography variant="h5" sx={{marginBottom: 2}}>
  243. {t("prefs_users_title")}
  244. </Typography>
  245. <Paragraph>
  246. {t("prefs_users_description")}
  247. </Paragraph>
  248. {users?.length > 0 && <UserTable users={users}/>}
  249. </CardContent>
  250. <CardActions>
  251. <Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button>
  252. <UserDialog
  253. key={`userAddDialog${dialogKey}`}
  254. open={dialogOpen}
  255. user={null}
  256. users={users}
  257. onCancel={handleDialogCancel}
  258. onSubmit={handleDialogSubmit}
  259. />
  260. </CardActions>
  261. </Card>
  262. );
  263. };
  264. const UserTable = (props) => {
  265. const { t } = useTranslation();
  266. const [dialogKey, setDialogKey] = useState(0);
  267. const [dialogOpen, setDialogOpen] = useState(false);
  268. const [dialogUser, setDialogUser] = useState(null);
  269. const handleEditClick = (user) => {
  270. setDialogKey(prev => prev+1);
  271. setDialogUser(user);
  272. setDialogOpen(true);
  273. };
  274. const handleDialogCancel = () => {
  275. setDialogOpen(false);
  276. };
  277. const handleDialogSubmit = async (user) => {
  278. setDialogOpen(false);
  279. try {
  280. await userManager.save(user);
  281. console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`);
  282. } catch (e) {
  283. console.log(`[Preferences] Error updating user.`, e);
  284. }
  285. };
  286. const handleDeleteClick = async (user) => {
  287. try {
  288. await userManager.delete(user.baseUrl);
  289. console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`);
  290. } catch (e) {
  291. console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e);
  292. }
  293. };
  294. return (
  295. <Table size="small" aria-label={t("prefs_users_table")}>
  296. <TableHead>
  297. <TableRow>
  298. <TableCell sx={{paddingLeft: 0}}>{t("prefs_users_table_user_header")}</TableCell>
  299. <TableCell>{t("prefs_users_table_base_url_header")}</TableCell>
  300. <TableCell/>
  301. </TableRow>
  302. </TableHead>
  303. <TableBody>
  304. {props.users?.map(user => (
  305. <TableRow
  306. key={user.baseUrl}
  307. sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
  308. >
  309. <TableCell component="th" scope="row" sx={{paddingLeft: 0}} aria-label={t("prefs_users_table_user_header")}>{user.username}</TableCell>
  310. <TableCell aria-label={t("prefs_users_table_base_url_header")}>{user.baseUrl}</TableCell>
  311. <TableCell align="right">
  312. <IconButton onClick={() => handleEditClick(user)} aria-label={t("prefs_users_edit_button")}>
  313. <EditIcon/>
  314. </IconButton>
  315. <IconButton onClick={() => handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}>
  316. <CloseIcon />
  317. </IconButton>
  318. </TableCell>
  319. </TableRow>
  320. ))}
  321. </TableBody>
  322. <UserDialog
  323. key={`userEditDialog${dialogKey}`}
  324. open={dialogOpen}
  325. user={dialogUser}
  326. users={props.users}
  327. onCancel={handleDialogCancel}
  328. onSubmit={handleDialogSubmit}
  329. />
  330. </Table>
  331. );
  332. };
  333. const UserDialog = (props) => {
  334. const { t } = useTranslation();
  335. const [baseUrl, setBaseUrl] = useState("");
  336. const [username, setUsername] = useState("");
  337. const [password, setPassword] = useState("");
  338. const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
  339. const editMode = props.user !== null;
  340. const addButtonEnabled = (() => {
  341. if (editMode) {
  342. return username.length > 0 && password.length > 0;
  343. }
  344. const baseUrlValid = validUrl(baseUrl);
  345. const baseUrlExists = props.users?.map(user => user.baseUrl).includes(baseUrl);
  346. return baseUrlValid
  347. && !baseUrlExists
  348. && username.length > 0
  349. && password.length > 0;
  350. })();
  351. const handleSubmit = async () => {
  352. props.onSubmit({
  353. baseUrl: baseUrl,
  354. username: username,
  355. password: password
  356. })
  357. };
  358. useEffect(() => {
  359. if (editMode) {
  360. setBaseUrl(props.user.baseUrl);
  361. setUsername(props.user.username);
  362. setPassword(props.user.password);
  363. }
  364. }, [editMode, props.user]);
  365. return (
  366. <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
  367. <DialogTitle>{editMode ? t("prefs_users_dialog_title_edit") : t("prefs_users_dialog_title_add")}</DialogTitle>
  368. <DialogContent>
  369. {!editMode && <TextField
  370. autoFocus
  371. margin="dense"
  372. id="baseUrl"
  373. label={t("prefs_users_dialog_base_url_label")}
  374. aria-label={t("prefs_users_dialog_base_url_label")}
  375. value={baseUrl}
  376. onChange={ev => setBaseUrl(ev.target.value)}
  377. type="url"
  378. fullWidth
  379. variant="standard"
  380. />}
  381. <TextField
  382. autoFocus={editMode}
  383. margin="dense"
  384. id="username"
  385. label={t("prefs_users_dialog_username_label")}
  386. aria-label={t("prefs_users_dialog_username_label")}
  387. value={username}
  388. onChange={ev => setUsername(ev.target.value)}
  389. type="text"
  390. fullWidth
  391. variant="standard"
  392. />
  393. <TextField
  394. margin="dense"
  395. id="password"
  396. label={t("prefs_users_dialog_password_label")}
  397. aria-label={t("prefs_users_dialog_password_label")}
  398. type="password"
  399. value={password}
  400. onChange={ev => setPassword(ev.target.value)}
  401. fullWidth
  402. variant="standard"
  403. />
  404. </DialogContent>
  405. <DialogActions>
  406. <Button onClick={props.onCancel}>{t("prefs_users_dialog_button_cancel")}</Button>
  407. <Button onClick={handleSubmit} disabled={!addButtonEnabled}>{editMode ? t("prefs_users_dialog_button_save") : t("prefs_users_dialog_button_add")}</Button>
  408. </DialogActions>
  409. </Dialog>
  410. );
  411. };
  412. const Appearance = () => {
  413. const { t } = useTranslation();
  414. return (
  415. <Card sx={{p: 3}} aria-label={t("prefs_appearance_title")}>
  416. <Typography variant="h5" sx={{marginBottom: 2}}>
  417. {t("prefs_appearance_title")}
  418. </Typography>
  419. <PrefGroup>
  420. <Language/>
  421. </PrefGroup>
  422. </Card>
  423. );
  424. };
  425. const Language = () => {
  426. const { t, i18n } = useTranslation();
  427. const labelId = "prefLanguage";
  428. const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇵🇱", "🇺🇦", "🇨🇳", "🇮🇹", "🇭🇺", "🇧🇷", "🇳🇱", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3);
  429. const title = t("prefs_appearance_language_title") + " " + randomFlags.join(" ");
  430. const lang = i18n.language ?? "en";
  431. // Remember: Flags are not languages. Don't put flags next to the language in the list.
  432. // Languages names from: https://www.omniglot.com/language/names.htm
  433. // Better: Sidebar in Wikipedia: https://en.wikipedia.org/wiki/Bokm%C3%A5l
  434. return (
  435. <Pref labelId={labelId} title={title}>
  436. <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
  437. <Select value={lang} onChange={(ev) => i18n.changeLanguage(ev.target.value)} aria-labelledby={labelId}>
  438. <MenuItem value="en">English</MenuItem>
  439. <MenuItem value="id">Bahasa Indonesia</MenuItem>
  440. <MenuItem value="bg">Български</MenuItem>
  441. <MenuItem value="cs">Čeština</MenuItem>
  442. <MenuItem value="zh_Hans">中文</MenuItem>
  443. <MenuItem value="de">Deutsch</MenuItem>
  444. <MenuItem value="es">Español</MenuItem>
  445. <MenuItem value="fr">Français</MenuItem>
  446. <MenuItem value="it">Italiano</MenuItem>
  447. <MenuItem value="hu">Magyar</MenuItem>
  448. <MenuItem value="ko">한국어</MenuItem>
  449. <MenuItem value="ja">日本語</MenuItem>
  450. <MenuItem value="nl">Nederlands</MenuItem>
  451. <MenuItem value="nb_NO">Norsk bokmål</MenuItem>
  452. <MenuItem value="uk">Українська</MenuItem>
  453. <MenuItem value="pt_BR">Português (Brasil)</MenuItem>
  454. <MenuItem value="pl">Polski</MenuItem>
  455. <MenuItem value="ru">Русский</MenuItem>
  456. <MenuItem value="tr">Türkçe</MenuItem>
  457. </Select>
  458. </FormControl>
  459. </Pref>
  460. )
  461. };
  462. const AccessControl = () => {
  463. const { t } = useTranslation();
  464. const [dialogKey, setDialogKey] = useState(0);
  465. const [dialogOpen, setDialogOpen] = useState(false);
  466. const entries = useLiveQuery(() => userManager.all());
  467. const handleAddClick = () => {
  468. setDialogKey(prev => prev+1);
  469. setDialogOpen(true);
  470. };
  471. const handleDialogCancel = () => {
  472. setDialogOpen(false);
  473. };
  474. const handleDialogSubmit = async (user) => {
  475. setDialogOpen(false);
  476. try {
  477. await userManager.save(user);
  478. console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`);
  479. } catch (e) {
  480. console.log(`[Preferences] Error adding user.`, e);
  481. }
  482. };
  483. return (
  484. <Card sx={{ padding: 1 }} aria-label={t("prefs_users_title")}>
  485. <CardContent sx={{ paddingBottom: 1 }}>
  486. <Typography variant="h5" sx={{marginBottom: 2}}>
  487. Access control
  488. </Typography>
  489. <Paragraph>
  490. Define read/write access to topics for this server.
  491. </Paragraph>
  492. {entries?.length > 0 && <AccessControlTable entries={entries}/>}
  493. </CardContent>
  494. <CardActions>
  495. <Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button>
  496. <AccessControlDialog
  497. key={`aclDialog${dialogKey}`}
  498. open={dialogOpen}
  499. user={null}
  500. users={entries}
  501. onCancel={handleDialogCancel}
  502. onSubmit={handleDialogSubmit}
  503. />
  504. </CardActions>
  505. </Card>
  506. );
  507. };
  508. const AccessControlTable = (props) => {
  509. const { t } = useTranslation();
  510. const [dialogKey, setDialogKey] = useState(0);
  511. const [dialogOpen, setDialogOpen] = useState(false);
  512. const [dialogUser, setDialogUser] = useState(null);
  513. const handleEditClick = (user) => {
  514. setDialogKey(prev => prev+1);
  515. setDialogUser(user);
  516. setDialogOpen(true);
  517. };
  518. const handleDialogCancel = () => {
  519. setDialogOpen(false);
  520. };
  521. const handleDialogSubmit = async (user) => {
  522. setDialogOpen(false);
  523. try {
  524. await userManager.save(user);
  525. console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`);
  526. } catch (e) {
  527. console.log(`[Preferences] Error updating user.`, e);
  528. }
  529. };
  530. const handleDeleteClick = async (user) => {
  531. try {
  532. await userManager.delete(user.baseUrl);
  533. console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`);
  534. } catch (e) {
  535. console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e);
  536. }
  537. };
  538. return (
  539. <Table size="small" aria-label={t("prefs_users_table")}>
  540. <TableHead>
  541. <TableRow>
  542. <TableCell sx={{paddingLeft: 0}}>Topic</TableCell>
  543. <TableCell>User</TableCell>
  544. <TableCell>Access</TableCell>
  545. <TableCell/>
  546. </TableRow>
  547. </TableHead>
  548. <TableBody>
  549. {props.entries?.map(user => (
  550. <TableRow
  551. key={user.baseUrl}
  552. sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
  553. >
  554. <TableCell component="th" scope="row" sx={{paddingLeft: 0}} aria-label={t("prefs_users_table_user_header")}>{user.username}</TableCell>
  555. <TableCell aria-label={t("xxxxxxxxxx")}>{user.baseUrl}</TableCell>
  556. <TableCell align="right">
  557. <IconButton onClick={() => handleEditClick(user)} aria-label={t("prefs_users_edit_button")}>
  558. <EditIcon/>
  559. </IconButton>
  560. <IconButton onClick={() => handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}>
  561. <CloseIcon />
  562. </IconButton>
  563. </TableCell>
  564. </TableRow>
  565. ))}
  566. </TableBody>
  567. <AccessControlDialog
  568. key={`userEditDialog${dialogKey}`}
  569. open={dialogOpen}
  570. user={dialogUser}
  571. users={props.entries}
  572. onCancel={handleDialogCancel}
  573. onSubmit={handleDialogSubmit}
  574. />
  575. </Table>
  576. );
  577. };
  578. const AccessControlDialog = (props) => {
  579. const { t } = useTranslation();
  580. const [topic, setTopic] = useState("");
  581. const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
  582. const addButtonEnabled = (() => {
  583. return validTopic(topic);
  584. })();
  585. const handleSubmit = async () => {
  586. // TODO
  587. };
  588. return (
  589. <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
  590. <DialogTitle>Add entry</DialogTitle>
  591. <DialogContent>
  592. <TextField
  593. autoFocus={editMode}
  594. margin="dense"
  595. id="topic"
  596. label={"Topic"}
  597. aria-label={"Topic xx"}
  598. value={topic}
  599. onChange={ev => setTopic(ev.target.value)}
  600. type="text"
  601. fullWidth
  602. variant="standard"
  603. />
  604. <FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
  605. <Select value={"read-write"} onChange={() => {}}>
  606. <MenuItem value={"private"}>Read/write access only by me</MenuItem>
  607. <MenuItem value={"read-only"}>Read/write access by user, anonymous read</MenuItem>
  608. </Select>
  609. </FormControl>
  610. </DialogContent>
  611. <DialogActions>
  612. <Button onClick={props.onCancel}>Cancel</Button>
  613. <Button onClick={handleSubmit} disabled={!addButtonEnabled}>Add entry</Button>
  614. </DialogActions>
  615. </Dialog>
  616. );
  617. };
  618. export default Preferences;