@cccsaurora/howler-ui 2.13.0-dev.129 → 2.13.0-dev.132

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.
@@ -1,15 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Autocomplete, Box, Button, CircularProgress, Divider, FormControl, LinearProgress, Stack, TextField, Tooltip, useTheme } from '@mui/material';
2
+ import { Autocomplete, Box, Button, CircularProgress, Divider, FormControl, LinearProgress, Stack, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, useTheme } from '@mui/material';
3
3
  import api from '@cccsaurora/howler-ui/api';
4
4
  import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
5
5
  import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
- import { Check, Delete, SsidChart } from '@mui/icons-material';
7
+ import { Check, DarkMode, Delete, SsidChart, WbSunny } from '@mui/icons-material';
8
+ import { useApp } from '@cccsaurora/howler-ui/commons/components/app/hooks';
8
9
  import AppInfoPanel from '@cccsaurora/howler-ui/commons/components/display/AppInfoPanel';
10
+ import useThemeBuilder from '@cccsaurora/howler-ui/commons/components/utils/hooks/useThemeBuilder';
9
11
  import { AnalyticContext } from '@cccsaurora/howler-ui/components/app/providers/AnalyticProvider';
10
12
  import { OverviewContext } from '@cccsaurora/howler-ui/components/app/providers/OverviewProvider';
11
13
  import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
12
14
  import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
15
+ import useMyTheme from '@cccsaurora/howler-ui/components/hooks/useMyTheme';
13
16
  import { useSearchParams } from 'react-router-dom';
14
17
  import hitsData from '@cccsaurora/howler-ui/utils/hit.json';
15
18
  import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
@@ -17,6 +20,8 @@ import OverviewEditor from './OverviewEditor';
17
20
  import { useStartingTemplate } from './startingTemplate';
18
21
  const OverviewViewer = () => {
19
22
  const theme = useTheme();
23
+ const app = useApp();
24
+ const { lightTheme, darkTheme } = useThemeBuilder(useMyTheme());
20
25
  const { t } = useTranslation();
21
26
  const [params, setParams] = useSearchParams();
22
27
  const { getOverviews } = useContext(OverviewContext);
@@ -24,6 +29,7 @@ const OverviewViewer = () => {
24
29
  const [overviewList, setOverviewList] = useState([]);
25
30
  const [selectedOverview, setSelectedOverview] = useState(null);
26
31
  const [content, setContent] = useState('');
32
+ const [chosenTheme, setChosenTheme] = useState(app.theme);
27
33
  const [analytics, setAnalytics] = useState([]);
28
34
  const [detections, setDetections] = useState([]);
29
35
  const [analytic, setAnalytic] = useState(params.get('analytic') ?? '');
@@ -173,7 +179,8 @@ const OverviewViewer = () => {
173
179
  }, [onMouseMove, onMouseUp]);
174
180
  const analyticOrDetectionMissing = useMemo(() => !analytic || !detection, [analytic, detection]);
175
181
  const noChange = useMemo(() => selectedOverview?.content === content, [content, selectedOverview?.content]);
176
- return (_jsxs(PageCenter, { maxWidth: "100%", width: "100%", textAlign: "left", height: "100%", children: [_jsx(LinearProgress, { sx: { mb: 1, opacity: +loading } }), _jsxs(Stack, { direction: "column", spacing: 2, divider: _jsx(Divider, { orientation: "horizontal", flexItem: true }), height: "100%", children: [_jsxs(Stack, { direction: "row", spacing: 2, mb: 2, alignItems: "stretch", children: [_jsx(FormControl, { sx: { maxWidth: { sm: '300px', lg: '450px' }, width: '100%' }, children: _jsx(Autocomplete, { id: "analytic", options: analytics.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())), getOptionLabel: option => option.name, value: analytics.find(a => a.name === analytic) || null, onChange: (_event, newValue) => setAnalytic(newValue ? newValue.name : ''), renderInput: autocompleteAnalyticParams => (_jsx(TextField, { ...autocompleteAnalyticParams, label: t('route.overviews.analytic'), size: "small" })) }) }), !analytics.find(_analytic => _analytic.name === analytic)?.rule ? (_jsx(FormControl, { sx: { minWidth: { sm: '200px' } }, disabled: !analytic, children: _jsx(Autocomplete, { id: "detection", options: ['ANY', ...detections.sort()], getOptionLabel: option => option, value: detection ?? '', onChange: (_event, newValue) => setDetection(newValue), renderInput: autocompleteDetectionParams => (_jsx(TextField, { ...autocompleteDetectionParams, label: t('route.overviews.detection'), size: "small" })) }) })) : (_jsx(Tooltip, { title: t('route.overviews.rule.explanation'), children: _jsx(SsidChart, { color: "info", sx: { alignSelf: 'center' } }) })), selectedOverview && (_jsx(Button, { variant: "outlined", startIcon: _jsx(Delete, {}), onClick: onDelete, children: t('button.delete') })), _jsx(Button, { variant: "outlined", disabled: analyticOrDetectionMissing || noChange, startIcon: overviewLoading ? _jsx(CircularProgress, { size: 16 }) : _jsx(Check, {}), onClick: onSave, children: t(!analyticOrDetectionMissing && !noChange ? 'button.save' : 'button.saved') })] }), analyticOrDetectionMissing ? (_jsx(AppInfoPanel, { i18nKey: "route.overviews.select", sx: { width: '100%', alignSelf: 'start' } })) : (_jsxs(Stack, { ref: wrapper, direction: "row", spacing: 1, height: "100%", onKeyDown: e => {
182
+ const activeTheme = chosenTheme === 'light' ? lightTheme : darkTheme;
183
+ return (_jsxs(PageCenter, { maxWidth: "100%", width: "100%", textAlign: "left", height: "100%", children: [_jsx(LinearProgress, { sx: { mb: 1, opacity: +loading } }), _jsxs(Stack, { direction: "column", spacing: 2, divider: _jsx(Divider, { orientation: "horizontal", flexItem: true }), height: "100%", children: [_jsxs(Stack, { direction: "row", spacing: 2, mb: 2, alignItems: "stretch", children: [_jsx(FormControl, { sx: { maxWidth: { sm: '300px', lg: '450px' }, width: '100%' }, children: _jsx(Autocomplete, { id: "analytic", options: analytics.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())), getOptionLabel: option => option.name, value: analytics.find(a => a.name === analytic) || null, onChange: (_event, newValue) => setAnalytic(newValue ? newValue.name : ''), renderInput: autocompleteAnalyticParams => (_jsx(TextField, { ...autocompleteAnalyticParams, label: t('route.overviews.analytic'), size: "small" })) }) }), !analytics.find(_analytic => _analytic.name === analytic)?.rule ? (_jsx(FormControl, { sx: { minWidth: { sm: '200px' } }, disabled: !analytic, children: _jsx(Autocomplete, { id: "detection", options: ['ANY', ...detections.sort()], getOptionLabel: option => option, value: detection ?? '', onChange: (_event, newValue) => setDetection(newValue), renderInput: autocompleteDetectionParams => (_jsx(TextField, { ...autocompleteDetectionParams, label: t('route.overviews.detection'), size: "small" })) }) })) : (_jsx(Tooltip, { title: t('route.overviews.rule.explanation'), children: _jsx(SsidChart, { color: "info", sx: { alignSelf: 'center' } }) })), selectedOverview && (_jsx(Button, { variant: "outlined", startIcon: _jsx(Delete, {}), onClick: onDelete, children: t('button.delete') })), _jsx(Button, { variant: "outlined", disabled: analyticOrDetectionMissing || noChange, startIcon: overviewLoading ? _jsx(CircularProgress, { size: 16 }) : _jsx(Check, {}), onClick: onSave, children: t(!analyticOrDetectionMissing && !noChange ? 'button.save' : 'button.saved') }), _jsx("div", { style: { flex: 1 } }), _jsxs(ToggleButtonGroup, { exclusive: true, value: chosenTheme, onChange: (_event, value) => setChosenTheme(value), sx: { maxHeight: '40px' }, children: [_jsx(Tooltip, { title: t('route.overviews.theme.light'), children: _jsx(ToggleButton, { value: "light", children: _jsx(WbSunny, {}) }) }), _jsx(Tooltip, { title: t('route.overviews.theme.dark'), children: _jsx(ToggleButton, { value: "dark", children: _jsx(DarkMode, {}) }) })] })] }), analyticOrDetectionMissing ? (_jsx(AppInfoPanel, { i18nKey: "route.overviews.select", sx: { width: '100%', alignSelf: 'start' } })) : (_jsxs(Stack, { ref: wrapper, direction: "row", spacing: 1, height: "100%", onKeyDown: e => {
177
184
  if (e.ctrlKey && e.key === 's') {
178
185
  if (!noChange) {
179
186
  onSave();
@@ -199,20 +206,22 @@ const OverviewViewer = () => {
199
206
  transform: `translateX(${x}px)`,
200
207
  zIndex: 1000,
201
208
  borderRadius: theme.shape.borderRadius
202
- }, onMouseDown: onMouseDown }), _jsx(Box, { flex: 1, px: 2, sx: {
203
- position: 'absolute',
204
- top: 0,
205
- left: `calc(50% + 7px + ${x}px)`,
206
- bottom: 0,
207
- right: 0,
208
- display: 'flex',
209
- alignItems: 'stretch',
210
- justifyContent: 'stretch',
211
- px: 1,
212
- pt: 1,
213
- mt: -1,
214
- '& > *': { width: '100%' },
215
- '& > div > :first-child': { mt: 0 }
216
- }, children: _jsx(HitOverview, { content: content || startingTemplate, hit: exampleHit }) })] }))] })] }));
209
+ }, onMouseDown: onMouseDown }), _jsx(ThemeProvider, { theme: activeTheme, children: _jsx(Box, { flex: 1, px: 2, sx: {
210
+ position: 'absolute',
211
+ top: 0,
212
+ left: `calc(50% + 7px + ${x}px)`,
213
+ bottom: 0,
214
+ right: 0,
215
+ display: 'flex',
216
+ alignItems: 'stretch',
217
+ justifyContent: 'stretch',
218
+ px: 1,
219
+ pt: 1,
220
+ mt: -1,
221
+ '& > *': { width: '100%' },
222
+ '& > div > :first-child': { mt: 0 },
223
+ backgroundColor: activeTheme.palette.background.default,
224
+ color: activeTheme.palette.text.primary
225
+ }, children: _jsx(HitOverview, { content: content || startingTemplate, hit: exampleHit }) }) })] }))] })] }));
217
226
  };
218
227
  export default OverviewViewer;
@@ -39,7 +39,7 @@ const ViewsBase = () => {
39
39
  const [phrase, setPhrase] = useState('');
40
40
  const [offset, setOffset] = useState(parseInt(searchParams.get('offset')) || 0);
41
41
  const [response, setResponse] = useState(null);
42
- const [types, setTypes] = useState([]);
42
+ const [type, setType] = useState(searchParams.get('type') || null);
43
43
  const [hasError, setHasError] = useState(false);
44
44
  const [searching, setSearching] = useState(false);
45
45
  const [favouritesOnly, setFavouritesOnly] = useState(false);
@@ -58,7 +58,7 @@ const ViewsBase = () => {
58
58
  setSearchParams(searchParams, { replace: true });
59
59
  const searchTerm = phrase ? `*${sanitizeLuceneQuery(phrase)}*` : '*';
60
60
  const phraseQuery = FIELDS_TO_SEARCH.map(_field => `${_field}:${searchTerm}`).join(' OR ');
61
- const typeQuery = `(type:global OR owner:(${user.username} OR none)) AND type:(${types.join(' OR ') || '*'}${types.includes('personal') ? ' OR readonly' : ''})`;
61
+ const typeQuery = `(type:global OR owner:(${user.username} OR none)) AND type:(${type ?? '*'}${type === 'personal' ? ' OR readonly' : ''})`;
62
62
  const favouritesQuery = favouritesOnly && user.favourite_views.length > 0 ? ` AND view_id:(${user.favourite_views.join(' OR ')})` : '';
63
63
  setResponse(await dispatchApi(api.search.view.post({
64
64
  query: `(${phraseQuery}) AND ${typeQuery}${favouritesQuery}`,
@@ -78,7 +78,7 @@ const ViewsBase = () => {
78
78
  searchParams,
79
79
  user.username,
80
80
  user.favourite_views,
81
- types,
81
+ type,
82
82
  favouritesOnly,
83
83
  dispatchApi,
84
84
  pageCount,
@@ -132,9 +132,29 @@ const ViewsBase = () => {
132
132
  setDefaultViewLoading(false);
133
133
  }
134
134
  }, [fetchViews]);
135
+ const onTypeChange = useCallback(async (_type) => {
136
+ setType(_type);
137
+ if (_type) {
138
+ searchParams.delete('type');
139
+ searchParams.set('type', _type);
140
+ setSearchParams(searchParams, { replace: true });
141
+ }
142
+ else if (searchParams.has('type')) {
143
+ searchParams.delete('type');
144
+ setSearchParams(searchParams, { replace: true });
145
+ }
146
+ }, [searchParams, setSearchParams]);
135
147
  useEffect(() => {
148
+ let changed = false;
136
149
  if (!searchParams.has('offset')) {
137
150
  searchParams.set('offset', '0');
151
+ changed = true;
152
+ }
153
+ if (searchParams.has('type') && !['personal', 'global'].includes(searchParams.get('type'))) {
154
+ searchParams.delete('type');
155
+ changed = true;
156
+ }
157
+ if (changed) {
138
158
  setSearchParams(searchParams, { replace: true });
139
159
  }
140
160
  }, [searchParams, setSearchParams]);
@@ -150,12 +170,8 @@ const ViewsBase = () => {
150
170
  onSearch();
151
171
  }
152
172
  // eslint-disable-next-line react-hooks/exhaustive-deps
153
- }, [offset, favouritesOnly, types]);
154
- return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: searching, searchFilters: _jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", children: _jsxs(ToggleButtonGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, size: "small", value: types, onChange: (__, _types) => {
155
- if (_types) {
156
- setTypes(_types.length < 2 ? _types : []);
157
- }
158
- }, children: [_jsx(ToggleButton, { value: "personal", "aria-label": "personal", children: t('route.views.manager.personal') }), _jsx(ToggleButton, { value: "global", "aria-label": "global", children: t('route.views.manager.global') })] }) }), aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.views.search.prompt') }), afterSearch: size(views) > 0 ? (_jsx(Autocomplete, { open: defaultViewOpen, loading: defaultViewLoading, onOpen: onDefaultViewOpen, onClose: () => setDefaultViewOpen(false), options: Object.values(omitBy(views, isNull)), renderOption: ({ key, ...props }, o) => (_createElement("li", { ...props, key: key },
173
+ }, [offset, favouritesOnly, type]);
174
+ return (_jsx(ItemManager, { onSearch: onSearch, onPageChange: onPageChange, phrase: phrase, setPhrase: setPhrase, hasError: hasError, searching: searching, searchFilters: _jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", children: _jsxs(ToggleButtonGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, size: "small", value: type, exclusive: true, onChange: (__, _type) => onTypeChange(_type), children: [_jsx(ToggleButton, { value: "personal", "aria-label": "personal", children: t('route.views.manager.personal') }), _jsx(ToggleButton, { value: "global", "aria-label": "global", children: t('route.views.manager.global') })] }) }), aboveSearch: _jsx(Typography, { sx: theme => ({ fontStyle: 'italic', color: theme.palette.text.disabled, mb: 0.5 }), variant: "body2", children: t('route.views.search.prompt') }), afterSearch: size(views) > 0 ? (_jsx(Autocomplete, { open: defaultViewOpen, loading: defaultViewLoading, onOpen: onDefaultViewOpen, onClose: () => setDefaultViewOpen(false), options: Object.values(omitBy(views, isNull)), renderOption: ({ key, ...props }, o) => (_createElement("li", { ...props, key: key },
159
175
  _jsxs(Stack, { children: [_jsx(Typography, { variant: "body1", children: t(o.title) }), _jsx(Typography, { variant: "caption", children: _jsx("code", { children: o.query }) })] }))), renderInput: params => (_jsx(TextField, { ...params, label: t('route.views.manager.default'), sx: { minWidth: '300px' } })), filterOptions: (_views, { inputValue }) => _views.filter(v => t(v.title).toLowerCase().includes(inputValue.toLowerCase()) ||
160
176
  v.query.toLowerCase().includes(inputValue.toLowerCase())), getOptionLabel: (v) => t(v.title), isOptionEqualToValue: (view, value) => view.view_id === value.view_id, value: views[defaultView] ?? null, onChange: (_, option) => setDefaultView(option?.view_id) })) : (_jsx(Skeleton, { variant: "rounded", width: "300px", height: "initial" })), belowSearch: _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Checkbox, { size: "small", disabled: user.favourite_views?.length < 1, checked: favouritesOnly, onChange: (_, checked) => setFavouritesOnly(checked) }), _jsx(Typography, { variant: "body1", sx: theme => ({ color: theme.palette.text.disabled }), children: t('route.views.manager.favourites') })] }), renderer: ({ item }, classRenderer) => (_jsx(Card, { variant: "outlined", sx: { p: 1, mb: 1, transitionProperty: 'border-color', '&:hover': { borderColor: 'primary.main' } }, className: classRenderer(), children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, sx: { color: 'inherit', textDecoration: 'none' }, component: Link, to: `/views/${item.item.view_id}`, children: [_jsx(ViewTitle, { ...item.item }), _jsx(FlexOne, {}), ((item.item.owner === user.username && item.item.type !== 'readonly') ||
161
177
  (item.item.type === 'global' && user.is_admin)) && (_jsx(Tooltip, { title: t('button.edit'), children: _jsx(IconButton, { component: Link, to: `/views/${item.item.view_id}/edit?query=${item.item.query}`, children: _jsx(Edit, {}) }) })), item.item.owner === user.username && item.item.type !== 'readonly' && (_jsx(Tooltip, { title: t('button.delete'), children: _jsx(IconButton, { onClick: event => onDelete(event, item.item.view_id), children: _jsx(Clear, {}) }) })), item.item.type === 'global' && item.item.owner !== user.username && (_jsx(Tooltip, { title: item.item.owner, children: _jsx("div", { children: _jsx(HowlerAvatar, { sx: { width: 24, height: 24, marginRight: '8px !important', marginLeft: '8px !important' }, userId: item.item.owner }) }) })), _jsx(Tooltip, { title: t('button.pin'), children: _jsx(IconButton, { onClick: e => onFavourite(e, item.item.view_id), children: user.favourite_views?.includes(item.item.view_id) ? _jsx(Star, {}) : _jsx(StarBorder, {}) }) })] }) }, item.item.view_id)), response: response, searchPrompt: "route.views.manager.search", onCreate: () => navigate('/views/create'), createPrompt: "route.views.create", createIcon: _jsx(SavedSearch, { sx: { mr: 1 } }) }));
@@ -587,6 +587,8 @@
587
587
  "route.overviews.manager.search": "Search Overviews",
588
588
  "route.overviews.manager.delete": "Delete Overview",
589
589
  "route.overviews.manager.delete.success": "Overview Removed.",
590
+ "route.overviews.theme.light": "Preview in Light Mode",
591
+ "route.overviews.theme.dark": "Preview in Dark Mode",
590
592
  "route.dossiers": "Dossiers",
591
593
  "route.dossiers.default": "Default",
592
594
  "route.dossiers.search.prompt": "Search by title, query, or owner.",
@@ -585,7 +585,8 @@
585
585
  "route.overviews.manager.search": "Rechercher les vues d'ensemble",
586
586
  "route.overviews.manager.delete": "Supprimer la vue d'ensemble",
587
587
  "route.overviews.manager.delete.success": "Vue d'ensemble Supprimée.",
588
- "route.overviews.rule.explanation": "Il s'agit d'une analyse Howler. Il n'y a qu'une seule détection, qui a été présélectionnée.",
588
+ "route.overviews.theme.light": "Prévoyez en mode clair",
589
+ "route.overviews.theme.dark": "Prévoyez en mode sombre",
589
590
  "route.dossiers": "Dossiers",
590
591
  "route.dossiers.default": "Défaut",
591
592
  "route.dossiers.search.prompt": "Recherche par titre, requête ou propriétaire.",
package/package.json CHANGED
@@ -96,7 +96,7 @@
96
96
  "internal-slot": "1.0.7"
97
97
  },
98
98
  "type": "module",
99
- "version": "2.13.0-dev.129",
99
+ "version": "2.13.0-dev.132",
100
100
  "exports": {
101
101
  "./i18n": "./i18n.js",
102
102
  "./index.css": "./index.css",