@cccsaurora/howler-ui 2.19.0-dev.1085 → 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/elements/hit/HitLabels.test.d.ts +1 -0
- package/components/elements/hit/HitLabels.test.js +77 -0
- package/components/elements/hit/HitLinks.d.ts +5 -4
- package/components/elements/hit/HitLinks.js +28 -10
- package/components/elements/hit/HitNotebooks.js +23 -22
- package/components/elements/hit/NotebookTooltip.d.ts +3 -0
- package/components/elements/hit/NotebookTooltip.js +8 -0
- package/components/elements/hit/PivotTooltip.d.ts +7 -0
- package/components/elements/hit/PivotTooltip.js +9 -0
- package/components/elements/hit/RelatedLinkTooltip.d.ts +6 -0
- package/components/elements/hit/RelatedLinkTooltip.js +8 -0
- package/components/elements/hit/ResolvePivotUrl.d.ts +4 -0
- package/components/elements/hit/ResolvePivotUrl.js +22 -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/hit/related/PivotLink.d.ts +3 -0
- package/components/elements/hit/related/PivotLink.js +5 -2
- package/components/elements/hit/related/RelatedLink.d.ts +1 -0
- package/components/elements/hit/related/RelatedLink.js +18 -15
- 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 +11 -3
- package/locales/fr/translation.json +11 -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
|
@@ -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;
|
|
@@ -322,6 +322,8 @@
|
|
|
322
322
|
"modal.rule.title": "Create a New Rule",
|
|
323
323
|
"no.data": "No Data",
|
|
324
324
|
"none": "None",
|
|
325
|
+
"notebook.opens.panel": "Opens notebook panel",
|
|
326
|
+
"notebook.title": "Notebook",
|
|
325
327
|
"on": "on",
|
|
326
328
|
"open": "Open",
|
|
327
329
|
"operations.add_label": "Add Label",
|
|
@@ -378,8 +380,8 @@
|
|
|
378
380
|
"page.settings.local.details.drawer.description": "Instead of switching between a divided view and the drawer on different screen sizes, this setting forces Howler to always use the drawer.",
|
|
379
381
|
"page.settings.local.flatten.json": "Flatten JSON in JSON View",
|
|
380
382
|
"page.settings.local.flatten.json.description": "Instead of showing the object structure of the JSON, show it as flattened keys.",
|
|
381
|
-
"page.settings.local.hits.display_type": "
|
|
382
|
-
"page.settings.local.hits.display_type.description": "This setting allows you to decide whether to use the list or grid view
|
|
383
|
+
"page.settings.local.hits.display_type": "Display type for results",
|
|
384
|
+
"page.settings.local.hits.display_type.description": "This setting allows you to decide whether to use the list or grid view when searching.",
|
|
383
385
|
"page.settings.local.hits.display_type.grid": "Grid",
|
|
384
386
|
"page.settings.local.hits.display_type.list": "List",
|
|
385
387
|
"page.settings.local.hits.field_count": "Template Field Count",
|
|
@@ -427,6 +429,9 @@
|
|
|
427
429
|
"personalization.reset_text": "Reset to site defaults",
|
|
428
430
|
"personalization.showbreadcrumbs": "Show Breadcrumbs",
|
|
429
431
|
"personalization.sticky": "Sticky Topbar",
|
|
432
|
+
"pivot.description.none": "No description",
|
|
433
|
+
"pivot.dossier.open": "Open Dossier",
|
|
434
|
+
"pivot.url": "URL:",
|
|
430
435
|
"query": "Query",
|
|
431
436
|
"query.invalid": "Invalid query",
|
|
432
437
|
"quicksearch.aria": "search",
|
|
@@ -758,9 +763,12 @@
|
|
|
758
763
|
"usermenu.settings": "Settings",
|
|
759
764
|
"using": "using",
|
|
760
765
|
"view.assigned_to_me": "Assigned to Me",
|
|
766
|
+
"view.display.grid.inactive_warning": "The view has grid properties which cannot be applied",
|
|
761
767
|
"view.notfound": "This view is not available to you, or does not exist.",
|
|
762
768
|
"view.open": "Open View Query",
|
|
763
769
|
"view.refresh": "Refresh View",
|
|
770
|
+
"view.settings": "View Settings",
|
|
764
771
|
"view.settings.advance_on_triage": "Advance to next hit on triage",
|
|
765
|
-
"view.settings.advance_on_triage.description": "If this is enabled, triaging a hit on this view will automatically move to the next hit in line, making rapid triage of hits easier."
|
|
772
|
+
"view.settings.advance_on_triage.description": "If this is enabled, triaging a hit on this view will automatically move to the next hit in line, making rapid triage of hits easier.",
|
|
773
|
+
"view.settings.layout": "Default layout in search"
|
|
766
774
|
}
|
|
@@ -322,6 +322,8 @@
|
|
|
322
322
|
"modal.rule.title": "Créer une nouvelle règle",
|
|
323
323
|
"no.data": "Aucune donnée",
|
|
324
324
|
"none": "Rien",
|
|
325
|
+
"notebook.opens.panel": "Ouvre le panneau des notes",
|
|
326
|
+
"notebook.title": "Carnet",
|
|
325
327
|
"on": "sur",
|
|
326
328
|
"open": "Ouvert",
|
|
327
329
|
"operations.add_label": "Ajouter un label",
|
|
@@ -378,8 +380,8 @@
|
|
|
378
380
|
"page.settings.local.details.drawer.description": "Au lieu de basculer entre une vue divisée et le tiroir en fonction de la taille de l'écran, ce paramètre oblige Howler à toujours utiliser le tiroir.",
|
|
379
381
|
"page.settings.local.flatten.json": "Aplatir le JSON dans la vue JSON",
|
|
380
382
|
"page.settings.local.flatten.json.description": "Au lieu d'afficher la structure d'objet du JSON, affichez-la sous forme de clés aplaties",
|
|
381
|
-
"page.settings.local.hits.display_type": "Type d'affichage
|
|
382
|
-
"page.settings.local.hits.display_type.description": "Ce paramètre vous permet de choisir entre l'affichage sous forme de liste ou de grille
|
|
383
|
+
"page.settings.local.hits.display_type": "Type d'affichage pour les résultats",
|
|
384
|
+
"page.settings.local.hits.display_type.description": "Ce paramètre vous permet de choisir entre l'affichage sous forme de liste ou de grille lors d'une recherche.",
|
|
383
385
|
"page.settings.local.hits.display_type.grid": "Grille",
|
|
384
386
|
"page.settings.local.hits.display_type.list": "Liste",
|
|
385
387
|
"page.settings.local.hits.field_count": "Nombre de champs du modèle",
|
|
@@ -427,6 +429,9 @@
|
|
|
427
429
|
"personalization.reset_text": "Réinitialiser les paramètres",
|
|
428
430
|
"personalization.showbreadcrumbs": "Afficher fils d'ariane",
|
|
429
431
|
"personalization.sticky": "Barre supérieure collante",
|
|
432
|
+
"pivot.description.none": "Aucune description",
|
|
433
|
+
"pivot.dossier.open": "Ouvrir le dossier",
|
|
434
|
+
"pivot.url": "URL :",
|
|
430
435
|
"query": "Requête",
|
|
431
436
|
"query.invalid": "Requête invalide",
|
|
432
437
|
"quicksearch.aria": "recherche",
|
|
@@ -758,9 +763,12 @@
|
|
|
758
763
|
"usermenu.settings": "Paramètres",
|
|
759
764
|
"using": "utilisant",
|
|
760
765
|
"view.assigned_to_me": "Assigné à moi",
|
|
766
|
+
"view.display.grid.inactive_warning": "La vue possède des propriétés de grille qui ne peuvent pas être appliquées",
|
|
761
767
|
"view.notfound": "Cette vue n'est pas disponible pour vous, ou n'existe pas.",
|
|
762
768
|
"view.open": "Ouvrir requête de la vue",
|
|
763
769
|
"view.refresh": "Rafraîchir la vue",
|
|
770
|
+
"view.settings": "Paramètres de vue",
|
|
764
771
|
"view.settings.advance_on_triage": "Passer à la hit suivant lors du triage",
|
|
765
|
-
"view.settings.advance_on_triage.description": "Si cette option est activée, le triage d'une hit sur cette vue passera automatiquement à la hit suivant, ce qui facilitera le triage rapide des résultats."
|
|
772
|
+
"view.settings.advance_on_triage.description": "Si cette option est activée, le triage d'une hit sur cette vue passera automatiquement à la hit suivant, ce qui facilitera le triage rapide des résultats.",
|
|
773
|
+
"view.settings.layout": "Mise en page par défaut dans la recherche"
|
|
766
774
|
}
|
package/package.json
CHANGED
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"url": "https://github.com/CybercentreCanada/howler"
|
|
94
94
|
},
|
|
95
95
|
"type": "module",
|
|
96
|
-
"version": "2.19.0-dev.
|
|
96
|
+
"version": "2.19.0-dev.1143",
|
|
97
97
|
"exports": {
|
|
98
98
|
"./i18n": "./i18n.js",
|
|
99
99
|
"./index.css": "./index.css",
|
|
@@ -211,6 +211,7 @@
|
|
|
211
211
|
"./components/elements/hit/aggregate/*": "./components/elements/hit/aggregate/*.js",
|
|
212
212
|
"./components/elements/hit/outlines/*": "./components/elements/hit/outlines/*.js",
|
|
213
213
|
"./components/elements/hit/actions/*": "./components/elements/hit/actions/*.js",
|
|
214
|
+
"./components/elements/hit/grid/*": "./components/elements/hit/grid/*.js",
|
|
214
215
|
"./components/elements/hit/elements/*": "./components/elements/hit/elements/*.js",
|
|
215
216
|
"./components/elements/hit/outlines/al/*": "./components/elements/hit/outlines/al/*.js",
|
|
216
217
|
"./components/elements/addons/search/*": "./components/elements/addons/search/*.js",
|
package/utils/stringUtils.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export declare const sanitizeLuceneQuery: (query: string) => string;
|
|
|
6
6
|
export declare const safeStringPropertyCompare: (propertyPath: string) => (a: unknown, b: unknown) => any;
|
|
7
7
|
export declare const sanitizeMultilineLucene: (query: string) => string;
|
|
8
8
|
export declare const validateRegex: (regex: string) => boolean;
|
|
9
|
+
export declare const parsePixelSizeStringToInt: (size: string) => number;
|
package/utils/stringUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="vitest" />
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { maxLenStr, nameToInitials, safeFieldValue, safeFieldValueURI, safeStringPropertyCompare, sanitizeLuceneQuery, sanitizeMultilineLucene, validateRegex } from './stringUtils';
|
|
3
|
+
import { maxLenStr, nameToInitials, parsePixelSizeStringToInt, safeFieldValue, safeFieldValueURI, safeStringPropertyCompare, sanitizeLuceneQuery, sanitizeMultilineLucene, validateRegex } from './stringUtils';
|
|
4
4
|
describe('nameToInitials', () => {
|
|
5
5
|
it('returns initials from a standard first-last name', () => {
|
|
6
6
|
expect(nameToInitials('John Doe')).toEqual(['J', 'D']);
|
|
@@ -157,3 +157,23 @@ describe('validateRegex', () => {
|
|
|
157
157
|
expect(validateRegex('a{2,5}')).toBe(true);
|
|
158
158
|
});
|
|
159
159
|
});
|
|
160
|
+
describe('parsePixelSizeStringToInt', () => {
|
|
161
|
+
it('parses a pixel size string with "px" suffix', () => {
|
|
162
|
+
expect(parsePixelSizeStringToInt('100px')).toBe(100);
|
|
163
|
+
});
|
|
164
|
+
it('parses a pixel size string without "px" suffix', () => {
|
|
165
|
+
expect(parsePixelSizeStringToInt('200')).toBe(200);
|
|
166
|
+
});
|
|
167
|
+
it('parses a pixel size string with decimal value', () => {
|
|
168
|
+
expect(parsePixelSizeStringToInt('150.5px')).toBe(150);
|
|
169
|
+
});
|
|
170
|
+
it('returns null for a non-numeric string', () => {
|
|
171
|
+
expect(parsePixelSizeStringToInt('abc')).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
it('returns null for an empty string', () => {
|
|
174
|
+
expect(parsePixelSizeStringToInt('')).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
it('returns null for a string with an invalid unit', () => {
|
|
177
|
+
expect(parsePixelSizeStringToInt('50rem')).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowDropDown, InfoOutlined, List, Settings, TableChart, 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 { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
8
|
-
import { useTranslation } from 'react-i18next';
|
|
9
|
-
import { useContextSelector } from 'use-context-selector';
|
|
10
|
-
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
11
|
-
const LayoutSettings = () => {
|
|
12
|
-
const { t } = useTranslation();
|
|
13
|
-
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
14
|
-
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
15
|
-
const [hitLayout, setHitLayout] = useMyLocalStorageItem(StorageKey.HIT_LAYOUT, false);
|
|
16
|
-
const [templateFieldCount, setTemplateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
|
|
17
|
-
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" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", "aria-labelledby": "display_type", children: [_jsx(ToggleButton, { value: "list", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(List, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.list') })] }) }), _jsx(ToggleButton, { value: "grid", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(TableChart, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.grid') })] }) })] }), _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 => {
|
|
18
|
-
const val = parseInt(e.target.value);
|
|
19
|
-
if (!isNaN(val)) {
|
|
20
|
-
setTemplateFieldCount(Math.min(15, Math.max(0, val)));
|
|
21
|
-
}
|
|
22
|
-
}, inputProps: { min: 0, max: 15, 'aria-labelledby': 'field_count' } })] })] }) }));
|
|
23
|
-
};
|
|
24
|
-
export default LayoutSettings;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/components/routes/hits/search/{BundleParentMenu.d.ts → shared/BundleParentMenu.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|