@ditojs/admin 2.3.1 → 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.1",
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.1",
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": "1b5f5121a2e8e6d3741f0bb2b81d38c2343810d6",
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
 
@@ -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>
@@ -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'
@@ -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;
@@ -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;
@@ -113,6 +113,10 @@ export function isPanel(schema) {
113
113
  return isSchema(schema) && schema.type === 'panel'
114
114
  }
115
115
 
116
+ export function isMenu(schema) {
117
+ return isSchema(schema) && schema.type === 'menu'
118
+ }
119
+
116
120
  export function getSchemaIdentifier(schema) {
117
121
  return JSON.stringify(schema)
118
122
  }
@@ -176,6 +180,19 @@ export async function resolveSchemas(
176
180
  return schemas
177
181
  }
178
182
 
183
+ export async function resolveViews(unresolvedViews) {
184
+ return resolveSchemas(unresolvedViews, async (schema, unwrapModule) => {
185
+ schema = await resolveSchema(schema, unwrapModule)
186
+ if (!schema.name && isMenu(schema)) {
187
+ // Generate a name for sub-menus from their label if it's missing.
188
+ // NOTE: This is never actually referenced from anywhere, but they need
189
+ // a name by which they're stored in the parent object.
190
+ schema.name = camelize(schema.label)
191
+ }
192
+ return schema
193
+ })
194
+ }
195
+
179
196
  export async function resolveSchemaComponent(schema) {
180
197
  // Resolves async schema components and adds DitoMixin and TypeMixin to them.
181
198
  let { component } = schema
@@ -250,22 +267,31 @@ export async function processSchemaComponent(
250
267
  }
251
268
 
252
269
  export async function processView(component, api, schema, name) {
253
- if (!isView(schema)) {
254
- throw new Error(`Invalid view schema: '${getSchemaIdentifier(schema)}'`)
255
- }
256
- processRouteSchema(api, schema, name)
257
270
  processSchemaDefaults(api, schema)
258
- await processNestedSchemas(api, schema)
259
- const children = []
260
- await processSchemaComponents(api, schema, children, 0)
261
- return {
262
- path: `/${schema.path}`,
263
- children,
264
- component,
265
- meta: {
266
- api,
267
- schema
271
+ if (isView(schema)) {
272
+ processRouteSchema(api, schema, name)
273
+ await processNestedSchemas(api, schema)
274
+ const children = []
275
+ await processSchemaComponents(api, schema, children, 0)
276
+ return {
277
+ path: `/${schema.path}`,
278
+ children,
279
+ component,
280
+ meta: {
281
+ api,
282
+ schema
283
+ }
268
284
  }
285
+ } else if (isMenu(schema)) {
286
+ schema.items = await resolveSchemas(schema.items)
287
+ return Promise.all(
288
+ Object.entries(schema.items).map(async ([name, item]) =>
289
+ processView(component, api, item, name)
290
+ )
291
+ )
292
+ //
293
+ } else {
294
+ throw new Error(`Invalid view schema: '${getSchemaIdentifier(schema)}'`)
269
295
  }
270
296
  }
271
297
 
@@ -303,8 +329,8 @@ export function processNestedSchemaDefaults(api, schema) {
303
329
 
304
330
  export function processRouteSchema(api, schema, name) {
305
331
  // Used for view and source schemas, see SourceMixin.
306
- schema.name = name
307
- schema.path ||= api.normalizePath(name)
332
+ schema.name ??= name
333
+ schema.path ??= api.normalizePath(name)
308
334
  }
309
335
 
310
336
  export async function processForms(api, schema, level) {