Skip to content

Commit

Permalink
Update react example, add start and dispose and allow to use strict mode
Browse files Browse the repository at this point in the history
- Restarting is now handled by the wrapper
  • Loading branch information
kaisalmen committed Aug 28, 2024
1 parent 8068296 commit eeed8f5
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 60 deletions.
3 changes: 3 additions & 0 deletions packages/examples/react_python.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

<body>
<h2>React: Python Language Client & Language Server (Web Socket)</h2>
<button type="button" id="button-start">Start</button>
<label>Enable Strict mode:</label><input type="checkbox" id="checkbox-strictmode" />
<button type="button" id="button-dispose">Dispose</button>
<div id="monaco-editor-root"></div>
<script type="module">
import { configureMonacoWorkers, runPythonReact } from './src/python/client/reactPython.tsx';
Expand Down
3 changes: 3 additions & 0 deletions packages/examples/react_statemachine.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

<body>
<h2>React: Langium Statemachine Language Client & Language Server (Worker)</h2>
<button type="button" id="button-start">Start</button>
<label>Enable Strict mode:</label><input type="checkbox" id="checkbox-strictmode" />
<button type="button" id="button-dispose">Dispose</button>
<div id="monaco-editor-root"></div>
<script type="module">
import { configureMonacoWorkers, runStatemachineReact } from './src/langium/statemachine/main-react.tsx';
Expand Down
73 changes: 40 additions & 33 deletions packages/examples/src/langium/statemachine/main-react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import React from 'react';
import React, { StrictMode, useEffect, useState } from 'react';
import ReactDOM from 'react-dom/client';
import { MonacoEditorReactComp } from '@typefox/monaco-editor-react';
import { useWorkerFactory } from 'monaco-editor-wrapper/workerFactory';
Expand All @@ -21,40 +21,47 @@ export const configureMonacoWorkers = () => {
};

export const runStatemachineReact = async () => {
const langiumGlobalConfig = await createLangiumGlobalConfig({
languageServerId: 'react',
useLanguageClient: true,
text,
worker: loadStatemachineWorkerRegular()
});

try {
const langiumGlobalConfig = await createLangiumGlobalConfig({
languageServerId: 'react',
useLanguageClient: true,
text,
worker: loadStatemachineWorkerRegular()
});
const styles = {
'paddingTop': '5px',
'height': '80vh'
};
const comp = <MonacoEditorReactComp
userConfig={langiumGlobalConfig}
style={styles}
/>;

const htmlElement = document.getElementById('monaco-editor-root');
ReactDOM.createRoot(htmlElement!).render(comp);

// container comp around MonacoEditorReactComp

// app comp
// => MonacoEditorReactComp

// use
setTimeout(() => {
console.log('Updating styles');
styles.height = '85vh';
if (langiumGlobalConfig.wrapperConfig.editorAppConfig.codeResources !== undefined) {
if (langiumGlobalConfig.wrapperConfig.editorAppConfig.codeResources.main !== undefined) {
langiumGlobalConfig.wrapperConfig.editorAppConfig.codeResources.main.text = 'tester';
}
document.querySelector('#button-start')?.addEventListener('click', async () => {
const htmlElement = document.getElementById('monaco-editor-root');
const App = () => {

const [ height, setHeight ] = useState('80vh');

useEffect(() => {
const timer = setTimeout(() => {
console.log('Updating styles');
setHeight('85vh');
}, 2000);

return () => clearTimeout(timer);
}, []);

return (
<div style={{ 'height': height }} >
<MonacoEditorReactComp
style={{ 'height': '100%' }}
userConfig={langiumGlobalConfig} />
</div>
);
};
const strictMode = (document.getElementById('checkbox-strictmode')! as HTMLInputElement).checked;
if (strictMode) {
ReactDOM.createRoot(htmlElement!).render(<StrictMode><App /></StrictMode>);
} else {
ReactDOM.createRoot(htmlElement!).render(<App />);
}
}, 2000);
});
document.querySelector('#button-dispose')?.addEventListener('click', () => {

});
} catch (e) {
console.error(e);
}
Expand Down
51 changes: 35 additions & 16 deletions packages/examples/src/python/client/reactPython.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,40 @@ export const runPythonReact = async () => {
const onTextChanged = (textChanges: TextChanges) => {
console.log(`Dirty? ${textChanges.isDirty}\ntext: ${textChanges.text}\ntextOriginal: ${textChanges.textOriginal}`);
};

const htmlElement = document.getElementById('monaco-editor-root');
const comp = <MonacoEditorReactComp
userConfig={createUserConfig('/workspace', badPyCode, '/workspace/bad.py')}
style={{
'paddingTop': '5px',
'height': '80vh'
}}
onTextChanged={onTextChanged}
onLoad={(wrapper: MonacoEditorLanguageClientWrapper) => {
console.log(`Loaded ${wrapper.reportStatus().join('\n').toString()}`);
}}
onError={(e) => {
console.error(e);
}}
/>;
ReactDOM.createRoot(htmlElement!).render(<StrictMode>{comp}</StrictMode>);
const root = ReactDOM.createRoot(htmlElement!);

try {
document.querySelector('#button-start')?.addEventListener('click', async () => {
const App = () => {
return (
<div style={{ 'height': '80vh', padding: '5px' }} >
<MonacoEditorReactComp
userConfig={createUserConfig('/workspace', badPyCode, '/workspace/bad.py')}
style={{ 'height': '100%' }}
onTextChanged={onTextChanged}
onLoad={(wrapper: MonacoEditorLanguageClientWrapper) => {
console.log(`Loaded ${wrapper.reportStatus().join('\n').toString()}`);
}}
onError={(e) => {
console.error(e);
}} />
</div>
);
};

const strictMode = (document.getElementById('checkbox-strictmode')! as HTMLInputElement).checked;

if (strictMode) {
root.render(<StrictMode><App /></StrictMode>);
} else {
root.render(<App />);
}
});
document.querySelector('#button-dispose')?.addEventListener('click', () => {
root.render([]);
});
} catch (e) {
console.error(e);
}
};
18 changes: 9 additions & 9 deletions packages/wrapper-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
const loggerRef = useRef<Logger>(new Logger());
const containerRef = useRef<HTMLDivElement>(null);
const [onTextChangedSubscriptions, setOnTextChangedSubscriptions] = useState<monaco.IDisposable[]>([]);
const [isRestarting, setIsRestarting] = useState<Promise<void> | undefined>();

useEffect(() => {
loggerRef.current.updateConfig(userConfig.loggerConfig);
Expand All @@ -60,18 +59,19 @@ export const MonacoEditorReactComp: React.FC<MonacoEditorProps> = (props) => {
}, [className]);

const handleReInit = useCallback(async () => {
if (isRestarting !== undefined) {
await isRestarting;
if (wrapperRef.current.isStopping() === undefined) {
await destroyMonaco();
} else {
await wrapperRef.current.isStopping();
}

const promiseExecution = async (resolve: (value: void | PromiseLike<void>) => void) => {
await destroyMonaco();
if (wrapperRef.current.isStarting() === undefined) {
await initMonaco();
await startMonaco();
setIsRestarting(undefined);
resolve();
};
setIsRestarting(new Promise<void>(promiseExecution));
} else {
await wrapperRef.current.isStarting();
}

}, [userConfig]);

const initMonaco = useCallback(async () => {
Expand Down
44 changes: 42 additions & 2 deletions packages/wrapper/src/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ export class MonacoEditorLanguageClientWrapper {
private languageClientWrapper?: LanguageClientWrapper;
private logger: Logger = new Logger();
private initDone = false;
private starting?: Promise<void>;
private startAwait: (value: void | PromiseLike<void>) => void;
private stopping?: Promise<void>;
private stopAwait: (value: void | PromiseLike<void>) => void;

/**
* Perform an isolated initialization of the user services and the languageclient wrapper (if used).
*/
async init(userConfig: UserConfig) {
this.markStarting();
if (this.initDone) {
throw new Error('init was already performed. Please call dispose first if you want to re-start.');
}
Expand Down Expand Up @@ -102,9 +107,25 @@ export class MonacoEditorLanguageClientWrapper {
if (this.languageClientWrapper?.haveLanguageClientConfig() ?? false) {
await this.languageClientWrapper?.start();
}
this.markStarted();
}

public isInitDone() {
private markStarting() {
this.starting = new Promise<void>((resolve) => {
this.startAwait = resolve;
});
}

private markStarted() {
this.startAwait();
this.starting = undefined;
}

isStarting() {
return this.starting;
}

isInitDone() {
return this.initDone;
}

Expand Down Expand Up @@ -168,7 +189,7 @@ export class MonacoEditorLanguageClientWrapper {
this.editorApp?.updateEditorModels(modelRefs);
}

public reportStatus() {
reportStatus() {
const status: string[] = [];
status.push('Wrapper status:');
status.push(`Editor: ${this.editorApp?.getEditor()?.getId()}`);
Expand All @@ -180,6 +201,8 @@ export class MonacoEditorLanguageClientWrapper {
* Disposes all application and editor resources, plus the languageclient (if used).
*/
async dispose(disposeLanguageClient: boolean = true): Promise<void> {
this.markStopping();

this.editorApp?.disposeApp();

if ((disposeLanguageClient && this.languageClientWrapper?.haveLanguageClient()) ?? false) {
Expand All @@ -191,6 +214,23 @@ export class MonacoEditorLanguageClientWrapper {
await Promise.resolve('Monaco editor has been disposed.');
}
this.initDone = false;

this.markStopped();
}

private markStopping() {
this.stopping = new Promise<void>((resolve) => {
this.stopAwait = resolve;
});
}

private markStopped() {
this.stopAwait();
this.stopping = undefined;
}

isStopping() {
return this.stopping;
}

updateLayout() {
Expand Down

0 comments on commit eeed8f5

Please sign in to comment.