UI improvements
Some checks are pending
Frontend CI / build (18.x) (push) Waiting to run

This commit is contained in:
IRBorisov 2024-06-18 15:06:52 +03:00
parent 5068d4952e
commit c9b7e45c85
9 changed files with 68 additions and 23 deletions

View File

@ -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]

View File

@ -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,

View File

@ -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';

View File

@ -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>
); );
} }

View File

@ -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>

View File

@ -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>
); );
} }

View File

@ -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

View File

@ -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

View File

@ -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/>открытой схемой'
}; };
/** /**