@ditojs/admin 2.3.0 → 2.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "type": "module",
5
5
  "description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/admin",
@@ -33,7 +33,7 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.3.0",
36
+ "@ditojs/ui": "^2.3.2",
37
37
  "@ditojs/utils": "^2.3.0",
38
38
  "@kyvg/vue3-notification": "^2.9.0",
39
39
  "@lk77/vue3-color": "^3.0.6",
@@ -83,7 +83,7 @@
83
83
  "vite": "^4.3.1"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "81398b333e755f30b2c3c1998c8879a3e87f7873",
86
+ "gitHead": "e8034d836e783c606746b8a51b20d8969f1472fc",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
@@ -179,6 +179,7 @@ export default DitoComponent.component('DitoDialog', {
179
179
  background: rgba(0, 0, 0, 0.2);
180
180
 
181
181
  &__focus-trap {
182
+ display: flex;
182
183
  max-height: 100%;
183
184
  }
184
185
 
@@ -10,23 +10,25 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
10
10
  )
11
11
  //- Firefox doesn't like <button> here, so use <a> instead:
12
12
  a.dito-button(
13
- v-if="isDraggable"
13
+ v-if="hasDraggable"
14
14
  v-bind="getButtonAttributes(verbs.drag)"
15
15
  )
16
16
  RouterLink.dito-button(
17
- v-if="isEditable"
18
- :to="{ path: editPath }"
17
+ v-if="hasEditable"
18
+ :class="{ 'dito-disabled': !editPath }"
19
+ :to="editPath ? { path: editPath } : {}"
19
20
  v-bind="getButtonAttributes(verbs.edit)"
20
21
  )
21
22
  DitoCreateButton(
22
- v-if="isCreatable"
23
+ v-if="hasCreatable"
24
+ :class="{ 'dito-disabled': !createPath }"
23
25
  :schema="schema"
24
26
  :path="createPath"
25
27
  :verb="verbs.create"
26
28
  :text="createButtonText"
27
29
  )
28
30
  button.dito-button(
29
- v-if="isDeletable"
31
+ v-if="hasDeletable"
30
32
  type="button"
31
33
  v-bind="getButtonAttributes(verbs.delete)"
32
34
  @click="$emit('delete')"
@@ -61,19 +63,19 @@ export default DitoComponent.component('DitoEditButtons', {
61
63
  return this.getLabel(this.schema.form)
62
64
  },
63
65
 
64
- isDraggable() {
66
+ hasDraggable() {
65
67
  return this.hasOption('draggable')
66
68
  },
67
69
 
68
- isEditable() {
69
- return this.hasOption('editable') && !!this.editPath
70
+ hasEditable() {
71
+ return this.hasOption('editable')
70
72
  },
71
73
 
72
- isCreatable() {
73
- return this.hasOption('creatable') && !!this.createPath
74
+ hasCreatable() {
75
+ return this.hasOption('creatable')
74
76
  },
75
77
 
76
- isDeletable() {
78
+ hasDeletable() {
77
79
  return this.hasOption('deletable')
78
80
  },
79
81
 
@@ -289,8 +289,9 @@ export default DitoComponent.component('DitoForm', {
289
289
  if (
290
290
  param &&
291
291
  this.providesData &&
292
- from.params[param] !== 'create' &&
293
- to.params[param] !== from.params[param]
292
+ from.matched[0].path === to.matched[0].path && // Staying on same form?
293
+ from.params[param] !== 'create' && // But haven't been creating?
294
+ to.params[param] !== from.params[param] // Going to a different entity?
294
295
  ) {
295
296
  this.loadData(true)
296
297
  }
@@ -20,7 +20,7 @@ nav.dito-header
20
20
  v-if="isLoading"
21
21
  )
22
22
  //- Teleport target for `.dito-schema-header`:
23
- .dito-menu
23
+ .dito-header__menu
24
24
  slot
25
25
  </template>
26
26
 
@@ -78,14 +78,14 @@ export default DitoComponent.component('DitoHeader', {
78
78
 
79
79
  .dito-header {
80
80
  background: $color-black;
81
- font-size: $menu-font-size;
82
- line-height: $menu-line-height;
83
- z-index: $menu-z-index;
81
+ font-size: $header-font-size;
82
+ line-height: $header-line-height;
83
+ z-index: $header-z-index;
84
84
  @include user-select(none);
85
85
 
86
86
  span {
87
87
  display: inline-block;
88
- padding: $menu-padding;
88
+ padding: $header-padding;
89
89
  color: $color-white;
90
90
  }
91
91
 
@@ -0,0 +1,172 @@
1
+ <template lang="pug">
2
+ ul.dito-menu(
3
+ v-resize="onResize"
4
+ :style="{ '--width': width ? `${width}px` : null }"
5
+ )
6
+ li(
7
+ v-for="item in items"
8
+ )
9
+ template(
10
+ v-if="shouldRenderSchema(item)"
11
+ )
12
+ a.dito-link(
13
+ :href="getItemHref(item)"
14
+ :class="getItemClass(item, 'dito-sub-menu-link')"
15
+ @click.prevent.stop="onClickItem(item)"
16
+ ) {{ getLabel(item) }}
17
+ DitoMenu(
18
+ v-if="item.items"
19
+ :class="getItemClass(item, 'dito-sub-menu')"
20
+ :items="item.items"
21
+ )
22
+ </template>
23
+
24
+ <script>
25
+ import DitoComponent from '../DitoComponent.js'
26
+
27
+ // @vue/component
28
+ export default DitoComponent.component('DitoMenu', {
29
+ props: {
30
+ items: {
31
+ type: [Object, Array],
32
+ default: () => []
33
+ }
34
+ },
35
+
36
+ data() {
37
+ return {
38
+ width: 0
39
+ }
40
+ },
41
+
42
+ methods: {
43
+ onResize({ contentRect: { width } }) {
44
+ if (width) {
45
+ this.width = width
46
+ }
47
+ },
48
+
49
+ getItemClass(item, subMenuClass) {
50
+ return {
51
+ [subMenuClass]: !!item.items,
52
+ 'dito-active': this.isActiveItem(item)
53
+ }
54
+ },
55
+
56
+ getItemHref(item) {
57
+ return item?.path
58
+ ? `/${item.path}`
59
+ : item.items
60
+ ? this.getItemHref(Object.values(item.items)[0])
61
+ : null
62
+ },
63
+
64
+ isActiveItem(item) {
65
+ return (
66
+ this.$route.path.startsWith(this.getItemHref(item)) ||
67
+ item.items && Object.values(item.items).some(this.isActiveItem)
68
+ )
69
+ },
70
+
71
+ onClickItem(item) {
72
+ const path = this.getItemHref(item)
73
+ if (path) {
74
+ this.$router.push({ path, force: true })
75
+ }
76
+ }
77
+ }
78
+ })
79
+ </script>
80
+
81
+ <style lang="scss">
82
+ @use 'sass:color';
83
+ @import '../styles/_imports';
84
+
85
+ .dito-menu {
86
+ $item-height: $menu-font-size + 2 * $menu-padding-ver;
87
+
88
+ border-right: $border-style;
89
+ padding: 0 $menu-spacing;
90
+
91
+ li {
92
+ &:has(.dito-sub-menu:not(.dito-active)) {
93
+ // Pop-out sub-menus on hover:
94
+ &:hover {
95
+ .dito-sub-menu-link {
96
+ background: $color-lightest;
97
+ }
98
+
99
+ .dito-sub-menu {
100
+ display: block;
101
+ position: absolute;
102
+ width: var(--width);
103
+ z-index: $header-z-index;
104
+ transform: translateX(calc(var(--width) + 2 * $menu-spacing))
105
+ translateY(-$item-height);
106
+
107
+ li:first-child {
108
+ .dito-link {
109
+ margin-top: 0;
110
+ }
111
+ }
112
+
113
+ &::before {
114
+ // Fill the gap to not loose the hover when moving over it.
115
+ content: '';
116
+ position: absolute;
117
+ top: 0;
118
+ left: -2 * $menu-spacing;
119
+ width: 2 * $menu-spacing;
120
+ height: $item-height;
121
+ opacity: 0;
122
+ }
123
+ }
124
+
125
+ // .dito-sub-menu-link,
126
+ .dito-sub-menu {
127
+ box-shadow: $shadow-window;
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ .dito-link {
134
+ display: block;
135
+ padding: $menu-padding;
136
+ line-height: $menu-line-height;
137
+ border-radius: $border-radius;
138
+ margin-top: $menu-spacing;
139
+
140
+ &:focus:not(:active, .dito-active) {
141
+ box-shadow: $shadow-focus;
142
+ }
143
+
144
+ &:hover {
145
+ background: rgba(255, 255, 255, 0.5);
146
+ }
147
+
148
+ &.dito-active {
149
+ color: $color-white;
150
+ background: $color-active;
151
+ }
152
+ }
153
+
154
+ .dito-sub-menu-link {
155
+ &.dito-active {
156
+ background: color.adjust($color-active, $alpha: -0.3);
157
+ }
158
+ }
159
+
160
+ .dito-sub-menu {
161
+ display: none;
162
+ border-right: 0;
163
+ padding: 0;
164
+ border-radius: $border-radius;
165
+ background: $color-lightest;
166
+
167
+ &.dito-active {
168
+ display: block;
169
+ }
170
+ }
171
+ }
172
+ </style>
@@ -224,10 +224,10 @@ export default DitoComponent.component('DitoPanel', {
224
224
  > .dito-schema-content {
225
225
  > .dito-pane {
226
226
  padding: $form-spacing-half $form-spacing;
227
- }
228
227
 
229
- .dito-container {
230
- padding: $form-spacing-half;
228
+ > .dito-container {
229
+ padding: $form-spacing-half;
230
+ }
231
231
  }
232
232
 
233
233
  .dito-object {
@@ -16,7 +16,7 @@
16
16
  :settings="dialog.settings"
17
17
  @remove="removeDialog(key)"
18
18
  )
19
- DitoMenu
19
+ DitoSidebar
20
20
  main.dito-page.dito-scroll-parent
21
21
  DitoHeader(
22
22
  :spinner="options.spinner"
@@ -43,7 +43,7 @@ import DitoDialog from './DitoDialog.vue'
43
43
  import DomMixin from '../mixins/DomMixin.js'
44
44
  import {
45
45
  processView,
46
- resolveSchemas,
46
+ resolveViews,
47
47
  processSchemaComponents
48
48
  } from '../utils/schema.js'
49
49
 
@@ -328,7 +328,7 @@ export default DitoComponent.component('DitoRoot', {
328
328
 
329
329
  async resolveViews() {
330
330
  try {
331
- this.resolvedViews = await resolveSchemas(this.unresolvedViews)
331
+ this.resolvedViews = await resolveViews(this.unresolvedViews)
332
332
  } catch (error) {
333
333
  if (!error.request) {
334
334
  console.error(error)
@@ -350,7 +350,7 @@ export default DitoComponent.component('DitoRoot', {
350
350
  path: '/',
351
351
  components: {}
352
352
  },
353
- ...routes
353
+ ...routes.flat()
354
354
  ])
355
355
  this.$router.replace(fullPath)
356
356
  }
@@ -5,7 +5,7 @@ slot(name="before")
5
5
  )
6
6
  .dito-schema-content(:class="{ 'dito-scroll': scrollable }")
7
7
  Teleport(
8
- to=".dito-menu"
8
+ to=".dito-header__menu"
9
9
  :disabled="!headerInMenu"
10
10
  )
11
11
  .dito-schema-header(
@@ -118,6 +118,7 @@ import { getStoreAccessor } from '../utils/accessor.js'
118
118
  export default DitoComponent.component('DitoSchema', {
119
119
  mixins: [ItemMixin],
120
120
  components: { TransitionHeight },
121
+ inheritAttrs: false,
121
122
 
122
123
  provide() {
123
124
  return {
@@ -787,13 +788,13 @@ export default DitoComponent.component('DitoSchema', {
787
788
  &--menu {
788
789
  // Align the tabs on top of to the header menu.
789
790
  position: absolute;
790
- height: $menu-height;
791
- padding: 0 $menu-padding-hor;
791
+ height: $header-height;
792
+ padding: 0 $header-padding-hor;
792
793
  max-width: $content-width;
793
794
  top: 0;
794
795
  left: 0;
795
796
  right: 0;
796
- z-index: $menu-z-index;
797
+ z-index: $header-z-index;
797
798
  // Turn off pointer events so that DitoTrail keeps receiving events...
798
799
  pointer-events: none;
799
800
  // ...but allow interaction with the tabs and buttons (e.g. clipboard)
@@ -801,8 +802,8 @@ export default DitoComponent.component('DitoSchema', {
801
802
  .dito-tabs,
802
803
  .dito-buttons {
803
804
  pointer-events: auto;
804
- line-height: $menu-line-height;
805
- font-size: $menu-font-size;
805
+ line-height: $header-line-height;
806
+ font-size: $header-font-size;
806
807
  }
807
808
  }
808
809
  }
@@ -1,78 +1,40 @@
1
1
  <template lang="pug">
2
2
  nav.dito-sidebar.dito-scroll-parent
3
- h1 {{ appState.title }}
4
- ul.dito-scroll
5
- li(
6
- v-for="view in views"
7
- )
8
- RouterLink(
9
- v-if="shouldRenderSchema(view)"
10
- v-slot="{ isActive, href, route }"
11
- custom
12
- :to="`/${view.path}`"
13
- )
14
- a.dito-link(
15
- :href="href"
16
- :class="{ 'dito-active': isActive }"
17
- @click.prevent="onClickLink(route)"
18
- ) {{ getLabel(view) }}
3
+ h1
4
+ RouterLink.dito-link(to="/") {{ appState.title }}
5
+ DitoMenu.dito-scroll(:items="views")
19
6
  </template>
20
7
 
21
8
  <script>
22
9
  import DitoComponent from '../DitoComponent.js'
23
10
 
24
11
  // @vue/component
25
- export default DitoComponent.component('DitoMenu', {
26
- methods: {
27
- onClickLink(route) {
28
- this.$router.push({ ...route, force: true })
29
- }
30
- }
31
- })
12
+ export default DitoComponent.component('DitoSidebar', {})
32
13
  </script>
33
14
 
34
15
  <style lang="scss">
35
16
  @import '../styles/_imports';
36
17
 
37
18
  .dito-sidebar {
19
+ @include user-select(none);
20
+
38
21
  flex: initial;
39
22
  font-size: $menu-font-size;
40
23
  white-space: nowrap;
41
- @include user-select(none);
42
-
43
- ul {
44
- background: $color-lighter;
45
- border-right: $border-style;
46
- }
24
+ background: $color-lighter;
47
25
 
48
- a,
49
26
  h1 {
50
27
  display: block;
51
- }
52
-
53
- h1 {
54
- padding: $menu-padding;
55
- line-height: $menu-line-height;
28
+ line-height: $header-line-height;
56
29
  font-weight: bold;
57
30
  background: $color-darker;
58
31
  border-right: $border-width solid $color-darkest;
59
32
  color: $color-white;
60
- }
61
-
62
- .dito-link {
63
- padding: $menu-padding;
64
- line-height: $menu-line-height;
65
33
 
66
- &.dito-active {
67
- color: $color-white;
68
- background: $color-active;
34
+ .dito-link {
35
+ display: block;
36
+ padding: $header-padding;
69
37
  }
70
38
  }
71
39
  }
72
-
73
- .dito-link {
74
- &:focus:not(:active, .dito-active) {
75
- box-shadow: $shadow-focus;
76
- }
77
- }
78
40
  </style>
@@ -4,6 +4,7 @@
4
4
  // convention is in order of encountered hierarchy in the DOM.
5
5
 
6
6
  export { default as DitoRoot } from './DitoRoot.vue'
7
+ export { default as DitoMenu } from './DitoMenu.vue'
7
8
  export { default as DitoSidebar } from './DitoSidebar.vue'
8
9
  export { default as DitoHeader } from './DitoHeader.vue'
9
10
  export { default as DitoAccount } from './DitoAccount.vue'
@@ -1,5 +1,6 @@
1
1
  import DitoContext from '../DitoContext.js'
2
2
  import DataMixin from './DataMixin.js'
3
+ import { hasViewSchema, getViewEditPath } from '../utils/schema.js'
3
4
  import { getSchemaAccessor } from '../utils/accessor.js'
4
5
  import { setTemporaryId, isReference } from '../utils/data.js'
5
6
  import {
@@ -145,6 +146,23 @@ export default {
145
146
  }
146
147
  }),
147
148
 
149
+ editable: getSchemaAccessor('editable', {
150
+ type: Boolean,
151
+ default: false,
152
+ get(editable) {
153
+ return (
154
+ editable &&
155
+ hasViewSchema(this.schema, this.context)
156
+ )
157
+ }
158
+ }),
159
+
160
+ editPath() {
161
+ return this.editable && this.selectedValue
162
+ ? getViewEditPath(this.schema, this.selectedValue, this.context)
163
+ : null
164
+ },
165
+
148
166
  groupByLabel() {
149
167
  return this.groupBy ? 'label' : null
150
168
  },
@@ -115,7 +115,7 @@
115
115
  &.dito-buttons-large {
116
116
  --button-margin: 3px;
117
117
 
118
- font-size: $menu-font-size;
118
+ font-size: $header-font-size;
119
119
  flex-flow: row wrap;
120
120
  justify-content: center;
121
121
  padding-bottom: $content-padding;
@@ -191,18 +191,18 @@
191
191
  cursor: pointer;
192
192
 
193
193
  &.dito-button-clear {
194
+ $self: &;
195
+
194
196
  width: $input-height;
195
197
  z-index: 1;
196
198
  background: $color-white;
197
199
  display: none;
198
200
 
199
- $button-clear: &;
200
-
201
201
  @at-root .dito-component:hover {
202
202
  // Support two levels of nesting inside .dito-component, used by TypeColor
203
- & > #{$button-clear},
204
- & > * > #{$button-clear},
205
- & > * > * > #{$button-clear} {
203
+ & > #{$self},
204
+ & > * > #{$self},
205
+ & > * > * > #{$self} {
206
206
  display: block;
207
207
  }
208
208
  }
@@ -9,7 +9,6 @@
9
9
  // To make vertical scrolling in .dito-scroll work:
10
10
  flex: 1;
11
11
  display: flex;
12
- position: relative;
13
12
  flex-flow: column;
14
13
  overflow: hidden;
15
14
  }
@@ -43,21 +43,28 @@ $content-color-background: $color-lightest;
43
43
  $form-spacing: $input-padding-hor;
44
44
  $form-spacing-half: calc($form-spacing / 2);
45
45
 
46
- // Menu
47
- $menu-font-size: 14px;
48
- $menu-line-height: 1;
49
- $menu-z-index: 1000;
50
- $menu-padding-ver: $menu-font-size;
51
- $menu-padding-hor: $content-padding;
52
- $menu-padding: $menu-padding-ver $menu-padding-hor;
53
- $menu-height: $menu-font-size + 2 * $menu-padding-ver;
46
+ // Header
47
+ $header-font-size: 14px;
48
+ $header-line-height: 1;
49
+ $header-z-index: 1000;
50
+ $header-padding-ver: $header-font-size;
51
+ $header-padding-hor: $content-padding;
52
+ $header-padding: $header-padding-ver $header-padding-hor;
53
+ $header-height: $header-font-size + 2 * $header-padding-ver;
54
54
 
55
55
  // Tabs
56
56
  $tab-margin: 6px;
57
57
  $tab-padding-hor: 12px;
58
- $tab-padding-ver: calc(($menu-padding-ver * 2 - $tab-margin) / 2);
58
+ $tab-padding-ver: calc(($header-padding-ver * 2 - $tab-margin) / 2);
59
59
  $tab-radius: 4px;
60
- // $border-radius
60
+
61
+ // Menu
62
+ $menu-font-size: $header-font-size;
63
+ $menu-line-height: $header-line-height;
64
+ $menu-spacing: 4px;
65
+ $menu-padding-ver: $header-padding-ver - $menu-spacing;
66
+ $menu-padding-hor: $header-padding-hor - $menu-spacing;
67
+ $menu-padding: $menu-padding-ver $menu-padding-hor;
61
68
 
62
69
  // Patterns
63
70
  $pattern-transparency-size: ($font-size - $border-width) * $input-height-factor;
@@ -98,10 +98,6 @@ export default DitoTypeComponent.register(
98
98
  .dito-button {
99
99
  $self: &;
100
100
 
101
- display: flex;
102
- align-items: center;
103
- justify-content: center;
104
-
105
101
  &__text {
106
102
  position: relative;
107
103
  min-width: min-content;
@@ -251,9 +251,11 @@ export default DitoTypeComponent.register('list', {
251
251
 
252
252
  getEditPath(item, index) {
253
253
  if (this.editable) {
254
- const path = getViewEditPath(this.schema, this.context) || this.path
255
254
  const id = this.getItemId(this.schema, item, index)
256
- return `${path}/${id}`
255
+ return (
256
+ getViewEditPath(this.schema, id, this.context) ||
257
+ `${this.path}/${id}`
258
+ )
257
259
  }
258
260
  return null
259
261
  },