diff --git a/README.md b/README.md
index d80e5085..9ce3c227 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ This readme file is used mostly to document project dependencies
- axios
- clsx
+ - react-icons
- react-router-dom
- react-toastify
- react-loader-spinner
diff --git a/rsconcept/frontend/package-lock.json b/rsconcept/frontend/package-lock.json
index b2d53616..5f6f6df1 100644
--- a/rsconcept/frontend/package-lock.json
+++ b/rsconcept/frontend/package-lock.json
@@ -18,6 +18,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
+ "react-icons": "^4.12.0",
"react-intl": "^6.5.5",
"react-loader-spinner": "^5.4.5",
"react-pdf": "^7.6.0",
@@ -8389,6 +8390,14 @@
"react": ">=16.13.1"
}
},
+ "node_modules/react-icons": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz",
+ "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-intl": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.5.5.tgz",
diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json
index 5c6874c5..c04f5207 100644
--- a/rsconcept/frontend/package.json
+++ b/rsconcept/frontend/package.json
@@ -22,6 +22,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
+ "react-icons": "^4.12.0",
"react-intl": "^6.5.5",
"react-loader-spinner": "^5.4.5",
"react-pdf": "^7.6.0",
diff --git a/rsconcept/frontend/src/components/Common/ConceptSearch.tsx b/rsconcept/frontend/src/components/Common/ConceptSearch.tsx
index bb6a03ba..e205f99f 100644
--- a/rsconcept/frontend/src/components/Common/ConceptSearch.tsx
+++ b/rsconcept/frontend/src/components/Common/ConceptSearch.tsx
@@ -1,4 +1,5 @@
-import { MagnifyingGlassIcon } from '../Icons';
+import { BiSearchAlt2 } from 'react-icons/bi';
+
import Overlay from './Overlay';
import TextInput from './TextInput';
@@ -16,7 +17,7 @@ function ConceptSearch({ value, onChange, noBorder, dimensions }: ConceptSearchP
position='top-[-0.125rem] left-3 translate-y-1/2'
className='pointer-events-none clr-text-controls'
>
-
+
void
disabled?: boolean
- children: React.ReactNode
+
+ children?: React.ReactNode
}
-function DropdownButton({ tooltip, onClick, disabled, children }: DropdownButtonProps) {
+function DropdownButton({
+ text, icon, children,
+ tooltip, className,
+ disabled,
+ onClick
+}: DropdownButtonProps) {
return (
- {children}
+ {children ? children : null}
+ {!children && icon ? icon : null}
+ {!children && text ? {text} : null}
);
}
diff --git a/rsconcept/frontend/src/components/Common/FileInput.tsx b/rsconcept/frontend/src/components/Common/FileInput.tsx
index 1bb725c5..b4670175 100644
--- a/rsconcept/frontend/src/components/Common/FileInput.tsx
+++ b/rsconcept/frontend/src/components/Common/FileInput.tsx
@@ -2,8 +2,8 @@
import clsx from 'clsx';
import { useRef, useState } from 'react';
+import { BiUpload } from 'react-icons/bi';
-import { UploadIcon } from '../Icons';
import Button from './Button';
import Label from './Label';
@@ -55,7 +55,7 @@ function FileInput({
/>
}
+ icon={ }
onClick={handleUploadClick}
tooltip={tooltip}
/>
diff --git a/rsconcept/frontend/src/components/Common/Modal.tsx b/rsconcept/frontend/src/components/Common/Modal.tsx
index 9f510d32..5fa741b5 100644
--- a/rsconcept/frontend/src/components/Common/Modal.tsx
+++ b/rsconcept/frontend/src/components/Common/Modal.tsx
@@ -2,10 +2,10 @@
import clsx from 'clsx';
import { useRef } from 'react';
+import { BiX } from 'react-icons/bi';
import useEscapeKey from '@/hooks/useEscapeKey';
-import { CrossIcon } from '../Icons';
import Button from './Button';
import MiniButton from './MiniButton';
import Overlay from './Overlay';
@@ -62,7 +62,7 @@ function Modal({
}
+ icon={ }
onClick={handleCancel}
/>
diff --git a/rsconcept/frontend/src/components/Common/PageControls.tsx b/rsconcept/frontend/src/components/Common/PageControls.tsx
index 36c52a21..4a5f6e71 100644
--- a/rsconcept/frontend/src/components/Common/PageControls.tsx
+++ b/rsconcept/frontend/src/components/Common/PageControls.tsx
@@ -1,4 +1,4 @@
-import { GotoFirstIcon, GotoLastIcon, GotoNextIcon, GotoPrevIcon } from '@/components/Icons';
+import { BiChevronLeft, BiChevronRight, BiFirstPage, BiLastPage } from 'react-icons/bi';
interface PageControlsProps {
pageNumber: number
@@ -16,14 +16,14 @@ function PageControls({
onClick={() => setPageNumber(1)}
disabled={pageNumber < 2}
>
-
+
setPageNumber(prev => prev - 1)}
disabled={pageNumber < 2}
>
-
+
Страница {pageNumber} из {pageCount}
setPageNumber(prev => prev + 1)}
disabled={pageNumber >= pageCount}
>
-
+
setPageNumber(pageCount)}
disabled={pageNumber >= pageCount}
>
-
+
>);
}
diff --git a/rsconcept/frontend/src/components/DataTable/PaginationTools.tsx b/rsconcept/frontend/src/components/DataTable/PaginationTools.tsx
index 9b87752c..8490acb8 100644
--- a/rsconcept/frontend/src/components/DataTable/PaginationTools.tsx
+++ b/rsconcept/frontend/src/components/DataTable/PaginationTools.tsx
@@ -3,11 +3,10 @@
import { Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { useCallback } from 'react';
+import { BiChevronLeft, BiChevronRight, BiFirstPage, BiLastPage } from 'react-icons/bi';
import { prefixes } from '@/utils/constants';
-import { GotoFirstIcon, GotoLastIcon, GotoNextIcon, GotoPrevIcon } from '../Icons';
-
interface PaginationToolsProps {
table: Table
paginationOptions: number[]
@@ -47,14 +46,14 @@ function PaginationTools({ table, paginationOptions, onChangePaginationOp
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
-
+
table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
-
+
({ table, paginationOptions, onChangePaginationOp
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
-
+
table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
-
+
{
function SortingIcon({ column }: SortingIconProps) {
return (<>
{{
- desc: ,
- asc: ,
+ desc: ,
+ asc: ,
}[column.getIsSorted() as string] ??
-
+
}
>);
}
diff --git a/rsconcept/frontend/src/components/Help/ConstituentaTooltip.tsx b/rsconcept/frontend/src/components/Help/ConstituentaTooltip.tsx
index b97a70ee..d6a34d4d 100644
--- a/rsconcept/frontend/src/components/Help/ConstituentaTooltip.tsx
+++ b/rsconcept/frontend/src/components/Help/ConstituentaTooltip.tsx
@@ -9,7 +9,7 @@ interface ConstituentaTooltipProps {
function ConstituentaTooltip({ data, anchor }: ConstituentaTooltipProps) {
return (
-
diff --git a/rsconcept/frontend/src/components/Help/HelpButton.tsx b/rsconcept/frontend/src/components/Help/HelpButton.tsx
index 67ccf833..de2593e0 100644
--- a/rsconcept/frontend/src/components/Help/HelpButton.tsx
+++ b/rsconcept/frontend/src/components/Help/HelpButton.tsx
@@ -1,6 +1,7 @@
+import { BiInfoCircle } from 'react-icons/bi';
+
import ConceptTooltip from '@/components/Common/ConceptTooltip';
import TextURL from '@/components/Common/TextURL';
-import { HelpIcon } from '@/components/Icons';
import { HelpTopic } from '@/models/miscelanious';
import InfoTopic from './InfoTopic';
@@ -18,7 +19,7 @@ function HelpButton({ topic, offset, dimensions }: HelpButtonProps) {
id={`help-${topic}`}
className='p-1'
>
-
+
На текущем этапе происходит наполнение Библиотеки концептуальными схемами.
Поиск осуществлеяется с помощью инструментов в верхней части страницы.
-
+
Аттрибут отслеживаемая обозначает отслеживание схемы.
-
+
Аттрибут общедоступная делает схему видимой в разделе библиотека.
-
+
Аттрибут неизменная выделяет стандартные схемы.
);
diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx
index 2c02c074..e2a56ef0 100644
--- a/rsconcept/frontend/src/components/Icons.tsx
+++ b/rsconcept/frontend/src/components/Icons.tsx
@@ -2,24 +2,23 @@
interface IconSVGProps {
viewbox: string
- size?: number
- color?: string
+ size?: string
+ className?: string
props?: React.SVGProps
children: React.ReactNode
}
export interface IconProps {
- size?: number
- color?: string
+ size?: string
+ className?: string
}
-function IconSVG({ viewbox, size = 6, color, props, children }: IconSVGProps) {
- const width = `${size * 1 / 4}rem`;
+function IconSVG({ viewbox, size = '1.5rem', className, props, children }: IconSVGProps) {
return (
);
}
-export function MagnifyingGlassIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function BellIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function EyeIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function EyeOffIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function SubscribedIcon(props: IconProps) {
return (
@@ -85,22 +51,6 @@ export function ASTNetworkIcon(props: IconProps) {
);
}
-export function EditIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function SquaresIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function GroupIcon(props: IconProps) {
return (
@@ -109,30 +59,6 @@ export function GroupIcon(props: IconProps) {
);
}
-export function FrameIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function AsteriskIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function MenuIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function ShareIcon(props: IconProps) {
return (
@@ -141,14 +67,6 @@ export function ShareIcon(props: IconProps) {
);
}
-export function FilterIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function SortIcon(props: IconProps) {
return (
@@ -157,14 +75,6 @@ export function SortIcon(props: IconProps) {
);
}
-export function BookmarkIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function UserIcon(props: IconProps) {
return (
@@ -181,22 +91,6 @@ export function EducationIcon(props: IconProps) {
);
}
-export function DarkThemeIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function LightThemeIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function LibraryIcon(props: IconProps) {
return (
@@ -215,66 +109,6 @@ export function PlusIcon(props: IconProps) {
);
}
-export function SmallPlusIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function ArrowDropdownIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function UploadIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function DownloadIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function OwnerIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function ArrowUpIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function ArrowDownIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function ArrowLeftIcon(props: IconProps) {
return (
@@ -291,49 +125,6 @@ export function ArrowRightIcon(props: IconProps) {
);
}
-export function CloneIcon(props: IconProps) {
- return (
-
-
-
-
-
- );
-}
-
-export function DiamondIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function DumpBinIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function ArrowsRotateIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function ArrowsFocusIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function LetterAIcon(props: IconProps) {
return (
@@ -350,48 +141,6 @@ export function LetterALinesIcon(props: IconProps) {
);
}
-export function PlanetIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function SaveIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function HelpIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function GithubIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function UpdateIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
export function InDoorIcon(props: IconProps) {
return (
@@ -401,38 +150,6 @@ export function InDoorIcon(props: IconProps) {
);
}
-export function GotoLastIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function GotoFirstIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function GotoNextIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function GotoPrevIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
export function DescendingIcon(props: IconProps) {
return (
@@ -471,62 +188,4 @@ export function CheckboxNullIcon() {
);
-}
-
-export function ChevronUpIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function ChevronDoubleUpIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function ChevronDoubleDownIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function CheckIcon(props: IconProps) {
- return (
-
-
-
- );
-}
-
-export function CogIcon(props: IconProps) {
- return (
-
-
-
-
- );
-}
-
-export function CrossIcon(props: IconProps) {
- return (
-
-
-
-
-
- );
}
\ No newline at end of file
diff --git a/rsconcept/frontend/src/components/Navigation/Logo.tsx b/rsconcept/frontend/src/components/Navigation/Logo.tsx
index 2c8dd019..0d9a913c 100644
--- a/rsconcept/frontend/src/components/Navigation/Logo.tsx
+++ b/rsconcept/frontend/src/components/Navigation/Logo.tsx
@@ -4,7 +4,7 @@ function Logo() {
const { darkMode } = useConceptTheme();
return (
);
}
diff --git a/rsconcept/frontend/src/components/Navigation/Navigation.tsx b/rsconcept/frontend/src/components/Navigation/Navigation.tsx
index 2a0024eb..0737b1cc 100644
--- a/rsconcept/frontend/src/components/Navigation/Navigation.tsx
+++ b/rsconcept/frontend/src/components/Navigation/Navigation.tsx
@@ -29,7 +29,7 @@ function Navigation () {
{!noNavigation ?
}
- onClick={toggleDarkMode}
- />);
- } else {
- return (
- }
- onClick={toggleDarkMode}
- />);
- }
-}
-
-export default ThemeSwitcher;
\ No newline at end of file
diff --git a/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx b/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx
index 9057e159..098a8015 100644
--- a/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx
+++ b/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx
@@ -27,20 +27,20 @@ function UserDropdown({ hideDropdown }: UserDropdownProps) {
return (
- {user?.username}
-
+ />
- {darkMode ? 'Светлая тема' : 'Темная тема'}
-
-
- Выйти...
-
+ />
+
);
}
diff --git a/rsconcept/frontend/src/components/Shared/InfoConstituenta.tsx b/rsconcept/frontend/src/components/Shared/InfoConstituenta.tsx
index 56969449..4acaae16 100644
--- a/rsconcept/frontend/src/components/Shared/InfoConstituenta.tsx
+++ b/rsconcept/frontend/src/components/Shared/InfoConstituenta.tsx
@@ -9,7 +9,7 @@ extends React.HTMLAttributes {
function InfoConstituenta({ data, ...restProps }: InfoConstituentaProps) {
return (
-
Конституента {data.alias}
+
Конституента {data.alias}
Типизация:
{labelCstTypification(data)}
diff --git a/rsconcept/frontend/src/context/AccessModeContext.tsx b/rsconcept/frontend/src/context/AccessModeContext.tsx
new file mode 100644
index 00000000..54063234
--- /dev/null
+++ b/rsconcept/frontend/src/context/AccessModeContext.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import { createContext, useContext, useState } from 'react';
+
+import { UserAccessMode } from '@/models/miscelanious';
+
+interface IAccessModeContext {
+ mode: UserAccessMode
+ setMode: React.Dispatch>
+}
+
+const AccessContext = createContext(null);
+export const useAccessMode = () => {
+ const context = useContext(AccessContext);
+ if (!context) {
+ throw new Error(
+ 'useAccessMode has to be used within '
+ );
+ }
+ return context;
+}
+
+interface AccessModeStateProps {
+ children: React.ReactNode
+}
+
+export const AccessModeState = ({ children }: AccessModeStateProps) => {
+ const [mode, setMode] = useState(UserAccessMode.READER);
+
+ return (
+
+ {children}
+ );
+};
\ No newline at end of file
diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx
index ce38ad49..839dff60 100644
--- a/rsconcept/frontend/src/context/RSFormContext.tsx
+++ b/rsconcept/frontend/src/context/RSFormContext.tsx
@@ -30,15 +30,9 @@ interface IRSFormContext {
loading: boolean
processing: boolean
- isMutable: boolean
isOwned: boolean
isClaimable: boolean
- isTracking: boolean
-
- adminMode: boolean
- toggleAdminMode: () => void
- readerMode: boolean
- toggleReaderMode: () => void
+ isSubscribed: boolean
update: (data: ILibraryUpdateData, callback?: DataCallback) => void
claim: (callback?: DataCallback) => void
@@ -76,8 +70,6 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID });
const [processing, setProcessing] = useState(false);
- const [adminMode, setAdminMode] = useState(false);
- const [readerMode, setReaderMode] = useState(false);
const [toggleTracking, setToggleTracking] = useState(false);
const isOwned = useMemo(
@@ -90,15 +82,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
return (user?.id !== schema?.owner && schema?.is_common && !schema?.is_canonical) ?? false;
}, [user, schema?.owner, schema?.is_common, schema?.is_canonical]);
- const isMutable = useMemo(
- () => {
- return (
- !loading && !processing && !readerMode &&
- ((isOwned || (adminMode && user?.is_staff)) ?? false)
- );
- }, [user?.is_staff, readerMode, adminMode, isOwned, loading, processing]);
-
- const isTracking = useMemo(
+ const isSubscribed = useMemo(
() => {
if (!user || !schema || !user.id) {
return false;
@@ -324,12 +308,10 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
setAdminMode(prev => !prev),
- toggleReaderMode: () => setReaderMode(prev => !prev)
}}>
{ children }
);
diff --git a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx
index 54ca0431..a753a574 100644
--- a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx
+++ b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/ArgumentsTab.tsx
@@ -3,10 +3,10 @@
import { createColumnHelper } from '@tanstack/react-table';
import clsx from 'clsx';
import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
+import { BiCheck, BiRefresh, BiX } from 'react-icons/bi';
import MiniButton from '@/components/Common/MiniButton';
import DataTable, { IConditionalStyle } from '@/components/DataTable';
-import { ArrowsRotateIcon, CheckIcon, CrossIcon } from '@/components/Icons';
import RSInput from '@/components/RSInput';
import ConstituentaPicker from '@/components/Shared/ConstituentaPicker';
import { useConceptTheme } from '@/context/ThemeContext';
@@ -133,7 +133,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
{props.row.original.value ?
}
+ icon={ }
noHover
onClick={() => handleClearArgument(props.row.original)}
/> : null}
@@ -192,7 +192,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
}
+ icon={
}
disabled={!argumentValue || !selectedArgument}
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
/>
@@ -200,12 +200,12 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
tooltip='Откатить значение'
disabled={!isModified}
onClick={handleReset}
- icon={
}
+ icon={
}
/>
}
+ icon={
}
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
/>
diff --git a/rsconcept/frontend/src/dialogs/DlgEditWordForms/DlgEditWordForms.tsx b/rsconcept/frontend/src/dialogs/DlgEditWordForms/DlgEditWordForms.tsx
index f4d1a2ba..62d247a2 100644
--- a/rsconcept/frontend/src/dialogs/DlgEditWordForms/DlgEditWordForms.tsx
+++ b/rsconcept/frontend/src/dialogs/DlgEditWordForms/DlgEditWordForms.tsx
@@ -2,6 +2,7 @@
import clsx from 'clsx';
import { useLayoutEffect, useState } from 'react';
+import { BiCheck, BiChevronsDown } from 'react-icons/bi';
import Label from '@/components/Common/Label';
import MiniButton from '@/components/Common/MiniButton';
@@ -9,7 +10,7 @@ import Modal from '@/components/Common/Modal';
import Overlay from '@/components/Common/Overlay';
import TextArea from '@/components/Common/TextArea';
import HelpButton from '@/components/Help/HelpButton';
-import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon } from '@/components/Icons';
+import { ArrowLeftIcon, ArrowRightIcon } from '@/components/Icons';
import SelectGrammeme from '@/components/Shared/SelectGrammeme';
import useConceptText from '@/hooks/useConceptText';
import { Grammeme, ITextRequest, IWordForm, IWordFormPlain } from '@/models/language';
@@ -152,15 +153,15 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
}
+ icon={
}
disabled={textProcessor.loading || inputGrams.length == 0}
onClick={handleInflect}
/>
}
disabled={textProcessor.loading || !inputText}
onClick={handleParse}
@@ -179,18 +180,16 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
}
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
onClick={handleAddForm}
/>
}
disabled={textProcessor.loading || !inputText}
onClick={handleGenerateLexeme}
diff --git a/rsconcept/frontend/src/dialogs/DlgEditWordForms/WordFormsTable.tsx b/rsconcept/frontend/src/dialogs/DlgEditWordForms/WordFormsTable.tsx
index bab871b8..242621bc 100644
--- a/rsconcept/frontend/src/dialogs/DlgEditWordForms/WordFormsTable.tsx
+++ b/rsconcept/frontend/src/dialogs/DlgEditWordForms/WordFormsTable.tsx
@@ -1,11 +1,11 @@
'use client';
import { useCallback, useMemo } from 'react';
+import { BiX } from 'react-icons/bi';
import MiniButton from '@/components/Common/MiniButton';
import Overlay from '@/components/Common/Overlay';
import DataTable, { createColumnHelper } from '@/components/DataTable';
-import { CrossIcon } from '@/components/Icons';
import WordFormBadge from '@/components/Shared/WordFormBadge';
import { IWordForm } from '@/models/language';
@@ -68,7 +68,7 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
cell: props =>
}
+ icon={ }
onClick={() => handleDeleteRow(props.row.index)}
/>
})
@@ -79,7 +79,7 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
}
+ icon={ }
disabled={loading || forms.length === 0}
onClick={handleResetAll}
/>
diff --git a/rsconcept/frontend/src/index.css b/rsconcept/frontend/src/index.css
index 48ac80e9..13ecfe3d 100644
--- a/rsconcept/frontend/src/index.css
+++ b/rsconcept/frontend/src/index.css
@@ -145,6 +145,10 @@
@apply text-lg font-semibold text-center
}
+ h2 {
+ @apply font-semibold text-center
+ }
+
b {
@apply font-semibold
}
diff --git a/rsconcept/frontend/src/models/miscelanious.ts b/rsconcept/frontend/src/models/miscelanious.ts
index fb01f029..eb7606b2 100644
--- a/rsconcept/frontend/src/models/miscelanious.ts
+++ b/rsconcept/frontend/src/models/miscelanious.ts
@@ -2,6 +2,15 @@
* Module: Miscellanious frontend model types. Future tagets for refactoring aimed at extracting modules.
*/
+/**
+ * Represents user access mode.
+*/
+export enum UserAccessMode {
+ READER = 0,
+ OWNER,
+ ADMIN
+}
+
/**
* Represents graph dependency mode.
*/
diff --git a/rsconcept/frontend/src/pages/CreateRSFormPage.tsx b/rsconcept/frontend/src/pages/CreateRSFormPage.tsx
index b96387d9..06506757 100644
--- a/rsconcept/frontend/src/pages/CreateRSFormPage.tsx
+++ b/rsconcept/frontend/src/pages/CreateRSFormPage.tsx
@@ -2,6 +2,7 @@
import clsx from 'clsx';
import { useEffect, useRef, useState } from 'react';
+import { BiDownload } from 'react-icons/bi';
import { toast } from 'react-toastify';
import Button from '@/components/Common/Button';
@@ -12,7 +13,6 @@ import Overlay from '@/components/Common/Overlay';
import SubmitButton from '@/components/Common/SubmitButton';
import TextArea from '@/components/Common/TextArea';
import TextInput from '@/components/Common/TextInput';
-import { DownloadIcon } from '@/components/Icons';
import InfoError from '@/components/InfoError';
import RequireAuth from '@/components/RequireAuth';
import { useLibrary } from '@/context/LibraryContext';
@@ -95,7 +95,7 @@ function CreateRSFormPage() {
/>
}
+ icon={ }
onClick={() => inputRef.current?.click()}
/>
diff --git a/rsconcept/frontend/src/pages/LibraryPage/ItemIcons.tsx b/rsconcept/frontend/src/pages/LibraryPage/ItemIcons.tsx
index fbaaf4b8..ebf11c37 100644
--- a/rsconcept/frontend/src/pages/LibraryPage/ItemIcons.tsx
+++ b/rsconcept/frontend/src/pages/LibraryPage/ItemIcons.tsx
@@ -20,15 +20,15 @@ function ItemIcons({ user, item }: ItemIconsProps) {
>
{(user && user.subscriptions.includes(item.id)) ?
-
+
: null}
{item.is_common ?
-
+
: null}
{item.is_canonical ?
-
+
: null}
);
}
diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx
index e4a8799a..8b670aef 100644
--- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx
+++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx
@@ -1,11 +1,11 @@
'use client';
import { useCallback } from 'react';
+import { BiFilterAlt } from 'react-icons/bi';
import Dropdown from '@/components/Common/Dropdown';
import DropdownCheckbox from '@/components/Common/DropdownCheckbox';
import SelectorButton from '@/components/Common/SelectorButton';
-import { FilterIcon } from '@/components/Icons';
import { useAuth } from '@/context/AuthContext';
import useDropdown from '@/hooks/useDropdown';
import { LibraryFilterStrategy } from '@/models/miscelanious';
@@ -44,7 +44,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
}
+ icon={ }
text={labelLibraryFilter(value)}
onClick={strategyMenu.toggle}
/>
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx
index 92ec7c2f..5a590975 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ConstituentaToolbar.tsx
@@ -1,13 +1,12 @@
'use client';
import { useMemo } from 'react';
+import { BiDiamond, BiDuplicate, BiPlusCircle, BiReset, BiTrash } from 'react-icons/bi';
+import { FiSave } from "react-icons/fi";
import MiniButton from '@/components/Common/MiniButton';
import Overlay from '@/components/Common/Overlay';
import HelpButton from '@/components/Help/HelpButton';
-import {
- ArrowsRotateIcon, CloneIcon, DiamondIcon, DumpBinIcon, SaveIcon, SmallPlusIcon
-} from '@/components/Icons';
import { HelpTopic } from '@/models/miscelanious';
interface ConstituentaToolbarProps {
@@ -34,30 +33,30 @@ function ConstituentaToolbar({
}
+ icon={ }
onClick={onSubmit}
/>
}
+ icon={ }
/>
}
+ icon={ }
/>
}
+ icon={ }
/>
}
+ icon={}
disabled={!isMutable}
onClick={onTemplates}
/>
@@ -65,7 +64,7 @@ function ConstituentaToolbar({
tooltip='Удалить редактируемую конституенту'
disabled={!isMutable}
onClick={onDelete}
- icon={ }
+ icon={ }
/>
);
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx
index 41bd4b2c..6e5f8370 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx
@@ -18,6 +18,8 @@ const UNFOLDED_HEIGHT = '59.1rem';
const SIDELIST_HIDE_THRESHOLD = 1100; // px
interface EditorConstituentaProps {
+ isMutable: boolean
+
activeID?: number
activeCst?: IConstituenta | undefined
isModified: boolean
@@ -32,15 +34,15 @@ interface EditorConstituentaProps {
}
function EditorConstituenta({
- isModified, setIsModified, activeID, activeCst, onEditTerm,
+ isMutable, isModified, setIsModified, activeID, activeCst, onEditTerm,
onCreateCst, onRenameCst, onOpenEdit, onDeleteCst, onTemplates
}: EditorConstituentaProps) {
const windowSize = useWindowSize();
- const { schema, isMutable } = useRSForm();
+ const { schema } = useRSForm();
const [toggleReset, setToggleReset] = useState(false);
- const readyForEdit = useMemo(() => (!!activeCst && isMutable), [activeCst, isMutable]);
+ const disabled = useMemo(() => (!activeCst || !isMutable), [activeCst, isMutable]);
function handleDelete() {
if (!schema || !activeID) {
@@ -84,7 +86,7 @@ function EditorConstituenta({
}
function handleInput(event: React.KeyboardEvent) {
- if (!isMutable) {
+ if (disabled) {
return;
}
if (event.ctrlKey && event.code === 'KeyS') {
@@ -120,7 +122,7 @@ function EditorConstituenta({
return (<>
-
(!!constituenta && isMutable), [constituenta, isMutable]);
+ const { schema, cstUpdate, processing } = useRSForm();
const [alias, setAlias] = useState('');
const [term, setTerm] = useState('');
@@ -106,10 +108,10 @@ function FormConstituenta({
}
+ icon={ }
/>
Имя
@@ -117,9 +119,9 @@ function FormConstituenta({
}
+ icon={ }
/>
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx
index 3d1c030d..7dc52b90 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx
@@ -2,11 +2,11 @@
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
+import { PiGraphLight } from "react-icons/pi";
import { toast } from 'react-toastify';
import MiniButton from '@/components/Common/MiniButton';
import Overlay from '@/components/Common/Overlay';
-import { ASTNetworkIcon } from '@/components/Icons';
import RSInput from '@/components/RSInput';
import { RSTextWrapper } from '@/components/RSInput/textEditing';
import { useRSForm } from '@/context/RSFormContext';
@@ -131,11 +131,11 @@ function EditorRSExpression({
syntaxTree={syntaxTree}
hideWindow={() => setShowAST(false)}
/> : null}
-
+
}
+ icon={ }
/>
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/EditorRSForm.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/EditorRSForm.tsx
index 12ae0f9f..8bf63ee5 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/EditorRSForm.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/EditorRSForm.tsx
@@ -13,16 +13,23 @@ import RSFormStats from './RSFormStats';
import RSFormToolbar from './RSFormToolbar';
interface EditorRSFormProps {
+ isModified: boolean
+ isMutable: boolean
+
+ setIsModified: Dispatch>
onDestroy: () => void
onClaim: () => void
onShare: () => void
onDownload: () => void
- isModified: boolean
- setIsModified: Dispatch>
+ onToggleSubscribe: () => void
}
-function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified, onDownload }: EditorRSFormProps) {
- const { schema, isMutable, isClaimable } = useRSForm();
+function EditorRSForm({
+ isModified, isMutable,
+ onDestroy, onClaim, onShare, setIsModified,
+ onDownload, onToggleSubscribe
+}: EditorRSFormProps) {
+ const { schema, isClaimable, isSubscribed, processing } = useRSForm();
const { user } = useAuth();
function initiateSubmit() {
@@ -45,6 +52,8 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
-
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/FormRSForm.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/FormRSForm.tsx
index d38b5320..eee34583 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/FormRSForm.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/FormRSForm.tsx
@@ -1,13 +1,14 @@
'use client';
import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
+import { FiSave } from 'react-icons/fi';
import { toast } from 'react-toastify';
import Checkbox from '@/components/Common/Checkbox';
import SubmitButton from '@/components/Common/SubmitButton';
import TextArea from '@/components/Common/TextArea';
import TextInput from '@/components/Common/TextInput';
-import { SaveIcon } from '@/components/Icons';
+import { useAuth } from '@/context/AuthContext';
import { useRSForm } from '@/context/RSFormContext';
import { LibraryItemType } from '@/models/library';
import { IRSFormCreateData } from '@/models/rsform';
@@ -15,17 +16,16 @@ import { limits, patterns } from '@/utils/constants';
interface FormRSFormProps {
id?: string
+ disabled: boolean
isModified: boolean
setIsModified: Dispatch
>
}
function FormRSForm({
- id, isModified, setIsModified,
+ id, disabled, isModified, setIsModified,
}: FormRSFormProps) {
- const {
- schema, update, adminMode: adminMode,
- isMutable: isMutable, processing
- } = useRSForm();
+ const { schema, update, processing } = useRSForm();
+ const { user } = useAuth();
const [title, setTitle] = useState('');
const [alias, setAlias] = useState('');
@@ -85,7 +85,7 @@ function FormRSForm({
setTitle(event.target.value)}
/>
setAlias(event.target.value)}
/>
setComment(event.target.value)}
/>
@@ -108,7 +108,7 @@ function FormRSForm({
label='Общедоступная схема'
tooltip='Общедоступные схемы видны всем пользователям и могут быть изменены'
dimensions='w-fit'
- disabled={!isMutable}
+ disabled={disabled}
value={common}
setValue={value => setCommon(value)}
/>
@@ -116,7 +116,7 @@ function FormRSForm({
label='Неизменная схема'
tooltip='Только администраторы могут присваивать схемам неизменный статус'
dimensions='w-fit'
- disabled={!isMutable || !adminMode}
+ disabled={disabled || !user?.is_staff}
value={canonical}
setValue={value => setCanonical(value)}
/>
@@ -125,8 +125,8 @@ function FormRSForm({
}
+ disabled={!isModified || disabled}
+ icon={ }
dimensions='my-2 w-fit'
/>
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/RSFormToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/RSFormToolbar.tsx
index 180baf3c..cdcbf65c 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/RSFormToolbar.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSForm/RSFormToolbar.tsx
@@ -1,28 +1,35 @@
'use client';
import { useMemo } from 'react';
+import { BiDownload, BiTrash } from 'react-icons/bi';
+import { FiSave } from 'react-icons/fi';
+import { LuCrown } from 'react-icons/lu';
import MiniButton from '@/components/Common/MiniButton';
import Overlay from '@/components/Common/Overlay';
import HelpButton from '@/components/Help/HelpButton';
-import { DownloadIcon, DumpBinIcon, OwnerIcon, SaveIcon, ShareIcon } from '@/components/Icons';
+import { NotSubscribedIcon, ShareIcon, SubscribedIcon } from '@/components/Icons';
import { HelpTopic } from '@/models/miscelanious';
interface RSFormToolbarProps {
isMutable: boolean
+ isSubscribed: boolean
modified: boolean
claimable: boolean
anonymous: boolean
+ processing: boolean
onSubmit: () => void
onShare: () => void
onDownload: () => void
onClaim: () => void
onDestroy: () => void
+ onToggleSubscribe: () => void
}
function RSFormToolbar({
isMutable, modified, claimable, anonymous,
+ isSubscribed, onToggleSubscribe, processing,
onSubmit, onShare, onDownload,
onClaim, onDestroy
}: RSFormToolbarProps) {
@@ -32,30 +39,41 @@ function RSFormToolbar({
}
+ icon={ }
onClick={onSubmit}
/>
}
+ icon={ }
onClick={onShare}
/>
}
+ icon={ }
onClick={onDownload}
/>
+
+ :
+ }
+ dimensions='h-full w-fit pr-2'
+ style={{outlineColor: 'transparent'}}
+ onClick={onToggleSubscribe}
+ />
}
- disabled={!claimable || anonymous}
+ icon={ }
+ disabled={!claimable || anonymous || processing}
onClick={onClaim}
/>
}
+ icon={ }
/>
);
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx
index c5d978d8..d9c84078 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/EditorRSList.tsx
@@ -1,7 +1,6 @@
'use client';
import { useLayoutEffect, useState } from 'react';
-import { toast } from 'react-toastify';
import { type RowSelectionState } from '@/components/DataTable';
import SelectedCounter from '@/components/Shared/SelectedCounter';
@@ -12,14 +11,20 @@ import RSListToolbar from './RSListToolbar';
import RSTable from './RSTable';
interface EditorRSListProps {
+ isMutable: boolean
onOpenEdit: (cstID: number) => void
onTemplates: (insertAfter?: number) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
+ onReindex: () => void
}
-function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
- const { schema, isMutable, cstMoveTo, resetAliases } = useRSForm();
+function EditorRSList({
+ isMutable,
+ onOpenEdit, onCreateCst,
+ onDeleteCst, onTemplates, onReindex
+}: EditorRSListProps) {
+ const { schema, cstMoveTo } = useRSForm();
const [selected, setSelected] = useState([]);
const [rowSelection, setRowSelection] = useState({});
@@ -107,11 +112,6 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
});
}
- // Generate new names for all constituents
- function handleReindex() {
- resetAliases(() => toast.success('Имена конституент обновлены'));
- }
-
function handleCreateCst(type?: CstType) {
if (!schema) {
return;
@@ -186,7 +186,7 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
switch (code) {
case 'Backquote': handleCreateCst(); return true;
case 'KeyE': onTemplates(); return true;
- case 'KeyR': handleReindex(); return true;
+ case 'KeyR': onReindex(); return true;
case 'Digit1': handleCreateCst(CstType.BASE); return true;
case 'Digit2': handleCreateCst(CstType.STRUCTURED); return true;
@@ -214,7 +214,7 @@ function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edi
onCreate={handleCreateCst}
onDelete={handleDelete}
onTemplates={() => onTemplates(selected.length !== 0 ? selected[selected.length-1] : undefined)}
- onReindex={handleReindex}
+ onReindex={onReindex}
/>
}
+ icon={ }
disabled={!isMutable || nothingSelected}
onClick={onMoveUp}
/>
}
+ icon={ }
disabled={!isMutable || nothingSelected}
onClick={onMoveDown}
/>
}
+ icon={ }
disabled={!isMutable || selectedCount !== 1}
onClick={onClone}
/>
}
+ icon={ }
disabled={!isMutable}
onClick={() => onCreate()}
/>
@@ -66,42 +66,39 @@ function RSListToolbar({
}
+ icon={ }
disabled={!isMutable}
onClick={insertMenu.toggle}
/>
{insertMenu.isActive ?
{(Object.values(CstType)).map(
- (typeStr) => {
- const type = typeStr as CstType;
- return (
+ (typeStr) =>
onCreate(type)}
- tooltip={getCstTypeShortcut(type)}
- >
- {`${getCstTypePrefix(type)}1 — ${labelCstType(type)}`}
- );
- })}
+ text={`${getCstTypePrefix(typeStr as CstType)}1 — ${labelCstType(typeStr as CstType)}`}
+ onClick={() => onCreate(typeStr as CstType)}
+ tooltip={getCstTypeShortcut(typeStr as CstType)}
+ />
+ )}
: null}
}
+ icon={ }
disabled={!isMutable}
onClick={onTemplates}
/>
}
+ tooltip='Сброс имён: присвоить порядковые имена [Alt + R]'
+ icon={ }
disabled={!isMutable}
onClick={onReindex}
/>
}
+ icon={ }
disabled={!isMutable || nothingSelected}
onClick={onDelete}
/>
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx
index 2ff9e7c6..9d7a8a7f 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSTable.tsx
@@ -1,5 +1,6 @@
'use client';
+import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import DataTable, { createColumnHelper,RowSelectionState,VisibilityState } from '@/components/DataTable';
@@ -71,7 +72,6 @@ function RSTable({
theme={colors}
value={props.row.original}
prefixID={prefixes.cst_list}
- shortTooltip
/>
}),
columnHelper.accessor(cst => labelCstTypification(cst), {
@@ -125,7 +125,15 @@ function RSTable({
}, [noNavigation]);
return (
-
+
void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
}
-function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
- const { schema, isMutable } = useRSForm();
+function EditorTermGraph({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
+ const { schema } = useRSForm();
const { colors } = useConceptTheme();
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx
index 72afe367..65d9b92a 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx
@@ -1,9 +1,11 @@
'use client';
+import { BiCollapse, BiFilterAlt, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
+
import MiniButton from '@/components/Common/MiniButton';
import Overlay from '@/components/Common/Overlay';
import HelpButton from '@/components/Help/HelpButton';
-import { ArrowsFocusIcon, DumpBinIcon, FilterIcon, LetterAIcon, LetterALinesIcon, PlanetIcon, SmallPlusIcon } from '@/components/Icons';
+import { LetterAIcon, LetterALinesIcon } from '@/components/Icons';
import { HelpTopic } from '@/models/miscelanious';
interface GraphToolbarProps {
@@ -34,37 +36,37 @@ function GraphToolbar({
}
+ icon={ }
onClick={showParamsDialog}
/>
- :
+ ?
+ :
}
onClick={toggleNoText}
/>
}
+ icon={ }
disabled={!isMutable}
onClick={onCreate}
/>
}
+ icon={ }
disabled={!isMutable || nothingSelected}
onClick={onDelete}
/>
}
+ icon={ }
tooltip='Восстановить камеру'
onClick={onResetViewpoint}
/>
}
+ icon={ }
tooltip='Анимация вращения'
disabled={!is3D}
onClick={toggleOrbit}
diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSFormPage.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSFormPage.tsx
index 669a30e3..55a1371c 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/RSFormPage.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/RSFormPage.tsx
@@ -2,6 +2,7 @@
import { useParams } from 'react-router-dom';
+import { AccessModeState } from '@/context/AccessModeContext';
import { RSFormState } from '@/context/RSFormContext';
import RSTabs from './RSTabs';
@@ -9,9 +10,11 @@ import RSTabs from './RSTabs';
function RSFormPage() {
const params = useParams();
return (
+
- );
+
+ );
}
export default RSFormPage;
\ No newline at end of file
diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx
index a3225d54..a91ee563 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/RSTabs.tsx
@@ -11,6 +11,8 @@ import { ConceptLoader } from '@/components/Common/ConceptLoader';
import ConceptTab from '@/components/Common/ConceptTab';
import TextURL from '@/components/Common/TextURL';
import InfoError, { ErrorData } from '@/components/InfoError';
+import { useAccessMode } from '@/context/AccessModeContext';
+import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import { useBlockNavigation, useConceptNavigation } from '@/context/NagivationContext';
import { useRSForm } from '@/context/RSFormContext';
@@ -23,6 +25,7 @@ import DlgEditWordForms from '@/dialogs/DlgEditWordForms';
import DlgRenameCst from '@/dialogs/DlgRenameCst';
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
import useQueryStrings from '@/hooks/useQueryStrings';
+import { UserAccessMode } from '@/models/miscelanious';
import { IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData, TermForm } from '@/models/rsform';
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '@/utils/constants';
import { createAliasFor } from '@/utils/misc';
@@ -56,20 +59,30 @@ function ProcessError({error}: {error: ErrorData}): React.ReactElement {
function RSTabs() {
const router = useConceptNavigation();
const query = useQueryStrings();
- const tabQuery = (Number(query.get('tab')) ?? RSTabID.CARD) as RSTabID;
+ const activeTab = (Number(query.get('tab')) ?? RSTabID.CARD) as RSTabID;
const cstQuery = query.get('active');
- const {
- error, schema, loading, claim, download, isTracking,
- cstCreate, cstDelete, cstRename, subscribe, unsubscribe, cstUpdate
+ const {
+ error, schema, loading, processing, isOwned,
+ claim, download, isSubscribed,
+ cstCreate, cstDelete, cstRename, subscribe, unsubscribe, cstUpdate, resetAliases
} = useRSForm();
const { destroyItem } = useLibrary();
const { setNoFooter, noNavigation } = useConceptTheme();
+ const { user } = useAuth();
+ const { mode, setMode } = useAccessMode();
const [isModified, setIsModified] = useState(false);
useBlockNavigation(isModified);
- const [activeTab, setActiveTab] = useState(RSTabID.CARD);
+ const isMutable = useMemo(
+ () => {
+ return (
+ !loading && !processing && mode !== UserAccessMode.READER &&
+ ((isOwned || (mode === UserAccessMode.ADMIN && user?.is_staff)) ?? false)
+ );
+ }, [user?.is_staff, mode, isOwned, loading, processing]);
+
const [activeID, setActiveID] = useState(undefined);
const activeCst = useMemo(
() => schema?.items?.find(cst => cst.id === activeID)
@@ -111,12 +124,22 @@ function RSTabs() {
}, [schema, schema?.title]);
useLayoutEffect(() => {
- setActiveTab(tabQuery);
- setNoFooter(tabQuery === RSTabID.CST_EDIT || tabQuery === RSTabID.CST_LIST);
+ setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST);
setActiveID(Number(cstQuery) ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined));
setIsModified(false);
return () => setNoFooter(false);
- }, [tabQuery, cstQuery, setActiveTab, setActiveID, schema, setNoFooter, setIsModified]);
+ }, [activeTab, cstQuery, setActiveID, schema, setNoFooter, setIsModified]);
+
+ useLayoutEffect(
+ () => setMode((prev) => {
+ if (prev === UserAccessMode.ADMIN) {
+ return prev;
+ } else if(isOwned) {
+ return UserAccessMode.OWNER;
+ } else {
+ return UserAccessMode.READER;
+ }
+ }), [schema, setMode, isOwned]);
function onSelectTab(index: number) {
navigateTab(index, activeID);
@@ -186,6 +209,11 @@ function RSTabs() {
setShowRenameCst(true);
}, []);
+ const onReindex = useCallback(
+ () => resetAliases(
+ () => toast.success('Имена конституент обновлены')
+ ), [resetAliases]);
+
const handleDeleteCst = useCallback(
(deleted: number[]) => {
if (!schema) {
@@ -290,12 +318,12 @@ function RSTabs() {
const handleToggleSubscribe = useCallback(
() => {
- if (isTracking) {
+ if (isSubscribed) {
unsubscribe(() => toast.success('Отслеживание отключено'));
} else {
subscribe(() => toast.success('Отслеживание включено'));
}
- }, [isTracking, subscribe, unsubscribe]);
+ }, [isSubscribed, subscribe, unsubscribe]);
const promptShowEditTerm = useCallback(
() => {
@@ -383,12 +411,13 @@ function RSTabs() {
'flex justify-stretch',
'border-b-2 border-x-2 divide-x-2'
)}>
- setShowUpload(true)}
/>
@@ -418,8 +447,10 @@ function RSTabs() {
>
- void
showCloneDialog: () => void
@@ -21,21 +26,25 @@ interface RSTabsMenuProps {
onClaim: () => void
onShare: () => void
onDownload: () => void
- onToggleSubscribe: () => void
+ onReindex: () => void
+ onTemplates: () => void
}
function RSTabsMenu({
+ isMutable,
showUploadDialog, showCloneDialog,
- onDestroy, onShare, onDownload, onClaim, onToggleSubscribe
+ onDestroy, onShare, onDownload,
+ onClaim, onReindex, onTemplates
}: RSTabsMenuProps) {
const router = useConceptNavigation();
const { user } = useAuth();
- const {
- isOwned, isMutable, isTracking, readerMode, isClaimable, adminMode,
- toggleAdminMode, toggleReaderMode, processing
- } = useRSForm();
+ const { isOwned, isClaimable } = useRSForm();
+
+ const { mode, setMode } = useAccessMode();
+
const schemaMenu = useDropdown();
const editMenu = useDropdown();
+ const accessMenu = useDropdown();
function handleClaimOwner() {
editMenu.hide();
@@ -67,6 +76,21 @@ function RSTabsMenu({
onShare();
}
+ function handleReindex() {
+ editMenu.hide();
+ onReindex();
+ }
+
+ function handleTemplates() {
+ editMenu.hide();
+ onTemplates();
+ }
+
+ function handleChangeMode(newMode: UserAccessMode) {
+ accessMenu.hide();
+ setMode(newMode);
+ }
+
function handleCreateNew() {
router.push('/rsform-create');
}
@@ -75,105 +99,111 @@ function RSTabsMenu({
}
+ tooltip='Меню'
+ icon={ }
dimensions='h-full w-fit pl-2'
style={{outlineColor: 'transparent'}}
onClick={schemaMenu.toggle}
/>
{schemaMenu.isActive ?
-
-
-
-
-
-
-
-
-
- Выгрузить в Экстеор
-
-
-
-
-
- Загрузить из Экстеора
-
-
-
-
-
- Удалить схему
-
-
-
-
-
- Создать новую схему
-
-
+ }
+ onClick={(!isOwned && user && isClaimable) ? handleClaimOwner : undefined}
+ />
+ }
+ onClick={handleShare}
+ />
+ }
+ onClick={handleClone}
+ />
+ }
+ onClick={handleDownload}
+ />
+ }
+ onClick={handleUpload}
+ />
+ }
+ onClick={handleDelete}
+ />
+ }
+ onClick={handleCreateNew}
+ />
: null}
+
}
+ icon={
}
onClick={editMenu.toggle}
/>
{editMenu.isActive ?
-
-
-
-
-
-
- {isOwned ? Вы — владелец : null}
- {!isOwned ? Стать владельцем : null}
-
-
-
- {(isOwned || user?.is_staff) ?
- : null}
- {user?.is_staff ?
- : null}
+ }
+ onClick={handleReindex}
+ />
+ }
+ onClick={handleTemplates}
+ />
: null}
-
+
+
- :
- }
+ tooltip={`режим ${labelAccessMode(mode)}`}
dimensions='h-full w-fit pr-2'
style={{outlineColor: 'transparent'}}
- onClick={onToggleSubscribe}
+ icon={
+ mode === UserAccessMode.ADMIN ?
+ : mode === UserAccessMode.OWNER ?
+ :
+ }
+ onClick={accessMenu.toggle}
+ />
+ {accessMenu.isActive ?
+
+ }
+ onClick={() => handleChangeMode(UserAccessMode.READER)}
/>
+ }
+ onClick={() => handleChangeMode(UserAccessMode.OWNER)}
+ />
+ }
+ onClick={() => handleChangeMode(UserAccessMode.ADMIN)}
+ />
+ : null}
);
}
diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx
index c715fd7a..0bd09c31 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx
@@ -1,12 +1,12 @@
'use client';
import { useCallback, useLayoutEffect } from 'react';
+import { BiCog, BiFilterAlt } from 'react-icons/bi';
import ConceptSearch from '@/components/Common/ConceptSearch';
import Dropdown from '@/components/Common/Dropdown';
import DropdownButton from '@/components/Common/DropdownButton';
import SelectorButton from '@/components/Common/SelectorButton';
-import { CogIcon, FilterIcon } from '@/components/Icons';
import useDropdown from '@/hooks/useDropdown';
import useLocalStorage from '@/hooks/useLocalStorage';
import { CstMatchMode, DependencyMode } from '@/models/miscelanious';
@@ -86,7 +86,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
}
+ icon={
}
text={labelCstMathchMode(filterMatch)}
onClick={matchModeMenu.toggle}
/>
@@ -100,7 +100,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
key={`${prefixes.cst_match_mode_list}${index}`}
onClick={() => handleMatchModeChange(matchMode)}
>
-
{labelCstMathchMode(matchMode)}: {describeCstMathchMode(matchMode)}
+
{labelCstMathchMode(matchMode)}: {describeCstMathchMode(matchMode)}
);
})}
: null}
@@ -110,7 +110,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
}
+ icon={
}
text={labelCstSource(filterSource)}
onClick={sourceMenu.toggle}
/>
@@ -124,7 +124,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
key={`${prefixes.cst_source_list}${index}`}
onClick={() => handleSourceChange(source)}
>
-
{labelCstSource(source)}: {describeCstSource(source)}
+
{labelCstSource(source)}: {describeCstSource(source)}
);
})}
: null}
diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx
index 058d3094..0c97c516 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsTable.tsx
@@ -43,13 +43,6 @@ function ConstituentsTable({
}, [windowSize, denseThreshold]);
const handleRowClicked = useCallback(
- (cst: IConstituenta, event: React.MouseEvent
) => {
- if (event.altKey && !isMockCst(cst)) {
- onOpenEdit(cst.id);
- }
- }, [onOpenEdit]);
-
- const handleDoubleClick = useCallback(
(cst: IConstituenta) => {
if (!isMockCst(cst)) {
onOpenEdit(cst.id);
@@ -135,7 +128,6 @@ function ConstituentsTable({
}
- onRowDoubleClicked={handleDoubleClick}
onRowClicked={handleRowClicked}
/>);
}
diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx
index 02d9ee29..816c01b3 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx
@@ -42,7 +42,7 @@ function ViewConstituents({ expression, baseHeight, schema, activeID, onOpenEdit
activeExpression={expression}
setFiltered={setFilteredData}
/>
-
+
-
+
- :
+ ?
+ :
}
onClick={() => setShowSubs(prev => !prev)}
/>
diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts
index 86c772e0..c8a4a203 100644
--- a/rsconcept/frontend/src/utils/labels.ts
+++ b/rsconcept/frontend/src/utils/labels.ts
@@ -5,7 +5,7 @@
* Description is a long description used in tooltips.
*/
import { GramData,Grammeme, ReferenceType } from '@/models/language';
-import { CstMatchMode, DependencyMode, HelpTopic, LibraryFilterStrategy } from '@/models/miscelanious';
+import { CstMatchMode, DependencyMode, HelpTopic, LibraryFilterStrategy, UserAccessMode } from '@/models/miscelanious';
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '@/models/rsform';
import { IArgumentInfo, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '@/models/rslang';
@@ -652,4 +652,29 @@ export function describeRSError(error: IRSErrorDescription): string {
return `Типизация выражения не соответствует типу конституенты`;
}
return 'UNKNOWN ERROR';
-}
\ No newline at end of file
+}
+
+/**
+ * Retrieves label for {@link UserAccessMode}.
+ */
+export function labelAccessMode(mode: UserAccessMode): string {
+ switch (mode) {
+ case UserAccessMode.READER: return 'Читатель';
+ case UserAccessMode.OWNER: return 'Владелец';
+ case UserAccessMode.ADMIN: return 'Администратор';
+ }
+}
+
+/**
+ * Retrieves description for {@link UserAccessMode}.
+ */
+export function describeAccessMode(mode: UserAccessMode): string {
+ switch (mode) {
+ case UserAccessMode.READER:
+ return 'Режим запрещает редактирование';
+ case UserAccessMode.OWNER:
+ return 'Режим редактирования владельцем';
+ case UserAccessMode.ADMIN:
+ return 'Режим редактирования администратором';
+ }
+}