mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Minor UI improvements and CSS refactoring
This commit is contained in:
parent
d64e4a9894
commit
ead0418564
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -108,6 +108,7 @@
|
|||
"signup",
|
||||
"Slng",
|
||||
"SMALLPR",
|
||||
"Stylesheet",
|
||||
"tagset",
|
||||
"tailwindcss",
|
||||
"tanstack",
|
||||
|
|
|
@ -6,7 +6,7 @@ import { IoLibrary } from 'react-icons/io5';
|
|||
import { EducationIcon } from '@/components/Icons';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { animateNavigation } from '@/utils/animations';
|
||||
import { animateNavigation } from '@/styling/animations';
|
||||
|
||||
import Logo from './Logo';
|
||||
import NavigationButton from './NavigationButton';
|
||||
|
@ -23,7 +23,14 @@ function Navigation() {
|
|||
const navigateCreateNew = () => router.push('/library/create');
|
||||
|
||||
return (
|
||||
<nav className={clsx('z-navigation', 'sticky top-0 left-0 right-0', 'clr-app', 'select-none')}>
|
||||
<nav
|
||||
className={clsx(
|
||||
'z-navigation', // prettier: split lines
|
||||
'sticky top-0 left-0 right-0',
|
||||
'clr-app',
|
||||
'select-none'
|
||||
)}
|
||||
>
|
||||
<ToggleNavigationButton />
|
||||
<motion.div
|
||||
className={clsx('pl-2 pr-[0.9rem] h-[3rem]', 'flex justify-between', 'shadow-border')}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { motion } from 'framer-motion';
|
|||
import { RiPushpinFill, RiUnpinLine } from 'react-icons/ri';
|
||||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { animateNavigationToggle } from '@/utils/animations';
|
||||
import { animateNavigationToggle } from '@/styling/animations';
|
||||
|
||||
function ToggleNavigationButton() {
|
||||
const { noNavigationAnimation, toggleNoNavigation } = useConceptTheme();
|
||||
|
|
22
rsconcept/frontend/src/components/AnimateFadeIn.tsx
Normal file
22
rsconcept/frontend/src/components/AnimateFadeIn.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { motion } from 'framer-motion';
|
||||
|
||||
import { animateFadeIn } from '@/styling/animations';
|
||||
|
||||
import { CProps } from './props';
|
||||
|
||||
interface AnimateFadeInProps extends CProps.AnimatedDiv {}
|
||||
|
||||
function AnimateFadeIn({ children, ...restProps }: AnimateFadeInProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ ...animateFadeIn.initial }}
|
||||
animate={{ ...animateFadeIn.animate }}
|
||||
exit={{ ...animateFadeIn.exit }}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnimateFadeIn;
|
|
@ -1,9 +1,9 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip';
|
||||
import ConstituentaTooltip from '@/components/ConstituentaTooltip';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { isMockCst } from '@/models/rsformAPI';
|
||||
import { colorFgCstStatus, IColorTheme } from '@/utils/color';
|
||||
import { colorFgCstStatus, IColorTheme } from '@/styling/color';
|
||||
|
||||
interface ConstituentaBadgeProps {
|
||||
prefixID?: string;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { prefixes } from '@/utils/constants';
|
|||
import { describeConstituenta } from '@/utils/labels';
|
||||
|
||||
import ConstituentaBadge from './ConstituentaBadge';
|
||||
import FlexColumn from './ui/FlexColumn';
|
||||
|
||||
interface ConstituentaPickerProps {
|
||||
prefixID?: string;
|
||||
|
@ -94,10 +95,10 @@ function ConstituentaPicker({
|
|||
columns={columns}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
noDataComponent={
|
||||
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'>
|
||||
<FlexColumn className='p-3 items-center min-h-[6rem]'>
|
||||
<p>Список конституент пуст</p>
|
||||
<p>Измените параметры фильтра</p>
|
||||
</span>
|
||||
</FlexColumn>
|
||||
}
|
||||
onRowClicked={onSelectValue}
|
||||
/>
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
useReactTable,
|
||||
type VisibilityState
|
||||
} from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { CProps } from '../props';
|
||||
|
@ -122,7 +121,7 @@ function DataTable<TData extends RowData>({
|
|||
const isEmpty = tableImpl.getRowModel().rows.length === 0;
|
||||
|
||||
return (
|
||||
<div className={clsx(className)} style={style}>
|
||||
<div className={className} style={style}>
|
||||
<table className='w-full'>
|
||||
{!noHeader ? (
|
||||
<TableHeader
|
||||
|
|
|
@ -52,14 +52,14 @@ function TableBody<TData>({
|
|||
style={conditionalRowStyles && getRowStyles(row)}
|
||||
>
|
||||
{enableRowSelection ? (
|
||||
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'>
|
||||
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y align-middle'>
|
||||
<SelectRow row={row} />
|
||||
</td>
|
||||
) : null}
|
||||
{row.getVisibleCells().map((cell: Cell<TData, unknown>) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className='px-2 border-y'
|
||||
className='px-2 border-y align-middle'
|
||||
style={{
|
||||
cursor: onRowClicked || onRowDoubleClicked ? 'pointer' : 'auto',
|
||||
paddingBottom: dense ? '0.25rem' : '0.5rem',
|
||||
|
|
|
@ -13,7 +13,7 @@ interface TableHeaderProps<TData> {
|
|||
function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSorting }: TableHeaderProps<TData>) {
|
||||
return (
|
||||
<thead
|
||||
className={`clr-app shadow-border`}
|
||||
className='clr-app shadow-border'
|
||||
style={{
|
||||
top: headPosition,
|
||||
position: 'sticky'
|
||||
|
@ -22,7 +22,7 @@ function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSor
|
|||
{table.getHeaderGroups().map((headerGroup: HeaderGroup<TData>) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{enableRowSelection ? (
|
||||
<th className='pl-3 pr-1'>
|
||||
<th className='pl-3 pr-1 align-middle'>
|
||||
<SelectAll table={table} />
|
||||
</th>
|
||||
) : null}
|
||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
|||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { GramData } from '@/models/language';
|
||||
import { colorFgGrammeme } from '@/utils/color';
|
||||
import { colorFgGrammeme } from '@/styling/color';
|
||||
import { labelGrammeme } from '@/utils/labels';
|
||||
|
||||
interface GrammemeBadgeProps {
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { BiInfoCircle } from 'react-icons/bi';
|
||||
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
||||
import InfoTopic from '../InfoTopic';
|
||||
import { CProps } from '../props';
|
||||
import InfoTopic from './InfoTopic';
|
||||
|
||||
interface HelpButtonProps extends CProps.Styling {
|
||||
topic: HelpTopic;
|
||||
offset?: number;
|
||||
place?: PlacesType;
|
||||
}
|
||||
|
||||
function HelpButton({ topic, ...restProps }: HelpButtonProps) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { urls } from '@/utils/constants';
|
|||
function HelpExteor() {
|
||||
// prettier-ignore
|
||||
return (
|
||||
<div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<h1>Экстеор</h1>
|
||||
<p>Экстеор 4.9 — редактор текстов систем понятий эксплицированных в родах структур, используемых для создания проектов систем организационного управления. Экстеор является идейным предком Портала.</p>
|
||||
<p>Портал превосходит Экстеор в части редактирования экспликаций, но функции синтеза и вычисления интерпретации пока доступны только в Экстеоре. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати.</p>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FiBell } from 'react-icons/fi';
|
|||
function HelpLibrary() {
|
||||
// prettier-ignore
|
||||
return (
|
||||
<div className='max-w-[80rem]'>
|
||||
<div>
|
||||
<h1>Библиотека концептуальных схем</h1>
|
||||
<p>В библиотеки собраны различные концептуальные схемы.</p>
|
||||
<p>Группировка и классификации схем на данный момент не проводится.</p>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { urls } from '@/utils/constants';
|
|||
function HelpMain() {
|
||||
// prettier-ignore
|
||||
return (
|
||||
<div className='max-w-[60rem]'>
|
||||
<div>
|
||||
<h1>Портал</h1>
|
||||
<p className=''>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p>
|
||||
<p className='mt-4 mb-1 text-center'><b>Основные разделы</b></p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
function HelpRSTemplates() {
|
||||
// prettier-ignore
|
||||
return (
|
||||
<div className='flex flex-col gap-2 pb-2 max-w-[80rem]'>
|
||||
<div className='flex flex-col gap-2 pb-2'>
|
||||
<h1>Банк выражений</h1>
|
||||
<p>Портал предоставляет быстрый доступ к часто используемым выражениям с помощью функции создания конституенты из шаблона.</p>
|
||||
<p>Источником шаблонов является <b>Банк выражений</b>, содержащий параметризованные понятия и утверждения, сгруппированные по разделам.</p>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
function HelpTerminologyControl() {
|
||||
// prettier-ignore
|
||||
return (
|
||||
<div className='flex flex-col gap-1 max-w-[80rem]'>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<h1>Терминологизация</h1>
|
||||
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
|
||||
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { IConstituenta } from '@/models/rsform';
|
||||
import { labelCstTypification } from '@/utils/labels';
|
||||
|
||||
interface InfoConstituentaProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
import { CProps } from './props';
|
||||
|
||||
interface InfoConstituentaProps extends CProps.Div {
|
||||
data: IConstituenta;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
|||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { CstClass } from '@/models/rsform';
|
||||
import { colorBgCstClass } from '@/utils/color';
|
||||
import { colorBgCstClass } from '@/styling/color';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { describeCstClass, labelCstClass } from '@/utils/labels';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import clsx from 'clsx';
|
|||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { ExpressionStatus } from '@/models/rsform';
|
||||
import { colorBgCstStatus } from '@/utils/color';
|
||||
import { colorBgCstStatus } from '@/styling/color';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { describeExpressionStatus, labelExpressionStatus } from '@/utils/labels';
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
||||
import HelpAPI from './HelpAPI';
|
||||
import HelpConstituenta from './HelpConstituenta';
|
||||
import HelpExteor from './HelpExteor';
|
||||
import HelpLibrary from './HelpLibrary';
|
||||
import HelpMain from './HelpMain';
|
||||
import HelpPrivacy from './HelpPrivacy';
|
||||
import HelpRSFormItems from './HelpRSFormItems';
|
||||
import HelpRSFormMeta from './HelpRSFormMeta';
|
||||
import HelpRSLang from './HelpRSLang';
|
||||
import HelpRSTemplates from './HelpRSTemplates';
|
||||
import HelpTermGraph from './HelpTermGraph';
|
||||
import HelpTerminologyControl from './HelpTerminologyControl';
|
||||
import HelpAPI from './Help/HelpAPI';
|
||||
import HelpConstituenta from './Help/HelpConstituenta';
|
||||
import HelpExteor from './Help/HelpExteor';
|
||||
import HelpLibrary from './Help/HelpLibrary';
|
||||
import HelpMain from './Help/HelpMain';
|
||||
import HelpPrivacy from './Help/HelpPrivacy';
|
||||
import HelpRSFormItems from './Help/HelpRSFormItems';
|
||||
import HelpRSFormMeta from './Help/HelpRSFormMeta';
|
||||
import HelpRSLang from './Help/HelpRSLang';
|
||||
import HelpRSTemplates from './Help/HelpRSTemplates';
|
||||
import HelpTermGraph from './Help/HelpTermGraph';
|
||||
import HelpTerminologyControl from './Help/HelpTerminologyControl';
|
||||
|
||||
interface InfoTopicProps {
|
||||
topic: HelpTopic;
|
|
@ -5,7 +5,7 @@ import { useMemo, useState } from 'react';
|
|||
import { Document, Page } from 'react-pdf';
|
||||
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { graphLightT } from '@/utils/color';
|
||||
import { graphLightT } from '@/styling/color';
|
||||
|
||||
import Overlay from '../ui/Overlay';
|
||||
import PageControls from './PageControls';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { bracketMatching, MatchResult } from '@codemirror/language';
|
||||
import { Decoration, EditorView } from '@codemirror/view';
|
||||
|
||||
import { bracketsDarkT, bracketsLightT } from '@/utils/color';
|
||||
import { bracketsDarkT, bracketsLightT } from '@/styling/color';
|
||||
|
||||
const matchingMark = Decoration.mark({ class: 'cc-matchingBracket' });
|
||||
const nonMatchingMark = Decoration.mark({ class: 'cc-nonmatchingBracket' });
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
findContainedNodes,
|
||||
findEnvelopingNodes
|
||||
} from '@/utils/codemirror';
|
||||
import { IColorTheme } from '@/utils/color';
|
||||
import { IColorTheme } from '@/styling/color';
|
||||
|
||||
import { ReferenceTokens } from './parse';
|
||||
import { RefEntity, RefSyntactic } from './parse/parser.terms';
|
||||
|
|
1
rsconcept/frontend/src/components/props.d.ts
vendored
1
rsconcept/frontend/src/components/props.d.ts
vendored
|
@ -38,4 +38,5 @@ export namespace CProps {
|
|||
export type Input = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
|
||||
|
||||
export type AnimatedButton = Omit<HTMLMotionProps<'button'>, 'type'>;
|
||||
export type AnimatedDiv = HTMLMotionProps<'div'>;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,13 @@ function Checkbox({ id, disabled, label, title, className, value, setValue, ...r
|
|||
<button
|
||||
type='button'
|
||||
id={id}
|
||||
className={clsx('flex items-center gap-2', 'outline-none', 'text-start', cursor, className)}
|
||||
className={clsx(
|
||||
'flex items-center gap-2', // prettier: split lines
|
||||
'outline-none',
|
||||
'text-start',
|
||||
cursor,
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
data-tooltip-id={title ? globalIDs.tooltip : undefined}
|
||||
|
@ -45,10 +51,14 @@ function Checkbox({ id, disabled, label, title, className, value, setValue, ...r
|
|||
{...restProps}
|
||||
>
|
||||
<div
|
||||
className={clsx('max-w-[1rem] min-w-[1rem] h-4', 'border rounded-sm', {
|
||||
className={clsx(
|
||||
'max-w-[1rem] min-w-[1rem] h-4', // prettier: split lines
|
||||
'border rounded-sm',
|
||||
{
|
||||
'clr-primary': value !== false,
|
||||
'clr-app': value === false
|
||||
})}
|
||||
}
|
||||
)}
|
||||
>
|
||||
{value ? (
|
||||
<div className='mt-[1px] ml-[1px]'>
|
||||
|
|
|
@ -49,7 +49,12 @@ function CheckboxTristate({
|
|||
<button
|
||||
type='button'
|
||||
id={id}
|
||||
className={clsx('flex items-center gap-2 text-start', 'outline-none', cursor, className)}
|
||||
className={clsx(
|
||||
'flex items-center gap-2 text-start', // prettier: split lines
|
||||
'outline-none',
|
||||
cursor,
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
data-tooltip-id={title ? globalIDs.tooltip : undefined}
|
||||
|
@ -57,10 +62,14 @@ function CheckboxTristate({
|
|||
{...restProps}
|
||||
>
|
||||
<div
|
||||
className={clsx('w-4 h-4', 'border rounded-sm', {
|
||||
className={clsx(
|
||||
'w-4 h-4', // prettier: split lines
|
||||
'border rounded-sm',
|
||||
{
|
||||
'clr-primary': value !== false,
|
||||
'clr-app': value === false
|
||||
})}
|
||||
}
|
||||
)}
|
||||
>
|
||||
{value ? (
|
||||
<div className='mt-[1px] ml-[1px]'>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { animateDropdown } from '@/utils/animations';
|
||||
import { animateDropdown } from '@/styling/animations';
|
||||
|
||||
import { CProps } from '../props';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { animateDropdownItem } from '@/utils/animations';
|
||||
import { animateDropdownItem } from '@/styling/animations';
|
||||
import { globalIDs } from '@/utils/constants';
|
||||
|
||||
import { CProps } from '../props';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { animateDropdownItem } from '@/utils/animations';
|
||||
import { animateDropdownItem } from '@/styling/animations';
|
||||
|
||||
import Checkbox from './Checkbox';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useRef } from 'react';
|
|||
import { BiX } from 'react-icons/bi';
|
||||
|
||||
import useEscapeKey from '@/hooks/useEscapeKey';
|
||||
import { animateModal } from '@/utils/animations';
|
||||
import { animateModal } from '@/styling/animations';
|
||||
|
||||
import { CProps } from '../props';
|
||||
import Button from './Button';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useMemo } from 'react';
|
|||
import Select, { GroupBase, Props, StylesConfig } from 'react-select';
|
||||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { selectDarkT, selectLightT } from '@/utils/color';
|
||||
import { selectDarkT, selectLightT } from '@/styling/color';
|
||||
|
||||
export interface SelectMultiProps<Option, Group extends GroupBase<Option> = GroupBase<Option>>
|
||||
extends Omit<Props<Option, true, Group>, 'theme' | 'menuPortalTarget'> {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useMemo } from 'react';
|
|||
import Select, { GroupBase, Props, StylesConfig } from 'react-select';
|
||||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { selectDarkT, selectLightT } from '@/utils/color';
|
||||
import { selectDarkT, selectLightT } from '@/styling/color';
|
||||
|
||||
interface SelectSingleProps<Option, Group extends GroupBase<Option> = GroupBase<Option>>
|
||||
extends Omit<Props<Option, false, Group>, 'theme' | 'menuPortalTarget'> {
|
||||
|
|
|
@ -7,6 +7,8 @@ import { ITooltip, Tooltip as TooltipImpl } from 'react-tooltip';
|
|||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
|
||||
export type { PlacesType } from 'react-tooltip';
|
||||
|
||||
interface TooltipProps extends Omit<ITooltip, 'variant'> {
|
||||
layer?: string;
|
||||
text?: string;
|
||||
|
|
|
@ -5,8 +5,8 @@ import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useSt
|
|||
|
||||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { animationDuration } from '@/utils/animations';
|
||||
import { darkT, IColorTheme, lightT } from '@/utils/color';
|
||||
import { animationDuration } from '@/styling/animations';
|
||||
import { darkT, IColorTheme, lightT } from '@/styling/color';
|
||||
import { globalIDs } from '@/utils/constants';
|
||||
|
||||
interface IThemeContext {
|
||||
|
|
|
@ -6,8 +6,8 @@ import GraphUI, { GraphEdge, GraphNode } from '@/components/GraphUI';
|
|||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { SyntaxTree } from '@/models/rslang';
|
||||
import { graphDarkT, graphLightT } from '@/utils/color';
|
||||
import { colorBgSyntaxTree } from '@/utils/color';
|
||||
import { graphDarkT, graphLightT } from '@/styling/color';
|
||||
import { colorBgSyntaxTree } from '@/styling/color';
|
||||
import { resources } from '@/utils/constants';
|
||||
import { labelSyntaxTree } from '@/utils/labels';
|
||||
|
||||
|
|
|
@ -1,361 +1,4 @@
|
|||
@import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* prettier-ignore */
|
||||
:root {
|
||||
--font-ui: 'Geologica', sans-serif;
|
||||
--font-main: 'Rubik', 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
|
||||
--font-math: 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
|
||||
|
||||
/* Light Theme */
|
||||
--cl-bg-120: hsl(000, 000%, 100%);
|
||||
--cl-bg-100: hsl(000, 000%, 098%);
|
||||
--cl-bg-80: hsl(000, 000%, 094%);
|
||||
--cl-bg-60: hsl(000, 000%, 091%);
|
||||
--cl-bg-40: hsl(000, 000%, 080%);
|
||||
|
||||
--cl-fg-60: hsl(000, 000%, 065%);
|
||||
--cl-fg-80: hsl(000, 000%, 047%);
|
||||
--cl-fg-100: hsl(000, 000%, 000%);
|
||||
|
||||
--cl-prim-bg-100: hsl(220, 100%, 060%);
|
||||
--cl-prim-bg-80: hsl(220, 080%, 092%);
|
||||
--cl-prim-bg-60: hsl(190, 080%, 094%);
|
||||
|
||||
--cl-prim-fg-80: hsl(220, 100%, 050%);
|
||||
--cl-prim-fg-100: hsl(000, 000%, 100%);
|
||||
|
||||
--cl-red-bg-100: hsl(000, 100%, 095%);
|
||||
--cl-red-fg-100: hsl(000, 072%, 051%);
|
||||
--cl-green-fg-100: hsl(120, 080%, 37%);
|
||||
|
||||
/* Dark Theme */
|
||||
--cd-bg-120: hsl(000, 000%, 005%);
|
||||
--cd-bg-100: hsl(000, 000%, 009%);
|
||||
--cd-bg-80: hsl(000, 000%, 015%);
|
||||
--cd-bg-60: hsl(000, 000%, 022%);
|
||||
--cd-bg-40: hsl(000, 000%, 035%);
|
||||
|
||||
--cd-fg-60: hsl(000, 000%, 055%);
|
||||
--cd-fg-80: hsl(000, 000%, 080%);
|
||||
--cd-fg-100: hsl(000, 000%, 093%);
|
||||
|
||||
--cd-prim-bg-100: hsl(267, 050%, 050%);
|
||||
--cd-prim-bg-80: hsl(267, 050%, 032%);
|
||||
--cd-prim-bg-60: hsl(269, 030%, 028%);
|
||||
|
||||
--cd-prim-fg-80: hsl(267, 070%, 070%);
|
||||
--cd-prim-fg-100: hsl(000, 000%, 100%);
|
||||
|
||||
--cd-red-bg-100: hsl(000, 100%, 015%);
|
||||
--cd-red-fg-100: hsl(000, 080%, 055%);
|
||||
--cd-green-fg-100: hsl(120, 080%, 042%);
|
||||
|
||||
/* Import overrides */
|
||||
--toastify-color-dark: var(--cd-bg-60);
|
||||
}
|
||||
|
||||
/* Depth layers */
|
||||
.z-bottom {
|
||||
z-index: 0;
|
||||
}
|
||||
.z-pop {
|
||||
z-index: 10;
|
||||
}
|
||||
:is(.z-sticky, .sticky) {
|
||||
z-index: 20;
|
||||
}
|
||||
.z-tooltip {
|
||||
z-index: 30;
|
||||
}
|
||||
.z-navigation {
|
||||
z-index: 50;
|
||||
}
|
||||
.z-modal {
|
||||
z-index: 60;
|
||||
}
|
||||
.z-modal-controls {
|
||||
z-index: 70;
|
||||
}
|
||||
.z-modal-top {
|
||||
z-index: 80;
|
||||
}
|
||||
.z-modal-tooltip {
|
||||
z-index: 90;
|
||||
}
|
||||
.z-topmost {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: var(--font-main);
|
||||
|
||||
color: var(--cl-fg-100);
|
||||
border-color: var(--cl-bg-40);
|
||||
background-color: var(--cl-bg-100);
|
||||
|
||||
&.dark {
|
||||
color: var(--cd-fg-100);
|
||||
border-color: var(--cd-bg-40);
|
||||
background-color: var(--cd-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: transparent;
|
||||
.dark & {
|
||||
outline-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--cl-prim-bg-60);
|
||||
.dark & {
|
||||
background: var(--cd-prim-bg-60);
|
||||
}
|
||||
tr :hover& {
|
||||
background: var(--cl-red-bg-100);
|
||||
.dark & {
|
||||
background: var(--cd-red-bg-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
|
||||
[data-color-scheme='dark'] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
[data-color-scheme='light'] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.font-controls {
|
||||
font-family: var(--font-ui);
|
||||
font-weight: 600;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
.font-math {
|
||||
font-family: var(--font-math);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
h1 {
|
||||
@apply text-lg font-semibold text-center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-semibold text-center;
|
||||
}
|
||||
|
||||
b {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
.border {
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.shadow-border {
|
||||
@apply shadow-sm shadow-[var(--cl-bg-40)] dark:shadow-[var(--cd-bg-40)];
|
||||
}
|
||||
|
||||
.clr-modal-backdrop {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
:is(
|
||||
.clr-border,
|
||||
.border,
|
||||
.border-x,
|
||||
.border-y,
|
||||
.border-b,
|
||||
.border-t,
|
||||
.border-l,
|
||||
.border-r,
|
||||
.border-2,
|
||||
.border-x-2,
|
||||
.border-y-2,
|
||||
.border-b-2,
|
||||
.border-t-2,
|
||||
.border-l-2,
|
||||
.border-r-2,
|
||||
.divide-x,
|
||||
.divide-y,
|
||||
.divide-x-2,
|
||||
.divide-y-2
|
||||
) {
|
||||
border-color: var(--cl-bg-40);
|
||||
@apply divide-inherit;
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-app, .clr-footer, .clr-modal-backdrop, .clr-btn-nav, .clr-input:disabled) {
|
||||
background-color: var(--cl-bg-100);
|
||||
.dark & {
|
||||
background-color: var(--cd-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-input) {
|
||||
background-color: var(--cl-bg-120);
|
||||
.dark & {
|
||||
background-color: var(--cd-bg-120);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-controls, .clr-tab, .clr-btn-default) {
|
||||
background-color: var(--cl-bg-80);
|
||||
.dark & {
|
||||
background-color: var(--cd-bg-80);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-primary, .clr-btn-primary:hover, .clr-btn-primary:focus) {
|
||||
@apply transition;
|
||||
color: var(--cl-prim-fg-100);
|
||||
background-color: var(--cl-prim-bg-100);
|
||||
.dark & {
|
||||
color: var(--cd-prim-fg-100);
|
||||
background-color: var(--cd-prim-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-selected, .clr-btn-primary) {
|
||||
color: var(--cl-fg-100);
|
||||
background-color: var(--cl-prim-bg-80);
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
background-color: var(--cd-prim-bg-80);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-disabled, .clr-btn-default, .clr-btn-primary):disabled {
|
||||
@apply transition;
|
||||
color: var(--cl-fg-80);
|
||||
background-color: var(--cl-bg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-80);
|
||||
background-color: var(--cd-bg-60);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-hover, .clr-tab, .clr-btn-nav, .clr-btn-default):hover:not(:disabled) {
|
||||
@apply transition;
|
||||
color: var(--cl-fg-100);
|
||||
background-color: var(--cl-prim-bg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
background-color: var(--cd-prim-bg-60);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-outline, .clr-btn-primary):focus {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: var(--cl-prim-bg-100);
|
||||
.dark & {
|
||||
outline-color: var(--cd-prim-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-text-primary, .clr-text-url) {
|
||||
color: var(--cl-prim-fg-80);
|
||||
.dark & {
|
||||
color: var(--cd-prim-fg-80);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-footer {
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) {
|
||||
color: var(--cl-fg-80);
|
||||
&:disabled {
|
||||
color: var(--cl-fg-60);
|
||||
}
|
||||
.dark & {
|
||||
color: var(--cd-fg-80);
|
||||
&:disabled {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clr-warning {
|
||||
background-color: var(--cl-red-bg-100);
|
||||
.dark & {
|
||||
background-color: var(--cd-red-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-text-default {
|
||||
color: var(--cl-fg-100);
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-text-warning {
|
||||
color: var(--cl-red-fg-100);
|
||||
.dark & {
|
||||
color: var(--cd-red-fg-100);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-text-success {
|
||||
color: var(--cl-green-fg-100);
|
||||
.dark & {
|
||||
color: var(--cd-green-fg-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
resize: vertical;
|
||||
overflow-y: auto;
|
||||
border-color: var(--cl-bg-40);
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
}
|
||||
@apply border rounded px-[0.375rem] py-[0.15rem];
|
||||
}
|
||||
.cm-editor.cm-focused {
|
||||
border-color: var(--cl-bg-40);
|
||||
outline-color: var(--cl-prim-bg-100);
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
outline-color: var(--cd-prim-bg-100);
|
||||
}
|
||||
@apply outline-2 outline;
|
||||
}
|
||||
|
||||
.cm-editor .cm-placeholder {
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
|
||||
.rdt_TableCell {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@import 'styling/setup.css';
|
||||
@import 'styling/styles.css';
|
||||
@import 'styling/imports.css';
|
||||
@import 'styling/overrides.css';
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||
import { BiDownload } from 'react-icons/bi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import RequireAuth from '@/components/RequireAuth';
|
||||
import Button from '@/components/ui/Button';
|
||||
|
@ -78,6 +79,7 @@ function CreateRSFormPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<AnimateFadeIn>
|
||||
<RequireAuth>
|
||||
<form className={clsx('px-6 py-3', classnames.flex_col)} onSubmit={handleSubmit}>
|
||||
<h1>Создание концептуальной схемы</h1>
|
||||
|
@ -128,6 +130,7 @@ function CreateRSFormPage() {
|
|||
{error ? <InfoError error={error} /> : null}
|
||||
</form>
|
||||
</RequireAuth>
|
||||
</AnimateFadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,13 +20,7 @@ function HomePage() {
|
|||
}
|
||||
}, [router, user]);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center px-4 py-2'>
|
||||
{user?.is_staff ? (
|
||||
<p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
return <div />;
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import { Loader } from '@/components/ui/Loader';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
|
@ -68,7 +69,7 @@ function LibraryPage() {
|
|||
{library.loading ? <Loader /> : null}
|
||||
{library.error ? <InfoError error={library.error} /> : null}
|
||||
{!library.loading && library.items ? (
|
||||
<>
|
||||
<AnimateFadeIn>
|
||||
<SearchPanel
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
|
@ -78,7 +79,7 @@ function LibraryPage() {
|
|||
setFilter={setFilter}
|
||||
/>
|
||||
<ViewLibrary resetQuery={resetQuery} items={items} />
|
||||
</>
|
||||
</AnimateFadeIn>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useIntl } from 'react-intl';
|
|||
|
||||
import DataTable, { createColumnHelper } from '@/components/DataTable';
|
||||
import HelpButton from '@/components/Help/HelpButton';
|
||||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
|
@ -95,7 +96,7 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
|
|||
'flex gap-1'
|
||||
)}
|
||||
>
|
||||
<HelpButton topic={HelpTopic.LIBRARY} className='max-w-[35rem]' offset={0} />
|
||||
<HelpButton topic={HelpTopic.LIBRARY} className='max-w-[35rem]' offset={5} place='right-start' />
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
|
@ -103,13 +104,13 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
|
|||
data={items}
|
||||
headPosition='2.3rem'
|
||||
noDataComponent={
|
||||
<div className='p-3 text-center min-h-[6rem]'>
|
||||
<FlexColumn className='p-3 items-center min-h-[6rem]'>
|
||||
<p>Список схем пуст</p>
|
||||
<p className='flex justify-center gap-6 mt-3'>
|
||||
<p className='flex gap-6'>
|
||||
<TextURL text='Создать схему' href='/library/create' />
|
||||
<TextURL text='Очистить фильтр' onClick={cleanQuery} />
|
||||
</p>
|
||||
</div>
|
||||
</FlexColumn>
|
||||
}
|
||||
onRowClicked={handleOpenItem}
|
||||
enableSorting
|
||||
|
|
|
@ -4,6 +4,7 @@ import axios from 'axios';
|
|||
import clsx from 'clsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import ExpectedAnonymous from '@/components/ExpectedAnonymous';
|
||||
import InfoError, { ErrorData } from '@/components/InfoError';
|
||||
import SubmitButton from '@/components/ui/SubmitButton';
|
||||
|
@ -62,6 +63,7 @@ function LoginPage() {
|
|||
return <ExpectedAnonymous />;
|
||||
}
|
||||
return (
|
||||
<AnimateFadeIn>
|
||||
<form className={clsx('w-[24rem]', 'pt-12 pb-6 px-6', classnames.flex_col)} onSubmit={handleSubmit}>
|
||||
<img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' />
|
||||
<TextInput
|
||||
|
@ -95,6 +97,7 @@ function LoginPage() {
|
|||
</div>
|
||||
{error ? <ProcessError error={error} /> : null}
|
||||
</form>
|
||||
</AnimateFadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
|
|||
<div
|
||||
className={clsx('sticky top-0 left-0', 'min-w-[13rem] self-start', 'border-x', 'clr-controls', '', 'select-none')}
|
||||
>
|
||||
<h1 className='my-1'>Справка</h1>
|
||||
{Object.values(HelpTopic).map((topic, index) => (
|
||||
<div
|
||||
key={`${prefixes.topic_list}${index}`}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import InfoTopic from '@/components/Help/InfoTopic';
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import InfoTopic from '@/components/InfoTopic';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
|
||||
interface ViewTopicProps {
|
||||
|
@ -7,9 +8,9 @@ interface ViewTopicProps {
|
|||
|
||||
function ViewTopic({ topic }: ViewTopicProps) {
|
||||
return (
|
||||
<div className='px-2 py-2 mx-auto'>
|
||||
<AnimateFadeIn key={topic} className='px-2 py-2 mx-auto'>
|
||||
<InfoTopic topic={topic} />
|
||||
</div>
|
||||
</AnimateFadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { motion } from 'framer-motion';
|
|||
|
||||
import { IExpressionParse, IRSErrorDescription } from '@/models/rslang';
|
||||
import { getRSErrorPrefix } from '@/models/rslangAPI';
|
||||
import { animateParseResults } from '@/utils/animations';
|
||||
import { animateParseResults } from '@/styling/animations';
|
||||
import { describeRSError } from '@/utils/labels';
|
||||
|
||||
interface ParsingResultProps {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { motion } from 'framer-motion';
|
||||
|
||||
import { TokenID } from '@/models/rslang';
|
||||
import { animateRSControl } from '@/utils/animations';
|
||||
import { animateRSControl } from '@/styling/animations';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import RSLocalButton from './RSLocalButton';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { ExpressionStatus } from '@/models/rsform';
|
|||
import { type IConstituenta } from '@/models/rsform';
|
||||
import { inferStatus } from '@/models/rsformAPI';
|
||||
import { IExpressionParse, ParsingStatus } from '@/models/rslang';
|
||||
import { colorBgCstStatus } from '@/utils/color';
|
||||
import { colorBgCstStatus } from '@/styling/color';
|
||||
import { globalIDs } from '@/utils/constants';
|
||||
import { labelExpressionStatus } from '@/utils/labels';
|
||||
|
||||
|
|
|
@ -195,6 +195,12 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
|
||||
return (
|
||||
<div tabIndex={-1} className='outline-none' onKeyDown={handleTableKey}>
|
||||
<SelectedCounter
|
||||
total={schema?.stats?.count_all ?? 0}
|
||||
selected={selected.length}
|
||||
position='top-[0.3rem] left-2'
|
||||
/>
|
||||
|
||||
<RSListToolbar
|
||||
selectedCount={selected.length}
|
||||
isMutable={isMutable}
|
||||
|
@ -204,7 +210,6 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
|||
onCreate={handleCreateCst}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
<SelectedCounter total={schema?.stats?.count_all ?? 0} selected={selected.length} position='left-1 top-2' />
|
||||
|
||||
<div className='pt-[2.3rem] border-b' />
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
|||
|
||||
import ConstituentaBadge from '@/components/ConstituentaBadge';
|
||||
import DataTable, { createColumnHelper, RowSelectionState, VisibilityState } from '@/components/DataTable';
|
||||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import useWindowSize from '@/hooks/useWindowSize';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
|
@ -135,12 +136,12 @@ function RSTable({ items, selected, setSelected, onEdit, onCreateNew }: RSTableP
|
|||
rowSelection={selected}
|
||||
onRowSelectionChange={setSelected}
|
||||
noDataComponent={
|
||||
<span className='flex flex-col justify-center p-2 text-center'>
|
||||
<FlexColumn className='p-3 items-center'>
|
||||
<p>Список пуст</p>
|
||||
<p className='cursor-pointer clr-text-primary hover:underline' onClick={() => onCreateNew()}>
|
||||
Создать новую конституенту
|
||||
</p>
|
||||
</span>
|
||||
</FlexColumn>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ import DlgGraphParams from '@/dialogs/DlgGraphParams';
|
|||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { GraphColoringScheme, GraphFilterParams } from '@/models/miscellaneous';
|
||||
import { CstType, ICstCreateData } from '@/models/rsform';
|
||||
import { colorBgGraphNode } from '@/utils/color';
|
||||
import { colorBgGraphNode } from '@/styling/color';
|
||||
import { classnames, TIMEOUT_GRAPH_REFRESH } from '@/utils/constants';
|
||||
|
||||
import GraphSidebar from './GraphSidebar';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
|
|||
|
||||
import GraphUI, { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, Sphere, useSelection } from '@/components/GraphUI';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { graphDarkT, graphLightT } from '@/utils/color';
|
||||
import { graphDarkT, graphLightT } from '@/styling/color';
|
||||
import { resources } from '@/utils/constants';
|
||||
|
||||
interface TermGraphProps {
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip';
|
||||
import ConstituentaTooltip from '@/components/ConstituentaTooltip';
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { GraphColoringScheme } from '@/models/miscellaneous';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { colorBgGraphNode } from '@/utils/color';
|
||||
import { colorBgGraphNode } from '@/styling/color';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
interface ViewHiddenProps {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
|||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import InfoError, { ErrorData } from '@/components/InfoError';
|
||||
import { Loader } from '@/components/ui/Loader';
|
||||
import TabLabel from '@/components/ui/TabLabel';
|
||||
|
@ -434,6 +435,7 @@ function RSTabs() {
|
|||
<TabLabel label='Граф термов' />
|
||||
</TabList>
|
||||
|
||||
<AnimateFadeIn>
|
||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '' : 'none' }}>
|
||||
<EditorRSForm
|
||||
isMutable={isMutable}
|
||||
|
@ -479,6 +481,7 @@ function RSTabs() {
|
|||
onDeleteCst={promptDeleteCst}
|
||||
/>
|
||||
</TabPanel>
|
||||
</AnimateFadeIn>
|
||||
</Tabs>
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
@ -125,7 +125,7 @@ function ConstituentsTable({ items, activeID, onOpenEdit, maxHeight, denseThresh
|
|||
columnVisibility={columnVisibility}
|
||||
onColumnVisibilityChange={setColumnVisibility}
|
||||
noDataComponent={
|
||||
<div className={clsx('min-h-[5rem]', 'p-2', 'text-center', 'select-none')}>
|
||||
<div className={clsx('min-h-[5rem]', 'p-3', 'text-center', 'select-none')}>
|
||||
<p>Список конституент пуст</p>
|
||||
<p>Измените параметры фильтра</p>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useMemo, useState } from 'react';
|
|||
|
||||
import { useConceptTheme } from '@/context/ThemeContext';
|
||||
import { IConstituenta, IRSForm } from '@/models/rsform';
|
||||
import { animateSideView } from '@/utils/animations';
|
||||
import { animateSideView } from '@/styling/animations';
|
||||
|
||||
import ConstituentsSearch from './ConstituentsSearch';
|
||||
import ConstituentsTable from './ConstituentsTable';
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
|
|||
import { BiInfoCircle } from 'react-icons/bi';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import ExpectedAnonymous from '@/components/ExpectedAnonymous';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import Button from '@/components/ui/Button';
|
||||
|
@ -67,6 +68,7 @@ function RegisterPage() {
|
|||
return <ExpectedAnonymous />;
|
||||
}
|
||||
return (
|
||||
<AnimateFadeIn>
|
||||
<form className={clsx('px-6 py-3', classnames.flex_col)} onSubmit={handleSubmit}>
|
||||
<h1>Новый пользователь</h1>
|
||||
<div className='flex gap-12'>
|
||||
|
@ -146,6 +148,7 @@ function RegisterPage() {
|
|||
</div>
|
||||
{error ? <InfoError error={error} /> : null}
|
||||
</form>
|
||||
</AnimateFadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import TextURL from '@/components/ui/TextURL';
|
||||
import { urls } from '@/utils/constants';
|
||||
|
||||
function RestorePasswordPage() {
|
||||
return (
|
||||
<div className='py-3'>
|
||||
<AnimateFadeIn className='py-3'>
|
||||
<p>Автоматическое восстановление пароля не доступно.</p>
|
||||
<p>
|
||||
Возможно восстановление пароля через обращение на <TextURL href={urls.mail_portal} text='portal@acconcept.ru' />
|
||||
</p>
|
||||
</div>
|
||||
</AnimateFadeIn>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { AnimatePresence } from 'framer-motion';
|
|||
import { useMemo, useState } from 'react';
|
||||
import { FiBell, FiBellOff } from 'react-icons/fi';
|
||||
|
||||
import AnimateFadeIn from '@/components/AnimateFadeIn';
|
||||
import InfoError from '@/components/InfoError';
|
||||
import { Loader } from '@/components/ui/Loader';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
|
@ -32,7 +33,7 @@ function UserTabs() {
|
|||
{loading ? <Loader /> : null}
|
||||
{error ? <InfoError error={error} /> : null}
|
||||
{user ? (
|
||||
<div className='flex gap-6 py-2'>
|
||||
<AnimateFadeIn className='flex gap-6 py-2'>
|
||||
<div>
|
||||
<Overlay position='top-0 right-0'>
|
||||
<MiniButton
|
||||
|
@ -56,7 +57,7 @@ function UserTabs() {
|
|||
<AnimatePresence>
|
||||
{subscriptions.length > 0 && showSubs ? <ViewSubscriptions items={subscriptions} /> : null}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</AnimateFadeIn>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useIntl } from 'react-intl';
|
|||
import DataTable, { createColumnHelper } from '@/components/DataTable';
|
||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { ILibraryItem } from '@/models/library';
|
||||
import { animateSideView } from '@/utils/animations';
|
||||
import { animateSideView } from '@/styling/animations';
|
||||
|
||||
interface ViewSubscriptionsProps {
|
||||
items: ILibraryItem[];
|
||||
|
|
|
@ -190,3 +190,25 @@ export const animateModal = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const animateFadeIn = {
|
||||
initial: {
|
||||
opacity: 0
|
||||
},
|
||||
animate: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
type: 'tween',
|
||||
ease: 'linear',
|
||||
duration: 0.3
|
||||
}
|
||||
},
|
||||
exit: {
|
||||
opacity: 0,
|
||||
transition: {
|
||||
type: 'tween',
|
||||
ease: 'linear',
|
||||
duration: 2
|
||||
}
|
||||
}
|
||||
};
|
54
rsconcept/frontend/src/styling/constants.css
Normal file
54
rsconcept/frontend/src/styling/constants.css
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Module: Define CSS variables.
|
||||
*/
|
||||
|
||||
/* prettier-ignore */
|
||||
:root {
|
||||
--font-ui: 'Geologica', sans-serif;
|
||||
--font-main: 'Rubik', 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
|
||||
--font-math: 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
|
||||
|
||||
/* Light Theme */
|
||||
--cl-bg-120: hsl(000, 000%, 100%);
|
||||
--cl-bg-100: hsl(000, 000%, 098%);
|
||||
--cl-bg-80: hsl(000, 000%, 094%);
|
||||
--cl-bg-60: hsl(000, 000%, 091%);
|
||||
--cl-bg-40: hsl(000, 000%, 080%);
|
||||
|
||||
--cl-fg-60: hsl(000, 000%, 065%);
|
||||
--cl-fg-80: hsl(000, 000%, 047%);
|
||||
--cl-fg-100: hsl(000, 000%, 000%);
|
||||
|
||||
--cl-prim-bg-100: hsl(220, 100%, 060%);
|
||||
--cl-prim-bg-80: hsl(220, 080%, 092%);
|
||||
--cl-prim-bg-60: hsl(190, 080%, 094%);
|
||||
|
||||
--cl-prim-fg-80: hsl(220, 100%, 050%);
|
||||
--cl-prim-fg-100: hsl(000, 000%, 100%);
|
||||
|
||||
--cl-red-bg-100: hsl(000, 100%, 095%);
|
||||
--cl-red-fg-100: hsl(000, 072%, 051%);
|
||||
--cl-green-fg-100: hsl(120, 080%, 37%);
|
||||
|
||||
/* Dark Theme */
|
||||
--cd-bg-120: hsl(000, 000%, 005%);
|
||||
--cd-bg-100: hsl(000, 000%, 009%);
|
||||
--cd-bg-80: hsl(000, 000%, 015%);
|
||||
--cd-bg-60: hsl(000, 000%, 022%);
|
||||
--cd-bg-40: hsl(000, 000%, 035%);
|
||||
|
||||
--cd-fg-60: hsl(000, 000%, 055%);
|
||||
--cd-fg-80: hsl(000, 000%, 080%);
|
||||
--cd-fg-100: hsl(000, 000%, 093%);
|
||||
|
||||
--cd-prim-bg-100: hsl(267, 050%, 050%);
|
||||
--cd-prim-bg-80: hsl(267, 050%, 032%);
|
||||
--cd-prim-bg-60: hsl(269, 030%, 028%);
|
||||
|
||||
--cd-prim-fg-80: hsl(267, 070%, 070%);
|
||||
--cd-prim-fg-100: hsl(000, 000%, 100%);
|
||||
|
||||
--cd-red-bg-100: hsl(000, 100%, 015%);
|
||||
--cd-red-fg-100: hsl(000, 080%, 055%);
|
||||
--cd-green-fg-100: hsl(120, 080%, 042%);
|
||||
}
|
5
rsconcept/frontend/src/styling/imports.css
Normal file
5
rsconcept/frontend/src/styling/imports.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Module: List external styling imports.
|
||||
*/
|
||||
|
||||
@import 'react-toastify/dist/ReactToastify.css';
|
40
rsconcept/frontend/src/styling/layers.css
Normal file
40
rsconcept/frontend/src/styling/layers.css
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* Depth layers */
|
||||
.z-bottom {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.z-pop {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
:is(.z-sticky, .sticky) {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.z-tooltip {
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.z-navigation {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-modal {
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.z-modal-controls {
|
||||
z-index: 70;
|
||||
}
|
||||
|
||||
.z-modal-top {
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.z-modal-tooltip {
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.z-topmost {
|
||||
z-index: 99;
|
||||
}
|
38
rsconcept/frontend/src/styling/overrides.css
Normal file
38
rsconcept/frontend/src/styling/overrides.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Module: Override imported components CSS styling.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Import overrides */
|
||||
--toastify-color-dark: var(--cd-bg-60);
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
resize: vertical;
|
||||
overflow-y: auto;
|
||||
border-color: var(--cl-bg-40);
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
}
|
||||
@apply border rounded px-[0.375rem] py-[0.15rem];
|
||||
}
|
||||
.cm-editor.cm-focused {
|
||||
border-color: var(--cl-bg-40);
|
||||
outline-color: var(--cl-prim-bg-100);
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
outline-color: var(--cd-prim-bg-100);
|
||||
}
|
||||
@apply outline-2 outline;
|
||||
}
|
||||
|
||||
.cm-editor .cm-placeholder {
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
|
||||
.rdt_TableCell {
|
||||
font-size: 0.875rem;
|
||||
}
|
113
rsconcept/frontend/src/styling/setup.css
Normal file
113
rsconcept/frontend/src/styling/setup.css
Normal file
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Module: Basic styling setup.
|
||||
*/
|
||||
|
||||
@import './constants.css';
|
||||
@import './layers.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
hanging-punctuation: first last;
|
||||
color-scheme: dark light;
|
||||
}
|
||||
|
||||
[data-color-scheme='dark'] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
[data-color-scheme='light'] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
/* Default scroll behavior */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:has(:target) {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: var(--font-main);
|
||||
|
||||
color: var(--cl-fg-100);
|
||||
border-color: var(--cl-bg-40);
|
||||
background-color: var(--cl-bg-100);
|
||||
|
||||
&.dark {
|
||||
color: var(--cd-fg-100);
|
||||
border-color: var(--cd-bg-40);
|
||||
background-color: var(--cd-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: transparent;
|
||||
.dark & {
|
||||
outline-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--cl-prim-bg-60);
|
||||
.dark & {
|
||||
background: var(--cd-prim-bg-60);
|
||||
}
|
||||
tr :hover& {
|
||||
background: var(--cl-red-bg-100);
|
||||
.dark & {
|
||||
background: var(--cd-red-bg-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wrapping headers */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* Limit text lines and setup wrapping */
|
||||
p,
|
||||
li {
|
||||
max-width: 90ch;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
h1 {
|
||||
@apply text-lg font-semibold text-center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply font-semibold text-center;
|
||||
}
|
||||
|
||||
b {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
.border {
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.shadow-border {
|
||||
@apply shadow-sm shadow-[var(--cl-bg-40)] dark:shadow-[var(--cd-bg-40)];
|
||||
}
|
||||
}
|
177
rsconcept/frontend/src/styling/styles.css
Normal file
177
rsconcept/frontend/src/styling/styles.css
Normal file
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* Module: Custom styling.
|
||||
*/
|
||||
@import './constants.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.font-controls {
|
||||
font-family: var(--font-ui);
|
||||
font-weight: 600;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
.font-math {
|
||||
font-family: var(--font-math);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.clr-modal-backdrop {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
:is(
|
||||
.clr-border,
|
||||
.border,
|
||||
.border-x,
|
||||
.border-y,
|
||||
.border-b,
|
||||
.border-t,
|
||||
.border-l,
|
||||
.border-r,
|
||||
.border-2,
|
||||
.border-x-2,
|
||||
.border-y-2,
|
||||
.border-b-2,
|
||||
.border-t-2,
|
||||
.border-l-2,
|
||||
.border-r-2,
|
||||
.divide-x,
|
||||
.divide-y,
|
||||
.divide-x-2,
|
||||
.divide-y-2
|
||||
) {
|
||||
border-color: var(--cl-bg-40);
|
||||
@apply divide-inherit;
|
||||
.dark & {
|
||||
border-color: var(--cd-bg-40);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-app, .clr-footer, .clr-modal-backdrop, .clr-btn-nav, .clr-input:disabled) {
|
||||
background-color: var(--cl-bg-100);
|
||||
.dark & {
|
||||
background-color: var(--cd-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-input) {
|
||||
background-color: var(--cl-bg-120);
|
||||
.dark & {
|
||||
background-color: var(--cd-bg-120);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-controls, .clr-tab, .clr-btn-default) {
|
||||
background-color: var(--cl-bg-80);
|
||||
.dark & {
|
||||
background-color: var(--cd-bg-80);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-primary, .clr-btn-primary:hover, .clr-btn-primary:focus) {
|
||||
@apply transition;
|
||||
color: var(--cl-prim-fg-100);
|
||||
background-color: var(--cl-prim-bg-100);
|
||||
.dark & {
|
||||
color: var(--cd-prim-fg-100);
|
||||
background-color: var(--cd-prim-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-selected, .clr-btn-primary) {
|
||||
color: var(--cl-fg-100);
|
||||
background-color: var(--cl-prim-bg-80);
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
background-color: var(--cd-prim-bg-80);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-disabled, .clr-btn-default, .clr-btn-primary):disabled {
|
||||
@apply transition;
|
||||
color: var(--cl-fg-80);
|
||||
background-color: var(--cl-bg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-80);
|
||||
background-color: var(--cd-bg-60);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-hover, .clr-tab, .clr-btn-nav, .clr-btn-default):hover:not(:disabled) {
|
||||
@apply transition;
|
||||
color: var(--cl-fg-100);
|
||||
background-color: var(--cl-prim-bg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
background-color: var(--cd-prim-bg-60);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-outline, .clr-btn-primary):focus {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: var(--cl-prim-bg-100);
|
||||
.dark & {
|
||||
outline-color: var(--cd-prim-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-text-primary, .clr-text-url) {
|
||||
color: var(--cl-prim-fg-80);
|
||||
.dark & {
|
||||
color: var(--cd-prim-fg-80);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-footer {
|
||||
color: var(--cl-fg-60);
|
||||
.dark & {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
|
||||
:is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) {
|
||||
color: var(--cl-fg-80);
|
||||
&:disabled {
|
||||
color: var(--cl-fg-60);
|
||||
}
|
||||
.dark & {
|
||||
color: var(--cd-fg-80);
|
||||
&:disabled {
|
||||
color: var(--cd-fg-60);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clr-warning {
|
||||
background-color: var(--cl-red-bg-100);
|
||||
.dark & {
|
||||
background-color: var(--cd-red-bg-100);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-text-default {
|
||||
color: var(--cl-fg-100);
|
||||
.dark & {
|
||||
color: var(--cd-fg-100);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-text-warning {
|
||||
color: var(--cl-red-fg-100);
|
||||
.dark & {
|
||||
color: var(--cd-red-fg-100);
|
||||
}
|
||||
}
|
||||
|
||||
.clr-text-success {
|
||||
color: var(--cl-green-fg-100);
|
||||
.dark & {
|
||||
color: var(--cd-green-fg-100);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import { IEntityReference, ISyntacticReference } from '@/models/language';
|
|||
import { parseGrammemes } from '@/models/languageAPI';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
|
||||
import { colorFgGrammeme, IColorTheme } from './color';
|
||||
import { colorFgGrammeme, IColorTheme } from '../styling/color';
|
||||
import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels';
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue
Block a user