|
|
@@ -1,7 +1,7 @@
|
|
|
-import React, { useCallback, useState } from 'react';
|
|
|
+import React, { useCallback, useEffect, useState } from 'react';
|
|
|
|
|
|
import {
|
|
|
- PageGrant, isPopulated, GroupType, type IGrantedGroup,
|
|
|
+ PageGrant, GroupType, getIdForRef,
|
|
|
} from '@growi/core';
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
import {
|
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
} from 'reactstrap';
|
|
|
|
|
|
import { LoadingSpinner } from '~/components/LoadingSpinner';
|
|
|
-import type { IPageGrantData } from '~/interfaces/page';
|
|
|
+import type { UserRelatedGroupsData } from '~/interfaces/page';
|
|
|
+import { UserGroupPageGrantStatus } from '~/interfaces/page';
|
|
|
import { useCurrentUser } from '~/stores/context';
|
|
|
+import { useCurrentPageId, useSWRxIsGrantNormalized } from '~/stores/page';
|
|
|
+import { useSelectedGrant } from '~/stores/ui';
|
|
|
|
|
|
-import { useMyUserGroups } from './use-my-user-groups';
|
|
|
|
|
|
const AVAILABLE_GRANTS = [
|
|
|
{
|
|
|
@@ -41,14 +43,6 @@ const AVAILABLE_GRANTS = [
|
|
|
type Props = {
|
|
|
disabled?: boolean,
|
|
|
openInModal?: boolean,
|
|
|
- grant: PageGrant,
|
|
|
- userRelatedGrantedGroups?: {
|
|
|
- id: string,
|
|
|
- name: string,
|
|
|
- type: GroupType,
|
|
|
- }[]
|
|
|
-
|
|
|
- onUpdateGrant?: (grantData: IPageGrantData) => void,
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -60,9 +54,6 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
const {
|
|
|
disabled,
|
|
|
openInModal,
|
|
|
- userRelatedGrantedGroups,
|
|
|
- onUpdateGrant,
|
|
|
- grant: currentGrant,
|
|
|
} = props;
|
|
|
|
|
|
|
|
|
@@ -71,12 +62,35 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
const { data: currentUser } = useCurrentUser();
|
|
|
|
|
|
const shouldFetch = isSelectGroupModalShown;
|
|
|
- const { data: myUserGroups, update: updateMyUserGroups } = useMyUserGroups(shouldFetch);
|
|
|
+ const { data: selectedGrant, mutate: mutateSelectedGrant } = useSelectedGrant();
|
|
|
+ const { data: currentPageId } = useCurrentPageId();
|
|
|
+ const { data: grantData } = useSWRxIsGrantNormalized(currentPageId);
|
|
|
+
|
|
|
+ const currentPageGrantData = grantData?.grantData.currentPageGrant;
|
|
|
+ const groupGrantData = currentPageGrantData?.groupGrantData;
|
|
|
+
|
|
|
+ const applyCurrentPageGrantToSelectedGrant = useCallback(() => {
|
|
|
+ const currentPageGrant = grantData?.grantData.currentPageGrant;
|
|
|
+ if (currentPageGrant == null) return;
|
|
|
+
|
|
|
+ const userRelatedGrantedGroups = currentPageGrant.groupGrantData
|
|
|
+ ?.userRelatedGroups.filter(group => group.status === UserGroupPageGrantStatus.isGranted)?.map((group) => {
|
|
|
+ return { item: group.id, type: group.type };
|
|
|
+ }) ?? [];
|
|
|
+ mutateSelectedGrant({
|
|
|
+ grant: currentPageGrant.grant,
|
|
|
+ userRelatedGrantedGroups,
|
|
|
+ });
|
|
|
+ }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant]);
|
|
|
+
|
|
|
+ // sync grant data
|
|
|
+ useEffect(() => {
|
|
|
+ applyCurrentPageGrantToSelectedGrant();
|
|
|
+ }, [applyCurrentPageGrantToSelectedGrant]);
|
|
|
|
|
|
const showSelectGroupModal = useCallback(() => {
|
|
|
- updateMyUserGroups();
|
|
|
setIsSelectGroupModalShown(true);
|
|
|
- }, [updateMyUserGroups]);
|
|
|
+ }, []);
|
|
|
|
|
|
/**
|
|
|
* change event handler for grant selector
|
|
|
@@ -84,28 +98,27 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
const changeGrantHandler = useCallback((grant: PageGrant) => {
|
|
|
// select group
|
|
|
if (grant === 5) {
|
|
|
+ if (selectedGrant?.grant !== 5) applyCurrentPageGrantToSelectedGrant();
|
|
|
showSelectGroupModal();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (onUpdateGrant != null) {
|
|
|
- onUpdateGrant({ grant, userRelatedGrantedGroups: undefined });
|
|
|
+ mutateSelectedGrant({ grant, userRelatedGrantedGroups: undefined });
|
|
|
+ }, [mutateSelectedGrant, showSelectGroupModal, applyCurrentPageGrantToSelectedGrant, selectedGrant?.grant]);
|
|
|
+
|
|
|
+ const groupListItemClickHandler = useCallback((clickedGroup: UserRelatedGroupsData) => {
|
|
|
+ const userRelatedGrantedGroups = selectedGrant?.userRelatedGrantedGroups ?? [];
|
|
|
+
|
|
|
+ let userRelatedGrantedGroupsCopy = [...userRelatedGrantedGroups];
|
|
|
+ if (userRelatedGrantedGroupsCopy.find(group => getIdForRef(group.item) === clickedGroup.id) == null) {
|
|
|
+ const grantGroupInfo = { item: clickedGroup.id, type: clickedGroup.type };
|
|
|
+ userRelatedGrantedGroupsCopy.push(grantGroupInfo);
|
|
|
}
|
|
|
- }, [onUpdateGrant, showSelectGroupModal]);
|
|
|
-
|
|
|
- const groupListItemClickHandler = useCallback((grantGroup: IGrantedGroup) => {
|
|
|
- if (onUpdateGrant != null && isPopulated(grantGroup.item)) {
|
|
|
- let userRelatedGrantedGroupsCopy = userRelatedGrantedGroups != null ? [...userRelatedGrantedGroups] : [];
|
|
|
- const grantGroupInfo = { id: grantGroup.item._id, name: grantGroup.item.name, type: grantGroup.type };
|
|
|
- if (userRelatedGrantedGroupsCopy.find(group => group.id === grantGroupInfo.id) == null) {
|
|
|
- userRelatedGrantedGroupsCopy.push(grantGroupInfo);
|
|
|
- }
|
|
|
- else {
|
|
|
- userRelatedGrantedGroupsCopy = userRelatedGrantedGroupsCopy.filter(group => group.id !== grantGroupInfo.id);
|
|
|
- }
|
|
|
- onUpdateGrant({ grant: 5, userRelatedGrantedGroups: userRelatedGrantedGroupsCopy });
|
|
|
+ else {
|
|
|
+ userRelatedGrantedGroupsCopy = userRelatedGrantedGroupsCopy.filter(group => getIdForRef(group.item) !== clickedGroup.id);
|
|
|
}
|
|
|
- }, [onUpdateGrant, userRelatedGrantedGroups]);
|
|
|
+ mutateSelectedGrant({ grant: 5, userRelatedGrantedGroups: userRelatedGrantedGroupsCopy });
|
|
|
+ }, [mutateSelectedGrant, selectedGrant?.userRelatedGrantedGroups]);
|
|
|
|
|
|
/**
|
|
|
* Render grant selector DOM.
|
|
|
@@ -115,8 +128,13 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
let dropdownToggleBtnColor;
|
|
|
let dropdownToggleLabelElm;
|
|
|
|
|
|
+ const userRelatedGrantedGroups = groupGrantData?.userRelatedGroups.filter((group) => {
|
|
|
+ return selectedGrant?.userRelatedGrantedGroups?.some(grantedGroup => getIdForRef(grantedGroup.item) === group.id);
|
|
|
+ }) ?? [];
|
|
|
+ const nonUserRelatedGrantedGroups = groupGrantData?.nonUserRelatedGrantedGroups ?? [];
|
|
|
+
|
|
|
const dropdownMenuElems = AVAILABLE_GRANTS.map((opt) => {
|
|
|
- const label = ((opt.grant === 5 && opt.reselectLabel != null) && userRelatedGrantedGroups != null && userRelatedGrantedGroups.length > 0)
|
|
|
+ const label = ((opt.grant === 5 && opt.reselectLabel != null) && userRelatedGrantedGroups.length > 0)
|
|
|
? opt.reselectLabel // when grantGroup is selected
|
|
|
: opt.label;
|
|
|
|
|
|
@@ -128,7 +146,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
);
|
|
|
|
|
|
// set dropdownToggleBtnColor, dropdownToggleLabelElm
|
|
|
- if (opt.grant === 1 || opt.grant === currentGrant) {
|
|
|
+ if (opt.grant === 1 || opt.grant === selectedGrant?.grant) {
|
|
|
dropdownToggleBtnColor = opt.btnStyleClass;
|
|
|
dropdownToggleLabelElm = labelElm;
|
|
|
}
|
|
|
@@ -137,18 +155,19 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
});
|
|
|
|
|
|
// add specified group option
|
|
|
- if (userRelatedGrantedGroups != null && userRelatedGrantedGroups.length > 0) {
|
|
|
+ if (selectedGrant?.grant === PageGrant.GRANT_USER_GROUP && (userRelatedGrantedGroups.length > 0 || nonUserRelatedGrantedGroups.length > 0)) {
|
|
|
+ const grantedGroupNames = [...userRelatedGrantedGroups.map(group => group.name), ...nonUserRelatedGrantedGroups.map(group => group.name)];
|
|
|
const labelElm = (
|
|
|
<span>
|
|
|
<span className="material-symbols-outlined me-1">account_tree</span>
|
|
|
<span className="label">
|
|
|
- {userRelatedGrantedGroups.length > 1
|
|
|
+ {grantedGroupNames.length > 1
|
|
|
? (
|
|
|
<span>
|
|
|
- {`${userRelatedGrantedGroups[0].name}... `}
|
|
|
- <span className="badge badge-purple">+{userRelatedGrantedGroups.length - 1}</span>
|
|
|
+ {`${grantedGroupNames[0]}... `}
|
|
|
+ <span className="badge badge-purple">+{grantedGroupNames.length - 1}</span>
|
|
|
</span>
|
|
|
- ) : userRelatedGrantedGroups[0].name}
|
|
|
+ ) : grantedGroupNames[0]}
|
|
|
</span>
|
|
|
</span>
|
|
|
);
|
|
|
@@ -171,7 +190,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
</UncontrolledDropdown>
|
|
|
</div>
|
|
|
);
|
|
|
- }, [changeGrantHandler, currentGrant, disabled, userRelatedGrantedGroups, t, openInModal]);
|
|
|
+ }, [changeGrantHandler, disabled, groupGrantData, selectedGrant, t, openInModal]);
|
|
|
|
|
|
/**
|
|
|
* Render select grantgroup modal.
|
|
|
@@ -182,7 +201,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
}
|
|
|
|
|
|
// show spinner
|
|
|
- if (myUserGroups == null) {
|
|
|
+ if (groupGrantData == null) {
|
|
|
return (
|
|
|
<div className="my-3 text-center">
|
|
|
<LoadingSpinner className="mx-auto text-muted fs-4" />
|
|
|
@@ -190,7 +209,9 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- if (myUserGroups.length === 0) {
|
|
|
+ const { userRelatedGroups, nonUserRelatedGrantedGroups } = groupGrantData;
|
|
|
+
|
|
|
+ if (userRelatedGroups.length === 0) {
|
|
|
return (
|
|
|
<div>
|
|
|
<h4>{t('user_group.belonging_to_no_group')}</h4>
|
|
|
@@ -203,20 +224,37 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
|
|
|
return (
|
|
|
<div className="d-flex flex-column">
|
|
|
- { myUserGroups.map((group) => {
|
|
|
- const groupIsGranted = userRelatedGrantedGroups?.find(g => g.id === group.item._id) != null;
|
|
|
- const activeClass = groupIsGranted ? 'active' : '';
|
|
|
+ { userRelatedGroups.map((group) => {
|
|
|
+ const isGroupGranted = selectedGrant?.userRelatedGrantedGroups?.some(grantedGroup => getIdForRef(grantedGroup.item) === group.id);
|
|
|
+ const cannotGrantGroup = group.status === UserGroupPageGrantStatus.cannotGrant;
|
|
|
+ const activeClass = isGroupGranted ? 'active' : '';
|
|
|
|
|
|
return (
|
|
|
<button
|
|
|
className={`btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 ${activeClass}`}
|
|
|
type="button"
|
|
|
- key={group.item._id}
|
|
|
+ key={group.id}
|
|
|
onClick={() => groupListItemClickHandler(group)}
|
|
|
+ disabled={cannotGrantGroup}
|
|
|
+ >
|
|
|
+ <span className="align-middle"><input type="checkbox" checked={isGroupGranted} disabled={cannotGrantGroup} /></span>
|
|
|
+ <h5 className="d-inline-block ms-3">{group.name}</h5>
|
|
|
+ {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.provider}</span>}
|
|
|
+ {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
|
|
|
+ </button>
|
|
|
+ );
|
|
|
+ }) }
|
|
|
+ { nonUserRelatedGrantedGroups.map((group) => {
|
|
|
+ return (
|
|
|
+ <button
|
|
|
+ className="btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 active"
|
|
|
+ type="button"
|
|
|
+ key={group.id}
|
|
|
+ disabled
|
|
|
>
|
|
|
- <span className="align-middle"><input type="checkbox" checked={groupIsGranted} /></span>
|
|
|
- <h5 className="d-inline-block ms-3">{group.item.name}</h5>
|
|
|
- {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.item.provider}</span>}
|
|
|
+ <span className="align-middle"><input type="checkbox" checked disabled /></span>
|
|
|
+ <h5 className="d-inline-block ms-3">{group.name}</h5>
|
|
|
+ {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.provider}</span>}
|
|
|
{/* TODO: Replace <div className="small">(TBD) List group members</div> */}
|
|
|
</button>
|
|
|
);
|
|
|
@@ -225,7 +263,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
|
|
|
</div>
|
|
|
);
|
|
|
|
|
|
- }, [currentUser?.admin, groupListItemClickHandler, myUserGroups, shouldFetch, t, userRelatedGrantedGroups]);
|
|
|
+ }, [currentUser?.admin, groupListItemClickHandler, shouldFetch, t, groupGrantData, selectedGrant?.userRelatedGrantedGroups]);
|
|
|
|
|
|
return (
|
|
|
<>
|