feat: Adds permission selector in document/collection invite flow (#6948)

* stash

* fix: Permissions cleared on collection addition
fix: Cannot remove user from document
Allow choosing permission in invite flow
This commit is contained in:
Tom Moor
2024-05-26 12:45:53 -04:00
committed by GitHub
parent 88eae87404
commit e3837f6ad5
7 changed files with 111 additions and 39 deletions

View File

@@ -10,19 +10,13 @@ export default function InputMemberPermissionSelect(
) {
const { value, onChange, ...rest } = props;
const { t } = useTranslation();
const handleChange = React.useCallback(
(value) => {
onChange?.(value === EmptySelectValue ? null : value);
},
[onChange]
);
return (
<Select
label={t("Permissions")}
options={props.permissions}
ariaLabel={t("Permissions")}
onChange={handleChange}
onChange={onChange}
value={value || EmptySelectValue}
labelHidden
nude

View File

@@ -37,7 +37,7 @@ export type Props = {
id?: string;
name?: string;
value?: string | null;
label?: string;
label?: React.ReactNode;
nude?: boolean;
ariaLabel: string;
short?: boolean;

View File

@@ -17,12 +17,6 @@ function InputSelectPermission(
) {
const { value, onChange, ...rest } = props;
const { t } = useTranslation();
const handleChange = React.useCallback(
(value) => {
onChange?.(value === EmptySelectValue ? null : value);
},
[onChange]
);
return (
<InputSelect
@@ -44,7 +38,7 @@ function InputSelectPermission(
]}
ariaLabel={t("Default access")}
value={value || EmptySelectValue}
onChange={handleChange}
onChange={onChange}
{...rest}
/>
);

View File

@@ -5,16 +5,19 @@ import { BackIcon, LinkIcon, UserIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useTheme } from "styled-components";
import styled, { useTheme } from "styled-components";
import Flex from "@shared/components/Flex";
import Squircle from "@shared/components/Squircle";
import { CollectionPermission, UserRole } from "@shared/types";
import { CollectionPermission } from "@shared/types";
import Collection from "~/models/Collection";
import Group from "~/models/Group";
import Share from "~/models/Share";
import User from "~/models/User";
import Avatar, { AvatarSize } from "~/components/Avatar/Avatar";
import { Inner } from "~/components/Button";
import ButtonSmall from "~/components/ButtonSmall";
import CopyToClipboard from "~/components/CopyToClipboard";
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
import InputSelectPermission from "~/components/InputSelectPermission";
import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip";
@@ -26,6 +29,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
import useKeyDown from "~/hooks/useKeyDown";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import { Permission } from "~/types";
import { collectionPath, urlify } from "~/utils/routeHelpers";
import { Wrapper, presence } from "../components";
import { ListItem } from "../components/ListItem";
@@ -55,6 +59,9 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
const [hasRendered, setHasRendered] = React.useState(visible);
const [pendingIds, setPendingIds] = React.useState<string[]>([]);
const [invitedInSession, setInvitedInSession] = React.useState<string[]>([]);
const [permission, setPermission] = React.useState<CollectionPermission>(
CollectionPermission.Read
);
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
const context = useActionContext();
@@ -153,11 +160,7 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
await memberships.create({
collectionId: collection.id,
userId: user.id,
permission:
user.role === UserRole.Viewer ||
user.role === UserRole.Guest
? CollectionPermission.Read
: CollectionPermission.ReadWrite,
permission,
});
return user;
}
@@ -225,15 +228,37 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
}),
[
collection.id,
collectionGroupMemberships,
groups,
hidePicker,
memberships,
pendingIds,
permission,
t,
team.defaultUserRole,
users,
]
);
const permissions = React.useMemo(
() =>
[
{
label: t("Admin"),
value: CollectionPermission.Admin,
},
{
label: t("Can edit"),
value: CollectionPermission.ReadWrite,
},
{
label: t("View only"),
value: CollectionPermission.Read,
},
] as Permission[],
[t]
);
if (!hasRendered) {
return null;
}
@@ -259,9 +284,18 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
const rightButton = picker ? (
pendingIds.length ? (
<ButtonSmall action={inviteAction} context={context} key="invite">
{t("Add")}
</ButtonSmall>
<Flex gap={4} key="invite">
<InputPermissionSelect
permissions={permissions}
onChange={(value: CollectionPermission) => setPermission(value)}
value={permission}
labelHidden
nude
/>
<ButtonSmall action={inviteAction} context={context}>
{t("Add")}
</ButtonSmall>
</Flex>
) : null
) : (
<Tooltip
@@ -339,4 +373,14 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
);
}
const InputPermissionSelect = styled(InputMemberPermissionSelect)`
font-size: 13px;
height: 26px;
${Inner} {
line-height: 26px;
min-height: 26px;
}
`;
export default observer(SharePopover);

View File

@@ -52,6 +52,7 @@ const DocumentMemberListItem = ({
value: DocumentPermission.ReadWrite,
},
{
divider: true,
label: t("Remove"),
value: EmptySelectValue,
},

View File

@@ -5,14 +5,18 @@ import { BackIcon, LinkIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { DocumentPermission, UserRole } from "@shared/types";
import styled from "styled-components";
import Flex from "@shared/components/Flex";
import { DocumentPermission } from "@shared/types";
import Document from "~/models/Document";
import Share from "~/models/Share";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import { AvatarSize } from "~/components/Avatar/Avatar";
import { Inner } from "~/components/Button";
import ButtonSmall from "~/components/ButtonSmall";
import CopyToClipboard from "~/components/CopyToClipboard";
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip";
import { createAction } from "~/actions";
@@ -23,6 +27,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
import useKeyDown from "~/hooks/useKeyDown";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import { Permission } from "~/types";
import { documentPath, urlify } from "~/utils/routeHelpers";
import { Separator, Wrapper, presence } from "../components";
import { SearchInput } from "../components/SearchInput";
@@ -64,6 +69,9 @@ function SharePopover({
const [invitedInSession, setInvitedInSession] = React.useState<string[]>([]);
const [pendingIds, setPendingIds] = React.useState<string[]>([]);
const collectionSharingDisabled = document.collection?.sharing === false;
const [permission, setPermission] = React.useState<DocumentPermission>(
DocumentPermission.Read
);
useKeyDown(
"Escape",
@@ -150,11 +158,7 @@ function SharePopover({
await userMemberships.create({
documentId: document.id,
userId: user.id,
permission:
user?.role === UserRole.Viewer ||
user?.role === UserRole.Guest
? DocumentPermission.Read
: DocumentPermission.ReadWrite,
permission,
});
return user;
@@ -190,6 +194,7 @@ function SharePopover({
hidePicker,
userMemberships,
document.id,
permission,
users,
team.defaultUserRole,
]
@@ -217,6 +222,21 @@ function SharePopover({
[setPendingIds]
);
const permissions = React.useMemo(
() =>
[
{
label: t("Can edit"),
value: DocumentPermission.ReadWrite,
},
{
label: t("View only"),
value: DocumentPermission.Read,
},
] as Permission[],
[t]
);
if (!hasRendered) {
return null;
}
@@ -242,9 +262,18 @@ function SharePopover({
const rightButton = picker ? (
pendingIds.length ? (
<ButtonSmall action={inviteAction} context={context} key="invite">
{t("Add")}
</ButtonSmall>
<Flex gap={4} key="invite">
<InputPermissionSelect
permissions={permissions}
onChange={(value: DocumentPermission) => setPermission(value)}
value={permission}
labelHidden
nude
/>
<ButtonSmall action={inviteAction} context={context}>
{t("Add")}
</ButtonSmall>
</Flex>
) : null
) : (
<Tooltip
@@ -312,4 +341,14 @@ function SharePopover({
);
}
const InputPermissionSelect = styled(InputMemberPermissionSelect)`
font-size: 13px;
height: 26px;
${Inner} {
line-height: 26px;
min-height: 26px;
}
`;
export default observer(SharePopover);

View File

@@ -416,12 +416,12 @@ class WebsocketProvider extends React.Component<Props> {
await collections.fetch(event.collectionId, {
force: true,
});
}
// Document policies might need updating as the permission changes
documents.inCollection(event.collectionId).forEach((document) => {
policies.remove(document.id);
});
// Document policies might need updating as the permission changes
documents.inCollection(event.collectionId).forEach((document) => {
policies.remove(document.id);
});
}
}
);