Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve lazy-loading of Monaco editor #532

Merged
merged 1 commit into from Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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';
};
}