glib-web 0.11.21 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/action.js CHANGED
@@ -4,6 +4,7 @@ import ActionsRunMultiple from "./actions/runMultiple";
4
4
 
5
5
  import ActionsFormsSubmitV1 from "./actions/forms/submit";
6
6
 
7
+ import ActionsHttpGetV1 from "./actions/http/get";
7
8
  import ActionsHttpPostV1 from "./actions/http/post";
8
9
  import ActionsHttpPatchV1 from "./actions/http/patch";
9
10
  import ActionsHttpDeleteV1 from "./actions/http/delete";
@@ -49,11 +50,16 @@ import ActionsAnalyticsLogEvent from "./actions/analytics/logEvent";
49
50
 
50
51
  import ActionCommandsCopy from "./actions/commands/copy";
51
52
 
53
+ import ActionToursStart from "./actions/tours/start";
54
+
55
+ import ActionComponentsUpdate from "./actions/components/update";
56
+
52
57
  const actions = {
53
58
  runMultiple: ActionsRunMultiple,
54
59
 
55
60
  "forms/submit": ActionsFormsSubmitV1,
56
61
 
62
+ "http/get": ActionsHttpGetV1,
57
63
  "http/post": ActionsHttpPostV1,
58
64
  "http/patch": ActionsHttpPatchV1,
59
65
  "http/delete": ActionsHttpDeleteV1,
@@ -96,7 +102,9 @@ const actions = {
96
102
  "auth/creditCard": ActionsCreditCard,
97
103
 
98
104
  "analytics/logEvent": ActionsAnalyticsLogEvent,
99
- "commands/copy": ActionCommandsCopy
105
+ "commands/copy": ActionCommandsCopy,
106
+ "tours/start": ActionToursStart,
107
+ "components/update": ActionComponentsUpdate
100
108
  };
101
109
 
102
110
  export default class Action {
@@ -159,6 +167,7 @@ export default class Action {
159
167
  }
160
168
 
161
169
  static handleResponse(response, component) {
170
+ // TODO: The execution should be in the following order: onResponse, page render, onLoad
162
171
  if (response.header || response.body || response.footer) {
163
172
  Utils.http.forceComponentUpdate(() => {
164
173
  window.vueApp.page = response;
@@ -1,11 +1,11 @@
1
1
  export default class {
2
- execute(properties) {
3
- let spec = Object.assign({}, properties, {
4
- message: "Copied",
5
- styleClasses: ["success"]
6
- });
2
+ execute(properties, component) {
3
+ // let spec = Object.assign({}, properties, {
4
+ // message: "Copied",
5
+ // styleClasses: ["success"]
6
+ // });
7
7
  navigator.clipboard.writeText(properties.text).then(
8
- () => Utils.launch.snackbar.open(spec),
8
+ () => GLib.action.execute(properties.onCopy, component),
9
9
  reason => console.error("Could not copy text: " + reason)
10
10
  );
11
11
  }
@@ -0,0 +1,11 @@
1
+ export default class {
2
+ execute(spec, component) {
3
+ if (!spec.views.length > 1) {
4
+ console.warn("Make sure views only have 1 child!");
5
+ }
6
+ if (!component.updatables[spec.targetId])
7
+ console.error("no component found :" + spec.targetId);
8
+
9
+ Object.assign(component.updatables[spec.targetId].spec, spec.views[0]);
10
+ }
11
+ }
@@ -0,0 +1,7 @@
1
+ export default class {
2
+ execute(properties, controller) {
3
+ GLib.http.execute(properties, "GET", controller, response =>
4
+ GLib.action.handleResponse(response, controller)
5
+ );
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ import Driver from "driver.js";
2
+ import "driver.js/dist/driver.min.css";
3
+
4
+ export default class {
5
+ execute(spec, component) {
6
+ let driver = new Driver(spec.options || {});
7
+ let steps = spec.steps;
8
+ steps
9
+ .filter(value => value.customBehavior)
10
+ .forEach(step => {
11
+ const element = document.querySelector(step.element);
12
+ component.driverCustomBehavior[step.customBehavior](element, driver);
13
+ });
14
+ driver.defineSteps(steps);
15
+ driver.start(spec.startFrom || 0);
16
+ }
17
+ }
package/app.vue CHANGED
@@ -42,14 +42,13 @@ import Utils from "./utils/helper";
42
42
  import ContentLayout from "./nav/content";
43
43
  import phoenixSocketMixin from "./components/mixins/ws/phoenixSocket.js";
44
44
  import actionCableMixin from "./components/mixins/ws/actionCable.js";
45
- import tourMixin from "./components/mixins/tour";
46
45
 
47
46
  export default {
48
47
  components: {
49
48
  "nav-appbar": NavAppBar,
50
49
  "layouts-content": ContentLayout
51
50
  },
52
- mixins: [phoenixSocketMixin, actionCableMixin, tourMixin],
51
+ mixins: [phoenixSocketMixin, actionCableMixin],
53
52
  props: {
54
53
  page: { type: Object, required: true }
55
54
  },
@@ -111,7 +110,6 @@ export default {
111
110
  setTimeout(() => {
112
111
  // Wait until page is rendered
113
112
  GLib.action.execute(this.page.onLoad, this);
114
- this.startTour();
115
113
  });
116
114
  }
117
115
  }
@@ -9,7 +9,7 @@
9
9
  >
10
10
  <v-row no-gutters class="full-height">
11
11
  <template v-for="(item, index) in spec.childViews">
12
- <ui-component
12
+ <glib-component
13
13
  v-if="isColumn(item)"
14
14
  :key="viewKey(item, index)"
15
15
  :spec="item"
@@ -20,7 +20,7 @@
20
20
  class="full-width"
21
21
  :style="innerStyles"
22
22
  >
23
- <ui-component :spec="item" />
23
+ <glib-component :spec="item" />
24
24
  </div>
25
25
  </template>
26
26
  </v-row>
@@ -19,10 +19,9 @@
19
19
 
20
20
  <script>
21
21
  import tooltipMixin from "./mixins/tooltip";
22
- import tourMixin from "./mixins/tour";
23
22
 
24
23
  export default {
25
- mixins: [tooltipMixin, tourMixin],
24
+ mixins: [tooltipMixin],
26
25
  props: {
27
26
  spec: { type: Object, required: true }
28
27
  },
@@ -33,7 +32,6 @@ export default {
33
32
  },
34
33
  methods: {
35
34
  $ready() {
36
- this.registerTour();
37
35
  this.tooltip = this.spec.tooltip || { disabled: true };
38
36
  }
39
37
  }
@@ -26,7 +26,7 @@
26
26
 
27
27
  <!-- <panels-responsive v-else-if="spec.view == 'panels/scroll-v1'" :spec="spec" /> -->
28
28
 
29
- <component :is="name" v-else-if="name" :spec="spec" />
29
+ <component :is="name" v-else-if="name" :id="spec.id" :spec="spec" />
30
30
 
31
31
  <div v-else>Unsupported view: {{ spec.view }}</div>
32
32
  </template>
@@ -1,6 +1,5 @@
1
1
  <template>
2
2
  <div :class="$classes()" :style="$styles()">
3
- <!-- <fields-hidden v-if="uncheckSpec" :spec="uncheckSpec" /> -->
4
3
  <!-- This hidden field should always be there to make sure the submitted param is not empty,
5
4
  which could cause "Not accessible" error on the server. -->
6
5
  <input type="hidden" :name="fieldName" :value="spec.uncheckValue" />
@@ -18,12 +17,7 @@
18
17
  </template>
19
18
 
20
19
  <script>
21
- // import HiddenField from "./hidden";
22
-
23
20
  export default {
24
- // components: {
25
- // "fields-hidden": HiddenField
26
- // },
27
21
  props: {
28
22
  spec: { type: Object, required: true }
29
23
  },
@@ -31,7 +25,6 @@ export default {
31
25
  return {
32
26
  groupName: null,
33
27
  fieldType: null
34
- // uncheckSpec: null
35
28
  };
36
29
  },
37
30
  methods: {
@@ -55,10 +48,6 @@ export default {
55
48
  this.fieldType = "switch";
56
49
  }
57
50
  });
58
-
59
- // Utils.type.ifNotNull(this.spec.uncheckValue, value => {
60
- // this.uncheckSpec = { name: this.spec.name, value: value };
61
- // });
62
51
  },
63
52
  changed(event) {
64
53
  // Execute later to ensure the checkbox's checked state has been updated.
@@ -1,35 +1,60 @@
1
1
  <template>
2
- <div ref="checkGroup" data-component="checkGroup" :name="spec.name">
3
- <fields-hidden v-if="!anyChecked" :spec="uncheckSpec" />
2
+ <div
3
+ ref="checkGroup"
4
+ class="checkgroup"
5
+ data-component="checkGroup"
6
+ :name="spec.name"
7
+ >
8
+ <!-- <fields-hidden v-if="!anyChecked" :spec="uncheckSpec" /> -->
4
9
 
5
10
  <div v-for="(item, index) in spec.childViews" :key="index">
6
- <ui-component :spec="item" />
11
+ <glib-component :spec="item" />
7
12
  </div>
13
+
14
+ <v-text-field
15
+ class="checkgroup__uncheckvalue"
16
+ :disabled="anyChecked"
17
+ :name="spec.name"
18
+ :value="spec.uncheckValue"
19
+ :rules="rules"
20
+ />
8
21
  </div>
9
22
  </template>
10
23
 
11
24
  <script>
12
- import HiddenField from "./hidden";
25
+ // import HiddenField from "./hidden";
13
26
 
14
27
  export default {
15
- components: {
16
- "fields-hidden": HiddenField
17
- },
28
+ // components: {
29
+ // "fields-hidden": HiddenField
30
+ // },
18
31
  props: {
19
32
  spec: { type: Object, required: true }
20
33
  },
21
34
  data() {
22
35
  return {
23
- anyChecked: false
36
+ anyChecked: false,
37
+ errorMessage: null,
38
+ rules: [
39
+ _v => {
40
+ return this.anyChecked || this.errorMessage || true;
41
+ }
42
+ ]
24
43
  };
25
44
  },
26
- computed: {
27
- uncheckSpec() {
28
- return { name: this.spec.name, value: this.spec.uncheckValue };
29
- }
30
- },
45
+ // computed: {
46
+ // uncheckSpec() {
47
+ // return { name: this.spec.name, value: this.spec.uncheckValue };
48
+ // }
49
+ // },
31
50
  methods: {
32
51
  $ready() {
52
+ const validation = this.spec.validation || {};
53
+
54
+ Utils.type.ifObject(validation.required, required => {
55
+ this.errorMessage = required.message;
56
+ });
57
+
33
58
  this.detectChecked();
34
59
  const vm = this;
35
60
  this.$el.addEventListener(
@@ -48,6 +73,7 @@ export default {
48
73
  .forEach(function(checkbox) {
49
74
  if (checkbox.checked) {
50
75
  vm.anyChecked = true;
76
+ return;
51
77
  }
52
78
  });
53
79
  }
@@ -55,4 +81,20 @@ export default {
55
81
  };
56
82
  </script>
57
83
 
58
- <style scoped></style>
84
+ <style lang="scss">
85
+ // Hide the uncheck field, but continue to show the validation div.
86
+ .checkgroup__uncheckvalue {
87
+ padding-top: 0px;
88
+
89
+ .v-input__slot {
90
+ display: none;
91
+ }
92
+
93
+ // Hide the entire component unless there is a validation message.
94
+ display: none;
95
+
96
+ &.error--text {
97
+ display: block;
98
+ }
99
+ }
100
+ </style>
@@ -50,17 +50,17 @@
50
50
  @dragend="onDrag"
51
51
  />
52
52
  </gmap-map>
53
- <ui-component
53
+ <glib-component
54
54
  v-if="latitudeField"
55
55
  ref="latitudeView"
56
56
  :spec="latitudeField"
57
57
  />
58
- <ui-component
58
+ <glib-component
59
59
  v-if="longitudeField"
60
60
  ref="longitudeView"
61
61
  :spec="longitudeField"
62
62
  />
63
- <ui-component v-if="zoomField" ref="zoomView" :spec="zoomField" />
63
+ <glib-component v-if="zoomField" ref="zoomView" :spec="zoomField" />
64
64
  </v-container>
65
65
  </template>
66
66
 
@@ -12,8 +12,8 @@
12
12
  <input
13
13
  v-for="(imageKey, index) in imageKeys"
14
14
  :key="index"
15
- type="hidden"
16
- name="imageUrls[]"
15
+ type="text"
16
+ :name="imageUploader.name"
17
17
  :value="images[imageKey]"
18
18
  />
19
19
 
@@ -52,7 +52,8 @@ export default {
52
52
  cleanValue: "",
53
53
  images: {},
54
54
  imageKeys: [],
55
- progress: { value: -1 }
55
+ progress: { value: -1 },
56
+ imageUploader: {}
56
57
  }),
57
58
  // mounted: function () {
58
59
  // const vm = this
@@ -92,10 +93,12 @@ export default {
92
93
  return "{{IMAGE_NOT_FOUND}}";
93
94
  }
94
95
  );
96
+
97
+ this.imageUploader = this.spec.imageUploader;
95
98
  },
96
99
  uploadImage: function(file, editor, cursorLocation) {
97
100
  let vm = this;
98
- const uploaderSpec = this.spec.imageUploader;
101
+ const uploaderSpec = this.imageUploader;
99
102
  // const input = this.$refs.directUploadFile
100
103
  const upload = new Uploader(
101
104
  file,
@@ -34,9 +34,9 @@ export default {
34
34
  }
35
35
  // Set all links to be openWeb or open in a new tab by default
36
36
  if (!this.spec.openWeb) {
37
- let anchors = document.querySelectorAll('a');
37
+ let anchors = document.querySelectorAll("a");
38
38
  for (let i = 0; i < anchors.length; i++) {
39
- anchors[i].setAttribute('target', '_blank')
39
+ anchors[i].setAttribute("target", "_blank");
40
40
  }
41
41
  }
42
42
  },
@@ -56,4 +56,10 @@ export default {
56
56
  };
57
57
  </script>
58
58
 
59
- <style scoped></style>
59
+ <style lang="scss">
60
+ .views-markdown {
61
+ & > span > :last-child {
62
+ margin-bottom: 0;
63
+ }
64
+ }
65
+ </style>
@@ -1,5 +1,6 @@
1
1
  import Hash from "../../utils/hash";
2
2
  import Vue from "vue";
3
+ import launch from "../../utils/launch";
3
4
 
4
5
  var jsonLogic = require("json-logic-js");
5
6
 
@@ -46,19 +47,7 @@ export default {
46
47
  this.$internalizeValue(val)
47
48
  );
48
49
 
49
- const dirtyCheckEnabled = !this.spec.disableDirtyCheck;
50
- // Make sure value has changed and make sure that it is different from the original value.
51
- // Be strict with this so it doesn't execute when the component is just initializing (e.g value changing
52
- // from `null` to `this.spec.value`).
53
- if (
54
- dirtyCheckEnabled &&
55
- !window.vueApp.isFormDirty &&
56
- val != oldVal &&
57
- val != this.spec.value
58
- ) {
59
- console.log("Form is now dirty");
60
- window.vueApp.isFormDirty = true;
61
- }
50
+ this._checkDirtyState(val, oldVal);
62
51
  }
63
52
  },
64
53
  methods: {
@@ -66,6 +55,26 @@ export default {
66
55
  genericStyles(spec) {
67
56
  return this.$styles(spec);
68
57
  },
58
+ _checkDirtyState(val, oldVal) {
59
+ const dirtyCheckEnabled = !this.spec.disableDirtyCheck;
60
+ // Make sure value has changed and make sure that it is different from the original value.
61
+ // Be strict with this so it doesn't execute when the component is just initializing (e.g value changing
62
+ // from `null` to `this.spec.value`).
63
+ if (dirtyCheckEnabled && val != oldVal && val != this.spec.value) {
64
+ const topDialog = Utils.launch.dialog.topDialog();
65
+ if (topDialog) {
66
+ if (!topDialog.isFormDirty) {
67
+ console.log("Dialog form is now dirty");
68
+ topDialog.isFormDirty = true;
69
+ }
70
+ } else {
71
+ if (!window.vueApp.isFormDirty) {
72
+ console.log("Window form is now dirty");
73
+ window.vueApp.isFormDirty = true;
74
+ }
75
+ }
76
+ }
77
+ },
69
78
  // NOTE: Styles are dynamic, do not save it in $ready().
70
79
  $styles(spec) {
71
80
  const properties = spec || this.spec;
@@ -7,7 +7,7 @@
7
7
  :show-arrows-on-hover="$classes().includes('show-arrows-on-hover')"
8
8
  >
9
9
  <v-carousel-item v-for="(item, i) in spec.childViews" :key="i">
10
- <ui-component :spec="item" />
10
+ <glib-component :spec="item" />
11
11
  </v-carousel-item>
12
12
  </v-carousel>
13
13
  </template>
@@ -6,7 +6,7 @@
6
6
  remaining space on the new line.
7
7
  -->
8
8
  <div :key="index" :style="innerStyles">
9
- <ui-component :spec="item" />
9
+ <glib-component :spec="item" />
10
10
  </div>
11
11
  </template>
12
12
  </div>
@@ -2,7 +2,7 @@
2
2
  <div :class="$classes()" :style="cssStyles">
3
3
  <template v-for="(item, index) in spec.childViews">
4
4
  <div :key="index">
5
- <ui-component :spec="item" />
5
+ <glib-component :spec="item" />
6
6
  </div>
7
7
  </template>
8
8
  </div>
@@ -7,7 +7,7 @@
7
7
  @click="$onClick()"
8
8
  >
9
9
  <template v-for="(item, index) in spec.childViews">
10
- <ui-component :key="index" :spec="item" />
10
+ <glib-component :key="index" :spec="item" />
11
11
  </template>
12
12
  </component>
13
13
  </template>
@@ -127,7 +127,8 @@ export default {
127
127
  fieldCheckName: `${prefix}[checked]`,
128
128
  fieldTitleName: this.spec.fieldTitleName
129
129
  ? `${prefix}[${this.spec.fieldTitleName}]`
130
- : null
130
+ : null,
131
+ fieldCheckValueIf: this.spec.fieldCheckValueIf
131
132
  });
132
133
  }
133
134
  return row;
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div :class="cssClasses" :style="cssStyles">
3
3
  <!-- <template v-for="(item, index) in spec.childViews">
4
- <ui-component :key="`${index}_${item.view}`" :spec="item" />
4
+ <glib-component :key="`${index}_${item.view}`" :spec="item" />
5
5
  </template> -->
6
6
  <panels-responsive :spec="spec" />
7
7
  </div>
@@ -8,7 +8,7 @@
8
8
  <panels-responsive v-if="spec.center" :spec="spec.center" />
9
9
  </div>
10
10
 
11
- <!-- <ui-component :spec="spec.centerView" v-if="spec.centerView" class="panels-split__expand"/>
11
+ <!-- <glib-component :spec="spec.centerView" v-if="spec.centerView" class="panels-split__expand"/>
12
12
  <div class="panels-split__expand" v-else></div> -->
13
13
 
14
14
  <div v-if="spec.right" class="layouts-split__side layouts-split__right">
@@ -65,7 +65,7 @@
65
65
  v-else
66
66
  :key="index"
67
67
  >
68
- <ui-component :spec="cell" />
68
+ <glib-component :spec="cell" />
69
69
  </th>
70
70
  </tr>
71
71
  </thead>
@@ -84,9 +84,9 @@
84
84
  :style="colStyles(row, cellIndex)"
85
85
  >
86
86
  <!-- Prevent double links -->
87
- <ui-component v-if="$href(cell)" :spec="cell" />
87
+ <glib-component v-if="$href(cell)" :spec="cell" />
88
88
  <a v-else :href="$href(row)" @click="$onClick($event, row)">
89
- <ui-component :spec="cell" />
89
+ <glib-component :spec="cell" />
90
90
  </a>
91
91
  </td>
92
92
  </tr>
@@ -2,7 +2,7 @@
2
2
  <ul :style="$styles()" :class="$classes()">
3
3
  <!-- Use view name for key to avoid component reuse issue -->
4
4
  <li v-for="(item, index) in spec.childViews" :key="`${index}_${item.view}`">
5
- <ui-component :spec="item" />
5
+ <glib-component :spec="item" />
6
6
  </li>
7
7
  </ul>
8
8
  </template>
@@ -8,7 +8,7 @@
8
8
  >
9
9
  <template v-for="(item, index) in spec.childViews">
10
10
  <!-- Use view name for key to avoid component reuse issue -->
11
- <ui-component :key="`${index}_${item.view}`" :spec="item" />
11
+ <glib-component :key="`${index}_${item.view}`" :spec="item" />
12
12
  </template>
13
13
  </component>
14
14
  </template>
@@ -115,7 +115,7 @@ export default {
115
115
  };
116
116
  </script>
117
117
 
118
- <style scoped>
118
+ <style lang="scss" scoped>
119
119
  a[class^="share-network-"] {
120
120
  flex: none;
121
121
  color: #ffffff;
@@ -127,8 +127,10 @@ a[class^="share-network-"] {
127
127
  align-content: center;
128
128
  align-items: center;
129
129
  cursor: pointer;
130
- margin: 0 10px 10px 0;
131
130
  text-decoration: none;
131
+
132
+ // Avoid hardcoding styling
133
+ // margin: 0 10px 10px 0;
132
134
  }
133
135
 
134
136
  a[class^="share-network-"] .fah {
package/index.js CHANGED
@@ -67,9 +67,6 @@ Vue.component("common-dropdownMenu", CommonDropdownMenu);
67
67
  Vue.component("common-responsive", CommonResponsive);
68
68
  Vue.component("templates-menu", CommonTemplateMenu);
69
69
 
70
- // TODO: Deprecate
71
- Vue.component("ui-component", Component);
72
-
73
70
  Vue.component("glib-component", Component);
74
71
  Vue.component("glib-icon", CommonIcon);
75
72
  Vue.component("glib-panels-responsive", ResponsivePanel);
@@ -125,6 +122,12 @@ window.GLib = Framework;
125
122
  import { settings } from "./utils/settings";
126
123
  export { settings };
127
124
 
125
+ import driverCustomBehavior from "./plugins/driverCustomBehavior";
126
+ Vue.use(driverCustomBehavior);
127
+
128
+ import updatableComponent from "./plugins/updatableComponent";
129
+ Vue.use(updatableComponent);
130
+
128
131
  // TODO: https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components
129
132
  // Vue.component('async-webpack-example', function (resolve) {
130
133
  // // This special require syntax will instruct Webpack to
package/nav/dialog.vue CHANGED
@@ -61,7 +61,9 @@ export default {
61
61
  model: null,
62
62
  url: null,
63
63
  urlLoaded: false,
64
- showClose: false
64
+ showClose: false,
65
+ isFormDirty: false,
66
+ isFormSubmitted: false
65
67
  };
66
68
  },
67
69
  watch: {
@@ -77,14 +79,15 @@ export default {
77
79
 
78
80
  this.show(false);
79
81
  this.stack.push(this);
80
- console.log("DIALOG READY1", this.stack.length);
81
82
  },
82
83
  $tearDown() {
83
84
  console.log("Dialog destroyed");
84
85
  this.stack.remove(this);
85
86
  },
86
87
  close() {
87
- this.model = false;
88
+ if (Utils.http.proceedEvenWhenDirty()) {
89
+ this.model = false;
90
+ }
88
91
  },
89
92
  click(spec, event) {
90
93
  const onClick = spec["onClick"];
@@ -92,9 +95,11 @@ export default {
92
95
  Action.execute(onClick, this);
93
96
  },
94
97
  reload(newSpec) {
95
- this.spec = newSpec;
96
- this.urlLoaded = false;
97
- this.show(true);
98
+ if (Utils.http.proceedEvenWhenDirty()) {
99
+ this.spec = newSpec;
100
+ this.urlLoaded = false;
101
+ this.show(true);
102
+ }
98
103
  },
99
104
  show(reload) {
100
105
  const spec = this.spec;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "0.11.21",
3
+ "version": "0.13.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -21,8 +21,6 @@
21
21
  "marked": "^4.0.0",
22
22
  "phoenix": "^1.5.3",
23
23
  "push.js": "^1.0.12",
24
- "sass": "^1.38.0",
25
- "sass-loader": "10.1.1",
26
24
  "vue": "2.6.14",
27
25
  "vue-chartkick": "^0.6.1",
28
26
  "vue-gtag": "^1.1.2",
@@ -0,0 +1,13 @@
1
+ export default {
2
+ install: (Vue, options) => {
3
+ let driverCustomBehavior = {};
4
+ driverCustomBehavior.clickToNext = (element, driver) => {
5
+ element.addEventListener("click", () => driver.moveNext());
6
+ };
7
+ driverCustomBehavior.focusToNext = (element, driver) => {
8
+ element.addEventListener("focus", () => driver.moveNext());
9
+ };
10
+
11
+ Vue.prototype.driverCustomBehavior = driverCustomBehavior;
12
+ }
13
+ };
@@ -0,0 +1,15 @@
1
+ export default {
2
+ install: (Vue, options) => {
3
+ Vue.prototype.updatables = {};
4
+ Vue.mixin({
5
+ created: function() {
6
+ let spec = this.spec;
7
+ if (spec && spec.compId) {
8
+ Object.assign(this.updatables, {
9
+ [spec.compId]: this
10
+ });
11
+ }
12
+ }
13
+ });
14
+ }
15
+ };
@@ -23,11 +23,13 @@
23
23
  >
24
24
  <v-icon v-if="spec.onReorder" class="handle">drag_indicator</v-icon>
25
25
 
26
- <v-checkbox
26
+ <!-- <v-checkbox
27
27
  v-if="spec.fieldCheckName"
28
28
  :name="spec.fieldCheckName"
29
29
  :value="true"
30
- ></v-checkbox>
30
+ ></v-checkbox> -->
31
+
32
+ <fields-check v-if="checkSpec" :spec="checkSpec" />
31
33
 
32
34
  <div v-if="spec.leftButtons" class="left-universal">
33
35
  <template v-for="(item, index) in spec.leftButtons">
@@ -96,7 +98,12 @@
96
98
  </template>
97
99
 
98
100
  <script>
101
+ import CheckField from "../components/fields/check";
102
+
99
103
  export default {
104
+ components: {
105
+ "fields-check": CheckField
106
+ },
100
107
  props: {
101
108
  spec: { type: Object, required: true },
102
109
  responsiveCols: { type: Number, default: () => 0 }
@@ -104,7 +111,8 @@ export default {
104
111
  data() {
105
112
  return {
106
113
  accessory: {},
107
- editButtons: []
114
+ editButtons: [],
115
+ checkSpec: null
108
116
  // chips: []
109
117
  };
110
118
  },
@@ -135,6 +143,15 @@ export default {
135
143
  $ready() {
136
144
  this.accessory = { childViews: this.spec.accessoryViews };
137
145
  this.editButtons = this.spec.editButtons || [];
146
+
147
+ if (this.spec.fieldCheckName) {
148
+ this.checkSpec = {
149
+ view: "fields/checkGroup",
150
+ name: this.spec.fieldCheckName,
151
+ checkValue: true,
152
+ valueIf: this.spec.fieldCheckValueIf
153
+ };
154
+ }
138
155
  },
139
156
  buttonSpec(item) {
140
157
  return Object.assign({}, item, {
package/utils/form.js CHANGED
@@ -6,6 +6,8 @@ export default class {
6
6
  static submitData(form, component) {
7
7
  // Analogous to Rails' form_with's `local: true`
8
8
  if (form.dataset.local) {
9
+ // Prevent onUnload dirty prompt.
10
+ Utils.http.clearDirtyState();
9
11
  form.submit();
10
12
  } else {
11
13
  const url = form.getAttribute("action");
@@ -15,6 +17,7 @@ export default class {
15
17
  const data = {
16
18
  url: Utils.url.appendParams(url, formData)
17
19
  };
20
+ Utils.http.notifyFormSubmitted();
18
21
  Utils.http.load(data, component);
19
22
  } else {
20
23
  const data = {
package/utils/http.js CHANGED
@@ -76,7 +76,7 @@ export default class {
76
76
  const featureAnalytics = pageAnalytics.feature;
77
77
 
78
78
  const headers = {};
79
- if (featureAnalytics) {
79
+ if (featureAnalytics && component.spec) {
80
80
  const componentAnalytics = component.spec.analytics || {};
81
81
  if (componentAnalytics.disabled) {
82
82
  return headers;
@@ -206,32 +206,44 @@ export default class {
206
206
 
207
207
  // See generic.js
208
208
  static forceComponentUpdate(handler) {
209
+ const dirtyContext = this.dirtyContext();
210
+
209
211
  window.vueApp.isStale = true;
210
212
 
211
213
  // Queue the execution so the first isStale has time to resets state before handler gets executed
212
214
  setTimeout(() => {
213
- window.vueApp.isFormSubmitted = false;
214
- window.vueApp.isFormDirty = false;
215
+ dirtyContext.isFormSubmitted = false;
216
+ dirtyContext.isFormDirty = false;
215
217
  handler();
216
218
  window.vueApp.isStale = false;
217
219
  }, 0);
218
220
  }
219
221
 
220
222
  static promptIfDirtyOnUnload() {
221
- window.onbeforeunload = function() {
222
- return window.vueApp.isFormDirty ? dirtyPrompt : null;
223
+ window.onbeforeunload = () => {
224
+ return this.dirtyContext().isFormDirty ? dirtyPrompt : null;
223
225
  };
224
226
  }
225
227
 
228
+ static dirtyContext() {
229
+ return Utils.launch.dialog.topDialog() || window.vueApp;
230
+ }
231
+
232
+ static clearDirtyState() {
233
+ this.dirtyContext().isFormDirty = false;
234
+ }
235
+
226
236
  static notifyFormSubmitted() {
227
- window.vueApp.isFormSubmitted = true;
237
+ this.dirtyContext().isFormSubmitted = true;
228
238
  }
229
239
 
240
+ // `context` can be either window or dialog.
230
241
  static proceedEvenWhenDirty() {
242
+ const dirtyContext = this.dirtyContext();
231
243
  // Don't prompt if this is a result of form submission
232
244
  if (
233
- window.vueApp.isFormDirty &&
234
- !window.vueApp.isFormSubmitted &&
245
+ dirtyContext.isFormDirty &&
246
+ !dirtyContext.isFormSubmitted &&
235
247
  !confirm(dirtyPrompt)
236
248
  ) {
237
249
  return false;
package/utils/launch.js CHANGED
@@ -19,6 +19,18 @@ export default class {
19
19
  }
20
20
 
21
21
  class LaunchDialog {
22
+ static _initStack() {
23
+ if (!this.stack) {
24
+ this.stack = [];
25
+ }
26
+ }
27
+
28
+ static topDialog() {
29
+ this._initStack();
30
+
31
+ return this.stack.last();
32
+ }
33
+
22
34
  static open(properties, component) {
23
35
  if (!this.stack) {
24
36
  this.stack = [];
@@ -1,75 +0,0 @@
1
- import Driver from "driver.js";
2
- import "driver.js/dist/driver.min.css";
3
-
4
- class TourHelper {
5
- constructor() {
6
- this.keys = [];
7
- this.storage = window.localStorage;
8
-
9
- this.driver = new Driver({
10
- doneBtnText: "Done",
11
- closeBtnText: "Skip tour",
12
- nextBtnText: "Next tip",
13
- prevBtnText: "Prev",
14
- onHighlighted: element => {
15
- console.log("onHighlighted", element);
16
- // TODO
17
- // this.keys.forEach(value => this.storage.removeItem(value));
18
- // this.storage.setItem(element.node.dataset.key, 1);
19
- }
20
- });
21
-
22
- this.elements = [];
23
- }
24
- }
25
-
26
- const helper = new TourHelper();
27
-
28
- export default {
29
- methods: {
30
- registerTour() {
31
- if (this.spec.tour) {
32
- helper.elements.push(this);
33
- }
34
- },
35
- startTour() {
36
- if (helper.elements && helper.elements.length > 0) {
37
- helper.elements = Array.from(helper.elements).sort(
38
- (a, b) => a.index - b.index
39
- );
40
- let stepDefinitions = helper.elements.map(element => {
41
- const tourSpec = element.spec.tour;
42
-
43
- return {
44
- element: element.$el,
45
- popover: {
46
- title: tourSpec.title,
47
- description: tourSpec.message,
48
- position: tourSpec.position
49
- }
50
- };
51
- });
52
-
53
- // TODO
54
- // this.keys = helper.elements.map(value => value.dataset.key);
55
-
56
- helper.driver.defineSteps(stepDefinitions);
57
-
58
- helper.driver.start(this.latestTourStep());
59
- }
60
- },
61
- latestTourStep() {
62
- let step = 0;
63
-
64
- // TODO
65
- // Array.from(this.elements).forEach(element => {
66
- // if (this.storage.getItem(element.dataset.key)) {
67
- // step = parseInt(element.dataset.index);
68
- // }
69
- // });
70
- // return step;
71
-
72
- return step;
73
- }
74
- }
75
- };