data-primals-engine 1.2.0 → 1.2.2

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 CHANGED
@@ -9,19 +9,27 @@
9
9
 
10
10
  > Whether you're building a CRM, e-commerce site, CMS, or SaaS platform, **data-primals-engine** provides the strong foundations so you can focus on what makes your application unique.
11
11
 
12
+ <p align="center">
13
+ <a href="https://data.primals.net/prez1.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez1.jpg" width="35%"></a>
14
+ <a href="https://data.primals.net/prez6.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez6.jpg" width="35%"></a>
15
+ <a href="https://data.primals.net/api-docs" target="_blank"><img alt="Dark" src="https://data.primals.net/prez5.jpg" width="25%"></a>
16
+ </p>
12
17
  ---
13
18
 
14
19
  ## 🚀 Key Features
15
20
 
16
- - **Dynamic data modeling**: Define and update schemas using JSON, no migrations required.
17
- - **Robust REST API**: Advanced CRUD operations, filtering, and querying.
18
- - **Modular architecture**: Load or extend modules dynamically.
19
- - **Automation workflows**: Trigger actions on events or schedules.
20
- - **Authentication & authorization**: Role-based access control, pluggable providers.
21
- - **📦 Starter Packs**: CRM, e-commerce, showcase websites, etc.
22
- - **🧠 AI Integration**: Supports OpenAI, Google Gemini via LangChain.
23
- - **🌐 i18n support**: Multilingual interfaces, translated validations.
24
- - **📄 Auto Documentation**: Swagger available at `/api-docs`.
21
+ - **Dynamic Data Modeling**: Define and update schemas using JSON, no migrations required.
22
+ - **Custom API Endpoints**: Create server-side logic and new API endpoints directly from the UI in a secure, sandboxed environment.
23
+ - **Automation Workflows**: Trigger complex actions based on data events (create, update, delete) or schedules (cron).
24
+ - **Advanced Querying & Aggregation**: Go beyond simple filters with deep relation expansion, complex lookups, and dynamic calculated fields.
25
+ - **Integrated Backup & Restore**: Secure, encrypted user data backups with rotation policies, supporting both local and AWS S3 storage.
26
+ - **Event-Driven & Extensible**: A core event system allows for deep customization and the easy creation of new modules or plugins.
27
+ - **Authentication & Authorization**: Robust role-based access control (RBAC) and pluggable user providers.
28
+ - **Built-in File Management**: Handle file uploads seamlessly with integrated support for AWS S3 storage.
29
+ - **🧠 AI Integration**: Natively supports OpenAI and Google Gemini models via LangChain for content generation, analysis, and more.
30
+ - **🌐 Internationalization (i18n)**: Fully supports multilingual interfaces and user-specific translated data.
31
+ - **📦 Starter Packs**: Quickly bootstrap applications with pre-built data packs for CRM, e-commerce, and more.
32
+ - **📄Auto-Generated API Documentation**: Interactive API documentation available via the interface or at `/api-docs`.
25
33
 
26
34
 
27
35
  ## 🌟 Why Choose data-primals-engine?
@@ -85,8 +93,8 @@ MONGO_DB_URL=mongodb://127.0.0.1:27017
85
93
  | SMTP_USER | Username for SMTP authentication. | user@example.com |
86
94
  | SMTP_PASS | Password for SMTP authentication. | password |
87
95
  | TLS | Encrypted connection (TLS) mode. Disabled by default | 0/1 false/true |
88
- | CERT | Path to cert file. | certs/ca.crt |
89
- | CA_CERT | Path to CA cert file. | certs/cert.pem |
96
+ | CA_CERT | Path to CA cert file. | certs/ca.crt |
97
+ | CERT | Path to cert file. | certs/cert.pem |
90
98
  | CERT_KEY | Path to the key file for your certificate. | certs/key.pem |
91
99
 
92
100
  ### Start the server
@@ -168,12 +176,11 @@ Activatable features:
168
176
  ### 🎫 Support Ticket System
169
177
  - Create ticket model with [open, pending, resolved] statuses
170
178
  - Configure notification workflows
171
- - Add custom endpoints for analytics
179
+ - Add custom endpoints or dashboards/kpi for analytics
172
180
 
173
181
  ### 🤖 AI Chatbot
174
182
  - Define your model
175
183
  - Set up workflow: "When new entry → generate AI content"
176
- - Connect to frontend chat interface
177
184
 
178
185
  ---
179
186
 
@@ -599,11 +606,68 @@ Expected response :
599
606
  },
600
607
  "publishedPosts": 15
601
608
  }
609
+
610
+ ```
611
+
612
+ ---
613
+ ## Extensibility
614
+
615
+ ### Events (Triggers) Table
616
+ > You can use the events below to access the engine and manipulate API responses.
617
+ > It is useful for custom modules or middlewares for your application.
618
+
619
+ Just use
620
+
621
+ ```javascript
622
+ Event.Listen("OnDataAdded", (data) => {
623
+ my_callback()
624
+ }, "event", "user");
625
+ ```
626
+
627
+ or the system version
628
+ ```javascript
629
+ Event.Listen("OnDataAdded", (engine, data) => {
630
+ my_callback()
631
+ }, "event", "system");
632
+ ```
633
+
634
+ | Event | Description | Scope | Triggered by | Arguments (Payload) |
635
+ | :--- |:------------------------------------------------------------------------| :--- | :--- | :--- |
636
+ | OnServerStart | Triggered once the HTTP server is started and listening. | System | engine.start() | engine |
637
+ | OnServerStop | Triggered right after the HTTP server is stopped. | System | engine.stop() | engine |
638
+ | OnModelsLoaded | Triggered after the initial models are loaded and validated at startup. | System | setupInitialModels() | engine, dbModels |
639
+ | OnModelsDeleted | Triggered after all models are deleted via the reset function. | System | engine.resetModels() | engine |
640
+ | OnUserDataDumped | Triggered after a user's data has been backed up (dumped). | System | jobDumpUserData() | engine |
641
+ | OnDataRestored | Triggered after a user's data has been restored from a backup. | System | loadFromDump() | (none) |
642
+ | OnPackInstalled | Triggered after a data pack has been successfully installed. | System | installPack() | pack |
643
+ | OnModelEdited | Triggered after a model definition has been modified. | System & User | editModel() | System: engine, newModel (Pipeline*)<br>User: newModel (or the version modified by the system) |
644
+ | OnDataAdded | Triggered after new data has been inserted. | System & User | insertData() | System: engine, insertedIds (Pipeline*)<br>User: insertedIds (or the version modified by the system) |
645
+ | OnDataDeleted | Triggered just after data is actually deleted. | System & User | deleteData() | System: engine, {model, filter} (Pipeline*)<br>User: {model, filter} |
646
+ | OnDataSearched | Triggered after a data search. | System & User | searchData() | System: engine, {data, count} (Pipeline*)<br>User: {data, count} (or the version modified by the system) |
647
+ | OnDataExported | Triggered after a data export. | System & User | exportData() | System: engine, exportResults, modelsToExport (Pipeline*)<br>User: exportResults, modelsToExport (or the version modified by the system) |
648
+
649
+ ### Triggering events
650
+
651
+ If you want to provide your own hooks, you can call :
652
+ ```javascript
653
+ const result = Event.Trigger("OnMyCustomEvent", "event", "user", ...args);
602
654
  ```
655
+ Results are merged together if multiple events are triggered.
656
+ - strings are concatenated
657
+ - numbers are added
658
+ - booleans are ANDed
659
+ - arrays are concatenated
660
+ - objects are merged using spread operator
661
+
662
+ ---
663
+
603
664
  ## 🤝 Contributing
604
665
 
666
+ Find the issues available for [contributions here](https://github.com/anonympins/data-primals-engine/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee)
667
+
605
668
  1. Fork the repo
606
669
  2. Create your feature branch: `git checkout -b feature/your-feature`
670
+ 3. Launch ```npm run dev``` and make your changes with hot-reload on local port
607
671
  3. Commit changes: `git commit -m "Add new feature"`
608
672
  4. Push to your branch: `git push origin feature/your-feature`
609
673
  5. Open a pull request
@@ -617,4 +681,4 @@ Distributed under the **MIT License**. See `LICENSE` file.
617
681
 
618
682
  ---
619
683
 
620
- ## [🔼](#) Back to Top
684
+ ## [🔼](https://github.com/anonympins/data-primals-engine?tab=readme-ov-file#data-primals-engine) Back to Top
@@ -1083,11 +1083,15 @@ h2.shadow {
1083
1083
  }
1084
1084
 
1085
1085
  .condition-details {
1086
- padding-left: 20px; // Indentation
1086
+ padding-left: 4px;
1087
+ margin-left:2px;
1088
+ @media only screen and (min-width: 480px) {
1089
+ padding-left: 20px; // Indentation
1090
+ margin-left: 10px;
1091
+ margin-top: 5px;
1092
+ margin-bottom: 10px;
1093
+ }
1087
1094
  border-left: 2px solid #eee; // Ligne visuelle
1088
- margin-left: 10px;
1089
- margin-top: 5px;
1090
- margin-bottom: 10px;
1091
1095
  gap: 5px; // Espacement entre les éléments de condition
1092
1096
  }
1093
1097
 
@@ -1290,9 +1294,10 @@ select[multiple]{
1290
1294
  }
1291
1295
 
1292
1296
  .datalayout {
1293
- padding: 0 16px;
1297
+ @media only screen and (min-width: 480px) {
1298
+ padding: 0 16px;
1299
+ }
1294
1300
  }
1295
-
1296
1301
  .main-menu {
1297
1302
  padding: 16px 0;
1298
1303
  @media only screen and (max-width: 479px) {
@@ -8,6 +8,30 @@ import {FaRepeat} from "react-icons/fa6";
8
8
  import {useAuthContext} from "./contexts/AuthContext.jsx";
9
9
 
10
10
 
11
+ // Déterminer si le champ doit être une date
12
+ const isDateArg = (operator, argIndex) => {
13
+ if (!operator) return false;
14
+
15
+ const opConfig = MONGO_CALC_OPERATORS[operator];
16
+ if (!opConfig) {
17
+ console.warn(`Opérateur non trouvé: ${operator}`);
18
+ return false;
19
+ }
20
+
21
+ // Cas spécial pour les opérateurs de date
22
+ if (operator === '$dateAdd' || operator === '$dateSubtract') {
23
+ return argIndex === 0; // Seul le premier argument est une date
24
+ }
25
+
26
+ // Pour les autres opérateurs marqués isDate (comme $hour, $second, etc.)
27
+ if (opConfig.isDate) {
28
+ return true; // Tous les arguments sont des dates pour ces opérateurs
29
+ }
30
+
31
+ return false;
32
+ };
33
+
34
+
11
35
  const OperatorSelector = ({ onSelect }) => {
12
36
  const [search, setSearch] = useState('');
13
37
 
@@ -117,10 +141,12 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
117
141
  setEditing(false);
118
142
  };
119
143
 
144
+ console.log(path);
145
+ console.log(isDateArg(path[path.length - 1]));
120
146
 
121
147
  const renderArgument = (arg, index, parentOperator, args) => {
122
148
  const isSimpleValue = typeof arg !== 'object' || arg === null || Array.isArray(arg);
123
-
149
+ const parentOpConfig = MONGO_CALC_OPERATORS[parentOperator];
124
150
  // Fonction pour mettre à jour cet argument spécifique
125
151
  const handleArgChange = (newArgValue) => {
126
152
  const updatedArgs = [...args];
@@ -148,8 +174,6 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
148
174
  onChange({ [parentOperator]: updatedArgs }, path);
149
175
  }
150
176
  };
151
- const parentOpConfig = MONGO_CALC_OPERATORS[parentOperator];
152
-
153
177
  return (
154
178
  <div key={`arg-${index}`} className="expression-arg">
155
179
  <div className="arg-content">
@@ -163,10 +187,11 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
163
187
  updatedArgs[index] = newValue;
164
188
  onChange({ [parentOperator]: updatedArgs }, path);
165
189
  }}
190
+ isDate={isDateArg(path[path.length - 1], 0)} // Utilis
166
191
  fieldNames={fieldNames}
167
192
  isInFindContext={isInFindContext}
168
193
  />
169
- {!parentOpConfig?.disableAdvancedValue && (<button
194
+ {!parentOpConfig?.disableAdvancedValue && !isDateArg(path[path.length -1]) && (<button
170
195
  type="button"
171
196
  className="switch-to-expr"
172
197
  onClick={() => handleArgChange({ $eq: [arg, ""] })}
@@ -281,6 +306,7 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
281
306
  onChange={handleFieldChange}
282
307
  fieldNames={fieldNames}
283
308
  isFieldName={true}
309
+ isDate={isDateArg(path[path.length - 1])}
284
310
  currentModelFields={currentModelFields} // Passer les champs complets
285
311
  onlyRelations={true}
286
312
  />
@@ -607,12 +633,13 @@ const ExpressionField = ({ value, onChange, path = [], fieldNames, isRoot = fals
607
633
  }}
608
634
  fieldNames={fieldNames}
609
635
  isInFindContext={isInFindContext}
636
+ isDate={isDateArg(path[path.length - 1])}
610
637
  currentModelFields={currentModelFields} // On propage le contexte
611
638
  />
612
639
 
613
640
  {/* --- MODIFICATION --- */}
614
641
  {/* On affiche le bouton de conversion uniquement si l'opérateur parent le permet */}
615
- {parentOpConfig?.disableAdvancedValue !== true && (
642
+ {parentOpConfig?.disableAdvancedValue !== true && !isDateArg(path[path.length - 1]) && (
616
643
  <button
617
644
  type="button"
618
645
  className="switch-to-expr"
@@ -637,11 +664,13 @@ const FieldInput = ({
637
664
  isFieldName = false,
638
665
  isInFindContext = false,
639
666
  currentModelFields = [],
640
- onlyRelations = false
667
+ onlyRelations = false,
668
+ isDate = false
641
669
  }) => {
642
670
  const [inputValue, setInputValue] = useState(value);
643
671
  const [suggestions, setSuggestions] = useState([]);
644
672
  const [showSuggestions, setShowSuggestions] = useState(false);
673
+ const [showDatePicker, setShowDatePicker] = useState(false);
645
674
 
646
675
  useEffect(() => {
647
676
  setInputValue(value);
@@ -685,6 +714,18 @@ const FieldInput = ({
685
714
  setShowSuggestions(false);
686
715
  };
687
716
 
717
+ // Si c'est un champ date, on affiche le datepicker
718
+ if (isDate) {
719
+ return (
720
+ <div className="date-input-container">
721
+ <input
722
+ type="datetime-local"
723
+ value={inputValue ? new Date(inputValue).toISOString().slice(0, 16) : ''}
724
+ onChange={(e) => handleDateChange(e.target.value)}
725
+ />
726
+ </div>
727
+ );
728
+ }
688
729
  return (
689
730
  <div className="field-input-container">
690
731
  <input
@@ -2,35 +2,19 @@ import React, {forwardRef, useCallback, useEffect, useMemo, useReducer, useRef,
2
2
 
3
3
  import "./App.scss";
4
4
  import {useMutation, useQuery, useQueryClient} from "react-query";
5
- import {CheckboxField, FileField, NumberField, SelectField, TextField} from "./Field.jsx";
6
5
  import ModelCreator from "./ModelCreator.jsx";
7
6
  import {useModelContext} from "./contexts/ModelContext.jsx";
8
- import {FaMagnifyingGlass, FaPencil} from "react-icons/fa6";
9
7
  import {Dialog, DialogProvider} from "./Dialog.jsx";
10
8
  import {Pagination} from "./Pagination.jsx";
9
+ import {Event} from "../../src/events.js";
10
+
11
11
  import {
12
- elementsPerPage, kilobytes,
13
- mainFieldsTypes,
14
- maxBytesPerSecondThrottleData,
15
- maxFileSize,
16
- maxRequestData, metaModels
17
- } from "../../src/constants.js";
18
- import {
19
- FaArrowDown,
20
- FaArrowUp, FaBell,
21
- FaBook, FaCopy,
22
- FaEdit,
23
- FaFileExport, FaFileImport,
24
12
  FaFilter, FaInfo,
25
- FaLanguage,
26
- FaLock,
27
- FaPlus,
28
- FaTrash
29
13
  } from "react-icons/fa";
30
14
  import {getDefaultForType, getUserId} from "../../src/data.js";
31
15
  import {Trans, useTranslation} from "react-i18next";
32
16
 
33
- import {debounce, escapeRegExp, event_trigger, getObjectHash, isGUID} from "../../src/core.js";
17
+ import {getObjectHash} from "../../src/core.js";
34
18
  import Button from "./Button.jsx";
35
19
  import {useAuthContext} from "./contexts/AuthContext.jsx";
36
20
  import APIInfo from "./APIInfo.jsx";
@@ -49,7 +33,6 @@ import CalendarConfigModal from "./CalendarConfigModal.jsx";
49
33
  import KanbanView from "./KanbanView.jsx";
50
34
 
51
35
 
52
- // --- AJOUT : Composants Placeholders pour la démonstration ---
53
36
  const CalendarView = ({ settings, model }) => (
54
37
  <div className="p-4 border rounded-md mt-4 bg-gray-50">
55
38
  <h3 className="font-bold">Vue Calendrier</h3>
@@ -349,7 +332,7 @@ function DataLayout() {
349
332
 
350
333
  console.log('Données enregistrées:', data, selectedModel);
351
334
 
352
- event_trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', {
335
+ Event.Trigger(recordToEdit ? 'API_ADD_DATA' : 'API_ADD_DATA', "custom", "data", {
353
336
  model: selectedModel.name,
354
337
  });
355
338
 
@@ -6,6 +6,8 @@ import {useAuthContext} from "./contexts/AuthContext.jsx";
6
6
  import React, {useEffect, useMemo, useRef, useState} from "react";
7
7
  import {getUserId} from "../../src/data.js";
8
8
  import cronstrue from 'cronstrue/i18n';
9
+ import {Event} from "../../src/events.js";
10
+
9
11
  import {
10
12
  FaBook,
11
13
  FaCopy,
@@ -238,7 +240,7 @@ export function DataTable({
238
240
  console.log('Données non trouvées');
239
241
  }
240
242
 
241
- event_trigger('API_DELETE_DATA', { model: item._model, id: item._id });
243
+ Event.Trigger('API_DELETE_DATA', "custom", "data",{ model: item._model, id: item._id });
242
244
  queryClient.invalidateQueries(['api/data', item._model, 'page', page, elementsPerPage, pagedFilters[item._model], pagedSort[item._model]]);
243
245
 
244
246
  } else {
@@ -493,7 +493,7 @@ const CheckboxField = forwardRef(
493
493
  };
494
494
  return (
495
495
  <>
496
- <div className={cn({field: true, "field-checkbox": true})}>
496
+ <div className={cn({field: true, "field-checkbox": true,"field-bg": true})}>
497
497
  <div className="inline"><input
498
498
  aria-required={required}
499
499
  aria-readonly={readOnly}
@@ -526,7 +526,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
526
526
  {/* Colonne de droite: Formulaire du modèle sélectionné */}
527
527
  <div className="model-form-container">
528
528
  {/* Le formulaire existant est placé ici */}
529
- <div className="flex">
529
+ <div className="flex field-bg">
530
530
  <label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
531
531
  </div>
532
532
  <TextField
@@ -539,7 +539,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
539
539
  required
540
540
  />
541
541
 
542
- <div className="flex">
542
+ <div className="flex field-bg">
543
543
  <label htmlFor="modelDescription"><Trans i18nKey={"modelcreator.description"}>Description:</Trans></label>
544
544
  </div>
545
545
  <TextField
@@ -570,7 +570,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
570
570
  {/* Affichage du formulaire en mode manuel ou édition */}
571
571
  {(!useAI || initialModel) && (
572
572
  <div className="model-form-container">
573
- <div className="flex">
573
+ <div className="flex field-bg">
574
574
  <label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
575
575
  </div>
576
576
  <TextField
@@ -583,7 +583,7 @@ const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGen
583
583
  required
584
584
  />
585
585
 
586
- <div className="flex">
586
+ <div className="flex field-bg">
587
587
  <label htmlFor="modelDescription"><Trans i18nKey={"modelcreator.description"}>Description:</Trans></label>
588
588
  </div>
589
589
  <TextField
@@ -2,6 +2,7 @@
2
2
  flex: 1;
3
3
  max-width: 720px;
4
4
  form {
5
+ padding: 0;
5
6
  @media only screen and (min-width: 480px){
6
7
  padding: 16px;
7
8
  }
@@ -151,4 +152,13 @@
151
152
  }
152
153
  }
153
154
  }
155
+ }
156
+
157
+ .field-bg {
158
+ background: #fcfcfc;
159
+ background: linear-gradient(180deg, rgba(252, 252, 252, 1) 0%, rgba(242, 242, 242, 1) 55%, rgba(242, 242, 242, 1) 100%);
160
+ }
161
+
162
+ .mg-item{
163
+ margin: 10px 0;
154
164
  }
@@ -61,7 +61,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
61
61
  </div>
62
62
  <div className="flex flex-row flex-stretch">
63
63
 
64
- <div className="flex fieldName">{hint('modelcreator.name.hint')}
64
+ <div className="flex fieldName field-bg">{hint('modelcreator.name.hint')}
65
65
  <div className="flex flex-no-gap flex-no-wrap flex-1">
66
66
  <TextField
67
67
  label={t('modelcreator.fieldName')}
@@ -88,7 +88,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
88
88
 
89
89
  <div className="flex">
90
90
  {hint('modelcreator.type.hint')}
91
- <div className="flex flex-1 flex-stretch flex-no-gap">
91
+ <div className="flex flex-1 flex-stretch field-bg flex-no-gap">
92
92
 
93
93
  <SelectField
94
94
  label={t('modelcreator.type', 'Type de champ')}
@@ -453,16 +453,15 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
453
453
  </div>
454
454
 
455
455
  {!['file', 'relation', 'array', 'calculated'].includes(field.type) && (<div
456
- className="flex flex-no-wrap">
457
- {hint('modelcreator.default.hint')}
456
+ className="flex flex-no-wrap field-bg mg-item">
458
457
 
459
458
  {['string_t', 'string', 'richtext', 'password', 'url', 'phone', 'email'].includes(field.type) && (<>
460
- <label className="flex flex-1">
461
- <Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>
462
- <input
463
- type="text"
459
+ {hint('modelcreator.default.hint')}
460
+ <div className="flex flex-1">
461
+ <TextField
464
462
  className="flex-1"
465
463
  value={field.default}
464
+ label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
466
465
  disabled={modelLocked || (isLocalUser(me) && field.locked)}
467
466
  onChange={(e) => {
468
467
  const newFields = [...fields];
@@ -470,9 +469,10 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
470
469
  setFields(newFields);
471
470
  }}
472
471
  />
473
- </label>
472
+ </div>
474
473
  </>)}
475
474
  {['number'].includes(field.type) && (<>
475
+ {hint('modelcreator.default.hint')}
476
476
  <NumberField
477
477
  label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
478
478
  type="number"
@@ -491,6 +491,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
491
491
  />
492
492
  </>)}
493
493
  {['enum'].includes(field.type) && (<>
494
+ {hint('modelcreator.default.hint')}
494
495
  <SelectField
495
496
  label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
496
497
  value={field.default}
@@ -505,6 +506,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
505
506
  />
506
507
  </>)}
507
508
  {['code'].includes(field.type) && (<>
509
+ {hint('modelcreator.default.hint')}
508
510
  <CodeField
509
511
  label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
510
512
  value={field.language === 'json' ? JSON.stringify(field.default, 2, null) : field.default}
@@ -516,6 +518,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
516
518
  />
517
519
  </>)}
518
520
  {['boolean'].includes(field.type) && (<>
521
+ {hint('modelcreator.default.hint')}
519
522
  <div className="checkbox-label flex flex-1">
520
523
  <CheckboxField
521
524
  label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
@@ -530,6 +533,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
530
533
  </div>
531
534
  </>)}
532
535
  {['color'].includes(field.type) && (<>
536
+ {hint('modelcreator.default.hint')}
533
537
  <ColorField
534
538
  label={<Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>}
535
539
  value={field.default || null} name={field.name}
@@ -543,6 +547,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
543
547
  />
544
548
  </>)}
545
549
  {['date', 'datetime'].includes(field.type) && (<>
550
+ {hint('modelcreator.default.hint')}
546
551
  <label className="flex flex-1">
547
552
  <Trans i18nKey={"modelcreator.default"}>Valeur par défaut :</Trans>
548
553
  <input
@@ -562,7 +567,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
562
567
 
563
568
 
564
569
  {(['number', 'datetime', 'date'].includes(field.itemsType || field.type)) && (
565
- <><label className="flex">
570
+ <><label className="flex field-bg mg-item">
566
571
  {hint('modelcreator.min.hint')}
567
572
  <span><Trans
568
573
  i18nKey={"modelcreator.min"}>Valeur minimale :</Trans></span>
@@ -581,7 +586,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
581
586
  }}
582
587
  /></div>
583
588
  </label>
584
- <label className="flex">
589
+ <label className="flex field-bg mg-item">
585
590
  {hint('modelcreator.max.hint')}
586
591
  <span><Trans i18nKey={"modelcreator.max"}>Valeur maximale :</Trans></span>
587
592
  <div className="flex flex-1"><input
@@ -601,13 +606,12 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
601
606
  </label>
602
607
  </>
603
608
  )}
604
- <div className="flex flex-no-wrap">
609
+ <div className="flex flex-no-wrap mg-item">
605
610
  {hint('modelcreator.condition.hint')}
606
611
 
607
- <label className="checkbox-label flex flex-1"><Trans
608
- i18nKey={"modelcreator.condition"}>Condition</Trans> :
609
- <input
610
- type="checkbox"
612
+ <CheckboxField
613
+ label={<Trans
614
+ i18nKey={"modelcreator.condition"}>Condition</Trans>}
611
615
  disabled={modelLocked || (isLocalUser(me) && field.locked)}
612
616
  checked={field.condition !== undefined}
613
617
  onChange={(e) => {
@@ -620,7 +624,6 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
620
624
  setFields(newFields);
621
625
  }}
622
626
  />
623
- </label>
624
627
  </div>
625
628
  {field.condition !== undefined && (
626
629
  <div className={"condition-details flex flex-start"}>
@@ -691,10 +694,10 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
691
694
 
692
695
 
693
696
  {mainFieldsTypes.includes(field.itemsType || field.type) && (<div
694
- className="flex flex-no-wrap"
697
+ className="flex flex-no-wrap mg-item"
695
698
  title={t("modelcreator.field.asMain", "Une information principale sera affichée dans le titre de l'enregistrement")}>
696
699
  {hint('modelcreator.asMain.hint')}
697
- <label className="checkbox-label flex flex-1">
700
+ <div className="flex flex-1">
698
701
 
699
702
  <CheckboxField
700
703
  label={<Trans i18nKey={"modelcreator.asMain"}>Information principale :</Trans>}
@@ -707,7 +710,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
707
710
  }}
708
711
  help={field.asMain && t('modelcreator.asMain.hint')}
709
712
  />
710
- </label>
713
+ </div>
711
714
  </div>)}
712
715
 
713
716
  <div className="flex flex-row flex-stretch">
@@ -729,7 +732,7 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
729
732
  </div>
730
733
 
731
734
 
732
- <label className="flex">
735
+ <label className="flex mg-item ">
733
736
  {hint('modelcreator.color.hint')}
734
737
  <Trans i18nKey={"field.color"}>Color :</Trans>
735
738
  <ColorField
@@ -763,19 +766,16 @@ const ModelCreatorField = ({model, handleRenameField, handleRemoveField, handleU
763
766
 
764
767
  <div className={"flex flex-no-wrap"}>
765
768
  {hint('modelcreator.anonymized.hint')}
766
- <label className={"checkbox-label flex flex-1"}>
767
- <Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>
768
- <input
769
- type="checkbox"
770
- disabled={modelLocked || (isLocalUser(me) && field.locked)}
771
- checked={field.anonymized}
772
- onChange={(e) => {
773
- const newFields = [...fields];
774
- newFields[index].anonymized = e.target.checked;
775
- setFields(newFields);
776
- }}
777
- />
778
- </label>
769
+ <CheckboxField
770
+ label={<Trans i18nKey={"modelcreator.anonymized"}>Donnée anonymisée :</Trans>}
771
+ disabled={modelLocked || (isLocalUser(me) && field.locked)}
772
+ checked={field.anonymized}
773
+ onChange={(e) => {
774
+ const newFields = [...fields];
775
+ newFields[index].anonymized = e.target.checked;
776
+ setFields(newFields);
777
+ }}
778
+ />
779
779
  </div>
780
780
 
781
781
 
@@ -28,6 +28,7 @@ import css from 'highlight.js/lib/languages/css'
28
28
  import js from 'highlight.js/lib/languages/javascript'
29
29
  import ts from 'highlight.js/lib/languages/typescript'
30
30
  import html from 'highlight.js/lib/languages/xml'
31
+ import {escapeHtml, escapeRegex} from "data-primals-engine/core";
31
32
  // create a lowlight instance
32
33
  const lowlight = createLowlight(all)
33
34
 
@@ -43,7 +44,7 @@ const code = (c) => {
43
44
  const doc = document.createElement('div');
44
45
  doc.innerHTML = c;
45
46
  doc.querySelectorAll('code').forEach(cc =>{
46
- cc.innerHTML = cc.textContent
47
+ cc.innerHTML = escapeHtml(cc.textContent)
47
48
  .replace(/\r\n|\n/g, "<br />")
48
49
  .replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;");
49
50
  })