F: Improve schema picker filtering
This commit is contained in:
parent
9199518b25
commit
dc9609d347
|
@ -1,13 +1,21 @@
|
|||
import { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||
import SearchBar from '@/components/ui/SearchBar';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
|
||||
import { matchLibraryItem } from '@/models/libraryAPI';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { IconClose, IconFolderTree } from '../Icons';
|
||||
import { CProps } from '../props';
|
||||
import Dropdown from '../ui/Dropdown';
|
||||
import FlexColumn from '../ui/FlexColumn';
|
||||
import MiniButton from '../ui/MiniButton';
|
||||
import SelectLocation from './SelectLocation';
|
||||
|
||||
interface PickSchemaProps {
|
||||
id?: string;
|
||||
|
@ -35,18 +43,27 @@ function PickSchema({
|
|||
}: PickSchemaProps) {
|
||||
const intl = useIntl();
|
||||
const { colors } = useConceptOptions();
|
||||
const { folders } = useLibrary();
|
||||
|
||||
const [filterText, setFilterText] = useState(initialFilter);
|
||||
const [filterLocation, setFilterLocation] = useState('');
|
||||
const [filtered, setFiltered] = useState<ILibraryItem[]>([]);
|
||||
const baseFiltered = useMemo(
|
||||
() => items.filter(item => item.item_type === itemType && (!baseFilter || baseFilter(item))),
|
||||
[items, itemType, baseFilter]
|
||||
);
|
||||
|
||||
const locationMenu = useDropdown();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const newFiltered = baseFiltered.filter(item => matchLibraryItem(item, filterText));
|
||||
let newFiltered = baseFiltered.filter(item => matchLibraryItem(item, filterText));
|
||||
if (filterLocation.length > 0) {
|
||||
newFiltered = newFiltered.filter(
|
||||
item => item.location === filterLocation || item.location.startsWith(`${filterLocation}/`)
|
||||
);
|
||||
}
|
||||
setFiltered(newFiltered);
|
||||
}, [filterText]);
|
||||
}, [filterText, filterLocation]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
|
@ -92,15 +109,51 @@ function PickSchema({
|
|||
[value, colors]
|
||||
);
|
||||
|
||||
const handleLocationClick = useCallback(
|
||||
(event: CProps.EventMouse, newValue: string) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
locationMenu.hide();
|
||||
setFilterLocation(newValue);
|
||||
},
|
||||
[locationMenu]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='border divide-y'>
|
||||
<SearchBar
|
||||
id={id ? `${id}__search` : undefined}
|
||||
className='clr-input'
|
||||
noBorder
|
||||
value={filterText}
|
||||
onChange={newValue => setFilterText(newValue)}
|
||||
/>
|
||||
<div className='flex justify-between clr-input items-center pr-1'>
|
||||
<SearchBar
|
||||
id={id ? `${id}__search` : undefined}
|
||||
className='clr-input w-full'
|
||||
noBorder
|
||||
value={filterText}
|
||||
onChange={newValue => setFilterText(newValue)}
|
||||
/>
|
||||
<div ref={locationMenu.ref}>
|
||||
<MiniButton
|
||||
icon={<IconFolderTree size='1.25rem' className={!!filterLocation ? 'icon-green' : 'icon-primary'} />}
|
||||
title='Фильтр по расположению'
|
||||
className='mt-1'
|
||||
onClick={() => locationMenu.toggle()}
|
||||
/>
|
||||
<Dropdown isOpen={locationMenu.isOpen} stretchLeft className='w-[20rem] h-[12.5rem] z-modalTooltip mt-0'>
|
||||
<SelectLocation
|
||||
folderTree={folders}
|
||||
value={filterLocation}
|
||||
prefix={prefixes.folders_list}
|
||||
dense
|
||||
onClick={(event, target) => handleLocationClick(event, target.getPath())}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{filterLocation.length > 0 ? (
|
||||
<MiniButton
|
||||
icon={<IconClose size='1.25rem' className='icon-red' />}
|
||||
title='Сбросить фильтр'
|
||||
onClick={() => setFilterLocation('')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<DataTable
|
||||
id={id}
|
||||
rows={rows}
|
||||
|
|
|
@ -15,13 +15,21 @@ import SelectLocation from './SelectLocation';
|
|||
|
||||
interface SelectLocationContextProps extends CProps.Styling {
|
||||
value: string;
|
||||
title?: string;
|
||||
folderTree: FolderTree;
|
||||
stretchTop?: boolean;
|
||||
|
||||
onChange: (newValue: string) => void;
|
||||
}
|
||||
|
||||
function SelectLocationContext({ value, folderTree, onChange, className, style }: SelectLocationContextProps) {
|
||||
function SelectLocationContext({
|
||||
value,
|
||||
title = 'Проводник...',
|
||||
folderTree,
|
||||
onChange,
|
||||
className,
|
||||
style
|
||||
}: SelectLocationContextProps) {
|
||||
const menu = useDropdown();
|
||||
|
||||
const handleClick = useCallback(
|
||||
|
@ -37,7 +45,7 @@ function SelectLocationContext({ value, folderTree, onChange, className, style }
|
|||
return (
|
||||
<div ref={menu.ref} className='h-full text-right self-start mt-[-0.25rem] ml-[-1.5rem]'>
|
||||
<MiniButton
|
||||
title='Проводник...'
|
||||
title={title}
|
||||
hideTitle={menu.isOpen}
|
||||
icon={<IconFolderTree size='1.25rem' className='icon-green' />}
|
||||
onClick={() => menu.toggle()}
|
||||
|
|
|
@ -63,7 +63,7 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
|
|||
itemType={LibraryItemType.RSFORM}
|
||||
value={selected} // prettier: split-line
|
||||
onSelectValue={handleSelectLocation}
|
||||
rows={8}
|
||||
rows={14}
|
||||
baseFilter={baseFilter}
|
||||
/>
|
||||
</Modal>
|
||||
|
|
|
@ -58,7 +58,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
|
|||
const schemaPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<TabSchema selected={donorID} setSelected={setDonorID} />
|
||||
<TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} />
|
||||
</TabPanel>
|
||||
),
|
||||
[donorID]
|
||||
|
|
|
@ -7,15 +7,19 @@ import TextInput from '@/components/ui/TextInput';
|
|||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { LibraryItemID, LibraryItemType } from '@/models/library';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { sortItemsForInlineSynthesis } from '@/models/rsformAPI';
|
||||
|
||||
interface TabSchemaProps {
|
||||
selected?: LibraryItemID;
|
||||
setSelected: (newValue: LibraryItemID) => void;
|
||||
receiver: IRSForm;
|
||||
}
|
||||
|
||||
function TabSchema({ selected, setSelected }: TabSchemaProps) {
|
||||
function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) {
|
||||
const library = useLibrary();
|
||||
const selectedInfo = useMemo(() => library.items.find(item => item.id === selected), [selected, library.items]);
|
||||
const sortedItems = useMemo(() => sortItemsForInlineSynthesis(receiver, library.items), [receiver, library.items]);
|
||||
|
||||
return (
|
||||
<AnimateFade className='flex flex-col'>
|
||||
|
@ -33,7 +37,7 @@ function TabSchema({ selected, setSelected }: TabSchemaProps) {
|
|||
</div>
|
||||
<PickSchema
|
||||
id='dlg_schema_picker' // prettier: split lines
|
||||
items={library.items}
|
||||
items={sortedItems}
|
||||
itemType={LibraryItemType.RSFORM}
|
||||
rows={14}
|
||||
value={selected}
|
||||
|
|
|
@ -32,6 +32,8 @@ export enum LocationHead {
|
|||
LIBRARY = '/L'
|
||||
}
|
||||
|
||||
export const BASIC_SCHEMAS = '/L/Базовые';
|
||||
|
||||
/**
|
||||
* Represents {@link LibraryItem} identifier type.
|
||||
*/
|
||||
|
|
|
@ -17,7 +17,7 @@ const LOCATION_REGEXP = /^\/[PLUS]((\/[!\d\p{L}]([!\d\p{L}\- ]*[!\d\p{L}])?)*)?$
|
|||
*/
|
||||
export function matchLibraryItem(target: ILibraryItem, query: string): boolean {
|
||||
const matcher = new TextMatcher(query);
|
||||
return matcher.test(target.alias) || matcher.test(target.title);
|
||||
return matcher.test(target.alias) || matcher.test(target.title) || matcher.test(target.comment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,9 +26,6 @@ export function matchOperation(target: IOperation, query: string): boolean {
|
|||
|
||||
/**
|
||||
* Sorts library items relevant for the specified {@link IOperationSchema}.
|
||||
*
|
||||
* @param oss - The {@link IOperationSchema} to be sorted.
|
||||
* @param items - The items to be sorted.
|
||||
*/
|
||||
export function sortItemsForOSS(oss: IOperationSchema, items: ILibraryItem[]): ILibraryItem[] {
|
||||
const result = items.filter(item => item.location === oss.location);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import { TextMatcher } from '@/utils/utils';
|
||||
|
||||
import { BASIC_SCHEMAS, ILibraryItem } from './library';
|
||||
import { CstMatchMode } from './miscellaneous';
|
||||
import {
|
||||
CATEGORY_CST_TYPE,
|
||||
|
@ -308,3 +309,31 @@ export function generateAlias(type: CstType, schema: IRSForm, takenAliases: stri
|
|||
}
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts library items relevant for InlineSynthesis with specified {@link IRSForm}.
|
||||
*/
|
||||
export function sortItemsForInlineSynthesis(receiver: IRSForm, items: ILibraryItem[]): ILibraryItem[] {
|
||||
const result = items.filter(item => item.location === receiver.location);
|
||||
for (const item of items) {
|
||||
if (item.visible && item.owner === item.owner && !result.includes(item)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
for (const item of items) {
|
||||
if (!result.includes(item) && item.location.startsWith(BASIC_SCHEMAS)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
for (const item of items) {
|
||||
if (item.visible && !result.includes(item)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
for (const item of items) {
|
||||
if (!result.includes(item)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,21 @@ function ViewSideLocation({
|
|||
const { user } = useAuth();
|
||||
const { items } = useLibrary();
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const canRename = useMemo(() => {
|
||||
if (active.length <= 3 || !user) {
|
||||
return false;
|
||||
}
|
||||
if (user.is_staff) {
|
||||
return true;
|
||||
}
|
||||
const owned = items.filter(item => item.owner == user.id);
|
||||
const located = owned.filter(item => item.location == active || item.location.startsWith(`${active}/`));
|
||||
return located.length !== 0;
|
||||
}, [active, user, items]);
|
||||
|
||||
const animations = useMemo(() => animateSideMinWidth(windowSize.isSmall ? '10rem' : '15rem'), [windowSize]);
|
||||
|
||||
const handleClickFolder = useCallback(
|
||||
(event: CProps.EventMouse, target: FolderNode) => {
|
||||
event.preventDefault();
|
||||
|
@ -56,20 +71,6 @@ function ViewSideLocation({
|
|||
[setActive]
|
||||
);
|
||||
|
||||
const canRename = useMemo(() => {
|
||||
if (active.length <= 3 || !user) {
|
||||
return false;
|
||||
}
|
||||
if (user.is_staff) {
|
||||
return true;
|
||||
}
|
||||
const owned = items.filter(item => item.owner == user.id);
|
||||
const located = owned.filter(item => item.location == active || item.location.startsWith(`${active}/`));
|
||||
return located.length !== 0;
|
||||
}, [active, user, items]);
|
||||
|
||||
const animations = useMemo(() => animateSideMinWidth(windowSize.isSmall ? '10rem' : '15rem'), [windowSize]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={clsx('max-w-[10rem] sm:max-w-[15rem]', 'flex flex-col', 'text:xs sm:text-sm', 'select-none')}
|
||||
|
|
|
@ -111,7 +111,7 @@ function NodeContextMenu({
|
|||
text='Редактировать'
|
||||
title='Редактировать операцию'
|
||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||
disabled={controller.isProcessing}
|
||||
disabled={!controller.isMutable || controller.isProcessing}
|
||||
onClick={handleEditOperation}
|
||||
/>
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@
|
|||
.clr-btn-nav,
|
||||
.clr-btn-clear {
|
||||
color: var(--cl-fg-80);
|
||||
background-color: transparent;
|
||||
&:disabled {
|
||||
color: var(--cl-fg-60);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user