@ditojs/admin 2.9.1 → 2.9.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.9.1",
3
+ "version": "2.9.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",
@@ -83,7 +83,7 @@
83
83
  "vite": "^4.3.5"
84
84
  },
85
85
  "types": "types",
86
- "gitHead": "002f3c713f9d3b10e348d2e9fd07d3a5bea426e6",
86
+ "gitHead": "1f8607d92bdd39d14af2a06968ca4914c2d3988f",
87
87
  "scripts": {
88
88
  "build": "vite build",
89
89
  "watch": "yarn build --mode 'development' --watch",
package/src/DitoAdmin.js CHANGED
@@ -199,8 +199,8 @@ export default class DitoAdmin {
199
199
  app.config.errorHandler = console.error
200
200
 
201
201
  app.use(VueNotifications, {
202
- name: 'notify',
203
- componentName: 'VueNotifications'
202
+ componentName: 'VueNotifications',
203
+ name: 'notify'
204
204
  })
205
205
 
206
206
  app.directive('resize', ResizeDirective)
@@ -82,6 +82,11 @@ export default DitoComponent.component('DitoClipboard', {
82
82
  },
83
83
 
84
84
  methods: {
85
+ checkClipboardData(clipboardData) {
86
+ const { $schema, ...data } = clipboardData || {}
87
+ return $schema === this.schema.name ? data : null
88
+ },
89
+
85
90
  async getClipboardData(report) {
86
91
  // Use the internal clipboard as fallback.
87
92
  let { clipboardData } = this.appState
@@ -101,12 +106,11 @@ export default DitoComponent.component('DitoClipboard', {
101
106
  }
102
107
  }
103
108
  }
104
- const { $schema, ...data } = clipboardData || {}
105
- return $schema === this.schema.name ? data : null
109
+ return this.checkClipboardData(clipboardData)
106
110
  },
107
111
 
108
112
  async updatePaste() {
109
- this.pasteEnabled = !!this.appState.clipboardData
113
+ this.pasteEnabled = !!this.checkClipboardData(this.appState.clipboardData)
110
114
  if (!this.pasteEnabled && this.appState.agent.chrome) {
111
115
  // See if the clipboard content is valid JSON data that is compatible
112
116
  // with the current target schema, and only then activate the pasting:
@@ -1,23 +1,10 @@
1
1
  <template lang="pug">
2
2
  nav.dito-header
3
- .dito-trail
4
- ul
5
- li(
6
- v-for="(component, index) in trail"
7
- )
8
- template(
9
- v-if="index === trail.length - 1"
10
- )
11
- span(:class="getBreadcrumbClass(component)")
12
- | {{ component.breadcrumb }}
13
- RouterLink.dito-breadcrumb(
14
- v-else
15
- :to="component.path"
16
- )
17
- span(:class="getBreadcrumbClass(component)")
18
- | {{ component.breadcrumb }}
3
+ DitoTrail
19
4
  DitoSpinner(
20
5
  v-if="isLoading"
6
+ :size="spinner?.size"
7
+ :color="spinner?.color"
21
8
  )
22
9
  //- Teleport target for `.dito-schema-header`:
23
10
  .dito-header__teleport
@@ -26,12 +13,9 @@ nav.dito-header
26
13
 
27
14
  <script>
28
15
  import DitoComponent from '../DitoComponent.js'
29
- import DitoSpinner from './DitoSpinner.vue'
30
16
 
31
17
  // @vue/component
32
18
  export default DitoComponent.component('DitoHeader', {
33
- components: { DitoSpinner },
34
-
35
19
  props: {
36
20
  spinner: {
37
21
  type: Object,
@@ -41,34 +25,6 @@ export default DitoComponent.component('DitoHeader', {
41
25
  type: Boolean,
42
26
  default: false
43
27
  }
44
- },
45
-
46
- computed: {
47
- trail() {
48
- return this.appState.routeComponents.filter(
49
- component => !!component.routeRecord
50
- )
51
- }
52
- },
53
-
54
- created() {
55
- const {
56
- size = '8px',
57
- color = '#999'
58
- } = this.spinner || {}
59
- // TODO: This is a hack to set the default props for the DitoSpinner.
60
- // Pass them on through the template instead!
61
- const { props } = DitoSpinner
62
- props.size.default = size
63
- props.color.default = color
64
- },
65
-
66
- methods: {
67
- getBreadcrumbClass(component) {
68
- return {
69
- 'dito-dirty': component.isDirty
70
- }
71
- }
72
28
  }
73
29
  })
74
30
  </script>
@@ -106,60 +62,6 @@ export default DitoComponent.component('DitoHeader', {
106
62
  }
107
63
  }
108
64
 
109
- .dito-trail {
110
- display: flex;
111
- box-sizing: border-box;
112
- height: 3em;
113
-
114
- ul {
115
- display: flex;
116
- }
117
-
118
- li {
119
- white-space: nowrap;
120
- }
121
-
122
- a {
123
- position: relative;
124
- display: block;
125
-
126
- $angle: 33deg;
127
-
128
- &:hover {
129
- span {
130
- color: $color-light;
131
- }
132
- }
133
-
134
- &::before,
135
- &::after {
136
- position: absolute;
137
- content: '';
138
- width: 1px;
139
- height: 0.75em;
140
- right: -0.25em;
141
- background: $color-white;
142
- opacity: 0.5;
143
- }
144
-
145
- &::before {
146
- top: 50%;
147
- transform: rotate($angle);
148
- transform-origin: top;
149
- }
150
-
151
- &::after {
152
- bottom: 50%;
153
- transform: rotate(-$angle);
154
- transform-origin: bottom;
155
- }
156
- }
157
- }
158
-
159
- .dito-spinner {
160
- margin-top: $header-padding-ver;
161
- }
162
-
163
65
  .dito-dirty {
164
66
  &::after {
165
67
  content: '';
@@ -0,0 +1,149 @@
1
+ <template lang="pug">
2
+ .dito-notifications
3
+ .dito-header
4
+ span
5
+ .dito-notifications__inner
6
+ VueNotifications(
7
+ ref="notifications"
8
+ position="none"
9
+ classes="dito-notification"
10
+ )
11
+ </template>
12
+
13
+ <script>
14
+ import DitoComponent from '../DitoComponent.js'
15
+ import { asArray, stripTags } from '@ditojs/utils'
16
+
17
+ // @vue/component
18
+ export default DitoComponent.component('DitoNotifications', {
19
+ notifications() {
20
+ return this.isMounted && this.$refs.notifications
21
+ },
22
+
23
+ methods: {
24
+ notify({ type = 'info', title, text } = {}) {
25
+ title ||= (
26
+ {
27
+ warning: 'Warning',
28
+ error: 'Error',
29
+ info: 'Information',
30
+ success: 'Success'
31
+ }[type] ||
32
+ 'Notification'
33
+ )
34
+ text = `<p>${
35
+ asArray(text).join('</p> <p>')
36
+ }</p>`.replace(/\n|\r\n|\r/g, '<br>')
37
+ const log = (
38
+ {
39
+ warning: 'warn',
40
+ error: 'error',
41
+ info: 'log',
42
+ success: 'log'
43
+ }[type] ||
44
+ 'error'
45
+ )
46
+ // eslint-disable-next-line no-console
47
+ console[log](stripTags(text))
48
+ const { notifications = true } = this.api
49
+ if (notifications) {
50
+ // Calculate display-duration for the notification based on its content
51
+ // and the setting of the `durationFactor` configuration. It defines the
52
+ // amount of milliseconds multiplied with the amount of characters
53
+ // displayed in the notification, plus 40 (40 + title + message):
54
+ const { durationFactor = 20 } = notifications
55
+ const duration = (40 + text.length + title.length) * durationFactor
56
+ this.$notify({ type, title, text, duration })
57
+ }
58
+ },
59
+
60
+ destroyAll() {
61
+ this.notifications.destroyAll()
62
+ }
63
+ }
64
+ })
65
+ </script>
66
+
67
+ <style lang="scss">
68
+ @use 'sass:color';
69
+ @import '../styles/_imports';
70
+
71
+ @mixin type($background) {
72
+ background: color.adjust($background, $lightness: 5%);
73
+ color: $color-white;
74
+ border-left: 12px solid color.adjust($background, $lightness: -10%);
75
+ }
76
+
77
+ .dito-notifications {
78
+ flex: 1;
79
+ // For the `@container` rule to work:
80
+ container-type: inline-size;
81
+
82
+ .dito-header {
83
+ span {
84
+ padding-left: 0;
85
+ padding-right: 0;
86
+ }
87
+ }
88
+
89
+ &__inner {
90
+ position: relative;
91
+ }
92
+
93
+ .vue-notification-group {
94
+ position: absolute;
95
+ left: 0;
96
+ top: 0;
97
+
98
+ @container (width < 300px) {
99
+ left: unset;
100
+ right: 0;
101
+ }
102
+ }
103
+
104
+ .vue-notification-wrapper {
105
+ overflow: visible;
106
+ }
107
+
108
+ .dito-notification {
109
+ padding: 8px;
110
+ margin: $content-padding;
111
+ font-size: inherit;
112
+ color: $color-white;
113
+ border-radius: $border-radius;
114
+ box-shadow: $shadow-window;
115
+
116
+ .notification-title {
117
+ font-weight: bold;
118
+ padding-bottom: 8px;
119
+ }
120
+
121
+ .notification-content {
122
+ p {
123
+ margin: 0;
124
+
125
+ & + p {
126
+ margin-top: 8px;
127
+ }
128
+ }
129
+ }
130
+
131
+ &,
132
+ &.info {
133
+ @include type($color-active);
134
+ }
135
+
136
+ &.success {
137
+ @include type($color-success);
138
+ }
139
+
140
+ &.warning {
141
+ @include type($color-warning);
142
+ }
143
+
144
+ &.error {
145
+ @include type($color-error);
146
+ }
147
+ }
148
+ }
149
+ </style>
@@ -4,11 +4,6 @@
4
4
  :data-agent-platform="appState.agent.platform"
5
5
  :data-agent-version="appState.agent.versionNumber"
6
6
  )
7
- VueNotifications.dito-notifications(
8
- ref="notifications"
9
- position="top right"
10
- classes="dito-notification"
11
- )
12
7
  Transition(name="dito-drag")
13
8
  .dito-drag-overlay(
14
9
  v-if="isDraggingFiles"
@@ -43,12 +38,12 @@
43
38
  @click="rootComponent.login()"
44
39
  )
45
40
  span Login
46
- .dito-fill
41
+ DitoNotifications(ref="notifications")
47
42
  </template>
48
43
 
49
44
  <script>
50
45
  import { delegate as tippyDelegate } from 'tippy.js'
51
- import { asArray, mapConcurrently, stripTags } from '@ditojs/utils'
46
+ import { mapConcurrently } from '@ditojs/utils'
52
47
  import DitoComponent from '../DitoComponent.js'
53
48
  import DomMixin from '../mixins/DomMixin.js'
54
49
  import DitoUser from '../DitoUser.js'
@@ -90,7 +85,7 @@ export default DitoComponent.component('DitoRoot', {
90
85
 
91
86
  computed: {
92
87
  notifications() {
93
- return this.$refs.notifications
88
+ return this.isMounted && this.$refs.notifications
94
89
  },
95
90
 
96
91
  isLoading() {
@@ -239,39 +234,7 @@ export default DitoComponent.component('DitoRoot', {
239
234
  },
240
235
 
241
236
  notify({ type = 'info', title, text } = {}) {
242
- title ||= (
243
- {
244
- warning: 'Warning',
245
- error: 'Error',
246
- info: 'Information',
247
- success: 'Success'
248
- }[type] ||
249
- 'Notification'
250
- )
251
- text = `<p>${
252
- asArray(text).join('</p> <p>')
253
- }</p>`.replace(/\n|\r\n|\r/g, '<br>')
254
- const log = (
255
- {
256
- warning: 'warn',
257
- error: 'error',
258
- info: 'log',
259
- success: 'log'
260
- }[type] ||
261
- 'error'
262
- )
263
- // eslint-disable-next-line no-console
264
- console[log](stripTags(text))
265
- const { notifications = true } = this.api
266
- if (notifications) {
267
- // Calculate display-duration for the notification based on its content
268
- // and the setting of the `durationFactor` configuration. It defines the
269
- // amount of milliseconds multiplied with the amount of characters
270
- // displayed in the notification, plus 40 (40 + title + message):
271
- const { durationFactor = 20 } = notifications
272
- const duration = (40 + text.length + title.length) * durationFactor
273
- this.$notify({ type, title, text, duration })
274
- }
237
+ this.notifications.notify({ type, title, text })
275
238
  },
276
239
 
277
240
  closeNotifications() {
@@ -509,17 +472,6 @@ function addRoutes(router, routes) {
509
472
  }
510
473
  }
511
474
 
512
- .dito-fill {
513
- flex: 1;
514
-
515
- .dito-header {
516
- span {
517
- padding-left: 0;
518
- padding-right: 0;
519
- }
520
- }
521
- }
522
-
523
475
  .dito-account,
524
476
  .dito-login {
525
477
  cursor: pointer;
@@ -757,7 +757,7 @@ export default DitoComponent.component('DitoSchema', {
757
757
  }
758
758
 
759
759
  &:has(> .dito-schema-content + .dito-edit-buttons) {
760
- // Display the edit buttons to the right of the schema:
760
+ // Display the inlined edit buttons to the right of the schema:
761
761
  display: flex;
762
762
  flex-direction: row;
763
763
  align-items: stretch;
@@ -1,15 +1,18 @@
1
1
  <template lang="pug">
2
2
  .dito-spinner(
3
3
  v-show="loading"
4
+ :style="{ '--color': color, '--size': size, '--margin': margin }"
4
5
  )
5
- //- TODO: Convert to BEM
6
- .v-pulse.v-pulse1(:style="[spinnerStyle, spinnerDelay1]")
7
- .v-pulse.v-pulse2(:style="[spinnerStyle, spinnerDelay2]")
8
- .v-pulse.v-pulse3(:style="[spinnerStyle, spinnerDelay3]")
6
+ .dito-spinner__pulse.dito-spinner__pulse1
7
+ .dito-spinner__pulse.dito-spinner__pulse2
8
+ .dito-spinner__pulse.dito-spinner__pulse3
9
9
  </template>
10
10
 
11
11
  <script>
12
- export default {
12
+ import DitoComponent from '../DitoComponent.js'
13
+
14
+ // @vue/component
15
+ export default DitoComponent.component('DitoSpinner', {
13
16
  props: {
14
17
  loading: {
15
18
  type: Boolean,
@@ -17,54 +20,54 @@ export default {
17
20
  },
18
21
  color: {
19
22
  type: String,
20
- default: '#5dc596'
23
+ default: null
21
24
  },
22
25
  size: {
23
26
  type: String,
24
- default: '15px'
27
+ default: null
25
28
  },
26
29
  margin: {
27
30
  type: String,
28
- default: '2px'
29
- },
30
- radius: {
31
- type: String,
32
- default: '100%'
33
- }
34
- },
35
-
36
- data() {
37
- // TODO: Convert to using only classes
38
- return {
39
- spinnerStyle: {
40
- backgroundColor: this.color,
41
- width: this.size,
42
- height: this.size,
43
- margin: this.margin,
44
- borderRadius: this.radius,
45
- display: 'inline-block',
46
- animationName: 'v-pulseStretchDelay',
47
- animationDuration: '0.75s',
48
- animationIterationCount: 'infinite',
49
- animationTimingFunction: 'cubic-bezier(.2,.68,.18,1.08)',
50
- animationFillMode: 'both'
51
- },
52
- spinnerDelay1: {
53
- animationDelay: '0.12s'
54
- },
55
- spinnerDelay2: {
56
- animationDelay: '0.24s'
57
- },
58
- spinnerDelay3: {
59
- animationDelay: '0.36s'
60
- }
31
+ default: null
61
32
  }
62
33
  }
63
- }
34
+ })
64
35
  </script>
65
36
 
66
37
  <style lang="scss">
67
- @keyframes v-pulseStretchDelay {
38
+ .dito-spinner {
39
+ --color: #999999;
40
+ --size: 8px;
41
+ --margin: 2px;
42
+
43
+ display: flex;
44
+ align-items: center;
45
+
46
+ &__pulse {
47
+ display: inline-block;
48
+ background: var(--color);
49
+ width: var(--size);
50
+ height: var(--size);
51
+ margin: var(--margin);
52
+ border-radius: 100%;
53
+ animation: dito-spinner-pulse 0.75s cubic-bezier(0.2, 0.68, 0.18, 1.08) 0s
54
+ infinite both;
55
+ }
56
+
57
+ &__pulse1 {
58
+ animation-delay: 0.12s;
59
+ }
60
+
61
+ &__pulse2 {
62
+ animation-delay: 0.24s;
63
+ }
64
+
65
+ &__pulse3 {
66
+ animation-delay: 0.36s;
67
+ }
68
+ }
69
+
70
+ @keyframes dito-spinner-pulse {
68
71
  0%,
69
72
  80% {
70
73
  transform: scale(1);
@@ -77,7 +80,7 @@ export default {
77
80
  }
78
81
  }
79
82
 
80
- @keyframes v-pulseStretchDelay {
83
+ @keyframes dito-spinner-pulse {
81
84
  0%,
82
85
  80% {
83
86
  transform: scale(1);
@@ -0,0 +1,119 @@
1
+ <template lang="pug">
2
+ .dito-trail
3
+ ul
4
+ li(
5
+ v-for="component in trail"
6
+ )
7
+ a.dito-link(
8
+ :class="getLinkClass(component)"
9
+ :href="getComponentHref(component)"
10
+ @click.prevent.stop="onClickComponent(component)"
11
+ )
12
+ span(:class="getSpanClass(component)")
13
+ | {{ component.breadcrumb }}
14
+ slot
15
+ </template>
16
+
17
+ <script>
18
+ import DitoComponent from '../DitoComponent.js'
19
+
20
+ // @vue/component
21
+ export default DitoComponent.component('DitoTrail', {
22
+ computed: {
23
+ trail() {
24
+ return this.appState.routeComponents.filter(
25
+ component => !!component.routeRecord
26
+ )
27
+ }
28
+ },
29
+
30
+ methods: {
31
+ getComponentPath(component) {
32
+ // Do the same as in `DitoMenu`: Link menu items to the first children.
33
+ const { schema } = component
34
+ return schema.type === 'menu'
35
+ ? Object.values(schema.items)[0].fullPath
36
+ : component.path
37
+ },
38
+
39
+ getComponentHref(component) {
40
+ return this.$router.resolve(this.getComponentPath(component)).href
41
+ },
42
+
43
+ onClickComponent(component) {
44
+ this.$router.push({
45
+ path: this.getComponentPath(component),
46
+ force: true
47
+ })
48
+ },
49
+
50
+ getLinkClass(component) {
51
+ return {
52
+ 'dito-active': component.path === this.$route.path
53
+ }
54
+ },
55
+
56
+ getSpanClass(component) {
57
+ return {
58
+ 'dito-dirty': component.isDirty
59
+ }
60
+ }
61
+ }
62
+ })
63
+ </script>
64
+
65
+ <style lang="scss">
66
+ @import '../styles/_imports';
67
+
68
+ .dito-trail {
69
+ display: flex;
70
+ box-sizing: border-box;
71
+ height: 3em;
72
+
73
+ ul {
74
+ display: flex;
75
+ }
76
+
77
+ li {
78
+ white-space: nowrap;
79
+ }
80
+
81
+ a {
82
+ position: relative;
83
+ display: block;
84
+
85
+ &:hover {
86
+ span {
87
+ color: $color-light;
88
+ }
89
+ }
90
+ }
91
+
92
+ li:not(:last-child) a {
93
+ $angle: 33deg;
94
+
95
+ &::before,
96
+ &::after {
97
+ position: absolute;
98
+ content: '';
99
+ width: 1px;
100
+ height: 0.75em;
101
+ right: -0.25em;
102
+ background: $color-white;
103
+ opacity: 0.5;
104
+ }
105
+
106
+ &::before {
107
+ top: 50%;
108
+ transform: rotate($angle);
109
+ transform-origin: top;
110
+ }
111
+
112
+ &::after {
113
+ bottom: 50%;
114
+ transform: rotate(-$angle);
115
+ transform-origin: bottom;
116
+ }
117
+ }
118
+ }
119
+ </style>
@@ -101,11 +101,15 @@ export default DitoComponent.component('DitoView', {
101
101
  },
102
102
 
103
103
  watch: {
104
- $route(to, from) {
105
- // See if the route changes completely, and clear the data if it does.
106
- if (this.isFullRouteChange(to, from)) {
107
- this.isLoading = false
108
- this.data = {}
104
+ $route: {
105
+ // https://github.com/vuejs/vue-router/issues/3393#issuecomment-1158470149
106
+ flush: 'post',
107
+ handler(to, from) {
108
+ // See if the route changes completely, and clear the data if it does.
109
+ if (this.isFullRouteChange(to, from)) {
110
+ this.isLoading = false
111
+ this.data = {}
112
+ }
109
113
  }
110
114
  }
111
115
  },