seung
seung

Reputation: 1

[Quasar v2][vuejs3] use other component in the golden layout

I am currently using quasar v2(vuejs3) and the gold-layout 2.5.0 version.
(I also tried vue-golden-layout, but I gave up because it was difficult to use in quasar v2)

project structure

components
- TestCom.vue : simple component with only div and h2
- WebEditor.vue : part where the monaco editor is defined, and it works normally when the golden layout is not used.

layouts
- GoldenLayout.vue : The page where I want to use the Golden-layout package.

App.vue

I imported other components in Golden Layout.vue and registered in Golden-layout. There is no error message when running, but the contents of the components are not visible.

Is there a right way to register the component in Golden layout?

layouts/GoldenLayout.vue

<template>
    <div>
        <link type="text/css" rel="stylesheet" href="//golden-layout.com/assets/css/goldenlayout-base.css" />
        <link type="text/css" rel="stylesheet" href="//golden-layout.com/assets/css/goldenlayout-light-theme.css" />

        <div ref ="test"></div>
    </div>
</template>

<script>
import {shallowRef , ref, onMounted, onUnmounted, h } from "vue";
import { GoldenLayout } from "golden-layout/src/index";
import WebEditor from "components/WebEditor.vue";
import TestCom from "components/TestCom.vue"
export default {
    name: 'App',

    components: {
    },

    setup(props) {
        let c = () => h(WebEditor, {
            code : "import java.util.*;\nimport java.io.*;\n\npublic class Main{\n    public static void main(String[] args) throws IOException {\n        BufferedReader re = new BufferedReader(new InputStreamReader(System.in));\n       \n        int a = Integer.parseInt(re.readLine());\n        int b = Integer.parseInt(re.readLine());\n\n        System.out.println(a+b);\n        re.close();\n    }\n}",
            language : "java",
            readonly : false
        });
        let d = () => h(TestCom);

        const test = ref(undefined);
        let goldenLayout;
        const config = {
            content:
            [
                { 
                    type: 'row',
                    content:
                    [
                        {
                            type: 'component',
                            componentName: 'WebEditor',
                            componentType : 'WebEditor'
                        },
                        {
                            type: 'component',
                            componentName: 'TestCom',
                            componentType : 'TestCom'
                        }      
                    ]          
                }
            ]
        }

        onMounted(() => {
            goldenLayout = new GoldenLayout(test);
            
            goldenLayout.registerComponent('WebEditor', c);
            goldenLayout.registerComponent('TestCom', d);

            goldenLayout.init();
            goldenLayout.loadLayout(config);
            
        });

        onUnmounted(() => {
            goldenLayout.destroy();
        });

        return {
            test
        };
    }
};
</script>

<style scoped>
</style>

components/TestCom.vue

<template>
    <div>
        <h2 style="height:200px">Test</h2>
    </div>
</template>

<script>
export default {
    setup () {
        

        return {}
    }
}
</script>

<style lang="scss" scoped>

</style>

componets/WebEditor.vue

<template>
    <div>
        <div ref="editorDiv" style="height: 100%; width:100%"></div>
        <div><h2 @click="updateEditor">refresh</h2></div>
    </div>
</template>

<script>

// package.json
// "monaco-editor": "^0.33.0",
// "monaco-editor-webpack-plugin": "^7.0.1",

// quasar.confg
// const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
// module.exports = configure(function (/* ctx */) {
//   return {
//     plugins: [new MonacoWebpackPlugin()],

import { ref, onMounted } from "vue";

import * as monaco from 'monaco-editor';
export default {
    // example
    // 
    // <web-editor code="import java.util.*" language="java" :readOnly="false"></web-editor>
    name : 'WebEditor',
    props :{
        code : String, // "import java.util.*;\nimport java.io.*;\n\npublic class Main{\n    public static void main(String[] args) throws IOException {\n        BufferedReader re = new BufferedReader(new InputStreamReader(System.in));\n       \n        int a = Integer.parseInt(re.readLine());\n        int b = Integer.parseInt(re.readLine());\n\n        System.out.println(a+b);\n        re.close();\n    }\n}"
        language : String, // "java", "c", "python"
        readOnly : Boolean, // "false"
    },
    setup (props) {
        const editorDiv = ref(undefined);
        let monacoEditor;

        let editorCode = JSON.parse(JSON.stringify(props.code));  
        let editorLanguage = JSON.parse(JSON.stringify(props.language));
        let editorReadOnly = JSON.parse(JSON.stringify(props.readOnly));

        onMounted(() => {
            monacoEditor = monaco.editor.create(editorDiv.value,{
                    // model: null,
                    readOnly: editorReadOnly,
                    value: editorCode,
                    language: editorLanguage,
                    // theme: 'vs', //light version
                    theme: 'vs-dark',
                    tabSize: 2,
                    fontFamily: "Consolas",
                    // fontFamily: 'D2Coding',
                    // fontFamily: 'IBM Plex Mono',
                    fontSize: 12,
                });
        });
        
        const updateEditor = () => {
            editorCode = monacoEditor.getValue();
            monacoEditor.dispose();
    
            monacoEditor = monaco.editor.create(editorDiv.value,{
                    // model: null,
                    readOnly: editorReadOnly,
                    value: editorCode,
                    // c,cpp,java,javascript,python
                    language: editorLanguage,
                    // theme: 'vs', //light version
                    theme: 'vs-dark',
                    tabSize: 2,
                    fontFamily: "Consolas",
                    fontSize: 12,
                });
        };

        return {
            editorDiv,
            monacoEditor,
            editorCode,
            editorLanguage,
            editorReadOnly,
            
            updateEditor
        };
    }
}
</script>

<style lang="scss" scoped>

</style>

This is my first time asking a question in stackoverflow. If you feel that I made a mistake or lack explanation for the problem, please let me know right away.

Upvotes: 0

Views: 620

Answers (1)

hyperupcall
hyperupcall

Reputation: 973

Unfortunately, the official documentation points to this code, which uses "Golden Layout’s virtual component (virtual via events binding)", but I wasn't able to understand it correctly.

To solve this myself, I created a GoldenLayoutVue.vue component that takes care of the lifecycle of GoldenLayouts:

It's not the recommended solution, so it's a hack and it might funny with persistent state (although as far as I can tell, it works fine).

<template>
    <div ref="goldenLayoutEl" style="width: 100%; height: 100%"></div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref, useSlots } from 'vue'
import { GoldenLayout, LayoutConfig } from 'golden-layout'
import 'golden-layout/dist/css/goldenlayout-base.css'
import 'golden-layout/dist/css/themes/goldenlayout-light-theme.css'
import { createVNode, render } from 'vue'

// Vue 2 can mount through a built-in function, but since Vue 3,
// that has been removed. I used the 'mount' function from here:
// https://github.com/pearofducks/mount-vue-component/blob/master/index.js
export function vueMount(component, { props, children, element, app } = {}) {
  let el = element

  let vNode = createVNode(component, props, children)
  if (app && app._context) vNode.appContext = app._context
  if (el) render(vNode, el)
  else if (typeof document !== 'undefined' ) render(vNode, el = document.createElement('div'))

  const destroy = () => {
    if (el) render(null, el)
    el = null
    vNode = null
  }

  return { vNode, destroy, el }
}

const { layoutConfig } = defineProps<{
    layoutConfig: LayoutConfig
}>()
const slots = useSlots()

let goldenLayoutEl = ref<null>(null)
let goldenLayout: GoldenLayout | null = null
let destroyArr: (() => void)[] = []

onMounted(async () => {
    if (!goldenLayoutEl.value) throw new Error('goldenLayoutEl not truthy')

    goldenLayout = new GoldenLayout(goldenLayoutEl.value)

    // iterate over all the slots, and mount the component to the
    // DOM element that GoldenLayout automatically creates for us
    for (const [name, component] of Object.entries(slots)) {
        goldenLayout.registerComponentFactoryFunction(name, (container, state) => {
            const { vNode, destroy, el } = vueMount(component, {
                element: container.element,
            })
            destroyArr.push(destroy)
        })
    }

    goldenLayout.loadLayout(layoutConfig)
})
onUnmounted(() => {
    for (const fn of destroyArr) {
        fn()
    }
})

// Resize
const _onResize = () => {
    if (goldenLayoutEl.value && goldenLayout) {
        const rect = goldenLayoutEl.value.getBoundingClientRect()
        goldenLayout.setSize(rect.width, rect.height)
    }
}
onMounted(() => {
    _onResize()
    window.addEventListener('resize', _onResize)
})
onUnmounted(() => {
    window.removeEventListener('resize', _onResize)
})
</script>

Use the component like so:

<template>
    <GoldenLayoutVue :layoutConfig="goldenLayoutConfig">
        <template #ComponentA>
            <h1>Left</h1>
        </template>
        <template #ComponentB>
            <h1>Right</h1>
        </template>
    </GoldenLayoutVue>
</template>

<script setup>
import GoldenLayoutVue from './GoldenLayoutVue.vue'

const goldenLayoutConfig = {
    root: {
        type: 'row',
        content: [
            {
                title: 'My Component 1',
                type: 'component',
                componentType: 'ComponentA',
                componentState: { text: 'Component 1' },
                size: '50%',
            },
            {
                title: 'My Component 2',
                type: 'component',
                componentType: 'ComponentB',
                componentState: { text: 'Component 2' },
            },
        ],
    },
}

</script>

Notice how both the slot name (<template #ComponentA>) and the property in the layoutConfig (componentType: 'ComponentA') match exactly.

Upvotes: 0

Related Questions