diff --git a/public/locales/en.json b/public/locales/en.json index f0f971f31..c852e8516 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -329,7 +329,21 @@ "button": "Delete note" }, "permissions": { - "title": "Permissions" + "title": "Permissions", + "owner": "Owner", + "sharedWithUsers": "Shared with users", + "sharedWithGroups": "Also share with…", + "editUser": "Change {{name}}'s permissions to view and edit", + "viewOnlyUser": "Change {{name}}'s permissions to view only", + "removeUser": "Remove {{name}}'s permissions", + "addUser": "Add user", + "editGroup": "Change permissions of group \"{{name}}\" to view & edit", + "viewOnlyGroup": "Change permissions of group \"{{name}}\" to view only", + "denyGroup": "Deny access to group \"{{name}}\"", + "addGroup": "Add group", + "allUser": "Everyone", + "allLoggedInUser": "All logged-in users", + "error": "An error occurred while fetching the user information of this note." }, "shareLink": { "title": "Share link", diff --git a/src/components/editor/document-bar/buttons/permission-button.tsx b/src/components/editor/document-bar/buttons/permission-button.tsx deleted file mode 100644 index 588122f27..000000000 --- a/src/components/editor/document-bar/buttons/permission-button.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { Fragment, useState } from 'react' -import { Modal } from 'react-bootstrap' -import { CommonModal } from '../../../common/modals/common-modal' -import { TranslatedIconButton } from '../../../common/icon-button/translated-icon-button' - -export const PermissionButton: React.FC = () => { - const [showReadOnly, setShowReadOnly] = useState(false) - - return ( - - setShowReadOnly(true)} i18nKey={'editor.documentBar.permissions'}/> - setShowReadOnly(false)} - closeButton={true} - titleI18nKey={'editor.modal.permissions.title'}> - - - - - - ) -} diff --git a/src/components/editor/document-bar/document-bar.tsx b/src/components/editor/document-bar/document-bar.tsx index e73f67fc9..4ab20c8ab 100644 --- a/src/components/editor/document-bar/document-bar.tsx +++ b/src/components/editor/document-bar/document-bar.tsx @@ -1,13 +1,13 @@ import React from 'react' import { useTranslation } from 'react-i18next' +import { PinToHistoryButton } from './buttons/pin-to-history-button' +import { ShareLinkButton } from './buttons/share-link-button' import { ConnectionIndicator } from './connection-indicator/connection-indicator' import { DocumentInfoButton } from './document-info/document-info-button' import { EditorMenu } from './menus/editor-menu' import { ExportMenu } from './menus/export-menu' import { ImportMenu } from './menus/import-menu' -import { PermissionButton } from './buttons/permission-button' -import { PinToHistoryButton } from './buttons/pin-to-history-button' -import { ShareLinkButton } from './buttons/share-link-button' +import { PermissionButton } from './permissions/permission-button' import { RevisionButton } from './revisions/revision-button' export interface DocumentBarProps { diff --git a/src/components/editor/document-bar/permissions/permission-button.tsx b/src/components/editor/document-bar/permissions/permission-button.tsx new file mode 100644 index 000000000..25572d1fc --- /dev/null +++ b/src/components/editor/document-bar/permissions/permission-button.tsx @@ -0,0 +1,14 @@ +import React, { Fragment, useState } from 'react' +import { TranslatedIconButton } from '../../../common/icon-button/translated-icon-button' +import { PermissionModal } from './permission-modal' + +export const PermissionButton: React.FC = () => { + const [showPermissionModal, setShowPermissionModal] = useState(false) + + return ( + + setShowPermissionModal(true)} i18nKey={'editor.documentBar.permissions'}/> + + + ) +} diff --git a/src/components/editor/document-bar/permissions/permission-group-entry.tsx b/src/components/editor/document-bar/permissions/permission-group-entry.tsx new file mode 100644 index 000000000..fdca61d16 --- /dev/null +++ b/src/components/editor/document-bar/permissions/permission-group-entry.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon' + +export interface PermissionGroupEntryProps { + title: string + editMode: GroupMode + onChangeEditMode: (newMode: GroupMode) => void +} + +export enum GroupMode { + NONE, + VIEW, + EDIT, +} + +export const PermissionGroupEntry: React.FC = ({ title, editMode, onChangeEditMode }) => { + const { t } = useTranslation() + + return ( + + + + + + + + + + + + + + + ) +} diff --git a/src/components/editor/document-bar/permissions/permission-list.tsx b/src/components/editor/document-bar/permissions/permission-list.tsx new file mode 100644 index 000000000..1dc55a99c --- /dev/null +++ b/src/components/editor/document-bar/permissions/permission-list.tsx @@ -0,0 +1,98 @@ +import React, { ReactElement, useState } from 'react' +import { Button, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap' +import { useTranslation } from 'react-i18next' +import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon' +import { Principal } from './permission-modal' + +export interface PermissionListProps { + list: Principal[] + identifier: (entry: Principal) => ReactElement + changeEditMode: (id: Principal['id'], canEdit: Principal['canEdit']) => void + removeEntry: (id: Principal['id']) => void + createEntry: (name: Principal['name']) => void + editI18nKey: string + viewI18nKey: string + removeI18nKey: string + addI18nKey: string +} + +export enum EditMode { + VIEW, + EDIT +} + +export const PermissionList: React.FC = ({ list, identifier, changeEditMode, removeEntry, createEntry, editI18nKey, viewI18nKey, removeI18nKey, addI18nKey }) => { + const { t } = useTranslation() + const [newEntry, setNewEntry] = useState('') + + const addEntry = () => { + createEntry(newEntry) + setNewEntry('') + } + + return ( + + {list.map(entry => ( + + {identifier(entry)} + + removeEntry(entry.id)} + > + + + changeEditMode(entry.id, value === EditMode.EDIT)} + > + + + + + + + + + + ))} + + { + event.preventDefault() + addEntry() + }}> + + setNewEntry(event.currentTarget.value)} + /> + + + + + + + + ) +} diff --git a/src/components/editor/document-bar/permissions/permission-modal.tsx b/src/components/editor/document-bar/permissions/permission-modal.tsx new file mode 100644 index 000000000..02a2d24f1 --- /dev/null +++ b/src/components/editor/document-bar/permissions/permission-modal.tsx @@ -0,0 +1,163 @@ +import React, { useEffect, useState } from 'react' +import { Alert, Modal } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { getUserById } from '../../../../api/users' +import { CommonModal } from '../../../common/modals/common-modal' +import { ShowIf } from '../../../common/show-if/show-if' +import { UserAvatar, UserAvatarProps } from '../../../common/user-avatar/user-avatar' +import { GroupMode, PermissionGroupEntry } from './permission-group-entry' +import { PermissionList } from './permission-list' + +export interface PermissionsModalProps { + show: boolean, + onChangeShow: (newShow: boolean) => void +} + +export interface Principal { + id: string + name: string + photo: string + canEdit: boolean +} + +interface NotePermissions { + owner: string + sharedTo: { + username: string + canEdit: boolean + }[], + sharedToGroup: { + id: string + canEdit: boolean + }[] +} + +export const EVERYONE_GROUP_ID = '1' +export const EVERYONE_LOGGED_IN_GROUP_ID = '2' + +const permissionsApiResponse: NotePermissions = { + owner: 'dermolly', + sharedTo: [{ + username: 'emcrx', + canEdit: true + }, { + username: 'mrdrogdrog', + canEdit: false + }], + sharedToGroup: [{ + id: EVERYONE_GROUP_ID, + canEdit: true + }, { + id: EVERYONE_LOGGED_IN_GROUP_ID, + canEdit: false + }] +} + +export const PermissionModal: React.FC = ({ show, onChangeShow }) => { + useTranslation() + const [error, setError] = useState(false) + const [userList, setUserList] = useState([]) + const [owner, setOwner] = useState() + const [allUserPermissions, setAllUserPermissions] = useState(GroupMode.NONE) + const [allLoggedInUserPermissions, setAllLoggedInUserPermissions] = useState(GroupMode.NONE) + + useEffect(() => { + // set owner + getUserById(permissionsApiResponse.owner).then(response => { + setOwner({ + name: response.name, + photo: response.photo + }) + }).catch(() => setError(true)) + // set user List + permissionsApiResponse.sharedTo.forEach(shareUser => { + getUserById(shareUser.username).then(response => { + setUserList(list => list.concat([{ + id: response.id, + name: response.name, + photo: response.photo, + canEdit: shareUser.canEdit + }])) + }).catch(() => setError(true)) + }) + // set group List + permissionsApiResponse.sharedToGroup.forEach(sharedGroup => { + if (sharedGroup.id === EVERYONE_GROUP_ID) { + setAllUserPermissions(sharedGroup.canEdit ? GroupMode.EDIT : GroupMode.VIEW) + } else if (sharedGroup.id === EVERYONE_LOGGED_IN_GROUP_ID) { + setAllLoggedInUserPermissions(sharedGroup.canEdit ? GroupMode.EDIT : GroupMode.VIEW) + } + }) + }, []) + + const changeUserMode = (userId: Principal['id'], canEdit: Principal['canEdit']) => { + setUserList(list => + list + .map(user => { + if (user.id === userId) { + user.canEdit = canEdit + } + return user + })) + } + + const removeUser = (userId: Principal['id']) => { + setUserList(list => list.filter(user => user.id !== userId)) + } + + const addUser = (name: Principal['name']) => { + setUserList(list => list.concat({ + id: name, + photo: '/avatar.png', + name: name, + canEdit: false + })) + } + + return ( + onChangeShow(false)} + closeButton={true} + titleI18nKey={'editor.modal.permissions.title'}> + + + + + + + + + + + + + + ()} + changeEditMode={changeUserMode} + removeEntry={removeUser} + createEntry={addUser} + editI18nKey={'editor.modal.permissions.editUser'} + viewI18nKey={'editor.modal.permissions.viewOnlyUser'} + removeI18nKey={'editor.modal.permissions.removeUser'} + addI18nKey={'editor.modal.permissions.addUser'} + /> + + + + + + + + ) +}