This commit is contained in:
parent
5068d4952e
commit
c9b7e45c85
13
TODO.txt
13
TODO.txt
|
@ -2,25 +2,26 @@
|
||||||
For more specific TODOs see comments in code
|
For more specific TODOs see comments in code
|
||||||
|
|
||||||
[Functionality - PROGRESS]
|
[Functionality - PROGRESS]
|
||||||
- Landing page
|
|
||||||
- Home page (user specific)
|
|
||||||
|
|
||||||
- Operational synthesis schema as LibraryItem ?
|
- Operational synthesis schema as LibraryItem ?
|
||||||
|
|
||||||
- Draggable rows in constituents table
|
|
||||||
- Clickable IDs in RSEditor tooltips
|
- Clickable IDs in RSEditor tooltips
|
||||||
|
|
||||||
- Library organization, search and exploration. Consider new user experience
|
- Library organization, search and exploration. Consider new user experience
|
||||||
- Private projects and permissions. Consider cooperative editing
|
- Private projects and permissions. Consider cooperative editing
|
||||||
- Rework access setup: project-based, user-based, enable sharing. Prevent enumerating access to private schemas by default
|
|
||||||
|
|
||||||
|
|
||||||
[Functionality - PENDING]
|
[Functionality - PENDING]
|
||||||
|
- Search functionality for manuals
|
||||||
- User notifications on edit - consider spam prevention and change aggregation
|
- User notifications on edit - consider spam prevention and change aggregation
|
||||||
- Static analyzer for RSForm
|
- Static analyzer for RSForm
|
||||||
- Content based search in Library
|
- Content based search in Library
|
||||||
- User profile: Settings + settings persistency
|
- User profile: Settings + settings persistency
|
||||||
|
|
||||||
|
- Landing page
|
||||||
|
- Home page (user specific)
|
||||||
|
|
||||||
|
- Draggable rows in constituents table
|
||||||
|
|
||||||
- Export PDF (Items list, Graph)
|
- Export PDF (Items list, Graph)
|
||||||
- ARIA (accessibility considerations) - for now machine reading not supported
|
- ARIA (accessibility considerations) - for now machine reading not supported
|
||||||
- Internationalization - at least english version. Consider react.intl
|
- Internationalization - at least english version. Consider react.intl
|
||||||
|
@ -42,6 +43,8 @@ For more specific TODOs see comments in code
|
||||||
[Security]
|
[Security]
|
||||||
- password-reset leaks info of email being used
|
- password-reset leaks info of email being used
|
||||||
- improve nginx config. Consider DDOS and other types of attacks on infrastructure
|
- improve nginx config. Consider DDOS and other types of attacks on infrastructure
|
||||||
|
- recaptcha for create user and rest password
|
||||||
|
https://yandex.cloud/ru/docs/smartcaptcha
|
||||||
|
|
||||||
|
|
||||||
[Research]
|
[Research]
|
||||||
|
|
|
@ -24,6 +24,7 @@ function NavigationButton({ icon, title, titleHtml, hideTitle, onClick, text }:
|
||||||
'flex items-center gap-1',
|
'flex items-center gap-1',
|
||||||
'clr-btn-nav',
|
'clr-btn-nav',
|
||||||
'rounded-xl',
|
'rounded-xl',
|
||||||
|
'transition duration-500',
|
||||||
'font-controls whitespace-nowrap',
|
'font-controls whitespace-nowrap',
|
||||||
{
|
{
|
||||||
'px-2': text,
|
'px-2': text,
|
||||||
|
|
|
@ -18,7 +18,8 @@ export { BiCog as IconSettings } from 'react-icons/bi';
|
||||||
export { TbEye as IconShow } from 'react-icons/tb';
|
export { TbEye as IconShow } from 'react-icons/tb';
|
||||||
export { TbEyeX as IconHide } from 'react-icons/tb';
|
export { TbEyeX as IconHide } from 'react-icons/tb';
|
||||||
export { BiShareAlt as IconShare } from 'react-icons/bi';
|
export { BiShareAlt as IconShare } from 'react-icons/bi';
|
||||||
export { BiFilterAlt as IconFilter } from 'react-icons/bi';
|
export { LuFilter as IconFilter } from 'react-icons/lu';
|
||||||
|
export { LuFilterX as IconFilterReset } from 'react-icons/lu';
|
||||||
export {BiDownArrowCircle as IconOpenList } from 'react-icons/bi';
|
export {BiDownArrowCircle as IconOpenList } from 'react-icons/bi';
|
||||||
export { LuAlertTriangle as IconAlert } from 'react-icons/lu';
|
export { LuAlertTriangle as IconAlert } from 'react-icons/lu';
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,12 @@ function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstPr
|
||||||
schema={schema}
|
schema={schema}
|
||||||
prefix={prefixes.cst_dependant_list}
|
prefix={prefixes.cst_dependant_list}
|
||||||
/>
|
/>
|
||||||
<Checkbox label='Удалить зависимые конституенты' value={expandOut} setValue={value => setExpandOut(value)} />
|
<Checkbox
|
||||||
|
label='Удалить зависимые конституенты'
|
||||||
|
className='my-2'
|
||||||
|
value={expandOut}
|
||||||
|
setValue={value => setExpandOut(value)}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,18 @@ function LibraryPage() {
|
||||||
[head, path, query, isEditor, isOwned, isSubscribed, isVisible, user]
|
[head, path, query, isEditor, isOwned, isSubscribed, isVisible, user]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasCustomFilter = useMemo(
|
||||||
|
() =>
|
||||||
|
!!filter.path ||
|
||||||
|
!!filter.query ||
|
||||||
|
filter.head !== undefined ||
|
||||||
|
filter.isEditor !== undefined ||
|
||||||
|
filter.isOwned !== undefined ||
|
||||||
|
filter.isSubscribed !== undefined ||
|
||||||
|
filter.isVisible !== true,
|
||||||
|
[filter]
|
||||||
|
);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setItems(library.applyFilter(filter));
|
setItems(library.applyFilter(filter));
|
||||||
}, [library, filter, filter.query]);
|
}, [library, filter, filter.query]);
|
||||||
|
@ -81,14 +93,15 @@ function LibraryPage() {
|
||||||
hasNoData={library.items.length === 0}
|
hasNoData={library.items.length === 0}
|
||||||
>
|
>
|
||||||
<SearchPanel
|
<SearchPanel
|
||||||
|
total={library.items.length ?? 0}
|
||||||
|
filtered={items.length}
|
||||||
|
hasCustomFilter={hasCustomFilter}
|
||||||
query={query}
|
query={query}
|
||||||
setQuery={setQuery}
|
setQuery={setQuery}
|
||||||
path={path}
|
path={path}
|
||||||
setPath={setPath}
|
setPath={setPath}
|
||||||
head={head}
|
head={head}
|
||||||
setHead={setHead}
|
setHead={setHead}
|
||||||
total={library.items.length ?? 0}
|
|
||||||
filtered={items.length}
|
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
isOwned={isOwned}
|
isOwned={isOwned}
|
||||||
toggleOwned={toggleOwned}
|
toggleOwned={toggleOwned}
|
||||||
|
@ -97,6 +110,7 @@ function LibraryPage() {
|
||||||
toggleSubscribed={toggleSubscribed}
|
toggleSubscribed={toggleSubscribed}
|
||||||
isEditor={isEditor}
|
isEditor={isEditor}
|
||||||
toggleEditor={toggleEditor}
|
toggleEditor={toggleEditor}
|
||||||
|
resetFilter={resetFilter}
|
||||||
/>
|
/>
|
||||||
{view}
|
{view}
|
||||||
</DataLoader>
|
</DataLoader>
|
||||||
|
|
|
@ -4,11 +4,12 @@ import clsx from 'clsx';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { LocationIcon, SubscribeIcon, VisibilityIcon } from '@/components/DomainIcons';
|
import { LocationIcon, SubscribeIcon, VisibilityIcon } from '@/components/DomainIcons';
|
||||||
import { IconEditor, IconFolder, IconOwner } from '@/components/Icons';
|
import { IconEditor, IconFilterReset, IconFolder, IconOwner } from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import Dropdown from '@/components/ui/Dropdown';
|
import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import SearchBar from '@/components/ui/SearchBar';
|
import SearchBar from '@/components/ui/SearchBar';
|
||||||
import SelectorButton from '@/components/ui/SelectorButton';
|
import SelectorButton from '@/components/ui/SelectorButton';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
@ -22,6 +23,7 @@ import { tripleToggleColor } from '@/utils/utils';
|
||||||
interface SearchPanelProps {
|
interface SearchPanelProps {
|
||||||
total: number;
|
total: number;
|
||||||
filtered: number;
|
filtered: number;
|
||||||
|
hasCustomFilter: boolean;
|
||||||
|
|
||||||
query: string;
|
query: string;
|
||||||
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
@ -38,11 +40,14 @@ interface SearchPanelProps {
|
||||||
toggleSubscribed: () => void;
|
toggleSubscribed: () => void;
|
||||||
isEditor: boolean | undefined;
|
isEditor: boolean | undefined;
|
||||||
toggleEditor: () => void;
|
toggleEditor: () => void;
|
||||||
|
resetFilter: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchPanel({
|
function SearchPanel({
|
||||||
total,
|
total,
|
||||||
filtered,
|
filtered,
|
||||||
|
hasCustomFilter,
|
||||||
|
|
||||||
query,
|
query,
|
||||||
setQuery,
|
setQuery,
|
||||||
path,
|
path,
|
||||||
|
@ -57,7 +62,8 @@ function SearchPanel({
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
toggleSubscribed,
|
toggleSubscribed,
|
||||||
isEditor,
|
isEditor,
|
||||||
toggleEditor
|
toggleEditor,
|
||||||
|
resetFilter
|
||||||
}: SearchPanelProps) {
|
}: SearchPanelProps) {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const headMenu = useDropdown();
|
const headMenu = useDropdown();
|
||||||
|
@ -109,6 +115,13 @@ function SearchPanel({
|
||||||
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
|
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
|
||||||
onClick={toggleEditor}
|
onClick={toggleEditor}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MiniButton
|
||||||
|
title='Сбросить фильтры'
|
||||||
|
icon={<IconFilterReset size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={resetFilter}
|
||||||
|
disabled={!hasCustomFilter}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -169,18 +182,19 @@ function SearchPanel({
|
||||||
placeholder='Путь'
|
placeholder='Путь'
|
||||||
noIcon
|
noIcon
|
||||||
noBorder
|
noBorder
|
||||||
className='min-w-[5rem]'
|
className='min-w-[4.5rem] sm:min-w-[5rem]'
|
||||||
value={path}
|
value={path}
|
||||||
onChange={setPath}
|
onChange={setPath}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Overlay position='top-[-0.75rem] right-0'>
|
||||||
<BadgeHelp
|
<BadgeHelp
|
||||||
topic={HelpTopic.UI_LIBRARY}
|
topic={HelpTopic.UI_LIBRARY}
|
||||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'text-sm')}
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'text-sm')}
|
||||||
offset={5}
|
offset={5}
|
||||||
place='right-start'
|
place='right-start'
|
||||||
/>
|
/>
|
||||||
|
</Overlay>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@ import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
|
import { AccessPolicy } from '@/models/library';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
import { UserLevel } from '@/models/user';
|
import { UserLevel } from '@/models/user';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { prepareTooltip } from '@/utils/labels';
|
import { prepareTooltip, tooltips } from '@/utils/labels';
|
||||||
|
|
||||||
import { useRSEdit } from '../RSEditContext';
|
import { useRSEdit } from '../RSEditContext';
|
||||||
|
|
||||||
|
@ -38,9 +39,10 @@ function RSFormToolbar({ modified, anonymous, subscribed, onSubmit, onDestroy }:
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Поделиться схемой'
|
titleHtml={tooltips.shareItem(controller.schema?.access_policy)}
|
||||||
icon={<IconShare size='1.25rem' className='icon-primary' />}
|
icon={<IconShare size='1.25rem' className='icon-primary' />}
|
||||||
onClick={controller.share}
|
onClick={controller.share}
|
||||||
|
disabled={controller.schema?.access_policy !== AccessPolicy.PUBLIC}
|
||||||
/>
|
/>
|
||||||
{!anonymous ? (
|
{!anonymous ? (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
|
|
|
@ -32,8 +32,9 @@ import { useAuth } from '@/context/AuthContext';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
import { useRSForm } from '@/context/RSFormContext';
|
import { useRSForm } from '@/context/RSFormContext';
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
|
import { AccessPolicy } from '@/models/library';
|
||||||
import { UserLevel } from '@/models/user';
|
import { UserLevel } from '@/models/user';
|
||||||
import { describeAccessMode, labelAccessMode } from '@/utils/labels';
|
import { describeAccessMode, labelAccessMode, tooltips } from '@/utils/labels';
|
||||||
|
|
||||||
import { useRSEdit } from './RSEditContext';
|
import { useRSEdit } from './RSEditContext';
|
||||||
|
|
||||||
|
@ -138,8 +139,10 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
||||||
<Dropdown isOpen={schemaMenu.isOpen}>
|
<Dropdown isOpen={schemaMenu.isOpen}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Поделиться'
|
text='Поделиться'
|
||||||
|
titleHtml={tooltips.shareItem(controller.schema?.access_policy)}
|
||||||
icon={<IconShare size='1rem' className='icon-primary' />}
|
icon={<IconShare size='1rem' className='icon-primary' />}
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
|
disabled={controller.schema?.access_policy !== AccessPolicy.PUBLIC}
|
||||||
/>
|
/>
|
||||||
{user ? (
|
{user ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
|
|
|
@ -909,7 +909,9 @@ export const errors = {
|
||||||
* UI tooltip descriptors.
|
* UI tooltip descriptors.
|
||||||
*/
|
*/
|
||||||
export const tooltips = {
|
export const tooltips = {
|
||||||
unsaved: 'Сохраните или отмените изменения'
|
unsaved: 'Сохраните или отмените изменения',
|
||||||
|
shareItem: (policy?: AccessPolicy) =>
|
||||||
|
policy === AccessPolicy.PUBLIC ? 'Поделиться схемой' : 'Поделиться можно только <br/>открытой схемой'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue
Block a user