diff --git a/rsconcept/frontend/src/components/DomainIcons.tsx b/rsconcept/frontend/src/components/DomainIcons.tsx index 514a1d80..8798d645 100644 --- a/rsconcept/frontend/src/components/DomainIcons.tsx +++ b/rsconcept/frontend/src/components/DomainIcons.tsx @@ -75,7 +75,7 @@ export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconPr if (value) { return ; } else { - return ; + return ; } } diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index 7886265a..9ce4274e 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -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'; diff --git a/rsconcept/frontend/src/models/miscellaneous.ts b/rsconcept/frontend/src/models/miscellaneous.ts index 94d2eae3..fa401a17 100644 --- a/rsconcept/frontend/src/models/miscellaneous.ts +++ b/rsconcept/frontend/src/models/miscellaneous.ts @@ -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; } /** diff --git a/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx b/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx index d1b95b3c..d119270d 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx @@ -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(storage.librarySearchHead, undefined); const [subfolders, setSubfolders] = useLocalStorage(storage.librarySearchSubfolders, false); const [isVisible, setIsVisible] = useLocalStorage(storage.librarySearchVisible, true); - const [isOwned, setIsOwned] = useLocalStorage(storage.librarySearchEditor, undefined); + const [isOwned, setIsOwned] = useLocalStorage(storage.librarySearchOwned, undefined); const [isEditor, setIsEditor] = useLocalStorage(storage.librarySearchEditor, undefined); + const [filterUser, setFilterUser] = useLocalStorage(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} diff --git a/rsconcept/frontend/src/pages/LibraryPage/ToolbarSearch.tsx b/rsconcept/frontend/src/pages/LibraryPage/ToolbarSearch.tsx index 4c33ba7c..714b3bd3 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/ToolbarSearch.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/ToolbarSearch.tsx @@ -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>; + 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({
- {user ? ( -
- } - onClick={toggleVisible} - /> +
+ } + onClick={toggleVisible} + /> +
} - onClick={toggleOwned} - /> - - } - onClick={toggleEditor} - /> - - } - onClick={resetFilter} - disabled={!hasCustomFilter} + title='Поиск пользователя' + hideTitle={userMenu.isOpen} + icon={} + onClick={userMenu.toggle} /> + + } + onClick={toggleOwned} + /> + } + onClick={toggleEditor} + /> + + + +
- ) : null} -
+ } + onClick={resetFilter} + disabled={!hasCustomFilter} + /> +
+ +
@@ -163,7 +204,7 @@ function ToolbarSearch({ head ? ( ) : ( - + ) } onClick={handleFolderClick} @@ -171,7 +212,7 @@ function ToolbarSearch({ /> - +
проводник... @@ -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} /> diff --git a/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx b/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx index 1e604240..91e53640 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx @@ -89,13 +89,18 @@ function ViewSideLocation({ place='right-start' />
+ {canRename ? ( + } + titleHtml='Редактирование пути
Перемещаются только Ваши схемы
в указанной папке (и подпапках)' + onClick={onRenameLocation} + /> + ) : null} } - titleHtml='Редактирование пути
Перемещаются только Ваши схемы
в указанной папке (и подпапках)' - onClick={onRenameLocation} - disabled={!canRename} + title='Вложенные папки' // prettier: split-lines + icon={} + onClick={toggleSubfolders} /> - } onClick={toggleSubfolders} /> } title='Переключение в режим Поиск' diff --git a/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpLibrary.tsx b/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpLibrary.tsx index 1b534ad0..4e324567 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpLibrary.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpLibrary.tsx @@ -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() { сортировка по клику на заголовок таблицы +
  • + фильтр по пользователю +
  • фильтр по названию и шифру
  • - фильтр по расположению + фильтр по расположению
  • сбросить фильтры diff --git a/rsconcept/frontend/src/utils/constants.ts b/rsconcept/frontend/src/utils/constants.ts index 97e09d12..8e3bd831 100644 --- a/rsconcept/frontend/src/utils/constants.ts +++ b/rsconcept/frontend/src/utils/constants.ts @@ -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', diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts index 2b4e5f1d..6377647a 100644 --- a/rsconcept/frontend/src/utils/labels.ts +++ b/rsconcept/frontend/src/utils/labels.ts @@ -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 '🖥️ Экстеор'; } }