From 0c3396bd4761d833e5cec05070544e03f91d1f2e Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Mon, 19 Mar 2018 20:42:17 -0400 Subject: wip vue --- .dir-locals.el | 32 ++++++++ .editorconfig | 3 + public-src/golden-layout-vue.jsm | 163 +++++++++++++++++++++++++++++++++++++++ public-src/jarmon.vue | 47 +++++++++++ 4 files changed, 245 insertions(+) create mode 100644 .dir-locals.el create mode 100644 public-src/golden-layout-vue.jsm create mode 100644 public-src/jarmon.vue diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..89ba72e --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,32 @@ +((web-mode . ((eval . (auto-fill-mode -1)) + ;; editorconfig-mode sets web-mode-attr-indent-offset, + ;; which disables attribute alignment (different than + ;; editorconfig-mode's behavior with sgml-mode); unset + ;; it. + (web-mode-attr-indent-offset . nil) + ;; for consistency with vue-mode, and for compatibility + ;; with eslint's "indent" rule (since + ;; eslint-plugin-vue's "vue/script-indent" rule is + ;; crap). + (web-mode-script-padding . 0) + ;; turn off case-extra-offset, for consistency with js-mode + (web-mode-indentation-params . (("lineup-args" . t) + ("lineup-calls" . t) + ("lineup-concats" . t) + ("lineup-quotes" . t) + ("lineup-ternary" . t) + ("case-extra-offset" . nil))))) + (vue-mode . ((eval . (auto-fill-mode -1)) + ;; editorconfig-mode doesn't correctly set + ;; js-indent-level, so we need to set it to match + ;; tab-width. + ;; + ;; Unfortunately, there's no good way to say "set one + ;; variable to match another variable". We'd like to + ;; set the js/css/sgml variables to all match whatever + ;; tab-width is set to, but we can't do that. So, we + ;; set them all to the sane value of "8". + (tab-width . 8) + (js-indent-level . 8) + (css-indent-offset . 8) + (sgml-basic-offset . 8)))) diff --git a/.editorconfig b/.editorconfig index 71581bf..aed72d1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,9 @@ insert_final_newline = true charset = utf-8 indent_style = tab +[*.el] +indent_style = space + [package.json] # This matches what `yarn` writes indent_style = space 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 @@ + + -- cgit v1.2.3-54-g00ecf