@codenotch/codenotch.react 1.0.82 → 2.0.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/dist/components/CodeEditor.d.ts +1 -1
- package/dist/components/CodeEditor.d.ts.map +1 -1
- package/dist/components/CodeEditor.js +66 -64
- package/dist/index.d.ts +67 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +134 -113
- package/dist/models/Codenotch.d.ts +28 -131
- package/dist/models/Codenotch.d.ts.map +1 -1
- package/package.json +45 -44
- package/src/components/CodeEditor.tsx +204 -203
- package/src/index.ts +399 -385
- package/src/models/Codenotch.ts +29 -148
- package/README.md +0 -145
- package/dist/components/Auml.d.ts +0 -46
- package/dist/components/Auml.d.ts.map +0 -1
- package/dist/components/Auml.js +0 -208
- package/dist/models/AppManifestModels.d.ts +0 -46
- package/dist/models/AppManifestModels.d.ts.map +0 -1
- package/dist/models/AppManifestModels.js +0 -2
- package/dist/models/ProjectManifestModels.d.ts +0 -126
- package/dist/models/ProjectManifestModels.d.ts.map +0 -1
- package/dist/models/ProjectManifestModels.js +0 -158
- package/dist/utils/I18nUtils.d.ts +0 -7
- package/dist/utils/I18nUtils.d.ts.map +0 -1
- package/dist/utils/I18nUtils.js +0 -37
- package/src/core/ProcessUtils.ts +0 -86
- package/src/core/SignalR.ts +0 -375
package/src/models/Codenotch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { IAppManifest, ICodenotchProjectManifest } from "@codenotch/codenotch.core";
|
|
2
3
|
import { ICodenotchSignal, IDisposable } from "./Misc";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -20,9 +21,7 @@ export interface ICodenotchEnv {
|
|
|
20
21
|
accessToken?: string;
|
|
21
22
|
/** Base URL of the current application. */
|
|
22
23
|
baseUrl?: string;
|
|
23
|
-
/**
|
|
24
|
-
i18n?: { [language: string]: { [key: string]: string } };
|
|
25
|
-
/** Current language code (e.g. `"en"`). Defaults to the first language found in {@link i18n}. */
|
|
24
|
+
/** Current language code (e.g. `"en"`). */
|
|
26
25
|
language?: string;
|
|
27
26
|
/** Current UI theme. Resolved from the `_theme` URL parameter, or `prefers-color-scheme` as a fallback. */
|
|
28
27
|
theme?: 'light' | 'dark';
|
|
@@ -30,94 +29,12 @@ export interface ICodenotchEnv {
|
|
|
30
29
|
/** Manifest of the current application (the `<AppName>.manifest.json` file next to the app). */
|
|
31
30
|
appManifest?: IAppManifest;
|
|
32
31
|
/** Manifest of the Codenotch project (the project's `manifest.json` file). */
|
|
33
|
-
projectManifest?:
|
|
32
|
+
projectManifest?: ICodenotchProjectManifest;
|
|
34
33
|
|
|
35
34
|
/** Any additional environment variable passed to `init()`. */
|
|
36
35
|
[key: string]: any; // Allow additional properties
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Registry of the project's BPMN processes, used to type {@link ICodenotchApi.startProcess}.
|
|
42
|
-
*
|
|
43
|
-
* Empty by default: the Codenotch IDE fills it through declaration merging in the
|
|
44
|
-
* auto-generated `typings/process.d.ts` file of each project, with one entry per
|
|
45
|
-
* BPMN process. Each entry maps the process name to its start events (`nodes`,
|
|
46
|
-
* keyed by start node id, valued with the start event's input parameters) and to
|
|
47
|
-
* the `output` of its end event.
|
|
48
|
-
*
|
|
49
|
-
* When the registry has not been augmented (project built without the IDE
|
|
50
|
-
* typings), `startProcess` falls back to plain string names and untyped inputs,
|
|
51
|
-
* so the code still compiles.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* // typings/process.d.ts (auto-generated by the Codenotch IDE)
|
|
55
|
-
* import 'codenotch-react';
|
|
56
|
-
* declare module 'codenotch-react' {
|
|
57
|
-
* interface ProcessRegistry {
|
|
58
|
-
* 'getTodos': {
|
|
59
|
-
* nodes: { 'start': { UserId: string } };
|
|
60
|
-
* output: { todos: any[] };
|
|
61
|
-
* };
|
|
62
|
-
* }
|
|
63
|
-
* }
|
|
64
|
-
*/
|
|
65
|
-
export interface ProcessRegistry {
|
|
66
|
-
// Extended by codenotch IDE
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Registry of the project's translation keys, used to type {@link ICodenotchApi.i18n}.
|
|
71
|
-
*
|
|
72
|
-
* Empty by default: the Codenotch IDE fills it through declaration merging in the
|
|
73
|
-
* auto-generated `typings/i18n.d.ts` file of each project, from the project's
|
|
74
|
-
* `.i18n.csv` files. Each entry maps a translation key to the tuple of arguments
|
|
75
|
-
* expected by its `{0}`, `{1}`, … placeholders.
|
|
76
|
-
*
|
|
77
|
-
* When the registry has not been augmented (project built without the IDE
|
|
78
|
-
* typings), `i18n` falls back to plain string keys and untyped arguments,
|
|
79
|
-
* so the code still compiles.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* // typings/i18n.d.ts (auto-generated by the Codenotch IDE)
|
|
83
|
-
* import 'codenotch-react';
|
|
84
|
-
* declare module 'codenotch-react' {
|
|
85
|
-
* interface TranslationRegistry {
|
|
86
|
-
* 'welcome': [];
|
|
87
|
-
* 'greeting': [arg1: any, arg2: any];
|
|
88
|
-
* }
|
|
89
|
-
* }
|
|
90
|
-
*/
|
|
91
|
-
export interface TranslationRegistry {
|
|
92
|
-
// Extended by codenotch IDE
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// When the codenotch IDE has not augmented the registries, `keyof Registry` is `never`
|
|
96
|
-
// and every call would fail to compile. These helpers fall back to plain string keys
|
|
97
|
-
// and untyped arguments so the API stays usable without code generation.
|
|
98
|
-
|
|
99
|
-
/** A BPMN process name: a key of {@link ProcessRegistry}, or any string when the registry is empty. */
|
|
100
|
-
export type ProcessName = keyof ProcessRegistry extends never ? string : keyof ProcessRegistry;
|
|
101
|
-
|
|
102
|
-
/** The start nodes of process `P`: its `nodes` map from {@link ProcessRegistry}, or an open map when unknown. */
|
|
103
|
-
export type ProcessNodes<P> = P extends keyof ProcessRegistry
|
|
104
|
-
? ProcessRegistry[P] extends { nodes: infer N } ? N : { [nodeId: string]: any }
|
|
105
|
-
: { [nodeId: string]: any };
|
|
106
|
-
|
|
107
|
-
/** The end-event output of process `P` from {@link ProcessRegistry}, or `any` when unknown. */
|
|
108
|
-
export type ProcessOutput<P> = P extends keyof ProcessRegistry
|
|
109
|
-
? ProcessRegistry[P] extends { output: infer O } ? O : any
|
|
110
|
-
: any;
|
|
111
|
-
|
|
112
|
-
/** A translation key: a key of {@link TranslationRegistry}, or any string when the registry is empty. */
|
|
113
|
-
export type TranslationKey = keyof TranslationRegistry extends never ? string : keyof TranslationRegistry;
|
|
114
|
-
|
|
115
|
-
/** The placeholder arguments of translation key `K` from {@link TranslationRegistry}, or `any[]` when unknown. */
|
|
116
|
-
export type TranslationArgs<K> = K extends keyof TranslationRegistry
|
|
117
|
-
? TranslationRegistry[K] extends any[] ? TranslationRegistry[K] : any[]
|
|
118
|
-
: any[];
|
|
119
|
-
|
|
120
|
-
|
|
121
38
|
/**
|
|
122
39
|
* Result of a BPMN process execution started with {@link ICodenotchApi.startProcess}.
|
|
123
40
|
*/
|
|
@@ -134,59 +51,24 @@ export interface IProcessResult<T = any> {
|
|
|
134
51
|
}
|
|
135
52
|
|
|
136
53
|
/**
|
|
137
|
-
* The Codenotch client API
|
|
54
|
+
* The Codenotch client API.
|
|
55
|
+
*
|
|
56
|
+
* Obtained with the `useCodenotch()` hook in function components, through the
|
|
57
|
+
* `cn` prop injected by `withCodenotch()` in class components, or with the
|
|
58
|
+
* plain `getCodenotch()` function anywhere else (handlers, modules, services).
|
|
138
59
|
*
|
|
139
60
|
* It is the bridge between a React app and the Codenotch runtime: it starts
|
|
140
|
-
* server-side BPMN processes, runs CNQL queries,
|
|
61
|
+
* server-side BPMN processes, runs CNQL queries,
|
|
141
62
|
* listens to real-time signals and manages theme/language.
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* import { useCodenotch } from 'codenotch-react';
|
|
145
|
-
*
|
|
146
|
-
* const cn = useCodenotch();
|
|
147
|
-
* const title = cn.i18n('welcome');
|
|
148
|
-
* const result = await cn.startProcess('getTodos', 'start', { UserId: '42' });
|
|
149
63
|
*/
|
|
150
64
|
export interface ICodenotchApi {
|
|
151
65
|
|
|
152
66
|
/**
|
|
153
67
|
* Start a server-side BPMN process and wait for its completion.
|
|
154
|
-
*
|
|
155
|
-
* @param processName Name of the process — the `.bpmn` file name without extension (e.g. `processes/getTodos.bpmn` → `'getTodos'`).
|
|
156
|
-
* @param startNodeId Id of the start event to trigger (conventionally `'start'`).
|
|
157
|
-
* @param inputs Input parameters declared by that start event.
|
|
158
|
-
* @returns The process result; check `isError` before using `output` (the parameters of the end event reached).
|
|
159
|
-
* @example
|
|
160
|
-
* const result = await cn.startProcess('getTodos', 'start', { UserId: userId });
|
|
161
|
-
* if (!result.isError) {
|
|
162
|
-
* setTodos(result.output.todos);
|
|
163
|
-
* }
|
|
164
|
-
*/
|
|
165
|
-
startProcess<P extends ProcessName, N extends keyof ProcessNodes<P>>(
|
|
166
|
-
processName: P,
|
|
167
|
-
startNodeId: N,
|
|
168
|
-
inputs: ProcessNodes<P>[N]
|
|
169
|
-
): Promise<IProcessResult<ProcessOutput<P>>>;
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Translate a key for the current language ({@link ICodenotchEnv.language}).
|
|
173
|
-
*
|
|
174
|
-
* Placeholders `{0}`, `{1}`, … in the translated text are replaced by `args`.
|
|
175
|
-
* Never throws: if the key or the language is missing, a warning is logged
|
|
176
|
-
* and the key itself is returned.
|
|
177
|
-
*
|
|
178
|
-
* @param key Translation key, as defined in the project's `.i18n.csv` files.
|
|
179
|
-
* @param args Values for the `{0}`, `{1}`, … placeholders of the translation.
|
|
180
|
-
* @example
|
|
181
|
-
* cn.i18n('welcome'); // "Bienvenue"
|
|
182
|
-
* cn.i18n('greeting', 'Fabian', 3); // "Bonjour Fabian, 3 messages" (from "Bonjour {0}, {1} messages")
|
|
183
68
|
*/
|
|
184
|
-
|
|
185
|
-
key: K,
|
|
186
|
-
...args: TranslationArgs<K>
|
|
187
|
-
): string;
|
|
69
|
+
startProcess<TProcess, TInput, TOutput>(process: TProcess, inputs: TInput): Promise<IProcessResult<TOutput>>;
|
|
188
70
|
|
|
189
|
-
/** Runtime environment (cluster, service, language, theme
|
|
71
|
+
/** Runtime environment (cluster, service, language, theme dictionaries, manifests…). Populated by `init()`. */
|
|
190
72
|
readonly env: ICodenotchEnv;
|
|
191
73
|
|
|
192
74
|
/**
|
|
@@ -223,7 +105,7 @@ export interface ICodenotchApi {
|
|
|
223
105
|
* </div>
|
|
224
106
|
* );
|
|
225
107
|
*/
|
|
226
|
-
readonly showDialog: (node:
|
|
108
|
+
readonly showDialog: (node: ReactNode) => ICodenotchDialog;
|
|
227
109
|
|
|
228
110
|
/**
|
|
229
111
|
* Fetch the text content of a file of the deployed Codenotch project.
|
|
@@ -238,26 +120,11 @@ export interface ICodenotchApi {
|
|
|
238
120
|
*/
|
|
239
121
|
readonly getProjectFileUrl: (relativePath: string) => string;
|
|
240
122
|
|
|
241
|
-
/**
|
|
242
|
-
* Subscribe to a real-time signal emitted by the project's BPMN processes (via SignalR).
|
|
243
|
-
*
|
|
244
|
-
* @param signalId Id of the signal, as declared in the BPMN process.
|
|
245
|
-
* @param callback Called each time the signal is received, with `{ signalId, data }`.
|
|
246
|
-
* @returns A disposable — call `dispose()` to unsubscribe (e.g. in a `useEffect` cleanup).
|
|
247
|
-
* @example
|
|
248
|
-
* useEffect(() => {
|
|
249
|
-
* let sub: IDisposable | undefined;
|
|
250
|
-
* cn.listenSignal('todosChanged', () => refresh()).then(s => sub = s);
|
|
251
|
-
* return () => sub?.dispose();
|
|
252
|
-
* }, []);
|
|
253
|
-
*/
|
|
254
|
-
readonly listenSignal: (signalId: string, callback: (data: ICodenotchSignal) => void) => Promise<IDisposable>;
|
|
255
|
-
|
|
256
123
|
/** Set the UI theme. Also toggles the `light`/`dark` class on `<html>` (Tailwind `dark:` variants). */
|
|
257
124
|
readonly setTheme: (theme: 'light' | 'dark') => void;
|
|
258
125
|
/** Current UI theme, if resolved. */
|
|
259
126
|
readonly getTheme: () => 'light' | 'dark' | undefined;
|
|
260
|
-
/** Set the current language
|
|
127
|
+
/** Set the current language. Must be one of the project's languages. */
|
|
261
128
|
readonly setLanguage: (lang: string) => void;
|
|
262
129
|
/** Current language code, if any. */
|
|
263
130
|
readonly getLanguage: () => string | undefined;
|
|
@@ -268,11 +135,25 @@ export interface ICodenotchApi {
|
|
|
268
135
|
/** Generate a random UUID v4. */
|
|
269
136
|
readonly uuid: () => string;
|
|
270
137
|
/** Manifest of the Codenotch project. Throws if not available in the environment. */
|
|
271
|
-
readonly getProjectManifest: () =>
|
|
138
|
+
readonly getProjectManifest: () => ICodenotchProjectManifest;
|
|
272
139
|
/** Manifest of the current application, if any. */
|
|
273
140
|
readonly getAppManifest: () => IAppManifest | undefined;
|
|
274
141
|
}
|
|
275
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Props injected by the `withCodenotch()` higher-order component.
|
|
145
|
+
* Extend it in the props of a class component wrapped by `withCodenotch`.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* interface Props extends WithCodenotchProps { userId: string }
|
|
149
|
+
* class TodoList extends React.Component<Props> { ... }
|
|
150
|
+
* export default withCodenotch(TodoList);
|
|
151
|
+
*/
|
|
152
|
+
export interface WithCodenotchProps {
|
|
153
|
+
/** The Codenotch client API; refreshed (new reference) whenever the environment changes. */
|
|
154
|
+
cn: ICodenotchApi;
|
|
155
|
+
}
|
|
156
|
+
|
|
276
157
|
/**
|
|
277
158
|
* Handle on a dialog opened with {@link ICodenotchApi.showDialog}.
|
|
278
159
|
*/
|
package/README.md
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# codenotch-react
|
|
2
|
-
|
|
3
|
-
React bindings for [Codenotch](https://codenotch.com) applications.
|
|
4
|
-
|
|
5
|
-
Codenotch is a full-stack development platform: a project combines server-side **BPMN processes**, **SQL tables** / **NoSQL documents**, **i18n** translations and **React** apps. This package is the client-side bridge between those React apps and the Codenotch runtime: it lets a component start BPMN processes, run CNQL queries, translate i18n keys, listen to real-time signals, and manage theme/language.
|
|
6
|
-
|
|
7
|
-
> **Requirements** — Codenotch apps run on **React 16** (`react@^16.14.0`). Do not use React 17/18 APIs (`createRoot`, `useId`, automatic JSX runtime…); hooks work fine.
|
|
8
|
-
|
|
9
|
-
## Quick start
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
import React from 'react';
|
|
13
|
-
import { useCodenotch } from 'codenotch-react';
|
|
14
|
-
|
|
15
|
-
const MyApp: React.FC = () => {
|
|
16
|
-
const cn = useCodenotch();
|
|
17
|
-
|
|
18
|
-
return <div className="p-4">
|
|
19
|
-
<h1 className="text-xl font-bold">{cn.i18n('welcome')}</h1>
|
|
20
|
-
</div>;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default MyApp;
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
`useCodenotch()` is **not a React hook** despite its name: it is a plain function that returns the API object bound to the current environment. It can be called anywhere — components, event handlers, plain modules.
|
|
27
|
-
|
|
28
|
-
The environment (cluster URL, service name, language, translations…) is set up by `init(envVariables)`, which the Codenotch runtime calls automatically from the page hosting the application. You only call `init` yourself in custom hosting scenarios.
|
|
29
|
-
|
|
30
|
-
## API overview
|
|
31
|
-
|
|
32
|
-
| Member | Description |
|
|
33
|
-
|--------|-------------|
|
|
34
|
-
| `cn.env` | Readonly environment: `clusterUrl`, `serviceName`, `tenantName`, `accessToken`, `language`, `theme`, `i18n`, `appManifest`, `projectManifest`. |
|
|
35
|
-
| `cn.i18n(key, ...args)` | Translate a key for the current language, filling `{0}`, `{1}`, … placeholders. |
|
|
36
|
-
| `cn.startProcess(name, startNodeId, inputs)` | Start a server-side BPMN process and await its result. |
|
|
37
|
-
| `cn.requestCnql(cnql, verbose?)` | Run a CNQL query (XML, SELECT-only) against the project's tables. |
|
|
38
|
-
| `cn.listenSignal(signalId, callback)` | Subscribe to real-time signals emitted by BPMN processes. |
|
|
39
|
-
| `cn.showDialog(jsx)` | Render a JSX element in a fullscreen modal dialog. |
|
|
40
|
-
| `cn.setTheme(t)` / `cn.getTheme()` | `'light' \| 'dark'`; `setTheme` also toggles the class on `<html>` (Tailwind `dark:`). |
|
|
41
|
-
| `cn.setLanguage(l)` / `cn.getLanguage()` / `cn.getLanguages()` | Current language and the languages declared in `manifest.json`. |
|
|
42
|
-
| `cn.getProjectFile(path)` / `cn.getProjectFileUrl(path)` | Read/link a file of the deployed project. |
|
|
43
|
-
| `cn.getUrlParams()` | Query-string parameters of the current URL as a plain object. |
|
|
44
|
-
| `cn.uuid()` | Random UUID v4. |
|
|
45
|
-
| `cn.getProjectManifest()` / `cn.getAppManifest()` | Project / application manifests. |
|
|
46
|
-
|
|
47
|
-
Full signatures and JSDoc live in `dist/index.d.ts` (sources in `src/`).
|
|
48
|
-
|
|
49
|
-
## Calling a BPMN process
|
|
50
|
-
|
|
51
|
-
`processName` is the `.bpmn` file name without extension; `startNodeId` is the id of the start event to trigger (conventionally `'start'`); `inputs` are the input parameters declared by that start event. The result's `output` contains the parameters of the end event reached.
|
|
52
|
-
|
|
53
|
-
```tsx
|
|
54
|
-
const result = await cn.startProcess('getTodos', 'start', { UserId: userId });
|
|
55
|
-
|
|
56
|
-
if (!result.isError) {
|
|
57
|
-
setTodos(result.output.todos);
|
|
58
|
-
} else {
|
|
59
|
-
console.error(result.errorMessage);
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Querying tables with CNQL
|
|
64
|
-
|
|
65
|
-
CNQL is Codenotch's XML query language over the project's SQL tables (SELECT only — writes go through BPMN processes). The root `xmlns` must be the project's `serviceName`; each queried table's `Ref` attribute names its result set:
|
|
66
|
-
|
|
67
|
-
```tsx
|
|
68
|
-
const data = await cn.requestCnql(`
|
|
69
|
-
<CNQL xmlns="myproject" PageSize="10" PageIndex="0">
|
|
70
|
-
<Users Ref="results">
|
|
71
|
-
<Id />
|
|
72
|
-
<Email />
|
|
73
|
-
<IsAdmin Equal="true" />
|
|
74
|
-
</Users>
|
|
75
|
-
</CNQL>`);
|
|
76
|
-
|
|
77
|
-
console.log(data.results); // [{ Id: '...', Email: '...' }, ...]
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Translations (i18n)
|
|
81
|
-
|
|
82
|
-
Translations come from the project's `.i18n.csv` files and are injected into `cn.env.i18n` at startup. `cn.i18n(key, ...args)` translates for the current language and replaces `{0}`, `{1}`, … placeholders; it never throws — an unknown key is returned as-is (with a console warning).
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
cn.i18n('welcome'); // "Bienvenue"
|
|
86
|
-
cn.i18n('greeting', 'Ada', 3); // "Bonjour Ada, 3 messages" (from "Bonjour {0}, {1} messages")
|
|
87
|
-
cn.setLanguage('en'); // switch language at runtime
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Real-time signals
|
|
91
|
-
|
|
92
|
-
BPMN processes can broadcast signals; subscribe from the UI with `listenSignal`:
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
let sub: IDisposable | undefined;
|
|
97
|
-
cn.listenSignal('todosChanged', (signal) => refresh(signal.data))
|
|
98
|
-
.then(s => { sub = s; });
|
|
99
|
-
return () => sub?.dispose();
|
|
100
|
-
}, []);
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Dialogs
|
|
104
|
-
|
|
105
|
-
```tsx
|
|
106
|
-
const dialog = cn.showDialog(
|
|
107
|
-
<div className="bg-white dark:bg-gray-800 p-6 rounded shadow">
|
|
108
|
-
<p>{cn.i18n('confirm.message')}</p>
|
|
109
|
-
<button onClick={() => dialog.close()}>{cn.i18n('close')}</button>
|
|
110
|
-
</div>
|
|
111
|
-
);
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
The dialog stays open until you call `dialog.close()`.
|
|
115
|
-
|
|
116
|
-
## Type-safe processes and translation keys
|
|
117
|
-
|
|
118
|
-
`startProcess` and `i18n` are typed through two registries, `ProcessRegistry` and `TranslationRegistry`, that the **Codenotch IDE fills by declaration merging**: it generates `typings/process.d.ts` and `typings/i18n.d.ts` in each project from the `.bpmn` and `.i18n.csv` files (never edit those files — refresh the project instead). The generated files look like:
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
import 'codenotch-react';
|
|
122
|
-
|
|
123
|
-
declare module 'codenotch-react' {
|
|
124
|
-
interface TranslationRegistry {
|
|
125
|
-
'welcome': [];
|
|
126
|
-
'greeting': [arg1: any, arg2: any];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
interface ProcessRegistry {
|
|
130
|
-
'getTodos': {
|
|
131
|
-
nodes: { 'start': { UserId: string } };
|
|
132
|
-
output: { todos: any[] };
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
With these in the compilation, keys, inputs and outputs are strictly checked and autocompleted. **Without them** (project compiled outside the IDE), the registries are empty and both methods gracefully fall back to plain `string` keys and untyped arguments — the code still compiles.
|
|
139
|
-
|
|
140
|
-
## Notes for AI assistants
|
|
141
|
-
|
|
142
|
-
- The complete typed API surface is in `dist/index.d.ts`; the readable implementation ships in `src/` (entry point `src/index.ts`).
|
|
143
|
-
- `useCodenotch()` is a plain function, not a hook — no hook rules apply.
|
|
144
|
-
- Never hand-edit a project's `typings/i18n.d.ts` / `typings/process.d.ts`: they are regenerated by the Codenotch IDE.
|
|
145
|
-
- Codenotch apps are React 16 + Tailwind CSS; CNQL is SELECT-only (writes go through BPMN processes via `startProcess`).
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { SpaRenderStatus } from '@echino/echino.ui.framework/components/SpaBuilder/common/ISpaRenderProps';
|
|
3
|
-
import type { ICodenotchEnv } from "../models/Codenotch";
|
|
4
|
-
interface IAumlProps {
|
|
5
|
-
/** The AUML (XML) application description to render. */
|
|
6
|
-
value: string;
|
|
7
|
-
/** When `true`, logs compilation and token-refresh details to the console. */
|
|
8
|
-
verbose?: boolean;
|
|
9
|
-
/** Codenotch environment; provides the i18n dictionaries injected into the AUML and the theme. */
|
|
10
|
-
env?: ICodenotchEnv;
|
|
11
|
-
}
|
|
12
|
-
interface IAumlState {
|
|
13
|
-
loaded: boolean;
|
|
14
|
-
inspectorEnabled: boolean;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Renders a legacy AUML (XML) application description inside a React app.
|
|
18
|
-
*
|
|
19
|
-
* Compiles the AUML with the environment's i18n variables, renders it through
|
|
20
|
-
* the Echino SPA renderer, shows a loading overlay until completed, and keeps
|
|
21
|
-
* the user's access token refreshed in the background.
|
|
22
|
-
*
|
|
23
|
-
* Relies on globals injected by the Codenotch runtime hosting page
|
|
24
|
-
* (`serviceName`, `tenant`, `user`, `languages`, `appManifest`…) — it is not
|
|
25
|
-
* usable outside a Codenotch-served application.
|
|
26
|
-
*/
|
|
27
|
-
export declare class Auml extends React.Component<IAumlProps, IAumlState> {
|
|
28
|
-
_refreshTokenTimer: NodeJS.Timeout | undefined;
|
|
29
|
-
constructor(props: IAumlProps);
|
|
30
|
-
componentDidMount(): void;
|
|
31
|
-
registerRefreshToken(): void;
|
|
32
|
-
componentWillUnmount(): void;
|
|
33
|
-
progress(s: SpaRenderStatus): void;
|
|
34
|
-
retrieveInputs(): {
|
|
35
|
-
[k: string]: string;
|
|
36
|
-
};
|
|
37
|
-
getTimeToTokenExpiration(): number | null;
|
|
38
|
-
getTimeToNewTokenExpiration(expirationDateTime: string): number;
|
|
39
|
-
msToTime(ms: number): string;
|
|
40
|
-
parseJwt(token: string): any;
|
|
41
|
-
render(): React.JSX.Element;
|
|
42
|
-
renderLoadingContent(): React.JSX.Element;
|
|
43
|
-
refreshExpiredToken(): Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
export {};
|
|
46
|
-
//# sourceMappingURL=Auml.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Auml.d.ts","sourceRoot":"","sources":["../../src/components/Auml.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,0EAA0E,CAAC;AAE3G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,UAAU,UAAU;IAChB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kGAAkG;IAClG,GAAG,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,UAAU,UAAU;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,IAAK,SAAQ,KAAK,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC;IAE7D,kBAAkB,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC;gBAEnC,KAAK,EAAE,UAAU;IAa7B,iBAAiB;IAIjB,oBAAoB;IAoCpB,oBAAoB;IAOpB,QAAQ,CAAC,CAAC,EAAE,eAAe;IAS3B,cAAc;;;IAMd,wBAAwB,IAAI,MAAM,GAAG,IAAI;IAqCzC,2BAA2B,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM;IAO/D,QAAQ,CAAC,EAAE,EAAE,MAAM;IAmBnB,QAAQ,CAAC,KAAK,EAAE,MAAM;IAUtB,MAAM;IAoDN,oBAAoB;IAkBd,mBAAmB;CAoC5B"}
|
package/dist/components/Auml.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Auml = void 0;
|
|
7
|
-
const react_1 = __importDefault(require("react"));
|
|
8
|
-
const SpaRenderWithBrowserRouter_1 = require("@echino/echino.ui.framework/components/SpaBuilder/SpaRender/SpaRenderWithBrowserRouter");
|
|
9
|
-
const I18nUtils_1 = __importDefault(require("../utils/I18nUtils"));
|
|
10
|
-
/**
|
|
11
|
-
* Renders a legacy AUML (XML) application description inside a React app.
|
|
12
|
-
*
|
|
13
|
-
* Compiles the AUML with the environment's i18n variables, renders it through
|
|
14
|
-
* the Echino SPA renderer, shows a loading overlay until completed, and keeps
|
|
15
|
-
* the user's access token refreshed in the background.
|
|
16
|
-
*
|
|
17
|
-
* Relies on globals injected by the Codenotch runtime hosting page
|
|
18
|
-
* (`serviceName`, `tenant`, `user`, `languages`, `appManifest`…) — it is not
|
|
19
|
-
* usable outside a Codenotch-served application.
|
|
20
|
-
*/
|
|
21
|
-
class Auml extends react_1.default.Component {
|
|
22
|
-
constructor(props) {
|
|
23
|
-
super(props);
|
|
24
|
-
this.state = {
|
|
25
|
-
loaded: false,
|
|
26
|
-
inspectorEnabled: false
|
|
27
|
-
};
|
|
28
|
-
if (typeof user === 'undefined') {
|
|
29
|
-
let globalObject = typeof window !== 'undefined' ? window : globalThis;
|
|
30
|
-
globalObject['user'] = null; // Make sure user is defined
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
componentDidMount() {
|
|
34
|
-
this.registerRefreshToken();
|
|
35
|
-
}
|
|
36
|
-
registerRefreshToken() {
|
|
37
|
-
try {
|
|
38
|
-
// Setup a method to refresh our access token when it expires
|
|
39
|
-
if (this._refreshTokenTimer) {
|
|
40
|
-
clearTimeout(this._refreshTokenTimer);
|
|
41
|
-
}
|
|
42
|
-
if (user === null) {
|
|
43
|
-
if (this.props.verbose === true) {
|
|
44
|
-
console.log("No user found, will not refresh the token");
|
|
45
|
-
}
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
let timeToExpireMs = this.getTimeToTokenExpiration();
|
|
49
|
-
if (timeToExpireMs === null) {
|
|
50
|
-
if (this.props.verbose === true) {
|
|
51
|
-
console.log("No identity token found, attempting refreshing the token in 5 minutes");
|
|
52
|
-
}
|
|
53
|
-
this._refreshTokenTimer = setTimeout(() => this.refreshExpiredToken(), 5 * 60 * 1000);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (timeToExpireMs <= 0) {
|
|
57
|
-
// Refresh it immediatelty
|
|
58
|
-
this.refreshExpiredToken();
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
this._refreshTokenTimer = setTimeout(() => this.refreshExpiredToken(), timeToExpireMs);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
console.warn("Could not setup token refresh timer: " + err);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
componentWillUnmount() {
|
|
69
|
-
// Cleanup the refresh of the token
|
|
70
|
-
if (this._refreshTokenTimer) {
|
|
71
|
-
clearTimeout(this._refreshTokenTimer);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
progress(s) {
|
|
75
|
-
//console.log('progessStatus ', s);
|
|
76
|
-
if (s === 'completed') {
|
|
77
|
-
this.setState({ loaded: true });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Get input from the query parameters in the url
|
|
81
|
-
retrieveInputs() {
|
|
82
|
-
return Object.fromEntries(new URLSearchParams(window.location.search).entries());
|
|
83
|
-
}
|
|
84
|
-
getTimeToTokenExpiration() {
|
|
85
|
-
// The identity token is the only one we have access here on the client
|
|
86
|
-
// We use it to know when the access token (which we can't read) will expire
|
|
87
|
-
let identityToken = null;
|
|
88
|
-
let identityTokenKey = `${tenant.name}IdToken=`;
|
|
89
|
-
let cookies = document.cookie.split(';');
|
|
90
|
-
for (let c of cookies) {
|
|
91
|
-
if (c.trim().startsWith(identityTokenKey)) {
|
|
92
|
-
identityToken = c.trim().slice(identityTokenKey.length);
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (identityToken === null) {
|
|
97
|
-
return null; // token not found
|
|
98
|
-
}
|
|
99
|
-
let identityTokenParsed = this.parseJwt(identityToken);
|
|
100
|
-
let expires = identityTokenParsed.exp; // Timestamp in second since Unix epoch
|
|
101
|
-
let timeToExpiresMs = expires * 1000 - new Date().getTime();
|
|
102
|
-
let timeToExpireStr = this.msToTime(timeToExpiresMs);
|
|
103
|
-
if (this.props.verbose === true) {
|
|
104
|
-
console.log(`Token will expire at ${new Date(expires * 1000).toISOString()} (in ${timeToExpireStr}), setting up a timer to refresh it`);
|
|
105
|
-
}
|
|
106
|
-
// Refresh it a bit before the expiration (5 min)
|
|
107
|
-
timeToExpiresMs -= 5 * 60 * 1000;
|
|
108
|
-
return timeToExpiresMs;
|
|
109
|
-
}
|
|
110
|
-
getTimeToNewTokenExpiration(expirationDateTime) {
|
|
111
|
-
let timeToExpiresMs = new Date(expirationDateTime).getTime() - new Date().getTime();
|
|
112
|
-
// Refresh it a bit before the expiration (5 min)
|
|
113
|
-
timeToExpiresMs -= 5 * 60 * 1000;
|
|
114
|
-
return timeToExpiresMs;
|
|
115
|
-
}
|
|
116
|
-
msToTime(ms) {
|
|
117
|
-
let seconds = Math.floor((ms / 1000) % 60), minutes = Math.floor((ms / (1000 * 60)) % 60), hours = Math.floor((ms / (1000 * 60 * 60)) % 24);
|
|
118
|
-
let timeString = seconds + " seconds";
|
|
119
|
-
if (minutes > 0) {
|
|
120
|
-
timeString = minutes + " minutes " + timeString;
|
|
121
|
-
}
|
|
122
|
-
if (hours > 0) {
|
|
123
|
-
timeString = hours + " hours " + timeString;
|
|
124
|
-
}
|
|
125
|
-
return timeString;
|
|
126
|
-
}
|
|
127
|
-
parseJwt(token) {
|
|
128
|
-
var base64Url = token.split('.')[1];
|
|
129
|
-
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
130
|
-
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) {
|
|
131
|
-
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
132
|
-
}).join(''));
|
|
133
|
-
return JSON.parse(jsonPayload);
|
|
134
|
-
}
|
|
135
|
-
render() {
|
|
136
|
-
let inputs = this.retrieveInputs();
|
|
137
|
-
let theme = this.props.env?.theme === "dark";
|
|
138
|
-
let aumlManifest = null;
|
|
139
|
-
try {
|
|
140
|
-
let appManifestObj = JSON.parse(appManifest);
|
|
141
|
-
aumlManifest = appManifestObj.auml;
|
|
142
|
-
}
|
|
143
|
-
catch { }
|
|
144
|
-
let auml = this.props.value;
|
|
145
|
-
if (this.props.env) {
|
|
146
|
-
try {
|
|
147
|
-
auml = I18nUtils_1.default.compileAuml(auml, this.props.env);
|
|
148
|
-
if (this.props.verbose === true) {
|
|
149
|
-
console.log("Compiled AUML with i18n variables: ", auml);
|
|
150
|
-
console.log("i18n variables: ", this.props.env.i18n);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
console.error("Error compiling AUML with i18n variables: " + err);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return react_1.default.createElement("div", { className: "app" },
|
|
158
|
-
react_1.default.createElement(SpaRenderWithBrowserRouter_1.SpaRenderWithBrowserRouter, { appDescription: auml, tenant: tenant, serviceName: serviceName, packageVersions: packageVersions, user: user, languages: languages, onProgress: (s) => this.progress(s), input: inputs, theme: theme, manifest: aumlManifest, inspectorEnabled: this.state.inspectorEnabled, children: [] }),
|
|
159
|
-
!this.state.loaded &&
|
|
160
|
-
react_1.default.createElement("div", { className: `app-loading ${this.props.env?.theme ?? 'light'}` },
|
|
161
|
-
this.renderLoadingContent(),
|
|
162
|
-
react_1.default.createElement("i", { className: "fas fa-circle-notch fa-spin" })));
|
|
163
|
-
}
|
|
164
|
-
renderLoadingContent() {
|
|
165
|
-
try {
|
|
166
|
-
//@ts-ignore
|
|
167
|
-
let tenant = global.tenant;
|
|
168
|
-
if (tenant.logoUrl) {
|
|
169
|
-
return react_1.default.createElement("img", { src: tenant.logoUrl, alt: tenant.displayName });
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
return react_1.default.createElement("div", { className: 'app-loading-title' }, tenant.displayName);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
catch (e) {
|
|
176
|
-
console.warn("Could not load tenant information: " + e);
|
|
177
|
-
return react_1.default.createElement("div", { className: 'app-loading-title' }, "Loading...");
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
async refreshExpiredToken() {
|
|
181
|
-
console.log("Token will expire soon, requesting a new one...");
|
|
182
|
-
// Using the refresh token we ask for a new access token using /portal/login/refresh
|
|
183
|
-
// If the refresh token has also expired, we will be redirected to the login page
|
|
184
|
-
let redirectUrl = window.location.href; // Where to send us back in case we need to be redirected to the login page
|
|
185
|
-
let url = `${tenant.clusterUrl}/portal/login/refresh?redirectUrl=${encodeURIComponent(redirectUrl)}`;
|
|
186
|
-
let response = await fetch(url); // For this request to work, we need to have a refresh token in the cookies
|
|
187
|
-
if (response.ok) {
|
|
188
|
-
// Setup next refresh
|
|
189
|
-
let timeToExpireMs;
|
|
190
|
-
try {
|
|
191
|
-
let newTokenExpiration = await response.text();
|
|
192
|
-
timeToExpireMs = this.getTimeToNewTokenExpiration(newTokenExpiration);
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
timeToExpireMs = 2 * 60 * 60 * 1000; // refresh in 2 hours
|
|
196
|
-
}
|
|
197
|
-
console.log(`Refresh request ok, next refresh in ${this.msToTime(timeToExpireMs)}`);
|
|
198
|
-
this._refreshTokenTimer = setTimeout(() => this.refreshExpiredToken(), timeToExpireMs);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
let content = await response.text();
|
|
202
|
-
console.error("Could not refresh the token", content);
|
|
203
|
-
console.log("Retrying refreshing the token in 5 minutes...");
|
|
204
|
-
this._refreshTokenTimer = setTimeout(() => this.refreshExpiredToken(), 5 * 60 * 1000);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
exports.Auml = Auml;
|