feat(frontend): deactivate permissions buttons if user is not owner

These buttons and their functionality only work if the user is the owner, so it doesn't make sense to make it possible to press them otherwise…

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2023-03-24 18:13:39 +01:00 committed by Tilman Vatteroth
parent 09e56a418e
commit e7e81cf670
10 changed files with 107 additions and 28 deletions

View file

@ -1,10 +1,11 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { UiIcon } from '../../../common/icons/ui-icon'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import React, { useCallback, useState } from 'react'
import { Button, FormControl, InputGroup } from 'react-bootstrap'
import { Plus as IconPlus } from 'react-bootstrap-icons'
@ -20,8 +21,13 @@ export interface PermissionAddEntryFieldProps {
*
* @param onAddEntry Callback that is fired with the entered username as identifier of the entry to add.
* @param i18nKey The localization key for the submit button.
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionAddEntryField: React.FC<PermissionAddEntryFieldProps> = ({ onAddEntry, i18nKey }) => {
export const PermissionAddEntryField: React.FC<PermissionAddEntryFieldProps & PermissionDisabledProps> = ({
onAddEntry,
i18nKey,
disabled
}) => {
const { t } = useTranslation()
const [newEntryIdentifier, setNewEntryIdentifier] = useState('')
@ -34,8 +40,18 @@ export const PermissionAddEntryField: React.FC<PermissionAddEntryFieldProps> = (
return (
<li className={'list-group-item'}>
<InputGroup className={'me-1 mb-1'}>
<FormControl value={newEntryIdentifier} placeholder={t(i18nKey) ?? undefined} onChange={onChange} />
<Button variant='light' className={'text-secondary ms-2'} title={t(i18nKey) ?? undefined} onClick={onSubmit}>
<FormControl
value={newEntryIdentifier}
placeholder={t(i18nKey) ?? undefined}
onChange={onChange}
disabled={disabled}
/>
<Button
variant='light'
className={'text-secondary ms-2'}
title={t(i18nKey) ?? undefined}
onClick={onSubmit}
disabled={disabled}>
<UiIcon icon={IconPlus} />
</Button>
</InputGroup>

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export interface PermissionDisabledProps {
disabled: boolean
}

View file

@ -1,9 +1,10 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { UiIcon } from '../../../common/icons/ui-icon'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import { AccessLevel } from './types'
import React, { useMemo } from 'react'
import { Button, ToggleButtonGroup } from 'react-bootstrap'
@ -41,14 +42,16 @@ export interface PermissionEntryButtonsProps {
* @param onSetReadOnly Callback that is fired when the entry is changed to read-only permission.
* @param onSetWriteable Callback that is fired when the entry is changed to writeable permission.
* @param onRemove Callback that is fired when the entry is removed.
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionEntryButtons: React.FC<PermissionEntryButtonsProps> = ({
export const PermissionEntryButtons: React.FC<PermissionEntryButtonsProps & PermissionDisabledProps> = ({
name,
type,
currentSetting,
onSetReadOnly,
onSetWriteable,
onRemove
onRemove,
disabled
}) => {
const { t } = useTranslation()
@ -74,18 +77,21 @@ export const PermissionEntryButtons: React.FC<PermissionEntryButtonsProps> = ({
<Button
variant='light'
className={'text-danger me-2'}
disabled={disabled}
title={t(i18nKeys.remove, { name }) ?? undefined}
onClick={onRemove}>
<UiIcon icon={IconX} />
</Button>
<ToggleButtonGroup type='radio' name='edit-mode' value={currentSetting}>
<Button
disabled={disabled}
title={t(i18nKeys.setReadOnly, { name }) ?? undefined}
variant={currentSetting === AccessLevel.READ_ONLY ? 'secondary' : 'outline-secondary'}
onClick={onSetReadOnly}>
<UiIcon icon={IconEye} />
</Button>
<Button
disabled={disabled}
title={t(i18nKeys.setWriteable, { name }) ?? undefined}
variant={currentSetting === AccessLevel.WRITEABLE ? 'secondary' : 'outline-secondary'}
onClick={onSetWriteable}>

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -8,6 +8,7 @@ import { useApplicationState } from '../../../../hooks/common/use-application-st
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { IconButton } from '../../../common/icon-button/icon-button'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import { AccessLevel, SpecialGroup } from './types'
import React, { useCallback, useMemo } from 'react'
import { ToggleButtonGroup } from 'react-bootstrap'
@ -26,8 +27,13 @@ export interface PermissionEntrySpecialGroupProps {
*
* @param level The access level that is currently set for the group.
* @param type The type of the special group. Must be either {@link SpecialGroup.EVERYONE} or {@link SpecialGroup.LOGGED_IN}.
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupProps> = ({ level, type }) => {
export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupProps & PermissionDisabledProps> = ({
level,
type,
disabled
}) => {
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
const { t } = useTranslation()
const { showErrorNotification } = useUiNotifications()
@ -75,6 +81,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
title={t('editor.modal.permissions.denyGroup', { name }) ?? undefined}
variant={level === AccessLevel.NONE ? 'secondary' : 'outline-secondary'}
onClick={onSetEntryDenied}
disabled={disabled}
className={'p-1'}
/>
<IconButton
@ -82,6 +89,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
title={t('editor.modal.permissions.viewOnlyGroup', { name }) ?? undefined}
variant={level === AccessLevel.READ_ONLY ? 'secondary' : 'outline-secondary'}
onClick={onSetEntryReadOnly}
disabled={disabled}
className={'p-1'}
/>
<IconButton
@ -89,6 +97,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
title={t('editor.modal.permissions.editGroup', { name }) ?? undefined}
variant={level === AccessLevel.WRITEABLE ? 'secondary' : 'outline-secondary'}
onClick={onSetEntryWriteable}
disabled={disabled}
className={'p-1'}
/>
</ToggleButtonGroup>

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,6 +11,7 @@ import { setNotePermissionsFromServer } from '../../../../redux/note-details/met
import { ShowIf } from '../../../common/show-if/show-if'
import { UserAvatarForUser } from '../../../common/user-avatar/user-avatar-for-user'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import { PermissionEntryButtons, PermissionType } from './permission-entry-buttons'
import { AccessLevel } from './types'
import React, { useCallback } from 'react'
@ -24,8 +25,12 @@ export interface PermissionEntryUserProps {
* Permission entry for a user that can be set to read-only or writeable and can be removed.
*
* @param entry The permission entry.
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionEntryUser: React.FC<PermissionEntryUserProps> = ({ entry }) => {
export const PermissionEntryUser: React.FC<PermissionEntryUserProps & PermissionDisabledProps> = ({
entry,
disabled
}) => {
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
const { showErrorNotification } = useUiNotifications()
@ -72,6 +77,7 @@ export const PermissionEntryUser: React.FC<PermissionEntryUserProps> = ({ entry
onSetReadOnly={onSetEntryReadOnly}
onSetWriteable={onSetEntryWriteable}
onRemove={onRemoveEntry}
disabled={disabled}
/>
</li>
</ShowIf>

View file

@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useIsOwner } from '../../../../hooks/common/use-is-owner'
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
import { CommonModal } from '../../../common/modals/common-modal'
import { PermissionSectionOwner } from './permission-section-owner'
@ -18,12 +19,13 @@ import { Modal } from 'react-bootstrap'
* @param onHide Callback that is fired when the modal is about to be closed.
*/
export const PermissionModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
const isOwner = useIsOwner()
return (
<CommonModal show={show} onHide={onHide} showCloseButton={true} titleI18nKey={'editor.modal.permissions.title'}>
<Modal.Body>
<PermissionSectionOwner />
<PermissionSectionUsers />
<PermissionSectionSpecialGroups />
<PermissionSectionOwner disabled={!isOwner} />
<PermissionSectionUsers disabled={!isOwner} />
<PermissionSectionSpecialGroups disabled={!isOwner} />
</Modal.Body>
</CommonModal>
)

View file

@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { UiIcon } from '../../../common/icons/ui-icon'
import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import React, { Fragment } from 'react'
import { Button } from 'react-bootstrap'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
@ -19,8 +20,12 @@ export interface PermissionOwnerInfoProps {
* Content for the owner section of the permission modal that shows the current note owner.
*
* @param onEditOwner Callback that is fired when the user chooses to change the note owner.
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps> = ({ onEditOwner }) => {
export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps & PermissionDisabledProps> = ({
onEditOwner,
disabled
}) => {
const { t } = useTranslation()
const noteOwner = useApplicationState((state) => state.noteDetails.permissions.owner)
@ -29,6 +34,7 @@ export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps> = ({ onEdit
<UserAvatarForUsername username={noteOwner} />
<Button
variant='light'
disabled={disabled}
title={t('editor.modal.permissions.ownerChange.button') ?? undefined}
onClick={onEditOwner}>
<UiIcon icon={IconPencil} />

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -7,6 +7,7 @@ import { setNoteOwner } from '../../../../api/permissions'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import { PermissionOwnerChange } from './permission-owner-change'
import { PermissionOwnerInfo } from './permission-owner-info'
import React, { Fragment, useCallback, useState } from 'react'
@ -14,8 +15,10 @@ import { Trans } from 'react-i18next'
/**
* Section in the permissions modal for managing the owner of a note.
*
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionSectionOwner: React.FC = () => {
export const PermissionSectionOwner: React.FC<PermissionDisabledProps> = ({ disabled }) => {
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
const [changeOwner, setChangeOwner] = useState(false)
const { showErrorNotification } = useUiNotifications()
@ -48,7 +51,7 @@ export const PermissionSectionOwner: React.FC = () => {
{changeOwner ? (
<PermissionOwnerChange onConfirmOwnerChange={onOwnerChange} />
) : (
<PermissionOwnerInfo onEditOwner={onSetChangeOwner} />
<PermissionOwnerInfo onEditOwner={onSetChangeOwner} disabled={disabled} />
)}
</li>
</ul>

View file

@ -1,9 +1,11 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { useIsOwner } from '../../../../hooks/common/use-is-owner'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import { PermissionEntrySpecialGroup } from './permission-entry-special-group'
import { AccessLevel, SpecialGroup } from './types'
import React, { Fragment, useMemo } from 'react'
@ -11,10 +13,13 @@ import { Trans, useTranslation } from 'react-i18next'
/**
* Section of the permission modal for managing special group access to the note.
*
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionSectionSpecialGroups: React.FC = () => {
export const PermissionSectionSpecialGroups: React.FC<PermissionDisabledProps> = ({ disabled }) => {
useTranslation()
const groupPermissions = useApplicationState((state) => state.noteDetails.permissions.sharedToGroups)
const isOwner = useIsOwner()
const specialGroupEntries = useMemo(() => {
const groupEveryone = groupPermissions.find((entry) => entry.groupName === SpecialGroup.EVERYONE)
@ -40,8 +45,16 @@ export const PermissionSectionSpecialGroups: React.FC = () => {
<Trans i18nKey={'editor.modal.permissions.sharedWithElse'} />
</h5>
<ul className={'list-group'}>
<PermissionEntrySpecialGroup level={specialGroupEntries.loggedIn} type={SpecialGroup.LOGGED_IN} />
<PermissionEntrySpecialGroup level={specialGroupEntries.everyone} type={SpecialGroup.EVERYONE} />
<PermissionEntrySpecialGroup
level={specialGroupEntries.loggedIn}
type={SpecialGroup.LOGGED_IN}
disabled={!isOwner}
/>
<PermissionEntrySpecialGroup
level={specialGroupEntries.everyone}
type={SpecialGroup.EVERYONE}
disabled={disabled}
/>
</ul>
</Fragment>
)

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -8,22 +8,27 @@ import { useApplicationState } from '../../../../hooks/common/use-application-st
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import { PermissionAddEntryField } from './permission-add-entry-field'
import type { PermissionDisabledProps } from './permission-disabled.prop'
import { PermissionEntryUser } from './permission-entry-user'
import React, { Fragment, useCallback, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
/**
* Section of the permission modal for managing user access to the note.
*
* @param disabled If the user is not the owner, functionality is disabled.
*/
export const PermissionSectionUsers: React.FC = () => {
export const PermissionSectionUsers: React.FC<PermissionDisabledProps> = ({ disabled }) => {
useTranslation()
const userPermissions = useApplicationState((state) => state.noteDetails.permissions.sharedToUsers)
const noteId = useApplicationState((state) => state.noteDetails.primaryAddress)
const { showErrorNotification } = useUiNotifications()
const userEntries = useMemo(() => {
return userPermissions.map((entry) => <PermissionEntryUser key={entry.username} entry={entry} />)
}, [userPermissions])
return userPermissions.map((entry) => (
<PermissionEntryUser key={entry.username} entry={entry} disabled={disabled} />
))
}, [userPermissions, disabled])
const onAddEntry = useCallback(
(username: string) => {
@ -43,7 +48,11 @@ export const PermissionSectionUsers: React.FC = () => {
</h5>
<ul className={'list-group'}>
{userEntries}
<PermissionAddEntryField onAddEntry={onAddEntry} i18nKey={'editor.modal.permissions.addUser'} />
<PermissionAddEntryField
onAddEntry={onAddEntry}
i18nKey={'editor.modal.permissions.addUser'}
disabled={disabled}
/>
</ul>
</Fragment>
)