Skip to content

Commit

Permalink
Improve lazy-loading of Monaco editor
Browse files Browse the repository at this point in the history
- Use more dynamic imports for Monaco editor
-- Expose dynamically imported
-- Use type imports where possible to avoid full resolution
- Expose as much options for outside configuration as possible
- Avoid unnecessary boolean return type
- Avoid loading Monaco editor only for key codes
- Align react-query versions with process-editor-client

Co-authored-by: Olaf Lessenich <olessenich@eclipsesource.com>
  • Loading branch information
martin-fleck-at and xai committed Mar 19, 2024
1 parent 9b2241e commit 96ee198
Show file tree
Hide file tree
Showing 23 changed files with 610 additions and 201 deletions.
88 changes: 73 additions & 15 deletions integrations/standalone/index.html
@@ -1,17 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Inscription Editor for Axon Ivy Process Elements" />
<title>Inscription Editor</title>
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Inscription Editor for Axon Ivy Process Elements" />
<title>Inscription Editor</title>
<style>
#root {
height: 100%;
}

/* from https://cssloaders.github.io/, licensed under MIT */
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
top: 50%;
left: 50%;
animation: rotate 1s linear infinite;
}

.loader::before {
content: '';
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid #a5a5a5;
animation: prixClipFix 2s linear infinite;
}

@keyframes rotate {
100% {
transform: rotate(360deg);
}
}

@keyframes prixClipFix {
0% {
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0);
}

25% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0);
}

50% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%);
}

75% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%);
}

100% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0);
}
}
</style>
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader"></div>
</div>
<script type="module" src="/src/index.tsx"></script>
</body>

</html>
88 changes: 73 additions & 15 deletions integrations/standalone/mock.html
@@ -1,17 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Inscription Editor for Axon Ivy Process Elements" />
<title>Inscription Editor Mock</title>
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/mock.tsx"></script>
</body>
</html>

<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Inscription Editor for Axon Ivy Process Elements" />
<title>Inscription Editor Mock</title>
<style>
#root {
height: 100%;
}

/* from https://cssloaders.github.io/, licensed under MIT */
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
top: 50%;
left: 50%;
animation: rotate 1s linear infinite;
}

.loader::before {
content: '';
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid #a5a5a5;
animation: prixClipFix 2s linear infinite;
}

@keyframes rotate {
100% {
transform: rotate(360deg);
}
}

@keyframes prixClipFix {
0% {
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0);
}

25% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0);
}

50% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%);
}

75% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%);
}

100% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0);
}
}
</style>
</head>

<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader"></div>
</div>
<script type="module" src="/src/mock.tsx"></script>
</body>

</html>
55 changes: 13 additions & 42 deletions integrations/standalone/src/index.tsx
@@ -1,52 +1,23 @@
import './index.css';
import { IvyScriptLanguage, InscriptionClientJsonRpc, MonacoUtil } from '@axonivy/inscription-core';
import {
App,
AppStateView,
ClientContextProvider,
MonacoEditorUtil,
QueryProvider,
ThemeContextProvider,
initQueryClient,
type ThemeMode
} from '@axonivy/inscription-editor';
import React from 'react';
import { InscriptionClientJsonRpc } from '@axonivy/inscription-core';
import { AppStateView } from '@axonivy/inscription-editor';
import { createRoot } from 'react-dom/client';
import './index.css';
import { LazyApp, type LazyAppProps } from './lazy-app';
import { URLParams } from './url-helper';

async function initMonaco(theme: ThemeMode): Promise<boolean> {
const monaco = await import('monaco-editor/esm/vs/editor/editor.api');
const editorWorker = await import('monaco-editor/esm/vs/editor/editor.worker?worker');
await MonacoUtil.initStandalone(editorWorker.default);
await MonacoEditorUtil.configureInstance(monaco, theme);
return true;
}

export async function start(): Promise<void> {
const server = URLParams.webSocketBase();
const app = URLParams.app();
const pmv = URLParams.pmv();
const pid = URLParams.pid();
const theme = URLParams.themeMode();
const props: LazyAppProps = {
server: URLParams.webSocketBase(),
app: URLParams.app(),
pmv: URLParams.pmv(),
pid: URLParams.pid(),
theme: URLParams.themeMode(),
clientCreator: () => InscriptionClientJsonRpc.startWebSocketClient(props.server!)
};

const root = createRoot(document.getElementById('root')!);
try {
const isMonacoReady = initMonaco(theme);
IvyScriptLanguage.startWebSocketClient(server, isMonacoReady);
const client = await InscriptionClientJsonRpc.startWebSocketClient(server);
const queryClient = initQueryClient();

root.render(
<React.StrictMode>
<ThemeContextProvider theme={theme}>
<ClientContextProvider client={client}>
<QueryProvider client={queryClient}>
<App app={app} pmv={pmv} pid={pid} />
</QueryProvider>
</ClientContextProvider>
</ThemeContextProvider>
</React.StrictMode>
);
root.render(<LazyApp {...props} />);
} catch (error) {
console.error(error);
root.render(<AppStateView>{'An error has occurred: ' + error}</AppStateView>);
Expand Down
50 changes: 50 additions & 0 deletions integrations/standalone/src/lazy-app.tsx
@@ -0,0 +1,50 @@
import { IvyScriptLanguage } from '@axonivy/inscription-core';
import {
App,
ClientContextProvider,
MonacoEditorUtil,
QueryProvider,
ThemeContextProvider,
initQueryClient,
type ThemeMode
} from '@axonivy/inscription-editor';
import type { InscriptionClient } from '@axonivy/inscription-protocol';
import type { QueryClient } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';

export interface LazyAppProps {
clientCreator: () => Promise<InscriptionClient>;
server?: string;
app: string;
pmv: string;
pid: string;
theme: ThemeMode;
}

export function LazyApp(props: LazyAppProps) {
const [client, setClient] = useState<InscriptionClient>();
const [queryClient] = useState<QueryClient>(initQueryClient());

useEffect(() => {
const instance = MonacoEditorUtil.configureInstance({ theme: props.theme, debug: true });
if (props.server) {
IvyScriptLanguage.startWebSocketClient(props.server, instance);
}
props.clientCreator().then(client => setClient(client));
}, [props, props.server, props.theme]);

if (client) {
return (
<React.StrictMode>
<ThemeContextProvider theme={props.theme}>
<ClientContextProvider client={client}>
<QueryProvider client={queryClient}>
<App app={props.app} pmv={props.pmv} pid={props.pid} />
</QueryProvider>
</ClientContextProvider>
</ThemeContextProvider>
</React.StrictMode>
);
}
return <div className='loader' />;
}
55 changes: 18 additions & 37 deletions integrations/standalone/src/mock.tsx
@@ -1,49 +1,30 @@
import './index.css';
import { MonacoUtil } from '@axonivy/inscription-core';
import {
App,
ClientContextProvider,
MonacoEditorUtil,
QueryProvider,
ThemeContextProvider,
initQueryClient,
type ThemeMode
} from '@axonivy/inscription-editor';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { URLParams } from './url-helper';
import { AppStateView } from '@axonivy/inscription-editor';
import type { ElementType } from '@axonivy/inscription-protocol';
import { createRoot } from 'react-dom/client';
import './index.css';
import { LazyApp, type LazyAppProps } from './lazy-app';
import { InscriptionClientMock } from './mock/inscription-client-mock';

async function initMonaco(theme: ThemeMode): Promise<void> {
const monaco = await import('monaco-editor/esm/vs/editor/editor.api');
await MonacoUtil.initStandalone();
await MonacoEditorUtil.configureInstance(monaco, theme);
}
import { URLParams } from './url-helper';

export async function start(): Promise<void> {
const theme = URLParams.themeMode();
const readonly = URLParams.parameter('readonly') ? true : false;
const type = (URLParams.parameter('type') as ElementType) ?? undefined;

initMonaco(theme);
const props: LazyAppProps = {
app: '',
pmv: '',
pid: '1',
theme: URLParams.themeMode(),
clientCreator: async () => new InscriptionClientMock(readonly, type)
};

const root = createRoot(document.getElementById('root')!);

const inscriptionClient = new InscriptionClientMock(readonly, type);
const queryClient = initQueryClient();

root.render(
<React.StrictMode>
<ThemeContextProvider theme={theme}>
<ClientContextProvider client={inscriptionClient}>
<QueryProvider client={queryClient}>
<App app='' pmv='' pid={'1'} />
</QueryProvider>
</ClientContextProvider>
</ThemeContextProvider>
</React.StrictMode>
);
try {
root.render(<LazyApp {...props} />);
} catch (error) {
console.error(error);
root.render(<AppStateView>{'An error has occurred: ' + error}</AppStateView>);
}
}

start();
8 changes: 2 additions & 6 deletions integrations/standalone/src/url-helper.ts
@@ -1,4 +1,4 @@
import type { ThemeMode } from '@axonivy/inscription-editor';
import { defaultThemeMode, type ThemeMode } from '@axonivy/inscription-editor';

export namespace URLParams {
export function parameter(key: string): string | undefined {
Expand All @@ -23,7 +23,7 @@ export namespace URLParams {
}

export function themeMode(): ThemeMode {
return (parameter('theme') as ThemeMode) ?? defaultTheme();
return (parameter('theme') as ThemeMode) ?? defaultThemeMode();
}

const isSecureConnection = () => {
Expand All @@ -49,8 +49,4 @@ export namespace URLParams {
}
return 'localhost:8081';
};

const defaultTheme = (): ThemeMode => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
}

0 comments on commit 96ee198

Please sign in to comment.