data-primals-engine 1.4.3 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +913 -867
- package/client/package-lock.json +49 -0
- package/client/package.json +1 -0
- package/client/src/AddWidgetTypeModal.jsx +47 -43
- package/client/src/App.jsx +3 -7
- package/client/src/App.scss +25 -3
- package/client/src/AssistantChat.jsx +363 -323
- package/client/src/AssistantChat.scss +30 -12
- package/client/src/Dashboard.jsx +480 -396
- package/client/src/Dashboard.scss +1 -1
- package/client/src/DashboardHtmlViewItem.jsx +147 -0
- package/client/src/DashboardView.jsx +104 -19
- package/client/src/DataEditor.jsx +12 -5
- package/client/src/DataLayout.jsx +805 -762
- package/client/src/DataLayout.scss +14 -0
- package/client/src/DataTable.jsx +63 -77
- package/client/src/Dialog.scss +1 -1
- package/client/src/Field.jsx +591 -322
- package/client/src/FlexDataRenderer.jsx +2 -0
- package/client/src/FlexTreeUtils.js +1 -1
- package/client/src/FlexViewCard.jsx +44 -0
- package/client/src/HistoryDialog.jsx +47 -14
- package/client/src/HtmlViewBuilderModal.jsx +91 -0
- package/client/src/HtmlViewBuilderModal.scss +18 -0
- package/client/src/HtmlViewCard.jsx +44 -0
- package/client/src/HtmlViewCard.scss +35 -0
- package/client/src/KPIDialog.jsx +11 -1
- package/client/src/KanbanCard.jsx +1 -2
- package/client/src/ModelCreator.jsx +6 -6
- package/client/src/ModelCreatorField.jsx +74 -31
- package/client/src/ModelList.jsx +93 -54
- package/client/src/Notification.jsx +136 -136
- package/client/src/Notification.scss +0 -18
- package/client/src/Pagination.jsx +5 -3
- package/client/src/RelationField.jsx +354 -258
- package/client/src/RelationSelectorWidget.jsx +173 -0
- package/client/src/constants.js +1 -1
- package/client/src/contexts/ModelContext.jsx +10 -1
- package/client/src/contexts/UIContext.jsx +72 -63
- package/client/src/filter.js +262 -212
- package/client/src/hooks/useTutorials.jsx +62 -65
- package/client/src/hooks/useValidation.js +75 -0
- package/client/src/translations.js +26 -24
- package/package.json +3 -1
- package/perf/README.md +147 -0
- package/perf/artillery-hooks.js +37 -0
- package/perf/perf-shot-hardwork.yml +84 -0
- package/perf/perf-shot-search.yml +45 -0
- package/perf/setup.yml +26 -0
- package/server.js +1 -1
- package/src/constants.js +3 -28
- package/src/core.js +15 -1
- package/src/data.js +1 -1
- package/src/defaultModels.js +63 -7
- package/src/email.js +5 -2
- package/src/engine.js +5 -3
- package/src/filter.js +5 -3
- package/src/i18n.js +710 -10
- package/src/modules/assistant/assistant.js +151 -19
- package/src/modules/bucket.js +14 -16
- package/src/modules/data/data.backup.js +11 -8
- package/src/modules/data/data.core.js +118 -92
- package/src/modules/data/data.history.js +531 -492
- package/src/modules/data/data.js +9 -56
- package/src/modules/data/data.operations.js +3282 -2999
- package/src/modules/data/data.relations.js +686 -686
- package/src/modules/data/data.routes.js +118 -24
- package/src/modules/data/data.scheduling.js +2 -1
- package/src/modules/data/data.validation.js +85 -3
- package/src/modules/file.js +247 -236
- package/src/modules/user.js +4 -1
- package/src/modules/workflow.js +9 -10
- package/src/openai.jobs.js +2 -0
- package/src/packs.js +5482 -5461
- package/src/providers.js +22 -7
- package/test/data.integration.test.js +136 -2
- package/test/import_export.integration.test.js +1 -1
package/client/src/DataTable.jsx
CHANGED
|
@@ -11,7 +11,7 @@ import {Event} from "../../src/events.js";
|
|
|
11
11
|
import {
|
|
12
12
|
FaBook,
|
|
13
13
|
FaCopy,
|
|
14
|
-
FaDatabase, FaEraser,
|
|
14
|
+
FaDatabase, FaEraser, FaEye,
|
|
15
15
|
FaFileExport,
|
|
16
16
|
FaFileImport,
|
|
17
17
|
FaFilter, FaHistory,
|
|
@@ -50,16 +50,15 @@ import {Zoom} from "yet-another-react-lightbox/plugins";
|
|
|
50
50
|
import Lightbox from "yet-another-react-lightbox";
|
|
51
51
|
|
|
52
52
|
import "./DataTable.scss"
|
|
53
|
-
import useLocalStorage from "./hooks/useLocalStorage.js";
|
|
54
53
|
import TutorialsMenu from "../src/TutorialsMenu.jsx";
|
|
55
54
|
import {useTutorials} from "./hooks/useTutorials.jsx";
|
|
56
|
-
import PackGallery from "./PackGallery.jsx";
|
|
57
55
|
import {HiddenableCell} from "./HiddenableCell.jsx";
|
|
58
56
|
import ConditionBuilder from "./ConditionBuilder.jsx";
|
|
59
57
|
import {pagedFilterToMongoConds} from "./filter.js";
|
|
60
58
|
import {isConditionMet} from "../../src/filter";
|
|
61
59
|
import {DataImporter} from "./DataImporter.jsx";
|
|
62
60
|
import {HistoryDialog} from "./HistoryDialog.jsx";
|
|
61
|
+
import {Config} from "../../src/config.js";
|
|
63
62
|
|
|
64
63
|
const Header = ({
|
|
65
64
|
reversed = false,
|
|
@@ -72,7 +71,8 @@ const Header = ({
|
|
|
72
71
|
onChangeFilterValue,
|
|
73
72
|
setFilterValues,
|
|
74
73
|
filterValues,
|
|
75
|
-
advanced=true
|
|
74
|
+
advanced=true,
|
|
75
|
+
selectionMode=false
|
|
76
76
|
}) => {
|
|
77
77
|
|
|
78
78
|
const {t} = useTranslation()
|
|
@@ -92,12 +92,26 @@ const Header = ({
|
|
|
92
92
|
const handleAdvancedFilter = () => {
|
|
93
93
|
setAdvancedFilterVisible(true);
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
const [iconFilterActive, setIconFilterActive] = useState(true);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (pagedFilters && selectedModel?.name) {
|
|
99
|
+
const modelFilters = pagedFilters[selectedModel.name] || {};
|
|
100
|
+
const isActive = Object.keys(modelFilters)
|
|
101
|
+
.some(fieldKey => {
|
|
102
|
+
const fieldFilter = modelFilters[fieldKey];
|
|
103
|
+
return fieldFilter && Object.keys(fieldFilter).length > 0;
|
|
104
|
+
});
|
|
105
|
+
setIconFilterActive(isActive);
|
|
106
|
+
}
|
|
107
|
+
}, [pagedFilters, selectedModel?.name]);
|
|
108
|
+
|
|
95
109
|
return <><tr className={reversed ? ' reversed' : ''}>
|
|
96
110
|
{advanced && (<th className={"mini"}>
|
|
97
111
|
<div className="flex flex-row">
|
|
98
112
|
|
|
99
|
-
<Button onClick={handleFilter} className={
|
|
100
|
-
{filterActive && <Button onClick={() => handleAdvancedFilter()}><FaWrench /></Button>}
|
|
113
|
+
<Button type={"button"} onClick={handleFilter} className={iconFilterActive ? ' active' : ''}><FaFilter/></Button>
|
|
114
|
+
{filterActive && <Button type={"button"} onClick={() => handleAdvancedFilter()}><FaWrench /></Button>}
|
|
101
115
|
<CheckboxField checkbox={true} checked={checkedItems?.length === data.length} onChange={e => {
|
|
102
116
|
if (checkedItems?.length === data.length) {
|
|
103
117
|
setCheckedItems([]);
|
|
@@ -117,7 +131,7 @@ const Header = ({
|
|
|
117
131
|
setPagedFilters(pagedFilters => ({
|
|
118
132
|
...pagedFilters,
|
|
119
133
|
[model.name]: c || pagedFilters[model.name] || {} }));
|
|
120
|
-
}} initialValue={{ $and: pagedFilterToMongoConds(pagedFilters, model)}} models={models} model={model
|
|
134
|
+
}} initialValue={{ $and: pagedFilterToMongoConds(pagedFilters, model)}} models={models} model={model} checkedItems={checkedItems} setCheckedItems={setCheckedItems} data={data} filterActive={filterActive} onChangeFilterValue={onChangeFilterValue} setFilterValues={setFilterValues}/>
|
|
121
135
|
<Button onClick={() =>{
|
|
122
136
|
setPagedFilters(pagedFilters => ({
|
|
123
137
|
...pagedFilters,
|
|
@@ -144,11 +158,11 @@ const Header = ({
|
|
|
144
158
|
</th>;
|
|
145
159
|
|
|
146
160
|
totalCol++;
|
|
147
|
-
return <FilterField advanced={advanced} reversed={reversed} filterValues={filterValues} setFilterValues={setFilterValues}
|
|
161
|
+
return <FilterField key={"datatable-th-"+model.name+":"+field.name} advanced={advanced} reversed={reversed} filterValues={filterValues} setFilterValues={setFilterValues}
|
|
148
162
|
model={model} field={field} active={filterActive}
|
|
149
163
|
onChangeFilterValue={onChangeFilterValue}/>;
|
|
150
164
|
})}
|
|
151
|
-
{advanced && (<th><Trans i18nKey="actions">Actions</Trans>
|
|
165
|
+
{advanced && !selectionMode && (<th><Trans i18nKey="actions">Actions</Trans>
|
|
152
166
|
{Object.keys(pagedFilters[model.name] || {})
|
|
153
167
|
.filter(f=> Object.keys(pagedFilters[model.name]?.[f] || {}).length > 0).length > 0 && <div>
|
|
154
168
|
<button onClick={() => {
|
|
@@ -246,9 +260,9 @@ export function DataTable({
|
|
|
246
260
|
onShowAPI,
|
|
247
261
|
filterValues,
|
|
248
262
|
setFilterValues = () => {},
|
|
249
|
-
data: propData,
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
data: propData,
|
|
264
|
+
advanced= true,
|
|
265
|
+
selectionMode= false
|
|
252
266
|
}) {
|
|
253
267
|
const {
|
|
254
268
|
models,
|
|
@@ -272,20 +286,9 @@ export function DataTable({
|
|
|
272
286
|
const isDataLoaded = true;
|
|
273
287
|
const [importVisible, setImportVisible] = useState(false);
|
|
274
288
|
const [filterActive, setFilterActive] = useState(false)
|
|
275
|
-
|
|
276
|
-
const [showPackGallery, setShowPackGallery] = useState(false);
|
|
277
289
|
const [showExportDialog, setExportDialogVisible] = useState(false);
|
|
278
290
|
|
|
279
|
-
|
|
280
291
|
const [selectedRow, setSelectedRow] = useState(null);
|
|
281
|
-
|
|
282
|
-
const [showAddPack, setAddPackVisible] = useState(false);
|
|
283
|
-
const handleAddPack = () => {
|
|
284
|
-
setAddPackVisible(!showAddPack);
|
|
285
|
-
}
|
|
286
|
-
const handleShowPacks = () => {
|
|
287
|
-
setShowPackGallery(true);
|
|
288
|
-
};
|
|
289
292
|
const handleDelete = async (item) => {
|
|
290
293
|
if (model && item && item._id) { // Assurez-vous d'avoir un identifiant unique pour chaque élément
|
|
291
294
|
try {
|
|
@@ -355,7 +358,9 @@ export function DataTable({
|
|
|
355
358
|
params.append("_user", getUserId(me));
|
|
356
359
|
|
|
357
360
|
// Cas de la table de données : requête paginée
|
|
358
|
-
|
|
361
|
+
|
|
362
|
+
const m = Config.Get('maxRequestData', maxRequestData);
|
|
363
|
+
params.append('limit', m + '');
|
|
359
364
|
params.append('attachment', '1');
|
|
360
365
|
params.append("depth", data.depth+"");
|
|
361
366
|
if( data.withModels )
|
|
@@ -446,17 +451,12 @@ export function DataTable({
|
|
|
446
451
|
setFilterActive(!filterActive);
|
|
447
452
|
}
|
|
448
453
|
|
|
449
|
-
|
|
450
454
|
const [lightboxIndex, setLightboxIndex] = useState(0);
|
|
451
455
|
const [lightboxOpened, setLightboxOpened] = useState(false);
|
|
452
456
|
const [lightboxSlides, setLightboxSlides] = useState([]);
|
|
453
|
-
const [tutorialDialogVisible, setTutorialDialogVisible] = useState(false);
|
|
454
457
|
|
|
455
458
|
const [newPackName, setNewPackName] = useState(''); // pack
|
|
456
459
|
|
|
457
|
-
const handleShowTutorialMenu = () => {
|
|
458
|
-
setTutorialDialogVisible(!tutorialDialogVisible);
|
|
459
|
-
}
|
|
460
460
|
|
|
461
461
|
if (!model)
|
|
462
462
|
return <></>;
|
|
@@ -477,32 +477,23 @@ export function DataTable({
|
|
|
477
477
|
|
|
478
478
|
onDuplicateData(dataToDuplicate);
|
|
479
479
|
};
|
|
480
|
+
|
|
481
|
+
const desc = t(`model_description_${selectedModel.name}`, selectedModel.description);
|
|
480
482
|
return (
|
|
481
483
|
<div className={`datatable${filterActive ? ' filter-active' : ''}`}>
|
|
482
|
-
{advanced && <div className="flex actions
|
|
483
|
-
|
|
484
|
-
|
|
484
|
+
{advanced && !selectionMode && <div className="flex actions">
|
|
485
|
+
{t(`model_${selectedModel?.name}`, selectedModel?.name) !== selectedModel?.name && (
|
|
486
|
+
<span className="badge"><strong>model</strong> : {selectedModel?.name}</span>)}
|
|
487
|
+
{selectedModel.name === 'dashboard' && <Button className={"btn"} onClick={() => {
|
|
488
|
+
nav('/user/'+getUserHash(me)+'/dashboards');
|
|
489
|
+
}}><FaEye /> Tableaux de bord</Button> }
|
|
490
|
+
{desc && <p className="model-desc hint">{desc}</p>}
|
|
485
491
|
<Button onClick={handleImport} title={t("btns.import")}><FaFileImport/><Trans
|
|
486
492
|
i18nKey="btns.import">Importer</Trans></Button>
|
|
487
|
-
<Button disabled={isLoading} onClick={handleExport} title={t("btns.export")}><FaFileExport/><Trans
|
|
488
|
-
i18nKey="btns.export">Exporter</Trans></Button>
|
|
489
|
-
<Button className="tourStep-import-datapack" onClick={handleShowPacks} title={t("btns.addPack")}><FaPlus/><Trans
|
|
490
|
-
i18nKey="btns.addPack">Packs...</Trans></Button>
|
|
491
|
-
|
|
492
|
-
<Button className={"tourStep-tutorials " + (/^demo[0-9]{1,2}$/.test(me.username) ? "btn-primary" : "btn")} onClick={handleShowTutorialMenu} title={t("btns.showTutos")}><FaPlus/><Trans
|
|
493
|
-
i18nKey="btns.showTutos">Tutoriels</Trans></Button>
|
|
493
|
+
<Button disabled={isLoading} onClick={handleExport} title={t("btns.export")}><FaFileExport/><Trans i18nKey="btns.export">Exporter</Trans></Button>
|
|
494
494
|
{!/^demo[0-9]{1,2}$/.test(me.username) && (<Button onClick={handleBackup} title={t("btns.backup")}><FaDatabase/><Trans
|
|
495
495
|
i18nKey="btns.backup">Backup</Trans></Button>)}
|
|
496
|
-
|
|
497
|
-
{tutorialDialogVisible && (
|
|
498
|
-
<Dialog isClosable={true} isModal={true} onClose={() => setTutorialDialogVisible(false)}>
|
|
499
|
-
<TutorialsMenu />
|
|
500
|
-
</Dialog>
|
|
501
|
-
)}
|
|
502
|
-
</DialogProvider>
|
|
503
|
-
<Button className="btn btn-primary" onClick={() => {
|
|
504
|
-
onShowAPI(selectedModel);
|
|
505
|
-
}}><FaBook/> {t('btns.api', 'API')}</Button>
|
|
496
|
+
|
|
506
497
|
</div>}
|
|
507
498
|
<div className={"table-wrapper"}>
|
|
508
499
|
<Tooltip id={"tooltipFile"} clickable={true} />
|
|
@@ -521,32 +512,29 @@ export function DataTable({
|
|
|
521
512
|
{isDataLoaded && (
|
|
522
513
|
<table>
|
|
523
514
|
<thead>
|
|
524
|
-
<Header advanced={advanced} model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>
|
|
515
|
+
<Header advanced={advanced} model={model} setCheckedItems={setCheckedItems} filterValues={filterValues} data={data} setFilterValues={setFilterValues} onChangeFilterValue={onChangeFilterValue} checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter} selectionMode={selectionMode}/>
|
|
525
516
|
</thead>
|
|
526
517
|
<tbody>
|
|
527
518
|
{(data || []).map((item) => (
|
|
528
519
|
<><DialogProvider>
|
|
529
520
|
<tr key={item._id} onDoubleClick={() => onEdit(item)} onClick={(e) => {
|
|
530
|
-
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
521
|
+
// Empêche le déclenchement du clic sur la ligne si on clique sur un bouton ou un lien
|
|
522
|
+
if (e.target.closest('button, a')) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const isCurrentlyChecked = checkedItems?.some(i => i?._id === item._id);
|
|
526
|
+
if (!isCurrentlyChecked) {
|
|
527
|
+
setCheckedItems([...(checkedItems || []), item]);
|
|
535
528
|
} else {
|
|
536
|
-
setCheckedItems(
|
|
529
|
+
setCheckedItems((checkedItems || []).filter(i => i._id !== item._id));
|
|
537
530
|
}
|
|
538
531
|
}}>
|
|
539
532
|
{advanced && (<td className={"mini"}>
|
|
540
533
|
<CheckboxField checkbox={true} className={"input-ref"}
|
|
541
|
-
checked={checkedItems?.some(i => i
|
|
542
|
-
onChange={(
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
return [...items, item];
|
|
546
|
-
});
|
|
547
|
-
} else {
|
|
548
|
-
setCheckedItems(items => items.filter(i => i._id !== item._id));
|
|
549
|
-
}
|
|
534
|
+
checked={checkedItems?.some(i => i?._id === item._id)}
|
|
535
|
+
onChange={() => {
|
|
536
|
+
// La logique est gérée par le onClick de la <tr>
|
|
537
|
+
// pour permettre de cliquer n'importe où sur la ligne.
|
|
550
538
|
}}/></td>)}
|
|
551
539
|
{(model?.fields ||[]).map(field => {
|
|
552
540
|
|
|
@@ -619,7 +607,6 @@ export function DataTable({
|
|
|
619
607
|
}
|
|
620
608
|
if (field.type === 'geolocation') {
|
|
621
609
|
const geoData = item[field.name];
|
|
622
|
-
console.log({geoData})
|
|
623
610
|
if (geoData && geoData.coordinates && geoData.coordinates.length === 2) {
|
|
624
611
|
const [lng, lat] = geoData.coordinates;
|
|
625
612
|
const coordinatesText = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
|
|
@@ -651,14 +638,18 @@ export function DataTable({
|
|
|
651
638
|
})));
|
|
652
639
|
e.preventDefault();
|
|
653
640
|
};
|
|
654
|
-
|
|
641
|
+
|
|
642
|
+
if( !Array.isArray(item[field.name]))
|
|
643
|
+
return <td key={field.name}>
|
|
644
|
+
</td>
|
|
645
|
+
t = (item[field.name] ||[]).map((it,i) => {
|
|
655
646
|
|
|
656
647
|
const r = `
|
|
657
648
|
<strong>filename</strong> : ${it.filename}<br />
|
|
658
649
|
<strong>guid</strong> : ${it.guid}<br />
|
|
659
650
|
<strong>type</strong> : ${it.mimeType}<br />
|
|
660
651
|
<strong>size</strong> : ${it.size} bytes<br />
|
|
661
|
-
<strong>timestamp</strong> : ${it.
|
|
652
|
+
<strong>timestamp</strong> : ${it.createdAt ? new Date(it.createdAts).toLocaleString(lang) : ''}
|
|
662
653
|
`;
|
|
663
654
|
return <a key={it.guid} href={`/resources/${it.guid}`} target="_blank"
|
|
664
655
|
data-tooltip-id={"tooltipFile"} data-tooltip-html={r}
|
|
@@ -693,7 +684,7 @@ export function DataTable({
|
|
|
693
684
|
<strong>guid</strong> : ${item[field.name].guid}<br />
|
|
694
685
|
<strong>type</strong> : ${item[field.name].mimeType}<br />
|
|
695
686
|
<strong>size</strong> : ${item[field.name].size} bytes<br />
|
|
696
|
-
<strong>timestamp</strong> : ${item[field.name].
|
|
687
|
+
<strong>timestamp</strong> : ${new Date(item[field.name].createdAt)?.toLocaleString(lang)}
|
|
697
688
|
`;
|
|
698
689
|
if (['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/svg+xml', 'image/webp', 'image/bmp', 'image/tiff', 'image/x-icon', 'image/x-windows-bmp'].includes(item[field.name].mimeType))
|
|
699
690
|
return <td key={field.name}>{hiddenable(<a
|
|
@@ -767,7 +758,7 @@ export function DataTable({
|
|
|
767
758
|
return <td key={field.name} className={isLightColor(field.color)?"lighted":""} style={{backgroundColor: field.color, color: isLightColor(field.color) ? 'black': '#E3E3E3'}}>
|
|
768
759
|
{hiddenable(item[field.name])}</td>;
|
|
769
760
|
})}
|
|
770
|
-
{advanced && (<td>
|
|
761
|
+
{advanced && !selectionMode && (<td>
|
|
771
762
|
<button data-tooltip-id="tooltipActions"
|
|
772
763
|
data-tooltip-content={t('btns.edit', 'Modifier')}
|
|
773
764
|
onClick={() => handleEdit(item)}><FaPencil/></button>
|
|
@@ -799,7 +790,7 @@ export function DataTable({
|
|
|
799
790
|
<tfoot>
|
|
800
791
|
{data.length > 10 && (<Header advanced={advanced} reversed={true} model={model} setCheckedItems={setCheckedItems}
|
|
801
792
|
filterValues={filterValues} data={data}
|
|
802
|
-
setFilterValues={setFilterValues}
|
|
793
|
+
setFilterValues={setFilterValues} selectionMode={selectionMode}
|
|
803
794
|
onChangeFilterValue={onChangeFilterValue}
|
|
804
795
|
checkedItems={checkedItems} filterActive={filterActive} handleFilter={handleFilter}/>)}
|
|
805
796
|
</tfoot>
|
|
@@ -821,14 +812,9 @@ export function DataTable({
|
|
|
821
812
|
/>
|
|
822
813
|
<ExportDialog isOpen={showExportDialog} onClose={() => {
|
|
823
814
|
setExportDialogVisible(false);
|
|
824
|
-
}} availableModels={models} currentModel={selectedModel
|
|
815
|
+
}} availableModels={models} currentModel={selectedModel?.name} hasSelection={true} onExport={(data)=>{
|
|
825
816
|
exportMutation(data);
|
|
826
817
|
}} />
|
|
827
|
-
{showPackGallery && (
|
|
828
|
-
<Dialog isClosable={true} isModal={true} onClose={() => setShowPackGallery(false)}>
|
|
829
|
-
<PackGallery />
|
|
830
|
-
</Dialog>
|
|
831
|
-
)}
|
|
832
818
|
</DialogProvider>
|
|
833
819
|
</div>
|
|
834
820
|
</div>
|