data-primals-engine 1.3.3 → 1.3.4
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/client/package-lock.json +7 -6
- package/client/package.json +1 -1
- package/client/src/App.scss +35 -0
- package/client/src/DataLayout.jsx +38 -9
- package/client/src/Dialog.scss +0 -3
- package/client/src/Field.jsx +75 -2
- package/client/src/ModelCreator.jsx +23 -1
- package/client/src/ModelList.jsx +19 -1
- package/client/src/contexts/UIContext.jsx +19 -10
- package/client/src/translations.js +24 -0
- package/package.json +2 -1
- package/server.js +1 -0
- package/src/core.js +9 -0
- package/src/defaultModels.js +52 -0
- package/src/filter.js +260 -256
- package/src/i18n.js +2 -2
- package/src/modules/data/data.js +1 -1
- package/src/modules/data/data.routes.js +1 -1
- package/src/modules/workflow.js +1818 -1817
package/client/package-lock.json
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"express-rate-limit": "^7.5.1",
|
|
31
31
|
"lowlight": "^3.3.0",
|
|
32
32
|
"react": "18.3.1",
|
|
33
|
-
"react-big-calendar": "
|
|
33
|
+
"react-big-calendar": "1.15.0",
|
|
34
34
|
"react-dom": "18.3.1",
|
|
35
35
|
"react-router": "^7.2.0",
|
|
36
36
|
"react-switch": "^6.1.0",
|
|
@@ -5619,9 +5619,10 @@
|
|
|
5619
5619
|
}
|
|
5620
5620
|
},
|
|
5621
5621
|
"node_modules/react-big-calendar": {
|
|
5622
|
-
"version": "1.
|
|
5623
|
-
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.
|
|
5624
|
-
"integrity": "sha512-
|
|
5622
|
+
"version": "1.15.0",
|
|
5623
|
+
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.15.0.tgz",
|
|
5624
|
+
"integrity": "sha512-RNiPH1Vh/fpJpNIValpl6lHvuEroWkDvS8z3YW2QpmGUuAk6a0Q1uEujlQTd/gQrpKAaBA4Gyc1mzCdNIQ7DZQ==",
|
|
5625
|
+
"license": "MIT",
|
|
5625
5626
|
"dependencies": {
|
|
5626
5627
|
"@babel/runtime": "^7.20.7",
|
|
5627
5628
|
"clsx": "^1.2.1",
|
|
@@ -5641,8 +5642,8 @@
|
|
|
5641
5642
|
"uncontrollable": "^7.2.1"
|
|
5642
5643
|
},
|
|
5643
5644
|
"peerDependencies": {
|
|
5644
|
-
"react": "^16.14.0 || ^17 || ^18
|
|
5645
|
-
"react-dom": "^16.14.0 || ^17 || ^18
|
|
5645
|
+
"react": "^16.14.0 || ^17 || ^18",
|
|
5646
|
+
"react-dom": "^16.14.0 || ^17 || ^18"
|
|
5646
5647
|
}
|
|
5647
5648
|
},
|
|
5648
5649
|
"node_modules/react-chartjs-2": {
|
package/client/package.json
CHANGED
package/client/src/App.scss
CHANGED
|
@@ -1505,3 +1505,38 @@ select[multiple]{
|
|
|
1505
1505
|
}
|
|
1506
1506
|
|
|
1507
1507
|
|
|
1508
|
+
// Styles for TextField with suggestions
|
|
1509
|
+
.with-suggestions {
|
|
1510
|
+
position: relative;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
.suggestions-list {
|
|
1514
|
+
position: absolute;
|
|
1515
|
+
background-color: white;
|
|
1516
|
+
border: 1px solid #ccc;
|
|
1517
|
+
border-top: none;
|
|
1518
|
+
list-style: none;
|
|
1519
|
+
margin: 0;
|
|
1520
|
+
padding: 0;
|
|
1521
|
+
width: 100%;
|
|
1522
|
+
z-index: 1000; // High z-index to appear over other elements
|
|
1523
|
+
max-height: 250px;
|
|
1524
|
+
overflow-y: auto;
|
|
1525
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
1526
|
+
border-radius: 0 0 4px 4px;
|
|
1527
|
+
|
|
1528
|
+
li {
|
|
1529
|
+
padding: 8px 12px;
|
|
1530
|
+
cursor: pointer;
|
|
1531
|
+
display: flex;
|
|
1532
|
+
align-items: center;
|
|
1533
|
+
gap: 8px;
|
|
1534
|
+
font-size: 0.9rem;
|
|
1535
|
+
|
|
1536
|
+
&:hover {
|
|
1537
|
+
background-color: #f0f8ff; // A light blue hover
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
|
|
@@ -32,6 +32,7 @@ import KanbanConfigModal from "./KanbanConfigModal.jsx";
|
|
|
32
32
|
import CalendarConfigModal from "./CalendarConfigModal.jsx";
|
|
33
33
|
import KanbanView from "./KanbanView.jsx";
|
|
34
34
|
import CalendarView from "./CalendarView.jsx";
|
|
35
|
+
import {useParams, useSearchParams} from "react-router-dom";
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
|
|
@@ -45,6 +46,7 @@ const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
|
|
|
45
46
|
);
|
|
46
47
|
|
|
47
48
|
function DataLayout() {
|
|
49
|
+
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
48
50
|
const [viewSettings, setViewSettings] = useLocalStorage('viewSettings', {});
|
|
49
51
|
|
|
50
52
|
const [isCalendarModalOpen, setCalendarModalOpen] = useState(false);
|
|
@@ -68,7 +70,8 @@ function DataLayout() {
|
|
|
68
70
|
pagedFilters, pagedSort,
|
|
69
71
|
elementsPerPage,
|
|
70
72
|
setRelations,
|
|
71
|
-
generatedModels
|
|
73
|
+
generatedModels,
|
|
74
|
+
models
|
|
72
75
|
} = useModelContext(); // Utilisez le contexte
|
|
73
76
|
const queryClient = useQueryClient();
|
|
74
77
|
|
|
@@ -87,6 +90,23 @@ function DataLayout() {
|
|
|
87
90
|
const [showDataEditor, setDataEditorVisible] = useState(false);
|
|
88
91
|
const [showAPIInfo, setAPIInfoVisible] = useState(false);
|
|
89
92
|
|
|
93
|
+
const mod = searchParams.get('model');
|
|
94
|
+
useEffect(() =>{
|
|
95
|
+
if (selectedModel?.name)
|
|
96
|
+
history.pushState({}, null, '?model='+selectedModel?.name);
|
|
97
|
+
}, [selectedModel?.name])
|
|
98
|
+
useEffect(() =>{
|
|
99
|
+
setSelectedModel(models.find(f => f.name === mod));
|
|
100
|
+
setEditionMode(false);
|
|
101
|
+
}, [mod, models])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
setSelectedModel(null)
|
|
106
|
+
setDataEditorVisible(false);
|
|
107
|
+
setAPIInfoVisible(false);
|
|
108
|
+
setEditionMode(true);
|
|
109
|
+
}, []);
|
|
90
110
|
// La vue courante est dérivée du modèle sélectionné et des préférences stockées.
|
|
91
111
|
const currentView = useMemo(() => {
|
|
92
112
|
if (!selectedModel) return 'table';
|
|
@@ -426,13 +446,6 @@ function DataLayout() {
|
|
|
426
446
|
queryClient.invalidateQueries(['api/data', model.name, r]);
|
|
427
447
|
}
|
|
428
448
|
|
|
429
|
-
useEffect(() => {
|
|
430
|
-
setSelectedModel(null)
|
|
431
|
-
setDataEditorVisible(false);
|
|
432
|
-
setAPIInfoVisible(false);
|
|
433
|
-
setEditionMode(true);
|
|
434
|
-
}, []);
|
|
435
|
-
|
|
436
449
|
const deleteMutation = useMutation((selectedModels) => {
|
|
437
450
|
return fetch('/api/data/'+checkedItems.map(m => m._id).join(',')+'?lang='+lang+'&_user='+encodeURIComponent(getUserId(me)), {
|
|
438
451
|
method: 'DELETE', headers: {
|
|
@@ -530,7 +543,7 @@ function DataLayout() {
|
|
|
530
543
|
|
|
531
544
|
|
|
532
545
|
const [currentProfile, setCurrentProfile] = useLocalStorage('profile', null);
|
|
533
|
-
const {isTourOpen, setIsTourOpen, currentTourSteps, allTourSteps, setTourStepIndex, setCurrentTourSteps, currentTour,setCurrentTour} = useUI();
|
|
546
|
+
const {isTourOpen, setIsTourOpen, currentTourSteps, allTourSteps, setTourStepIndex, setCurrentTourSteps, currentTour,setCurrentTour, addLaunchedTour} = useUI();
|
|
534
547
|
|
|
535
548
|
const startTour = () => {
|
|
536
549
|
setIsTourOpen(true);
|
|
@@ -538,12 +551,28 @@ function DataLayout() {
|
|
|
538
551
|
|
|
539
552
|
const closeTour = (completedTourName) => {
|
|
540
553
|
// On génère le nom du tour de démo pour le comparer
|
|
554
|
+
// This function is called when ANY tour is closed.
|
|
555
|
+
// It needs to persist the fact that the tour has been seen.
|
|
556
|
+
|
|
557
|
+
// 1. Add the tour's unique name to the list of launched tours.
|
|
558
|
+
// This list is managed by the UI context and stored in localStorage.
|
|
559
|
+
if (addLaunchedTour) { // Check if the function exists to be safe
|
|
560
|
+
addLaunchedTour(completedTourName);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 2. Close the tour's UI.
|
|
564
|
+
setIsTourOpen(false);
|
|
565
|
+
|
|
566
|
+
// 3. Specific logic for the very first "demo" tour:
|
|
567
|
+
// We also set the user's profile to mark that they are no longer a brand new user.
|
|
568
|
+
// This prevents the demo tour from trying to launch on every page load.
|
|
541
569
|
const demoTourName = `tour_${getObjectHash({steps: allTourSteps.demo || []})}`;
|
|
542
570
|
|
|
543
571
|
// Si le tour qui vient de se terminer est le tour de démo
|
|
544
572
|
if (completedTourName === demoTourName) {
|
|
545
573
|
|
|
546
574
|
setIsTourOpen(false);
|
|
575
|
+
setCurrentProfile({ lastSeen: new Date().toISOString() });
|
|
547
576
|
}
|
|
548
577
|
};
|
|
549
578
|
|
package/client/src/Dialog.scss
CHANGED
package/client/src/Field.jsx
CHANGED
|
@@ -19,13 +19,14 @@ import {
|
|
|
19
19
|
FaArrowDown,
|
|
20
20
|
FaArrowUp, FaAt,
|
|
21
21
|
FaCalendar, FaCalendarWeek, FaCode, FaFile,
|
|
22
|
-
FaHashtag,
|
|
22
|
+
FaHashtag, FaIcons,
|
|
23
23
|
FaImage,
|
|
24
24
|
FaLink, FaListOl, FaListUl,
|
|
25
25
|
FaLock, FaMinus,
|
|
26
26
|
FaPallet, FaPhone, FaSitemap,
|
|
27
27
|
FaToggleOn
|
|
28
28
|
} from "react-icons/fa";
|
|
29
|
+
import * as Fa6Icons from 'react-icons/fa6'; // Importer Fa6
|
|
29
30
|
import {FaCalendarDays, FaCodeCompare, FaPencil, FaT, FaTableColumns} from "react-icons/fa6";
|
|
30
31
|
import { CodeBlock, tomorrowNightBright } from 'react-code-blocks';
|
|
31
32
|
import SyntaxHighlighter from 'react-syntax-highlighter';
|
|
@@ -93,6 +94,7 @@ const TextField = forwardRef(function TextField(
|
|
|
93
94
|
searchable,
|
|
94
95
|
labelProps,
|
|
95
96
|
showErrors=false,
|
|
97
|
+
before,
|
|
96
98
|
after,
|
|
97
99
|
...rest
|
|
98
100
|
},
|
|
@@ -187,6 +189,7 @@ const TextField = forwardRef(function TextField(
|
|
|
187
189
|
></textarea>
|
|
188
190
|
)}
|
|
189
191
|
|
|
192
|
+
{before}
|
|
190
193
|
<div className={"flex flex-1 flex-no-gap flex-start"}>
|
|
191
194
|
{!mult && (
|
|
192
195
|
<input
|
|
@@ -1491,7 +1494,77 @@ export const ModelField = ({field, disableable=false, showModel=true, value, fie
|
|
|
1491
1494
|
)}
|
|
1492
1495
|
</div>
|
|
1493
1496
|
);
|
|
1494
|
-
};
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
// Fonction pour obtenir le composant icône par son nom
|
|
1500
|
+
const getIconComponent = (iconName) => {
|
|
1501
|
+
if (!iconName) return null;
|
|
1502
|
+
const IconComponent = FaIcons[iconName] || Fa6Icons[iconName];
|
|
1503
|
+
return IconComponent ? <IconComponent /> : null; // Retourne l'élément React ou null
|
|
1504
|
+
};
|
|
1505
|
+
export const IconField = ({name, label, value, disabled, onChange, className, ...rest}) => {
|
|
1506
|
+
const { t } = useTranslation();
|
|
1507
|
+
const [iconSuggestions, setIconSuggestions] = useState([]);
|
|
1508
|
+
// Tri alphabétique pour une recherche plus prévisible
|
|
1509
|
+
const [allFaIcons] = useState(() => [...Object.keys(FaIcons), ...Object.keys(Fa6Icons)].sort());
|
|
1510
|
+
|
|
1511
|
+
const handleIconChange = (e) => {
|
|
1512
|
+
const value = e.target.value;
|
|
1513
|
+
onChange(value);
|
|
1514
|
+
if (value) {
|
|
1515
|
+
const filtered = allFaIcons.filter(
|
|
1516
|
+
icon => icon.toLowerCase().includes(value.toLowerCase())
|
|
1517
|
+
);
|
|
1518
|
+
setIconSuggestions(filtered.slice(0, 20));
|
|
1519
|
+
} else {
|
|
1520
|
+
setIconSuggestions([]);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
const handleIconFocus = () => {
|
|
1525
|
+
if (value) {
|
|
1526
|
+
const filtered = allFaIcons.filter(
|
|
1527
|
+
icon => icon.toLowerCase().includes(value.toLowerCase())
|
|
1528
|
+
);
|
|
1529
|
+
setIconSuggestions(filtered.slice(0, 20));
|
|
1530
|
+
} else {
|
|
1531
|
+
setIconSuggestions(allFaIcons.slice(0, 10));
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
const onSuggestionClick = (suggestion) => {
|
|
1536
|
+
onChange(suggestion);
|
|
1537
|
+
setIconSuggestions([]);
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
return <div className="textfield-wrapper with-suggestions">
|
|
1541
|
+
<div className={"flex flex-1 flex-no-wrap"}>
|
|
1542
|
+
<TextField
|
|
1543
|
+
help={t('modelcreator.field.icon')}
|
|
1544
|
+
id="modelIcon"
|
|
1545
|
+
disabled={disabled}
|
|
1546
|
+
value={value}
|
|
1547
|
+
label={label}
|
|
1548
|
+
before={<div>{getIconComponent(value)}</div>}
|
|
1549
|
+
onChange={handleIconChange}
|
|
1550
|
+
onFocus={handleIconFocus}
|
|
1551
|
+
onBlur={() => setTimeout(() => setIconSuggestions([]), 200)}
|
|
1552
|
+
autoComplete="off"
|
|
1553
|
+
/>
|
|
1554
|
+
</div>
|
|
1555
|
+
{iconSuggestions.length > 0 && (
|
|
1556
|
+
<ul className="suggestions-list">
|
|
1557
|
+
{iconSuggestions.map(icon => (
|
|
1558
|
+
<li key={icon} onMouseDown={() => onSuggestionClick(icon)}>
|
|
1559
|
+
<span className="suggestion-icon">{getIconComponent(icon)}</span>
|
|
1560
|
+
<span>{icon}</span>
|
|
1561
|
+
</li>
|
|
1562
|
+
))}
|
|
1563
|
+
</ul>
|
|
1564
|
+
)}
|
|
1565
|
+
</div>
|
|
1566
|
+
};
|
|
1567
|
+
export const ColorField = ({name, label, value, disabled, onChange, className, ...rest}) => {
|
|
1495
1568
|
// 1. État interne pour une réactivité immédiate de l'interface.
|
|
1496
1569
|
const [internalValue, setInternalValue] = useState(value);
|
|
1497
1570
|
|
|
@@ -5,7 +5,7 @@ import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
|
5
5
|
import {FaArrowDown, FaArrowUp, FaEdit, FaInfo, FaMinus, FaPlus, FaSave, FaTrash} from "react-icons/fa";
|
|
6
6
|
|
|
7
7
|
import "./ModelCreator.scss"
|
|
8
|
-
import {CheckboxField, CodeField, ColorField, NumberField, SelectField, TextField} from "./Field.jsx";
|
|
8
|
+
import {CheckboxField, CodeField, ColorField, IconField, NumberField, SelectField, TextField} from "./Field.jsx";
|
|
9
9
|
import Button from "./Button.jsx";
|
|
10
10
|
import {Trans, useTranslation} from "react-i18next";
|
|
11
11
|
import {
|
|
@@ -42,6 +42,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
42
42
|
const [modelMaxRequestData, setModelMaxRequestData] = useState(initialModel?.maxRequestData || ''); // Utilisation de initialModel
|
|
43
43
|
const [modelDescription, setModelDescription] = useState(initialModel?.name ? t(`model_description_${initialModel?.name}`, initialModel?.description) : '');
|
|
44
44
|
const [modelHistory, setModelHistory] = useState(!!initialModel?.history || undefined);
|
|
45
|
+
const [modelIcon, setModelIcon] = useState(!!initialModel?.icon || undefined);
|
|
45
46
|
const [fields, setFields] = useState([]);
|
|
46
47
|
const [changed, setChanged] = useState(false);
|
|
47
48
|
|
|
@@ -55,6 +56,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
55
56
|
setModelDescription(initialModel.name ? t(`model_description_${initialModel.name}`, initialModel.description) : '');
|
|
56
57
|
setFields([...(initialModel.fields || []).map(m => ({...m}))]);
|
|
57
58
|
setModelHistory(initialModel.history);
|
|
59
|
+
setModelIcon(initialModel.icon);
|
|
58
60
|
} else {
|
|
59
61
|
// Mode création : on réinitialise tout pour une nouvelle génération
|
|
60
62
|
setModelName('');
|
|
@@ -63,6 +65,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
63
65
|
setUseAI(true); // On active l'IA par défaut
|
|
64
66
|
setModelVisible(false);
|
|
65
67
|
setModelHistory(false);
|
|
68
|
+
setModelIcon(null);
|
|
66
69
|
}
|
|
67
70
|
}, [initialModel]);
|
|
68
71
|
|
|
@@ -74,6 +77,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
74
77
|
setModelDescription(selectedModel.description || '');
|
|
75
78
|
setFields(selectedModel.fields || []);
|
|
76
79
|
setModelMaxRequestData(selectedModel.maxRequestData || defaultMaxRequestData);
|
|
80
|
+
setModelIcon(selectedModel.icon || null);
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
}, [generatedModels, selectedGeneratedModelIndex]);
|
|
@@ -134,6 +138,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
134
138
|
setModelMaxRequestData(defaultMaxRequestData);
|
|
135
139
|
setFields([{ name: '', type: 'string' }]);
|
|
136
140
|
setModelHistory(undefined);
|
|
141
|
+
setModelIcon(null);
|
|
137
142
|
}
|
|
138
143
|
setDatasToLoad(datas=>[...datas, modelName]);
|
|
139
144
|
|
|
@@ -177,6 +182,8 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
177
182
|
setModelName('');
|
|
178
183
|
setModelDescription('');
|
|
179
184
|
setSelectedModel(null)
|
|
185
|
+
setModelHistory(undefined)
|
|
186
|
+
setModelIcon(null);
|
|
180
187
|
setFields([{ name: '', type: 'string', _isNewField: true }]);
|
|
181
188
|
setDatasToLoad(datas => datas.filter(f => f !== modelName));
|
|
182
189
|
setGeneratedModels(mods => {
|
|
@@ -213,6 +220,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
213
220
|
description: modelDescription,
|
|
214
221
|
maxRequestData: modelMaxRequestData,
|
|
215
222
|
history: modelHistory,
|
|
223
|
+
icon: modelIcon,
|
|
216
224
|
fields: (fields || []).map((field) => {
|
|
217
225
|
delete field['_isNewField'];
|
|
218
226
|
let otherFields = [];
|
|
@@ -608,6 +616,20 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
|
|
|
608
616
|
}}
|
|
609
617
|
/>
|
|
610
618
|
|
|
619
|
+
<div className="flex field-bg">
|
|
620
|
+
<label htmlFor="modelIcon"><Trans i18nKey={"modelcreator.icon"}>Icône:</Trans></label>
|
|
621
|
+
</div>
|
|
622
|
+
<IconField
|
|
623
|
+
help={t('modelcreator.field.icon')}
|
|
624
|
+
id="modelIcon"
|
|
625
|
+
disabled={modelLocked}
|
|
626
|
+
value={modelIcon}
|
|
627
|
+
onChange={(e) => {
|
|
628
|
+
setModelIcon(e);
|
|
629
|
+
setChanged(true)
|
|
630
|
+
}}
|
|
631
|
+
/>
|
|
632
|
+
|
|
611
633
|
<div className="flex flex-no-wrap">
|
|
612
634
|
<div className="checkbox-label flex flex-1">
|
|
613
635
|
<CheckboxField
|
package/client/src/ModelList.jsx
CHANGED
|
@@ -9,6 +9,17 @@ import useLocalStorage from "./hooks/useLocalStorage.js";
|
|
|
9
9
|
import {Tooltip} from "react-tooltip";
|
|
10
10
|
import {useUI} from "./contexts/UIContext.jsx";
|
|
11
11
|
import {profiles} from "./constants.js";
|
|
12
|
+
import * as FaIcons from "react-icons/fa";
|
|
13
|
+
import * as Fa6Icons from "react-icons/fa6";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// Fonction pour obtenir le composant icône par son nom
|
|
17
|
+
const getIconComponent = (iconName) => {
|
|
18
|
+
if (!iconName) return null;
|
|
19
|
+
const IconComponent = FaIcons[iconName] || Fa6Icons[iconName];
|
|
20
|
+
return IconComponent ? <IconComponent /> : null; // Retourne l'élément React ou null
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
|
|
13
24
|
export function ModelList({ onModelSelect, onCreateModel, onImportModel, onEditModel, onAPIInfo, onNewData }) {
|
|
14
25
|
const {allTourSteps, setIsTourOpen,setCurrentTourSteps, setTourStepIndex, currentTour, setCurrentTour} = useUI();
|
|
@@ -34,6 +45,7 @@ export function ModelList({ onModelSelect, onCreateModel, onImportModel, onEditM
|
|
|
34
45
|
return models.filter(model => {
|
|
35
46
|
return (model.name && t('model_' + model.name, model.name).toLowerCase().includes(lowerSearchTerm)) ||
|
|
36
47
|
(model.fields.some(f =>
|
|
48
|
+
(model.icon && model.icon.toLowerCase().includes(lowerSearchTerm)) ||
|
|
37
49
|
f.name?.toLowerCase().includes(lowerSearchTerm) ||
|
|
38
50
|
f.hint?.toLowerCase().includes(lowerSearchTerm)) ||
|
|
39
51
|
(model.description && model.description.toLowerCase().includes(lowerSearchTerm)))
|
|
@@ -169,12 +181,18 @@ export function ModelList({ onModelSelect, onCreateModel, onImportModel, onEditM
|
|
|
169
181
|
{filteredModels.sort((a, b) => t('model_' + a.name, a.name).localeCompare(t('model_' + b.name, b.name))).map((model) => {
|
|
170
182
|
if (model._user !== me.username)
|
|
171
183
|
return <></>
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
const IconComponent = getIconComponent(model.icon);
|
|
187
|
+
|
|
172
188
|
return (
|
|
173
189
|
<li data-testid={'model_'+model.name} className={`${model.name === selectedModel?.name ? 'active' : ''}`}
|
|
174
190
|
key={'modelist' + model.name} onClick={() => generatedModels.some(g => g.name === model.name) ? onEditModel(model) : onModelSelect(model)}>
|
|
175
191
|
<div className="flex flex-center flex-fw">
|
|
176
192
|
<div
|
|
177
|
-
className="flex-1 break-word">
|
|
193
|
+
className="flex flex-1 break-word">
|
|
194
|
+
<div className={"icon"}>{IconComponent ? IconComponent : <></>}</div>
|
|
195
|
+
<div>{t(`model_${model.name}`, model.name)} {generatedModels.some(f => f.name === model.name) ? '(tmp)' : (mods.some(f => f.mod === model.name) ? `(${mods.find(f => f.mod === model.name).count})` : '')}</div></div>
|
|
178
196
|
<div className="btns">
|
|
179
197
|
{!generatedModels.some(g => g.name === model.name) && (<button data-tooltip-id="tooltipML" data-tooltip-content={t('btns.addData')} onClick={(e) => {
|
|
180
198
|
e.stopPropagation();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ModelContext.jsx
|
|
2
|
-
import React, {createContext, useContext, useEffect, useMemo, useState} from 'react';
|
|
2
|
+
import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react';
|
|
3
3
|
import useLocalStorage from "../hooks/useLocalStorage.js";
|
|
4
4
|
|
|
5
5
|
const UIContext = createContext(null);
|
|
@@ -9,22 +9,31 @@ export const UIProvider = ({ children }) => {
|
|
|
9
9
|
const [tourStepIndex, setTourStepIndex] = useState(0);
|
|
10
10
|
const [currentTourSteps, setCurrentTourSteps] = useState([]);
|
|
11
11
|
const [isTourOpen, setIsTourOpen] = useState(false);
|
|
12
|
-
const [allTourSteps, setAllTourSteps] = useState(
|
|
13
|
-
const [launchedTours, setLaunchedTours] = useState([]);
|
|
12
|
+
const [allTourSteps, setAllTourSteps] = useState({});
|
|
14
13
|
|
|
15
14
|
const [currentTour, setCurrentTour] = useLocalStorage("spotlight-tour", null);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// This is the single source of truth for tours that have been launched.
|
|
16
|
+
// It correctly reads from localStorage on initial load and persists any changes.
|
|
17
|
+
const [launchedTours, setLaunchedTours] = useLocalStorage('launchedTours', []);
|
|
18
|
+
|
|
19
|
+
// A stable helper function to add a tour to the list without overwriting.
|
|
20
|
+
const addLaunchedTour = useCallback((tourName) => {
|
|
21
|
+
setLaunchedTours(prevTours => {
|
|
22
|
+
// Ensure we're working with an array, even if localStorage is empty/corrupted.
|
|
23
|
+
const currentTours = Array.isArray(prevTours) ? prevTours : [];
|
|
24
|
+
if (!currentTours.includes(tourName)) {
|
|
25
|
+
return [...currentTours, tourName];
|
|
26
|
+
}
|
|
27
|
+
return currentTours; // Return unchanged if already present
|
|
28
|
+
});
|
|
29
|
+
}, [setLaunchedTours]); // setLaunchedTours from useLocalStorage is stable
|
|
21
30
|
|
|
22
31
|
const [isLocked, setLocked] = useState(false);
|
|
23
32
|
const contextValue = useMemo(() => ({
|
|
24
33
|
isLocked,
|
|
25
34
|
setLocked,
|
|
26
35
|
currentTourSteps, setCurrentTourSteps,
|
|
27
|
-
launchedTours,setLaunchedTours,
|
|
36
|
+
launchedTours, setLaunchedTours, addLaunchedTour,
|
|
28
37
|
currentTour, setCurrentTour,
|
|
29
38
|
isTourOpen, setIsTourOpen, setAllTourSteps, allTourSteps,
|
|
30
39
|
tourStepIndex, setTourStepIndex
|
|
@@ -34,7 +43,7 @@ useEffect(() =>{
|
|
|
34
43
|
launchedTours,setLaunchedTours,
|
|
35
44
|
currentTour, setCurrentTour,
|
|
36
45
|
isTourOpen, setIsTourOpen, setAllTourSteps, allTourSteps,
|
|
37
|
-
tourStepIndex, setTourStepIndex]);
|
|
46
|
+
tourStepIndex, setTourStepIndex, addLaunchedTour]);
|
|
38
47
|
|
|
39
48
|
return (
|
|
40
49
|
<UIContext.Provider value={contextValue}>
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export const websiteTranslations = {
|
|
3
3
|
fr: {
|
|
4
4
|
translation: {
|
|
5
|
+
"modelcreator.icon": "Icône",
|
|
6
|
+
"modelcreator.field.icon": "Icône (FontAwesome) associée au modèle",
|
|
5
7
|
"modelcreator.field.history": "Permet d'afficher et de superviser les modifications effectuées sur vos données. Consomme de l'espace.'",
|
|
6
8
|
"history": "Historique",
|
|
7
9
|
"history.op.create": "Création",
|
|
@@ -568,6 +570,8 @@ export const websiteTranslations = {
|
|
|
568
570
|
},
|
|
569
571
|
en: {
|
|
570
572
|
translation: {
|
|
573
|
+
"modelcreator.icon": "Icon",
|
|
574
|
+
"modelcreator.field.icon": "Icon (FontAwesome) associated with the model",
|
|
571
575
|
"modelcreator.field.history": "Allows you to view and monitor changes made to your data. Consumes space.",
|
|
572
576
|
"history": "History",
|
|
573
577
|
"history.op.create": "Creation",
|
|
@@ -2008,6 +2012,8 @@ export const websiteTranslations = {
|
|
|
2008
2012
|
},
|
|
2009
2013
|
es: {
|
|
2010
2014
|
translation: {
|
|
2015
|
+
"modelcreator.icon": "Icono",
|
|
2016
|
+
"modelcreator.field.icon": "Icono (FontAwesome) asociado al modelo",
|
|
2011
2017
|
"modelcreator.field.history": "Permite ver y supervisar los cambios realizados en los datos. Consume espacio.",
|
|
2012
2018
|
"history": "Historial",
|
|
2013
2019
|
"history.op.create": "Creación",
|
|
@@ -3450,6 +3456,8 @@ export const websiteTranslations = {
|
|
|
3450
3456
|
},
|
|
3451
3457
|
pt: {
|
|
3452
3458
|
translation: {
|
|
3459
|
+
"modelcreator.icon": "Ícone",
|
|
3460
|
+
"modelcreator.field.icon": "Ícone (FontAwesome) associado ao modelo",
|
|
3453
3461
|
"modelcreator.field.history": "Permite visualizar e monitorar as alterações feitas nos seus dados. Consome espaço.",
|
|
3454
3462
|
"history": "Histórico",
|
|
3455
3463
|
"history.op.create": "Criação",
|
|
@@ -4887,6 +4895,8 @@ export const websiteTranslations = {
|
|
|
4887
4895
|
},
|
|
4888
4896
|
de: {
|
|
4889
4897
|
translation: {
|
|
4898
|
+
"modelcreator.icon": "Symbol",
|
|
4899
|
+
"modelcreator.field.icon": "Mit dem Modell verknüpftes Symbol (FontAwesome)",
|
|
4890
4900
|
"modelcreator.field.history": "Ermöglicht Ihnen, Änderungen an Ihren Daten anzuzeigen und zu überwachen. Verbraucht Speicherplatz.",
|
|
4891
4901
|
"history": "Verlauf",
|
|
4892
4902
|
|
|
@@ -6310,6 +6320,8 @@ export const websiteTranslations = {
|
|
|
6310
6320
|
},
|
|
6311
6321
|
it: {
|
|
6312
6322
|
translation: {
|
|
6323
|
+
"modelcreator.icon": "Icona",
|
|
6324
|
+
"modelcreator.field.icon": "Icona (FontAwesome) associata al modello",
|
|
6313
6325
|
"modelcreator.field.history": "Consente di visualizzare e monitorare le modifiche apportate ai dati. Occupa spazio.",
|
|
6314
6326
|
"history": "Cronologia",
|
|
6315
6327
|
|
|
@@ -7745,6 +7757,8 @@ export const websiteTranslations = {
|
|
|
7745
7757
|
},
|
|
7746
7758
|
cs: {
|
|
7747
7759
|
translation: {
|
|
7760
|
+
"modelcreator.icon": "Ikona",
|
|
7761
|
+
"modelcreator.field.icon": "Ikona (FontAwesome) přidružená k modelu",
|
|
7748
7762
|
"modelcreator.field.history": "Umožňuje zobrazit a sledovat změny provedené ve vašich datech. Spotřebovává místo.",
|
|
7749
7763
|
"history": "Historie",
|
|
7750
7764
|
"history.op.create": "Vytvoření",
|
|
@@ -9174,6 +9188,8 @@ export const websiteTranslations = {
|
|
|
9174
9188
|
},
|
|
9175
9189
|
ru: {
|
|
9176
9190
|
translation: {
|
|
9191
|
+
"modelcreator.icon": "Значок",
|
|
9192
|
+
"modelcreator.field.icon": "Значок (FontAwesome), связанный с моделью",
|
|
9177
9193
|
"modelcreator.field.history": "Позволяет просматривать и отслеживать изменения, внесённые в ваши данные. Занимает место.",
|
|
9178
9194
|
"history": "История",
|
|
9179
9195
|
|
|
@@ -10615,6 +10631,8 @@ export const websiteTranslations = {
|
|
|
10615
10631
|
},
|
|
10616
10632
|
ar: {
|
|
10617
10633
|
translation: {
|
|
10634
|
+
"modelcreator.icon": "أيقونة",
|
|
10635
|
+
"modelcreator.field.icon": "الأيقونة (FontAwesome) المرتبطة بالنموذج",
|
|
10618
10636
|
"modelcreator.field.history": "يسمح لك بعرض ومراقبة التغييرات التي أجريتها على بياناتك. يستهلك مساحة.",
|
|
10619
10637
|
"history": "السجل",
|
|
10620
10638
|
"history.op.create": "إنشاء",
|
|
@@ -12071,6 +12089,8 @@ export const websiteTranslations = {
|
|
|
12071
12089
|
},
|
|
12072
12090
|
sv: {
|
|
12073
12091
|
translation: {
|
|
12092
|
+
"modelcreator.icon": "Ikon",
|
|
12093
|
+
"modelcreator.field.icon": "Ikon (FontAwesome) associerad med modellen",
|
|
12074
12094
|
"modelcreator.field.history": "Låter dig visa och övervaka ändringar som gjorts i dina data. Tar plats.",
|
|
12075
12095
|
"history": "Historik",
|
|
12076
12096
|
"history.op.create": "Skapande",
|
|
@@ -13500,6 +13520,8 @@ export const websiteTranslations = {
|
|
|
13500
13520
|
},
|
|
13501
13521
|
el: {
|
|
13502
13522
|
translation: {
|
|
13523
|
+
"modelcreator.icon": "Εικονίδιο",
|
|
13524
|
+
"modelcreator.field.icon": "Εικονίδιο (FontAwesome) που σχετίζεται με το μοντέλο",
|
|
13503
13525
|
"modelcreator.field.history": "Σας επιτρέπει να βλέπετε και να παρακολουθείτε τις αλλαγές που έγιναν στα δεδομένα σας. Καταλαμβάνει χώρο.",
|
|
13504
13526
|
"history": "Ιστορικό",
|
|
13505
13527
|
"history.op.create": "Δημιουργία",
|
|
@@ -14938,6 +14960,8 @@ export const websiteTranslations = {
|
|
|
14938
14960
|
},
|
|
14939
14961
|
fa: {
|
|
14940
14962
|
"translation": {
|
|
14963
|
+
"modelcreator.icon": "آیکون",
|
|
14964
|
+
"modelcreator.field.icon": "آیکون (FontAwesome) مرتبط با مدل",
|
|
14941
14965
|
"modelcreator.field.history": "به شما امکان میدهد تغییرات ایجاد شده در دادههای خود را مشاهده و نظارت کنید. فضا مصرف میکند.",
|
|
14942
14966
|
"history": "تاریخچه",
|
|
14943
14967
|
"history.op.create": "ایجاد",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"react-markdown": "^10.1.0",
|
|
93
93
|
"read-excel-file": "^5.8.8",
|
|
94
94
|
"request-ip": "^3.3.0",
|
|
95
|
+
"safe-regex": "^2.1.1",
|
|
95
96
|
"sanitize-html": "^2.17.0",
|
|
96
97
|
"sirv": "^3.0.1",
|
|
97
98
|
"stripe": "^18.4.0",
|
package/server.js
CHANGED
|
@@ -11,6 +11,7 @@ import {port} from "./src/constants.js";
|
|
|
11
11
|
|
|
12
12
|
Config.Set("modules", ["mongodb", "data", "file", "bucket", "workflow","user", "assistant", "swagger"])
|
|
13
13
|
Config.Set("middlewares", []);
|
|
14
|
+
Config.Set("useDemoAccounts", true);
|
|
14
15
|
|
|
15
16
|
const bench = GameObject.Create("Benchmark");
|
|
16
17
|
const timer = bench.addComponent(BenchmarkTool);
|
package/src/core.js
CHANGED
|
@@ -9,6 +9,15 @@ export function escapeRegex(string) {
|
|
|
9
9
|
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export function isValidRegex(s) {
|
|
13
|
+
try {
|
|
14
|
+
const m = s.match(/^([/~@;%#'])(.*?)\1([gimsuy]*)$/);
|
|
15
|
+
return m ? !!new RegExp(m[2],m[3])
|
|
16
|
+
: false;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
12
21
|
export function escapeHtml(string){
|
|
13
22
|
return string.replace(/(javascript|data|vbscript):/gi, '').replace(/[^\w-_. ]/gi, function (c) {
|
|
14
23
|
return `&#${c.charCodeAt(0)};`;
|