1. Anuncie Aqui ! Entre em contato fdantas@4each.com.br

[Flutter] Monaco editor in monaco works correctly the first time but not any time after

Discussão em 'Mobile' iniciado por Stack, Outubro 15, 2024 às 21:03.

  1. Stack

    Stack Membro Participativo

    the first time the component loads the initial input script is loaded and displayed correctly.

    when go to another component that does not have the script column, then come back, the initial input script is not loaded, "// loading ..." is what appears.

    here is a video of the issue:: https://www.youtube.com/watch?v=AkacFwwtf1k

    I think the issue has something to do with with the incorect dispose of the element, but I cant figure it out.

    from the logs I can see that the second time the page with the script component loads the Monico editor didnt fire the following console logs::

    console.log("Received updateContent message with event:", event);
    console.log("Received updateContent message with data:", event.data);
    console.log("Received updateContent message with content:", event.data.content);


    I got pretty much all the code from this video:: https://www.youtube.com/watch?v=Vb_lBxiaQ5I

    this was the only stack overflow I could find that mentioned flutter and monaco editor:: Flutter web project with javascript modules that use requireJS (Monaco editor in Flutter web)

    this is flutter web.

    any help is greatly appreciated.

    code1::

    import 'dart:async';
    import 'dart:convert';
    import 'dart:html' as html;
    import 'dart:ui_web';
    import 'package:flutter/material.dart';
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    import 'package:worknow_front_end/main.dart';
    import 'dart:ui' as ui;

    import '../../../core/notifiers/form_page_notifier.dart';
    import '../../../core/providers/form_page_provider.dart';

    class ScriptColumn extends ConsumerStatefulWidget {
    final Map<String, dynamic> column;
    final Function(String, dynamic) on_changed;

    ScriptColumn({
    Key? key,
    required this.column,
    required this.on_changed,
    }) : super(key: key);

    @override
    _ScriptColumnState createState() => _ScriptColumnState();
    }

    class _ScriptColumnState extends ConsumerState<ScriptColumn> {
    late html.IFrameElement _iframeElement;
    bool _is_loading = true;
    bool _editor_ready = false;
    StreamSubscription? _messageSubscription;
    bool _isDisposed = false;
    String? _lastSentContent;

    Map<String, dynamic> objects = {
    "current": {"sys_name": "pecker"}
    };

    @override
    void initState() {
    super.initState();
    _initializeEditor();
    }

    void _initializeEditor() {
    final sysId = widget.column['sys_id'];

    _iframeElement = html.IFrameElement()
    ..src = 'monaco_editor.html?sys_id=$sysId'
    ..style.border = 'none'
    ..style.height = '100%'
    ..style.width = '100%';

    _messageSubscription = html.window.onMessage.listen(_handleMessage);

    _is_loading = false;

    if (mounted && !_isDisposed) {
    setState(() {
    print("Editor initialized with sys_id: $sysId");
    });
    }
    }

    void _handleMessage(html.MessageEvent event) {
    if (_isDisposed || !mounted) return;

    if (event.data['type'] == 'editorContent' && event.data['sys_id'] == widget.column['sys_id']) {
    final editorContent = event.data['content'] ?? "";
    print("Received editor content: $editorContent");
    if (editorContent != ref.read(form_page_provider).record[widget.column['sys_name']]) {
    print("Editor content changed, updating form");
    widget.on_changed(widget.column['sys_id'], editorContent);
    }
    } else if (event.data['type'] == 'editorReady' && event.data['sys_id'] == widget.column['sys_id']) {
    print("Editor ready");
    _editor_ready = true;
    _updateEditorContent();
    }
    }

    void _updateEditorContent() {
    if (_isDisposed || !mounted) return;

    try {
    final current = ref.read(form_page_provider).record;
    final currentText = current[widget.column['sys_name']] ?? '';

    if (_lastSentContent != currentText) {
    final message = {
    'type': 'updateContent',
    'content': currentText.toString(),
    'objects': jsonEncode(objects),
    'sys_id': widget.column['sys_id'],
    };

    print("Sending content to editor: $currentText");
    _iframeElement.contentWindow?.postMessage(message, '*');
    _lastSentContent = currentText;
    } else {
    print("Content unchanged, not sending update");
    }
    } catch (e) {
    print("Error updating editor content: $e");
    }
    }

    @override
    void didUpdateWidget(ScriptColumn oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_isDisposed || !mounted) return;

    if (oldWidget.column['sys_id'] != widget.column['sys_id'] || oldWidget.column['sys_name'] != widget.column['sys_name']) {
    _messageSubscription?.cancel();
    _initializeEditor();
    } else {
    _updateEditorContent();
    }
    }

    @override
    void dispose() {
    _isDisposed = true;
    _messageSubscription?.cancel();
    super.dispose();
    }

    @override
    Widget build(BuildContext context) {
    final currentContent = ref.watch(form_page_provider.select((data) => data.record[widget.column['sys_name']]));

    if (currentContent != _lastSentContent && _editor_ready) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
    _updateEditorContent();
    });
    }

    if (_is_loading) {
    return Center(child: CircularProgressIndicator());
    }

    final viewType = 'monaco-editor-container-${widget.column['sys_id']}';

    platformViewRegistry.registerViewFactory(
    viewType,
    (int viewId) => _iframeElement,
    );

    return HtmlElementView(viewType: viewType);
    }
    }


    code2::

    <!DOCTYPE html>
    <html>
    <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs/loader.min.js"></script>
    <style>
    html, body, #container {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    }
    </style>
    </head>
    <body>
    <div id="container"></div>
    <script>
    let editor;
    let receivedObjects = {};
    let isEditorBlurred = false;

    // Extract sys_id from the iframe URL
    const urlParams = new URLSearchParams(window.location.search);
    const sysId = urlParams.get('sys_id'); // Unique ID

    require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' }});
    require(['vs/editor/editor.main'], function() {
    editor = monaco.editor.create(document.getElementById('container'), {
    value: "// loading ...",
    language: 'javascript',
    theme: 'vs-light',
    automaticLayout: true
    });

    editor.onDidFocusEditorText(() => {
    isEditorBlurred = false;
    });

    editor.onDidBlurEditorText(() => {
    isEditorBlurred = true;
    sendEditorContent();
    });

    monaco.languages.registerCompletionItemProvider('javascript', {
    provideCompletionItems: function(model, position) {
    const textUntilPosition = model.getValueInRange({
    startLineNumber: 1,
    startColumn: 1,
    endLineNumber: position.lineNumber,
    endColumn: position.column
    });
    const match = textUntilPosition.match(/(\w+)$/);
    const wordToReplace = match ? match[1] : '';

    const customSuggestions = generateSuggestions(receivedObjects, '', wordToReplace);
    const builtInSuggestions = generateBuiltInSuggestions(wordToReplace);

    return {
    suggestions: [...customSuggestions, ...builtInSuggestions],
    range: {
    startLineNumber: position.lineNumber,
    endLineNumber: position.lineNumber,
    startColumn: position.column - wordToReplace.length,
    endColumn: position.column
    }
    };
    }
    });

    window.parent.postMessage({ type: 'editorReady', sys_id: sysId }, '*');
    });

    function sendEditorContent() {
    if (isEditorBlurred) {
    const content = editor.getValue();
    console.log("Sending editor content to parent:", content); // Log the content being sent
    window.parent.postMessage({
    type: 'editorContent',
    content: content,
    sys_id: sysId // Send sys_id with the content
    }, '*');
    }
    }

    function generateSuggestions(obj, prefix = '', wordToReplace = '') {
    return Object.entries(obj).flatMap(([key, value]) => {
    const fullKey = prefix ? `${prefix}.${key}` : key;
    const suggestions = [{
    label: fullKey,
    kind: monaco.languages.CompletionItemKind.Field,
    insertText: fullKey,
    detail: JSON.stringify(value)
    }];
    if (typeof value === 'object' && value !== null) {
    suggestions.push(...generateSuggestions(value, fullKey, wordToReplace));
    }
    return suggestions;
    });
    }

    function generateBuiltInSuggestions(wordToReplace) {
    const builtIns = [
    {
    label: 'JSON.stringify',
    kind: monaco.languages.CompletionItemKind.Function,
    insertText: 'JSON.stringify',
    detail: 'Convert a JavaScript object to a JSON string'
    },
    {
    label: 'JSON.parse',
    kind: monaco.languages.CompletionItemKind.Function,
    insertText: 'JSON.parse',
    detail: 'Parse a JSON string into a JavaScript object'
    }
    ];

    return builtIns.filter(item => item.label.toLowerCase().startsWith(wordToReplace.toLowerCase()));
    }

    window.addEventListener('message', function(event) {
    if (event.data && event.data.type === 'updateContent' && event.data.sys_id === sysId) {
    console.log("Received updateContent message with event:", event);
    console.log("Received updateContent message with data:", event.data);
    console.log("Received updateContent message with content:", event.data.content);
    editor.setValue(event.data.content);
    if (event.data.objects) {
    receivedObjects = JSON.parse(event.data.objects);
    }
    }
    }, false);
    </script>
    </body>
    </html>


    output:::

    Restarted application in 75ms.
    The webOnlySetPluginHandler API is deprecated and will be removed in a future release. Please use `setPluginHandler` from `dart:ui_web` instead.
    The debugEmulateFlutterTesterEnvironment getter is deprecated and will be removed in a future release. Please use `debugEmulateFlutterTesterEnvironment` from `dart:ui_web` instead.

    Waiting for JS to complete...
    _initialize_data()

    Waiting for JS to complete...

    Waiting for JS to complete...
    Waiting for JS to complete...
    Waiting for JS to complete...
    Editor initialized with sys_id: a290ba63-8447-421a-a8e5-a2ba694bba7d_1729022184297
    Editor ready
    Sending content to editor:
    Editor ready
    Sending content to editor: function onLoad() {
    if( f.get('sys_id') != null ){return;}
    f.set( "sys_table_name" , f.getTable()['sys_name'] );
    f.set( "sys_active" , true );
    f.set( "sys_application_id" , a.getApp()['sys_id'] );
    }

    Received updateContent message with event:
    Received updateContent message with data:
    Received updateContent message with content:

    Waiting for JS to complete...
    _initialize_data()

    Waiting for JS to complete...

    Waiting for JS to complete...
    Waiting for JS to complete...
    Editor initialized with sys_id: a290ba63-8447-421a-a8e5-a2ba694bba7d_1729022184297
    Editor ready
    Content unchanged, not sending update
    Editor ready
    Sending content to editor: function onLoad() {
    if( f.get('sys_id') != null ){return;}
    f.set( "sys_table_name" , f.getTable()['sys_name'] );
    f.set( "sys_active" , true );
    f.set( "sys_application_id" , a.getApp()['sys_id'] );
    }

    Continue reading...

Compartilhe esta Página