mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Rework user filter for LibraryPage
This commit is contained in:
parent
77be306cf1
commit
df81c4e125
|
@ -75,7 +75,7 @@ export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconPr
|
|||
if (value) {
|
||||
return <IconSubfolders size={size} className={className ?? 'clr-text-green'} />;
|
||||
} else {
|
||||
return <IconSubfolders size={size} className={className ?? 'clr-text-controls'} />;
|
||||
return <IconSubfolders size={size} className={className ?? 'clr-text-primary'} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export { LuMoon as IconDarkTheme } from 'react-icons/lu';
|
|||
export { LuSun as IconLightTheme } from 'react-icons/lu';
|
||||
export { LuFolderTree as IconFolderTree } from 'react-icons/lu';
|
||||
export { LuFolder as IconFolder } from 'react-icons/lu';
|
||||
export { LuFolderSearch as IconFolderSearch } from 'react-icons/lu';
|
||||
export { LuFolders as IconSubfolders } from 'react-icons/lu';
|
||||
export { LuFolderEdit as IconFolderEdit } from 'react-icons/lu';
|
||||
export { LuFolderOpen as IconFolderOpened } from 'react-icons/lu';
|
||||
|
@ -57,6 +58,7 @@ export { PiFileCsv as IconCSV } from 'react-icons/pi';
|
|||
export { LuUserCircle2 as IconUser } from 'react-icons/lu';
|
||||
export { FaCircleUser as IconUser2 } from 'react-icons/fa6';
|
||||
export { TbUserEdit as IconEditor } from 'react-icons/tb';
|
||||
export { TbUserSearch as IconUserSearch } from 'react-icons/tb';
|
||||
export { LuCrown as IconOwner } from 'react-icons/lu';
|
||||
export { TbMeteor as IconAdmin } from 'react-icons/tb';
|
||||
export { TbMeteorOff as IconAdminOff } from 'react-icons/tb';
|
||||
|
|
|
@ -110,6 +110,9 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
|
|||
if (filter.isEditor !== undefined) {
|
||||
result = result.filter(item => filter.isEditor == user?.editor.includes(item.id));
|
||||
}
|
||||
if (filter.filterUser !== undefined) {
|
||||
result = result.filter(item => filter.filterUser === item.owner);
|
||||
}
|
||||
if (!filter.folderMode && filter.path) {
|
||||
result = result.filter(item => matchLibraryItemLocation(item, filter.path!));
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Node } from 'reactflow';
|
|||
|
||||
import { LibraryItemType, LocationHead } from './library';
|
||||
import { IOperation } from './oss';
|
||||
import { UserID } from './user';
|
||||
|
||||
/**
|
||||
* Represents graph dependency mode.
|
||||
|
@ -182,6 +183,7 @@ export interface ILibraryFilter {
|
|||
isVisible?: boolean;
|
||||
isOwned?: boolean;
|
||||
isEditor?: boolean;
|
||||
filterUser?: UserID;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@ import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
|||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { ILibraryItem, IRenameLocationData, LocationHead } from '@/models/library';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { UserID } from '@/models/user';
|
||||
import { storage } from '@/utils/constants';
|
||||
import { information } from '@/utils/labels';
|
||||
import { convertToCSV, toggleTristateFlag } from '@/utils/utils';
|
||||
|
@ -36,8 +37,9 @@ function LibraryPage() {
|
|||
const [head, setHead] = useLocalStorage<LocationHead | undefined>(storage.librarySearchHead, undefined);
|
||||
const [subfolders, setSubfolders] = useLocalStorage<boolean>(storage.librarySearchSubfolders, false);
|
||||
const [isVisible, setIsVisible] = useLocalStorage<boolean | undefined>(storage.librarySearchVisible, true);
|
||||
const [isOwned, setIsOwned] = useLocalStorage<boolean | undefined>(storage.librarySearchEditor, undefined);
|
||||
const [isOwned, setIsOwned] = useLocalStorage<boolean | undefined>(storage.librarySearchOwned, undefined);
|
||||
const [isEditor, setIsEditor] = useLocalStorage<boolean | undefined>(storage.librarySearchEditor, undefined);
|
||||
const [filterUser, setFilterUser] = useLocalStorage<UserID | undefined>(storage.librarySearchUser, undefined);
|
||||
const [showRenameLocation, setShowRenameLocation] = useState(false);
|
||||
|
||||
const filter: ILibraryFilter = useMemo(
|
||||
|
@ -50,9 +52,22 @@ function LibraryPage() {
|
|||
isVisible: user ? isVisible : true,
|
||||
folderMode: options.folderMode,
|
||||
subfolders: subfolders,
|
||||
location: options.location
|
||||
location: options.location,
|
||||
filterUser: filterUser
|
||||
}),
|
||||
[head, path, query, isEditor, isOwned, isVisible, user, options.folderMode, options.location, subfolders]
|
||||
[
|
||||
head,
|
||||
path,
|
||||
query,
|
||||
isEditor,
|
||||
isOwned,
|
||||
isVisible,
|
||||
user,
|
||||
options.folderMode,
|
||||
options.location,
|
||||
subfolders,
|
||||
filterUser
|
||||
]
|
||||
);
|
||||
|
||||
const hasCustomFilter = useMemo(
|
||||
|
@ -63,6 +78,7 @@ function LibraryPage() {
|
|||
filter.isEditor !== undefined ||
|
||||
filter.isOwned !== undefined ||
|
||||
filter.isVisible !== true ||
|
||||
filter.filterUser !== undefined ||
|
||||
!!filter.location,
|
||||
[filter]
|
||||
);
|
||||
|
@ -84,8 +100,9 @@ function LibraryPage() {
|
|||
setIsVisible(true);
|
||||
setIsOwned(undefined);
|
||||
setIsEditor(undefined);
|
||||
setFilterUser(undefined);
|
||||
options.setLocation('');
|
||||
}, [setHead, setIsVisible, setIsOwned, setIsEditor, options.setLocation]);
|
||||
}, [setHead, setIsVisible, setIsOwned, setIsEditor, setFilterUser, options.setLocation]);
|
||||
|
||||
const promptRenameLocation = useCallback(() => {
|
||||
setShowRenameLocation(true);
|
||||
|
@ -186,6 +203,8 @@ function LibraryPage() {
|
|||
toggleVisible={toggleVisible}
|
||||
isEditor={isEditor}
|
||||
toggleEditor={toggleEditor}
|
||||
filterUser={filterUser}
|
||||
setFilterUser={setFilterUser}
|
||||
resetFilter={resetFilter}
|
||||
folderMode={options.folderMode}
|
||||
toggleFolderMode={toggleFolderMode}
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { LocationIcon, VisibilityIcon } from '@/components/DomainIcons';
|
||||
import { IconEditor, IconFilterReset, IconFolder, IconFolderTree, IconOwner } from '@/components/Icons';
|
||||
import {
|
||||
IconEditor,
|
||||
IconFilterReset,
|
||||
IconFolder,
|
||||
IconFolderSearch,
|
||||
IconFolderTree,
|
||||
IconOwner,
|
||||
IconUserSearch
|
||||
} from '@/components/Icons';
|
||||
import { CProps } from '@/components/props';
|
||||
import SelectUser from '@/components/select/SelectUser';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
import DropdownButton from '@/components/ui/DropdownButton';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import SearchBar from '@/components/ui/SearchBar';
|
||||
import SelectorButton from '@/components/ui/SelectorButton';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useUsers } from '@/context/UsersContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { LocationHead } from '@/models/library';
|
||||
import { UserID } from '@/models/user';
|
||||
import { animateDropdownItem } from '@/styling/animations';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { describeLocationHead, labelLocationHead } from '@/utils/labels';
|
||||
import { tripleToggleColor } from '@/utils/utils';
|
||||
|
@ -39,6 +51,9 @@ interface ToolbarSearchProps {
|
|||
toggleOwned: () => void;
|
||||
isEditor: boolean | undefined;
|
||||
toggleEditor: () => void;
|
||||
filterUser: UserID | undefined;
|
||||
setFilterUser: React.Dispatch<React.SetStateAction<UserID | undefined>>;
|
||||
|
||||
resetFilter: () => void;
|
||||
}
|
||||
|
||||
|
@ -63,10 +78,19 @@ function ToolbarSearch({
|
|||
toggleOwned,
|
||||
isEditor,
|
||||
toggleEditor,
|
||||
filterUser,
|
||||
setFilterUser,
|
||||
|
||||
resetFilter
|
||||
}: ToolbarSearchProps) {
|
||||
const { user } = useAuth();
|
||||
const headMenu = useDropdown();
|
||||
const userMenu = useDropdown();
|
||||
const { users } = useUsers();
|
||||
|
||||
const userActive = useMemo(
|
||||
() => isOwned !== undefined || isEditor !== undefined || filterUser !== undefined,
|
||||
[isOwned, isEditor, filterUser]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newValue: LocationHead | undefined) => {
|
||||
|
@ -106,7 +130,7 @@ function ToolbarSearch({
|
|||
<div
|
||||
className={clsx(
|
||||
'ml-3 pt-1 self-center',
|
||||
'min-w-[4.5rem] sm:min-w-[5.5rem]',
|
||||
'min-w-[4.5rem] sm:min-w-[7.4rem]',
|
||||
'select-none',
|
||||
'whitespace-nowrap'
|
||||
)}
|
||||
|
@ -114,41 +138,58 @@ function ToolbarSearch({
|
|||
{filtered} из {total}
|
||||
</div>
|
||||
|
||||
{user ? (
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
title='Видимость'
|
||||
icon={<VisibilityIcon value={true} className={tripleToggleColor(isVisible)} />}
|
||||
onClick={toggleVisible}
|
||||
/>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
title='Видимость'
|
||||
icon={<VisibilityIcon value={true} className={tripleToggleColor(isVisible)} />}
|
||||
onClick={toggleVisible}
|
||||
/>
|
||||
|
||||
<div ref={userMenu.ref} className='flex'>
|
||||
<MiniButton
|
||||
title='Я - Владелец'
|
||||
icon={<IconOwner size='1.25rem' className={tripleToggleColor(isOwned)} />}
|
||||
onClick={toggleOwned}
|
||||
/>
|
||||
|
||||
<MiniButton
|
||||
title='Я - Редактор'
|
||||
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
|
||||
onClick={toggleEditor}
|
||||
/>
|
||||
|
||||
<MiniButton
|
||||
title='Сбросить фильтры'
|
||||
icon={<IconFilterReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={resetFilter}
|
||||
disabled={!hasCustomFilter}
|
||||
title='Поиск пользователя'
|
||||
hideTitle={userMenu.isOpen}
|
||||
icon={<IconUserSearch size='1.25rem' className={userActive ? 'icon-green' : 'icon-primary'} />}
|
||||
onClick={userMenu.toggle}
|
||||
/>
|
||||
<Dropdown isOpen={userMenu.isOpen}>
|
||||
<DropdownButton
|
||||
text='Я - Владелец'
|
||||
icon={<IconOwner size='1.25rem' className={tripleToggleColor(isOwned)} />}
|
||||
onClick={toggleOwned}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Я - Редактор'
|
||||
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
|
||||
onClick={toggleEditor}
|
||||
/>
|
||||
<motion.div className='px-1 pb-1' variants={animateDropdownItem}>
|
||||
<SelectUser
|
||||
noBorder
|
||||
placeholder='Выберите владельца'
|
||||
className='min-w-[15rem] text-sm'
|
||||
items={users}
|
||||
value={filterUser}
|
||||
onSelectValue={setFilterUser}
|
||||
/>
|
||||
</motion.div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className='flex h-full'>
|
||||
<MiniButton
|
||||
title='Сбросить фильтры'
|
||||
icon={<IconFilterReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={resetFilter}
|
||||
disabled={!hasCustomFilter}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex h-full flex-grow pr-4'>
|
||||
<SearchBar
|
||||
id='library_search'
|
||||
placeholder='Поиск'
|
||||
noBorder
|
||||
className='min-w-[7rem] sm:min-w-[10rem]'
|
||||
className={clsx('min-w-[7rem] sm:min-w-[10rem] max-w-[20rem]', folderMode && 'flex-grow')}
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
/>
|
||||
|
@ -163,7 +204,7 @@ function ToolbarSearch({
|
|||
head ? (
|
||||
<LocationIcon value={head} size='1.25rem' />
|
||||
) : (
|
||||
<IconFolder size='1.25rem' className='clr-text-controls' />
|
||||
<IconFolderSearch size='1.25rem' className='clr-text-controls' />
|
||||
)
|
||||
}
|
||||
onClick={handleFolderClick}
|
||||
|
@ -171,7 +212,7 @@ function ToolbarSearch({
|
|||
/>
|
||||
|
||||
<Dropdown isOpen={headMenu.isOpen} stretchLeft className='z-modalTooltip'>
|
||||
<DropdownButton className='w-[10rem]' title='Переключение в режим Проводник' onClick={handleToggleFolder}>
|
||||
<DropdownButton title='Переключение в режим Проводник' onClick={handleToggleFolder}>
|
||||
<div className='inline-flex items-center gap-3'>
|
||||
<IconFolderTree size='1rem' className='clr-text-controls' />
|
||||
<span>проводник...</span>
|
||||
|
@ -207,7 +248,7 @@ function ToolbarSearch({
|
|||
placeholder='Путь'
|
||||
noIcon
|
||||
noBorder
|
||||
className='min-w-[4.5rem] sm:min-w-[5rem]'
|
||||
className='w-[4.5rem] sm:w-[5rem] flex-grow'
|
||||
value={path}
|
||||
onChange={setPath}
|
||||
/>
|
||||
|
|
|
@ -89,13 +89,18 @@ function ViewSideLocation({
|
|||
place='right-start'
|
||||
/>
|
||||
<div className='cc-icons'>
|
||||
{canRename ? (
|
||||
<MiniButton
|
||||
icon={<IconFolderEdit size='1.25rem' className='icon-primary' />}
|
||||
titleHtml='<b>Редактирование пути</b><br/>Перемещаются только Ваши схемы<br/>в указанной папке (и подпапках)'
|
||||
onClick={onRenameLocation}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
icon={<IconFolderEdit size='1.25rem' className='icon-primary' />}
|
||||
titleHtml='<b>Редактирование пути</b><br/>Перемещаются только Ваши схемы<br/>в указанной папке (и подпапках)'
|
||||
onClick={onRenameLocation}
|
||||
disabled={!canRename}
|
||||
title='Вложенные папки' // prettier: split-lines
|
||||
icon={<SubfoldersIcon value={subfolders} />}
|
||||
onClick={toggleSubfolders}
|
||||
/>
|
||||
<MiniButton title='Вложенные папки' icon={<SubfoldersIcon value={subfolders} />} onClick={toggleSubfolders} />
|
||||
<MiniButton
|
||||
icon={<IconFolderTree size='1.25rem' className='icon-green' />}
|
||||
title='Переключение в режим Поиск'
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
IconFolderEdit,
|
||||
IconFolderEmpty,
|
||||
IconFolderOpened,
|
||||
IconFolderSearch,
|
||||
IconFolderTree,
|
||||
IconOSS,
|
||||
IconRSForm,
|
||||
|
@ -12,7 +13,8 @@ import {
|
|||
IconShow,
|
||||
IconSortAsc,
|
||||
IconSortDesc,
|
||||
IconSubfolders
|
||||
IconSubfolders,
|
||||
IconUserSearch
|
||||
} from '@/components/Icons';
|
||||
import LinkTopic from '@/components/ui/LinkTopic';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
|
@ -43,11 +45,14 @@ function HelpLibrary() {
|
|||
<IconSortAsc size='1rem' className='inline-icon' />
|
||||
<IconSortDesc size='1rem' className='inline-icon' /> сортировка по клику на заголовок таблицы
|
||||
</li>
|
||||
<li>
|
||||
<IconUserSearch size='1rem' className='inline-icon' /> фильтр по пользователю
|
||||
</li>
|
||||
<li>
|
||||
<IconSearch size='1rem' className='inline-icon' /> фильтр по названию и шифру
|
||||
</li>
|
||||
<li>
|
||||
<IconFolder size='1rem' className='inline-icon' /> фильтр по расположению
|
||||
<IconFolderSearch size='1rem' className='inline-icon' /> фильтр по расположению
|
||||
</li>
|
||||
<li>
|
||||
<IconFilterReset size='1rem' className='inline-icon' /> сбросить фильтры
|
||||
|
|
|
@ -117,6 +117,7 @@ export const storage = {
|
|||
librarySearchVisible: 'library.search.visible',
|
||||
librarySearchOwned: 'library.search.owned',
|
||||
librarySearchEditor: 'library.search.editor',
|
||||
librarySearchUser: 'library.search.user',
|
||||
libraryPagination: 'library.pagination',
|
||||
|
||||
rsgraphFilter: 'rsgraph.filter2',
|
||||
|
|
|
@ -362,11 +362,11 @@ export function describeExpressionStatus(status: ExpressionStatus): string {
|
|||
export function labelHelpTopic(topic: HelpTopic): string {
|
||||
// prettier-ignore
|
||||
switch (topic) {
|
||||
case HelpTopic.MAIN: return 'Портал';
|
||||
case HelpTopic.MAIN: return '🏠 Портал';
|
||||
|
||||
case HelpTopic.THESAURUS: return 'Тезаурус';
|
||||
case HelpTopic.THESAURUS: return '📖 Тезаурус';
|
||||
|
||||
case HelpTopic.INTERFACE: return 'Интерфейс';
|
||||
case HelpTopic.INTERFACE: return '🦄 Интерфейс';
|
||||
case HelpTopic.UI_LIBRARY: return 'Библиотека';
|
||||
case HelpTopic.UI_RS_MENU: return 'Меню схемы';
|
||||
case HelpTopic.UI_RS_CARD: return 'Карточка схемы';
|
||||
|
@ -378,7 +378,7 @@ export function labelHelpTopic(topic: HelpTopic): string {
|
|||
case HelpTopic.UI_CST_CLASS: return 'Класс конституенты';
|
||||
case HelpTopic.UI_OSS_GRAPH: return 'Граф синтеза';
|
||||
|
||||
case HelpTopic.CONCEPTUAL: return 'Концептуализация';
|
||||
case HelpTopic.CONCEPTUAL: return '♨️ Концептуализация';
|
||||
case HelpTopic.CC_SYSTEM: return 'Система определений';
|
||||
case HelpTopic.CC_CONSTITUENTA: return 'Конституента';
|
||||
case HelpTopic.CC_RELATIONS: return 'Связи понятий';
|
||||
|
@ -386,24 +386,24 @@ export function labelHelpTopic(topic: HelpTopic): string {
|
|||
case HelpTopic.CC_OSS: return 'Операционная схема';
|
||||
case HelpTopic.CC_PROPAGATION: return 'Сквозные изменения';
|
||||
|
||||
case HelpTopic.RSLANG: return 'Экспликация';
|
||||
case HelpTopic.RSLANG: return '🚀 Экспликация';
|
||||
case HelpTopic.RSL_TYPES: return 'Типизация';
|
||||
case HelpTopic.RSL_CORRECT: return 'Переносимость';
|
||||
case HelpTopic.RSL_INTERPRET: return 'Интерпретируемость';
|
||||
case HelpTopic.RSL_OPERATIONS: return 'Операции';
|
||||
case HelpTopic.RSL_TEMPLATES: return 'Банк выражений';
|
||||
|
||||
case HelpTopic.TERM_CONTROL: return 'Терминологизация';
|
||||
case HelpTopic.ACCESS: return 'Доступы';
|
||||
case HelpTopic.VERSIONS: return 'Версионирование';
|
||||
case HelpTopic.TERM_CONTROL: return '🪸 Терминологизация';
|
||||
case HelpTopic.ACCESS: return '👀 Доступы';
|
||||
case HelpTopic.VERSIONS: return '🏺 Версионирование';
|
||||
|
||||
case HelpTopic.INFO: return 'Информация';
|
||||
case HelpTopic.INFO: return '📰 Информация';
|
||||
case HelpTopic.INFO_RULES: return 'Правила';
|
||||
case HelpTopic.INFO_CONTRIB: return 'Разработчики';
|
||||
case HelpTopic.INFO_PRIVACY: return 'Обработка данных';
|
||||
case HelpTopic.INFO_API: return 'REST API';
|
||||
|
||||
case HelpTopic.EXTEOR: return 'Экстеор';
|
||||
case HelpTopic.EXTEOR: return '🖥️ Экстеор';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user