@cccsaurora/howler-ui 2.19.0-dev.1099 → 2.19.0-dev.1143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/app/App.js +4 -2
- package/components/app/providers/GridColumnsProvider.d.ts +52 -0
- package/components/app/providers/GridColumnsProvider.js +177 -0
- package/components/app/providers/GridColumnsProvider.test.js +255 -0
- package/components/elements/hit/HitContextMenu.test.d.ts +1 -0
- package/components/{routes/hits/search → elements/hit}/grid/ColumnHeader.d.ts +2 -1
- package/components/{routes/hits/search → elements/hit}/grid/ColumnHeader.js +3 -3
- package/components/{routes/hits/search → elements/hit}/grid/HitRow.d.ts +1 -1
- package/components/elements/hit/grid/HitTable.d.ts +12 -0
- package/components/elements/hit/grid/HitTable.js +82 -0
- package/components/elements/view/LayoutToggle.d.ts +8 -0
- package/components/elements/view/LayoutToggle.js +12 -0
- package/components/elements/view/ViewTitle.d.ts +3 -0
- package/components/elements/view/ViewTitle.js +6 -3
- package/components/routes/hits/search/HitBrowser.js +2 -1
- package/components/routes/hits/search/SearchPane.js +6 -8
- package/components/routes/hits/search/ViewLink.js +3 -2
- package/components/routes/hits/search/grid/HitGrid.js +17 -85
- package/components/routes/hits/search/shared/LayoutSettings.js +25 -0
- package/components/routes/hits/search/shared/SearchActionMenu.d.ts +4 -0
- package/components/routes/hits/search/shared/SearchActionMenu.js +17 -0
- package/components/routes/home/ViewCard.js +1 -1
- package/components/routes/views/ViewComposer.js +32 -6
- package/locales/en/translation.json +6 -3
- package/locales/fr/translation.json +6 -3
- package/models/entities/generated/Column.d.ts +7 -0
- package/models/entities/generated/Host.d.ts +1 -0
- package/models/entities/generated/Settings.d.ts +4 -0
- package/package.json +2 -1
- package/utils/stringUtils.d.ts +1 -0
- package/utils/stringUtils.js +7 -0
- package/utils/stringUtils.test.js +21 -1
- package/components/routes/hits/search/LayoutSettings.js +0 -24
- /package/components/{routes/hits/search/HitContextMenu.test.d.ts → app/providers/GridColumnsProvider.test.d.ts} +0 -0
- /package/components/{routes/hits/search → elements/hit}/HitContextMenu.d.ts +0 -0
- /package/components/{routes/hits/search → elements/hit}/HitContextMenu.js +0 -0
- /package/components/{routes/hits/search → elements/hit}/HitContextMenu.test.js +0 -0
- /package/components/{routes/hits/search → elements/hit}/grid/AddColumnModal.d.ts +0 -0
- /package/components/{routes/hits/search → elements/hit}/grid/AddColumnModal.js +0 -0
- /package/components/{routes/hits/search → elements/hit}/grid/EnhancedCell.d.ts +0 -0
- /package/components/{routes/hits/search → elements/hit}/grid/EnhancedCell.js +0 -0
- /package/components/{routes/hits/search → elements/hit}/grid/HitRow.js +0 -0
- /package/components/routes/hits/search/{BundleParentMenu.d.ts → shared/BundleParentMenu.d.ts} +0 -0
- /package/components/routes/hits/search/{BundleParentMenu.js → shared/BundleParentMenu.js} +0 -0
- /package/components/routes/hits/search/{LayoutSettings.d.ts → shared/LayoutSettings.d.ts} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DndContext, KeyboardSensor, PointerSensor, pointerWithin, useSensor, useSensors } from '@dnd-kit/core';
|
|
3
|
+
import { arrayMove, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
4
|
+
import { FormatIndentDecrease, FormatIndentIncrease, Search } from '@mui/icons-material';
|
|
5
|
+
import { IconButton, Stack, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
|
6
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
|
+
import { GridColumnsContext } from '@cccsaurora/howler-ui/components/app/providers/GridColumnsProvider';
|
|
8
|
+
import ColumnHeader from '@cccsaurora/howler-ui/components/elements/hit/grid/ColumnHeader';
|
|
9
|
+
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
10
|
+
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
11
|
+
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
12
|
+
import HitRow from './HitRow';
|
|
13
|
+
const HitTable = ({ query, items, refreshItems, ContextMenu, contextMenuProps, onItemClick }) => {
|
|
14
|
+
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
15
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
16
|
+
const [collapseMainColumn, setCollapseMainColumn] = useMyLocalStorageItem(StorageKey.GRID_COLLAPSE_COLUMN, false);
|
|
17
|
+
const [analyticIds, setAnalyticIds] = useState({});
|
|
18
|
+
const { columns, columnWidths, columnSources, setColumnWidth, setColumns, isReady } = useContext(GridColumnsContext);
|
|
19
|
+
const resizingCol = useRef();
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
items?.forEach(hit => {
|
|
22
|
+
if (!analyticIds[hit.howler.analytic]) {
|
|
23
|
+
getMatchingAnalytic(hit).then(_analytic => {
|
|
24
|
+
if (_analytic) {
|
|
25
|
+
setAnalyticIds(_analyticIds => ({ ..._analyticIds, [hit.howler.analytic]: _analytic.analytic_id }));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
31
|
+
}, [analyticIds, items]);
|
|
32
|
+
const onMouseMove = useCallback((event) => {
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
const { col, width } = resizingCol.current;
|
|
36
|
+
const newWidth = width + event.movementX;
|
|
37
|
+
document.querySelectorAll(`.col-${col.replaceAll('.', '-')}`).forEach(el => {
|
|
38
|
+
el.style.maxWidth = newWidth + 'px';
|
|
39
|
+
el.style.width = newWidth + 'px';
|
|
40
|
+
});
|
|
41
|
+
resizingCol.current.width = newWidth;
|
|
42
|
+
}, []);
|
|
43
|
+
const onMouseUp = useCallback(() => {
|
|
44
|
+
const { col, width, element } = resizingCol.current;
|
|
45
|
+
if (isReady) {
|
|
46
|
+
setColumnWidth(col, Math.round(width));
|
|
47
|
+
}
|
|
48
|
+
element.style.width = null;
|
|
49
|
+
element.style.maxWidth = null;
|
|
50
|
+
document.querySelectorAll(`.col-${col.replaceAll('.', '-')}`).forEach(el => {
|
|
51
|
+
el.style.maxWidth = null;
|
|
52
|
+
el.style.width = null;
|
|
53
|
+
});
|
|
54
|
+
window.removeEventListener('mousemove', onMouseMove);
|
|
55
|
+
window.removeEventListener('mouseup', onMouseUp);
|
|
56
|
+
}, [onMouseMove, setColumnWidth, isReady]);
|
|
57
|
+
const onMouseDown = useCallback((col, event) => {
|
|
58
|
+
event.stopPropagation();
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
const element = event.target.parentElement;
|
|
61
|
+
const rect = element.getBoundingClientRect();
|
|
62
|
+
resizingCol.current = { col, width: rect.width, element };
|
|
63
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
64
|
+
window.addEventListener('mouseup', onMouseUp);
|
|
65
|
+
}, [onMouseMove, onMouseUp]);
|
|
66
|
+
const handleDragEnd = useCallback((event) => {
|
|
67
|
+
const { active, over } = event;
|
|
68
|
+
if (over && active.id !== over.id) {
|
|
69
|
+
const oldIndex = (columns ?? []).findIndex(entry => entry === active.id);
|
|
70
|
+
const newIndex = (columns ?? []).findIndex(entry => entry === over.id);
|
|
71
|
+
if (isReady) {
|
|
72
|
+
setColumns(arrayMove(columns, oldIndex, newIndex));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}, [columns, setColumns, isReady]);
|
|
76
|
+
const tableContent = (_jsxs(_Fragment, { children: [items?.map(hit => (_jsx(HitRow, { hit: hit, analyticIds: analyticIds, columns: columns, columnWidths: columnWidths, collapseMainColumn: collapseMainColumn, onClick: onItemClick ? onItemClick : (_ev, _hit) => null }, hit.howler.id))), refreshItems && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 2, children: _jsx(Stack, { alignItems: "center", justifyContent: "center", py: 0.5, px: 1, children: _jsx(IconButton, { onClick: () => refreshItems(query, true), children: _jsx(Search, {}) }) }) }) }))] }));
|
|
77
|
+
return (_jsxs(Table, { sx: { '& td,th': { px: 1, py: 0.25, whiteSpace: 'nowrap' } }, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { sx: {
|
|
78
|
+
borderRight: 'thin solid',
|
|
79
|
+
borderRightColor: 'divider'
|
|
80
|
+
}, children: _jsx(IconButton, { onClick: () => setCollapseMainColumn(!collapseMainColumn), children: collapseMainColumn ? (_jsx(FormatIndentIncrease, { fontSize: "small" })) : (_jsx(FormatIndentDecrease, { fontSize: "small" })) }) }), _jsx(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columns, children: columns.map(col => (_jsx(ColumnHeader, { col: col, width: columnWidths[col], colSource: columnSources[col], onMouseDown: onMouseDown, setColumns: isReady ? setColumns : () => null }, col))) }) }), _jsx(TableCell, { sx: { width: '100%' } })] }) }), ContextMenu ? (_jsx(ContextMenu, { ...contextMenuProps, children: tableContent })) : (_jsx(TableBody, { children: tableContent }))] }));
|
|
81
|
+
};
|
|
82
|
+
export default HitTable;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type HowlerViewLayoutType = 'list' | 'grid' | null;
|
|
2
|
+
declare const LayoutToggle: ({ displayType, setDisplayType, size, allowNullValue }: {
|
|
3
|
+
displayType: HowlerViewLayoutType;
|
|
4
|
+
setDisplayType: (type: HowlerViewLayoutType) => void;
|
|
5
|
+
size?: "small" | "medium" | "large";
|
|
6
|
+
allowNullValue?: boolean;
|
|
7
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default LayoutToggle;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { List, TableChart } from '@mui/icons-material';
|
|
3
|
+
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
|
|
4
|
+
const LayoutToggle = ({ displayType, setDisplayType, size, allowNullValue = false }) => {
|
|
5
|
+
return (_jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => {
|
|
6
|
+
if (!value && !allowNullValue) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
setDisplayType(value);
|
|
10
|
+
}, size: size ?? 'small', children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, { fontSize: size ?? 'medium' }) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, { fontSize: size ?? 'medium' }) })] }));
|
|
11
|
+
};
|
|
12
|
+
export default LayoutToggle;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowDownward, ArrowUpward, Language, Lock, Person } from '@mui/icons-material';
|
|
2
|
+
import { ArrowDownward, ArrowUpward, Language, List, Lock, Person, TableChart } from '@mui/icons-material';
|
|
3
3
|
import { Chip, Stack, Tooltip, Typography } from '@mui/material';
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { convertLuceneToDate } from '@cccsaurora/howler-ui/utils/utils';
|
|
7
|
-
export const ViewTitle = ({ title, type, query, sort, span }) => {
|
|
7
|
+
export const ViewTitle = ({ title, type, query, sort, span, settings }) => {
|
|
8
8
|
const { t } = useTranslation();
|
|
9
9
|
const spanLabel = useMemo(() => {
|
|
10
10
|
if (!span) {
|
|
@@ -21,5 +21,8 @@ export const ViewTitle = ({ title, type, query, sort, span }) => {
|
|
|
21
21
|
readonly: _jsx(Lock, { fontSize: "small" }),
|
|
22
22
|
global: _jsx(Language, { fontSize: "small" }),
|
|
23
23
|
personal: _jsx(Person, { fontSize: "small" })
|
|
24
|
-
}[type] }),
|
|
24
|
+
}[type] }), settings?.display && (_jsx(Tooltip, { title: t(`page.settings.local.hits.display_type.${settings.display}`), children: {
|
|
25
|
+
list: _jsx(List, { fontSize: "small" }),
|
|
26
|
+
grid: _jsx(TableChart, { fontSize: "small" })
|
|
27
|
+
}[settings.display] })), _jsx(Typography, { variant: "body1", children: t(title) })] }), _jsx(Typography, { variant: "caption", children: _jsx("code", { children: query }) }), (sort || span) && (_jsxs(Stack, { direction: "row", sx: { mt: 1 }, spacing: 1, children: [sort?.split(',').map(_sort => (_jsx(Chip, { size: "small", label: _sort.split(' ')[0], icon: _sort.endsWith('desc') ? _jsx(ArrowDownward, {}) : _jsx(ArrowUpward, {}) }, _sort.split(' ')[0]))), spanLabel && _jsx(Chip, { size: "small", label: spanLabel })] }))] }));
|
|
25
28
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronLeft, Close, ManageSearch } from '@mui/icons-material';
|
|
3
3
|
import { Box, Card, Checkbox, Collapse, Drawer, Fab, IconButton, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
4
|
+
import GridColumnsProvider from '@cccsaurora/howler-ui/components/app/providers/GridColumnsProvider';
|
|
4
5
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
5
6
|
import HitSearchProvider, { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
6
7
|
import ParameterProvider, { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
@@ -128,6 +129,6 @@ const HitBrowser = () => {
|
|
|
128
129
|
}, children: _jsx(ChevronLeft, { sx: { transition: 'rotate 250ms', rotate: show ? '180deg' : '0deg' } }) }))] }));
|
|
129
130
|
};
|
|
130
131
|
const HitBrowserProvider = () => {
|
|
131
|
-
return (_jsx(ParameterProvider, { children: _jsx(HitSearchProvider, { children: _jsx(HitBrowser, {}) }) }));
|
|
132
|
+
return (_jsx(ParameterProvider, { children: _jsx(HitSearchProvider, { children: _jsx(GridColumnsProvider, { children: _jsx(HitBrowser, {}) }) }) }));
|
|
132
133
|
};
|
|
133
134
|
export default HitBrowserProvider;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { Box,
|
|
2
|
+
import { ErrorOutline } from '@mui/icons-material';
|
|
3
|
+
import { Box, LinearProgress, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
4
4
|
import { grey } from '@mui/material/colors';
|
|
5
5
|
import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
|
|
6
6
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
@@ -17,21 +17,20 @@ import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search
|
|
|
17
17
|
import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
|
|
18
18
|
import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
|
|
19
19
|
import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
|
|
20
|
+
import HitContextMenu from '@cccsaurora/howler-ui/components/elements/hit/HitContextMenu';
|
|
20
21
|
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
21
22
|
import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
|
|
22
23
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
23
24
|
import React, { memo, useCallback, useEffect, useMemo } from 'react';
|
|
24
25
|
import { isMobile } from 'react-device-detect';
|
|
25
26
|
import { useTranslation } from 'react-i18next';
|
|
26
|
-
import {
|
|
27
|
+
import { useLocation, useParams } from 'react-router-dom';
|
|
27
28
|
import { useContextSelector } from 'use-context-selector';
|
|
28
29
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
29
|
-
import BundleParentMenu from './BundleParentMenu';
|
|
30
30
|
import { BundleScroller } from './BundleScroller';
|
|
31
|
-
import HitContextMenu from './HitContextMenu';
|
|
32
31
|
import HitQuery from './HitQuery';
|
|
33
|
-
import LayoutSettings from './LayoutSettings';
|
|
34
32
|
import QuerySettings from './QuerySettings';
|
|
33
|
+
import SearchActionMenu from './shared/SearchActionMenu';
|
|
35
34
|
const Item = memo(({ hit, onClick }) => {
|
|
36
35
|
const theme = useTheme();
|
|
37
36
|
const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
|
|
@@ -80,7 +79,6 @@ const Item = memo(({ hit, onClick }) => {
|
|
|
80
79
|
const SearchPane = () => {
|
|
81
80
|
const { t } = useTranslation();
|
|
82
81
|
const location = useLocation();
|
|
83
|
-
const navigate = useNavigate();
|
|
84
82
|
const routeParams = useParams();
|
|
85
83
|
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
86
84
|
const setSelected = useContextSelector(ParameterContext, ctx => ctx.setSelected);
|
|
@@ -117,7 +115,7 @@ const SearchPane = () => {
|
|
|
117
115
|
], onClick: () => {
|
|
118
116
|
clearSelectedHits(bundleHit.howler.id);
|
|
119
117
|
setSelected(bundleHit.howler.id);
|
|
120
|
-
}, children: _jsx(HitBanner, { hit: bundleHit, layout: HitLayout.DENSE, useListener: true }) }) }) }) })), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", children: t('hit.search.prompt') }), error && (_jsx(Tooltip, { title: `${t('route.advanced.error')}: ${error}`, children: _jsx(ErrorOutline, { fontSize: "small", color: "error" }) })), _jsx(FlexOne, {}),
|
|
118
|
+
}, children: _jsx(HitBanner, { hit: bundleHit, layout: HitLayout.DENSE, useListener: true }) }) }) }) })), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", children: t('hit.search.prompt') }), error && (_jsx(Tooltip, { title: `${t('route.advanced.error')}: ${error}`, children: _jsx(ErrorOutline, { fontSize: "small", color: "error" }) })), _jsx(FlexOne, {}), _jsx(SearchActionMenu, { query: query })] })] }), _jsxs(VSBoxHeader, { ml: -3, mr: -3, px: 2, pb: 1, sx: { zIndex: 989 }, children: [_jsxs(Stack, { sx: { pt: 1 }, children: [_jsxs(Stack, { sx: { position: 'relative', flex: 1 }, children: [_jsx(HitQuery, { searching: searching, triggerSearch: triggerSearch }), searching && (_jsx(LinearProgress, { sx: theme => ({
|
|
121
119
|
position: 'absolute',
|
|
122
120
|
left: 0,
|
|
123
121
|
right: 0,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement as _createElement } from "react";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { ArrowDropDown, Delete, Edit, Language, Lock, OpenInNew, Person, Refresh, SavedSearch, SelectAll } from '@mui/icons-material';
|
|
3
|
+
import { ArrowDropDown, Delete, Edit, Language, Lock, OpenInNew, Person, Refresh, SavedSearch, SelectAll, Warning } from '@mui/icons-material';
|
|
4
4
|
import { Autocomplete, Chip, CircularProgress, IconButton, Stack, TextField, Tooltip, Typography } from '@mui/material';
|
|
5
5
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
6
6
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
@@ -21,6 +21,7 @@ const ViewLink = ({ id, viewId }) => {
|
|
|
21
21
|
const removeView = useContextSelector(ParameterContext, ctx => ctx.removeView);
|
|
22
22
|
const setParamView = useContextSelector(ParameterContext, ctx => ctx.setView);
|
|
23
23
|
const search = useContextSelector(HitSearchContext, ctx => ctx.search);
|
|
24
|
+
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
24
25
|
const [loading, setLoading] = useState(true);
|
|
25
26
|
const [view, setView] = useState(null);
|
|
26
27
|
useEffect(() => {
|
|
@@ -60,6 +61,6 @@ const ViewLink = ({ id, viewId }) => {
|
|
|
60
61
|
readonly: _jsx(Lock, { fontSize: "small", "aria-label": t(`route.views.manager.${view.type}`) }),
|
|
61
62
|
global: _jsx(Language, { fontSize: "small", "aria-label": t(`route.views.manager.${view.type}`) }),
|
|
62
63
|
personal: _jsx(Person, { fontSize: "small", "aria-label": t(`route.views.manager.${view.type}`) })
|
|
63
|
-
}[view.type] }), label: _jsx(Tooltip, { title: view.query, children: _jsx(Typography, { role: "link", sx: { color: 'text.primary' }, variant: "body2", component: Link, to: `/views/${view.view_id}/edit`, "aria-label": `${t(view.title)} - ${view.query ?? t('unknown')}`, children: t(view.title) }) }), onDelete: () => removeView(viewId), children: _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", children: [_jsx(Tooltip, { title: view ? t('route.views.edit') : t('route.views.create'), children: _jsx(IconButton, { "aria-label": view ? t('route.views.edit') : t('route.views.create'), size: "small", component: Link, disabled: (!view && !query) || span?.endsWith('custom'), to: viewUrl, role: "link", children: view ? _jsx(Edit, { fontSize: "small" }) : _jsx(SavedSearch, {}) }) }), _jsx(Tooltip, { title: t('view.refresh'), children: _jsx(IconButton, { size: "small", onClick: () => search(query), "aria-label": t('view.refresh'), children: _jsx(Refresh, { fontSize: "small" }) }) }), _jsx(Tooltip, { title: t('view.open'), children: _jsx(IconButton, { size: "small", component: Link, to: `/search?query=${view.query}`, "aria-label": t('view.open'), role: "link", children: _jsx(OpenInNew, { fontSize: "small" }) }) })] }) }));
|
|
64
|
+
}[view.type] }), label: _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", children: [_jsx(Tooltip, { title: view.query, children: _jsx(Typography, { role: "link", sx: { color: 'text.primary' }, variant: "body2", component: Link, to: `/views/${view.view_id}/edit`, "aria-label": `${t(view.title)} - ${view.query ?? t('unknown')}`, children: t(view.title) }) }), view.settings?.display === 'grid' && displayType !== 'grid' && (_jsx(Tooltip, { title: t('view.display.grid.inactive_warning'), children: _jsx(Warning, { fontSize: "small", color: "warning" }) }))] }), onDelete: () => removeView(viewId), children: _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", children: [_jsx(Tooltip, { title: view ? t('route.views.edit') : t('route.views.create'), children: _jsx(IconButton, { "aria-label": view ? t('route.views.edit') : t('route.views.create'), size: "small", component: Link, disabled: (!view && !query) || span?.endsWith('custom'), to: viewUrl, role: "link", children: view ? _jsx(Edit, { fontSize: "small" }) : _jsx(SavedSearch, {}) }) }), _jsx(Tooltip, { title: t('view.refresh'), children: _jsx(IconButton, { size: "small", onClick: () => search(query), "aria-label": t('view.refresh'), children: _jsx(Refresh, { fontSize: "small" }) }) }), _jsx(Tooltip, { title: t('view.open'), children: _jsx(IconButton, { size: "small", component: Link, to: `/search?query=${view.query}`, "aria-label": t('view.open'), role: "link", children: _jsx(OpenInNew, { fontSize: "small" }) }) })] }) }));
|
|
64
65
|
};
|
|
65
66
|
export default memo(ViewLink);
|
|
@@ -1,51 +1,34 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { IconButton, LinearProgress, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, ToggleButton, ToggleButtonGroup, Typography, useTheme } from '@mui/material';
|
|
6
|
-
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
2
|
+
import { Info } from '@mui/icons-material';
|
|
3
|
+
import { LinearProgress, Paper, Stack, TableBody, Typography, useTheme } from '@mui/material';
|
|
4
|
+
import { GridColumnsContext } from '@cccsaurora/howler-ui/components/app/providers/GridColumnsProvider';
|
|
7
5
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
8
6
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
9
7
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
10
8
|
import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
|
|
11
9
|
import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentBanner';
|
|
10
|
+
import AddColumnModal from '@cccsaurora/howler-ui/components/elements/hit/grid/AddColumnModal';
|
|
11
|
+
import HitTable from '@cccsaurora/howler-ui/components/elements/hit/grid/HitTable';
|
|
12
|
+
import HitContextMenu from '@cccsaurora/howler-ui/components/elements/hit/HitContextMenu';
|
|
12
13
|
import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
|
|
13
|
-
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
14
14
|
import { uniq } from 'lodash-es';
|
|
15
|
-
import { useCallback,
|
|
15
|
+
import { useCallback, useContext, useMemo } from 'react';
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
17
|
import { useContextSelector } from 'use-context-selector';
|
|
18
|
-
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
19
|
-
import HitContextMenu from '../HitContextMenu';
|
|
20
18
|
import HitQuery from '../HitQuery';
|
|
21
19
|
import QuerySettings from '../QuerySettings';
|
|
22
|
-
import
|
|
23
|
-
import ColumnHeader from './ColumnHeader';
|
|
24
|
-
import HitRow from './HitRow';
|
|
20
|
+
import SearchActionMenu from '../shared/SearchActionMenu';
|
|
25
21
|
const HitGrid = () => {
|
|
26
22
|
const { t } = useTranslation();
|
|
27
23
|
const theme = useTheme();
|
|
28
|
-
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
29
24
|
const { onClick } = useHitSelection();
|
|
30
|
-
const { getMatchingAnalytic } = useMatchers();
|
|
31
25
|
const search = useContextSelector(HitSearchContext, ctx => ctx.search);
|
|
32
|
-
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
33
|
-
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
34
26
|
const response = useContextSelector(HitSearchContext, ctx => ctx.response);
|
|
35
27
|
const searching = useContextSelector(HitSearchContext, ctx => ctx.searching);
|
|
36
28
|
const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
|
|
37
29
|
const query = useContextSelector(ParameterContext, ctx => ctx.query);
|
|
38
30
|
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
39
|
-
const
|
|
40
|
-
const [columns, setColumns] = useMyLocalStorageItem(StorageKey.GRID_COLUMNS, [
|
|
41
|
-
'howler.outline.threat',
|
|
42
|
-
'howler.outline.target',
|
|
43
|
-
'howler.outline.indicators',
|
|
44
|
-
'howler.outline.summary'
|
|
45
|
-
]);
|
|
46
|
-
const [columnWidths, setColumnWidths] = useMyLocalStorageItem(StorageKey.GRID_COLUMN_WIDTHS, {});
|
|
47
|
-
const [analyticIds, setAnalyticIds] = useState({});
|
|
48
|
-
const resizingCol = useRef();
|
|
31
|
+
const { columns, setColumns, isReady } = useContext(GridColumnsContext);
|
|
49
32
|
const showSelectBar = useMemo(() => {
|
|
50
33
|
if (selectedHits.length > 1) {
|
|
51
34
|
return true;
|
|
@@ -55,60 +38,12 @@ const HitGrid = () => {
|
|
|
55
38
|
}
|
|
56
39
|
return false;
|
|
57
40
|
}, [selected, selectedHits]);
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
response?.items.forEach(hit => {
|
|
60
|
-
if (!analyticIds[hit.howler.analytic]) {
|
|
61
|
-
getMatchingAnalytic(hit).then(_analytic => setAnalyticIds(_analyticIds => ({ ..._analyticIds, [hit.howler.analytic]: _analytic.analytic_id })));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
-
}, [analyticIds, response]);
|
|
66
|
-
const onMouseMove = useCallback((event) => {
|
|
67
|
-
event.stopPropagation();
|
|
68
|
-
event.preventDefault();
|
|
69
|
-
const [col, element] = resizingCol.current;
|
|
70
|
-
const rect = element.getBoundingClientRect();
|
|
71
|
-
document.querySelectorAll(`.col-${col.replaceAll('.', '-')}`).forEach(el => {
|
|
72
|
-
el.style.maxWidth = rect.width + event.movementX + 'px';
|
|
73
|
-
el.style.width = rect.width + event.movementX + 'px';
|
|
74
|
-
});
|
|
75
|
-
}, []);
|
|
76
|
-
const onMouseUp = useCallback(() => {
|
|
77
|
-
const [col, element] = resizingCol.current;
|
|
78
|
-
setColumnWidths({
|
|
79
|
-
...columnWidths,
|
|
80
|
-
[col]: element.style.width
|
|
81
|
-
});
|
|
82
|
-
element.style.width = null;
|
|
83
|
-
element.style.maxWidth = null;
|
|
84
|
-
document.querySelectorAll(`.col-${col.replaceAll('.', '-')}`).forEach(el => {
|
|
85
|
-
el.style.maxWidth = null;
|
|
86
|
-
el.style.width = null;
|
|
87
|
-
});
|
|
88
|
-
window.removeEventListener('mousemove', onMouseMove);
|
|
89
|
-
window.removeEventListener('mouseup', onMouseUp);
|
|
90
|
-
}, [columnWidths, onMouseMove, setColumnWidths]);
|
|
91
|
-
const onMouseDown = useCallback((col, event) => {
|
|
92
|
-
event.stopPropagation();
|
|
93
|
-
event.preventDefault();
|
|
94
|
-
resizingCol.current = [col, event.target.parentElement];
|
|
95
|
-
window.addEventListener('mousemove', onMouseMove);
|
|
96
|
-
window.addEventListener('mouseup', onMouseUp);
|
|
97
|
-
}, [onMouseMove, onMouseUp]);
|
|
98
41
|
const onScroll = useCallback((event) => {
|
|
99
42
|
const target = event.target;
|
|
100
43
|
if (target.scrollHeight - target.scrollTop === target.clientHeight) {
|
|
101
44
|
search(query, true);
|
|
102
45
|
}
|
|
103
46
|
}, [query, search]);
|
|
104
|
-
const handleDragEnd = useCallback((event) => {
|
|
105
|
-
const { active, over } = event;
|
|
106
|
-
if (over && active.id !== over.id) {
|
|
107
|
-
const oldIndex = (columns ?? []).findIndex(entry => entry === active.id);
|
|
108
|
-
const newIndex = (columns ?? []).findIndex(entry => entry === over.id);
|
|
109
|
-
setColumns(arrayMove(columns, oldIndex, newIndex));
|
|
110
|
-
}
|
|
111
|
-
}, [columns, setColumns]);
|
|
112
47
|
const getSelectedId = useCallback((event) => {
|
|
113
48
|
const target = event.target;
|
|
114
49
|
const selectedElement = target.closest('[id]');
|
|
@@ -117,16 +52,13 @@ const HitGrid = () => {
|
|
|
117
52
|
}
|
|
118
53
|
return selectedElement.id;
|
|
119
54
|
}, []);
|
|
120
|
-
return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5, textAlign: 'left' }, variant: "body2", children: t('hit.search.prompt') }),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
borderRight: 'thin solid',
|
|
129
|
-
borderRightColor: 'divider'
|
|
130
|
-
}, children: _jsx(IconButton, { onClick: () => setCollapseMainColumn(!collapseMainColumn), children: collapseMainColumn ? (_jsx(FormatIndentIncrease, { fontSize: "small" })) : (_jsx(FormatIndentDecrease, { fontSize: "small" })) }) }), _jsx(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columns, children: columns.map(col => (_jsx(ColumnHeader, { col: col, width: columnWidths[col], onMouseDown: onMouseDown, setColumns: setColumns }, col))) }) }), _jsx(TableCell, { sx: { width: '100%' } })] }) }), _jsxs(HitContextMenu, { Component: TableBody, getSelectedId: getSelectedId, children: [response?.items.map(hit => (_jsx(HitRow, { hit: hit, analyticIds: analyticIds, columns: columns, columnWidths: columnWidths, collapseMainColumn: collapseMainColumn, onClick: onClick }, hit.howler.id))), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 2, children: _jsx(Stack, { alignItems: "center", justifyContent: "center", py: 0.5, px: 1, children: _jsx(IconButton, { onClick: () => search(query, true), children: _jsx(Search, {}) }) }) }) })] })] }), (response?.total ?? 0) < 1 && (_jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", p: 1, justifyContent: "center", flex: 1, children: _jsxs(Typography, { variant: "h3", color: "text.secondary", display: "flex", flexDirection: "row", alignItems: "center", children: [_jsx(Info, { fontSize: "inherit", sx: { color: 'text.secondary', mr: 1 } }), _jsx("span", { children: t('app.list.empty') })] }) }))] })] }));
|
|
55
|
+
return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5, textAlign: 'left' }, variant: "body2", children: t('hit.search.prompt') }), _jsx(SearchActionMenu, { query: query })] }), _jsx(Stack, { direction: "row", spacing: 1, children: _jsxs(Stack, { position: "relative", flex: 1, children: [_jsx(HitQuery, { searching: searching, triggerSearch: search, compact: true }), searching && (_jsx(LinearProgress, { sx: {
|
|
56
|
+
position: 'absolute',
|
|
57
|
+
left: 0,
|
|
58
|
+
right: 0,
|
|
59
|
+
bottom: 0,
|
|
60
|
+
borderBottomLeftRadius: theme.shape.borderRadius,
|
|
61
|
+
borderBottomRightRadius: theme.shape.borderRadius
|
|
62
|
+
} }))] }) }), _jsxs(Stack, { direction: "row", spacing: 1, width: "100%", alignItems: "center", children: [_jsx(QuerySettings, { boxSx: { flex: 1 } }), _jsx(AddColumnModal, { columns: columns, addColumn: key => isReady && setColumns(uniq([...columns, key])) })] }), response && (_jsx(SearchTotal, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", offset: response.offset, pageLength: response.rows, total: response.total })), _jsxs(Stack, { component: Paper, spacing: 1, width: "100%", height: "100%", sx: { overflow: 'auto', flex: 1 }, onScroll: onScroll, children: [_jsx(HitTable, { query: query, items: response?.items, refreshItems: search, ContextMenu: HitContextMenu, contextMenuProps: { Component: TableBody, getSelectedId: getSelectedId }, onItemClick: onClick }), (response?.total ?? 0) < 1 && (_jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", p: 1, justifyContent: "center", flex: 1, children: _jsxs(Typography, { variant: "h3", color: "text.secondary", display: "flex", flexDirection: "row", alignItems: "center", children: [_jsx(Info, { fontSize: "inherit", sx: { color: 'text.secondary', mr: 1 } }), _jsx("span", { children: t('app.list.empty') })] }) }))] })] }));
|
|
131
63
|
};
|
|
132
64
|
export default HitGrid;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowDropDown, InfoOutlined, Settings, ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
|
|
3
|
+
import { Checkbox, Divider, FormLabel, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material';
|
|
4
|
+
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
5
|
+
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
6
|
+
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
7
|
+
import LayoutToggle from '@cccsaurora/howler-ui/components/elements/view/LayoutToggle';
|
|
8
|
+
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
import { useContextSelector } from 'use-context-selector';
|
|
11
|
+
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
12
|
+
const LayoutSettings = () => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
15
|
+
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
16
|
+
const [hitLayout, setHitLayout] = useMyLocalStorageItem(StorageKey.HIT_LAYOUT, false);
|
|
17
|
+
const [templateFieldCount, setTemplateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
|
|
18
|
+
return (_jsx(ChipPopper, { icon: _jsx(Tooltip, { title: t('search.layout.settings'), children: _jsx(Settings, {}) }), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, disablePortal: false, slotProps: { chip: { size: 'medium', 'aria-label': t('search.layout.settings') } }, placement: "bottom-end", children: _jsxs(Stack, { spacing: 1, alignItems: "start", children: [_jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "display_type", children: t('page.settings.local.hits.display_type') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.display_type.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsx(LayoutToggle, { displayType: displayType, setDisplayType: setDisplayType }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "layout", children: t('page.settings.local.hits.layout') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.layout.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, size: "small", value: hitLayout, onChange: (_, value) => setHitLayout(value), "aria-labelledby": "layout", children: [_jsx(ToggleButton, { value: HitLayout.DENSE, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewCompact, {}), _jsx("span", { children: t('page.settings.local.hits.layout.dense') })] }) }), _jsx(ToggleButton, { value: HitLayout.NORMAL, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewModule, {}), _jsx("span", { children: t('page.settings.local.hits.layout.normal') })] }) }), _jsx(ToggleButton, { value: HitLayout.COMFY, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewComfy, {}), _jsx("span", { children: t('page.settings.local.hits.layout.comfy') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "field_count", children: t('page.settings.local.hits.field_count') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.field_count.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignSelf: "stretch", children: [_jsx(Checkbox, { checked: templateFieldCount !== null, onChange: (_, checked) => setTemplateFieldCount(checked ? 3 : null), size: "small" }), _jsx(TextField, { type: "number", size: "small", disabled: templateFieldCount === null, value: templateFieldCount ?? 3, fullWidth: true, onChange: e => {
|
|
19
|
+
const val = parseInt(e.target.value);
|
|
20
|
+
if (!isNaN(val)) {
|
|
21
|
+
setTemplateFieldCount(Math.min(15, Math.max(0, val)));
|
|
22
|
+
}
|
|
23
|
+
}, inputProps: { min: 0, max: 15, 'aria-labelledby': 'field_count' } })] })] }) }));
|
|
24
|
+
};
|
|
25
|
+
export default LayoutSettings;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Close, SavedSearch, Terminal } from '@mui/icons-material';
|
|
3
|
+
import { IconButton, Stack, Tooltip } from '@mui/material';
|
|
4
|
+
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Link, useNavigate, useParams } from 'react-router-dom';
|
|
7
|
+
import { useContextSelector } from 'use-context-selector';
|
|
8
|
+
import BundleParentMenu from './BundleParentMenu';
|
|
9
|
+
import LayoutSettings from './LayoutSettings';
|
|
10
|
+
const SearchActionMenu = ({ query }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const navigate = useNavigate();
|
|
13
|
+
const routeParams = useParams();
|
|
14
|
+
const bundleHit = useContextSelector(HitContext, ctx => location.pathname.startsWith('/bundles') ? ctx.hits[routeParams.id] : null);
|
|
15
|
+
return (_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [bundleHit?.howler.bundles.length > 0 && _jsx(BundleParentMenu, { bundle: bundleHit }), bundleHit && (_jsx(Tooltip, { title: t('hit.bundle.close'), children: _jsx(IconButton, { size: "small", onClick: () => navigate('/search'), children: _jsx(Close, {}) }) })), _jsx(Tooltip, { title: t('route.views.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/views/create?query=${query}`, children: _jsx(SavedSearch, {}) }) }), _jsx(Tooltip, { title: t('route.actions.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/action/execute?query=${query}`, children: _jsx(Terminal, {}) }) }), _jsx(LayoutSettings, {})] }));
|
|
16
|
+
};
|
|
17
|
+
export default SearchActionMenu;
|
|
@@ -6,9 +6,9 @@ import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppLi
|
|
|
6
6
|
import { useHitContextSelector } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
7
7
|
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
8
8
|
import HitBanner from '@cccsaurora/howler-ui/components/elements/hit/HitBanner';
|
|
9
|
+
import HitContextMenu from '@cccsaurora/howler-ui/components/elements/hit/HitContextMenu';
|
|
9
10
|
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
10
11
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
11
|
-
import HitContextMenu from '@cccsaurora/howler-ui/components/routes/hits/search/HitContextMenu';
|
|
12
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
14
|
import { Link, useNavigate } from 'react-router-dom';
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { HelpOutline, Save } from '@mui/icons-material';
|
|
5
|
-
import { Alert, Checkbox, CircularProgress, LinearProgress, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from '@mui/material';
|
|
4
|
+
import { HelpOutline, Save, Settings } from '@mui/icons-material';
|
|
5
|
+
import { Alert, Checkbox, CircularProgress, LinearProgress, Paper, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography } from '@mui/material';
|
|
6
6
|
import api from '@cccsaurora/howler-ui/api';
|
|
7
7
|
import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
|
|
8
8
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
9
|
+
import { GridColumnsContext } from '@cccsaurora/howler-ui/components/app/providers/GridColumnsProvider';
|
|
9
10
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
11
|
+
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
10
12
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
11
13
|
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
12
14
|
import CustomButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomButton';
|
|
15
|
+
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
13
16
|
import FlexPort from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexPort';
|
|
14
17
|
import VSBox from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBox';
|
|
15
18
|
import VSBoxContent from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxContent';
|
|
16
19
|
import VSBoxHeader from '@cccsaurora/howler-ui/components/elements/addons/layout/vsbox/VSBoxHeader';
|
|
17
20
|
import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
|
|
21
|
+
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
22
|
+
import AddColumnModal from '@cccsaurora/howler-ui/components/elements/hit/grid/AddColumnModal';
|
|
23
|
+
import HitTable from '@cccsaurora/howler-ui/components/elements/hit/grid/HitTable';
|
|
18
24
|
import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
|
|
19
25
|
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
26
|
+
import LayoutToggle, {} from '@cccsaurora/howler-ui/components/elements/view/LayoutToggle';
|
|
20
27
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
21
28
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
22
29
|
import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
|
|
30
|
+
import { uniq } from 'lodash-es';
|
|
23
31
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
24
32
|
import { useContextSelector } from 'use-context-selector';
|
|
25
33
|
import { DEFAULT_QUERY, StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
@@ -44,6 +52,7 @@ const ViewComposer = () => {
|
|
|
44
52
|
const [title, setTitle] = useState('');
|
|
45
53
|
const [type, setType] = useState('global');
|
|
46
54
|
const [advanceOnTriage, setAdvanceOnTriage] = useState(false);
|
|
55
|
+
const { columns, setColumns, columnWidths, isReady } = useContext(GridColumnsContext);
|
|
47
56
|
const query = useContextSelector(ParameterContext, ctx => ctx.query);
|
|
48
57
|
const setQuery = useContextSelector(ParameterContext, ctx => ctx.setQuery);
|
|
49
58
|
const sort = useContextSelector(ParameterContext, ctx => ctx.sort);
|
|
@@ -56,8 +65,14 @@ const ViewComposer = () => {
|
|
|
56
65
|
const [searching, setSearching] = useState(false);
|
|
57
66
|
const [error, setError] = useState(null);
|
|
58
67
|
const [response, setResponse] = useState();
|
|
68
|
+
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
69
|
+
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
59
70
|
const onSave = useCallback(async () => {
|
|
60
71
|
setLoading(true);
|
|
72
|
+
const _columnData = columns.map(column => ({
|
|
73
|
+
field: column,
|
|
74
|
+
width: columnWidths[column] ?? null
|
|
75
|
+
}));
|
|
61
76
|
try {
|
|
62
77
|
if (!routeParams.id) {
|
|
63
78
|
const newView = await addView({
|
|
@@ -67,7 +82,9 @@ const ViewComposer = () => {
|
|
|
67
82
|
sort: sort || null,
|
|
68
83
|
span: span || null,
|
|
69
84
|
settings: {
|
|
70
|
-
advance_on_triage: advanceOnTriage
|
|
85
|
+
advance_on_triage: advanceOnTriage,
|
|
86
|
+
display: displayType,
|
|
87
|
+
columns: displayType === 'grid' ? _columnData : null
|
|
71
88
|
}
|
|
72
89
|
});
|
|
73
90
|
navigate(buildViewUrl(newView));
|
|
@@ -79,7 +96,11 @@ const ViewComposer = () => {
|
|
|
79
96
|
query,
|
|
80
97
|
sort,
|
|
81
98
|
span,
|
|
82
|
-
settings: {
|
|
99
|
+
settings: {
|
|
100
|
+
advance_on_triage: advanceOnTriage,
|
|
101
|
+
display: displayType,
|
|
102
|
+
columns: displayType === 'grid' ? _columnData : null
|
|
103
|
+
}
|
|
83
104
|
});
|
|
84
105
|
}
|
|
85
106
|
showSuccessMessage(t(routeParams.id ? 'route.views.update.success' : 'route.views.create.success'));
|
|
@@ -101,7 +122,10 @@ const ViewComposer = () => {
|
|
|
101
122
|
sort,
|
|
102
123
|
span,
|
|
103
124
|
advanceOnTriage,
|
|
125
|
+
displayType,
|
|
126
|
+
columns,
|
|
104
127
|
navigate,
|
|
128
|
+
columnWidths,
|
|
105
129
|
editView,
|
|
106
130
|
showErrorMessage
|
|
107
131
|
]);
|
|
@@ -154,6 +178,8 @@ const ViewComposer = () => {
|
|
|
154
178
|
setTitle(viewToEdit.title);
|
|
155
179
|
setAdvanceOnTriage(viewToEdit.settings?.advance_on_triage ?? false);
|
|
156
180
|
setQuery(viewToEdit.query);
|
|
181
|
+
setDisplayType((viewToEdit.settings?.display ?? null));
|
|
182
|
+
setType(viewToEdit.type);
|
|
157
183
|
if (viewToEdit.sort) {
|
|
158
184
|
setSort(viewToEdit.sort);
|
|
159
185
|
}
|
|
@@ -172,6 +198,6 @@ const ViewComposer = () => {
|
|
|
172
198
|
fontSize: '0.9em',
|
|
173
199
|
fontStyle: 'italic',
|
|
174
200
|
mb: 0.5
|
|
175
|
-
}), variant: "body2", children: t('hit.search.prompt') }), _jsx(HitQuery, { triggerSearch: search, searching: searching, onChange: (_query, isDirty) => setIsSearchDirty(isDirty) }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(HitSort, {}), _jsx(SearchSpan, { omitCustom: true }), _jsx("div", { style: { flex: 1 } }), _jsxs(Stack, { spacing: 1, direction: "row", alignItems: "center", sx: { flex: '0 !important', minWidth: '300px' }, children: [_jsx(Typography, { component: "span", children: t('view.settings.advance_on_triage') }), _jsx(Tooltip, { title: t('view.settings.advance_on_triage.description'), children: _jsx(HelpOutline, { sx: { fontSize: '16px' } }) }), _jsx(Checkbox, { size: "small", checked: advanceOnTriage, onChange: (_event, checked) => setAdvanceOnTriage(checked) })] })] }), response
|
|
201
|
+
}), variant: "body2", children: t('hit.search.prompt') }), _jsx(Stack, { direction: "row", width: "100%", spacing: 2, alignItems: "flex-start", paddingBottom: 1, children: _jsxs(Stack, { direction: "column", width: "100%", spacing: 1, children: [_jsx(HitQuery, { triggerSearch: search, searching: searching, onChange: (_query, isDirty) => setIsSearchDirty(isDirty) }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(HitSort, {}), _jsx(SearchSpan, { omitCustom: true }), _jsx("div", { style: { flex: 1 } }), _jsx(ChipPopper, { label: _jsx(Typography, { variant: "body2", children: t('view.settings') }), deleteIcon: _jsx(Settings, {}), toggleOnDelete: true, slotProps: { chip: { size: 'small' } }, placement: "bottom-end", children: _jsxs(Stack, { direction: "column", spacing: 1, children: [_jsxs(Stack, { spacing: 1, direction: "row", alignItems: "center", sx: { flex: '0 !important', minWidth: '300px' }, children: [_jsx(Typography, { component: "span", children: t('view.settings.advance_on_triage') }), _jsx(Tooltip, { title: t('view.settings.advance_on_triage.description'), children: _jsx(HelpOutline, { sx: { fontSize: '16px' } }) }), _jsx(FlexOne, {}), _jsx(Checkbox, { size: "small", checked: advanceOnTriage, onChange: (_event, checked) => setAdvanceOnTriage(checked) })] }), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", justifyContent: "space-between", children: [_jsx(Typography, { component: "span", children: t('view.settings.layout') }), _jsx(LayoutToggle, { displayType: displayType, setDisplayType: setDisplayType, size: "small", allowNullValue: true })] })] }) })] })] }) }), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "flex-end", justifyContent: "space-between", children: [response && (_jsx(SearchTotal, { total: response.total, pageLength: response.items.length, offset: response.offset, sx: theme => ({ color: theme.palette.text.secondary, fontSize: '0.9em', fontStyle: 'italic' }) })), _jsx(FlexOne, {}), displayType === 'grid' && (_jsx(AddColumnModal, { columns: columns, addColumn: key => isReady && setColumns(uniq([...columns, key])) }))] }), _jsx(LinearProgress, { sx: [!searching && { opacity: 0 }] })] }) }), _jsx(VSBoxContent, { children: displayType === 'grid' ? (_jsx(Stack, { component: Paper, spacing: 1, width: "100%", height: "100%", sx: { overflow: 'auto', flex: 1 }, children: _jsx(HitTable, { query: query, items: response?.items }) })) : (_jsxs(Stack, { spacing: 1, children: [!response?.total && _jsx(AppListEmpty, {}), response?.items.map(hit => (_jsx(HitCard, { id: hit.howler.id, layout: HitLayout.DENSE }, hit.howler.id)))] })) })] }) }) }) }));
|
|
176
202
|
};
|
|
177
203
|
export default ViewComposer;
|