diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2018-03-19 20:42:17 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2018-03-19 22:31:02 -0400 |
commit | 0c3396bd4761d833e5cec05070544e03f91d1f2e (patch) | |
tree | 75fdf781626f88c2db973c31c31e1c8d2d017f33 /public-src | |
parent | 66615bb2095df3dda23ff393f6bb8bd9dd50a766 (diff) |
wip vuevue
Diffstat (limited to 'public-src')
-rw-r--r-- | public-src/golden-layout-vue.jsm | 163 | ||||
-rw-r--r-- | public-src/jarmon.vue | 47 |
2 files changed, 210 insertions, 0 deletions
diff --git a/public-src/golden-layout-vue.jsm b/public-src/golden-layout-vue.jsm new file mode 100644 index 0000000..92c36fb --- /dev/null +++ b/public-src/golden-layout-vue.jsm @@ -0,0 +1,163 @@ +import Vue from 'vue'; +import LayoutManager from 'golden-layout'; + +/** + * `this` will be set to the VueComponentHandler instance. + */ +function onOpen() { + /* mount the Vue instance *inside* of _container.getElement(). + * To do that we need to create a temporary element inside of + * it for the Vue instance to replace. */ + const tmp = document.createElement('div'); + this._container.getElement()[0].appendChild(tmp); + this._vueInstance.$mount(tmp); +} + +/** + * `this` will be set to the VueComponentHandler instance. + */ +function onDestroy() { + this._vueInstance.$destroy(); + this._container.off('open', onOpen, this); + this._container.off('destroy', onDestroy, this); +} + +function deepCopySanitize(obj) { + if (obj === undefined) + return undefined; + return JSON.parse(JSON.stringify(obj)); +} + +/** + * A specialised GoldenLayout component that binds GoldenLayout container + * lifecycle events to Vue components + */ +class VueComponentHandler { + /** + * @param {Function} the Vue component constructor/class that we're wrapping + * @param {lm.container.ItemContainer} container as passed by GoldenLayout + * @param {Object} state as passed by GoldenLayout + */ + constructor(vueComponentClass, container, state) { + this._container = container; + /* Get the state directly from the container, rather + * than trusting the `state` argument; `state` has + * `config.componentName` injected in to it, poluting + * our Vue instance's `$data`. + * + * But, the `state` argument was a deep copy of the + * real state; we still need it to be a copy, so that + * data-propagation doesn't bypass our $watch. + * + * See golden-layout/src/js/items/Component.js:lm.items.Component(). + */ + const realState = deepCopySanitize(container.getState()); + + var options = {}; + Object.assign(options, container.layoutManager._vueOptions); + if (container._config.vueOptions) { + Object.assign(options, container._config.vueOptions); + } + if (realState) { + options.data = Object.assign(options.data || {}, realState); + } + this._vueInstance = new vueComponentClass(options); + + // add Vue -> GoldenLayout event translator + this._vueInstance.$watch('$data', (newData, oldData) => { + this._container.setState(deepCopySanitize(newData)); + }, { deep: true }); + + // add GoldenLayout -> Vue event translators + this._container.on('open', onOpen, this); + this._container.on('destroy', onDestroy, this); + } +} + +LayoutManager.prototype._vueInit = function() { + if (!this._vueInitialized) { + this._components["lm-vue-component"] = VueComponentHandler; + this._vueOptions = {}; + this._vueInitialized = true; + } +}; + +/** + * Register a Vue component for use with this GoldenLayout + * LayoutManager. + * + * The component can then be used just like any other GoldenLayout + * component. + * + * - The `componentState` config item is bound with the Vue + * component's `$data`; the component's private data will be + * reflected in `layoutManager.toConfig()`, allowing the component + * state to be persisted as part of the layoutManager state. + * + * - The `vueOptions` config item becomes the Vue component's options. + * This is merged with any global Vue options set with + * `.setVueOptions()`, and obviously `componentState` becomes + * `vueOptions.data`. This MUST be a "plain" JSON-encodable object, + * and cannot contain complex objects like a Vuex.Store. + * + * @public + * @param {String} name Like `Vue.component()` registration, the + * `name` argument is optional if the component + * itself has a default name. + * @param {Function|Object} component Like `Vue.component()` + * registration, this can be a + * constructor function, or an + * options object (in which case it + * will be automatically turned in + * to a constructor function by + * passing it to `Vue.extend`). + * @returns {void} + */ +LayoutManager.prototype.registerVueComponent = function(name, component) { + this._vueInit(); + + if (component === undefined) { + component = name; + name = component.name; + } + + if (!name) { + throw new Error('Cannot register a component without a name'); + } + + if (!component.name) { + component.name = name; + } + + if (typeof component !== "function") { + component = Vue.extend(component); + } + + class ThisVueComponentHandler extends VueComponentHandler { + constructor(container, state) { + super(component, container, state); + } + } + + this.registerComponent(name, ThisVueComponentHandler); +}; + +/** + * Set the Vue options. + * + * You may be wondering why this is a separate function, instead of + * simply relying on the `vueOptions` field to the settings object. + * The settings object *must* be a "plain" JSON-type object; it can't + * include complex objects, like a Vuex.Store (it would actually stack + * overflow if we tried putting a Vuex.Store in the settings object). + * + * @public + * @param {Object} options + * + * @returns {void} + */ +LayoutManager.prototype.setVueOptions = function(options) { + this._vueInit(); + + this._vueOptions = options; +}; diff --git a/public-src/jarmon.vue b/public-src/jarmon.vue new file mode 100644 index 0000000..11ac223 --- /dev/null +++ b/public-src/jarmon.vue @@ -0,0 +1,47 @@ +<template> + <div v-bind:class="['jarmon', name]"> + <div class="chart-container" ref="chartContainer"> + <h2 class="title"></h2> + <form> + <input name="chart_edit" value="Edit" type="button" /> + <input name="chart_delete" value="Delete" type="button" /> + </form> + <div class="error"></div> + <div class="chart"></div> + <div class="graph-legend"></div> + </div> + <div class="chartRangeControl" ref="chartRangeControl"> + <form> + <div class="range-inputs"> + <input name="from" type="datetime-local" step="1" /> + <input name="to" type="datetime-local" step="1" /> + <select name="shortcuts" title="Time range shortcuts - click to select an alternative time range" ></select> + <select name="tzoffset" title="Timezone offset - click to choose a custom timezone offset" ></select> + <input name="action" value="Update" type="button" + title="Graph update - click to update all graphs" /> + </div> + <div class="range-preview" + title="Time range preview - click and drag to select a custom timerange" ></div> + </form> + </div> + <div class="tabbed-chart-interface" ref="tabbedChartInterface"></div> + </div> +</template> +<script> +import $ from 'jquery'; +import jarmon from 'jarmon'; + +export default { + mounted: function() { + jarmon.buildTabbedChartUi( + $(this.refs.chartcontainer).remove(), + chartRecipes, + $(this.refs.tabbedChartInterface), + tabRecipes, + $(this.refs.chartRangeControl)); + } + props: [ + 'name', + ], +}; +</script> |