@afeefa/vue-app 0.0.64 → 0.0.67
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/.afeefa/package/release/version.txt +1 -1
- package/package.json +1 -1
- package/src/components/ABreadcrumbs.vue +75 -18
- package/src/components/ADatePicker.vue +1 -1
- package/src/components/AIcon.vue +3 -6
- package/src/components/AIconButton.vue +1 -2
- package/src/components/ARichTextArea.vue +95 -85
- package/src/components/ARow.vue +0 -7
- package/src/components/form/EditForm.vue +9 -0
- package/src/components/form/EditModal.vue +2 -0
- package/src/components/form/FormFieldMixin.js +13 -4
- package/src/components/form/fields/FormFieldRichTextArea.vue +5 -3
- package/src/components/list/ListViewMixin.js +25 -2
- package/src/components/mixins/ClickOutsideMixin.js +5 -1
- package/src/components/search-select/SearchSelectList.vue +0 -1
- package/src/components/vue/Component.js +9 -2
- package/src/events.js +1 -0
- package/src-admin/components/App.vue +56 -61
- package/src-admin/components/Sidebar.vue +66 -0
- package/src-admin/components/SidebarItem.vue +59 -0
- package/src-admin/components/StickyFooter.vue +42 -0
- package/src-admin/components/StickyFooterContainer.vue +66 -0
- package/src-admin/components/StickyHeader.vue +73 -0
- package/src-admin/components/app/AppBarButtons.vue +0 -7
- package/src-admin/components/app/AppBarTitle.vue +55 -11
- package/src-admin/components/app/AppBarTitleContainer.vue +2 -3
- package/src-admin/components/controls/SearchSelectFormField.vue +1 -0
- package/src-admin/components/detail/DetailProperty.vue +20 -16
- package/src-admin/components/form/EditFormButtons.vue +21 -4
- package/src-admin/components/form/RemoveButton.vue +17 -8
- package/src-admin/components/index.js +4 -0
- package/src-admin/components/list/ListView.vue +5 -6
- package/src-admin/components/pages/EditPage.vue +11 -7
- package/src-admin/components/routes/DataRouteMixin.js +1 -1
- package/src-admin/config/vuetify.js +22 -2
- package/src-admin/styles.scss +21 -4
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.67
|
package/package.json
CHANGED
@@ -1,19 +1,38 @@
|
|
1
1
|
<template>
|
2
|
-
<div class="d-flex
|
3
|
-
<div
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<v-icon>$chevronRightIcon</v-icon>
|
9
|
-
|
10
|
-
<router-link
|
11
|
-
:to="breadcrumb.to"
|
12
|
-
:exact="true"
|
2
|
+
<div class="a-breadcrumbs d-flex align-start gap-2 mr-4">
|
3
|
+
<div :class="['breadcrumbs d-flex align-center', {'flex-wrap': wrapBreadcrumbs_}]">
|
4
|
+
<div
|
5
|
+
v-for="(breadcrumb, index) in breadcrumbs"
|
6
|
+
:key="index"
|
7
|
+
class="item mr-2 d-flex align-center"
|
13
8
|
>
|
14
|
-
|
15
|
-
|
9
|
+
<v-icon v-if="index > 0">
|
10
|
+
$chevronRightIcon
|
11
|
+
</v-icon>
|
12
|
+
|
13
|
+
<router-link
|
14
|
+
:to="breadcrumb.to"
|
15
|
+
:exact="true"
|
16
|
+
>
|
17
|
+
{{ breadcrumb.title }}
|
18
|
+
</router-link>
|
19
|
+
</div>
|
16
20
|
</div>
|
21
|
+
|
22
|
+
<v-avatar
|
23
|
+
v-if="expandVisible"
|
24
|
+
class="expand"
|
25
|
+
color="#EEE"
|
26
|
+
size="1.3rem"
|
27
|
+
@click="wrapBreadcrumbs"
|
28
|
+
>
|
29
|
+
<a-icon>$caret{{ wrapBreadcrumbs_ ? 'Up' : 'Down' }}Icon</a-icon>
|
30
|
+
</v-avatar>
|
31
|
+
|
32
|
+
<div
|
33
|
+
v-else
|
34
|
+
class="expandDummy"
|
35
|
+
/>
|
17
36
|
</div>
|
18
37
|
</template>
|
19
38
|
|
@@ -28,6 +47,8 @@ export default class ABreadcrumbs extends Vue {
|
|
28
47
|
breadcrumbs = []
|
29
48
|
titleCache = {}
|
30
49
|
lastRoute = null
|
50
|
+
expandVisible = false
|
51
|
+
wrapBreadcrumbs_ = false
|
31
52
|
|
32
53
|
created () {
|
33
54
|
this.$events.on(SaveEvent.STOP_SAVING, this.afterSave)
|
@@ -101,20 +122,46 @@ export default class ABreadcrumbs extends Vue {
|
|
101
122
|
}
|
102
123
|
|
103
124
|
this.breadcrumbs = breadcrumbs
|
125
|
+
this.wrapBreadcrumbs_ = false
|
126
|
+
|
127
|
+
this.scrollBreadcrumbs()
|
128
|
+
}
|
129
|
+
|
130
|
+
scrollBreadcrumbs () {
|
131
|
+
this.$nextTick(() => {
|
132
|
+
const objDiv = this.$el.querySelector('.breadcrumbs')
|
133
|
+
if (objDiv.scrollWidth > objDiv.offsetWidth) {
|
134
|
+
objDiv.scrollLeft = objDiv.scrollWidth
|
135
|
+
this.expandVisible = true
|
136
|
+
} else {
|
137
|
+
objDiv.scrollLeft = 0
|
138
|
+
this.expandVisible = false
|
139
|
+
}
|
140
|
+
})
|
104
141
|
}
|
105
142
|
|
106
|
-
|
107
|
-
|
108
|
-
if (
|
109
|
-
|
143
|
+
wrapBreadcrumbs () {
|
144
|
+
this.wrapBreadcrumbs_ = !this.wrapBreadcrumbs_
|
145
|
+
if (this.wrapBreadcrumbs_) {
|
146
|
+
const objDiv = this.$el.querySelector('.breadcrumbs')
|
147
|
+
objDiv.scrollLeft = 0
|
148
|
+
} else {
|
149
|
+
this.scrollBreadcrumbs()
|
110
150
|
}
|
111
|
-
return title.toUpperCase()
|
112
151
|
}
|
113
152
|
}
|
114
153
|
</script>
|
115
154
|
|
116
155
|
|
117
156
|
<style lang="scss" scoped>
|
157
|
+
.a-breadcrumbs {
|
158
|
+
overflow: hidden;
|
159
|
+
}
|
160
|
+
|
161
|
+
.breadcrumbs {
|
162
|
+
overflow: hidden;
|
163
|
+
}
|
164
|
+
|
118
165
|
.item {
|
119
166
|
white-space: nowrap;
|
120
167
|
|
@@ -130,4 +177,14 @@ export default class ABreadcrumbs extends Vue {
|
|
130
177
|
}
|
131
178
|
}
|
132
179
|
}
|
180
|
+
|
181
|
+
.expand {
|
182
|
+
cursor: pointer;
|
183
|
+
margin-top: 1px;
|
184
|
+
}
|
185
|
+
|
186
|
+
.expandDummy {
|
187
|
+
width: 1.5rem;
|
188
|
+
height: 1.5rem;
|
189
|
+
}
|
133
190
|
</style>
|
package/src/components/AIcon.vue
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
<template>
|
2
2
|
<v-icon
|
3
|
-
:class="{
|
3
|
+
:class="{button}"
|
4
4
|
v-bind="$attrs"
|
5
5
|
v-on="$listeners"
|
6
6
|
>
|
@@ -13,17 +13,14 @@
|
|
13
13
|
import { Component, Vue } from '@a-vue'
|
14
14
|
|
15
15
|
@Component({
|
16
|
-
props: [
|
16
|
+
props: [{button: false}]
|
17
17
|
})
|
18
18
|
export default class AIcon extends Vue {
|
19
|
-
get isButton () {
|
20
|
-
return this.button !== undefined
|
21
|
-
}
|
22
19
|
}
|
23
20
|
</script>
|
24
21
|
|
25
22
|
<style lang="scss" scoped>
|
26
|
-
.v-icon:not(.
|
23
|
+
.v-icon:not(.button)::after {
|
27
24
|
background: none;
|
28
25
|
}
|
29
26
|
</style>
|
@@ -4,85 +4,84 @@
|
|
4
4
|
v-if="editor"
|
5
5
|
class="menu-bar"
|
6
6
|
>
|
7
|
-
<
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
</v-btn>
|
7
|
+
<div>
|
8
|
+
<v-btn
|
9
|
+
small
|
10
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('bold')}]"
|
11
|
+
@click="editor.chain().focus().toggleBold().run()"
|
12
|
+
>
|
13
|
+
<v-icon>{{ boldIcon }}</v-icon>
|
14
|
+
</v-btn>
|
15
|
+
|
16
|
+
<v-btn
|
17
|
+
small
|
18
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('italic')}]"
|
19
|
+
@click="editor.chain().focus().toggleItalic().run()"
|
20
|
+
>
|
21
|
+
<v-icon>{{ italicIcon }}</v-icon>
|
22
|
+
</v-btn>
|
23
|
+
|
24
|
+
<v-btn
|
25
|
+
small
|
26
|
+
:class="['menu-button', 'strike', {'is-active': focus && editor.isActive('strike')}]"
|
27
|
+
@click="editor.chain().focus().toggleStrike().run()"
|
28
|
+
>
|
29
|
+
<v-icon>{{ strikeIcon }}</v-icon>
|
30
|
+
</v-btn>
|
31
|
+
|
32
|
+
<v-btn
|
33
|
+
small
|
34
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 1})}]"
|
35
|
+
@click="editor.chain().focus().toggleHeading({level: 1}).run()"
|
36
|
+
>
|
37
|
+
<v-icon>{{ h1Icon }}</v-icon>
|
38
|
+
</v-btn>
|
39
|
+
|
40
|
+
<v-btn
|
41
|
+
small
|
42
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('heading', {level: 2})}]"
|
43
|
+
@click="editor.chain().focus().toggleHeading({level: 2}).run()"
|
44
|
+
>
|
45
|
+
<v-icon>{{ h2Icon }}</v-icon>
|
46
|
+
</v-btn>
|
47
|
+
|
48
|
+
<v-btn
|
49
|
+
small
|
50
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('bulletList')}]"
|
51
|
+
@click="editor.chain().focus().toggleBulletList().run()"
|
52
|
+
>
|
53
|
+
<v-icon>{{ ulIcon }}</v-icon>
|
54
|
+
</v-btn>
|
55
|
+
|
56
|
+
<v-btn
|
57
|
+
small
|
58
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('orderedList')}]"
|
59
|
+
@click="editor.chain().focus().toggleOrderedList().run()"
|
60
|
+
>
|
61
|
+
<v-icon>{{ olIcon }}</v-icon>
|
62
|
+
</v-btn>
|
63
|
+
|
64
|
+
<v-btn
|
65
|
+
small
|
66
|
+
:class="['menu-button', {'is-active': focus && editor.isActive('blockquote')}]"
|
67
|
+
@click="editor.chain().focus().toggleBlockquote().run()"
|
68
|
+
>
|
69
|
+
<v-icon>{{ commentIcon }}</v-icon>
|
70
|
+
</v-btn>
|
71
|
+
|
72
|
+
<v-btn
|
73
|
+
small
|
74
|
+
class="menu-button"
|
75
|
+
:disabled="initialValue === editor.getHTML()"
|
76
|
+
@click="editor.chain().focus().undo().run()"
|
77
|
+
>
|
78
|
+
<v-icon>{{ undoIcon }}</v-icon>
|
79
|
+
</v-btn>
|
80
|
+
</div>
|
81
|
+
|
82
|
+
<div>
|
83
|
+
<slot name="buttons" />
|
84
|
+
</div>
|
86
85
|
</div>
|
87
86
|
|
88
87
|
<editor-content
|
@@ -119,6 +118,7 @@ import {
|
|
119
118
|
export default class ARichTextArea extends Vue {
|
120
119
|
editor = null
|
121
120
|
internalValue = null
|
121
|
+
initialValue = null
|
122
122
|
focus = false
|
123
123
|
|
124
124
|
boldIcon = mdiFormatBold
|
@@ -133,6 +133,7 @@ export default class ARichTextArea extends Vue {
|
|
133
133
|
redoIcon = mdiRotateRight
|
134
134
|
|
135
135
|
created () {
|
136
|
+
this.initialValue = this.value
|
136
137
|
this.internalValue = this.value
|
137
138
|
}
|
138
139
|
|
@@ -158,14 +159,20 @@ export default class ARichTextArea extends Vue {
|
|
158
159
|
this.$emit('blur')
|
159
160
|
}
|
160
161
|
})
|
161
|
-
|
162
|
-
this.editor.commands.setContent(this.internalValue, false)
|
163
162
|
}
|
164
163
|
|
165
164
|
beforeDestroy () {
|
166
165
|
this.editor.destroy()
|
167
166
|
}
|
168
167
|
|
168
|
+
/**
|
169
|
+
* reset the text area to disable the undo button
|
170
|
+
* e.g. after saving the form while keeping it open
|
171
|
+
*/
|
172
|
+
reset () {
|
173
|
+
this.initialValue = this.value
|
174
|
+
}
|
175
|
+
|
169
176
|
@Watch('value')
|
170
177
|
valueChanged () {
|
171
178
|
this.internalValue = this.value
|
@@ -192,10 +199,6 @@ export default class ARichTextArea extends Vue {
|
|
192
199
|
|
193
200
|
|
194
201
|
<style lang="scss" scoped>
|
195
|
-
.v-input:not(.v-input--is-focused) ::v-deep .v-counter {
|
196
|
-
display: none;
|
197
|
-
}
|
198
|
-
|
199
202
|
.a-rich-text-editor {
|
200
203
|
::v-deep .ProseMirror {
|
201
204
|
&-focused {
|
@@ -205,9 +208,12 @@ export default class ARichTextArea extends Vue {
|
|
205
208
|
}
|
206
209
|
|
207
210
|
.menu-bar {
|
211
|
+
display: flex;
|
212
|
+
justify-content: space-between;
|
208
213
|
margin: -.2rem 0 .5rem -.2rem;
|
209
214
|
}
|
210
215
|
|
216
|
+
.menu-bar .v-btn,
|
211
217
|
.menu-button {
|
212
218
|
padding: 0 !important;
|
213
219
|
width: 30px !important;
|
@@ -239,6 +245,10 @@ export default class ARichTextArea extends Vue {
|
|
239
245
|
&.is-active {
|
240
246
|
background: #ECECEC !important;
|
241
247
|
}
|
248
|
+
|
249
|
+
&[disabled] {
|
250
|
+
background: none !important;
|
251
|
+
}
|
242
252
|
}
|
243
253
|
|
244
254
|
::v-deep .ProseMirror {
|
package/src/components/ARow.vue
CHANGED
@@ -58,13 +58,6 @@ export default class ARow extends Vue {
|
|
58
58
|
<style scoped lang="scss">
|
59
59
|
.a-row {
|
60
60
|
display: flex;
|
61
|
-
overflow: hidden;
|
62
|
-
@media (max-width: 900px), (orientation : portrait) {
|
63
|
-
flex-wrap: wrap;
|
64
|
-
& > * {
|
65
|
-
flex: 0 0 auto;
|
66
|
-
}
|
67
|
-
}
|
68
61
|
|
69
62
|
&.full {
|
70
63
|
width: 100%;
|
@@ -28,11 +28,17 @@ export default class EditForm extends Vue {
|
|
28
28
|
modelToEdit = null
|
29
29
|
valid = false
|
30
30
|
lastJson = null
|
31
|
+
forcedUnchange = false
|
31
32
|
|
32
33
|
created () {
|
33
34
|
this.reset()
|
34
35
|
}
|
35
36
|
|
37
|
+
forceUnchanged () {
|
38
|
+
this.forcedUnchange = true
|
39
|
+
this.$emit('update:changed', false)
|
40
|
+
}
|
41
|
+
|
36
42
|
reset () {
|
37
43
|
if (this.createModelToEdit) {
|
38
44
|
this.modelToEdit = this.createModelToEdit(this.model)
|
@@ -66,6 +72,9 @@ export default class EditForm extends Vue {
|
|
66
72
|
}
|
67
73
|
|
68
74
|
get changed () {
|
75
|
+
if (this.forcedUnchange) {
|
76
|
+
return false
|
77
|
+
}
|
69
78
|
// console.log(this.json)
|
70
79
|
// console.log(this.lastJson)
|
71
80
|
return this.json !== this.lastJson
|
@@ -125,6 +125,8 @@ export default class EditModal extends Vue {
|
|
125
125
|
* hook to allow to leave a just created (saved) model
|
126
126
|
*/
|
127
127
|
ignoreChangesOnClose () {
|
128
|
+
// this.$refs.form.forceUnchanged()
|
129
|
+
console.info('TODO switch form to forceUnchanged')
|
128
130
|
this.ignoreChangesOnClose_ = true
|
129
131
|
}
|
130
132
|
}
|
@@ -70,10 +70,19 @@ export class FormFieldMixin extends Vue {
|
|
70
70
|
|
71
71
|
if (field.hasOptions()) {
|
72
72
|
const options = field.getOptions()
|
73
|
-
return
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
return options.map((value, index) => {
|
74
|
+
if (typeof value === 'object') { // object option
|
75
|
+
return {
|
76
|
+
itemText: value.title,
|
77
|
+
itemValue: value.value
|
78
|
+
}
|
79
|
+
} else { // scalar option
|
80
|
+
return {
|
81
|
+
itemText: value,
|
82
|
+
itemValue: index
|
83
|
+
}
|
84
|
+
}
|
85
|
+
})
|
77
86
|
}
|
78
87
|
}
|
79
88
|
|
@@ -11,6 +11,7 @@ import { FilterSourceType } from './FilterSourceType'
|
|
11
11
|
'listAction',
|
12
12
|
'filterHistoryKey',
|
13
13
|
'loadOnlyIfKeyword',
|
14
|
+
'checkBeforeLoad',
|
14
15
|
{
|
15
16
|
filterSource: FilterSourceType.QUERY_STRING,
|
16
17
|
events: true,
|
@@ -64,9 +65,16 @@ export class ListViewMixin extends Vue {
|
|
64
65
|
.on('change', this.filtersChanged) // listen to change
|
65
66
|
|
66
67
|
this._filtersInitialized()
|
68
|
+
this.$emit('update:filters', this.filters)
|
69
|
+
this.$emit('update:listViewModel', this.listViewModel)
|
67
70
|
|
68
71
|
if (this.models) {
|
69
72
|
this.$emit('update:count', this.meta_.count_search)
|
73
|
+
|
74
|
+
this.$emit('onLoad', {
|
75
|
+
models: this.models_,
|
76
|
+
meta: this.meta_
|
77
|
+
})
|
70
78
|
} else {
|
71
79
|
this.load()
|
72
80
|
}
|
@@ -113,6 +121,16 @@ export class ListViewMixin extends Vue {
|
|
113
121
|
}
|
114
122
|
|
115
123
|
async load () {
|
124
|
+
if (this.checkBeforeLoad) {
|
125
|
+
const canLoad = await this.checkBeforeLoad()
|
126
|
+
if (!canLoad) {
|
127
|
+
if (this.meta_.used_filters) {
|
128
|
+
this.listViewModel.initFromUsedFilters(this.meta_.used_filters, this.meta_.count_search)
|
129
|
+
}
|
130
|
+
return
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
116
134
|
if (this._loadOnlyIfKeyword && !this.filters.q.value) {
|
117
135
|
this.models_ = []
|
118
136
|
this.meta_ = {}
|
@@ -132,7 +150,7 @@ export class ListViewMixin extends Vue {
|
|
132
150
|
|
133
151
|
if (!models) { // error happened
|
134
152
|
this.isLoading = false
|
135
|
-
this.$emit('update:isLoading',
|
153
|
+
this.$emit('update:isLoading', false)
|
136
154
|
return
|
137
155
|
}
|
138
156
|
|
@@ -144,8 +162,13 @@ export class ListViewMixin extends Vue {
|
|
144
162
|
}
|
145
163
|
|
146
164
|
this.isLoading = false
|
147
|
-
this.$emit('update:isLoading',
|
165
|
+
this.$emit('update:isLoading', false)
|
148
166
|
|
149
167
|
this.$emit('update:count', this.meta_.count_search)
|
168
|
+
|
169
|
+
this.$emit('onLoad', {
|
170
|
+
models,
|
171
|
+
meta
|
172
|
+
})
|
150
173
|
}
|
151
174
|
}
|
@@ -25,10 +25,14 @@ export class ClickOutsideMixin extends Vue {
|
|
25
25
|
// popup clicked
|
26
26
|
const thisIndex = getZIndex(this.$el)
|
27
27
|
const targetIndex = getZIndex(e.target)
|
28
|
-
if (targetIndex > thisIndex) {
|
28
|
+
if (targetIndex > 10 && targetIndex > thisIndex) { // sidebar === 6
|
29
29
|
return
|
30
30
|
}
|
31
31
|
|
32
|
+
this.com_onClickOutside()
|
32
33
|
this.$emit('click:outside')
|
33
34
|
}
|
35
|
+
|
36
|
+
com_onClickOutside () {
|
37
|
+
}
|
34
38
|
}
|
@@ -32,9 +32,16 @@ function propsWithDefaults (props) {
|
|
32
32
|
// property: { some object }, should be a normal vue props object
|
33
33
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
34
34
|
normalizedProps[subProp] = value
|
35
|
-
// property: true, null, ...
|
35
|
+
// property: true, false, null, ...
|
36
36
|
} else {
|
37
|
-
|
37
|
+
if (typeof value === 'boolean') {
|
38
|
+
normalizedProps[subProp] = {
|
39
|
+
type: Boolean,
|
40
|
+
default: value
|
41
|
+
}
|
42
|
+
} else {
|
43
|
+
normalizedProps[subProp] = { default: value }
|
44
|
+
}
|
38
45
|
}
|
39
46
|
})
|
40
47
|
} else {
|
package/src/events.js
CHANGED