@afeefa/vue-app 0.0.172 → 0.0.174

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. package/.afeefa/package/release/version.txt +1 -1
  2. package/package.json +1 -1
  3. package/src/components/AInfo.vue +11 -1
  4. package/src/components/ALoadingIndicator.vue +4 -0
  5. package/src/components/ATable.vue +9 -4
  6. package/src/components/ATableHeader.vue +3 -1
  7. package/src/components/ATableRow.vue +11 -5
  8. package/src/plugins/route-config/RouteConfigPlugin.js +17 -0
  9. package/src/utils/format-time.js +17 -0
  10. package/src-admin/components/App.vue +48 -186
  11. package/src-admin/components/CollapsibleSection.vue +64 -0
  12. package/src-admin/components/FlyingContextContainer.vue +1 -1
  13. package/src-admin/components/HSeparator.vue +90 -0
  14. package/src-admin/components/InformationBar.vue +176 -0
  15. package/src-admin/components/NavigationBar.vue +187 -0
  16. package/src-admin/components/StickyHeader.vue +39 -8
  17. package/src-admin/components/app/AppBarTitle.vue +6 -11
  18. package/src-admin/components/detail/DetailProperty.vue +11 -16
  19. package/src-admin/components/form/RemoveDialog.vue +1 -1
  20. package/src-admin/components/index.js +9 -2
  21. package/src-admin/components/list/ListView.vue +31 -29
  22. package/src-admin/components/sidebar/InformationBarItem.vue +178 -0
  23. package/src-admin/components/sidebar/SidebarEvent.js +5 -0
  24. package/src-admin/components/sidebar/SidebarService.js +107 -0
  25. package/src-admin/components/transitions/CollapseTransition.vue +61 -0
  26. package/src-admin/config/vuetify.js +9 -1
  27. package/src-admin/events.js +1 -0
  28. package/src-admin/styles.scss +10 -2
  29. package/src-admin/components/Sidebar.vue +0 -66
  30. package/src-admin/components/SidebarItem.vue +0 -59
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <div
3
+ v-show="visible"
4
+ id="information-bar"
5
+ :class="{mobile, rail}"
6
+ >
7
+ <div :class="['toggleButton', {rail}]">
8
+ <v-app-bar-nav-icon
9
+ :title="'Menü ' + (rail ? 'ausklappen' : 'einklappen')"
10
+ @click="toggleRail"
11
+ >
12
+ <v-icon>
13
+ {{ rail ? openMenuIcon : closeMenuIcon }}
14
+ </v-icon>
15
+ </v-app-bar-nav-icon>
16
+ </div>
17
+
18
+ <div
19
+ id="information-bar__children"
20
+ :class="{rail}"
21
+ >
22
+ <div class="top" />
23
+
24
+ <div class="bottom" />
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script>
30
+ import { Component, Vue, Watch } from '@a-vue'
31
+ import { mdiBackburger, mdiForwardburger } from '@mdi/js'
32
+ import { SidebarEvent } from '@a-admin/events'
33
+ import { sidebarService, SidebarState } from './sidebar/SidebarService'
34
+
35
+ @Component
36
+ export default class InformationBar extends Vue {
37
+ openMenuIcon = mdiBackburger
38
+ closeMenuIcon = mdiForwardburger
39
+
40
+ visible = false
41
+ mobile = false
42
+ rail = false
43
+
44
+ created () {
45
+ this.$events.on(SidebarEvent.STATUS, ({payload: {information, informationRailed, mobile}}) => {
46
+ this.visible = information
47
+ this.rail = informationRailed
48
+ this.mobile = mobile
49
+ })
50
+ }
51
+
52
+ mounted () {
53
+ this.mutationWatcher = new MutationObserver(this.domChanged)
54
+ this.mutationWatcher.observe(this.$el.querySelector('#information-bar__children > .top'), { childList: true })
55
+ this.mutationWatcher.observe(this.$el.querySelector('#information-bar__children > .bottom'), { childList: true })
56
+ this.domChanged()
57
+ }
58
+
59
+ /**
60
+ * park sidebar children and attach - only for use with HMR which would remove all children on destroy 234567890
61
+ *
62
+ * s
63
+ */
64
+ destroyed () {
65
+ function moveChildren (from, to) {
66
+ for (const child of from.children) {
67
+ to.appendChild(child)
68
+ }
69
+ }
70
+
71
+ const elTop = document.createElement('div')
72
+ document.body.appendChild(elTop)
73
+ moveChildren(this.$el.querySelector('#information-bar__children .top'), elTop)
74
+
75
+ const elBottom = document.createElement('div')
76
+ document.body.appendChild(elBottom)
77
+ moveChildren(this.$el.querySelector('#information-bar__children .bottom'), elBottom)
78
+
79
+ this.$nextTick(() => {
80
+ moveChildren(elTop, document.querySelector('#information-bar__children .top'))
81
+ moveChildren(elBottom, document.querySelector('#information-bar__children .bottom'))
82
+ this.$nextTick(() => {
83
+ this.$events.dispatch(new SidebarEvent(SidebarEvent.STATUS, new SidebarState(sidebarService)))
84
+ })
85
+ })
86
+ }
87
+
88
+ @Watch('rail')
89
+ railChanged () {
90
+ this.$el.style.marginRight = this.rail ? '-240px' : 0
91
+ }
92
+
93
+ toggleRail () {
94
+ sidebarService.setRailInformation(!this.rail)
95
+ }
96
+
97
+ domChanged () {
98
+ const old = this.visible
99
+ const visible = this.hasSidebarItems()
100
+
101
+ if (visible && !old) {
102
+ sidebarService.setInformation(true)
103
+ }
104
+
105
+ if (!visible && old) {
106
+ sidebarService.setInformation(false)
107
+ }
108
+ }
109
+
110
+ getChildrenContainer () {
111
+ return this.$el.querySelector('#information-bar__children')
112
+ }
113
+
114
+ hasSidebarItems () {
115
+ return !!(this.$el.querySelector('#information-bar__children .top').children.length +
116
+ this.$el.querySelector('#information-bar__children .bottom').children.length)
117
+ }
118
+
119
+ hideSidebar () {
120
+ sidebarService.setInformation(false)
121
+ }
122
+ }
123
+ </script>
124
+
125
+
126
+ <style lang="scss" scoped>
127
+ #information-bar {
128
+ position: relative;
129
+
130
+ flex: 0 0 300px;
131
+ width: 300px;
132
+ height: 100vh;
133
+ overflow-y: auto;
134
+
135
+ border-left: 1px solid rgba(0, 0, 0, .12);
136
+ transition: all .2s;
137
+
138
+ background: white;
139
+ padding: 4rem 1.5rem 1.5rem;
140
+
141
+ &.rail {
142
+ padding: 4rem .8rem .8rem;
143
+ }
144
+
145
+ &.mobile {
146
+ position: fixed;
147
+ z-index: 299;
148
+ right: 0;
149
+
150
+ &:not(.rail) {
151
+ border-left: none;
152
+ box-shadow: 0 0 7px #00000066;
153
+ }
154
+ }
155
+ }
156
+
157
+ .toggleButton {
158
+ position: absolute;
159
+ top: 0;
160
+ left: .3rem;
161
+ }
162
+
163
+ #information-bar__children {
164
+ display: flex;
165
+ flex-direction: column;
166
+ justify-content: space-between;
167
+ height: 100%;
168
+ }
169
+
170
+ .top, .bottom {
171
+ display: flex;
172
+ flex-direction: column;
173
+ align-items: flex-start;
174
+ gap: .75rem;
175
+ }
176
+ </style>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <div
3
+ id="navigation-bar"
4
+ :class="{mobile}"
5
+ >
6
+ <div class="toggleButton">
7
+ <v-app-bar-nav-icon
8
+ title="Menü schließen"
9
+ @click="hideSidebar"
10
+ >
11
+ <v-icon>{{ closeMenuIcon }}</v-icon>
12
+ </v-app-bar-nav-icon>
13
+ </div>
14
+
15
+ <router-link
16
+ :to="{name: rootRouteName}"
17
+ class="logoContainer d-flex flex-column align-center pa-6"
18
+ >
19
+ <img
20
+ v-if="logoUrl"
21
+ class="logo"
22
+ :src="logoUrl"
23
+ >
24
+ <div class="text-button">
25
+ {{ title }}
26
+ </div>
27
+ </router-link>
28
+
29
+ <component
30
+ :is="SidebarMenu"
31
+ class="px-0 mt-0 flex-grow-1"
32
+ />
33
+
34
+ <v-container
35
+ v-if="hasAuthService"
36
+ d-flex
37
+ align-center
38
+ gap-4
39
+ pa-6
40
+ pb-8
41
+ >
42
+ <div class="d-flex align-center gap-3">
43
+ <v-avatar
44
+ color="primary white--text"
45
+ size="40"
46
+ >
47
+ {{ account.first_name.charAt(0).toUpperCase() }}{{ account.last_name.charAt(0).toUpperCase() }}
48
+ </v-avatar>
49
+
50
+ <div>
51
+ <div class="accountName">
52
+ {{ account.first_name }}
53
+ <template v-if="$auth.roles[0]">
54
+ ({{ $auth.roles[0].title }})
55
+ </template>
56
+ </div>
57
+
58
+ <div class="body-2 d-flex align-center">
59
+ <v-icon class="ml-n1 mr-1">
60
+ $logoutIcon
61
+ </v-icon>
62
+ <a @click="logout()">Logout</a>
63
+ </div>
64
+ </div>
65
+
66
+ <a-context-menu v-if="$has.settings">
67
+ <a-context-menu-item
68
+ :to="{name: 'settings', params: {accountId: account.id}}"
69
+ >
70
+ <v-icon>$pencilIcon</v-icon>
71
+ Einstellungen
72
+ </a-context-menu-item>
73
+ </a-context-menu>
74
+ </div>
75
+ </v-container>
76
+ </div>
77
+ </template>
78
+
79
+ <script>
80
+ import { Component, Vue, Watch } from '@a-vue'
81
+ import { mdiBackburger } from '@mdi/js'
82
+ import { adminConfig } from '@a-admin/config/AdminConfig'
83
+ import { SidebarEvent } from '@a-admin/events'
84
+ import { sidebarService } from './sidebar/SidebarService'
85
+
86
+ @Component
87
+ export default class NavigationBar extends Vue {
88
+ $hasOptions = ['settings']
89
+
90
+ closeMenuIcon = mdiBackburger
91
+
92
+ visible = true
93
+ mobile = false
94
+
95
+ created () {
96
+ this.$events.on(SidebarEvent.STATUS, ({payload: {navigation, mobile}}) => {
97
+ this.visible = navigation
98
+ this.mobile = mobile
99
+ })
100
+
101
+ this.mobile = sidebarService.mobile
102
+ }
103
+
104
+ @Watch('visible')
105
+ visibleChanged () {
106
+ this.$el.style.marginLeft = this.visible ? 0 : '-300px'
107
+ }
108
+
109
+ hideSidebar () {
110
+ sidebarService.setNavigation(false)
111
+ }
112
+
113
+ get rootRouteName () {
114
+ return adminConfig.app.rootRouteName || 'root'
115
+ }
116
+
117
+ get logoUrl () {
118
+ return adminConfig.app.logo
119
+ }
120
+
121
+ get title () {
122
+ return adminConfig.app.title
123
+ }
124
+
125
+ get SidebarMenu () {
126
+ return adminConfig.app.components.SidebarMenu
127
+ }
128
+
129
+ get hasAuthService () {
130
+ return !!this.$auth
131
+ }
132
+
133
+ get account () {
134
+ if (this.hasAuthService) {
135
+ return this.$auth.account
136
+ }
137
+ return null
138
+ }
139
+ }
140
+ </script>
141
+
142
+ <style lang="scss" scoped>
143
+ #navigation-bar {
144
+ position: relative;
145
+
146
+ flex: 0 0 300px;
147
+ width: 300px;
148
+ height: 100vh;
149
+ overflow: hidden;
150
+
151
+ border-right: 1px solid rgba(0, 0, 0, .12);
152
+ transition: all .2s;
153
+
154
+ background: white;
155
+ padding-top: 2rem;
156
+
157
+ display: flex;
158
+ flex-direction: column;
159
+ justify-content: space-between;
160
+
161
+ &.mobile {
162
+ position: fixed;
163
+ box-shadow: 0 0 7px #00000066;
164
+ z-index: 299;
165
+ }
166
+ }
167
+
168
+ .toggleButton {
169
+ position: absolute;
170
+ top: 0;
171
+ right: .3rem;
172
+ }
173
+
174
+ .accountName {
175
+ line-height: 1.2;
176
+ word-break: break-all;
177
+ }
178
+
179
+ .logoContainer {
180
+ text-decoration: none;
181
+ }
182
+
183
+ .logo {
184
+ max-height: 80px;
185
+ max-width: 90%;
186
+ }
187
+ </style>
@@ -1,10 +1,23 @@
1
1
  <template>
2
2
  <div
3
3
  id="stickyHeader"
4
- :class="['d-flex align-center gap-8', {visible}]"
4
+ :class="{visible}"
5
5
  >
6
- <app-bar-title-container class="appBarTitle flex-grow-1" />
7
- <app-bar-buttons class="appBarButtons mr-2" />
6
+ <a-row class="topbar">
7
+ <v-app-bar-nav-icon
8
+ v-if="sidebarIconVisible"
9
+ class="mr-2 ml-3"
10
+ title="Menu öffnen"
11
+ @click="openSidebar"
12
+ />
13
+
14
+ <a-breadcrumbs />
15
+ </a-row>
16
+
17
+ <div :class="['d-flex align-center gap-8 mt-2']">
18
+ <app-bar-title-container class="appBarTitle flex-grow-1" />
19
+ <app-bar-buttons class="appBarButtons mr-2" />
20
+ </div>
8
21
  </div>
9
22
  </template>
10
23
 
@@ -12,6 +25,8 @@
12
25
  import { Component, Vue } from '@a-vue'
13
26
  import AppBarButtons from './app/AppBarButtons'
14
27
  import AppBarTitleContainer from './app/AppBarTitleContainer'
28
+ import { SidebarEvent } from '@a-admin/events'
29
+ import { sidebarService } from './sidebar/SidebarService'
15
30
 
16
31
  @Component({
17
32
  components: {
@@ -22,6 +37,12 @@ import AppBarTitleContainer from './app/AppBarTitleContainer'
22
37
  export default class StickyHeader extends Vue {
23
38
  visible = false
24
39
 
40
+ sidebarIconVisible = false
41
+
42
+ created () {
43
+ this.$events.on(SidebarEvent.STATUS, ({payload: {navigation, mobile}}) => (this.sidebarIconVisible = !navigation || mobile))
44
+ }
45
+
25
46
  mounted () {
26
47
  // watch mutation
27
48
  this.mutationWatcher = new MutationObserver(this.domChanged)
@@ -34,7 +55,7 @@ export default class StickyHeader extends Vue {
34
55
  ([e]) => {
35
56
  e.target.classList.toggle('is-pinned', e.intersectionRatio < 1)
36
57
  },
37
- { threshold: [1] }
58
+ { threshold: 1 }
38
59
  )
39
60
  observer.observe(el)
40
61
 
@@ -49,6 +70,10 @@ export default class StickyHeader extends Vue {
49
70
  return !!(this.$el.querySelector('.appBarTitle').children.length +
50
71
  this.$el.querySelector('.appBarButtons').children.length)
51
72
  }
73
+
74
+ openSidebar () {
75
+ sidebarService.setNavigation(true)
76
+ }
52
77
  }
53
78
  </script>
54
79
 
@@ -56,9 +81,9 @@ export default class StickyHeader extends Vue {
56
81
  <style lang="scss" scoped>
57
82
  #stickyHeader {
58
83
  position: sticky;
59
- top: -1px;
60
- margin: 0 -2rem 2rem;
61
- padding: 1rem 2rem;
84
+ left: 0;
85
+ top: -32px;
86
+ padding: .75rem 2rem;
62
87
  background-color: white;
63
88
 
64
89
  &:not(.visible) {
@@ -68,7 +93,13 @@ export default class StickyHeader extends Vue {
68
93
  &.is-pinned {
69
94
  background: white;
70
95
  z-index: 2;
71
- box-shadow: 0 4px 7px -4px #00000033;
96
+ box-shadow: 0 0 7px #00000033;
97
+ }
98
+
99
+ .topbar {
100
+ margin-left: -18px;
101
+ margin-top: -18px;
102
+ height:40px;
72
103
  }
73
104
  }
74
105
  </style>
@@ -14,18 +14,13 @@
14
14
  </v-icon>
15
15
  </v-btn>
16
16
 
17
- <v-avatar
18
- color="#F4F4F4"
19
- size="3rem"
20
- >
21
- <v-icon
22
- :color="icon.color"
23
- size="2.2rem"
24
- v-text="icon.icon"
25
- />
26
- </v-avatar>
17
+ <v-icon
18
+ :color="icon.color"
19
+ size="3.5rem"
20
+ v-text="icon.icon"
21
+ />
27
22
 
28
- <div class="titleContainer">
23
+ <div class="titleContainer ml-n2">
29
24
  <h3 v-if="subtitle">
30
25
  {{ subtitle }}
31
26
  </h3>
@@ -1,18 +1,13 @@
1
1
  <template>
2
2
  <div class="detailProperty">
3
3
  <div class="header">
4
- <v-avatar
4
+ <v-icon
5
5
  v-if="_icon"
6
- color="#F4F4F4"
7
- size="2.5rem"
6
+ :color="_icon.color"
7
+ size="3rem"
8
8
  >
9
- <v-icon
10
- :color="_icon.color"
11
- size="1.5rem"
12
- >
13
- {{ _icon.icon }}
14
- </v-icon>
15
- </v-avatar>
9
+ {{ _icon.icon }}
10
+ </v-icon>
16
11
 
17
12
  <div
18
13
  v-else
@@ -70,26 +65,26 @@ export default class DetailProperty extends Vue {
70
65
  height: 40px;
71
66
  margin-bottom: .5rem;
72
67
 
73
- .v-avatar {
74
- flex: 0 0 40px;
75
- margin-right: 15px;
68
+ > .v-icon {
69
+ flex: 0 0 3rem;
70
+ margin-right: 1.5rem;
76
71
  }
77
72
 
78
73
  .iconPlaceholder {
79
- width: 55px;
74
+ width: 4.5rem;
80
75
  }
81
76
 
82
77
  .label {
83
78
  display: block;
84
79
  text-transform: uppercase;
85
80
  letter-spacing: 3px;
86
- border-bottom: 5px solid #CCCCCC;
81
+ border-bottom: 5px solid #DDDDDD;
87
82
  color: #666666;
88
83
  }
89
84
  }
90
85
 
91
86
  .content {
92
- padding-left: 55px;
87
+ padding-left: 4.5rem;
93
88
  }
94
89
  }
95
90
  </style>
@@ -47,7 +47,7 @@ export default class RemoveDialog extends Vue {
47
47
 
48
48
  const result = await this.$events.dispatch(new DialogEvent(DialogEvent.SHOW, {
49
49
  id: this.dialogId,
50
- title: this.itemTitle + ' löschen?',
50
+ title: this.title || this.itemTitle + ' löschen?',
51
51
  message: ['Soll ' + this.itemTitle + ' gelöscht werden?', this.message].filter(m => m).join('<br><br>'),
52
52
  info: this.info,
53
53
  yesButton: 'Löschen',
@@ -2,6 +2,7 @@ import Vue from 'vue'
2
2
 
3
3
  import AppBarButton from './app/AppBarButton'
4
4
  import AppBarTitle from './app/AppBarTitle'
5
+ import CollapsibleSection from './CollapsibleSection.vue'
5
6
  import DetailColumn from './detail/DetailColumn'
6
7
  import DetailContent from './detail/DetailContent'
7
8
  import DetailMeta from './detail/DetailMeta'
@@ -11,6 +12,7 @@ import FlyingContext from './FlyingContext.vue'
11
12
  import EditFormButtons from './form/EditFormButtons'
12
13
  import RemoveButton from './form/RemoveButton'
13
14
  import RemoveDialog from './form/RemoveDialog'
15
+ import HSeparator from './HSeparator.vue'
14
16
  import ListCard from './list/ListCard'
15
17
  import ListColumnHeader from './list/ListColumnHeader'
16
18
  import ListContent from './list/ListContent'
@@ -20,9 +22,10 @@ import ListView from './list/ListView'
20
22
  import ModelCount from './model/ModelCount'
21
23
  import ModelIcon from './model/ModelIcon'
22
24
  import EditPage from './pages/EditPage'
23
- import SidebarItem from './SidebarItem.vue'
25
+ import InformationBarItem from './sidebar/InformationBarItem.vue'
24
26
  import Start from './Start.vue'
25
27
  import StickyFooter from './StickyFooter.vue'
28
+ import CollapseTransition from './transitions/CollapseTransition.vue'
26
29
 
27
30
  Vue.component('ListCard', ListCard)
28
31
  Vue.component('ListColumnHeader', ListColumnHeader)
@@ -48,7 +51,11 @@ Vue.component('DetailColumn', DetailColumn)
48
51
  Vue.component('AppBarButton', AppBarButton)
49
52
  Vue.component('AppBarTitle', AppBarTitle)
50
53
 
54
+ Vue.component('HSeparator', HSeparator)
55
+ Vue.component('CollapsibleSection', CollapsibleSection)
56
+ Vue.component('CollapseTransition', CollapseTransition)
57
+
51
58
  Vue.component('Start', Start)
52
59
  Vue.component('FlyingContext', FlyingContext)
53
60
  Vue.component('StickyFooter', StickyFooter)
54
- Vue.component('SidebarItem', SidebarItem)
61
+ Vue.component('InformationBarItem', InformationBarItem)
@@ -25,35 +25,37 @@
25
25
  </template>
26
26
 
27
27
  <template v-else-if="$scopedSlots['model-table']">
28
- <a-table>
29
- <a-table-header>
30
- <div v-if="$has.icon" />
31
-
32
- <slot name="header-table" />
33
- </a-table-header>
34
-
35
- <a-table-row
36
- v-for="model in models_"
37
- :key="model.id"
38
- v-flying-context-trigger="hasFlyingContext"
39
- :class="getRowClasses(model)"
40
- v-bind="getRowAttributes(model)"
41
- v-on="getRowListeners(model)"
42
- @click="emitFlyingContext(model)"
43
- >
44
- <v-icon
45
- v-if="$has.icon"
46
- :color="model.getIcon().color"
47
- size="1.2rem"
48
- v-text="model.getIcon().icon"
49
- />
50
-
51
- <slot
52
- name="model-table"
53
- :model="model"
54
- />
55
- </a-table-row>
56
- </a-table>
28
+ <div class="a-table-wrapper">
29
+ <a-table>
30
+ <a-table-header>
31
+ <div v-if="$has.icon" />
32
+
33
+ <slot name="header-table" />
34
+ </a-table-header>
35
+
36
+ <a-table-row
37
+ v-for="model in models_"
38
+ :key="model.id"
39
+ v-flying-context-trigger="hasFlyingContext"
40
+ :class="getRowClasses(model)"
41
+ v-bind="getRowAttributes(model)"
42
+ v-on="getRowListeners(model)"
43
+ @click="emitFlyingContext(model)"
44
+ >
45
+ <v-icon
46
+ v-if="$has.icon"
47
+ :color="model.getIcon().color"
48
+ size="1.2rem"
49
+ v-text="model.getIcon().icon"
50
+ />
51
+
52
+ <slot
53
+ name="model-table"
54
+ :model="model"
55
+ />
56
+ </a-table-row>
57
+ </a-table>
58
+ </div>
57
59
  </template>
58
60
 
59
61
  <template v-else-if="$scopedSlots.model">