summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el32
-rw-r--r--.editorconfig3
-rw-r--r--public-src/golden-layout-vue.jsm163
-rw-r--r--public-src/jarmon.vue47
4 files changed, 245 insertions, 0 deletions
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 @@
+<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>