@cccsaurora/howler-ui 2.12.0-dev.48 → 2.12.0-dev.65

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 (36) hide show
  1. package/components/app/providers/DossierProvider.d.ts +1 -0
  2. package/components/app/providers/DossierProvider.js +4 -1
  3. package/components/app/providers/HitProvider.d.ts +1 -0
  4. package/components/app/providers/HitProvider.js +4 -1
  5. package/components/app/providers/HitSearchProvider.d.ts +1 -0
  6. package/components/app/providers/HitSearchProvider.js +5 -1
  7. package/components/app/providers/ParameterProvider.d.ts +1 -0
  8. package/components/app/providers/ParameterProvider.js +4 -1
  9. package/components/app/providers/SocketProvider.js +1 -1
  10. package/components/app/providers/TemplateProvider.d.ts +1 -0
  11. package/components/app/providers/TemplateProvider.js +4 -1
  12. package/components/app/providers/ViewProvider.d.ts +1 -0
  13. package/components/app/providers/ViewProvider.js +4 -1
  14. package/components/elements/PluginTypography.js +1 -1
  15. package/components/hooks/useHitSelection.d.ts +1 -2
  16. package/components/hooks/useHitSelection.js +4 -3
  17. package/components/routes/hits/search/HitBrowser.js +1 -1
  18. package/components/routes/hits/search/HitContextMenu.d.ts +1 -0
  19. package/components/routes/hits/search/HitContextMenu.js +2 -2
  20. package/components/routes/hits/search/SearchPane.js +1 -1
  21. package/components/routes/hits/search/grid/AddColumnModal.js +7 -3
  22. package/components/routes/hits/search/grid/ColumnHeader.d.ts +8 -0
  23. package/components/routes/hits/search/grid/ColumnHeader.js +30 -0
  24. package/components/routes/hits/search/grid/EnhancedCell.js +18 -0
  25. package/components/routes/hits/search/grid/HitGrid.js +29 -26
  26. package/components/routes/hits/search/grid/HitRow.d.ts +1 -0
  27. package/components/routes/hits/search/grid/HitRow.js +3 -7
  28. package/components/routes/hits/search/shared/SearchSpan.js +1 -1
  29. package/components/routes/settings/LocalSection.js +3 -2
  30. package/locales/en/translation.json +4 -0
  31. package/locales/fr/translation.json +4 -0
  32. package/package.json +1 -1
  33. package/utils/constants.d.ts +2 -1
  34. package/utils/constants.js +1 -0
  35. package/components/routes/hits/search/grid/EnchancedCell.js +0 -18
  36. /package/components/routes/hits/search/grid/{EnchancedCell.d.ts → EnhancedCell.d.ts} +0 -0
@@ -12,4 +12,5 @@ export interface DossierContextType {
12
12
  }
13
13
  export declare const DossierContext: import("use-context-selector").Context<DossierContextType>;
14
14
  declare const DossierProvider: FC<PropsWithChildren>;
15
+ export declare const useDossierContextSelector: <Selected>(selector: (value: DossierContextType) => Selected) => Selected;
15
16
  export default DossierProvider;
@@ -3,7 +3,7 @@ import api from '@cccsaurora/howler-ui/api';
3
3
  import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
4
4
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
5
5
  import { useCallback, useEffect, useState } from 'react';
6
- import { createContext } from 'use-context-selector';
6
+ import { createContext, useContextSelector } from 'use-context-selector';
7
7
  export const DossierContext = createContext(null);
8
8
  const DossierProvider = ({ children }) => {
9
9
  const { dispatchApi } = useMyApi();
@@ -76,4 +76,7 @@ const DossierProvider = ({ children }) => {
76
76
  getMatchingDossiers
77
77
  }, children: children }));
78
78
  };
79
+ export const useDossierContextSelector = (selector) => {
80
+ return useContextSelector(DossierContext, selector);
81
+ };
79
82
  export default DossierProvider;
@@ -17,4 +17,5 @@ export declare const HitContext: import("use-context-selector").Context<HitProvi
17
17
  * Central repository for storing individual hit data across the application. Allows efficient retrieval of hits across componenents.
18
18
  */
19
19
  declare const HitProvider: FC<PropsWithChildren>;
20
+ export declare const useHitContextSelector: <Selected>(selector: (value: HitProviderType) => Selected) => Selected;
20
21
  export default HitProvider;
@@ -3,7 +3,7 @@ import api from '@cccsaurora/howler-ui/api';
3
3
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
4
4
  import { uniq } from 'lodash-es';
5
5
  import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
6
- import { createContext } from 'use-context-selector';
6
+ import { createContext, useContextSelector } from 'use-context-selector';
7
7
  import { SocketContext } from './SocketProvider';
8
8
  export const HitContext = createContext(null);
9
9
  /**
@@ -101,4 +101,7 @@ const HitProvider = ({ children }) => {
101
101
  loadHits
102
102
  }, children: children }));
103
103
  };
104
+ export const useHitContextSelector = (selector) => {
105
+ return useContextSelector(HitContext, selector);
106
+ };
104
107
  export default HitProvider;
@@ -22,4 +22,5 @@ interface HitSearchProviderType {
22
22
  }
23
23
  export declare const HitSearchContext: import("use-context-selector").Context<HitSearchProviderType>;
24
24
  declare const HitSearchProvider: FC<PropsWithChildren>;
25
+ export declare const useHitSearchContextSelector: <Selected>(selector: (value: HitSearchProviderType) => Selected) => Selected;
25
26
  export default HitSearchProvider;
@@ -11,6 +11,7 @@ import { isMobile } from 'react-device-detect';
11
11
  import { useLocation, useParams } from 'react-router-dom';
12
12
  import { createContext, useContextSelector } from 'use-context-selector';
13
13
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
14
+ import { getStored } from '@cccsaurora/howler-ui/utils/localStorage';
14
15
  import Throttler from '@cccsaurora/howler-ui/utils/Throttler';
15
16
  import { convertCustomDateRangeToLucene, convertDateToLucene } from '@cccsaurora/howler-ui/utils/utils';
16
17
  import { HitContext } from './HitProvider';
@@ -37,7 +38,7 @@ const HitSearchProvider = ({ children }) => {
37
38
  const startDate = useContextSelector(ParameterContext, ctx => ctx.startDate);
38
39
  const endDate = useContextSelector(ParameterContext, ctx => ctx.endDate);
39
40
  const loadHits = useContextSelector(HitContext, ctx => ctx.loadHits);
40
- const [displayType, setDisplayType] = useState('list');
41
+ const [displayType, setDisplayType] = useState(getStored(StorageKey.DISPLAY_TYPE) ?? 'list');
41
42
  const [searching, setSearching] = useState(false);
42
43
  const [error, setError] = useState(null);
43
44
  const [response, setResponse] = useState();
@@ -165,4 +166,7 @@ const HitSearchProvider = ({ children }) => {
165
166
  setFzfSearch
166
167
  }, children: children }));
167
168
  };
169
+ export const useHitSearchContextSelector = (selector) => {
170
+ return useContextSelector(HitSearchContext, selector);
171
+ };
168
172
  export default HitSearchProvider;
@@ -22,4 +22,5 @@ export declare const ParameterContext: import("use-context-selector").Context<Pa
22
22
  * Context responsible for tracking updates to query operations in hit and view search.
23
23
  */
24
24
  declare const ParameterProvider: FC<PropsWithChildren>;
25
+ export declare const useParameterContextSelector: <Selected>(selector: (value: ParameterProviderType) => Selected) => Selected;
25
26
  export default ParameterProvider;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { isEmpty, isNull, isUndefined, omitBy, pickBy } from 'lodash-es';
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { useLocation, useParams, useSearchParams } from 'react-router-dom';
5
- import { createContext } from 'use-context-selector';
5
+ import { createContext, useContextSelector } from 'use-context-selector';
6
6
  import Throttler from '@cccsaurora/howler-ui/utils/Throttler';
7
7
  export const ParameterContext = createContext(null);
8
8
  const DEFAULT_VALUES = {
@@ -177,4 +177,7 @@ const ParameterProvider = ({ children }) => {
177
177
  setFilter: useMemo(() => set('filter'), [set])
178
178
  }, children: children }));
179
179
  };
180
+ export const useParameterContextSelector = (selector) => {
181
+ return useContextSelector(ParameterContext, selector);
182
+ };
180
183
  export default ParameterProvider;
@@ -121,7 +121,7 @@ const SocketProvider = ({ children }) => {
121
121
  setRetry(false);
122
122
  // Here we go!
123
123
  setStatus(Status.CONNECTING);
124
- const host = window.location.host.startsWith('localhost') ? 'localhost:5000' : window.location.host;
124
+ const host = window.location.host;
125
125
  const protocol = window.location.protocol.startsWith('http:') ? 'ws' : 'wss';
126
126
  const ws = new WebSocket(`${protocol}://${host}/socket/v1/connect`);
127
127
  // Add our listeners to the websocket
@@ -10,4 +10,5 @@ interface TemplateContextType {
10
10
  }
11
11
  export declare const TemplateContext: import("use-context-selector").Context<TemplateContextType>;
12
12
  declare const TemplateProvider: FC<PropsWithChildren>;
13
+ export declare const useTemplateContextSelector: <Selected>(selector: (value: TemplateContextType) => Selected) => Selected;
13
14
  export default TemplateProvider;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import api from '@cccsaurora/howler-ui/api';
3
3
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
4
4
  import { useCallback, useRef, useState } from 'react';
5
- import { createContext } from 'use-context-selector';
5
+ import { createContext, useContextSelector } from 'use-context-selector';
6
6
  const SIX_TAIL_PHISH_DETAILS = [
7
7
  'event.start',
8
8
  'event.end',
@@ -97,4 +97,7 @@ const TemplateProvider = ({ children }) => {
97
97
  }, []);
98
98
  return (_jsx(TemplateContext.Provider, { value: { templates, getTemplates, getMatchingTemplate, refresh, loaded }, children: children }));
99
99
  };
100
+ export const useTemplateContextSelector = (selector) => {
101
+ return useContextSelector(TemplateContext, selector);
102
+ };
100
103
  export default TemplateProvider;
@@ -15,4 +15,5 @@ export interface ViewContextType {
15
15
  }
16
16
  export declare const ViewContext: import("use-context-selector").Context<ViewContextType>;
17
17
  declare const ViewProvider: FC<PropsWithChildren>;
18
+ export declare const useViewContextSelector: <Selected>(selector: (value: ViewContextType) => Selected) => Selected;
18
19
  export default ViewProvider;
@@ -5,7 +5,7 @@ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
5
5
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
6
6
  import { useCallback, useEffect, useState } from 'react';
7
7
  import { useLocation, useParams } from 'react-router-dom';
8
- import { createContext } from 'use-context-selector';
8
+ import { createContext, useContextSelector } from 'use-context-selector';
9
9
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
10
10
  export const ViewContext = createContext(null);
11
11
  const ViewProvider = ({ children }) => {
@@ -96,4 +96,7 @@ const ViewProvider = ({ children }) => {
96
96
  getCurrentView
97
97
  }, children: children }));
98
98
  };
99
+ export const useViewContextSelector = (selector) => {
100
+ return useContextSelector(ViewContext, selector);
101
+ };
99
102
  export default ViewProvider;
@@ -16,6 +16,6 @@ const PluginTypography = ({ children, value, context, ...props }) => {
16
16
  return component;
17
17
  }
18
18
  }
19
- return _jsx(Typography, { ...props, children: children });
19
+ return _jsx(Typography, { ...props, children: children ?? value });
20
20
  };
21
21
  export default PluginTypography;
@@ -1,7 +1,6 @@
1
- import type { HowlerSearchResponse } from '@cccsaurora/howler-ui/api/search';
2
1
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
2
  import type React from 'react';
4
- declare const useHitSelection: (response?: HowlerSearchResponse<Hit>) => {
3
+ declare const useHitSelection: () => {
5
4
  lastSelected: string;
6
5
  setLastSelected: React.Dispatch<React.SetStateAction<string>>;
7
6
  onClick: (e: React.MouseEvent<HTMLDivElement>, hit: Hit) => void;
@@ -1,14 +1,16 @@
1
1
  import { useAppBreadcrumbs } from '@cccsaurora/howler-ui/commons/components/app/hooks';
2
2
  import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
3
+ import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
3
4
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
4
5
  import useMySitemap from '@cccsaurora/howler-ui/components/hooks/useMySitemap';
5
6
  import { useCallback, useState } from 'react';
6
7
  import { useNavigate } from 'react-router-dom';
7
8
  import { useContextSelector } from 'use-context-selector';
8
- const useHitSelection = (response) => {
9
+ const useHitSelection = () => {
9
10
  const navigate = useNavigate();
10
11
  const { setItems } = useAppBreadcrumbs();
11
12
  const { routes } = useMySitemap();
13
+ const response = useContextSelector(HitSearchContext, ctx => ctx.response);
12
14
  const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
13
15
  const addHitToSelection = useContextSelector(HitContext, ctx => ctx.addHitToSelection);
14
16
  const removeHitFromSelection = useContextSelector(HitContext, ctx => ctx.removeHitFromSelection);
@@ -65,11 +67,10 @@ const useHitSelection = (response) => {
65
67
  lastSelected,
66
68
  navigate,
67
69
  removeHitFromSelection,
68
- response?.items,
70
+ response,
69
71
  routes,
70
72
  selectedHits,
71
73
  setItems,
72
- setLastSelected,
73
74
  setSelected
74
75
  ]);
75
76
  return { lastSelected, setLastSelected, onClick };
@@ -118,7 +118,7 @@ const HitBrowser = () => {
118
118
  }
119
119
  // eslint-disable-next-line react-hooks/exhaustive-deps
120
120
  }, [addHitToSelection, location.pathname, removeHitFromSelection, response, routeParams.id, selected, setSelected]);
121
- return (_jsxs(Stack, { direction: "row", flex: 1, sx: { overflow: 'hidden' }, children: [_jsxs(Box, { position: "relative", flex: 1, height: "100%", display: "flex", sx: [{ overflow: 'auto' }, displayType === 'list' && !isNull(searchPaneWidth) && { maxWidth: searchPaneWidth }], children: [_jsx(ErrorBoundary, { children: displayType === 'list' ? _jsx(SearchPane, {}) : _jsx(HitGrid, {}) }), _jsx(Collapse, { in: showSelectBar, unmountOnExit: true, sx: {
121
+ return (_jsxs(Stack, { direction: "row", flex: 1, sx: { overflow: 'hidden' }, children: [_jsxs(Box, { position: "relative", flex: 1.15, height: "100%", display: "flex", sx: [{ overflow: 'auto' }, displayType === 'list' && !isNull(searchPaneWidth) && { maxWidth: searchPaneWidth }], children: [_jsx(ErrorBoundary, { children: displayType === 'list' ? _jsx(SearchPane, {}) : _jsx(HitGrid, {}) }), _jsx(Collapse, { in: showSelectBar, unmountOnExit: true, sx: {
122
122
  position: 'absolute',
123
123
  bottom: 0,
124
124
  left: 0,
@@ -1,6 +1,7 @@
1
1
  import type { FC, PropsWithChildren } from 'react';
2
2
  interface HitContextMenuProps {
3
3
  getSelectedId: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => string;
4
+ Component?: React.ElementType;
4
5
  }
5
6
  declare const HitContextMenu: FC<PropsWithChildren<HitContextMenuProps>>;
6
7
  export default HitContextMenu;
@@ -22,7 +22,7 @@ const ICON_MAP = {
22
22
  vote: _jsx(HowToVote, {}),
23
23
  action: _jsx(Edit, {})
24
24
  };
25
- const HitContextMenu = ({ children, getSelectedId }) => {
25
+ const HitContextMenu = ({ children, getSelectedId, Component = Box }) => {
26
26
  const { t } = useTranslation();
27
27
  const analyticContext = useContext(AnalyticContext);
28
28
  const { dispatchApi } = useMyApi();
@@ -104,7 +104,7 @@ const HitContextMenu = ({ children, getSelectedId }) => {
104
104
  setAnalytic(null);
105
105
  }
106
106
  }, [anchorEl]);
107
- return (_jsxs(Box, { id: "contextMenu", onContextMenu: onContextMenu, children: [children, _jsxs(Menu, { id: "hit-menu", open: !!anchorEl, anchorEl: anchorEl, onClose: () => setAnchorEl(null), slotProps: {
107
+ return (_jsxs(Component, { id: "contextMenu", onContextMenu: onContextMenu, children: [children, _jsxs(Menu, { id: "hit-menu", open: !!anchorEl, anchorEl: anchorEl, onClose: () => setAnchorEl(null), slotProps: {
108
108
  paper: {
109
109
  sx: {
110
110
  transform: `translate(${clickLocation[0]}px, ${clickLocation[1]}px) !important`,
@@ -94,7 +94,7 @@ const SearchPane = () => {
94
94
  const response = useContextSelector(HitSearchContext, ctx => ctx.response);
95
95
  const error = useContextSelector(HitSearchContext, ctx => ctx.error);
96
96
  const viewId = useContextSelector(HitSearchContext, ctx => ctx.viewId);
97
- const { onClick } = useHitSelection(response);
97
+ const { onClick } = useHitSelection();
98
98
  const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
99
99
  const clearSelectedHits = useContextSelector(HitContext, ctx => ctx.clearSelectedHits);
100
100
  const bundleHit = useContextSelector(HitContext, ctx => location.pathname.startsWith('/bundles') ? ctx.hits[routeParams.id] : null);
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Add, Check } from '@mui/icons-material';
3
- import { Autocomplete, Chip, Divider, Grid, Popover, Stack, TextField } from '@mui/material';
3
+ import { Autocomplete, Chip, Divider, Grid, IconButton, Popover, Stack, TextField } from '@mui/material';
4
4
  import { FieldContext } from '@cccsaurora/howler-ui/components/app/providers/FieldProvider';
5
5
  import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
6
6
  import { TemplateContext } from '@cccsaurora/howler-ui/components/app/providers/TemplateProvider';
7
7
  import { sortBy, uniq } from 'lodash-es';
8
- import { memo, useContext, useMemo } from 'react';
8
+ import { memo, useContext, useMemo, useState } from 'react';
9
9
  import { useTranslation } from 'react-i18next';
10
10
  import { useContextSelector } from 'use-context-selector';
11
11
  const AddColumnModal = ({ open, onClose, anchorEl, addColumn, columns }) => {
@@ -13,9 +13,13 @@ const AddColumnModal = ({ open, onClose, anchorEl, addColumn, columns }) => {
13
13
  const { hitFields } = useContext(FieldContext);
14
14
  const response = useContextSelector(HitSearchContext, ctx => ctx.response);
15
15
  const getMatchingTemplate = useContextSelector(TemplateContext, ctx => ctx.getMatchingTemplate);
16
+ const [columnToAdd, setColumnToAdd] = useState(null);
16
17
  const options = useMemo(() => hitFields.map(field => field.key), [hitFields]);
17
18
  const suggestions = useMemo(() => uniq((response?.items ?? []).flatMap(_hit => getMatchingTemplate(_hit)?.keys ?? [])), [getMatchingTemplate, response?.items]);
18
- return (_jsx(Popover, { open: open, onClose: onClose, anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsxs(Stack, { spacing: 1, p: 1, width: "500px", children: [_jsx(Autocomplete, { options: options, renderInput: params => _jsx(TextField, { fullWidth: true, placeholder: t('hit.fields'), ...params }) }), _jsx(Divider, { orientation: "horizontal" }), _jsx(Grid, { container: true, spacing: 1, children: sortBy(suggestions.map(key => ({ key, used: columns.includes(key) })), 'used').map(({ key, used }) => {
19
+ return (_jsx(Popover, { open: open, onClose: onClose, anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsxs(Stack, { spacing: 1, p: 1, width: "500px", children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Autocomplete, { sx: { flex: 1 }, size: "small", options: options, value: columnToAdd, renderInput: params => _jsx(TextField, { fullWidth: true, placeholder: t('hit.fields'), ...params }), onChange: (_ev, value) => setColumnToAdd(value) }), _jsx(IconButton, { disabled: !columnToAdd, onClick: () => {
20
+ addColumn(columnToAdd);
21
+ setColumnToAdd(null);
22
+ }, children: _jsx(Add, {}) })] }), _jsx(Divider, { orientation: "horizontal" }), _jsx(Grid, { container: true, spacing: 1, children: sortBy(suggestions.map(key => ({ key, used: columns.includes(key) })), 'used').map(({ key, used }) => {
19
23
  return (_jsx(Grid, { item: true, children: _jsx(Chip, { size: "small", variant: "outlined", color: used ? 'success' : 'default', label: key, icon: used ? _jsx(Check, {}) : _jsx(Add, {}), onClick: () => addColumn(key), disabled: used }) }, key));
20
24
  }) })] }) }));
21
25
  };
@@ -0,0 +1,8 @@
1
+ import { type FC, type SetStateAction } from 'react';
2
+ declare const ColumnHeader: FC<{
3
+ width: string;
4
+ col: string;
5
+ setColumns: (val: SetStateAction<string[]>) => void;
6
+ onMouseDown: (col: string, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
7
+ }>;
8
+ export default ColumnHeader;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useSortable } from '@dnd-kit/sortable';
3
+ import { CSS } from '@dnd-kit/utilities';
4
+ import { DragIndicator, Remove } from '@mui/icons-material';
5
+ import { Box, IconButton, Stack, TableCell, Tooltip, useTheme } from '@mui/material';
6
+ import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
7
+ import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
8
+ import { useContext } from 'react';
9
+ const ColumnHeader = ({ col, width, setColumns, onMouseDown }) => {
10
+ const theme = useTheme();
11
+ const { config } = useContext(ApiConfigContext);
12
+ const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: col });
13
+ return (_jsx(TableCell, { ref: setNodeRef, sx: {
14
+ borderRight: 'thin solid',
15
+ borderRightColor: 'divider',
16
+ py: 0.5,
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: {
19
+ position: 'absolute',
20
+ top: theme.spacing(0.75),
21
+ bottom: theme.spacing(0.75),
22
+ right: -3,
23
+ width: '5px',
24
+ borderRight: 'thin solid',
25
+ borderLeft: 'thin solid',
26
+ borderColor: 'divider',
27
+ cursor: 'col-resize'
28
+ }, onMouseDown: e => onMouseDown(col, e) })] }) }));
29
+ };
30
+ export default ColumnHeader;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable react/no-array-index-key */
3
+ import { Stack, TableCell } from '@mui/material';
4
+ import PluginTypography from '@cccsaurora/howler-ui/components/elements/PluginTypography';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ const EnhancedCell = ({ value: rawValue, sx = {}, className }) => {
8
+ const { t } = useTranslation();
9
+ if (!rawValue) {
10
+ return _jsx(TableCell, { style: { borderBottom: 'none' }, children: t('none') });
11
+ }
12
+ const values = (Array.isArray(rawValue) ? rawValue : [rawValue]).filter(_value => !!_value);
13
+ return (_jsx(TableCell, { sx: { borderBottom: 'none', borderRight: 'thin solid', borderRightColor: 'divider', fontSize: '0.8rem' }, children: _jsx(Stack, { direction: "row", className: className, spacing: 0.5, sx: [
14
+ { display: 'flex', justifyContent: 'start', width: '100%', overflow: 'hidden' },
15
+ ...(Array.isArray(sx) ? sx : [sx])
16
+ ], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, children: value }, value + index))) }) }));
17
+ };
18
+ export default memo(EnhancedCell);
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Add, FormatIndentDecrease, FormatIndentIncrease, Info, List, Remove, Search, TableChart } from '@mui/icons-material';
3
- import { Box, IconButton, LinearProgress, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, ToggleButton, ToggleButtonGroup, Tooltip, Typography, useTheme } from '@mui/material';
2
+ import { DndContext, KeyboardSensor, PointerSensor, pointerWithin, useSensor, useSensors } from '@dnd-kit/core';
3
+ import { arrayMove, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
4
+ import { Add, FormatIndentDecrease, FormatIndentIncrease, Info, List, Search, TableChart } from '@mui/icons-material';
5
+ import { IconButton, LinearProgress, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, ToggleButton, ToggleButtonGroup, Typography, useTheme } from '@mui/material';
4
6
  import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
5
- import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
6
7
  import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
7
8
  import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
8
9
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
@@ -11,24 +12,28 @@ import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/Fle
11
12
  import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
12
13
  import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentBanner';
13
14
  import DevelopmentIcon from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentIcon';
15
+ import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
14
16
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
15
17
  import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
16
18
  import { useTranslation } from 'react-i18next';
17
19
  import { useLocation, useParams } from 'react-router-dom';
18
20
  import { useContextSelector } from 'use-context-selector';
19
21
  import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
22
+ import HitContextMenu from '../HitContextMenu';
20
23
  import HitQuery from '../HitQuery';
21
24
  import QuerySettings from '../shared/QuerySettings';
22
25
  import ViewLink from '../ViewLink';
23
26
  import AddColumnModal from './AddColumnModal';
27
+ import ColumnHeader from './ColumnHeader';
24
28
  import HitRow from './HitRow';
25
29
  const HitGrid = () => {
26
30
  const { t } = useTranslation();
27
- const { config } = useContext(ApiConfigContext);
28
31
  const { getIdFromName } = useContext(AnalyticContext);
29
32
  const routeParams = useParams();
30
33
  const location = useLocation();
31
34
  const theme = useTheme();
35
+ const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
36
+ const { onClick } = useHitSelection();
32
37
  const search = useContextSelector(HitSearchContext, ctx => ctx.search);
33
38
  const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
34
39
  const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
@@ -92,6 +97,8 @@ const HitGrid = () => {
92
97
  window.removeEventListener('mouseup', onMouseUp);
93
98
  }, [onMouseMove]);
94
99
  const onMouseDown = useCallback((col, event) => {
100
+ event.stopPropagation();
101
+ event.preventDefault();
95
102
  resizingCol.current = [col, event.target.parentElement];
96
103
  window.addEventListener('mousemove', onMouseMove);
97
104
  window.addEventListener('mouseup', onMouseUp);
@@ -102,6 +109,22 @@ const HitGrid = () => {
102
109
  search(query, true);
103
110
  }
104
111
  }, [query, search]);
112
+ const handleDragEnd = useCallback((event) => {
113
+ const { active, over } = event;
114
+ if (active.id !== over.id) {
115
+ const oldIndex = (columns ?? []).findIndex(entry => entry === active.id);
116
+ const newIndex = (columns ?? []).findIndex(entry => entry === over.id);
117
+ setColumns(arrayMove(columns, oldIndex, newIndex));
118
+ }
119
+ }, [columns]);
120
+ const getSelectedId = useCallback((event) => {
121
+ const target = event.target;
122
+ const selectedElement = target.closest('[id]');
123
+ if (!selectedElement) {
124
+ return;
125
+ }
126
+ return selectedElement.id;
127
+ }, []);
105
128
  return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsx(ViewLink, {}), _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(DevelopmentIcon, {})] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Stack, { position: "relative", flex: 1, children: [_jsx(HitQuery, { disabled: viewId && !selectedView, searching: searching, triggerSearch: search, compact: true }), searching && (_jsx(LinearProgress, { sx: {
106
129
  position: 'absolute',
107
130
  left: 0,
@@ -109,29 +132,9 @@ const HitGrid = () => {
109
132
  bottom: 0,
110
133
  borderBottomLeftRadius: theme.shape.borderRadius,
111
134
  borderBottomRightRadius: theme.shape.borderRadius
112
- } }))] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] }), _jsxs(Stack, { direction: "row", spacing: 1, width: "100%", sx: { '& > *': { flex: 1 } }, children: [_jsx(QuerySettings, {}), response && _jsx(SearchTotal, { offset: response.offset, pageLength: response.rows, total: response.total }), _jsxs(Stack, { direction: "row", children: [_jsx(FlexOne, {}), _jsx(IconButton, { ref: columnModalRef, onClick: () => setShowAddColumn(true), children: _jsx(Add, { fontSize: "small" }) })] }), _jsx(AddColumnModal, { anchorEl: columnModalRef.current, open: showAddColumn, onClose: () => setShowAddColumn(false), columns: columns, addColumn: key => setColumns(_columns => [..._columns, key]) })] }), _jsxs(Stack, { component: Paper, spacing: 1, width: "100%", height: "100%", sx: { overflow: 'auto', flex: 1 }, onScroll: onScroll, children: [_jsxs(Table, { sx: { '& td,th': { px: 1, py: 0.25, whiteSpace: 'nowrap' } }, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { sx: {
135
+ } }))] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] }), _jsxs(Stack, { direction: "row", spacing: 1, width: "100%", sx: { '& > *': { flex: 1 } }, children: [_jsx(QuerySettings, {}), response && (_jsx(SearchTotal, { sx: { alignSelf: 'center' }, color: "text.secondary", offset: response.offset, pageLength: response.rows, total: response.total })), _jsxs(Stack, { direction: "row", children: [_jsx(FlexOne, {}), _jsx(IconButton, { ref: columnModalRef, onClick: () => setShowAddColumn(true), children: _jsx(Add, { fontSize: "small" }) })] }), _jsx(AddColumnModal, { anchorEl: columnModalRef.current, open: showAddColumn, onClose: () => setShowAddColumn(false), columns: columns, addColumn: key => setColumns(_columns => [..._columns, key]) })] }), _jsxs(Stack, { component: Paper, spacing: 1, width: "100%", height: "100%", sx: { overflow: 'auto', flex: 1 }, onScroll: onScroll, children: [_jsxs(Table, { sx: { '& td,th': { px: 1, py: 0.25, whiteSpace: 'nowrap' } }, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { sx: {
113
136
  borderRight: 'thin solid',
114
137
  borderRightColor: 'divider'
115
- }, children: _jsx(IconButton, { onClick: () => setCollapseMainColumn(!collapseMainColumn), children: collapseMainColumn ? (_jsx(FormatIndentIncrease, { fontSize: "small" })) : (_jsx(FormatIndentDecrease, { fontSize: "small" })) }) }), columns.map(col => (_jsx(TableCell, { sx: {
116
- borderRight: 'thin solid',
117
- borderRightColor: 'divider',
118
- py: 0.5,
119
- position: 'relative'
120
- }, children: _jsxs(Stack, { className: `col-${col.replaceAll('.', '-')}`, direction: "row", spacing: 1, alignItems: "center", sx: [
121
- { minWidth: '150px' },
122
- !!columnWidths[col]
123
- ? { width: columnWidths[col], maxWidth: columnWidths[col] }
124
- : { maxWidth: '300px' }
125
- ], 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(Box, { sx: {
126
- position: 'absolute',
127
- top: theme.spacing(0.75),
128
- bottom: theme.spacing(0.75),
129
- right: -3,
130
- width: '5px',
131
- borderRight: 'thin solid',
132
- borderLeft: 'thin solid',
133
- borderColor: 'divider',
134
- cursor: 'col-resize'
135
- }, onMouseDown: e => onMouseDown(col, e) })] }) }, col))), _jsx(TableCell, { sx: { width: '100%' } })] }) }), _jsxs(TableBody, { children: [response?.items.map(hit => (_jsx(HitRow, { hit: hit, analyticIds: analyticIds, columns: columns, columnWidths: columnWidths, collapseMainColumn: collapseMainColumn }, 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') })] }) }))] })] }));
138
+ }, 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') })] }) }))] })] }));
136
139
  };
137
140
  export default HitGrid;
@@ -5,5 +5,6 @@ declare const _default: import("react").NamedExoticComponent<{
5
5
  columns: string[];
6
6
  columnWidths: Record<string, string>;
7
7
  collapseMainColumn: boolean;
8
+ onClick: (e: React.MouseEvent<HTMLDivElement>, hit: Hit) => void;
8
9
  }>;
9
10
  export default _default;
@@ -2,26 +2,22 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { KeyboardArrowUp } from '@mui/icons-material';
3
3
  import { Box, Collapse, IconButton, lighten, Stack, TableCell, TableRow, Typography, useTheme } from '@mui/material';
4
4
  import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
5
- import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
6
5
  import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
7
6
  import Assigned from '@cccsaurora/howler-ui/components/elements/hit/elements/Assigned';
8
7
  import EscalationChip from '@cccsaurora/howler-ui/components/elements/hit/elements/EscalationChip';
9
8
  import HitCard from '@cccsaurora/howler-ui/components/elements/hit/HitCard';
10
9
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
11
- import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
12
10
  import { get } from 'lodash-es';
13
11
  import { memo, useState } from 'react';
14
12
  import { useTranslation } from 'react-i18next';
15
13
  import { Link } from 'react-router-dom';
16
14
  import { useContextSelector } from 'use-context-selector';
17
- import EnhancedCell from './EnchancedCell';
18
- const HitRow = ({ hit, analyticIds, columns, columnWidths, collapseMainColumn }) => {
15
+ import EnhancedCell from './EnhancedCell';
16
+ const HitRow = ({ hit, analyticIds, columns, columnWidths, collapseMainColumn, onClick }) => {
19
17
  const theme = useTheme();
20
18
  const { t } = useTranslation();
21
19
  const selectedHits = useContextSelector(HitContext, ctx => ctx.selectedHits);
22
- const response = useContextSelector(HitSearchContext, ctx => ctx.response);
23
20
  const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
24
- const { onClick } = useHitSelection(response);
25
21
  const [expandRow, setExpandRow] = useState(false);
26
22
  return (_jsxs(_Fragment, { children: [_jsxs(TableRow, { id: hit.howler.id, onClick: ev => onClick(ev, hit), sx: [
27
23
  {
@@ -49,6 +45,6 @@ const HitRow = ({ hit, analyticIds, columns, columnWidths, collapseMainColumn })
49
45
  e.preventDefault();
50
46
  e.stopPropagation();
51
47
  setExpandRow(_expanded => !_expanded);
52
- }, children: _jsx(KeyboardArrowUp, {}) }), _jsx(Collapse, { in: !collapseMainColumn, orientation: "horizontal", unmountOnExit: true, children: _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "nowrap", children: [_jsx(EscalationChip, { hit: hit, layout: HitLayout.DENSE, hideLabel: true }), _jsxs(Typography, { sx: { textWrap: 'nowrap', whiteSpace: 'nowrap', fontSize: 'inherit' }, children: [analyticIds[hit.howler.analytic] ? (_jsx(Link, { to: `/analytics/${analyticIds[hit.howler.analytic]}`, onClick: e => e.stopPropagation(), children: hit.howler.analytic })) : (hit.howler.analytic), hit.howler.detection && ': ', hit.howler.detection] }), hit.howler.assignment !== 'unassigned' && _jsx(Assigned, { hit: hit, layout: HitLayout.DENSE, hideLabel: true })] }) })] }) }), columns.map(col => (_jsx(EnhancedCell, { className: `col-${col.replaceAll('.', '-')}`, value: get(hit, col) ?? t('none'), sx: columnWidths[col] ? { width: columnWidths[col] } : { width: '150px', maxWidth: '300px' } }, col))), _jsx(TableCell, { style: { borderBottom: 'none' } })] }, hit.howler.id), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 2, style: { paddingBottom: 0, paddingTop: 0 }, children: _jsx(Collapse, { in: expandRow, unmountOnExit: true, children: _jsx(Box, { width: "100%", maxWidth: "1200px", margin: 1, onClick: ev => onClick(ev, hit), children: _jsx(HitCard, { id: hit.howler.id, layout: HitLayout.NORMAL }) }) }) }) })] }));
48
+ }, children: _jsx(KeyboardArrowUp, {}) }), _jsx(Collapse, { in: !collapseMainColumn, orientation: "horizontal", unmountOnExit: true, children: _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "nowrap", children: [_jsx(EscalationChip, { hit: hit, layout: HitLayout.DENSE, hideLabel: true }), _jsxs(Typography, { sx: { textWrap: 'nowrap', whiteSpace: 'nowrap', fontSize: 'inherit' }, children: [analyticIds[hit.howler.analytic] ? (_jsx(Link, { to: `/analytics/${analyticIds[hit.howler.analytic]}`, onClick: e => e.stopPropagation(), children: hit.howler.analytic })) : (hit.howler.analytic), hit.howler.detection && ': ', hit.howler.detection] }), hit.howler.assignment !== 'unassigned' && _jsx(Assigned, { hit: hit, layout: HitLayout.DENSE, hideLabel: true })] }) })] }) }), columns.map(col => (_jsx(EnhancedCell, { className: `col-${col.replaceAll('.', '-')}`, value: get(hit, col) ?? t('none'), sx: columnWidths[col] ? { width: columnWidths[col] } : { width: '220px', maxWidth: '300px' } }, col)))] }, hit.howler.id), _jsx(TableRow, { onClick: ev => onClick(ev, hit), children: _jsx(TableCell, { colSpan: columns.length + 2, style: { paddingBottom: 0, paddingTop: 0 }, children: _jsx(Collapse, { in: expandRow, unmountOnExit: true, children: _jsx(Box, { width: "100%", maxWidth: "1200px", margin: 1, children: _jsx(HitCard, { id: hit.howler.id, layout: HitLayout.NORMAL }) }) }) }) })] }));
53
49
  };
54
50
  export default memo(HitRow);
@@ -35,6 +35,6 @@ const SearchSpan = ({ omitCustom = false, size }) => {
35
35
  }
36
36
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
37
  }, [selectedView]);
38
- return (_jsx(Autocomplete, { fullWidth: true, sx: { minWidth: '150px', flex: 1 }, size: size ?? 'small', value: span, options: omitCustom ? DATE_RANGES.slice(0, DATE_RANGES.length - 1) : DATE_RANGES, renderInput: _params => _jsx(TextField, { ..._params, label: t('hit.search.span') }), getOptionLabel: option => t(option), onChange: (_, value) => setSpan(value), disableClearable: true }));
38
+ return (_jsx(Autocomplete, { fullWidth: true, sx: { minWidth: '200px', flex: 1 }, size: size ?? 'small', value: span, options: omitCustom ? DATE_RANGES.slice(0, DATE_RANGES.length - 1) : DATE_RANGES, renderInput: _params => _jsx(TextField, { ..._params, label: t('hit.search.span') }), getOptionLabel: option => t(option), onChange: (_, value) => setSpan(value), disableClearable: true }));
39
39
  };
40
40
  export default memo(SearchSpan);
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable react/jsx-no-literals */
3
- import { ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
3
+ import { List, TableChart, ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
4
4
  import { MenuItem, Select, Stack, TableCell, TableRow, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
5
5
  import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
6
6
  import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
@@ -18,8 +18,9 @@ const LocalSection = () => {
18
18
  const [flattenJson, setFlattenJson] = useMyLocalStorageItem(StorageKey.FLATTEN_JSON, false);
19
19
  const [forceDrawer, setForceDrawer] = useMyLocalStorageItem(StorageKey.FORCE_DRAWER, false);
20
20
  const [hitLayout, setHitLayout] = useMyLocalStorageItem(StorageKey.HIT_LAYOUT, false);
21
+ const [displayType, setDisplayType] = useMyLocalStorageItem(StorageKey.DISPLAY_TYPE, 'list');
21
22
  const [pageCount, setPageCount] = useMyLocalStorageItem(StorageKey.PAGE_COUNT, 25);
22
23
  const [searchWidth, setSearchWidth] = useMyLocalStorageItem(StorageKey.SEARCH_PANE_WIDTH, null);
23
- return (_jsxs(SettingsSection, { title: t('page.settings.local.title'), colSpan: 3, children: [_jsx(EditRow, { titleKey: "page.settings.local.compact.json", descriptionKey: "page.settings.local.compact.json.description", value: compactJson, type: "checkbox", onEdit: async (value) => setCompactJson(JSON.parse(value)) }), _jsx(EditRow, { titleKey: "page.settings.local.flatten.json", descriptionKey: "page.settings.local.flatten.json.description", value: flattenJson, type: "checkbox", onEdit: async (value) => setFlattenJson(JSON.parse(value)) }), _jsx(EditRow, { titleKey: "page.settings.local.details.drawer", descriptionKey: "page.settings.local.details.drawer.description", value: forceDrawer, type: "checkbox", onEdit: async (value) => setForceDrawer(JSON.parse(value)) }), _jsx(EditRow, { titleKey: "page.settings.local.search.width", descriptionKey: "page.settings.local.search.width.description", value: searchWidth, type: "range", min: 400, max: Math.floor(window.innerWidth / 100) * 100, optional: true, onEdit: async (value) => setSearchWidth(value ? parseInt(value) : null) }), _jsxs(TableRow, { children: [_jsx(TableCell, { sx: CELL_SX, style: { whiteSpace: 'nowrap' }, children: t('page.settings.local.hits.layout') }), _jsx(TableCell, { sx: CELL_SX, colSpan: 2, align: "right", children: _jsxs(ToggleButtonGroup, { size: "small", value: hitLayout, exclusive: true, onChange: (_, value) => setHitLayout(value), 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(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t('page.settings.local.hits.layout.description') }) }) }), _jsxs(TableRow, { children: [_jsx(TableCell, { sx: CELL_SX, style: { whiteSpace: 'nowrap' }, children: t('page.settings.local.results.count') }), _jsx(TableCell, { sx: CELL_SX, colSpan: 2, align: "right", children: _jsxs(Select, { size: "small", sx: { minWidth: '75px', textAlign: 'left' }, value: pageCount, onChange: event => setPageCount(typeof event.target.value === 'string' ? parseInt(event.target.value) : event.target.value), children: [_jsx(MenuItem, { value: 5, children: "5" }), _jsx(MenuItem, { value: 10, children: "10" }), _jsx(MenuItem, { value: 25, children: "25" }), _jsx(MenuItem, { value: 50, children: "50" }), _jsx(MenuItem, { value: 75, children: "75" }), _jsx(MenuItem, { value: 100, children: "100" }), _jsx(MenuItem, { value: 150, children: "150" }), _jsx(MenuItem, { value: 250, children: "250" })] }) })] }), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t('page.settings.local.results.count.description') }) }) }), howlerPluginStore.plugins.map(plugin => pluginStore.executeFunction(`${plugin}.settings`, 'local'))] }));
24
+ return (_jsxs(SettingsSection, { title: t('page.settings.local.title'), colSpan: 3, children: [_jsx(EditRow, { titleKey: "page.settings.local.compact.json", descriptionKey: "page.settings.local.compact.json.description", value: compactJson, type: "checkbox", onEdit: async (value) => setCompactJson(JSON.parse(value)) }), _jsx(EditRow, { titleKey: "page.settings.local.flatten.json", descriptionKey: "page.settings.local.flatten.json.description", value: flattenJson, type: "checkbox", onEdit: async (value) => setFlattenJson(JSON.parse(value)) }), _jsx(EditRow, { titleKey: "page.settings.local.details.drawer", descriptionKey: "page.settings.local.details.drawer.description", value: forceDrawer, type: "checkbox", onEdit: async (value) => setForceDrawer(JSON.parse(value)) }), _jsx(EditRow, { titleKey: "page.settings.local.search.width", descriptionKey: "page.settings.local.search.width.description", value: searchWidth, type: "range", min: 400, max: Math.floor(window.innerWidth / 100) * 100, optional: true, onEdit: async (value) => setSearchWidth(value ? parseInt(value) : null) }), _jsxs(TableRow, { children: [_jsx(TableCell, { sx: CELL_SX, style: { whiteSpace: 'nowrap' }, children: t('page.settings.local.hits.layout') }), _jsx(TableCell, { sx: CELL_SX, colSpan: 2, align: "right", children: _jsxs(ToggleButtonGroup, { size: "small", value: hitLayout, exclusive: true, onChange: (_, value) => setHitLayout(value), 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(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t('page.settings.local.hits.layout.description') }) }) }), _jsxs(TableRow, { children: [_jsx(TableCell, { sx: CELL_SX, style: { whiteSpace: 'nowrap' }, children: t('page.settings.local.hits.display_type') }), _jsx(TableCell, { sx: CELL_SX, colSpan: 2, align: "right", children: _jsxs(ToggleButtonGroup, { size: "small", value: displayType, exclusive: true, onChange: (_, value) => setDisplayType(value), 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(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t('page.settings.local.hits.display_type.description') }) }) }), _jsxs(TableRow, { children: [_jsx(TableCell, { sx: CELL_SX, style: { whiteSpace: 'nowrap' }, children: t('page.settings.local.results.count') }), _jsx(TableCell, { sx: CELL_SX, colSpan: 2, align: "right", children: _jsxs(Select, { size: "small", sx: { minWidth: '75px', textAlign: 'left' }, value: pageCount, onChange: event => setPageCount(typeof event.target.value === 'string' ? parseInt(event.target.value) : event.target.value), children: [_jsx(MenuItem, { value: 5, children: "5" }), _jsx(MenuItem, { value: 10, children: "10" }), _jsx(MenuItem, { value: 25, children: "25" }), _jsx(MenuItem, { value: 50, children: "50" }), _jsx(MenuItem, { value: 75, children: "75" }), _jsx(MenuItem, { value: 100, children: "100" }), _jsx(MenuItem, { value: 150, children: "150" }), _jsx(MenuItem, { value: 250, children: "250" })] }) })] }), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: 3, sx: { paddingTop: '0 !important' }, children: _jsx(Typography, { variant: "caption", color: "text.secondary", children: t('page.settings.local.results.count.description') }) }) }), howlerPluginStore.plugins.map(plugin => pluginStore.executeFunction(`${plugin}.settings`, 'local'))] }));
24
25
  };
25
26
  export default LocalSection;
@@ -372,6 +372,10 @@
372
372
  "page.settings.local.flatten.json.description": "Instead of showing the object structure of the JSON, show it as flattened keys.",
373
373
  "page.settings.local.details.drawer": "Always use drawer in Hit Search",
374
374
  "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.",
375
+ "page.settings.local.hits.display_type": "Default display type for results",
376
+ "page.settings.local.hits.display_type.description": "This setting allows you to decide whether to use the list or grid view by default when searching.",
377
+ "page.settings.local.hits.display_type.list": "List",
378
+ "page.settings.local.hits.display_type.grid": "Grid",
375
379
  "page.settings.local.hits.layout": "Change Layout",
376
380
  "page.settings.local.hits.layout.description": "This setting allows you to decide how much padding and spacing, as well as font size and element size, we use when rendering the UI. The denser the UI, the more data can be viewed.",
377
381
  "page.settings.local.hits.layout.dense": "Dense",
@@ -373,6 +373,10 @@
373
373
  "page.settings.local.flatten.json.description": "Au lieu d'afficher la structure d'objet du JSON, affichez-la sous forme de clés aplaties",
374
374
  "page.settings.local.details.drawer": "Toujours utiliser le tiroir dans la recherche du hit",
375
375
  "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.",
376
+ "page.settings.local.hits.display_type": "Type d'affichage par défaut pour les résultats",
377
+ "page.settings.local.hits.display_type.description": "Ce paramètre vous permet de choisir entre l'affichage sous forme de liste ou de grille par défaut lors d'une recherche.",
378
+ "page.settings.local.hits.display_type.list": "Liste",
379
+ "page.settings.local.hits.display_type.grid": "Grille",
376
380
  "page.settings.local.hits.layout": "Change Layout",
377
381
  "page.settings.local.hits.layout.description": "Ce paramètre vous permet de décider de la quantité de rembourrage et d'espacement, ainsi que de la taille de la police et de la taille de l'élément, que nous utilisons lors du rendu de l'interface utilisateur. Plus l'interface est dense, plus il est possible d'afficher de données.",
378
382
  "page.settings.local.hits.layout.dense": "Dense",
package/package.json CHANGED
@@ -96,7 +96,7 @@
96
96
  "internal-slot": "1.0.7"
97
97
  },
98
98
  "type": "module",
99
- "version": "2.12.0-dev.48",
99
+ "version": "2.12.0-dev.65",
100
100
  "exports": {
101
101
  "./i18n": "./i18n.js",
102
102
  "./index.css": "./index.css",
@@ -55,7 +55,8 @@ export declare enum StorageKey {
55
55
  SEARCH_PANE_WIDTH = "search_pane_width",
56
56
  GRID_COLLAPSE_COLUMN = "grid_collapse_column",
57
57
  QUERY_HISTORY = "query_history",
58
- LOGIN_NONCE = "login_nonce"
58
+ LOGIN_NONCE = "login_nonce",
59
+ DISPLAY_TYPE = "display_type"
59
60
  }
60
61
  export declare const MOCK_SEARCH_QUERY_STORE = "howler.ui.mock_search_query_store";
61
62
  export declare const MOCK_FAVOURITES_STORE = "howler.ui.mock_favourite_store";
@@ -61,6 +61,7 @@ export var StorageKey;
61
61
  StorageKey["GRID_COLLAPSE_COLUMN"] = "grid_collapse_column";
62
62
  StorageKey["QUERY_HISTORY"] = "query_history";
63
63
  StorageKey["LOGIN_NONCE"] = "login_nonce";
64
+ StorageKey["DISPLAY_TYPE"] = "display_type";
64
65
  })(StorageKey || (StorageKey = {}));
65
66
  export const MOCK_SEARCH_QUERY_STORE = `${MY_LOCAL_STORAGE_PREFIX}.${StorageKey.MOCK_SEARCH_QUERY_STORE}`;
66
67
  export const MOCK_FAVOURITES_STORE = `${MY_LOCAL_STORAGE_PREFIX}.${StorageKey.MOCK_FAVOURITES_STORE}`;
@@ -1,18 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /* eslint-disable react/no-array-index-key */
3
- import { Stack, TableCell, Tooltip } from '@mui/material';
4
- import PluginTypography from '@cccsaurora/howler-ui/components/elements/PluginTypography';
5
- import { memo } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
- const EnhancedCell = ({ value: rawValue, sx = {}, className }) => {
8
- const { t } = useTranslation();
9
- if (!rawValue) {
10
- return _jsx(TableCell, { style: { borderBottom: 'none' }, children: t('none') });
11
- }
12
- const values = (Array.isArray(rawValue) ? rawValue : [rawValue]).filter(_value => !!_value);
13
- return (_jsx(Tooltip, { title: _jsx(Stack, { spacing: 0.5, children: values.map((value, index) => (_jsx("span", { children: value }, value + index))) }), children: _jsx(TableCell, { sx: { borderBottom: 'none', borderRight: 'thin solid', borderRightColor: 'divider', fontSize: '0.8rem' }, children: _jsx(Stack, { direction: "row", className: className, spacing: 0.5, sx: [
14
- { display: 'flex', justifyContent: 'start', width: '100%', overflow: 'hidden' },
15
- ...(Array.isArray(sx) ? sx : [sx])
16
- ], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, children: value }, value + index))) }) }) }));
17
- };
18
- export default memo(EnhancedCell);