mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
This commit is contained in:
parent
3acf0600e4
commit
31c32d5983
|
@ -7,11 +7,12 @@ import { HelpTopic } from '../models/helpTopic';
|
|||
|
||||
export function HelpRSLang() {
|
||||
const windowSize = useWindowSize();
|
||||
const isSmall = windowSize.isSmall;
|
||||
|
||||
const videoHeight = (() => {
|
||||
const viewH = windowSize.height ?? 0;
|
||||
const viewW = windowSize.width ?? 0;
|
||||
const availableWidth = viewW - (windowSize.isSmall ? 35 : 310);
|
||||
const availableWidth = viewW - (isSmall ? 35 : 310);
|
||||
return Math.min(1080, Math.max(viewH - 450, 300), Math.floor((availableWidth * 9) / 16));
|
||||
})();
|
||||
|
||||
|
|
|
@ -1,43 +1,48 @@
|
|||
'use client';
|
||||
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useLabelUser } from '@/features/users';
|
||||
|
||||
import { FlexColumn } from '@/components/Container';
|
||||
import { MiniButton, TextURL } from '@/components/Control';
|
||||
import { createColumnHelper, DataTable, type IConditionalStyle, type VisibilityState } from '@/components/DataTable';
|
||||
import { IconFolderTree } from '@/components/Icons';
|
||||
import { TextURL } from '@/components/Control';
|
||||
import { DataTable, type IConditionalStyle, type VisibilityState } from '@/components/DataTable';
|
||||
import { useWindowSize } from '@/hooks/useWindowSize';
|
||||
import { useFitHeight } from '@/stores/appLayout';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { APP_COLORS } from '@/styling/colors';
|
||||
|
||||
import { type ILibraryItem, LibraryItemType } from '../../backend/types';
|
||||
import { BadgeLocation } from '../../components/BadgeLocation';
|
||||
import { useLibrarySearchStore } from '../../stores/librarySearch';
|
||||
|
||||
import { useLibraryColumns } from './useLibraryColumns';
|
||||
|
||||
interface TableLibraryItemsProps {
|
||||
items: ILibraryItem[];
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||
|
||||
export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
||||
const router = useConceptNavigation();
|
||||
const intl = useIntl();
|
||||
const getUserLabel = useLabelUser();
|
||||
const { isSmall } = useWindowSize();
|
||||
|
||||
const folderMode = useLibrarySearchStore(state => state.folderMode);
|
||||
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
|
||||
const resetFilter = useLibrarySearchStore(state => state.resetFilter);
|
||||
|
||||
const itemsPerPage = usePreferencesStore(state => state.libraryPagination);
|
||||
const setItemsPerPage = usePreferencesStore(state => state.setLibraryPagination);
|
||||
|
||||
const columns = useLibraryColumns();
|
||||
const columnVisibility: VisibilityState = { owner: !isSmall };
|
||||
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
|
||||
{
|
||||
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
|
||||
style: {
|
||||
color: APP_COLORS.fgGreen
|
||||
}
|
||||
}
|
||||
];
|
||||
const tableHeight = useFitHeight('2.2rem');
|
||||
|
||||
function handleOpenItem(item: ILibraryItem, event: React.MouseEvent<Element>) {
|
||||
const selection = window.getSelection();
|
||||
if (!!selection && selection.toString().length > 0) {
|
||||
|
@ -50,108 +55,6 @@ export function TableLibraryItems({ items }: TableLibraryItemsProps) {
|
|||
}
|
||||
}
|
||||
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setColumnVisibility({
|
||||
owner: !windowSize.isSmall
|
||||
});
|
||||
}, [windowSize]);
|
||||
|
||||
function handleToggleFolder(event: React.MouseEvent<Element>) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleFolderMode();
|
||||
}
|
||||
|
||||
const columns = [
|
||||
...(folderMode
|
||||
? []
|
||||
: [
|
||||
columnHelper.accessor('location', {
|
||||
id: 'location',
|
||||
header: () => (
|
||||
<MiniButton
|
||||
noPadding
|
||||
noHover
|
||||
className='pl-2 max-h-[1rem] translate-y-[-0.125rem]'
|
||||
onClick={handleToggleFolder}
|
||||
titleHtml='Переключение в режим Проводник'
|
||||
icon={<IconFolderTree size='1.25rem' className='clr-text-controls' />}
|
||||
/>
|
||||
),
|
||||
size: 50,
|
||||
minSize: 50,
|
||||
maxSize: 50,
|
||||
enableSorting: true,
|
||||
cell: props => <BadgeLocation location={props.getValue()} />,
|
||||
sortingFn: 'text'
|
||||
})
|
||||
]),
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150,
|
||||
enableSorting: true,
|
||||
cell: props => <div className='min-w-[5rem]'>{props.getValue()}</div>,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
maxSize: 1200,
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor(item => item.owner ?? 0, {
|
||||
id: 'owner',
|
||||
header: 'Владелец',
|
||||
size: 400,
|
||||
minSize: 100,
|
||||
maxSize: 400,
|
||||
cell: props => getUserLabel(props.getValue()),
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: windowSize.isSmall ? 'Дата' : 'Обновлена',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
...(!windowSize.isSmall && {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
enableSorting: true,
|
||||
sortingFn: 'datetime',
|
||||
sortDescFirst: true
|
||||
})
|
||||
];
|
||||
|
||||
const tableHeight = useFitHeight('2.2rem');
|
||||
|
||||
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
|
||||
{
|
||||
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
|
||||
style: {
|
||||
color: APP_COLORS.fgGreen
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
id='library_data'
|
||||
|
|
|
@ -26,7 +26,7 @@ interface ViewSideLocationProps {
|
|||
export function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocationProps) {
|
||||
const { user, isAnonymous } = useAuthSuspense();
|
||||
const { items } = useLibrary();
|
||||
const windowSize = useWindowSize();
|
||||
const { isSmall } = useWindowSize();
|
||||
|
||||
const location = useLibrarySearchStore(state => state.location);
|
||||
const setLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
|
@ -68,7 +68,7 @@ export function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocati
|
|||
transitionProperty: 'width, min-width, opacity',
|
||||
transitionDuration: `${PARAMETER.moveDuration}ms`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
minWidth: isVisible ? (windowSize.isSmall ? '10rem' : '15rem') : '0',
|
||||
minWidth: isVisible ? (isSmall ? '10rem' : '15rem') : '0',
|
||||
width: isVisible ? '100%' : '0',
|
||||
opacity: isVisible ? 1 : 0
|
||||
}}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { useLabelUser } from '@/features/users';
|
||||
|
||||
import { MiniButton } from '@/components/Control';
|
||||
import { createColumnHelper } from '@/components/DataTable';
|
||||
import { IconFolderTree } from '@/components/Icons';
|
||||
import { useWindowSize } from '@/hooks/useWindowSize';
|
||||
|
||||
import { type ILibraryItem } from '../../backend/types';
|
||||
import { BadgeLocation } from '../../components/BadgeLocation';
|
||||
import { useLibrarySearchStore } from '../../stores/librarySearch';
|
||||
|
||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||
|
||||
export function useLibraryColumns() {
|
||||
const { isSmall } = useWindowSize();
|
||||
const intl = useIntl();
|
||||
|
||||
const getUserLabel = useLabelUser();
|
||||
const folderMode = useLibrarySearchStore(state => state.folderMode);
|
||||
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
|
||||
|
||||
function handleToggleFolder(event: React.MouseEvent<Element>) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
toggleFolderMode();
|
||||
}
|
||||
|
||||
return [
|
||||
...(folderMode
|
||||
? []
|
||||
: [
|
||||
columnHelper.accessor('location', {
|
||||
id: 'location',
|
||||
header: () => (
|
||||
<MiniButton
|
||||
noPadding
|
||||
noHover
|
||||
className='pl-2 max-h-[1rem] translate-y-[-0.125rem]'
|
||||
onClick={handleToggleFolder}
|
||||
titleHtml='Переключение в режим Проводник'
|
||||
icon={<IconFolderTree size='1.25rem' className='clr-text-controls' />}
|
||||
/>
|
||||
),
|
||||
size: 50,
|
||||
minSize: 50,
|
||||
maxSize: 50,
|
||||
enableSorting: true,
|
||||
cell: props => <BadgeLocation location={props.getValue()} />,
|
||||
sortingFn: 'text'
|
||||
})
|
||||
]),
|
||||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150,
|
||||
enableSorting: true,
|
||||
cell: props => <div className='min-w-[5rem]'>{props.getValue()}</div>,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
maxSize: 1200,
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor(item => item.owner ?? 0, {
|
||||
id: 'owner',
|
||||
header: 'Владелец',
|
||||
size: 400,
|
||||
minSize: 100,
|
||||
maxSize: 400,
|
||||
cell: props => getUserLabel(props.getValue()),
|
||||
enableSorting: true,
|
||||
sortingFn: 'text'
|
||||
}),
|
||||
columnHelper.accessor('time_update', {
|
||||
id: 'time_update',
|
||||
header: isSmall ? 'Дата' : 'Обновлена',
|
||||
cell: props => (
|
||||
<div className='whitespace-nowrap'>
|
||||
{new Date(props.getValue()).toLocaleString(intl.locale, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
...(!isSmall && {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
enableSorting: true,
|
||||
sortingFn: 'datetime',
|
||||
sortDescFirst: true
|
||||
})
|
||||
];
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { TextURL } from '@/components/Control';
|
||||
|
@ -49,16 +48,6 @@ export function TableRSList({
|
|||
}: TableRSListProps) {
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setColumnVisibility({
|
||||
type: (windowSize.width ?? 0) >= COLUMN_TYPE_HIDE_THRESHOLD,
|
||||
convention: (windowSize.width ?? 0) >= COLUMN_CONVENTION_HIDE_THRESHOLD,
|
||||
definition: (windowSize.width ?? 0) >= COLUMN_DEFINITION_HIDE_THRESHOLD
|
||||
});
|
||||
}, [windowSize]);
|
||||
|
||||
function handleRowClicked(cst: IConstituenta, event: React.MouseEvent<Element>) {
|
||||
if (event.altKey) {
|
||||
event.preventDefault();
|
||||
|
@ -127,6 +116,12 @@ export function TableRSList({
|
|||
})
|
||||
];
|
||||
|
||||
const columnVisibility: VisibilityState = {
|
||||
type: (windowSize.width ?? 0) >= COLUMN_TYPE_HIDE_THRESHOLD,
|
||||
convention: (windowSize.width ?? 0) >= COLUMN_CONVENTION_HIDE_THRESHOLD,
|
||||
definition: (windowSize.width ?? 0) >= COLUMN_DEFINITION_HIDE_THRESHOLD
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
dense
|
||||
|
@ -140,7 +135,6 @@ export function TableRSList({
|
|||
onRowClicked={handleRowClicked}
|
||||
enableHiding
|
||||
columnVisibility={columnVisibility}
|
||||
onColumnVisibilityChange={setColumnVisibility}
|
||||
enableRowSelection={enableSelection}
|
||||
rowSelection={selected}
|
||||
onRowSelectionChange={setSelected}
|
||||
|
|
|
@ -21,7 +21,7 @@ interface ViewHiddenProps {
|
|||
}
|
||||
|
||||
export function ViewHidden({ items }: ViewHiddenProps) {
|
||||
const windowSize = useWindowSize();
|
||||
const { isSmall } = useWindowSize();
|
||||
const coloring = useTermGraphStore(state => state.coloring);
|
||||
const { navigateCst, setFocus, schema, selected, toggleSelect } = useRSEdit();
|
||||
|
||||
|
@ -29,7 +29,7 @@ export function ViewHidden({ items }: ViewHiddenProps) {
|
|||
const isFolded = useTermGraphStore(state => state.foldHidden);
|
||||
const toggleFolded = useTermGraphStore(state => state.toggleFoldHidden);
|
||||
const setActiveCst = useCstTooltipStore(state => state.setActiveCst);
|
||||
const hiddenHeight = useFitHeight(windowSize.isSmall ? '10.4rem + 2px' : '12.5rem + 2px');
|
||||
const hiddenHeight = useFitHeight(isSmall ? '10.4rem + 2px' : '12.5rem + 2px');
|
||||
|
||||
function handleClick(event: React.MouseEvent<Element>, cstID: number) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -9,11 +9,11 @@ import { PARAMETER, prefixes } from '@/utils/constants';
|
|||
|
||||
import { BadgeConstituenta } from '../../../components/BadgeConstituenta';
|
||||
import { describeConstituenta } from '../../../labels';
|
||||
import { type IConstituenta, type IRSForm } from '../../../models/rsform';
|
||||
import { matchConstituenta } from '../../../models/rsformAPI';
|
||||
import { DependencyMode, useCstSearchStore } from '../../../stores/cstSearch';
|
||||
import { type IConstituenta } from '../../../models/rsform';
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
||||
import { useFilteredItems } from './useFilteredItems';
|
||||
|
||||
const DESCRIPTION_MAX_SYMBOLS = 280;
|
||||
|
||||
interface TableSideConstituentsProps {
|
||||
|
@ -24,16 +24,8 @@ interface TableSideConstituentsProps {
|
|||
const columnHelper = createColumnHelper<IConstituenta>();
|
||||
|
||||
export function TableSideConstituents({ autoScroll = true, maxHeight }: TableSideConstituentsProps) {
|
||||
const { schema, activeCst, navigateCst } = useRSEdit();
|
||||
|
||||
const query = useCstSearchStore(state => state.query);
|
||||
const filterMatch = useCstSearchStore(state => state.match);
|
||||
const filterSource = useCstSearchStore(state => state.source);
|
||||
const includeInherited = useCstSearchStore(state => state.includeInherited);
|
||||
|
||||
const graphFiltered = activeCst ? applyGraphQuery(schema, activeCst.id, filterSource) : schema.items;
|
||||
const queryFiltered = query ? graphFiltered.filter(cst => matchConstituenta(cst, query, filterMatch)) : graphFiltered;
|
||||
const items = !includeInherited ? queryFiltered.filter(cst => !cst.is_inherited) : queryFiltered;
|
||||
const { activeCst, navigateCst } = useRSEdit();
|
||||
const items = useFilteredItems();
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeCst) {
|
||||
|
@ -125,34 +117,3 @@ export function TableSideConstituents({ autoScroll = true, maxHeight }: TableSid
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ====== Internals =========
|
||||
/**
|
||||
* Filter list of {@link ILibraryItem} to a given graph query.
|
||||
*/
|
||||
function applyGraphQuery(target: IRSForm, pivot: number, mode: DependencyMode): IConstituenta[] {
|
||||
if (mode === DependencyMode.ALL) {
|
||||
return target.items;
|
||||
}
|
||||
const ids = (() => {
|
||||
switch (mode) {
|
||||
case DependencyMode.OUTPUTS: {
|
||||
return target.graph.nodes.get(pivot)?.outputs;
|
||||
}
|
||||
case DependencyMode.INPUTS: {
|
||||
return target.graph.nodes.get(pivot)?.inputs;
|
||||
}
|
||||
case DependencyMode.EXPAND_OUTPUTS: {
|
||||
return target.graph.expandAllOutputs([pivot]);
|
||||
}
|
||||
case DependencyMode.EXPAND_INPUTS: {
|
||||
return target.graph.expandAllInputs([pivot]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (ids) {
|
||||
return target.items.filter(cst => ids.find(id => id === cst.id));
|
||||
} else {
|
||||
return target.items;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { type IConstituenta, type IRSForm } from '../../../models/rsform';
|
||||
import { matchConstituenta } from '../../../models/rsformAPI';
|
||||
import { DependencyMode, useCstSearchStore } from '../../../stores/cstSearch';
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
||||
export function useFilteredItems() {
|
||||
const { schema, activeCst } = useRSEdit();
|
||||
|
||||
const query = useCstSearchStore(state => state.query);
|
||||
const filterMatch = useCstSearchStore(state => state.match);
|
||||
const filterSource = useCstSearchStore(state => state.source);
|
||||
const includeInherited = useCstSearchStore(state => state.includeInherited);
|
||||
|
||||
const graphFiltered = activeCst ? applyGraphQuery(schema, activeCst.id, filterSource) : schema.items;
|
||||
const queryFiltered = query ? graphFiltered.filter(cst => matchConstituenta(cst, query, filterMatch)) : graphFiltered;
|
||||
const items = !includeInherited ? queryFiltered.filter(cst => !cst.is_inherited) : queryFiltered;
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter list of {@link ILibraryItem} to a given graph query.
|
||||
*/
|
||||
function applyGraphQuery(target: IRSForm, pivot: number, mode: DependencyMode): IConstituenta[] {
|
||||
if (mode === DependencyMode.ALL) {
|
||||
return target.items;
|
||||
}
|
||||
const ids = (() => {
|
||||
switch (mode) {
|
||||
case DependencyMode.OUTPUTS: {
|
||||
return target.graph.nodes.get(pivot)?.outputs;
|
||||
}
|
||||
case DependencyMode.INPUTS: {
|
||||
return target.graph.nodes.get(pivot)?.inputs;
|
||||
}
|
||||
case DependencyMode.EXPAND_OUTPUTS: {
|
||||
return target.graph.expandAllOutputs([pivot]);
|
||||
}
|
||||
case DependencyMode.EXPAND_INPUTS: {
|
||||
return target.graph.expandAllInputs([pivot]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (ids) {
|
||||
return target.items.filter(cst => ids.find(id => id === cst.id));
|
||||
} else {
|
||||
return target.items;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user