@codenotch/codenotch.react 1.0.81
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 +145 -0
- package/dist/components/Auml.d.ts +46 -0
- package/dist/components/Auml.d.ts.map +1 -0
- package/dist/components/Auml.js +208 -0
- package/dist/components/CodeEditor.d.ts +39 -0
- package/dist/components/CodeEditor.d.ts.map +1 -0
- package/dist/components/CodeEditor.js +174 -0
- package/dist/core/ProcessUtils.d.ts +10 -0
- package/dist/core/ProcessUtils.d.ts.map +1 -0
- package/dist/core/ProcessUtils.js +65 -0
- package/dist/core/SignalR.d.ts +30 -0
- package/dist/core/SignalR.d.ts.map +1 -0
- package/dist/core/SignalR.js +245 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +370 -0
- package/dist/models/AppManifestModels.d.ts +46 -0
- package/dist/models/AppManifestModels.d.ts.map +1 -0
- package/dist/models/AppManifestModels.js +2 -0
- package/dist/models/Codenotch.d.ts +256 -0
- package/dist/models/Codenotch.d.ts.map +1 -0
- package/dist/models/Codenotch.js +2 -0
- package/dist/models/Misc.d.ts +28 -0
- package/dist/models/Misc.d.ts.map +1 -0
- package/dist/models/Misc.js +2 -0
- package/dist/models/ProjectManifestModels.d.ts +126 -0
- package/dist/models/ProjectManifestModels.d.ts.map +1 -0
- package/dist/models/ProjectManifestModels.js +158 -0
- package/dist/utils/I18nUtils.d.ts +7 -0
- package/dist/utils/I18nUtils.d.ts.map +1 -0
- package/dist/utils/I18nUtils.js +37 -0
- package/package.json +44 -0
- package/src/components/CodeEditor.tsx +203 -0
- package/src/core/ProcessUtils.ts +86 -0
- package/src/core/SignalR.ts +375 -0
- package/src/index.ts +387 -0
- package/src/models/AppManifestModels.ts +54 -0
- package/src/models/Codenotch.ts +285 -0
- package/src/models/Misc.ts +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
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 SioQL 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.requestSioql(sioql, verbose?)` | Run a SioQL 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 SioQL
|
|
64
|
+
|
|
65
|
+
SioQL 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.requestSioql(`
|
|
69
|
+
<SioQL xmlns="myproject" PageSize="10" PageIndex="0">
|
|
70
|
+
<Users Ref="results">
|
|
71
|
+
<Id />
|
|
72
|
+
<Email />
|
|
73
|
+
<IsAdmin Equal="true" />
|
|
74
|
+
</Users>
|
|
75
|
+
</SioQL>`);
|
|
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; SioQL is SELECT-only (writes go through BPMN processes via `startProcess`).
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,208 @@
|
|
|
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;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ICodenotchEnv } from "..";
|
|
3
|
+
interface ICodeEditorProps {
|
|
4
|
+
/** Codenotch environment; only used to follow the current theme (`vs`/`vs-dark`). */
|
|
5
|
+
env?: ICodenotchEnv;
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
className?: string;
|
|
8
|
+
/** Text content of the editor. */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** Monaco language id (e.g. `'typescript'`, `'json'`, `'xml'`). Defaults to `'plaintext'`. */
|
|
11
|
+
language?: string;
|
|
12
|
+
readOnly?: boolean;
|
|
13
|
+
/** Called (debounced, 300 ms) when the user edits the content. */
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Light Monaco code editor embedded in a sandboxed iframe
|
|
18
|
+
* (Monaco is loaded from the jsdelivr CDN — requires network access).
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <CodeEditor env={cn.env} language="json" value={text} onChange={setText} />
|
|
22
|
+
*/
|
|
23
|
+
export default class CodeEditor extends React.Component<ICodeEditorProps> {
|
|
24
|
+
private isIframeReady;
|
|
25
|
+
private onChangeTimeoutHandle;
|
|
26
|
+
private onMessageHandler;
|
|
27
|
+
refFrame: React.RefObject<HTMLIFrameElement>;
|
|
28
|
+
constructor(props: ICodeEditorProps);
|
|
29
|
+
setValue(value: string): void;
|
|
30
|
+
componentDidMount(): void;
|
|
31
|
+
componentWillUnmount(): void;
|
|
32
|
+
componentDidUpdate(prevProps: ICodeEditorProps): void;
|
|
33
|
+
private handleMessage;
|
|
34
|
+
private postToIframe;
|
|
35
|
+
private writeIframe;
|
|
36
|
+
render(): React.JSX.Element;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=CodeEditor.d.ts.map
|
|
@@ -0,0 +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,qCAAwC;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;CAeT"}
|
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
/**
|
|
8
|
+
* Light Monaco code editor embedded in a sandboxed iframe
|
|
9
|
+
* (Monaco is loaded from the jsdelivr CDN — requires network access).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <CodeEditor env={cn.env} language="json" value={text} onChange={setText} />
|
|
13
|
+
*/
|
|
14
|
+
class CodeEditor extends react_1.default.Component {
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props);
|
|
17
|
+
this.isIframeReady = false;
|
|
18
|
+
this.onChangeTimeoutHandle = null;
|
|
19
|
+
this.refFrame = react_1.default.createRef();
|
|
20
|
+
this.onMessageHandler = this.handleMessage.bind(this);
|
|
21
|
+
}
|
|
22
|
+
setValue(value) {
|
|
23
|
+
if (this.isIframeReady) {
|
|
24
|
+
this.postToIframe({
|
|
25
|
+
type: 'update',
|
|
26
|
+
value: value,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
componentDidMount() {
|
|
31
|
+
window.addEventListener('message', this.onMessageHandler);
|
|
32
|
+
this.writeIframe();
|
|
33
|
+
}
|
|
34
|
+
componentWillUnmount() {
|
|
35
|
+
window.removeEventListener('message', this.onMessageHandler);
|
|
36
|
+
}
|
|
37
|
+
componentDidUpdate(prevProps) {
|
|
38
|
+
const changed = (prevProps.value !== this.props.value ||
|
|
39
|
+
prevProps.language !== this.props.language ||
|
|
40
|
+
prevProps.readOnly !== this.props.readOnly);
|
|
41
|
+
if (changed && this.isIframeReady) {
|
|
42
|
+
let isDark = this.props.env?.theme === 'dark';
|
|
43
|
+
this.postToIframe({
|
|
44
|
+
type: 'update',
|
|
45
|
+
value: this.props.value ?? '',
|
|
46
|
+
language: this.props.language || 'plaintext',
|
|
47
|
+
readOnly: !!this.props.readOnly,
|
|
48
|
+
theme: isDark ? 'vs-dark' : 'vs'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
handleMessage(event) {
|
|
53
|
+
const data = event.data;
|
|
54
|
+
if (!data || typeof data !== 'object')
|
|
55
|
+
return;
|
|
56
|
+
if (data.__from !== 'MonacoIframe')
|
|
57
|
+
return;
|
|
58
|
+
switch (data.type) {
|
|
59
|
+
case 'ready': {
|
|
60
|
+
this.isIframeReady = true;
|
|
61
|
+
// send initial payload
|
|
62
|
+
this.postToIframe({
|
|
63
|
+
type: 'init',
|
|
64
|
+
value: this.props.value ?? '',
|
|
65
|
+
language: this.props.language || 'plaintext',
|
|
66
|
+
readOnly: !!this.props.readOnly,
|
|
67
|
+
theme: this.props.env?.theme === 'dark' ? 'vs-dark' : 'vs'
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'change': {
|
|
72
|
+
const next = String(data.value ?? '');
|
|
73
|
+
if (this.onChangeTimeoutHandle) {
|
|
74
|
+
clearTimeout(this.onChangeTimeoutHandle);
|
|
75
|
+
}
|
|
76
|
+
this.onChangeTimeoutHandle = setTimeout(() => {
|
|
77
|
+
this.props.onChange?.(next);
|
|
78
|
+
}, 300);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
;
|
|
84
|
+
postToIframe(message) {
|
|
85
|
+
const w = this.refFrame.current?.contentWindow;
|
|
86
|
+
if (!w)
|
|
87
|
+
return;
|
|
88
|
+
w.postMessage({ __from: 'MonacoIframe', ...message }, '*');
|
|
89
|
+
}
|
|
90
|
+
writeIframe() {
|
|
91
|
+
const iframe = this.refFrame.current;
|
|
92
|
+
if (!iframe)
|
|
93
|
+
return;
|
|
94
|
+
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>
|
|
157
|
+
</html>`;
|
|
158
|
+
const doc = iframe.contentWindow?.document;
|
|
159
|
+
if (!doc)
|
|
160
|
+
return;
|
|
161
|
+
doc.open();
|
|
162
|
+
doc.write(html);
|
|
163
|
+
doc.close();
|
|
164
|
+
}
|
|
165
|
+
render() {
|
|
166
|
+
let style = this.props.style || {};
|
|
167
|
+
style.width = style.width || '100%';
|
|
168
|
+
style.height = style.height || '100%';
|
|
169
|
+
style.border = style.border || '0';
|
|
170
|
+
style.outline = style.outline || '0';
|
|
171
|
+
return react_1.default.createElement("iframe", { style: style, title: "Codenotch", ref: this.refFrame, className: this.props.className, sandbox: "allow-scripts allow-same-origin" });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.default = CodeEditor;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IProcessCallbacks } from "../models/Misc";
|
|
2
|
+
import { IProcessResult } from "../models/Codenotch";
|
|
3
|
+
/**
|
|
4
|
+
* static class mainly containing a method to start processes
|
|
5
|
+
*/
|
|
6
|
+
export default class ProcessUtils {
|
|
7
|
+
static startProcess(subscribeFunc: (processInstanceId: string, callbacks: IProcessCallbacks) => Promise<void>, clusterUrl: string, tenantName: string, projectName: string, processId: string, processInput: any, processInstanceId?: string, startNodeId?: string, token?: string): Promise<IProcessResult>;
|
|
8
|
+
private static launchMainProcess;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=ProcessUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcessUtils.d.ts","sourceRoot":"","sources":["../../src/core/ProcessUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,YAAY;WAET,YAAY,CAAC,aAAa,EAAE,CAAC,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;mBAsCpS,iBAAiB;CAsCzC"}
|