@codenotch/codenotch.react 1.0.82 → 2.0.0
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 +50 -12
- 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 +85 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -22
- package/dist/models/Codenotch.d.ts +20 -2
- package/dist/models/Codenotch.d.ts.map +1 -1
- package/package.json +46 -44
- package/src/components/CodeEditor.tsx +204 -203
- package/src/index.ts +531 -385
- package/src/models/Codenotch.ts +21 -2
- 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/README.md
CHANGED
|
@@ -4,15 +4,14 @@ React bindings for [Codenotch](https://codenotch.com) applications.
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
-
> **Requirements** — Codenotch apps run on **React
|
|
7
|
+
> **Requirements** — Codenotch apps run on **React 19** (`react@^19`, `react-dom@^19`). The package uses the automatic JSX runtime and `createRoot`; all modern React APIs (hooks, `useId`, `use`, Actions…) are available.
|
|
8
8
|
|
|
9
9
|
## Quick start
|
|
10
10
|
|
|
11
11
|
```tsx
|
|
12
|
-
import React from 'react';
|
|
13
12
|
import { useCodenotch } from 'codenotch-react';
|
|
14
13
|
|
|
15
|
-
const MyApp
|
|
14
|
+
const MyApp = () => {
|
|
16
15
|
const cn = useCodenotch();
|
|
17
16
|
|
|
18
17
|
return <div className="p-4">
|
|
@@ -23,7 +22,41 @@ const MyApp: React.FC = () => {
|
|
|
23
22
|
export default MyApp;
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
`useCodenotch()` is
|
|
25
|
+
`useCodenotch()` is a real React hook: the returned object is stable across renders and is replaced — re-rendering the component — whenever the environment changes (`setLanguage`, `setTheme`, `init`). Regular hook rules apply. The same API is available everywhere else through two non-hook entry points:
|
|
26
|
+
|
|
27
|
+
| Where | Use |
|
|
28
|
+
|-------|-----|
|
|
29
|
+
| Function component / custom hook | `const cn = useCodenotch();` — re-renders on language/theme change. |
|
|
30
|
+
| Class component | `export default withCodenotch(MyClass);` — injects `this.props.cn`, re-renders on change, forwards `ref`. |
|
|
31
|
+
| Event handler, plain module, service, anywhere | `getCodenotch()` — plain function, never stale, no re-render. |
|
|
32
|
+
| Manual subscription (rare) | `onCodenotchChange(listener)` → `IDisposable`. |
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import React from 'react';
|
|
36
|
+
import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
|
|
37
|
+
|
|
38
|
+
interface Props extends WithCodenotchProps {
|
|
39
|
+
userId: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class TodoList extends React.Component<Props> {
|
|
43
|
+
render() {
|
|
44
|
+
return <h1>{this.props.cn.i18n('todos.title')}</h1>;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default withCodenotch(TodoList); // <TodoList userId="42" /> — `cn` is injected
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// services/todos.ts — outside React
|
|
53
|
+
import { getCodenotch } from 'codenotch-react';
|
|
54
|
+
|
|
55
|
+
export async function loadTodos(userId: string) {
|
|
56
|
+
const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
|
|
57
|
+
return result.output.todos;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
27
60
|
|
|
28
61
|
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
62
|
|
|
@@ -36,7 +69,7 @@ The environment (cluster URL, service name, language, translations…) is set up
|
|
|
36
69
|
| `cn.startProcess(name, startNodeId, inputs)` | Start a server-side BPMN process and await its result. |
|
|
37
70
|
| `cn.requestCnql(cnql, verbose?)` | Run a CNQL query (XML, SELECT-only) against the project's tables. |
|
|
38
71
|
| `cn.listenSignal(signalId, callback)` | Subscribe to real-time signals emitted by BPMN processes. |
|
|
39
|
-
| `cn.showDialog(
|
|
72
|
+
| `cn.showDialog(node)` | Render a React node in a fullscreen modal dialog (own React root, unmounted on `close()`). |
|
|
40
73
|
| `cn.setTheme(t)` / `cn.getTheme()` | `'light' \| 'dark'`; `setTheme` also toggles the class on `<html>` (Tailwind `dark:`). |
|
|
41
74
|
| `cn.setLanguage(l)` / `cn.getLanguage()` / `cn.getLanguages()` | Current language and the languages declared in `manifest.json`. |
|
|
42
75
|
| `cn.getProjectFile(path)` / `cn.getProjectFileUrl(path)` | Read/link a file of the deployed project. |
|
|
@@ -84,7 +117,8 @@ Translations come from the project's `.i18n.csv` files and are injected into `cn
|
|
|
84
117
|
```tsx
|
|
85
118
|
cn.i18n('welcome'); // "Bienvenue"
|
|
86
119
|
cn.i18n('greeting', 'Ada', 3); // "Bonjour Ada, 3 messages" (from "Bonjour {0}, {1} messages")
|
|
87
|
-
cn.setLanguage('en'); // switch language at runtime
|
|
120
|
+
cn.setLanguage('en'); // switch language at runtime — every component using
|
|
121
|
+
// useCodenotch() / withCodenotch() re-renders
|
|
88
122
|
```
|
|
89
123
|
|
|
90
124
|
## Real-time signals
|
|
@@ -94,12 +128,15 @@ BPMN processes can broadcast signals; subscribe from the UI with `listenSignal`:
|
|
|
94
128
|
```tsx
|
|
95
129
|
useEffect(() => {
|
|
96
130
|
let sub: IDisposable | undefined;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
131
|
+
let cancelled = false;
|
|
132
|
+
getCodenotch().listenSignal('todosChanged', (signal) => refresh(signal.data))
|
|
133
|
+
.then(s => { if (cancelled) s.dispose(); else sub = s; });
|
|
134
|
+
return () => { cancelled = true; sub?.dispose(); };
|
|
100
135
|
}, []);
|
|
101
136
|
```
|
|
102
137
|
|
|
138
|
+
Two details: the `cancelled` flag matters under React 19's StrictMode, which mounts / unmounts / remounts effects in development — without it the first subscription would leak when the cleanup runs before the promise resolves. And the effect uses `getCodenotch()` rather than the `cn` from `useCodenotch()`: the hook's object changes on every language/theme change, so listing it in the dependency array would re-subscribe on each change, while omitting it trips `react-hooks/exhaustive-deps`. `getCodenotch()` sidesteps both.
|
|
139
|
+
|
|
103
140
|
## Dialogs
|
|
104
141
|
|
|
105
142
|
```tsx
|
|
@@ -111,7 +148,7 @@ const dialog = cn.showDialog(
|
|
|
111
148
|
);
|
|
112
149
|
```
|
|
113
150
|
|
|
114
|
-
The dialog stays open until you call `dialog.close()
|
|
151
|
+
The dialog stays open until you call `dialog.close()`, which unmounts the React tree and removes the `<dialog>` element. The content is rendered in its own React root, so it does not share context (providers) with the calling component.
|
|
115
152
|
|
|
116
153
|
## Type-safe processes and translation keys
|
|
117
154
|
|
|
@@ -140,6 +177,7 @@ With these in the compilation, keys, inputs and outputs are strictly checked and
|
|
|
140
177
|
## Notes for AI assistants
|
|
141
178
|
|
|
142
179
|
- 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
|
|
180
|
+
- `useCodenotch()` is a real hook (hook rules apply; re-renders on language/theme change). In class components use `withCodenotch(Component)` and read `this.props.cn`; in event handlers, services, plain modules or effects use `getCodenotch()`. Never call `useCodenotch()` outside a function component / custom hook.
|
|
181
|
+
- The object returned by `useCodenotch()` changes when the environment changes: fine in `useMemo` deps, but in `useEffect` prefer `getCodenotch()` unless the effect should re-run on language/theme change.
|
|
144
182
|
- 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
|
|
183
|
+
- Codenotch apps are React 19 + Tailwind CSS (automatic JSX runtime — no `import React` needed for JSX); CNQL is SELECT-only (writes go through BPMN processes via `startProcess`).
|
|
@@ -24,7 +24,7 @@ export default class CodeEditor extends React.Component<ICodeEditorProps> {
|
|
|
24
24
|
private isIframeReady;
|
|
25
25
|
private onChangeTimeoutHandle;
|
|
26
26
|
private onMessageHandler;
|
|
27
|
-
refFrame: React.RefObject<HTMLIFrameElement>;
|
|
27
|
+
refFrame: React.RefObject<HTMLIFrameElement | null>;
|
|
28
28
|
constructor(props: ICodeEditorProps);
|
|
29
29
|
setValue(value: string): void;
|
|
30
30
|
componentDidMount(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeEditor.d.ts","sourceRoot":"","sources":["../../src/components/CodeEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAEnC,UAAU,gBAAgB;IACtB,qFAAqF;IACrF,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC;IACrE,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,QAAQ,
|
|
1
|
+
{"version":3,"file":"CodeEditor.d.ts","sourceRoot":"","sources":["../../src/components/CodeEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAEnC,UAAU,gBAAgB;IACtB,qFAAqF;IACrF,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC;IACrE,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,gBAAgB,CAAM;IAC9B,QAAQ,4CAAwC;gBAEpC,KAAK,EAAE,gBAAgB;IAKnC,QAAQ,CAAC,KAAK,EAAE,MAAM;IAStB,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAI5B,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAoBrD,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IA4EnB,MAAM;CAgBT"}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
7
|
const react_1 = __importDefault(require("react"));
|
|
7
8
|
/**
|
|
8
9
|
* Light Monaco code editor embedded in a sandboxed iframe
|
|
@@ -92,68 +93,68 @@ class CodeEditor extends react_1.default.Component {
|
|
|
92
93
|
if (!iframe)
|
|
93
94
|
return;
|
|
94
95
|
const vsBase = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs';
|
|
95
|
-
const html = `<!DOCTYPE html>
|
|
96
|
-
<html>
|
|
97
|
-
<head>
|
|
98
|
-
<meta charset="utf-8" />
|
|
99
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
100
|
-
<style>
|
|
101
|
-
html, body, #container { height: 100%; width: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
102
|
-
</style>
|
|
103
|
-
<script>
|
|
104
|
-
(function(){
|
|
105
|
-
var vsBase = ${JSON.stringify(vsBase)};
|
|
106
|
-
// AMD loader
|
|
107
|
-
var s = document.createElement('script');
|
|
108
|
-
s.src = vsBase + '/loader.js';
|
|
109
|
-
s.onload = function(){
|
|
110
|
-
// Configure and load monaco
|
|
111
|
-
window.require.config({ paths: { vs: vsBase } });
|
|
112
|
-
window.require(['vs/editor/editor.main'], function(){
|
|
113
|
-
var editor;
|
|
114
|
-
|
|
115
|
-
function post(msg){ parent.postMessage(Object.assign({__from:'MonacoIframe'}, msg), '*'); }
|
|
116
|
-
|
|
117
|
-
function ensureEditor(){
|
|
118
|
-
if (editor) return editor;
|
|
119
|
-
editor = monaco.editor.create(document.getElementById('container'), {
|
|
120
|
-
value: '',
|
|
121
|
-
language: 'plaintext',
|
|
122
|
-
theme: 'vs',
|
|
123
|
-
automaticLayout: true,
|
|
124
|
-
minimap: { enabled: false },
|
|
125
|
-
scrollBeyondLastLine: true,
|
|
126
|
-
wordWrap: "on"
|
|
127
|
-
});
|
|
128
|
-
editor.onDidChangeModelContent(function(){
|
|
129
|
-
var v = editor.getValue();
|
|
130
|
-
post({ type: 'change', value: v });
|
|
131
|
-
});
|
|
132
|
-
return editor;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
window.addEventListener('message', function(ev){
|
|
136
|
-
var data = ev.data || {}; if (data.__from !== 'MonacoIframe') return;
|
|
137
|
-
if (data.type === 'init' || data.type === 'update'){
|
|
138
|
-
var ed = ensureEditor();
|
|
139
|
-
if (typeof data.value === 'string' && ed.getValue() !== data.value){ ed.setValue(data.value); }
|
|
140
|
-
if (typeof data.language === 'string') { monaco.editor.setModelLanguage(ed.getModel(), data.language); }
|
|
141
|
-
if (typeof data.theme === 'string') { monaco.editor.setTheme(data.theme); }
|
|
142
|
-
if (typeof data.readOnly === 'boolean') { ed.updateOptions({ readOnly: data.readOnly }); }
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Signal ready after monaco is loaded
|
|
147
|
-
post({ type: 'ready' });
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
document.head.appendChild(s);
|
|
151
|
-
})();
|
|
152
|
-
</script>
|
|
153
|
-
</head>
|
|
154
|
-
<body>
|
|
155
|
-
<div id="container"></div>
|
|
156
|
-
</body>
|
|
96
|
+
const html = `<!DOCTYPE html>
|
|
97
|
+
<html>
|
|
98
|
+
<head>
|
|
99
|
+
<meta charset="utf-8" />
|
|
100
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
101
|
+
<style>
|
|
102
|
+
html, body, #container { height: 100%; width: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
103
|
+
</style>
|
|
104
|
+
<script>
|
|
105
|
+
(function(){
|
|
106
|
+
var vsBase = ${JSON.stringify(vsBase)};
|
|
107
|
+
// AMD loader
|
|
108
|
+
var s = document.createElement('script');
|
|
109
|
+
s.src = vsBase + '/loader.js';
|
|
110
|
+
s.onload = function(){
|
|
111
|
+
// Configure and load monaco
|
|
112
|
+
window.require.config({ paths: { vs: vsBase } });
|
|
113
|
+
window.require(['vs/editor/editor.main'], function(){
|
|
114
|
+
var editor;
|
|
115
|
+
|
|
116
|
+
function post(msg){ parent.postMessage(Object.assign({__from:'MonacoIframe'}, msg), '*'); }
|
|
117
|
+
|
|
118
|
+
function ensureEditor(){
|
|
119
|
+
if (editor) return editor;
|
|
120
|
+
editor = monaco.editor.create(document.getElementById('container'), {
|
|
121
|
+
value: '',
|
|
122
|
+
language: 'plaintext',
|
|
123
|
+
theme: 'vs',
|
|
124
|
+
automaticLayout: true,
|
|
125
|
+
minimap: { enabled: false },
|
|
126
|
+
scrollBeyondLastLine: true,
|
|
127
|
+
wordWrap: "on"
|
|
128
|
+
});
|
|
129
|
+
editor.onDidChangeModelContent(function(){
|
|
130
|
+
var v = editor.getValue();
|
|
131
|
+
post({ type: 'change', value: v });
|
|
132
|
+
});
|
|
133
|
+
return editor;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
window.addEventListener('message', function(ev){
|
|
137
|
+
var data = ev.data || {}; if (data.__from !== 'MonacoIframe') return;
|
|
138
|
+
if (data.type === 'init' || data.type === 'update'){
|
|
139
|
+
var ed = ensureEditor();
|
|
140
|
+
if (typeof data.value === 'string' && ed.getValue() !== data.value){ ed.setValue(data.value); }
|
|
141
|
+
if (typeof data.language === 'string') { monaco.editor.setModelLanguage(ed.getModel(), data.language); }
|
|
142
|
+
if (typeof data.theme === 'string') { monaco.editor.setTheme(data.theme); }
|
|
143
|
+
if (typeof data.readOnly === 'boolean') { ed.updateOptions({ readOnly: data.readOnly }); }
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Signal ready after monaco is loaded
|
|
148
|
+
post({ type: 'ready' });
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
document.head.appendChild(s);
|
|
152
|
+
})();
|
|
153
|
+
</script>
|
|
154
|
+
</head>
|
|
155
|
+
<body>
|
|
156
|
+
<div id="container"></div>
|
|
157
|
+
</body>
|
|
157
158
|
</html>`;
|
|
158
159
|
const doc = iframe.contentWindow?.document;
|
|
159
160
|
if (!doc)
|
|
@@ -163,12 +164,13 @@ class CodeEditor extends react_1.default.Component {
|
|
|
163
164
|
doc.close();
|
|
164
165
|
}
|
|
165
166
|
render() {
|
|
166
|
-
|
|
167
|
+
// Never mutate props: copy the style object before applying defaults.
|
|
168
|
+
const style = { ...this.props.style };
|
|
167
169
|
style.width = style.width || '100%';
|
|
168
170
|
style.height = style.height || '100%';
|
|
169
171
|
style.border = style.border || '0';
|
|
170
172
|
style.outline = style.outline || '0';
|
|
171
|
-
return
|
|
173
|
+
return (0, jsx_runtime_1.jsx)("iframe", { style: style, title: "Codenotch", ref: this.refFrame, className: this.props.className, sandbox: "allow-scripts allow-same-origin" });
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
exports.default = CodeEditor;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ComponentType, type ComponentProps, type FunctionComponent, type RefAttributes } from "react";
|
|
2
|
+
import { IDisposable } from "./models/Misc";
|
|
3
|
+
import { ICodenotchApi, ICodenotchEnv, WithCodenotchProps } from "./models/Codenotch";
|
|
2
4
|
import CodeEditor from "./components/CodeEditor";
|
|
3
5
|
/**
|
|
4
6
|
* The Codenotch runtime environment, populated by {@link init}.
|
|
@@ -35,24 +37,99 @@ declare const env: ICodenotchEnv;
|
|
|
35
37
|
*/
|
|
36
38
|
declare function init(envVariables: any): void;
|
|
37
39
|
/**
|
|
38
|
-
* Return the Codenotch client API bound to the current environment
|
|
40
|
+
* Return the Codenotch client API bound to the current environment, from
|
|
41
|
+
* anywhere: event handlers, plain modules, class components, services…
|
|
39
42
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* This is a plain function (not a hook). The returned object is never stale —
|
|
44
|
+
* its methods always read the live environment — but it does not trigger any
|
|
45
|
+
* re-render when the language or theme changes: inside React components prefer
|
|
46
|
+
* {@link useCodenotch} (function components) or {@link withCodenotch}
|
|
47
|
+
* (class components), which do.
|
|
48
|
+
*
|
|
49
|
+
* `init()` must have been called first, which the Codenotch runtime does
|
|
50
|
+
* automatically when serving the application.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* import { getCodenotch } from 'codenotch-react';
|
|
54
|
+
*
|
|
55
|
+
* export async function loadTodos(userId: string) {
|
|
56
|
+
* const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
|
|
57
|
+
* return result.output.todos;
|
|
58
|
+
* }
|
|
59
|
+
*/
|
|
60
|
+
declare function getCodenotch(): ICodenotchApi;
|
|
61
|
+
/**
|
|
62
|
+
* React hook returning the Codenotch client API.
|
|
63
|
+
*
|
|
64
|
+
* The reference is stable across renders and only changes when the environment
|
|
65
|
+
* changes (`setLanguage`, `setTheme`, `init`), in which case every component
|
|
66
|
+
* using the hook re-renders — so `cn.i18n(...)` output follows the current
|
|
67
|
+
* language automatically, and the object is safe to use in `useMemo` /
|
|
68
|
+
* `useEffect` dependency arrays.
|
|
69
|
+
*
|
|
70
|
+
* Regular hook rules apply (call it unconditionally at the top of a function
|
|
71
|
+
* component or a custom hook). Outside of components — handlers defined in
|
|
72
|
+
* plain modules, services, class components — use {@link getCodenotch} or
|
|
73
|
+
* {@link withCodenotch} instead.
|
|
44
74
|
*
|
|
45
75
|
* @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
|
|
46
76
|
* @example
|
|
47
77
|
* import { useCodenotch } from 'codenotch-react';
|
|
48
78
|
*
|
|
49
|
-
* const MyApp
|
|
79
|
+
* const MyApp = () => {
|
|
50
80
|
* const cn = useCodenotch();
|
|
51
81
|
* return <h1>{cn.i18n('welcome')}</h1>;
|
|
52
82
|
* };
|
|
53
83
|
*/
|
|
54
84
|
declare function useCodenotch(): ICodenotchApi;
|
|
55
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Register a listener called each time the Codenotch environment changes
|
|
87
|
+
* (`setLanguage`, `setTheme`, `init`). Escape hatch for code that cannot use
|
|
88
|
+
* {@link useCodenotch} or {@link withCodenotch}, e.g. a class component that
|
|
89
|
+
* wants to `forceUpdate()` itself, or a non-React module caching translations.
|
|
90
|
+
*
|
|
91
|
+
* @returns A disposable — call `dispose()` to stop listening.
|
|
92
|
+
* @example
|
|
93
|
+
* componentDidMount() {
|
|
94
|
+
* this.sub = onCodenotchChange(() => this.forceUpdate());
|
|
95
|
+
* }
|
|
96
|
+
* componentWillUnmount() {
|
|
97
|
+
* this.sub.dispose();
|
|
98
|
+
* }
|
|
99
|
+
*/
|
|
100
|
+
declare function onCodenotchChange(listener: () => void): IDisposable;
|
|
101
|
+
/** Props of a component wrapped by {@link withCodenotch}, without the injected `cn`. */
|
|
102
|
+
type WithoutCodenotchProps<P> = Omit<P, keyof WithCodenotchProps>;
|
|
103
|
+
/** Instance type of a component (class components only), used to type the forwarded `ref`. */
|
|
104
|
+
type ComponentInstance<C> = C extends new (...args: any[]) => infer I ? I : never;
|
|
105
|
+
/**
|
|
106
|
+
* Higher-order component injecting the Codenotch API as a `cn` prop.
|
|
107
|
+
*
|
|
108
|
+
* Meant for class components, which cannot call {@link useCodenotch}: the
|
|
109
|
+
* wrapped component receives `this.props.cn` and re-renders whenever the
|
|
110
|
+
* environment changes (language, theme…), exactly like the hook. A `ref`
|
|
111
|
+
* passed to the wrapper is forwarded to the wrapped component instance.
|
|
112
|
+
*
|
|
113
|
+
* @param Component A component whose props extend {@link WithCodenotchProps}.
|
|
114
|
+
* @returns A component with the same props minus `cn`.
|
|
115
|
+
* @example
|
|
116
|
+
* import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
|
|
117
|
+
*
|
|
118
|
+
* interface Props extends WithCodenotchProps {
|
|
119
|
+
* userId: string;
|
|
120
|
+
* }
|
|
121
|
+
*
|
|
122
|
+
* class TodoList extends React.Component<Props> {
|
|
123
|
+
* render() {
|
|
124
|
+
* return <h1>{this.props.cn.i18n('todos.title')}</h1>;
|
|
125
|
+
* }
|
|
126
|
+
* }
|
|
127
|
+
*
|
|
128
|
+
* export default withCodenotch(TodoList);
|
|
129
|
+
* // <TodoList userId="42" /> — `cn` is injected
|
|
130
|
+
*/
|
|
131
|
+
declare function withCodenotch<C extends ComponentType<any>>(Component: C): FunctionComponent<WithoutCodenotchProps<ComponentProps<C>> & RefAttributes<ComponentInstance<C>>>;
|
|
132
|
+
export { env, useCodenotch, getCodenotch, withCodenotch, onCodenotchChange, init, CodeEditor };
|
|
56
133
|
export * from "./models/Codenotch";
|
|
57
134
|
export * from "./models/Misc";
|
|
58
135
|
export * from "@codenotch/codenotch.core";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAkB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAKjK,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAoB,aAAa,EAAkB,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExH,OAAO,UAAU,MAAM,yBAAyB,CAAC;AAIjD;;;GAGG;AACH,QAAA,MAAM,GAAG,EAAE,aAAkB,CAAC;AA6B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,iBAAS,IAAI,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI,CA2FrC;AA2OD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,iBAAS,YAAY,IAAI,aAAa,CAErC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,iBAAS,YAAY,IAAI,aAAa,CAErC;AAED;;;;;;;;;;;;;;GAcG;AACH,iBAAS,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAG5D;AAED,wFAAwF;AACxF,KAAK,qBAAqB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,kBAAkB,CAAC,CAAC;AAElE,8FAA8F;AAC9F,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,aAAa,CAAC,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC,EAC/C,SAAS,EAAE,CAAC,GACb,iBAAiB,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAQnG;AAED,OAAO,EACH,GAAG,EACH,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,IAAI,EACJ,UAAU,EACb,CAAC;AAMF,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.CodeEditor = exports.env = void 0;
|
|
21
21
|
exports.useCodenotch = useCodenotch;
|
|
22
|
+
exports.getCodenotch = getCodenotch;
|
|
23
|
+
exports.withCodenotch = withCodenotch;
|
|
24
|
+
exports.onCodenotchChange = onCodenotchChange;
|
|
22
25
|
exports.init = init;
|
|
23
|
-
const
|
|
26
|
+
const react_1 = require("react");
|
|
27
|
+
const react_dom_1 = require("react-dom");
|
|
28
|
+
const client_1 = require("react-dom/client");
|
|
24
29
|
const SignalR_1 = require("./core/SignalR");
|
|
25
30
|
const ProcessUtils_1 = __importDefault(require("./core/ProcessUtils"));
|
|
26
31
|
const uuid_1 = require("uuid");
|
|
@@ -36,6 +41,30 @@ exports.env = env;
|
|
|
36
41
|
const MISSING_CLUSTER_URL_ERROR = "Codenotch cluster URL is not defined. Please set it in the Codenotch configuration.";
|
|
37
42
|
const MISSING_SERVICE_NAME_ERROR = "Codenotch service name is not defined. Please set it in the Codenotch configuration.";
|
|
38
43
|
const MISSING_TENANT_NAME_ERROR = "Codenotch tenant name is not defined. Please set it in the Codenotch configuration.";
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Change tracking. `env` is a plain mutable object; every mutation done through
|
|
46
|
+
// the public API (init, setTheme, setLanguage) goes through notify(), which
|
|
47
|
+
// rotates the API object (so React sees a new reference) and wakes up the
|
|
48
|
+
// subscribers: useCodenotch() via useSyncExternalStore, withCodenotch(),
|
|
49
|
+
// and any listener registered with onCodenotchChange().
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
const listeners = new Set();
|
|
52
|
+
let currentApi;
|
|
53
|
+
function notify() {
|
|
54
|
+
currentApi = createApi();
|
|
55
|
+
listeners.forEach((listener) => {
|
|
56
|
+
try {
|
|
57
|
+
listener();
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("Error in Codenotch change listener:", error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function subscribe(listener) {
|
|
65
|
+
listeners.add(listener);
|
|
66
|
+
return () => { listeners.delete(listener); };
|
|
67
|
+
}
|
|
39
68
|
/**
|
|
40
69
|
* Initialize the Codenotch environment from the given key-value pairs.
|
|
41
70
|
*
|
|
@@ -141,26 +170,15 @@ function init(envVariables) {
|
|
|
141
170
|
}
|
|
142
171
|
catch {
|
|
143
172
|
}
|
|
173
|
+
notify();
|
|
144
174
|
}
|
|
145
175
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* hook rules attached: it can be called anywhere (components, handlers, plain
|
|
150
|
-
* modules). `init()` must have been called first, which the Codenotch runtime
|
|
151
|
-
* does automatically when serving the application.
|
|
152
|
-
*
|
|
153
|
-
* @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
|
|
154
|
-
* @example
|
|
155
|
-
* import { useCodenotch } from 'codenotch-react';
|
|
156
|
-
*
|
|
157
|
-
* const MyApp: React.FC = () => {
|
|
158
|
-
* const cn = useCodenotch();
|
|
159
|
-
* return <h1>{cn.i18n('welcome')}</h1>;
|
|
160
|
-
* };
|
|
176
|
+
* Build the API object bound to {@link env}. Every method reads `env` lazily,
|
|
177
|
+
* so an instance never goes stale; a new one is created by {@link notify} only
|
|
178
|
+
* to give React a fresh reference when the environment changes.
|
|
161
179
|
*/
|
|
162
|
-
function
|
|
163
|
-
|
|
180
|
+
function createApi() {
|
|
181
|
+
const api = {
|
|
164
182
|
env: env,
|
|
165
183
|
uuid: () => (0, uuid_1.v4)(),
|
|
166
184
|
getProjectFileUrl: (relativePath) => {
|
|
@@ -175,7 +193,7 @@ function useCodenotch() {
|
|
|
175
193
|
return url;
|
|
176
194
|
},
|
|
177
195
|
getProjectFile: async (relativePath) => {
|
|
178
|
-
let url =
|
|
196
|
+
let url = api.getProjectFileUrl(relativePath);
|
|
179
197
|
let resp = await fetch(url);
|
|
180
198
|
if (resp.status.toString().startsWith('2') === false) {
|
|
181
199
|
throw new Error(`Failed to fetch file content: ${resp.status} ${resp.statusText}`);
|
|
@@ -266,9 +284,14 @@ function useCodenotch() {
|
|
|
266
284
|
},
|
|
267
285
|
showDialog: (node) => {
|
|
268
286
|
let dialogId = "codenotch-dialog-" + Math.random().toString(36).substring(2, 9);
|
|
287
|
+
let root = undefined;
|
|
269
288
|
let result = {
|
|
270
289
|
id: dialogId,
|
|
271
290
|
close: () => {
|
|
291
|
+
// Unmount the React tree first so effects are cleaned up and nothing leaks,
|
|
292
|
+
// then drop the <dialog> element itself.
|
|
293
|
+
root?.unmount();
|
|
294
|
+
root = undefined;
|
|
272
295
|
let dialogElement = document.getElementById(dialogId);
|
|
273
296
|
if (dialogElement) {
|
|
274
297
|
dialogElement.remove();
|
|
@@ -311,9 +334,11 @@ function useCodenotch() {
|
|
|
311
334
|
dialogElement.style.background = '#78787822';
|
|
312
335
|
dialogElement.style.backdropFilter = 'blur(2px)';
|
|
313
336
|
container.appendChild(dialogElement);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
337
|
+
// React 19: ReactDOM.render() is gone. Render synchronously through a dedicated root
|
|
338
|
+
// so the content is in the DOM before showModal() moves the focus into the dialog.
|
|
339
|
+
root = (0, client_1.createRoot)(dialogElement);
|
|
340
|
+
(0, react_dom_1.flushSync)(() => root.render(node));
|
|
341
|
+
dialogElement.showModal();
|
|
317
342
|
return result;
|
|
318
343
|
},
|
|
319
344
|
listenSignal: async (signalId, callback) => {
|
|
@@ -334,11 +359,13 @@ function useCodenotch() {
|
|
|
334
359
|
document.documentElement.classList.remove(env.theme);
|
|
335
360
|
env.theme = theme;
|
|
336
361
|
document.documentElement.classList.add(theme);
|
|
362
|
+
notify();
|
|
337
363
|
},
|
|
338
364
|
setLanguage: (lang) => {
|
|
339
365
|
if (env.language === lang)
|
|
340
366
|
return;
|
|
341
367
|
env.language = lang;
|
|
368
|
+
notify();
|
|
342
369
|
},
|
|
343
370
|
getLanguages: () => {
|
|
344
371
|
return env.projectManifest?.languages ?? [];
|
|
@@ -359,6 +386,112 @@ function useCodenotch() {
|
|
|
359
386
|
return env.appManifest;
|
|
360
387
|
}
|
|
361
388
|
};
|
|
389
|
+
return api;
|
|
390
|
+
}
|
|
391
|
+
currentApi = createApi();
|
|
392
|
+
/**
|
|
393
|
+
* Return the Codenotch client API bound to the current environment, from
|
|
394
|
+
* anywhere: event handlers, plain modules, class components, services…
|
|
395
|
+
*
|
|
396
|
+
* This is a plain function (not a hook). The returned object is never stale —
|
|
397
|
+
* its methods always read the live environment — but it does not trigger any
|
|
398
|
+
* re-render when the language or theme changes: inside React components prefer
|
|
399
|
+
* {@link useCodenotch} (function components) or {@link withCodenotch}
|
|
400
|
+
* (class components), which do.
|
|
401
|
+
*
|
|
402
|
+
* `init()` must have been called first, which the Codenotch runtime does
|
|
403
|
+
* automatically when serving the application.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* import { getCodenotch } from 'codenotch-react';
|
|
407
|
+
*
|
|
408
|
+
* export async function loadTodos(userId: string) {
|
|
409
|
+
* const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
|
|
410
|
+
* return result.output.todos;
|
|
411
|
+
* }
|
|
412
|
+
*/
|
|
413
|
+
function getCodenotch() {
|
|
414
|
+
return currentApi;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* React hook returning the Codenotch client API.
|
|
418
|
+
*
|
|
419
|
+
* The reference is stable across renders and only changes when the environment
|
|
420
|
+
* changes (`setLanguage`, `setTheme`, `init`), in which case every component
|
|
421
|
+
* using the hook re-renders — so `cn.i18n(...)` output follows the current
|
|
422
|
+
* language automatically, and the object is safe to use in `useMemo` /
|
|
423
|
+
* `useEffect` dependency arrays.
|
|
424
|
+
*
|
|
425
|
+
* Regular hook rules apply (call it unconditionally at the top of a function
|
|
426
|
+
* component or a custom hook). Outside of components — handlers defined in
|
|
427
|
+
* plain modules, services, class components — use {@link getCodenotch} or
|
|
428
|
+
* {@link withCodenotch} instead.
|
|
429
|
+
*
|
|
430
|
+
* @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
|
|
431
|
+
* @example
|
|
432
|
+
* import { useCodenotch } from 'codenotch-react';
|
|
433
|
+
*
|
|
434
|
+
* const MyApp = () => {
|
|
435
|
+
* const cn = useCodenotch();
|
|
436
|
+
* return <h1>{cn.i18n('welcome')}</h1>;
|
|
437
|
+
* };
|
|
438
|
+
*/
|
|
439
|
+
function useCodenotch() {
|
|
440
|
+
return (0, react_1.useSyncExternalStore)(subscribe, getCodenotch, getCodenotch);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Register a listener called each time the Codenotch environment changes
|
|
444
|
+
* (`setLanguage`, `setTheme`, `init`). Escape hatch for code that cannot use
|
|
445
|
+
* {@link useCodenotch} or {@link withCodenotch}, e.g. a class component that
|
|
446
|
+
* wants to `forceUpdate()` itself, or a non-React module caching translations.
|
|
447
|
+
*
|
|
448
|
+
* @returns A disposable — call `dispose()` to stop listening.
|
|
449
|
+
* @example
|
|
450
|
+
* componentDidMount() {
|
|
451
|
+
* this.sub = onCodenotchChange(() => this.forceUpdate());
|
|
452
|
+
* }
|
|
453
|
+
* componentWillUnmount() {
|
|
454
|
+
* this.sub.dispose();
|
|
455
|
+
* }
|
|
456
|
+
*/
|
|
457
|
+
function onCodenotchChange(listener) {
|
|
458
|
+
const unsubscribe = subscribe(listener);
|
|
459
|
+
return { dispose: unsubscribe };
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Higher-order component injecting the Codenotch API as a `cn` prop.
|
|
463
|
+
*
|
|
464
|
+
* Meant for class components, which cannot call {@link useCodenotch}: the
|
|
465
|
+
* wrapped component receives `this.props.cn` and re-renders whenever the
|
|
466
|
+
* environment changes (language, theme…), exactly like the hook. A `ref`
|
|
467
|
+
* passed to the wrapper is forwarded to the wrapped component instance.
|
|
468
|
+
*
|
|
469
|
+
* @param Component A component whose props extend {@link WithCodenotchProps}.
|
|
470
|
+
* @returns A component with the same props minus `cn`.
|
|
471
|
+
* @example
|
|
472
|
+
* import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
|
|
473
|
+
*
|
|
474
|
+
* interface Props extends WithCodenotchProps {
|
|
475
|
+
* userId: string;
|
|
476
|
+
* }
|
|
477
|
+
*
|
|
478
|
+
* class TodoList extends React.Component<Props> {
|
|
479
|
+
* render() {
|
|
480
|
+
* return <h1>{this.props.cn.i18n('todos.title')}</h1>;
|
|
481
|
+
* }
|
|
482
|
+
* }
|
|
483
|
+
*
|
|
484
|
+
* export default withCodenotch(TodoList);
|
|
485
|
+
* // <TodoList userId="42" /> — `cn` is injected
|
|
486
|
+
*/
|
|
487
|
+
function withCodenotch(Component) {
|
|
488
|
+
const Wrapped = (props) => {
|
|
489
|
+
const cn = useCodenotch();
|
|
490
|
+
// React 19: `ref` is a regular prop, so spreading `props` forwards it.
|
|
491
|
+
return (0, react_1.createElement)(Component, { ...props, cn });
|
|
492
|
+
};
|
|
493
|
+
Wrapped.displayName = `withCodenotch(${Component.displayName || Component.name || "Component"})`;
|
|
494
|
+
return Wrapped;
|
|
362
495
|
}
|
|
363
496
|
// Public models. Re-exported from the package root so that the typings
|
|
364
497
|
// generated by the Codenotch IDE (typings/i18n.d.ts and typings/process.d.ts,
|