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 +10 -1
- package/actions/commands/copy.js +6 -6
- package/actions/components/update.js +11 -0
- package/actions/http/get.js +7 -0
- package/actions/tours/start.js +17 -0
- package/app.vue +1 -3
- package/components/_responsive.vue +2 -2
- package/components/button.vue +1 -3
- package/components/component.vue +1 -1
- package/components/fields/check.vue +0 -11
- package/components/fields/checkGroup.vue +56 -14
- package/components/fields/location.vue +3 -3
- package/components/fields/richText.vue +7 -4
- package/components/markdown.vue +9 -3
- package/components/mixins/styles.js +22 -13
- package/components/panels/carousel.vue +1 -1
- package/components/panels/flow.vue +1 -1
- package/components/panels/grid.vue +1 -1
- package/components/panels/horizontal.vue +1 -1
- package/components/panels/list.vue +2 -1
- package/components/panels/scroll.vue +1 -1
- package/components/panels/split.vue +1 -1
- package/components/panels/table.vue +3 -3
- package/components/panels/ul.vue +1 -1
- package/components/panels/vertical.vue +1 -1
- package/components/shareButton.vue +4 -2
- package/index.js +6 -3
- package/nav/dialog.vue +11 -6
- package/package.json +1 -3
- package/plugins/driverCustomBehavior.js +13 -0
- package/plugins/updatableComponent.js +15 -0
- package/templates/thumbnail.vue +20 -3
- package/utils/form.js +3 -0
- package/utils/http.js +20 -8
- package/utils/launch.js +12 -0
- package/components/mixins/tour.js +0 -75
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;
|
package/actions/commands/copy.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export default class {
|
|
2
|
-
execute(properties) {
|
|
3
|
-
let spec = Object.assign({}, properties, {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
() =>
|
|
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,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
|
|
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
|
-
<
|
|
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
|
-
<
|
|
23
|
+
<glib-component :spec="item" />
|
|
24
24
|
</div>
|
|
25
25
|
</template>
|
|
26
26
|
</v-row>
|
package/components/button.vue
CHANGED
|
@@ -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
|
|
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
|
}
|
package/components/component.vue
CHANGED
|
@@ -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
|
|
3
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
|
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
|
-
<
|
|
53
|
+
<glib-component
|
|
54
54
|
v-if="latitudeField"
|
|
55
55
|
ref="latitudeView"
|
|
56
56
|
:spec="latitudeField"
|
|
57
57
|
/>
|
|
58
|
-
<
|
|
58
|
+
<glib-component
|
|
59
59
|
v-if="longitudeField"
|
|
60
60
|
ref="longitudeView"
|
|
61
61
|
:spec="longitudeField"
|
|
62
62
|
/>
|
|
63
|
-
<
|
|
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="
|
|
16
|
-
name="
|
|
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.
|
|
101
|
+
const uploaderSpec = this.imageUploader;
|
|
99
102
|
// const input = this.$refs.directUploadFile
|
|
100
103
|
const upload = new Uploader(
|
|
101
104
|
file,
|
package/components/markdown.vue
CHANGED
|
@@ -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(
|
|
37
|
+
let anchors = document.querySelectorAll("a");
|
|
38
38
|
for (let i = 0; i < anchors.length; i++) {
|
|
39
|
-
anchors[i].setAttribute(
|
|
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
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
-
<
|
|
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
|
-
<!-- <
|
|
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
|
-
<
|
|
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
|
-
<
|
|
87
|
+
<glib-component v-if="$href(cell)" :spec="cell" />
|
|
88
88
|
<a v-else :href="$href(row)" @click="$onClick($event, row)">
|
|
89
|
-
<
|
|
89
|
+
<glib-component :spec="cell" />
|
|
90
90
|
</a>
|
|
91
91
|
</td>
|
|
92
92
|
</tr>
|
package/components/panels/ul.vue
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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.
|
|
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
|
+
};
|
package/templates/thumbnail.vue
CHANGED
|
@@ -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
|
-
|
|
214
|
-
|
|
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 =
|
|
222
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
!
|
|
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
|
-
};
|