Translations
This commit is contained in:
		
							parent
							
								
									3512db1fe7
								
							
						
					
					
						commit
						9be8be49ef
					
				
					 6 changed files with 206 additions and 358 deletions
				
			
		|  | @ -41,9 +41,9 @@ const Account = () => { | |||
| const Basics = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     return ( | ||||
|         <Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}> | ||||
|         <Card sx={{p: 3}} aria-label={t("account_basics_title")}> | ||||
|             <Typography variant="h5" sx={{marginBottom: 2}}> | ||||
|                 Account | ||||
|                 {t("account_basics_title")} | ||||
|             </Typography> | ||||
|             <PrefGroup> | ||||
|                 <Username/> | ||||
|  | @ -53,80 +53,15 @@ const Basics = () => { | |||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Stats = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     const { account } = useOutletContext(); | ||||
|     if (!account) { | ||||
|         return <></>; | ||||
|     } | ||||
|     const accountType = account.plan.code ?? "none"; | ||||
|     const normalize = (value, max) => (value / max * 100); | ||||
|     return ( | ||||
|         <Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}> | ||||
|             <Typography variant="h5" sx={{marginBottom: 2}}> | ||||
|                 {t("Usage")} | ||||
|             </Typography> | ||||
|             <PrefGroup> | ||||
|                 <Pref labelId={"accountType"} title={t("Account type")}> | ||||
|                     <div> | ||||
|                         {account?.role === "admin" | ||||
|                             ? <>Unlimited <Tooltip title={"You are Admin"}><span style={{cursor: "default"}}>👑</span></Tooltip></> | ||||
|                             : t(`account_type_${accountType}`)} | ||||
|                     </div> | ||||
|                 </Pref> | ||||
|                 <Pref labelId={"messages"} title={t("Published messages")}> | ||||
|                     <div> | ||||
|                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography> | ||||
|                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.messages > 0 ? t("of {{limit}}", { limit: account.limits.messages }) : t("Unlimited")}</Typography> | ||||
|                     </div> | ||||
|                     <LinearProgress variant="determinate" value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100} /> | ||||
|                 </Pref> | ||||
|                 <Pref labelId={"emails"} title={t("Emails sent")}> | ||||
|                     <div> | ||||
|                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography> | ||||
|                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.emails > 0 ? t("of {{limit}}", { limit: account.limits.emails }) : t("Unlimited")}</Typography> | ||||
|                     </div> | ||||
|                     <LinearProgress variant="determinate" value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100} /> | ||||
|                 </Pref> | ||||
|                 <Pref labelId={"attachments"} title={t("Attachment storage")} subtitle={t("{{filesize}} per file", { filesize: formatBytes(account.limits.attachment_file_size) })}> | ||||
|                     <div> | ||||
|                         <Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography> | ||||
|                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("of {{limit}}", { limit: formatBytes(account.limits.attachment_total_size) }) : t("Unlimited")}</Typography> | ||||
|                     </div> | ||||
|                     <LinearProgress variant="determinate" value={account.limits.attachment_total_size > 0 ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100} /> | ||||
|                 </Pref> | ||||
|             </PrefGroup> | ||||
|             {account.limits.basis === "ip" && <Typography variant="body1"> | ||||
|                 <em>Usage stats and limits for this account are based on your IP address, so they may be shared | ||||
|                     with other users.</em> | ||||
|             </Typography>} | ||||
|         </Card> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Delete = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     return ( | ||||
|         <Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}> | ||||
|             <Typography variant="h5" sx={{marginBottom: 2}}> | ||||
|                 {t("Delete account")} | ||||
|             </Typography> | ||||
|             <PrefGroup> | ||||
|                 <DeleteAccount/> | ||||
|             </PrefGroup> | ||||
|         </Card> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Username = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     const { account } = useOutletContext(); | ||||
|     return ( | ||||
|         <Pref labelId={"username"} title={t("Username")} description={t("Hey, that's you ❤")}> | ||||
|         <Pref title={t("account_basics_username_title")} description={t("account_basics_username_description")}> | ||||
|             <div> | ||||
|                 {session.username()} | ||||
|                 {account?.role === "admin" | ||||
|                     ? <>{" "}<Tooltip title={"You are Admin"}><span style={{cursor: "default"}}>👑</span></Tooltip></> | ||||
|                     ? <>{" "}<Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></> | ||||
|                     : ""} | ||||
|             </div> | ||||
|         </Pref> | ||||
|  | @ -137,14 +72,16 @@ const ChangePassword = () => { | |||
|     const { t } = useTranslation(); | ||||
|     const [dialogKey, setDialogKey] = useState(0); | ||||
|     const [dialogOpen, setDialogOpen] = useState(false); | ||||
|     const labelId = "prefChangePassword"; | ||||
| 
 | ||||
|     const handleDialogOpen = () => { | ||||
|         setDialogKey(prev => prev+1); | ||||
|         setDialogOpen(true); | ||||
|     }; | ||||
| 
 | ||||
|     const handleDialogCancel = () => { | ||||
|         setDialogOpen(false); | ||||
|     }; | ||||
| 
 | ||||
|     const handleDialogSubmit = async (newPassword) => { | ||||
|         try { | ||||
|             await accountApi.changePassword(newPassword); | ||||
|  | @ -158,11 +95,12 @@ const ChangePassword = () => { | |||
|             // TODO show error
 | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Pref labelId={labelId} title={t("Password")} description={t("Change your account password")}> | ||||
|         <Pref title={t("account_basics_password_title")} description={t("account_basics_password_description")}> | ||||
|             <div> | ||||
|                 <Typography color="gray" sx={{float: "left", fontSize: "0.7rem", lineHeight: "3.5"}}>⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤</Typography> | ||||
|                 <IconButton onClick={handleDialogOpen} aria-label={t("xxxxxxxx")}> | ||||
|                 <IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}> | ||||
|                     <EditIcon/> | ||||
|                 </IconButton> | ||||
|             </div> | ||||
|  | @ -186,13 +124,13 @@ const ChangePasswordDialog = (props) => { | |||
|     })(); | ||||
|     return ( | ||||
|         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}> | ||||
|             <DialogTitle>Change password</DialogTitle> | ||||
|             <DialogTitle>{t("account_basics_password_dialog_title")}</DialogTitle> | ||||
|             <DialogContent> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     id="new-password" | ||||
|                     label={t("New password")} | ||||
|                     aria-label={t("xxxx")} | ||||
|                     label={t("account_basics_password_dialog_new_password_label")} | ||||
|                     aria-label={t("account_basics_password_dialog_new_password_label")} | ||||
|                     type="password" | ||||
|                     value={newPassword} | ||||
|                     onChange={ev => setNewPassword(ev.target.value)} | ||||
|  | @ -202,8 +140,8 @@ const ChangePasswordDialog = (props) => { | |||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     id="confirm" | ||||
|                     label={t("Confirm password")} | ||||
|                     aria-label={t("xxx")} | ||||
|                     label={t("account_basics_password_dialog_confirm_password_label")} | ||||
|                     aria-label={t("account_basics_password_dialog_confirm_password_label")} | ||||
|                     type="password" | ||||
|                     value={confirmPassword} | ||||
|                     onChange={ev => setConfirmPassword(ev.target.value)} | ||||
|  | @ -212,26 +150,94 @@ const ChangePasswordDialog = (props) => { | |||
|                 /> | ||||
|             </DialogContent> | ||||
|             <DialogActions> | ||||
|                 <Button onClick={props.onCancel}>{t("Cancel")}</Button> | ||||
|                 <Button onClick={() => props.onSubmit(newPassword)} disabled={!changeButtonEnabled}>{t("Change password")}</Button> | ||||
|                 <Button onClick={props.onCancel}>{t("account_basics_password_dialog_button_cancel")}</Button> | ||||
|                 <Button onClick={() => props.onSubmit(newPassword)} disabled={!changeButtonEnabled}>{t("account_basics_password_dialog_button_submit")}</Button> | ||||
|             </DialogActions> | ||||
|         </Dialog> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Stats = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     const { account } = useOutletContext(); | ||||
|     if (!account) { | ||||
|         return <></>; | ||||
|     } | ||||
|     const planCode = account.plan.code ?? "none"; | ||||
|     const normalize = (value, max) => (value / max * 100); | ||||
|     return ( | ||||
|         <Card sx={{p: 3}} aria-label={t("account_usage_title")}> | ||||
|             <Typography variant="h5" sx={{marginBottom: 2}}> | ||||
|                 {t("account_usage_title")} | ||||
|             </Typography> | ||||
|             <PrefGroup> | ||||
|                 <Pref title={t("account_usage_plan_title")}> | ||||
|                     <div> | ||||
|                         {account?.role === "admin" | ||||
|                             ? <>{t("account_usage_unlimited")} <Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></> | ||||
|                             : t(`account_usage_plan_code_${planCode}`)} | ||||
|                     </div> | ||||
|                 </Pref> | ||||
|                 <Pref title={t("account_usage_messages_title")}> | ||||
|                     <div> | ||||
|                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography> | ||||
|                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.messages > 0 ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography> | ||||
|                     </div> | ||||
|                     <LinearProgress variant="determinate" value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100} /> | ||||
|                 </Pref> | ||||
|                 <Pref title={t("account_usage_emails_title")}> | ||||
|                     <div> | ||||
|                         <Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography> | ||||
|                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.emails > 0 ? t("account_usage_of_limit", { limit: account.limits.emails }) : t("account_usage_unlimited")}</Typography> | ||||
|                     </div> | ||||
|                     <LinearProgress variant="determinate" value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100} /> | ||||
|                 </Pref> | ||||
|                 <Pref title={t("account_usage_attachment_storage_title")} subtitle={t("account_usage_attachment_storage_subtitle", { filesize: formatBytes(account.limits.attachment_file_size) })}> | ||||
|                     <div> | ||||
|                         <Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography> | ||||
|                         <Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography> | ||||
|                     </div> | ||||
|                     <LinearProgress variant="determinate" value={account.limits.attachment_total_size > 0 ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100} /> | ||||
|                 </Pref> | ||||
|             </PrefGroup> | ||||
|             {account.limits.basis === "ip" && | ||||
|                 <Typography variant="body1"> | ||||
|                     <em>{t("account_usage_basis_ip_description")}</em> | ||||
|                 </Typography> | ||||
|             } | ||||
|         </Card> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const Delete = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     return ( | ||||
|         <Card sx={{p: 3}} aria-label={t("account_delete_title")}> | ||||
|             <Typography variant="h5" sx={{marginBottom: 2}}> | ||||
|                 {t("account_delete_title")} | ||||
|             </Typography> | ||||
|             <PrefGroup> | ||||
|                 <DeleteAccount/> | ||||
|             </PrefGroup> | ||||
|         </Card> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const DeleteAccount = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     const [dialogKey, setDialogKey] = useState(0); | ||||
|     const [dialogOpen, setDialogOpen] = useState(false); | ||||
|     const labelId = "prefDeleteAccount"; | ||||
| 
 | ||||
|     const handleDialogOpen = () => { | ||||
|         setDialogKey(prev => prev+1); | ||||
|         setDialogOpen(true); | ||||
|     }; | ||||
| 
 | ||||
|     const handleDialogCancel = () => { | ||||
|         setDialogOpen(false); | ||||
|     }; | ||||
|     const handleDialogSubmit = async (newPassword) => { | ||||
| 
 | ||||
|     const handleDialogSubmit = async () => { | ||||
|         try { | ||||
|             await accountApi.delete(); | ||||
|             await db.delete(); | ||||
|  | @ -246,11 +252,12 @@ const DeleteAccount = () => { | |||
|             // TODO show error
 | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Pref labelId={labelId} title={t("Delete account")} description={t("Permanently delete your account")}> | ||||
|         <Pref title={t("account_delete_title")} description={t("account_delete_description")}> | ||||
|             <div> | ||||
|                 <Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}> | ||||
|                     Delete account | ||||
|                     {t("account_delete_title")} | ||||
|                 </Button> | ||||
|             </div> | ||||
|             <DeleteAccountDialog | ||||
|  | @ -270,16 +277,16 @@ const DeleteAccountDialog = (props) => { | |||
|     const buttonEnabled = username === session.username(); | ||||
|     return ( | ||||
|         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}> | ||||
|             <DialogTitle>{t("Delete account")}</DialogTitle> | ||||
|             <DialogTitle>{t("account_delete_title")}</DialogTitle> | ||||
|             <DialogContent> | ||||
|                 <Typography variant="body1"> | ||||
|                     {t("This will permanently delete your account, including all data that is stored on the server. If you really want to proceed, please type '{{username}}' in the text box below.", { username: session.username()})} | ||||
|                     {t("account_delete_dialog_description", { username: session.username()})} | ||||
|                 </Typography> | ||||
|                 <TextField | ||||
|                     margin="dense" | ||||
|                     id="account-delete-confirm" | ||||
|                     label={t("Type '{{username}}' to delete account", { username: session.username()})} | ||||
|                     aria-label={t("xxxx")} | ||||
|                     label={t("account_delete_dialog_label", { username: session.username()})} | ||||
|                     aria-label={t("account_delete_dialog_label", { username: session.username()})} | ||||
|                     type="text" | ||||
|                     value={username} | ||||
|                     onChange={ev => setUsername(ev.target.value)} | ||||
|  | @ -288,8 +295,8 @@ const DeleteAccountDialog = (props) => { | |||
|                 /> | ||||
|             </DialogContent> | ||||
|             <DialogActions> | ||||
|                 <Button onClick={props.onCancel}>{t("prefs_users_dialog_button_cancel")}</Button> | ||||
|                 <Button onClick={props.onSubmit} color="error" disabled={!buttonEnabled}>{t("Permanently delete account")}</Button> | ||||
|                 <Button onClick={props.onCancel}>{t("account_delete_dialog_button_cancel")}</Button> | ||||
|                 <Button onClick={props.onSubmit} color="error" disabled={!buttonEnabled}>{t("account_delete_dialog_button_submit")}</Button> | ||||
|             </DialogActions> | ||||
|         </Dialog> | ||||
|     ); | ||||
|  | @ -319,7 +326,6 @@ const Pref = (props) => { | |||
|         > | ||||
|             <div | ||||
|                 role="cell" | ||||
|                 id={props.labelId} | ||||
|                 aria-label={props.title} | ||||
|                 style={{ | ||||
|                     flex: '1 0 40%', | ||||
|  |  | |||
|  | @ -83,18 +83,17 @@ const ActionBar = (props) => { | |||
|     ); | ||||
| }; | ||||
| 
 | ||||
| // Originally from https://mui.com/components/menus/#MenuListComposition.js
 | ||||
| const SettingsIcons = (props) => { | ||||
|     const { t } = useTranslation(); | ||||
|     const navigate = useNavigate(); | ||||
|     const [open, setOpen] = useState(false); | ||||
|     const [anchorEl, setAnchorEl] = useState(null); | ||||
|     const [snackOpen, setSnackOpen] = useState(false); | ||||
|     const [subscriptionSettingsOpen, setSubscriptionSettingsOpen] = useState(false); | ||||
|     const anchorRef = useRef(null); | ||||
|     const subscription = props.subscription; | ||||
|     const open = Boolean(anchorEl); | ||||
| 
 | ||||
|     const handleToggleOpen = () => { | ||||
|         setOpen((prevOpen) => !prevOpen); | ||||
|     const handleToggleOpen = (event) => { | ||||
|         setAnchorEl(event.currentTarget); | ||||
|     }; | ||||
| 
 | ||||
|     const handleToggleMute = async () => { | ||||
|  | @ -102,22 +101,17 @@ const SettingsIcons = (props) => { | |||
|         await subscriptionManager.setMutedUntil(subscription.id, mutedUntil); | ||||
|     } | ||||
| 
 | ||||
|     const handleClose = (event) => { | ||||
|         if (anchorRef.current && anchorRef.current.contains(event.target)) { | ||||
|             return; | ||||
|         } | ||||
|         setOpen(false); | ||||
|     const handleClose = () => { | ||||
|         setAnchorEl(null); | ||||
|     }; | ||||
| 
 | ||||
|     const handleClearAll = async (event) => { | ||||
|         handleClose(event); | ||||
|         console.log(`[ActionBar] Deleting all notifications from ${props.subscription.id}`); | ||||
|         await subscriptionManager.deleteNotifications(props.subscription.id); | ||||
|     }; | ||||
| 
 | ||||
|     const handleUnsubscribe = async (event) => { | ||||
|         console.log(`[ActionBar] Unsubscribing from ${props.subscription.id}`, props.subscription); | ||||
|         handleClose(event); | ||||
|         await subscriptionManager.remove(props.subscription.id); | ||||
|         if (session.exists() && props.subscription.remoteId) { | ||||
|             try { | ||||
|  | @ -181,61 +175,26 @@ const SettingsIcons = (props) => { | |||
|             console.log(`[ActionBar] Error publishing message`, e); | ||||
|             setSnackOpen(true); | ||||
|         } | ||||
|         setOpen(false); | ||||
|     } | ||||
| 
 | ||||
|     const handleListKeyDown = (event) => { | ||||
|         if (event.key === 'Tab') { | ||||
|             event.preventDefault(); | ||||
|             setOpen(false); | ||||
|         } else if (event.key === 'Escape') { | ||||
|             setOpen(false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // return focus to the button when we transitioned from !open -> open
 | ||||
|     const prevOpen = useRef(open); | ||||
|     useEffect(() => { | ||||
|         if (prevOpen.current === true && open === false) { | ||||
|             anchorRef.current.focus(); | ||||
|         } | ||||
|         prevOpen.current = open; | ||||
|     }, [open]); | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} aria-label={t("action_bar_toggle_mute")}> | ||||
|                 {subscription.mutedUntil ? <NotificationsOffIcon/> : <NotificationsIcon/>} | ||||
|             </IconButton> | ||||
|             <IconButton color="inherit" size="large" edge="end" ref={anchorRef} onClick={handleToggleOpen} aria-label={t("action_bar_toggle_action_menu")}> | ||||
|             <IconButton color="inherit" size="large" edge="end" onClick={handleToggleOpen} aria-label={t("action_bar_toggle_action_menu")}> | ||||
|                 <MoreVertIcon/> | ||||
|             </IconButton> | ||||
|             <Popper | ||||
|             <PopupMenu | ||||
|                 anchorEl={anchorEl} | ||||
|                 open={open} | ||||
|                 anchorEl={anchorRef.current} | ||||
|                 role={undefined} | ||||
|                 placement="bottom-start" | ||||
|                 transition | ||||
|                 disablePortal | ||||
|                 onClose={handleClose} | ||||
|             > | ||||
|                 {({TransitionProps, placement}) => ( | ||||
|                     <Grow | ||||
|                         {...TransitionProps} | ||||
|                         style={{transformOrigin: placement === 'bottom-start' ? 'left top' : 'left bottom'}} | ||||
|                     > | ||||
|                         <Paper> | ||||
|                             <ClickAwayListener onClickAway={handleClose}> | ||||
|                                 <MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}> | ||||
|                                     <MenuItem onClick={handleSubscriptionSettings}>{t("action_bar_subscription_settings")}</MenuItem> | ||||
|                                     <MenuItem onClick={handleSendTestMessage}>{t("action_bar_send_test_notification")}</MenuItem> | ||||
|                                     <MenuItem onClick={handleClearAll}>{t("action_bar_clear_notifications")}</MenuItem> | ||||
|                                     <MenuItem onClick={handleUnsubscribe}>{t("action_bar_unsubscribe")}</MenuItem> | ||||
|                                 </MenuList> | ||||
|                             </ClickAwayListener> | ||||
|                         </Paper> | ||||
|                     </Grow> | ||||
|                 )} | ||||
|             </Popper> | ||||
|                 <MenuItem onClick={handleSubscriptionSettings}>{t("action_bar_subscription_settings")}</MenuItem> | ||||
|                 <MenuItem onClick={handleSendTestMessage}>{t("action_bar_send_test_notification")}</MenuItem> | ||||
|                 <MenuItem onClick={handleClearAll}>{t("action_bar_clear_notifications")}</MenuItem> | ||||
|                 <MenuItem onClick={handleUnsubscribe}>{t("action_bar_unsubscribe")}</MenuItem> | ||||
|             </PopupMenu> | ||||
|             <Portal> | ||||
|                 <Snackbar | ||||
|                     open={snackOpen} | ||||
|  | @ -256,7 +215,7 @@ const SettingsIcons = (props) => { | |||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const ProfileIcon = (props) => { | ||||
| const ProfileIcon = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     const [anchorEl, setAnchorEl] = useState(null); | ||||
|     const open = Boolean(anchorEl); | ||||
|  | @ -265,9 +224,11 @@ const ProfileIcon = (props) => { | |||
|     const handleClick = (event) => { | ||||
|         setAnchorEl(event.currentTarget); | ||||
|     }; | ||||
| 
 | ||||
|     const handleClose = () => { | ||||
|         setAnchorEl(null); | ||||
|     }; | ||||
| 
 | ||||
|     const handleLogout = async () => { | ||||
|         try { | ||||
|             await accountApi.logout(); | ||||
|  | @ -276,53 +237,28 @@ const ProfileIcon = (props) => { | |||
|             session.resetAndRedirect(routes.app); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             {session.exists() && | ||||
|                 <IconButton color="inherit" size="large" edge="end" onClick={handleClick} aria-label={t("xxxxxxx")}> | ||||
|                 <IconButton color="inherit" size="large" edge="end" onClick={handleClick} aria-label={t("action_bar_profile_title")}> | ||||
|                     <AccountCircleIcon/> | ||||
|                 </IconButton> | ||||
|             } | ||||
|             {!session.exists() && config.enableLogin && | ||||
|                 <Button color="inherit" variant="text" onClick={() => navigate(routes.login)} sx={{m: 1}}>Sign in</Button> | ||||
|                 <Button color="inherit" variant="text" onClick={() => navigate(routes.login)} sx={{m: 1}} aria-label={t("action_bar_sign_in")}> | ||||
|                     {t("action_bar_sign_in")} | ||||
|                 </Button> | ||||
|             } | ||||
|             {!session.exists() && config.enableSignup && | ||||
|                 <Button color="inherit" variant="outlined" onClick={() => navigate(routes.signup)}>Sign up</Button> | ||||
|                 <Button color="inherit" variant="outlined" onClick={() => navigate(routes.signup)} aria-label={t("action_bar_sign_up")}> | ||||
|                     {t("action_bar_sign_up")} | ||||
|                 </Button> | ||||
|             } | ||||
|             <Menu | ||||
|             <PopupMenu | ||||
|                 anchorEl={anchorEl} | ||||
|                 id="account-menu" | ||||
|                 open={open} | ||||
|                 onClose={handleClose} | ||||
|                 onClick={handleClose} | ||||
|                 PaperProps={{ | ||||
|                     elevation: 0, | ||||
|                     sx: { | ||||
|                         overflow: 'visible', | ||||
|                         filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))', | ||||
|                         mt: 1.5, | ||||
|                         '& .MuiAvatar-root': { | ||||
|                             width: 32, | ||||
|                             height: 32, | ||||
|                             ml: -0.5, | ||||
|                             mr: 1, | ||||
|                         }, | ||||
|                         '&:before': { | ||||
|                             content: '""', | ||||
|                             display: 'block', | ||||
|                             position: 'absolute', | ||||
|                             top: 0, | ||||
|                             right: 19, | ||||
|                             width: 10, | ||||
|                             height: 10, | ||||
|                             bgcolor: 'background.paper', | ||||
|                             transform: 'translateY(-50%) rotate(45deg)', | ||||
|                             zIndex: 0, | ||||
|                         }, | ||||
|                     }, | ||||
|                 }} | ||||
|                 transformOrigin={{ horizontal: 'right', vertical: 'top' }} | ||||
|                 anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} | ||||
|             > | ||||
|                 <MenuItem onClick={() => navigate(routes.account)}> | ||||
|                     <ListItemIcon> | ||||
|  | @ -335,18 +271,58 @@ const ProfileIcon = (props) => { | |||
|                     <ListItemIcon> | ||||
|                         <Settings fontSize="small" /> | ||||
|                     </ListItemIcon> | ||||
|                     Settings | ||||
|                     {t("action_bar_profile_settings")} | ||||
|                 </MenuItem> | ||||
|                 <MenuItem onClick={handleLogout}> | ||||
|                     <ListItemIcon> | ||||
|                         <Logout fontSize="small" /> | ||||
|                     </ListItemIcon> | ||||
|                     Logout | ||||
|                     {t("action_bar_profile_logout")} | ||||
|                 </MenuItem> | ||||
|             </Menu> | ||||
|             </PopupMenu> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const PopupMenu = (props) => { | ||||
|     return ( | ||||
|         <Menu | ||||
|             anchorEl={props.anchorEl} | ||||
|             open={props.open} | ||||
|             onClose={props.onClose} | ||||
|             onClick={props.onClose} | ||||
|             PaperProps={{ | ||||
|                 elevation: 0, | ||||
|                 sx: { | ||||
|                     overflow: 'visible', | ||||
|                     filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))', | ||||
|                     mt: 1.5, | ||||
|                     '& .MuiAvatar-root': { | ||||
|                         width: 32, | ||||
|                         height: 32, | ||||
|                         ml: -0.5, | ||||
|                         mr: 1, | ||||
|                     }, | ||||
|                     '&:before': { | ||||
|                         content: '""', | ||||
|                         display: 'block', | ||||
|                         position: 'absolute', | ||||
|                         top: 0, | ||||
|                         right: 19, | ||||
|                         width: 10, | ||||
|                         height: 10, | ||||
|                         bgcolor: 'background.paper', | ||||
|                         transform: 'translateY(-50%) rotate(45deg)', | ||||
|                         zIndex: 0, | ||||
|                     }, | ||||
|                 }, | ||||
|             }} | ||||
|             transformOrigin={{ horizontal: 'right', vertical: 'top' }} | ||||
|             anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} | ||||
|         > | ||||
|             {props.children} | ||||
|         </Menu> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default ActionBar; | ||||
|  |  | |||
|  | @ -124,10 +124,11 @@ const NavList = (props) => { | |||
|                         <Divider sx={{my: 1}}/> | ||||
|                     </>} | ||||
|                 {session.exists() && | ||||
|                         <ListItemButton onClick={() => navigate(routes.account)} selected={location.pathname === routes.account}> | ||||
|                     <ListItemButton onClick={() => navigate(routes.account)} selected={location.pathname === routes.account}> | ||||
|                         <ListItemIcon><Person/></ListItemIcon> | ||||
|                         <ListItemText primary={t("nav_button_account")}/> | ||||
|                     </ListItemButton>} | ||||
|                     </ListItemButton> | ||||
|                 } | ||||
|                 <ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}> | ||||
|                     <ListItemIcon><SettingsIcon/></ListItemIcon> | ||||
|                     <ListItemText primary={t("nav_button_settings")}/> | ||||
|  |  | |||
|  | @ -45,7 +45,6 @@ const Preferences = () => { | |||
|                 <Notifications/> | ||||
|                 <Appearance/> | ||||
|                 <Users/> | ||||
|                 <AccessControl/> | ||||
|             </Stack> | ||||
|         </Container> | ||||
|     ); | ||||
|  | @ -507,170 +506,6 @@ const Language = () => { | |||
|     ) | ||||
| }; | ||||
| 
 | ||||
| const AccessControl = () => { | ||||
|     return <></>; | ||||
| } | ||||
| /* | ||||
| const AccessControl = () => { | ||||
|     const { t } = useTranslation(); | ||||
|     const [dialogKey, setDialogKey] = useState(0); | ||||
|     const [dialogOpen, setDialogOpen] = useState(false); | ||||
|     const entries = 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}}> | ||||
|                     Access control | ||||
|                 </Typography> | ||||
|                 <Paragraph> | ||||
|                     Define read/write access to topics for this server. | ||||
|                 </Paragraph> | ||||
|                 {entries?.length > 0 && <AccessControlTable entries={entries}/>} | ||||
|             </CardContent> | ||||
|             <CardActions> | ||||
|                 <Button onClick={handleAddClick}>{t("prefs_users_add_button")}</Button> | ||||
|                 <AccessControlDialog | ||||
|                     key={`aclDialog${dialogKey}`} | ||||
|                     open={dialogOpen} | ||||
|                     user={null} | ||||
|                     users={entries} | ||||
|                     onCancel={handleDialogCancel} | ||||
|                     onSubmit={handleDialogSubmit} | ||||
|                 /> | ||||
|             </CardActions> | ||||
|         </Card> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const AccessControlTable = (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}}>Topic</TableCell> | ||||
|                     <TableCell>User</TableCell> | ||||
|                     <TableCell>Access</TableCell> | ||||
|                     <TableCell/> | ||||
|                 </TableRow> | ||||
|             </TableHead> | ||||
|             <TableBody> | ||||
|                 {props.entries?.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("xxxxxxxxxx")}>{user.baseUrl}</TableCell> | ||||
|                         <TableCell align="right"> | ||||
|                             <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> | ||||
|                         </TableCell> | ||||
|                     </TableRow> | ||||
|                 ))} | ||||
|             </TableBody> | ||||
|             <AccessControlDialog | ||||
|                 key={`userEditDialog${dialogKey}`} | ||||
|                 open={dialogOpen} | ||||
|                 user={dialogUser} | ||||
|                 users={props.entries} | ||||
|                 onCancel={handleDialogCancel} | ||||
|                 onSubmit={handleDialogSubmit} | ||||
|             /> | ||||
|         </Table> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| const AccessControlDialog = (props) => { | ||||
|     const { t } = useTranslation(); | ||||
|     const [topic, setTopic] = useState(""); | ||||
|     const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); | ||||
|     const addButtonEnabled = (() => { | ||||
|         return validTopic(topic); | ||||
|     })(); | ||||
|     const handleSubmit = async () => { | ||||
|         // TODO
 | ||||
|     }; | ||||
|     return ( | ||||
|         <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}> | ||||
|             <DialogTitle>Add entry</DialogTitle> | ||||
|             <DialogContent> | ||||
|                 <TextField | ||||
|                     autoFocus={editMode} | ||||
|                     margin="dense" | ||||
|                     id="topic" | ||||
|                     label={"Topic"} | ||||
|                     aria-label={"Topic xx"} | ||||
|                     value={topic} | ||||
|                     onChange={ev => setTopic(ev.target.value)} | ||||
|                     type="text" | ||||
|                     fullWidth | ||||
|                     variant="standard" | ||||
|                 /> | ||||
|                 <FormControl fullWidth variant="standard" sx={{ margin: 1 }}> | ||||
|                     <Select value={"read-write"} onChange={() => {}}> | ||||
|                         <MenuItem value={"private"}>Read/write access only by me</MenuItem> | ||||
|                         <MenuItem value={"read-only"}>Read/write access by user, anonymous read</MenuItem> | ||||
|                     </Select> | ||||
|                 </FormControl> | ||||
|             </DialogContent> | ||||
|             <DialogActions> | ||||
|                 <Button onClick={props.onCancel}>Cancel</Button> | ||||
|                 <Button onClick={handleSubmit} disabled={!addButtonEnabled}>Add entry</Button> | ||||
|             </DialogActions> | ||||
|         </Dialog> | ||||
|     ); | ||||
| }; | ||||
| */ | ||||
| 
 | ||||
| const maybeUpdateAccountSettings = async (payload) => { | ||||
|     if (!session.exists()) { | ||||
|         return; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue