diff --git a/rsconcept/frontend/src/components/info/InfoTopic.tsx b/rsconcept/frontend/src/components/info/InfoTopic.tsx index 27d2de57..d79acc8e 100644 --- a/rsconcept/frontend/src/components/info/InfoTopic.tsx +++ b/rsconcept/frontend/src/components/info/InfoTopic.tsx @@ -34,14 +34,14 @@ function InfoTopic({ topic }: InfoTopicProps) { if (topic === HelpTopic.MAIN) return ; if (topic === HelpTopic.INTERFACE) return ; - if (topic === HelpTopic.LIBRARY) return ; - if (topic === HelpTopic.RSFORM_UI) return ; - if (topic === HelpTopic.RSFORM_CARD) return ; - if (topic === HelpTopic.RSFORM_LIST) return ; - if (topic === HelpTopic.RSFORM_EDITOR) return ; - if (topic === HelpTopic.GRAPH_TERM) return ; - if (topic === HelpTopic.CST_STATUS) return ; - if (topic === HelpTopic.CST_CLASS) return ; + if (topic === HelpTopic.UI_LIBRARY) return ; + if (topic === HelpTopic.UI_RSFORM) return ; + if (topic === HelpTopic.UI_RSFORM_CARD) return ; + if (topic === HelpTopic.UI_RSFORM_LIST) return ; + if (topic === HelpTopic.UI_RSFORM_EDITOR) return ; + if (topic === HelpTopic.UI_GRAPH_TERM) return ; + if (topic === HelpTopic.UI_CST_STATUS) return ; + if (topic === HelpTopic.UI_CST_CLASS) return ; if (topic === HelpTopic.CONCEPTUAL) return ; if (topic === HelpTopic.CC_SYSTEM) return ; diff --git a/rsconcept/frontend/src/models/miscellaneous.ts b/rsconcept/frontend/src/models/miscellaneous.ts index ca64b35b..dd2c9cfe 100644 --- a/rsconcept/frontend/src/models/miscellaneous.ts +++ b/rsconcept/frontend/src/models/miscellaneous.ts @@ -45,14 +45,14 @@ export enum HelpTopic { MAIN = 'main', INTERFACE = 'user-interface', - LIBRARY = 'ui-library', - RSFORM_UI = 'ui-rsform', - RSFORM_CARD = 'ui-rsform-card', - RSFORM_LIST = 'ui-rsform-list', - RSFORM_EDITOR = 'ui-rsform-editor', - GRAPH_TERM = 'ui-rsform-graph', - CST_STATUS = 'ui-rsform-cst-status', - CST_CLASS = 'ui-rsform-cst-class', + UI_LIBRARY = 'ui-library', + UI_RSFORM = 'ui-rsform', + UI_RSFORM_CARD = 'ui-rsform-card', + UI_RSFORM_LIST = 'ui-rsform-list', + UI_RSFORM_EDITOR = 'ui-rsform-editor', + UI_GRAPH_TERM = 'ui-rsform-graph', + UI_CST_STATUS = 'ui-rsform-cst-status', + UI_CST_CLASS = 'ui-rsform-cst-class', CONCEPTUAL = 'concept', CC_SYSTEM = 'rslang-rsform', @@ -73,6 +73,46 @@ export enum HelpTopic { PRIVACY = 'privacy' } +/** + * Manual topics hierarchy. + */ +export const topicParent: Map = new Map([ + [HelpTopic.MAIN, HelpTopic.MAIN], + + [HelpTopic.INTERFACE, HelpTopic.INTERFACE], + [HelpTopic.UI_LIBRARY, HelpTopic.INTERFACE], + [HelpTopic.UI_RSFORM, HelpTopic.INTERFACE], + [HelpTopic.UI_RSFORM_CARD, HelpTopic.UI_RSFORM], + [HelpTopic.UI_RSFORM_LIST, HelpTopic.UI_RSFORM], + [HelpTopic.UI_RSFORM_EDITOR, HelpTopic.UI_RSFORM], + [HelpTopic.UI_GRAPH_TERM, HelpTopic.UI_RSFORM], + [HelpTopic.UI_CST_STATUS, HelpTopic.UI_RSFORM], + [HelpTopic.UI_CST_CLASS, HelpTopic.UI_RSFORM], + + [HelpTopic.CONCEPTUAL, HelpTopic.CONCEPTUAL], + [HelpTopic.CC_SYSTEM, HelpTopic.CONCEPTUAL], + [HelpTopic.CC_CONSTITUENTA, HelpTopic.CONCEPTUAL], + [HelpTopic.CC_RELATIONS, HelpTopic.CONCEPTUAL], + + [HelpTopic.RSLANG, HelpTopic.RSLANG], + [HelpTopic.RSL_TYPES, HelpTopic.RSLANG], + [HelpTopic.RSL_CORRECT, HelpTopic.RSLANG], + [HelpTopic.RSL_INTERPRET, HelpTopic.RSLANG], + [HelpTopic.RSL_TEMPLATES, HelpTopic.RSLANG], + [HelpTopic.RSL_OPERATIONS, HelpTopic.RSLANG], + + [HelpTopic.TERM_CONTROL, HelpTopic.TERM_CONTROL], + [HelpTopic.VERSIONS, HelpTopic.VERSIONS], + [HelpTopic.EXTEOR, HelpTopic.EXTEOR], + [HelpTopic.API, HelpTopic.API], + [HelpTopic.PRIVACY, HelpTopic.PRIVACY] +]); + +/** + * Topics that can be folded. + */ +export const foldableTopics = [HelpTopic.INTERFACE, HelpTopic.UI_RSFORM, HelpTopic.RSLANG]; + /** * Represents {@link IConstituenta} matching mode. */ diff --git a/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx b/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx index db9506d5..87c9ef31 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx @@ -124,7 +124,7 @@ function ViewLibrary({ items, resetQuery }: ViewLibraryProps) { 'flex gap-1' )} > - + >( + new Map( + Object.values(HelpTopic).map(value => { + const topic = value as HelpTopic; + return [ + topic, + topicParent.get(activeTopic) !== topic && topicParent.get(topicParent.get(activeTopic)!) !== topic + ]; + }) + ) + ); const { mainHeight } = useConceptOptions(); + const onFoldTopic = useCallback( + (target: HelpTopic, showChildren: boolean) => { + if (topicFolded.get(target) === !showChildren) { + return; + } + setFolded( + new Map( + Object.values(HelpTopic).map(value => { + const topic = value as HelpTopic; + if (topic === target) { + return [topic, !showChildren]; + } + if ( + !showChildren && + (topicParent.get(topic) === target || topicParent.get(topicParent.get(topic)!) === target) + ) { + return [topic, true]; + } + const oldValue = topicFolded.get(topic)!; + return [topic, oldValue]; + }) + ) + ); + }, + [topicFolded] + ); - function onSelectTopic(newTopic: HelpTopic) { - router.push(urls.help_topic(newTopic)); - } + const onSelectTopic = useCallback( + (newTopic: HelpTopic) => { + router.push(urls.help_topic(newTopic)); + }, + [router] + ); return (
- onSelectTopic(topic)} /> - + onSelectTopic(topic)} + topicFolded={topicFolded} + onFoldTopic={onFoldTopic} + /> +
); } diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx index 4b55d726..5a2f0e6b 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx @@ -15,10 +15,12 @@ import TopicsTree from './TopicsTree'; interface TopicsDropdownProps { activeTopic: HelpTopic; + topicFolded: Map; onChangeTopic: (newTopic: HelpTopic) => void; + onFoldTopic: (target: HelpTopic, showChildren: boolean) => void; } -function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) { +function TopicsDropdown({ activeTopic, topicFolded, onChangeTopic, onFoldTopic }: TopicsDropdownProps) { const menu = useDropdown(); const { noNavigation, calculateHeight } = useConceptOptions(); @@ -34,7 +36,7 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
- +
); diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsList.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsList.tsx index 5a3b1075..e9d24004 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/TopicsList.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsList.tsx @@ -8,16 +8,32 @@ import TopicsStatic from './TopicsStatic'; interface TopicsListProps { activeTopic: HelpTopic; + topicFolded: Map; onChangeTopic: (newTopic: HelpTopic) => void; + onFoldTopic: (target: HelpTopic, showChildren: boolean) => void; } -function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) { +function TopicsList({ activeTopic, topicFolded, onChangeTopic, onFoldTopic }: TopicsListProps) { const size = useWindowSize(); if (!size.isSmall) { - return ; + return ( + + ); } else { - return ; + return ( + + ); } } diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx index 3ecb6a75..a10b6b1c 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx @@ -7,16 +7,18 @@ import TopicsTree from './TopicsTree'; interface TopicsStaticProps { activeTopic: HelpTopic; + topicFolded: Map; onChangeTopic: (newTopic: HelpTopic) => void; + onFoldTopic: (target: HelpTopic, showChildren: boolean) => void; } -function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) { +function TopicsStatic({ activeTopic, topicFolded, onChangeTopic, onFoldTopic }: TopicsStaticProps) { const { calculateHeight } = useConceptOptions(); return (
- +
); } diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx index fbaba992..da765b9b 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsTree.tsx @@ -1,35 +1,75 @@ 'use client'; import clsx from 'clsx'; -import { AnimatePresence } from 'framer-motion'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useCallback } from 'react'; -import { HelpTopic } from '@/models/miscellaneous'; +import { IconDropArrow, IconPageRight } from '@/components/Icons'; +import { CProps } from '@/components/props'; +import MiniButton from '@/components/ui/MiniButton'; +import Overlay from '@/components/ui/Overlay'; +import { foldableTopics, HelpTopic, topicParent } from '@/models/miscellaneous'; +import { animateSideAppear } from '@/styling/animations'; import { prefixes } from '@/utils/constants'; import { describeHelpTopic, labelHelpTopic } from '@/utils/labels'; interface TopicsTreeProps { activeTopic: HelpTopic; + topicFolded: Map; onChangeTopic: (newTopic: HelpTopic) => void; + onFoldTopic: (target: HelpTopic, showChildren: boolean) => void; } -function TopicsTree({ activeTopic, onChangeTopic }: TopicsTreeProps) { +function TopicsTree({ activeTopic, topicFolded, onChangeTopic, onFoldTopic }: TopicsTreeProps) { + const handleClickFold = useCallback( + (event: CProps.EventMouse, topic: HelpTopic, showChildren: boolean) => { + event.preventDefault(); + event.stopPropagation(); + onFoldTopic(topic, showChildren); + }, + [onFoldTopic] + ); return ( - {Object.values(HelpTopic).map((topic, index) => ( -
onChangeTopic(topic)} - > - {labelHelpTopic(topic)} -
- ))} + {Object.values(HelpTopic).map((topic, index) => { + const parent = topicParent.get(topic); + if (parent !== topic && topicFolded.get(topicParent.get(topic)!)) { + return null; + } + const isFoldable = !!foldableTopics.find(id => id === topic); + const isFolded = topicFolded.get(topic)!; + return ( + onChangeTopic(topic)} + initial={{ ...animateSideAppear.initial }} + animate={{ ...animateSideAppear.animate }} + exit={{ ...animateSideAppear.exit }} + > + {isFoldable ? ( + + : } + onClick={event => handleClickFold(event, topic, isFolded)} + /> + + ) : null} + {labelHelpTopic(topic)} + + ); + })}
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx index 128b3798..b699a0cb 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx @@ -74,7 +74,7 @@ function ConstituentaToolbar({ disabled={disabled || modified} onClick={onMoveDown} /> - + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx index 8e94de4c..84f22d34 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx @@ -205,7 +205,7 @@ function EditorRSExpression({ parseData={parser.parseData} onAnalyze={() => handleCheckExpression()} /> - + ) : null} - + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx index 9cc183b3..9f34e63b 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx @@ -68,7 +68,7 @@ function RSListToolbar() { disabled={controller.isProcessing || controller.nothingSelected} onClick={controller.deleteCst} /> - + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphSelectors.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphSelectors.tsx index 506b367f..5454ac6e 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphSelectors.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphSelectors.tsx @@ -28,8 +28,8 @@ function GraphSelectors({ coloring, setColoring, layout, setLayout, sizing, setS onChange={data => setLayout(data?.value ?? SelectorGraphLayout[0].value)} /> - {coloring === 'status' ? : null} - {coloring === 'type' ? : null} + {coloring === 'status' ? : null} + {coloring === 'type' ? : null} - + ); } diff --git a/rsconcept/frontend/src/styling/animations.ts b/rsconcept/frontend/src/styling/animations.ts index 4a3855fd..61dce04c 100644 --- a/rsconcept/frontend/src/styling/animations.ts +++ b/rsconcept/frontend/src/styling/animations.ts @@ -208,6 +208,28 @@ export const animateSideView = { } }; +export const animateSideAppear = { + initial: { + clipPath: 'inset(0% 100% 0% 0%)' + }, + animate: { + clipPath: 'inset(0% 0% 0% 0%)', + transition: { + type: 'spring', + bounce: 0, + duration: 0.3 + } + }, + exit: { + clipPath: 'inset(0% 100% 0% 0%)', + transition: { + type: 'spring', + bounce: 0, + duration: 0.3 + } + } +}; + export const animateModal = { initial: { clipPath: 'inset(50% 50% 50% 50%)', diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts index b72c5d68..97f85480 100644 --- a/rsconcept/frontend/src/utils/labels.ts +++ b/rsconcept/frontend/src/utils/labels.ts @@ -361,14 +361,14 @@ export function labelHelpTopic(topic: HelpTopic): string { case HelpTopic.MAIN: return 'Портал'; case HelpTopic.INTERFACE: return 'Интерфейс'; - case HelpTopic.LIBRARY: return '- библиотека'; - case HelpTopic.RSFORM_UI: return '- концептуальная схема'; - case HelpTopic.RSFORM_CARD: return '= карточка схемы'; - case HelpTopic.RSFORM_LIST: return '= список конституент'; - case HelpTopic.RSFORM_EDITOR: return '= редактор конституенты'; - case HelpTopic.GRAPH_TERM: return '= граф термов'; - case HelpTopic.CST_STATUS: return '= статус конституенты'; - case HelpTopic.CST_CLASS: return '= класс конституенты'; + case HelpTopic.UI_LIBRARY: return '- библиотека'; + case HelpTopic.UI_RSFORM: return '- концептуальная схема'; + case HelpTopic.UI_RSFORM_CARD: return '= карточка схемы'; + case HelpTopic.UI_RSFORM_LIST: return '= список конституент'; + case HelpTopic.UI_RSFORM_EDITOR: return '= редактор конституенты'; + case HelpTopic.UI_GRAPH_TERM: return '= граф термов'; + case HelpTopic.UI_CST_STATUS: return '= статус конституенты'; + case HelpTopic.UI_CST_CLASS: return '= класс конституенты'; case HelpTopic.CONCEPTUAL: return 'Концептуализация'; case HelpTopic.CC_SYSTEM: return '- система определений'; @@ -399,14 +399,14 @@ export function describeHelpTopic(topic: HelpTopic): string { case HelpTopic.MAIN: return 'Общая справка по порталу'; case HelpTopic.INTERFACE: return 'Описание интерфейса пользователя'; - case HelpTopic.LIBRARY: return 'Интерфейс Библиотеки схем'; - case HelpTopic.RSFORM_UI: return 'Просмотр и редактирование концептуальной схемы'; - case HelpTopic.RSFORM_CARD: return 'Интерфейс Карточки схемы'; - case HelpTopic.RSFORM_LIST: return 'Интерфейс Списка конституент'; - case HelpTopic.RSFORM_EDITOR: return 'Интерфейс редактирования конституенты'; - case HelpTopic.GRAPH_TERM: return 'Интерфейс графа термов'; - case HelpTopic.CST_STATUS: return 'Нотация отображения статуса конституенты'; - case HelpTopic.CST_CLASS: return 'Нотация отображения класса конституенты'; + case HelpTopic.UI_LIBRARY: return 'Интерфейс Библиотеки схем'; + case HelpTopic.UI_RSFORM: return 'Просмотр и редактирование концептуальной схемы'; + case HelpTopic.UI_RSFORM_CARD: return 'Интерфейс Карточки схемы'; + case HelpTopic.UI_RSFORM_LIST: return 'Интерфейс Списка конституент'; + case HelpTopic.UI_RSFORM_EDITOR: return 'Интерфейс редактирования конституенты'; + case HelpTopic.UI_GRAPH_TERM: return 'Интерфейс графа термов'; + case HelpTopic.UI_CST_STATUS: return 'Нотация отображения статуса конституенты'; + case HelpTopic.UI_CST_CLASS: return 'Нотация отображения класса конституенты'; case HelpTopic.CONCEPTUAL: return 'Основы концептуализации и концептуального мышления'; case HelpTopic.CC_SYSTEM: return 'Концептуальная схема как система понятий';