R: Replace AccessMode with RoleStore

This commit is contained in:
Ivan 2025-01-15 23:03:23 +03:00
parent 55fa09c6fb
commit 539ed87ddf
14 changed files with 147 additions and 164 deletions

View File

@ -1,25 +0,0 @@
'use client';
import { createContext, useContext, useState } from 'react';
import { UserLevel } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
interface IAccessModeContext {
accessLevel: UserLevel;
setAccessLevel: React.Dispatch<React.SetStateAction<UserLevel>>;
}
const AccessContext = createContext<IAccessModeContext | null>(null);
export const useAccessMode = () => {
const context = useContext(AccessContext);
if (!context) {
throw new Error(contextOutsideScope('useAccessMode', 'AccessModeState'));
}
return context;
};
export const AccessModeState = ({ children }: React.PropsWithChildren) => {
const [accessLevel, setAccessLevel] = useState<UserLevel>(UserLevel.READER);
return <AccessContext value={{ accessLevel, setAccessLevel }}>{children}</AccessContext>;
};

View File

@ -103,7 +103,7 @@ export interface ITargetUsers {
/** /**
* Represents user access mode. * Represents user access mode.
*/ */
export enum UserLevel { export enum UserRole {
READER = 0, READER = 0,
EDITOR, EDITOR,
OWNER, OWNER,

View File

@ -19,13 +19,13 @@ import Button from '@/components/ui/Button';
import Divider from '@/components/ui/Divider'; import Divider from '@/components/ui/Divider';
import Dropdown from '@/components/ui/Dropdown'; import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton'; import DropdownButton from '@/components/ui/DropdownButton';
import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext'; import { useOSS } from '@/context/OssContext';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import { UserLevel } from '@/models/user'; import { UserRole } from '@/models/user';
import { describeAccessMode, labelAccessMode } from '@/utils/labels'; import { useRoleStore } from '@/stores/role';
import { describeAccessMode as describeUserRole, labelAccessMode as labelUserRole } from '@/utils/labels';
import { useOssEdit } from './OssEditContext'; import { useOssEdit } from './OssEditContext';
@ -39,7 +39,8 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
const { user } = useAuth(); const { user } = useAuth();
const model = useOSS(); const model = useOSS();
const { accessLevel, setAccessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const setRole = useRoleStore(state => state.setRole);
const schemaMenu = useDropdown(); const schemaMenu = useDropdown();
const editMenu = useDropdown(); const editMenu = useDropdown();
@ -55,9 +56,9 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
controller.share(); controller.share();
} }
function handleChangeMode(newMode: UserLevel) { function handleChangeRole(newMode: UserRole) {
accessMenu.hide(); accessMenu.hide();
setAccessLevel(newMode); setRole(newMode);
} }
function handleCreateNew() { function handleCreateNew() {
@ -97,7 +98,7 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
<DropdownButton <DropdownButton
text='Удалить схему' text='Удалить схему'
icon={<IconDestroy size='1rem' className='icon-red' />} icon={<IconDestroy size='1rem' className='icon-red' />}
disabled={controller.isProcessing || accessLevel < UserLevel.OWNER} disabled={controller.isProcessing || role < UserRole.OWNER}
onClick={handleDelete} onClick={handleDelete}
/> />
) : null} ) : null}
@ -151,15 +152,15 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
noBorder noBorder
noOutline noOutline
tabIndex={-1} tabIndex={-1}
title={`Режим ${labelAccessMode(accessLevel)}`} title={`Режим ${labelUserRole(role)}`}
hideTitle={accessMenu.isOpen} hideTitle={accessMenu.isOpen}
className='h-full pr-2' className='h-full pr-2'
icon={ icon={
accessLevel === UserLevel.ADMIN ? ( role === UserRole.ADMIN ? (
<IconAdmin size='1.25rem' className='icon-primary' /> <IconAdmin size='1.25rem' className='icon-primary' />
) : accessLevel === UserLevel.OWNER ? ( ) : role === UserRole.OWNER ? (
<IconOwner size='1.25rem' className='icon-primary' /> <IconOwner size='1.25rem' className='icon-primary' />
) : accessLevel === UserLevel.EDITOR ? ( ) : role === UserRole.EDITOR ? (
<IconEditor size='1.25rem' className='icon-primary' /> <IconEditor size='1.25rem' className='icon-primary' />
) : ( ) : (
<IconReader size='1.25rem' className='icon-primary' /> <IconReader size='1.25rem' className='icon-primary' />
@ -169,31 +170,31 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
/> />
<Dropdown isOpen={accessMenu.isOpen}> <Dropdown isOpen={accessMenu.isOpen}>
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.READER)} text={labelUserRole(UserRole.READER)}
title={describeAccessMode(UserLevel.READER)} title={describeUserRole(UserRole.READER)}
icon={<IconReader size='1rem' className='icon-primary' />} icon={<IconReader size='1rem' className='icon-primary' />}
onClick={() => handleChangeMode(UserLevel.READER)} onClick={() => handleChangeRole(UserRole.READER)}
/> />
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.EDITOR)} text={labelUserRole(UserRole.EDITOR)}
title={describeAccessMode(UserLevel.EDITOR)} title={describeUserRole(UserRole.EDITOR)}
icon={<IconEditor size='1rem' className='icon-primary' />} icon={<IconEditor size='1rem' className='icon-primary' />}
disabled={!model.isOwned && !model.schema?.editors.includes(user.id)} disabled={!model.isOwned && !model.schema?.editors.includes(user.id)}
onClick={() => handleChangeMode(UserLevel.EDITOR)} onClick={() => handleChangeRole(UserRole.EDITOR)}
/> />
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.OWNER)} text={labelUserRole(UserRole.OWNER)}
title={describeAccessMode(UserLevel.OWNER)} title={describeUserRole(UserRole.OWNER)}
icon={<IconOwner size='1rem' className='icon-primary' />} icon={<IconOwner size='1rem' className='icon-primary' />}
disabled={!model.isOwned} disabled={!model.isOwned}
onClick={() => handleChangeMode(UserLevel.OWNER)} onClick={() => handleChangeRole(UserRole.OWNER)}
/> />
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.ADMIN)} text={labelUserRole(UserRole.ADMIN)}
title={describeAccessMode(UserLevel.ADMIN)} title={describeUserRole(UserRole.ADMIN)}
icon={<IconAdmin size='1rem' className='icon-primary' />} icon={<IconAdmin size='1rem' className='icon-primary' />}
disabled={!user?.is_staff} disabled={!user?.is_staff}
onClick={() => handleChangeMode(UserLevel.ADMIN)} onClick={() => handleChangeRole(UserRole.ADMIN)}
/> />
</Dropdown> </Dropdown>
</div> </div>

View File

@ -4,7 +4,6 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState }
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
@ -30,8 +29,9 @@ import {
OperationID, OperationID,
OperationType OperationType
} from '@/models/oss'; } from '@/models/oss';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserRole } from '@/models/user';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { errors, information } from '@/utils/labels'; import { errors, information } from '@/utils/labels';
@ -96,14 +96,13 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuth();
const adminMode = usePreferencesStore(state => state.adminMode); const adminMode = usePreferencesStore(state => state.adminMode);
const { accessLevel, setAccessLevel } = useAccessMode();
const role = useRoleStore(state => state.role);
const adjustRole = useRoleStore(state => state.adjustRole);
const model = useOSS(); const model = useOSS();
const library = useLibrary(); const library = useLibrary();
const isMutable = useMemo( const isMutable = role > UserRole.READER && !model.schema?.read_only;
() => accessLevel > UserLevel.READER && !model.schema?.read_only,
[accessLevel, model.schema?.read_only]
);
const [showTooltip, setShowTooltip] = useState(true); const [showTooltip, setShowTooltip] = useState(true);
@ -128,23 +127,13 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
useEffect( useEffect(
() => () =>
setAccessLevel(prev => { adjustRole({
if ( isOwner: model.isOwned,
prev === UserLevel.EDITOR && isEditor: (user && model.schema?.editors.includes(user?.id)) ?? false,
(model.isOwned || user?.is_staff || (user && model.schema?.editors.includes(user.id))) isStaff: user?.is_staff ?? false,
) { adminMode: adminMode
return UserLevel.EDITOR;
} else if (user?.is_staff && (prev === UserLevel.ADMIN || adminMode)) {
return UserLevel.ADMIN;
} else if (model.isOwned) {
return UserLevel.OWNER;
} else if (user?.id && model.schema?.editors.includes(user?.id)) {
return UserLevel.EDITOR;
} else {
return UserLevel.READER;
}
}), }),
[model.schema, setAccessLevel, model.isOwned, user, adminMode] [model.schema, adjustRole, model.isOwned, user, adminMode]
); );
const handleSetLocation = useCallback( const handleSetLocation = useCallback(

View File

@ -2,7 +2,6 @@
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { AccessModeState } from '@/context/AccessModeContext';
import { OssState } from '@/context/OssContext'; import { OssState } from '@/context/OssContext';
import OssTabs from './OssTabs'; import OssTabs from './OssTabs';
@ -10,11 +9,9 @@ import OssTabs from './OssTabs';
function OssPage() { function OssPage() {
const params = useParams(); const params = useParams();
return ( return (
<AccessModeState>
<OssState itemID={params.id ?? ''}> <OssState itemID={params.id ?? ''}>
<OssTabs /> <OssTabs />
</OssState> </OssState>
</AccessModeState>
); );
} }

View File

@ -17,13 +17,13 @@ import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import Tooltip from '@/components/ui/Tooltip'; import Tooltip from '@/components/ui/Tooltip';
import ValueIcon from '@/components/ui/ValueIcon'; import ValueIcon from '@/components/ui/ValueIcon';
import { useAccessMode } from '@/context/AccessModeContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useUsers } from '@/context/UsersContext'; import { useUsers } from '@/context/UsersContext';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import { ILibraryItemData, ILibraryItemEditor } from '@/models/library'; import { ILibraryItemData, ILibraryItemEditor } from '@/models/library';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserRole } from '@/models/user';
import { useLibrarySearchStore } from '@/stores/librarySearch'; import { useLibrarySearchStore } from '@/stores/librarySearch';
import { useRoleStore } from '@/stores/role';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
@ -35,7 +35,7 @@ interface EditorLibraryItemProps {
function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemProps) { function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemProps) {
const { getUserLabel, users } = useUsers(); const { getUserLabel, users } = useUsers();
const { accessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const intl = useIntl(); const intl = useIntl();
const router = useConceptNavigation(); const router = useConceptNavigation();
const setLocation = useLibrarySearchStore(state => state.setLocation); const setLocation = useLibrarySearchStore(state => state.setLocation);
@ -86,9 +86,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
value={item.location} value={item.location}
title={controller.isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Путь'} title={controller.isAttachedToOSS ? 'Путь наследуется от ОСС' : 'Путь'}
onClick={controller.promptLocation} onClick={controller.promptLocation}
disabled={ disabled={isModified || controller.isProcessing || controller.isAttachedToOSS || role < UserRole.OWNER}
isModified || controller.isProcessing || controller.isAttachedToOSS || accessLevel < UserLevel.OWNER
}
/> />
</div> </div>
@ -110,7 +108,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
value={getUserLabel(item.owner)} value={getUserLabel(item.owner)}
title={controller.isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Владелец'} title={controller.isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Владелец'}
onClick={ownerSelector.toggle} onClick={ownerSelector.toggle}
disabled={isModified || controller.isProcessing || controller.isAttachedToOSS || accessLevel < UserLevel.OWNER} disabled={isModified || controller.isProcessing || controller.isAttachedToOSS || role < UserRole.OWNER}
/> />
<div className='sm:mb-1 flex justify-between items-center'> <div className='sm:mb-1 flex justify-between items-center'>
@ -120,7 +118,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
icon={<IconEditor size='1.25rem' className='icon-primary' />} icon={<IconEditor size='1.25rem' className='icon-primary' />}
value={item.editors.length} value={item.editors.length}
onClick={controller.promptEditors} onClick={controller.promptEditors}
disabled={isModified || controller.isProcessing || accessLevel < UserLevel.OWNER} disabled={isModified || controller.isProcessing || role < UserRole.OWNER}
/> />
<Tooltip anchorSelect='#editor_stats' layer='z-modalTooltip'> <Tooltip anchorSelect='#editor_stats' layer='z-modalTooltip'>
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} header='Редакторы' /> <InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} header='Редакторы' />

View File

@ -5,10 +5,10 @@ import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext';
import { AccessPolicy, ILibraryItemEditor } from '@/models/library'; import { AccessPolicy, ILibraryItemEditor } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { UserLevel } from '@/models/user'; import { UserRole } from '@/models/user';
import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
interface ToolbarItemAccessProps { interface ToolbarItemAccessProps {
@ -20,7 +20,7 @@ interface ToolbarItemAccessProps {
} }
function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, controller }: ToolbarItemAccessProps) { function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, controller }: ToolbarItemAccessProps) {
const { accessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const policy = controller.schema?.access_policy ?? AccessPolicy.PRIVATE; const policy = controller.schema?.access_policy ?? AccessPolicy.PRIVATE;
return ( return (
@ -28,7 +28,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
<Label text='Доступ' className='self-center select-none' /> <Label text='Доступ' className='self-center select-none' />
<div className='ml-auto cc-icons'> <div className='ml-auto cc-icons'>
<SelectAccessPolicy <SelectAccessPolicy
disabled={accessLevel <= UserLevel.EDITOR || controller.isProcessing || controller.isAttachedToOSS} disabled={role <= UserRole.EDITOR || controller.isProcessing || controller.isAttachedToOSS}
value={policy} value={policy}
onChange={newPolicy => controller.setAccessPolicy(newPolicy)} onChange={newPolicy => controller.setAccessPolicy(newPolicy)}
/> />
@ -37,7 +37,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'} title={visible ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
icon={<VisibilityIcon value={visible} />} icon={<VisibilityIcon value={visible} />}
onClick={toggleVisible} onClick={toggleVisible}
disabled={accessLevel === UserLevel.READER || controller.isProcessing} disabled={role === UserRole.READER || controller.isProcessing}
/> />
<MiniButton <MiniButton
@ -50,7 +50,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
) )
} }
onClick={toggleReadOnly} onClick={toggleReadOnly}
disabled={accessLevel === UserLevel.READER || controller.isProcessing} disabled={role === UserRole.READER || controller.isProcessing}
/> />
<BadgeHelp topic={HelpTopic.ACCESS} className={PARAMETER.TOOLTIP_WIDTH} offset={4} /> <BadgeHelp topic={HelpTopic.ACCESS} className={PARAMETER.TOOLTIP_WIDTH} offset={4} />

View File

@ -5,11 +5,11 @@ import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS'; import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext';
import { AccessPolicy, ILibraryItemEditor, LibraryItemType } from '@/models/library'; import { AccessPolicy, ILibraryItemEditor, LibraryItemType } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { UserLevel } from '@/models/user'; import { UserRole } from '@/models/user';
import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels'; import { prepareTooltip, tooltips } from '@/utils/labels';
@ -23,7 +23,7 @@ interface ToolbarRSFormCardProps {
} }
function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: ToolbarRSFormCardProps) { function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: ToolbarRSFormCardProps) {
const { accessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const canSave = modified && !controller.isProcessing; const canSave = modified && !controller.isProcessing;
const ossSelector = (() => { const ossSelector = (() => {
@ -63,7 +63,7 @@ function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: Toolba
<MiniButton <MiniButton
title='Удалить схему' title='Удалить схему'
icon={<IconDestroy size='1.25rem' className='icon-red' />} icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={!controller.isMutable || controller.isProcessing || accessLevel < UserLevel.OWNER} disabled={!controller.isMutable || controller.isProcessing || role < UserRole.OWNER}
onClick={onDestroy} onClick={onDestroy}
/> />
) : null} ) : null}

View File

@ -31,14 +31,14 @@ import Button from '@/components/ui/Button';
import Divider from '@/components/ui/Divider'; import Divider from '@/components/ui/Divider';
import Dropdown from '@/components/ui/Dropdown'; import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton'; import DropdownButton from '@/components/ui/DropdownButton';
import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useGlobalOss } from '@/context/GlobalOssContext'; import { useGlobalOss } from '@/context/GlobalOssContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useRSForm } from '@/context/RSFormContext'; import { useRSForm } from '@/context/RSFormContext';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import { AccessPolicy } from '@/models/library'; import { AccessPolicy } from '@/models/library';
import { UserLevel } from '@/models/user'; import { UserRole } from '@/models/user';
import { useRoleStore } from '@/stores/role';
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels'; import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
import { OssTabID } from '../OssPage/OssTabs'; import { OssTabID } from '../OssPage/OssTabs';
@ -55,7 +55,8 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
const model = useRSForm(); const model = useRSForm();
const oss = useGlobalOss(); const oss = useGlobalOss();
const { accessLevel, setAccessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const setRole = useRoleStore(state => state.setRole);
const schemaMenu = useDropdown(); const schemaMenu = useDropdown();
const editMenu = useDropdown(); const editMenu = useDropdown();
@ -126,9 +127,9 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
controller.inlineSynthesis(); controller.inlineSynthesis();
} }
function handleChangeMode(newMode: UserLevel) { function handleChangeMode(newMode: UserRole) {
accessMenu.hide(); accessMenu.hide();
setAccessLevel(newMode); setRole(newMode);
} }
function handleCreateNew() { function handleCreateNew() {
@ -198,7 +199,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
<DropdownButton <DropdownButton
text='Удалить схему' text='Удалить схему'
icon={<IconDestroy size='1rem' className='icon-red' />} icon={<IconDestroy size='1rem' className='icon-red' />}
disabled={controller.isProcessing || accessLevel < UserLevel.OWNER} disabled={controller.isProcessing || role < UserRole.OWNER}
onClick={handleDelete} onClick={handleDelete}
/> />
) : null} ) : null}
@ -310,15 +311,15 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
noBorder noBorder
noOutline noOutline
tabIndex={-1} tabIndex={-1}
title={`Режим ${labelAccessMode(accessLevel)}`} title={`Режим ${labelAccessMode(role)}`}
hideTitle={accessMenu.isOpen} hideTitle={accessMenu.isOpen}
className='h-full pr-2' className='h-full pr-2'
icon={ icon={
accessLevel === UserLevel.ADMIN ? ( role === UserRole.ADMIN ? (
<IconAdmin size='1.25rem' className='icon-primary' /> <IconAdmin size='1.25rem' className='icon-primary' />
) : accessLevel === UserLevel.OWNER ? ( ) : role === UserRole.OWNER ? (
<IconOwner size='1.25rem' className='icon-primary' /> <IconOwner size='1.25rem' className='icon-primary' />
) : accessLevel === UserLevel.EDITOR ? ( ) : role === UserRole.EDITOR ? (
<IconEditor size='1.25rem' className='icon-primary' /> <IconEditor size='1.25rem' className='icon-primary' />
) : ( ) : (
<IconReader size='1.25rem' className='icon-primary' /> <IconReader size='1.25rem' className='icon-primary' />
@ -328,31 +329,31 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
/> />
<Dropdown isOpen={accessMenu.isOpen}> <Dropdown isOpen={accessMenu.isOpen}>
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.READER)} text={labelAccessMode(UserRole.READER)}
title={describeAccessMode(UserLevel.READER)} title={describeAccessMode(UserRole.READER)}
icon={<IconReader size='1rem' className='icon-primary' />} icon={<IconReader size='1rem' className='icon-primary' />}
onClick={() => handleChangeMode(UserLevel.READER)} onClick={() => handleChangeMode(UserRole.READER)}
/> />
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.EDITOR)} text={labelAccessMode(UserRole.EDITOR)}
title={describeAccessMode(UserLevel.EDITOR)} title={describeAccessMode(UserRole.EDITOR)}
icon={<IconEditor size='1rem' className='icon-primary' />} icon={<IconEditor size='1rem' className='icon-primary' />}
disabled={!model.isOwned && !model.schema?.editors.includes(user.id)} disabled={!model.isOwned && !model.schema?.editors.includes(user.id)}
onClick={() => handleChangeMode(UserLevel.EDITOR)} onClick={() => handleChangeMode(UserRole.EDITOR)}
/> />
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.OWNER)} text={labelAccessMode(UserRole.OWNER)}
title={describeAccessMode(UserLevel.OWNER)} title={describeAccessMode(UserRole.OWNER)}
icon={<IconOwner size='1rem' className='icon-primary' />} icon={<IconOwner size='1rem' className='icon-primary' />}
disabled={!model.isOwned} disabled={!model.isOwned}
onClick={() => handleChangeMode(UserLevel.OWNER)} onClick={() => handleChangeMode(UserRole.OWNER)}
/> />
<DropdownButton <DropdownButton
text={labelAccessMode(UserLevel.ADMIN)} text={labelAccessMode(UserRole.ADMIN)}
title={describeAccessMode(UserLevel.ADMIN)} title={describeAccessMode(UserRole.ADMIN)}
icon={<IconAdmin size='1rem' className='icon-primary' />} icon={<IconAdmin size='1rem' className='icon-primary' />}
disabled={!user?.is_staff} disabled={!user?.is_staff}
onClick={() => handleChangeMode(UserLevel.ADMIN)} onClick={() => handleChangeMode(UserRole.ADMIN)}
/> />
</Dropdown> </Dropdown>
</div> </div>

View File

@ -5,7 +5,6 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState }
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useRSForm } from '@/context/RSFormContext'; import { useRSForm } from '@/context/RSFormContext';
@ -49,8 +48,9 @@ import {
TermForm TermForm
} from '@/models/rsform'; } from '@/models/rsform';
import { generateAlias } from '@/models/rsformAPI'; import { generateAlias } from '@/models/rsformAPI';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserRole } from '@/models/user';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
import { useRoleStore } from '@/stores/role';
import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { information, prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
@ -143,13 +143,11 @@ export const RSEditState = ({
const router = useConceptNavigation(); const router = useConceptNavigation();
const { user } = useAuth(); const { user } = useAuth();
const adminMode = usePreferencesStore(state => state.adminMode); const adminMode = usePreferencesStore(state => state.adminMode);
const { accessLevel, setAccessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const adjustRole = useRoleStore(state => state.adjustRole);
const model = useRSForm(); const model = useRSForm();
const isMutable = useMemo( const isMutable = useMemo(() => role > UserRole.READER && !model.schema?.read_only, [role, model.schema?.read_only]);
() => accessLevel > UserLevel.READER && !model.schema?.read_only,
[accessLevel, model.schema?.read_only]
);
const isContentEditable = useMemo(() => isMutable && !model.isArchive, [isMutable, model.isArchive]); const isContentEditable = useMemo(() => isMutable && !model.isArchive, [isMutable, model.isArchive]);
const canDeleteSelected = useMemo( const canDeleteSelected = useMemo(
() => selected.length > 0 && selected.every(id => !model.schema?.cstByID.get(id)?.is_inherited), () => selected.length > 0 && selected.every(id => !model.schema?.cstByID.get(id)?.is_inherited),
@ -199,23 +197,13 @@ export const RSEditState = ({
useEffect( useEffect(
() => () =>
setAccessLevel(prev => { adjustRole({
if ( isOwner: model.isOwned,
prev === UserLevel.EDITOR && isEditor: (user && model.schema?.editors.includes(user?.id)) ?? false,
(model.isOwned || user?.is_staff || (user && model.schema?.editors.includes(user.id))) isStaff: user?.is_staff ?? false,
) { adminMode: adminMode
return UserLevel.EDITOR;
} else if (user?.is_staff && (prev === UserLevel.ADMIN || adminMode)) {
return UserLevel.ADMIN;
} else if (model.isOwned) {
return UserLevel.OWNER;
} else if (user?.id && model.schema?.editors.includes(user?.id)) {
return UserLevel.EDITOR;
} else {
return UserLevel.READER;
}
}), }),
[model.schema, setAccessLevel, model.isOwned, user, adminMode] [model.schema, adjustRole, model.isOwned, user, adminMode]
); );
const updateSchema = useCallback( const updateSchema = useCallback(

View File

@ -2,7 +2,6 @@
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { AccessModeState } from '@/context/AccessModeContext';
import { RSFormState } from '@/context/RSFormContext'; import { RSFormState } from '@/context/RSFormContext';
import useQueryStrings from '@/hooks/useQueryStrings'; import useQueryStrings from '@/hooks/useQueryStrings';
@ -13,11 +12,9 @@ function RSFormPage() {
const query = useQueryStrings(); const query = useQueryStrings();
const version = query.get('v') ?? undefined; const version = query.get('v') ?? undefined;
return ( return (
<AccessModeState>
<RSFormState itemID={params.id ?? ''} versionID={version}> <RSFormState itemID={params.id ?? ''} versionID={version}>
<RSTabs /> <RSTabs />
</RSFormState> </RSFormState>
</AccessModeState>
); );
} }

View File

@ -3,11 +3,11 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { useAccessMode } from '@/context/AccessModeContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { UserLevel } from '@/models/user'; import { UserRole } from '@/models/user';
import { useFitHeight } from '@/stores/appLayout'; import { useFitHeight } from '@/stores/appLayout';
import { useRoleStore } from '@/stores/role';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import ConstituentsSearch from './ConstituentsSearch'; import ConstituentsSearch from './ConstituentsSearch';
@ -27,8 +27,8 @@ interface ViewConstituentsProps {
function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit, isMounted }: ViewConstituentsProps) { function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit, isMounted }: ViewConstituentsProps) {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { accessLevel } = useAccessMode(); const role = useRoleStore(state => state.role);
const listHeight = useFitHeight(!isBottom ? '8.2rem' : accessLevel !== UserLevel.READER ? '42rem' : '35rem', '10rem'); const listHeight = useFitHeight(!isBottom ? '8.2rem' : role !== UserRole.READER ? '42rem' : '35rem', '10rem');
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []); const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);

View File

@ -0,0 +1,37 @@
import { create } from 'zustand';
import { UserRole } from '@/models/user';
export interface RoleFlags {
isOwner: boolean;
isEditor: boolean;
isStaff: boolean;
adminMode: boolean;
}
interface RoleStore {
role: UserRole;
setRole: (value: UserRole) => void;
adjustRole: (flags: RoleFlags) => void;
}
export const useRoleStore = create<RoleStore>()(set => ({
role: UserRole.READER,
setRole: value => set({ role: value }),
adjustRole: ({ isOwner, isEditor, isStaff, adminMode }: RoleFlags) =>
set(state => {
if (state.role === UserRole.EDITOR && (isOwner || isStaff || isEditor)) {
return { role: UserRole.EDITOR };
}
if (isStaff && (state.role === UserRole.ADMIN || adminMode)) {
return { role: UserRole.ADMIN };
}
if (isOwner) {
return { role: UserRole.OWNER };
}
if (isEditor) {
return { role: UserRole.EDITOR };
}
return { role: UserRole.READER };
})
}));

View File

@ -19,7 +19,7 @@ import {
RSErrorType, RSErrorType,
TokenID TokenID
} from '@/models/rslang'; } from '@/models/rslang';
import { UserLevel } from '@/models/user'; import { UserRole } from '@/models/user';
import { PARAMETER } from './constants'; import { PARAMETER } from './constants';
@ -822,31 +822,31 @@ export function describeSubstitutionError(error: ISubstitutionErrorDescription):
} }
/** /**
* Retrieves label for {@link UserLevel}. * Retrieves label for {@link UserRole}.
*/ */
export function labelAccessMode(mode: UserLevel): string { export function labelAccessMode(mode: UserRole): string {
// prettier-ignore // prettier-ignore
switch (mode) { switch (mode) {
case UserLevel.READER: return 'Читатель'; case UserRole.READER: return 'Читатель';
case UserLevel.EDITOR: return 'Редактор'; case UserRole.EDITOR: return 'Редактор';
case UserLevel.OWNER: return 'Владелец'; case UserRole.OWNER: return 'Владелец';
case UserLevel.ADMIN: return 'Администратор'; case UserRole.ADMIN: return 'Администратор';
} }
} }
/** /**
* Retrieves description for {@link UserLevel}. * Retrieves description for {@link UserRole}.
*/ */
export function describeAccessMode(mode: UserLevel): string { export function describeAccessMode(mode: UserRole): string {
// prettier-ignore // prettier-ignore
switch (mode) { switch (mode) {
case UserLevel.READER: case UserRole.READER:
return 'Режим запрещает редактирование'; return 'Режим запрещает редактирование';
case UserLevel.EDITOR: case UserRole.EDITOR:
return 'Режим редактирования'; return 'Режим редактирования';
case UserLevel.OWNER: case UserRole.OWNER:
return 'Режим владельца'; return 'Режим владельца';
case UserLevel.ADMIN: case UserRole.ADMIN:
return 'Режим администратора'; return 'Режим администратора';
} }
} }