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