glib-web 0.6.17 → 0.8.2

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.
@@ -1,5 +1,5 @@
1
1
  export default class {
2
2
  execute(properties, component) {
3
- Utils.http.load(properties, null, component);
3
+ Utils.http.load(properties, component);
4
4
  }
5
5
  }
@@ -1,21 +1,6 @@
1
1
  export default class {
2
2
  execute(properties, component) {
3
3
  if (Utils.settings.reactive) {
4
- // const currentUrl = window.location.href;
5
- // const data = {
6
- // url: properties.url || currentUrl
7
- // };
8
-
9
- // Utils.http.execute(data, "GET", component, (page, response) => {
10
- // Utils.http.forceComponentUpdate(() => {
11
- // window.vueApp.page = page;
12
- // const redirectUrl = Utils.url.htmlUrl(response.url);
13
- // Utils.history.updatePage(redirectUrl);
14
-
15
- // GLib.action.execute(properties["onReload"], null, component);
16
- // });
17
- // });
18
-
19
4
  Utils.http.reload(properties, component);
20
5
  } else {
21
6
  window.location.reload();
package/app.vue CHANGED
@@ -80,6 +80,7 @@ export default {
80
80
  );
81
81
  Utils.history.saveInitialContent(this.page);
82
82
  Utils.history.restoreOnBackOrForward();
83
+ Utils.http.promptIfDirtyOnUnload();
83
84
  },
84
85
  methods: {
85
86
  $ready: function() {
@@ -113,7 +113,7 @@ import SelectBanner from "./banners/select";
113
113
  import LineChart from "./charts/line";
114
114
  import ColumnChart from "./charts/column";
115
115
 
116
- import SocialSharing from "./socialSharing";
116
+ import ShareButton from "./shareButton";
117
117
 
118
118
  export default {
119
119
  components: {
@@ -145,7 +145,7 @@ export default {
145
145
  "views-map": Map,
146
146
  "views-tabBar": TabBar,
147
147
  "views-calendar": Calendar,
148
- "views-socialSharing": SocialSharing,
148
+ "views-shareButton": ShareButton,
149
149
 
150
150
  "fields-hidden": HiddenField,
151
151
  "fields-text": TextField,
@@ -46,8 +46,18 @@ export default {
46
46
  this.$internalizeValue(val)
47
47
  );
48
48
 
49
- // this.$saveValue(val);
50
- // Vue.set(this.$data._fieldModels, this.fieldName, val);
49
+ // Make sure value has changed and make sure that it is different from the original value.
50
+ // Be strict with this so it doesn't execute when the component is just initializing (e.g value changing
51
+ // from `null` to `this.spec.value`).
52
+ if (
53
+ !window.vueApp.page.disableDirtyPrompt &&
54
+ !window.vueApp.isFormDirty &&
55
+ val != oldVal &&
56
+ val != this.spec.value
57
+ ) {
58
+ console.log("Form is now dirty");
59
+ window.vueApp.isFormDirty = true;
60
+ }
51
61
  }
52
62
  },
53
63
  methods: {
@@ -42,7 +42,6 @@ export default {
42
42
  };
43
43
  Utils.http.execute(data, "POST", vm, response => {
44
44
  GLib.action.handleResponse(response, vm);
45
- // Action.execute(response["onResponse"], target, vm);
46
45
  vm._submitEachRow(rows, target);
47
46
  });
48
47
  }
@@ -14,13 +14,22 @@ export default {
14
14
  computed: {
15
15
  cssClasses: function() {
16
16
  const classes = this.$classes().concat("layouts-horizontal");
17
- switch (this.spec.distribution) {
17
+ const distribution = this.spec.distribution;
18
+ switch (distribution) {
18
19
  case "fillEqually":
19
20
  classes.push("layouts-horizontal--fill-equally");
20
21
  break;
21
22
  case "spaceEqually":
22
23
  classes.push("layouts-horizontal--space-equally");
23
24
  break;
25
+ default:
26
+ Utils.type.ifString(distribution, distribution => {
27
+ // Uses Material Design spacings: https://vuetifyjs.com/en/styles/spacing/#how-it-works
28
+ if (distribution.startsWith("overlap")) {
29
+ classes.push(`layouts-horizontal--${distribution}`);
30
+ }
31
+ });
32
+ break;
24
33
  }
25
34
  return classes;
26
35
  },
@@ -67,7 +76,19 @@ export default {
67
76
  .layouts-horizontal--space-equally > * {
68
77
  flex: initial;
69
78
  }
70
- /* .layouts-horizontal > div {
71
- display: inline-block;
72
- } */
79
+ .layouts-horizontal--overlap-1 > *:not(:first-child) {
80
+ margin-left: -4px;
81
+ }
82
+ .layouts-horizontal--overlap-2 > *:not(:first-child) {
83
+ margin-left: -8px;
84
+ }
85
+ .layouts-horizontal--overlap-3 > *:not(:first-child) {
86
+ margin-left: -12px;
87
+ }
88
+ .layouts-horizontal--overlap-4 > *:not(:first-child) {
89
+ margin-left: -16px;
90
+ }
91
+ .layouts-horizontal--overlap-5 > *:not(:first-child) {
92
+ margin-left: -20px;
93
+ }
73
94
  </style>
@@ -5,7 +5,7 @@
5
5
  :url="spec.url"
6
6
  :title="spec.title"
7
7
  :description="spec.description"
8
- :quote="spec.quote"
8
+ :quote="spec.facebookQuote"
9
9
  :hashtags="spec.hashtags"
10
10
  :twitter-user="spec.twitterUser"
11
11
  >
@@ -16,6 +16,8 @@
16
16
  </template>
17
17
 
18
18
  <script>
19
+ import Action from "../action";
20
+
19
21
  export default {
20
22
  props: {
21
23
  spec: { type: Object, required: true }
@@ -23,6 +25,10 @@ export default {
23
25
  data() {
24
26
  return {
25
27
  config: {
28
+ copy: {
29
+ icon: "far fah fa-lg fa-copy",
30
+ color: "#183153"
31
+ },
26
32
  email: {
27
33
  icon: "far fah fa-lg fa-envelope",
28
34
  color: "#333333"
@@ -85,6 +91,19 @@ export default {
85
91
  }
86
92
  }
87
93
  };
94
+ },
95
+ mounted() {
96
+ if (this.spec.onClick) {
97
+ const parentElement = this.$el.parentElement;
98
+ // remove default event listener
99
+ this.$el.replaceWith(this.$el.cloneNode(true));
100
+ // manually attach event listener because @click not working
101
+ parentElement
102
+ .querySelector(`a.${this.$el.classList[0]}`)
103
+ .addEventListener("click", () => {
104
+ Action.execute(this.spec.onClick, this);
105
+ });
106
+ }
88
107
  }
89
108
  };
90
109
  </script>
package/index.js CHANGED
@@ -39,7 +39,7 @@ Vue.use(VueYoutube);
39
39
  // })
40
40
 
41
41
  import VueSocialSharing from "vue-social-sharing";
42
- Vue.use(VueSocialSharing);
42
+ Vue.use(VueSocialSharing, { networks: { copy: "" } });
43
43
 
44
44
  import "./extensions/string.js";
45
45
  import "./extensions/array.js";
@@ -160,6 +160,10 @@ document.addEventListener("DOMContentLoaded", () => {
160
160
  indicator: false,
161
161
  // Rename to isPageStale
162
162
  isStale: false,
163
+ /// Dirty form handling
164
+ isFormSubmitted: false,
165
+ isFormDirty: false,
166
+ ///
163
167
  stateUpdatedAt: null,
164
168
  webSocket: { channels: {}, header: {} },
165
169
  actionCable: { channels: {} },
package/nav/dialog.vue CHANGED
@@ -83,7 +83,7 @@ export default {
83
83
  this.message = "";
84
84
  this.body = response.body;
85
85
  this.footer = response.footer;
86
- this.showClose = false;
86
+ this.showClose = this.spec.showClose || false;
87
87
  });
88
88
  } else {
89
89
  this.title = this.spec.title;
package/nav/drawer.vue CHANGED
@@ -50,6 +50,75 @@
50
50
  import NavDrawerButton from "./drawerButton";
51
51
  import NavDrawerLabel from "./drawerLabel";
52
52
 
53
+ const activeClass = (element, action) => {
54
+ let indicator = element.querySelector(".indicator");
55
+ if (indicator) {
56
+ indicator.classList[action]("active");
57
+ }
58
+ let icon = element.querySelector(".v-list-item__action");
59
+ if (icon) {
60
+ icon.classList[action]("active");
61
+ }
62
+ };
63
+
64
+ const comparePathname = (hrefPathname, locationPathname) => {
65
+ let value = 0;
66
+ const loc = locationPathname.split("/").filter(v => v);
67
+ const hr = hrefPathname.split("/").filter(v => v);
68
+
69
+ if (loc.length == hr.length) value = value + 1;
70
+
71
+ loc.forEach((route, index) => {
72
+ if (!hr[index]) {
73
+ value = value + 0;
74
+ } else if (hr[index] == route) {
75
+ value = value + 1;
76
+ } else if (hr[index] != route) {
77
+ value = value - 2;
78
+ }
79
+ });
80
+
81
+ return value;
82
+ };
83
+
84
+ const navigationMap = (element, locationPathname) => {
85
+ return Array.from(element.querySelectorAll(".nav-item")).reduce(
86
+ (prev, curr) => {
87
+ let a = curr.querySelector("a");
88
+ if (a) {
89
+ let obj = {};
90
+ obj[new URL(a.href).pathname] = {
91
+ element: curr,
92
+ matchValue: comparePathname(
93
+ new URL(a.href).pathname,
94
+ locationPathname
95
+ )
96
+ };
97
+ return Object.assign(prev, obj);
98
+ } else {
99
+ return prev;
100
+ }
101
+ },
102
+ {}
103
+ );
104
+ };
105
+
106
+ const getActiveNav = navigationMap => {
107
+ let currentRoute = "";
108
+ let maxMatchValue = null;
109
+
110
+ for (let route in navigationMap) {
111
+ if (!maxMatchValue) {
112
+ maxMatchValue = navigationMap[route].matchValue;
113
+ currentRoute = route;
114
+ } else if (maxMatchValue < navigationMap[route].matchValue) {
115
+ maxMatchValue = navigationMap[route].matchValue;
116
+ currentRoute = route;
117
+ }
118
+ }
119
+ return navigationMap[currentRoute];
120
+ };
121
+
53
122
  export default {
54
123
  components: {
55
124
  "nav-drawerButton": NavDrawerButton,
@@ -98,6 +167,8 @@ export default {
98
167
  },
99
168
  methods: {
100
169
  $ready() {
170
+ this.updateNavigationStyle(window.location);
171
+
101
172
  // this.$el.addEventListener('drawers/clickButton', () => { this.updateState(false) }, false)
102
173
  this.$el.addEventListener("drawers/clickButton", () => {
103
174
  if (!this.permanent) {
@@ -113,6 +184,15 @@ export default {
113
184
  this.cssClasses.includes("mini") ||
114
185
  (this.cssClasses.includes("mini-lg-and-above") && this.lgAndAbove)
115
186
  );
187
+ },
188
+ updateNavigationStyle(urlObject) {
189
+ const navMap = navigationMap(this.$el, urlObject.pathname);
190
+ // remove all active class
191
+ for (let route in navMap) {
192
+ activeClass(navMap[route].element, "remove");
193
+ }
194
+ // add active class to nav
195
+ activeClass(getActiveNav(navMap).element, "add");
116
196
  }
117
197
  }
118
198
  };
@@ -122,12 +202,13 @@ export default {
122
202
  .nav-item {
123
203
  position: relative;
124
204
  }
125
- .nav-item:hover .indicator {
205
+ .nav-item:hover .indicator,
206
+ .nav-item .indicator.active {
126
207
  display: flex;
127
208
  position: absolute;
128
209
  width: 3px;
129
210
  height: 65%;
130
- margin: 10px 0;
211
+ margin: 8px 0;
131
212
  justify-content: center;
132
213
  align-items: center;
133
214
  background-color: white;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glib-web",
3
- "version": "0.6.17",
3
+ "version": "0.8.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/utils/form.js CHANGED
@@ -15,13 +15,14 @@ export default class {
15
15
  const data = {
16
16
  url: Utils.url.appendParams(url, formData)
17
17
  };
18
- Utils.http.load(data, null, component);
18
+ Utils.http.load(data, component);
19
19
  } else {
20
20
  const data = {
21
21
  url: url,
22
22
  formData: formData
23
23
  };
24
24
  Utils.http.execute(data, method, component, response => {
25
+ Utils.http.notifyFormSubmitted();
25
26
  GLib.action.handleResponse(response, component);
26
27
  });
27
28
  }
package/utils/history.js CHANGED
@@ -35,6 +35,15 @@ export default class {
35
35
  static restoreOnBackOrForward() {
36
36
  const vm = this;
37
37
  window.onpopstate = event => {
38
+ // TODO: Ideally display a prompt when dirty
39
+ // if (Utils.http.proceedEvenWhenDirty()) {
40
+ // event.preventDefault();
41
+ // history.go(1);
42
+ // return false;
43
+ // }
44
+
45
+ window.vueApp.isFormDirty = false;
46
+
38
47
  // Save scroll position of the current page when navigating through back/forward button
39
48
  this.bodyScrollTops[this.navigationCount] = this._pageBody.scrollTop;
40
49
 
package/utils/http.js CHANGED
@@ -2,6 +2,7 @@ import Type from "./type";
2
2
  import Action from "../action";
3
3
 
4
4
  let loading = false;
5
+ const dirtyPrompt = "Changes you made have not been saved. Are you sure?";
5
6
 
6
7
  class HttpRequest {
7
8
  constructor() {
@@ -38,9 +39,15 @@ export default class {
38
39
  return formData;
39
40
  }
40
41
 
41
- static load(properties, target, component) {
42
+ static load(properties, component) {
42
43
  const url = new URL(properties["url"]);
43
44
  const domainMatched = window.location.hostname == url.hostname;
45
+
46
+ // If this is an external domain, we rely on `onUnload()` instead.
47
+ if (domainMatched && !this.proceedEvenWhenDirty()) {
48
+ return;
49
+ }
50
+
44
51
  if (Utils.settings.reactive && domainMatched) {
45
52
  const currentUrl = window.location.href;
46
53
  const htmlUrl = Utils.url.htmlUrl(properties["url"]);
@@ -60,6 +67,7 @@ export default class {
60
67
  } else {
61
68
  window.location = Utils.url.htmlUrl(properties["url"]);
62
69
  }
70
+ // bus.$emit("glibWebHttpLoad", new URL(Utils.url.htmlUrl(properties["url"])));
63
71
  }
64
72
 
65
73
  static analyticsHeaders(component) {
@@ -202,8 +210,32 @@ export default class {
202
210
 
203
211
  // Queue the execution so the first isStale has time to resets state before handler gets executed
204
212
  setTimeout(() => {
213
+ window.vueApp.isFormSubmitted = false;
214
+ window.vueApp.isFormDirty = false;
205
215
  handler();
206
216
  window.vueApp.isStale = false;
207
217
  }, 0);
208
218
  }
219
+
220
+ static promptIfDirtyOnUnload() {
221
+ window.onbeforeunload = function() {
222
+ return window.vueApp.isFormDirty ? dirtyPrompt : null;
223
+ };
224
+ }
225
+
226
+ static notifyFormSubmitted() {
227
+ window.vueApp.isFormSubmitted = true;
228
+ }
229
+
230
+ static proceedEvenWhenDirty() {
231
+ // Don't prompt if this is a result of form submission
232
+ if (
233
+ window.vueApp.isFormDirty &&
234
+ !window.vueApp.isFormSubmitted &&
235
+ !confirm(dirtyPrompt)
236
+ ) {
237
+ return false;
238
+ }
239
+ return true;
240
+ }
209
241
  }
package/utils/uploader.js CHANGED
@@ -5,13 +5,6 @@ import mimeType from "../utils/mime_type";
5
5
  const MB_SIZE = 1000;
6
6
 
7
7
  export default class Uploader {
8
- // constructor(input, file, url) {
9
- // this.input = input
10
- // this.file = file
11
- // this.url = url
12
- // this.upload = new DirectUpload(this.file, this.url, this)
13
- // }
14
-
15
8
  constructor(file, url, progress) {
16
9
  this.file = file;
17
10
  this.url = url;