@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.
Files changed (62) hide show
  1. package/components/app/App.js +4 -2
  2. package/components/app/providers/GridColumnsProvider.d.ts +52 -0
  3. package/components/app/providers/GridColumnsProvider.js +177 -0
  4. package/components/app/providers/GridColumnsProvider.test.js +255 -0
  5. package/components/elements/hit/HitContextMenu.test.d.ts +1 -0
  6. package/components/elements/hit/HitLabels.test.d.ts +1 -0
  7. package/components/elements/hit/HitLabels.test.js +77 -0
  8. package/components/elements/hit/HitLinks.d.ts +5 -4
  9. package/components/elements/hit/HitLinks.js +28 -10
  10. package/components/elements/hit/HitNotebooks.js +23 -22
  11. package/components/elements/hit/NotebookTooltip.d.ts +3 -0
  12. package/components/elements/hit/NotebookTooltip.js +8 -0
  13. package/components/elements/hit/PivotTooltip.d.ts +7 -0
  14. package/components/elements/hit/PivotTooltip.js +9 -0
  15. package/components/elements/hit/RelatedLinkTooltip.d.ts +6 -0
  16. package/components/elements/hit/RelatedLinkTooltip.js +8 -0
  17. package/components/elements/hit/ResolvePivotUrl.d.ts +4 -0
  18. package/components/elements/hit/ResolvePivotUrl.js +22 -0
  19. package/components/{routes/hits/search → elements/hit}/grid/ColumnHeader.d.ts +2 -1
  20. package/components/{routes/hits/search → elements/hit}/grid/ColumnHeader.js +3 -3
  21. package/components/{routes/hits/search → elements/hit}/grid/HitRow.d.ts +1 -1
  22. package/components/elements/hit/grid/HitTable.d.ts +12 -0
  23. package/components/elements/hit/grid/HitTable.js +82 -0
  24. package/components/elements/hit/related/PivotLink.d.ts +3 -0
  25. package/components/elements/hit/related/PivotLink.js +5 -2
  26. package/components/elements/hit/related/RelatedLink.d.ts +1 -0
  27. package/components/elements/hit/related/RelatedLink.js +18 -15
  28. package/components/elements/view/LayoutToggle.d.ts +8 -0
  29. package/components/elements/view/LayoutToggle.js +12 -0
  30. package/components/elements/view/ViewTitle.d.ts +3 -0
  31. package/components/elements/view/ViewTitle.js +6 -3
  32. package/components/routes/hits/search/HitBrowser.js +2 -1
  33. package/components/routes/hits/search/SearchPane.js +6 -8
  34. package/components/routes/hits/search/ViewLink.js +3 -2
  35. package/components/routes/hits/search/grid/HitGrid.js +17 -85
  36. package/components/routes/hits/search/shared/LayoutSettings.js +25 -0
  37. package/components/routes/hits/search/shared/SearchActionMenu.d.ts +4 -0
  38. package/components/routes/hits/search/shared/SearchActionMenu.js +17 -0
  39. package/components/routes/home/ViewCard.js +1 -1
  40. package/components/routes/views/ViewComposer.js +32 -6
  41. package/locales/en/translation.json +11 -3
  42. package/locales/fr/translation.json +11 -3
  43. package/models/entities/generated/Column.d.ts +7 -0
  44. package/models/entities/generated/Host.d.ts +1 -0
  45. package/models/entities/generated/Settings.d.ts +4 -0
  46. package/package.json +2 -1
  47. package/utils/stringUtils.d.ts +1 -0
  48. package/utils/stringUtils.js +7 -0
  49. package/utils/stringUtils.test.js +21 -1
  50. package/components/routes/hits/search/LayoutSettings.js +0 -24
  51. /package/components/{routes/hits/search/HitContextMenu.test.d.ts → app/providers/GridColumnsProvider.test.d.ts} +0 -0
  52. /package/components/{routes/hits/search → elements/hit}/HitContextMenu.d.ts +0 -0
  53. /package/components/{routes/hits/search → elements/hit}/HitContextMenu.js +0 -0
  54. /package/components/{routes/hits/search → elements/hit}/HitContextMenu.test.js +0 -0
  55. /package/components/{routes/hits/search → elements/hit}/grid/AddColumnModal.d.ts +0 -0
  56. /package/components/{routes/hits/search → elements/hit}/grid/AddColumnModal.js +0 -0
  57. /package/components/{routes/hits/search → elements/hit}/grid/EnhancedCell.d.ts +0 -0
  58. /package/components/{routes/hits/search → elements/hit}/grid/EnhancedCell.js +0 -0
  59. /package/components/{routes/hits/search → elements/hit}/grid/HitRow.js +0 -0
  60. /package/components/routes/hits/search/{BundleParentMenu.d.ts → shared/BundleParentMenu.d.ts} +0 -0
  61. /package/components/routes/hits/search/{BundleParentMenu.js → shared/BundleParentMenu.js} +0 -0
  62. /package/components/routes/hits/search/{LayoutSettings.d.ts → shared/LayoutSettings.d.ts} +0 -0
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Avatar, Backdrop, Box, Button, Chip, CircularProgress, ClickAwayListener, Fade, MenuItem, Paper, Popper, Stack, TextField } from '@mui/material';
2
+ import { Avatar, Backdrop, Box, Button, Chip, CircularProgress, ClickAwayListener, Fade, MenuItem, Paper, Popper, Stack, TextField, Tooltip } from '@mui/material';
3
3
  import api from '@cccsaurora/howler-ui/api';
4
4
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
5
5
  import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
6
+ import NotebookTooltip from '@cccsaurora/howler-ui/components/elements/hit/NotebookTooltip';
6
7
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
7
8
  import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
8
9
  import { memo, useCallback, useContext, useEffect, useState } from 'react';
@@ -124,26 +125,26 @@ const HitNotebooks = ({ analytic, selectedNotebook, hit, compact }) => {
124
125
  MenuProps: {
125
126
  style: { zIndex: 35001 }
126
127
  }
127
- }, defaultValue: selectedNotebook ? analytic?.notebooks.find(n => n.name === selectedNotebook).value : '', children: [_jsx(MenuItem, { disabled: true, value: "", children: _jsx("em", { children: t('hit.notebook.select') }) }), analytic?.notebooks.sort(safeStringPropertyCompare('detection')).map(e => (_jsx(MenuItem, { value: e.value, children: _jsxs(Stack, { direction: 'row', sx: { width: '100%' }, children: [e.name, e.detection && (_jsxs(_Fragment, { children: [_jsx(Box, { flex: 1 }), _jsx(Chip, { label: e.detection, size: "small" })] }))] }) }, e.value)))] }), _jsx(Button, { variant: "outlined", disabled: loadedNotebook.name === '' || envs.length === 0, color: "success", onClick: () => checkJupyhub(), children: t('hit.notebook.goTo') })] })] }) })) }), _jsx(HowlerCard, { variant: compact ? 'outlined' : 'elevation', onClick: handleToggle, sx: [
128
- theme => ({
129
- cursor: 'pointer',
130
- backgroundColor: 'transparent',
131
- transition: theme.transitions.create(['border-color']),
132
- '&:hover': { borderColor: 'primary.main', '& a': { textDecoration: 'underline' } },
133
- '& > div': {
134
- height: '100%'
135
- },
136
- '& a': { textDecoration: 'none', color: 'text.primary' }
137
- }),
138
- !compact && { border: 'thin solid', borderColor: 'transparent' }
139
- ], children: _jsxs(Stack, { direction: "row", p: 1, spacing: 1, alignItems: "center", children: [_jsx(Avatar, { variant: "rounded", src: '/images/jupyter_notebook_file_icon.png', sx: [
140
- theme => ({
141
- width: theme.spacing(3),
142
- height: theme.spacing(3),
143
- '& img': {
144
- objectFit: 'contain'
145
- }
146
- })
147
- ], children: 'jupyter_notebook_file_icon.png' }), t('hit.notebook.tooltip')] }) })] }) })));
128
+ }, defaultValue: selectedNotebook ? analytic?.notebooks.find(n => n.name === selectedNotebook).value : '', children: [_jsx(MenuItem, { disabled: true, value: "", children: _jsx("em", { children: t('hit.notebook.select') }) }), analytic?.notebooks.sort(safeStringPropertyCompare('detection')).map(e => (_jsx(MenuItem, { value: e.value, children: _jsxs(Stack, { direction: 'row', sx: { width: '100%' }, children: [e.name, e.detection && (_jsxs(_Fragment, { children: [_jsx(Box, { flex: 1 }), _jsx(Chip, { label: e.detection, size: "small" })] }))] }) }, e.value)))] }), _jsx(Button, { variant: "outlined", disabled: loadedNotebook.name === '' || envs.length === 0, color: "success", onClick: () => checkJupyhub(), children: t('hit.notebook.goTo') })] })] }) })) }), _jsx(Tooltip, { title: _jsx(NotebookTooltip, {}), children: _jsx("span", { children: _jsx(HowlerCard, { variant: compact ? 'outlined' : 'elevation', onClick: handleToggle, sx: [
129
+ theme => ({
130
+ cursor: 'pointer',
131
+ backgroundColor: 'transparent',
132
+ transition: theme.transitions.create(['border-color']),
133
+ '&:hover': { borderColor: 'primary.main', '& a': { textDecoration: 'underline' } },
134
+ '& > div': {
135
+ height: '100%'
136
+ },
137
+ '& a': { textDecoration: 'none', color: 'text.primary' }
138
+ }),
139
+ !compact && { border: 'thin solid', borderColor: 'transparent' }
140
+ ], children: _jsxs(Stack, { direction: "row", p: 1, spacing: 1, alignItems: "center", children: [_jsx(Avatar, { variant: "rounded", src: '/images/jupyter_notebook_file_icon.png', sx: [
141
+ theme => ({
142
+ width: theme.spacing(3),
143
+ height: theme.spacing(3),
144
+ '& img': {
145
+ objectFit: 'contain'
146
+ }
147
+ })
148
+ ], children: 'jupyter_notebook_file_icon.png' }), t('hit.notebook.tooltip')] }) }) }) })] }) })));
148
149
  };
149
150
  export default memo(HitNotebooks);
@@ -0,0 +1,3 @@
1
+ import type { FC } from 'react';
2
+ declare const NotebookTooltip: FC;
3
+ export default NotebookTooltip;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Typography } from '@mui/material';
3
+ import { useTranslation } from 'react-i18next';
4
+ const NotebookTooltip = () => {
5
+ const { t } = useTranslation();
6
+ return (_jsxs(Box, { sx: { maxWidth: 300 }, children: [_jsx(Typography, { variant: "subtitle2", children: t('notebook.title') }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: t('notebook.opens.panel') })] }));
7
+ };
8
+ export default NotebookTooltip;
@@ -0,0 +1,7 @@
1
+ import type { Dossier } from '@cccsaurora/howler-ui/models/entities/generated/Dossier';
2
+ import type { FC } from 'react';
3
+ declare const PivotTooltip: FC<{
4
+ dossier: Dossier;
5
+ resolvedUrl: string;
6
+ }>;
7
+ export default PivotTooltip;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Link, Typography } from '@mui/material';
3
+ import { useTranslation } from 'react-i18next';
4
+ const PivotTooltip = ({ dossier, resolvedUrl }) => {
5
+ const dossierUrl = `/dossiers/${dossier.dossier_id}/edit?tab=leads&query=${encodeURIComponent(dossier.query)}`;
6
+ const { t } = useTranslation();
7
+ return (_jsxs(Box, { sx: { maxWidth: 300 }, children: [_jsx(Typography, { variant: "subtitle2", children: dossier.title }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: dossier.owner }), _jsx(Typography, { variant: "body2", sx: { mt: 2 }, children: dossier.leads?.[0]?.content?.slice(0, 120) ?? t('pivot.description.none') }), _jsxs(Box, { sx: { mt: 2, wordBreak: 'break-all' }, children: [_jsx(Typography, { variant: "caption", display: "block", children: t('pivot.url') }), _jsx(Link, { href: resolvedUrl, target: "_blank", rel: "noopener noreferrer", underline: "hover", children: resolvedUrl })] }), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Link, { href: dossierUrl, target: "_blank", rel: "noopener noreferrer", underline: "hover", children: t('pivot.dossier.open') }) })] }));
8
+ };
9
+ export default PivotTooltip;
@@ -0,0 +1,6 @@
1
+ import type { FC } from 'react';
2
+ declare const RelatedLinkTooltip: FC<{
3
+ title: string;
4
+ href: string;
5
+ }>;
6
+ export default RelatedLinkTooltip;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Link, Typography } from '@mui/material';
3
+ import { useTranslation } from 'react-i18next';
4
+ const RelatedLinkTooltip = ({ title, href }) => {
5
+ const { t } = useTranslation();
6
+ return (_jsxs(Box, { sx: { maxWidth: 300 }, children: [_jsx(Typography, { variant: "subtitle2", children: title }), _jsx(Box, { sx: { mt: 1, wordBreak: 'break-all' }, children: _jsx(Link, { href: href, target: "_blank", rel: "noopener noreferrer", underline: "hover", children: href }) }), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Link, { href: href, target: "_blank", rel: "noopener noreferrer", underline: "hover", children: t('hit.header.link') }) })] }));
7
+ };
8
+ export default RelatedLinkTooltip;
@@ -0,0 +1,4 @@
1
+ import type { Dossier } from '@cccsaurora/howler-ui/models/entities/generated/Dossier';
2
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
+ declare const resolvePivotUrl: (pivot: NonNullable<Dossier["pivots"]>[number], currentHit?: Hit) => string;
4
+ export default resolvePivotUrl;
@@ -0,0 +1,22 @@
1
+ import Handlebars from 'handlebars';
2
+ import { flattenDeep } from '@cccsaurora/howler-ui/utils/utils';
3
+ const resolvePivotUrl = (pivot, currentHit) => {
4
+ const flatHit = flattenDeep(currentHit ?? {});
5
+ const templateObject = Object.fromEntries((pivot.mappings ?? []).map(mapping => {
6
+ const value = mapping.field === 'custom'
7
+ ? mapping.custom_value
8
+ : Array.isArray(flatHit[mapping.field])
9
+ ? flatHit[mapping.field][0]
10
+ : flatHit[mapping.field];
11
+ return [mapping.key, value];
12
+ }));
13
+ try {
14
+ return Handlebars.compile(pivot.value)(templateObject);
15
+ }
16
+ catch (e) {
17
+ // eslint-disable-next-line no-console
18
+ console.error(`Failed to compile pivot template for value "${pivot.value}":`, e);
19
+ return pivot.value;
20
+ }
21
+ };
22
+ export default resolvePivotUrl;
@@ -1,7 +1,8 @@
1
1
  import { type FC, type SetStateAction } from 'react';
2
2
  declare const ColumnHeader: FC<{
3
- width: string;
3
+ width: number;
4
4
  col: string;
5
+ colSource?: string[];
5
6
  setColumns: (val: SetStateAction<string[]>) => void;
6
7
  onMouseDown: (col: string, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
7
8
  }>;
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useSortable } from '@dnd-kit/sortable';
3
3
  import { CSS } from '@dnd-kit/utilities';
4
- import { DragIndicator, Remove } from '@mui/icons-material';
4
+ import { DragIndicator, JoinFull, Remove } from '@mui/icons-material';
5
5
  import { Box, IconButton, Stack, TableCell, Tooltip, useTheme } from '@mui/material';
6
6
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
7
7
  import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
8
8
  import { useContext } from 'react';
9
- const ColumnHeader = ({ col, width, setColumns, onMouseDown }) => {
9
+ const ColumnHeader = ({ col, width, colSource, setColumns, onMouseDown }) => {
10
10
  const theme = useTheme();
11
11
  const { config } = useContext(ApiConfigContext);
12
12
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: col });
@@ -15,7 +15,7 @@ const ColumnHeader = ({ col, width, setColumns, onMouseDown }) => {
15
15
  borderRightColor: 'divider',
16
16
  py: 0.5,
17
17
  position: 'relative'
18
- }, style: { transform: CSS.Translate.toString(transform), transition }, children: _jsxs(Stack, { className: `col-${col.replaceAll('.', '-')}`, direction: "row", spacing: 1, alignItems: "center", sx: [{ minWidth: '220px' }, !!width ? { width, maxWidth: width } : { maxWidth: '300px' }], children: [_jsx(Tooltip, { title: config.indexes.hit[col].description, children: _jsx("span", { children: col }) }), _jsx(FlexOne, {}), _jsx(IconButton, { size: "small", sx: { fontSize: '1rem' }, onClick: () => setColumns(_columns => _columns.filter(_col => _col !== col)), children: _jsx(Remove, { fontSize: "inherit" }) }), _jsx(DragIndicator, { fontSize: "small", sx: { cursor: 'grab' }, ...attributes, ...listeners }), _jsx(Box, { sx: {
18
+ }, style: { transform: CSS.Translate.toString(transform), transition }, children: _jsxs(Stack, { className: `col-${col.replaceAll('.', '-')}`, direction: "row", spacing: 1, alignItems: "center", sx: [{ minWidth: '220px' }, !!width ? { width, maxWidth: width } : { maxWidth: '300px' }], children: [_jsx(Tooltip, { title: config.indexes.hit[col].description, children: _jsx("span", { children: col }) }), colSource?.length > 1 && (_jsx(Tooltip, { title: colSource.join(', '), children: _jsx(JoinFull, { fontSize: "small" }) })), _jsx(FlexOne, {}), _jsx(IconButton, { size: "small", sx: { fontSize: '1rem' }, onClick: () => setColumns(_columns => _columns.filter(_col => _col !== col)), children: _jsx(Remove, { fontSize: "inherit" }) }), _jsx(DragIndicator, { fontSize: "small", sx: { cursor: 'grab' }, ...attributes, ...listeners }), _jsx(Box, { sx: {
19
19
  position: 'absolute',
20
20
  top: theme.spacing(0.75),
21
21
  bottom: theme.spacing(0.75),
@@ -3,7 +3,7 @@ declare const _default: import("react").NamedExoticComponent<{
3
3
  hit: Hit;
4
4
  analyticIds: Record<string, string>;
5
5
  columns: string[];
6
- columnWidths: Record<string, string>;
6
+ columnWidths: Record<string, number>;
7
7
  collapseMainColumn: boolean;
8
8
  onClick: (e: React.MouseEvent<HTMLDivElement>, hit: Hit) => void;
9
9
  }>;
@@ -0,0 +1,12 @@
1
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
+ import type { WithMetadata } from '@cccsaurora/howler-ui/models/WithMetadata';
3
+ import React, { type PropsWithChildren } from 'react';
4
+ declare const HitTable: ({ query, items, refreshItems, ContextMenu, contextMenuProps, onItemClick }: {
5
+ query: string;
6
+ items?: WithMetadata<Hit>[];
7
+ refreshItems?: (query: string, append?: boolean) => void;
8
+ ContextMenu?: React.FC<PropsWithChildren<object>>;
9
+ contextMenuProps?: object;
10
+ onItemClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>, hit: Hit) => void;
11
+ }) => import("react/jsx-runtime").JSX.Element;
12
+ export default HitTable;
@@ -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;
@@ -1,3 +1,4 @@
1
+ import type { Dossier } from '@cccsaurora/howler-ui/models/entities/generated/Dossier';
1
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
3
  import type { Pivot } from '@cccsaurora/howler-ui/models/entities/generated/Pivot';
3
4
  import { type FC } from 'react';
@@ -5,6 +6,8 @@ export interface PivotLinkProps {
5
6
  pivot: Pivot;
6
7
  hit: Hit;
7
8
  compact?: boolean;
9
+ dossier: Dossier;
10
+ resolvedUrl: string;
8
11
  }
9
12
  declare const PivotLink: FC<PivotLinkProps>;
10
13
  export default PivotLink;
@@ -3,6 +3,7 @@ import { ErrorOutline } from '@mui/icons-material';
3
3
  import { Tooltip } from '@mui/material';
4
4
  import { useHelpers } from '@cccsaurora/howler-ui/components/elements/display/handlebars/helpers';
5
5
  import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
6
+ import PivotTooltip from '@cccsaurora/howler-ui/components/elements/hit/PivotTooltip';
6
7
  import Handlebars from 'handlebars';
7
8
  import { isEmpty } from 'lodash-es';
8
9
  import React, { useMemo } from 'react';
@@ -10,7 +11,7 @@ import { useTranslation } from 'react-i18next';
10
11
  import { usePluginStore } from 'react-pluggable';
11
12
  import { flattenDeep } from '@cccsaurora/howler-ui/utils/utils';
12
13
  import RelatedLink from './RelatedLink';
13
- const PivotLink = ({ pivot, hit, compact = false }) => {
14
+ const PivotLink = ({ pivot, hit, compact = false, dossier, resolvedUrl }) => {
14
15
  const { i18n } = useTranslation();
15
16
  const helpers = useHelpers({ async: false, components: false });
16
17
  const pluginStore = usePluginStore();
@@ -47,11 +48,13 @@ const PivotLink = ({ pivot, hit, compact = false }) => {
47
48
  return handlebars.compile(pivot.value)(templateObject);
48
49
  }
49
50
  catch (e) {
51
+ // eslint-disable-next-line no-console
52
+ console.error(`Failed to compile pivot template for value "${pivot.value}":`, e);
50
53
  return pivot.value;
51
54
  }
52
55
  }, [flatHit, pivot, handlebars, helpers]);
53
56
  if (href) {
54
- return (_jsx(RelatedLink, { title: pivot.label[i18n.language], href: href, compact: compact, icon: pivot.icon, target: "_blank", rel: "noopener noreferrer" }));
57
+ return (_jsx(RelatedLink, { title: pivot.label[i18n.language], href: href, compact: compact, icon: pivot.icon, target: "_blank", rel: "noopener noreferrer", tooltip: _jsx(PivotTooltip, { dossier: dossier, resolvedUrl: resolvedUrl }) }));
55
58
  }
56
59
  // Hide a relatively useless console error, we'll show a UI component instead
57
60
  // eslint-disable-next-line no-console
@@ -6,5 +6,6 @@ declare const RelatedLink: React.FC<PropsWithChildren<{
6
6
  compact?: boolean;
7
7
  target?: string;
8
8
  rel?: string;
9
+ tooltip?: React.ReactNode;
9
10
  }>>;
10
11
  export default RelatedLink;
@@ -1,22 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Stack, Typography } from '@mui/material';
2
+ import { Stack, Tooltip, Typography } from '@mui/material';
3
3
  import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
4
+ import RelatedLinkTooltip from '@cccsaurora/howler-ui/components/elements/hit/RelatedLinkTooltip';
4
5
  import React, {} from 'react';
5
6
  import { Link } from 'react-router-dom';
6
7
  import RelatedIcon from './RelatedIcon';
7
- const RelatedLink = ({ icon, title, href, target, rel, compact = false, children }) => {
8
- return (_jsx(HowlerCard, { variant: compact ? 'outlined' : 'elevation', onClick: () => window.open(href, target), sx: [
9
- theme => ({
10
- cursor: 'pointer',
11
- backgroundColor: 'transparent',
12
- transition: theme.transitions.create(['border-color']),
13
- '&:hover': { borderColor: 'primary.main', '& a': { textDecoration: 'underline' } },
14
- '& > div': {
15
- height: '100%'
16
- },
17
- '& a': { textDecoration: 'none', color: 'text.primary' }
18
- }),
19
- !compact && { border: 'thin solid', borderColor: 'transparent' }
20
- ], children: _jsxs(Stack, { direction: "row", p: compact ? 0.5 : 1, spacing: 1, alignItems: "center", children: [children || _jsx(RelatedIcon, { icon: icon, title: title, href: href, compact: compact }), _jsx(Typography, { component: Link, to: href, target: target, rel: rel, onClick: e => e.stopPropagation(), children: title ?? href })] }) }, href));
8
+ const RelatedLink = ({ icon, title, href, target, rel, compact = false, tooltip, children }) => {
9
+ const safeTitle = title ?? href ?? '';
10
+ const tooltipContent = tooltip ?? _jsx(RelatedLinkTooltip, { title: safeTitle, href: href ?? '' });
11
+ return (_jsx(Tooltip, { title: tooltipContent, children: _jsx("div", { style: { display: 'flex' }, children: _jsx(HowlerCard, { variant: compact ? 'outlined' : 'elevation', onClick: () => href && window.open(href, target), sx: [
12
+ theme => ({
13
+ cursor: 'pointer',
14
+ backgroundColor: 'transparent',
15
+ transition: theme.transitions.create(['border-color']),
16
+ '&:hover': { borderColor: 'primary.main', '& a': { textDecoration: 'underline' } },
17
+ '& > div': {
18
+ height: '100%'
19
+ },
20
+ '& a': { textDecoration: 'none', color: 'text.primary' }
21
+ }),
22
+ !compact && { border: 'thin solid', borderColor: 'transparent' }
23
+ ], children: _jsxs(Stack, { direction: "row", p: compact ? 0.5 : 1, spacing: 1, alignItems: "center", children: [children || _jsx(RelatedIcon, { icon: icon, title: title, href: href, compact: compact }), _jsx(Typography, { component: Link, to: href ?? '#', target: target, rel: rel, onClick: e => e.stopPropagation(), children: safeTitle })] }) }, href) }) }));
21
24
  };
22
25
  export default RelatedLink;
@@ -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;
@@ -5,6 +5,9 @@ interface ViewTitleProps {
5
5
  query?: string;
6
6
  sort?: string;
7
7
  span?: string;
8
+ settings?: {
9
+ display?: string;
10
+ };
8
11
  }
9
12
  export declare const ViewTitle: FC<ViewTitleProps>;
10
13
  export {};
@@ -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] }), _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 })] }))] }));
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 { Close, ErrorOutline, SavedSearch, Terminal } from '@mui/icons-material';
3
- import { Box, IconButton, LinearProgress, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
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 { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
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, {}), 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, {})] })] }), _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 => ({
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);