@ditojs/admin 3.2.7 → 3.3.0
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/dist/dito-admin.css +1 -1
- package/dist/dito-admin.es.js +6011 -5826
- package/dist/dito-admin.umd.js +3 -7
- package/package.json +18 -17
- package/src/DitoAdmin.js +44 -24
- package/src/DitoContext.js +28 -2
- package/src/DitoTypeComponent.js +1 -0
- package/src/appState.js +6 -3
- package/src/components/DitoAccount.vue +35 -27
- package/src/components/DitoAffixes.vue +9 -2
- package/src/components/DitoClipboard.vue +59 -93
- package/src/components/DitoConfirmMessage.vue +18 -0
- package/src/components/DitoContainer.vue +56 -18
- package/src/components/DitoCreateButton.vue +36 -84
- package/src/components/DitoDialog.vue +20 -15
- package/src/components/DitoDragHandle.vue +44 -0
- package/src/components/DitoDraggable.vue +43 -16
- package/src/components/DitoEditButtons.vue +48 -24
- package/src/components/DitoErrors.vue +9 -56
- package/src/components/DitoForm.vue +82 -42
- package/src/components/DitoInputAffixes.vue +50 -0
- package/src/components/DitoLabel.vue +1 -15
- package/src/components/DitoMenu.vue +45 -52
- package/src/components/DitoNotifications.vue +38 -14
- package/src/components/DitoOptionList.vue +81 -0
- package/src/components/DitoOptionsEditButtons.vue +57 -0
- package/src/components/DitoPagination.vue +1 -8
- package/src/components/DitoPane.vue +51 -39
- package/src/components/DitoPanel.vue +12 -26
- package/src/components/DitoPanels.vue +11 -5
- package/src/components/DitoResizeHandle.vue +47 -0
- package/src/components/DitoRoot.vue +113 -320
- package/src/components/DitoSchema.vue +72 -63
- package/src/components/DitoSchemaInlined.vue +8 -9
- package/src/components/DitoScopes.vue +14 -18
- package/src/components/DitoSidebar.vue +4 -29
- package/src/components/DitoSourceItem.vue +114 -0
- package/src/components/DitoTableCell.vue +4 -0
- package/src/components/DitoTableHead.vue +15 -17
- package/src/components/DitoTabs.vue +25 -0
- package/src/components/DitoTrail.vue +25 -25
- package/src/components/DitoTreeItem.vue +102 -141
- package/src/components/DitoUploadFile.vue +5 -2
- package/src/components/DitoView.vue +33 -24
- package/src/index.js +4 -1
- package/src/mixins/DitoMixin.js +77 -110
- package/src/mixins/DomMixin.js +12 -14
- package/src/mixins/EmitterMixin.js +3 -2
- package/src/mixins/ItemMixin.js +38 -94
- package/src/mixins/OptionsMixin.js +38 -25
- package/src/mixins/ResourceMixin.js +182 -111
- package/src/mixins/RouteMixin.js +61 -44
- package/src/mixins/SchemaParentMixin.js +4 -1
- package/src/mixins/SourceMixin.js +118 -100
- package/src/mixins/TypeMixin.js +42 -40
- package/src/mixins/ValidationMixin.js +16 -4
- package/src/mixins/ValidatorMixin.js +1 -1
- package/src/styles/_settings.scss +4 -0
- package/src/styles/_tippy.scss +0 -11
- package/src/styles/style.scss +0 -1
- package/src/types/DitoTypeButton.vue +11 -14
- package/src/types/DitoTypeCheckboxes.vue +35 -17
- package/src/types/DitoTypeCode.vue +19 -41
- package/src/types/DitoTypeColor.vue +67 -19
- package/src/types/DitoTypeDate.vue +11 -14
- package/src/types/DitoTypeList.vue +50 -62
- package/src/types/DitoTypeMarkup.vue +41 -54
- package/src/types/DitoTypeMultiselect.vue +82 -52
- package/src/types/DitoTypeNumber.vue +12 -15
- package/src/types/DitoTypeObject.vue +4 -38
- package/src/types/DitoTypeRadio.vue +19 -6
- package/src/types/DitoTypeSection.vue +23 -9
- package/src/types/DitoTypeSelect.vue +39 -23
- package/src/types/DitoTypeSlider.vue +9 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +11 -14
- package/src/types/DitoTypeTreeList.vue +46 -8
- package/src/types/DitoTypeUpload.vue +190 -67
- package/src/utils/DataModel.js +368 -146
- package/src/utils/FileDropTargets.js +97 -0
- package/src/utils/ItemStores.js +31 -0
- package/src/utils/ListQuery.js +115 -0
- package/src/utils/LoadingTracker.js +76 -0
- package/src/utils/SchemaGraph.js +3 -2
- package/src/utils/Session.js +151 -0
- package/src/utils/ViewRegistry.js +85 -0
- package/src/utils/accessor.js +12 -4
- package/src/utils/affixes.js +33 -0
- package/src/utils/changeOnceEdited.js +33 -0
- package/src/utils/clipboard.js +55 -0
- package/src/utils/data.js +10 -5
- package/src/utils/dialogs.js +59 -0
- package/src/utils/dragResize.js +61 -0
- package/src/utils/filter.js +36 -2
- package/src/utils/html.js +9 -0
- package/src/utils/list.js +10 -0
- package/src/utils/options.js +4 -0
- package/src/utils/registration.js +18 -0
- package/src/utils/request.js +108 -13
- package/src/utils/route.js +6 -17
- package/src/utils/schema/data.js +24 -2
- package/src/utils/schema/events.js +19 -0
- package/src/utils/schema/item.js +114 -0
- package/src/utils/schema/lookup.js +39 -14
- package/src/utils/schema/setup.js +5 -5
- package/src/utils/schema/validation.js +16 -2
- package/src/utils/uid.js +105 -8
- package/src/validators/_decimals.js +16 -5
- package/types/index.d.ts +168 -41
- package/src/mixins/LoadingMixin.js +0 -23
- package/src/mixins/PulldownMixin.js +0 -72
- package/src/styles/_pulldown.scss +0 -38
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
)
|
|
7
7
|
Transition(name="dito-drag")
|
|
8
8
|
.dito-drag-overlay(
|
|
9
|
-
v-if="isDraggingFiles"
|
|
9
|
+
v-if="fileDropTargets.isDraggingFiles"
|
|
10
10
|
)
|
|
11
11
|
TransitionGroup(name="dito-dialog")
|
|
12
12
|
DitoDialog(
|
|
@@ -20,10 +20,7 @@
|
|
|
20
20
|
@remove="removeDialog(key)"
|
|
21
21
|
)
|
|
22
22
|
DitoNavigation
|
|
23
|
-
main.dito-page.dito-scroll-parent(
|
|
24
|
-
v-resize="onResizePage"
|
|
25
|
-
:class="pageClasses"
|
|
26
|
-
)
|
|
23
|
+
main.dito-page.dito-scroll-parent(:class="appState.pageClass")
|
|
27
24
|
DitoHeader(
|
|
28
25
|
:spinner="options.spinner"
|
|
29
26
|
:isLoading="isLoading"
|
|
@@ -33,53 +30,59 @@
|
|
|
33
30
|
DitoAccount(
|
|
34
31
|
v-if="user"
|
|
35
32
|
)
|
|
36
|
-
|
|
37
|
-
v-else-if="
|
|
38
|
-
|
|
33
|
+
button.dito-login(
|
|
34
|
+
v-else-if="hasSessionStarted"
|
|
35
|
+
type="button"
|
|
36
|
+
@click="session.login()"
|
|
39
37
|
)
|
|
40
38
|
span Login
|
|
41
39
|
DitoNotifications(ref="notifications")
|
|
42
40
|
</template>
|
|
43
41
|
|
|
44
42
|
<script>
|
|
43
|
+
import { markRaw } from 'vue'
|
|
45
44
|
import { delegate as tippyDelegate } from 'tippy.js'
|
|
46
|
-
import {
|
|
45
|
+
import { deprecate } from '@ditojs/utils'
|
|
47
46
|
import DitoComponent from '../DitoComponent.js'
|
|
48
47
|
import DomMixin from '../mixins/DomMixin.js'
|
|
49
|
-
import DitoUser from '../DitoUser.js'
|
|
50
|
-
import DitoView from '../components/DitoView.vue'
|
|
51
48
|
import DitoDialog from './DitoDialog.vue'
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
setupSchemaComponents
|
|
56
|
-
} from '../utils/schema/setup.js'
|
|
49
|
+
import { setupSchemaComponents } from '../utils/schema/setup.js'
|
|
50
|
+
import { LoadingTracker } from '../utils/LoadingTracker.js'
|
|
51
|
+
import { FileDropTargets } from '../utils/FileDropTargets.js'
|
|
57
52
|
|
|
58
53
|
// @vue/component
|
|
59
54
|
export default DitoComponent.component('DitoRoot', {
|
|
60
55
|
mixins: [DomMixin],
|
|
61
56
|
components: { DitoDialog },
|
|
57
|
+
inject: ['viewRegistry'],
|
|
62
58
|
|
|
63
59
|
provide() {
|
|
64
60
|
return {
|
|
65
|
-
$
|
|
61
|
+
$loadingTracker: () => this.loadingTracker,
|
|
62
|
+
$fileDropTargets: () => this.fileDropTargets
|
|
66
63
|
}
|
|
67
64
|
},
|
|
68
65
|
|
|
69
66
|
props: {
|
|
70
|
-
unresolvedViews: { type: [Object, Function, Promise], required: true },
|
|
71
67
|
options: { type: Object, default: () => ({}) }
|
|
72
68
|
},
|
|
73
69
|
|
|
74
70
|
data() {
|
|
75
71
|
return {
|
|
76
|
-
resolvedViews: {},
|
|
77
|
-
removeRoutes: null,
|
|
78
72
|
dialogs: {},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
// Tracks all pending requests of the admin, for the header's spinner:
|
|
74
|
+
loadingTracker: markRaw(new LoadingTracker()),
|
|
75
|
+
// The functions that end the loading operations begun by the deprecated
|
|
76
|
+
// `registerLoading(true)`, ended in reverse by `registerLoading(false)`:
|
|
77
|
+
registeredLoadingEnds: markRaw([]),
|
|
78
|
+
// Tracks the files dragged over the admin, for the uploads to drop them
|
|
79
|
+
// on, see `DitoTypeUpload`:
|
|
80
|
+
fileDropTargets: markRaw(new FileDropTargets()),
|
|
81
|
+
// Whether `session.start()` finished, after which the login link is
|
|
82
|
+
// shown without a user, e.g. once the login dialog was canceled:
|
|
83
|
+
hasSessionStarted: false,
|
|
84
|
+
// Detaches the login dialog and notifications from the session:
|
|
85
|
+
detachSessionUserInterface: null
|
|
83
86
|
}
|
|
84
87
|
},
|
|
85
88
|
|
|
@@ -89,20 +92,7 @@ export default DitoComponent.component('DitoRoot', {
|
|
|
89
92
|
},
|
|
90
93
|
|
|
91
94
|
isLoading() {
|
|
92
|
-
return this.
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
pageClasses() {
|
|
96
|
-
const prefix = 'dito-page'
|
|
97
|
-
// NOTE: Keep synced with $content-width in SCSS:
|
|
98
|
-
const contentWidth = 900
|
|
99
|
-
return [
|
|
100
|
-
this.appState.pageClass,
|
|
101
|
-
{
|
|
102
|
-
[`${prefix}--width-80`]: this.pageWidth <= contentWidth * 0.8,
|
|
103
|
-
[`${prefix}--width-60`]: this.pageWidth <= contentWidth * 0.6
|
|
104
|
-
}
|
|
105
|
-
]
|
|
95
|
+
return this.loadingTracker.isLoading
|
|
106
96
|
}
|
|
107
97
|
},
|
|
108
98
|
|
|
@@ -111,10 +101,19 @@ export default DitoComponent.component('DitoRoot', {
|
|
|
111
101
|
// With hot-reloading, it looks like destroyed hooks aren't always called
|
|
112
102
|
// for route components so reset the array of registered components instead.
|
|
113
103
|
this.appState.routeComponents = []
|
|
104
|
+
this.detachSessionUserInterface = this.session.attachUserInterface({
|
|
105
|
+
requestLoginData: () => this.showLoginDialog(),
|
|
106
|
+
notify: options => this.notify(options)
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
unmounted() {
|
|
111
|
+
this.detachSessionUserInterface()
|
|
114
112
|
},
|
|
115
113
|
|
|
116
114
|
async mounted() {
|
|
117
|
-
|
|
115
|
+
// Only the overlay is rendered here, the uploads handle the dropped files.
|
|
116
|
+
this.domOn(document, this.fileDropTargets.getDocumentEventHandlers())
|
|
118
117
|
|
|
119
118
|
tippyDelegate(this.$el, {
|
|
120
119
|
target: '.dito-info',
|
|
@@ -127,164 +126,98 @@ export default DitoComponent.component('DitoRoot', {
|
|
|
127
126
|
onShow: instance => instance.setContent(instance.reference.dataset.info)
|
|
128
127
|
})
|
|
129
128
|
|
|
130
|
-
//
|
|
131
|
-
// the
|
|
132
|
-
this.domOn(
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
keyup: event => {
|
|
140
|
-
if (event.code === 'Tab') {
|
|
141
|
-
this.appState.activeLabel = null
|
|
129
|
+
// Warn before the page is reloaded or closed with unsaved changes. Route
|
|
130
|
+
// changes within the admin are guarded by `RouteMixin` instead.
|
|
131
|
+
this.domOn(window, {
|
|
132
|
+
beforeunload: event => {
|
|
133
|
+
if (this.hasUnsavedChanges()) {
|
|
134
|
+
event.preventDefault()
|
|
135
|
+
// Required by older browsers to show the warning:
|
|
136
|
+
event.returnValue = ''
|
|
142
137
|
}
|
|
143
138
|
}
|
|
144
139
|
})
|
|
145
140
|
|
|
146
141
|
try {
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
} else {
|
|
151
|
-
await this.login()
|
|
152
|
-
}
|
|
153
|
-
} catch (err) {
|
|
154
|
-
console.error(err)
|
|
142
|
+
await this.session.start()
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(error)
|
|
155
145
|
}
|
|
156
|
-
this.
|
|
146
|
+
this.hasSessionStarted = true
|
|
157
147
|
},
|
|
158
148
|
|
|
159
149
|
methods: {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (!enabled) {
|
|
175
|
-
uploads = []
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const setDraggingFiles = enabled => {
|
|
180
|
-
this.isDraggingFiles = enabled
|
|
181
|
-
if (enabled) {
|
|
182
|
-
toggleDropTargetClass(true)
|
|
183
|
-
} else {
|
|
184
|
-
setTimeout(() => toggleDropTargetClass(false), 150)
|
|
185
|
-
}
|
|
150
|
+
/**
|
|
151
|
+
* @deprecated `isLoading` is tracked by `loadingTracker` now. Use
|
|
152
|
+
* `loadingTracker.begin()` and call the function that it returns, or
|
|
153
|
+
* `loadingTracker.track(callback)` instead.
|
|
154
|
+
*/
|
|
155
|
+
registerLoading(isLoading) {
|
|
156
|
+
deprecate(
|
|
157
|
+
'DitoRoot.registerLoading() is deprecated, use ' +
|
|
158
|
+
'`loadingTracker.begin()` or `loadingTracker.track()` instead.'
|
|
159
|
+
)
|
|
160
|
+
if (isLoading) {
|
|
161
|
+
this.registeredLoadingEnds.push(this.loadingTracker.begin())
|
|
162
|
+
} else {
|
|
163
|
+
this.registeredLoadingEnds.pop()?.()
|
|
186
164
|
}
|
|
187
|
-
|
|
188
|
-
this.domOn(document, {
|
|
189
|
-
dragenter: event => {
|
|
190
|
-
if (!dragCount && event.dataTransfer) {
|
|
191
|
-
uploads = document.querySelectorAll('.dito-upload')
|
|
192
|
-
const hasUploads = uploads.length > 0
|
|
193
|
-
event.dataTransfer.effectAllowed = hasUploads ? 'copy' : 'none'
|
|
194
|
-
if (hasUploads) {
|
|
195
|
-
setDraggingFiles(true)
|
|
196
|
-
} else {
|
|
197
|
-
event.preventDefault()
|
|
198
|
-
event.stopPropagation()
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
dragCount++
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
dragleave: event => {
|
|
206
|
-
dragCount--
|
|
207
|
-
if (!dragCount && event.dataTransfer) {
|
|
208
|
-
setDraggingFiles(false)
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
dragover: event => {
|
|
213
|
-
if (event.dataTransfer) {
|
|
214
|
-
const canDrop = event.target.closest(
|
|
215
|
-
'.dito-container:has(.dito-upload)'
|
|
216
|
-
)
|
|
217
|
-
event.dataTransfer.dropEffect = canDrop ? 'copy' : 'none'
|
|
218
|
-
if (!canDrop) {
|
|
219
|
-
event.preventDefault()
|
|
220
|
-
event.stopPropagation()
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
drop: event => {
|
|
226
|
-
dragCount = 0
|
|
227
|
-
if (event.dataTransfer) {
|
|
228
|
-
setDraggingFiles(false)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
165
|
},
|
|
233
166
|
|
|
234
|
-
notify(
|
|
235
|
-
this.notifications.notify(
|
|
167
|
+
notify(options) {
|
|
168
|
+
this.notifications.notify(options)
|
|
236
169
|
},
|
|
237
170
|
|
|
238
171
|
closeNotifications() {
|
|
239
172
|
this.notifications.destroyAll()
|
|
240
173
|
},
|
|
241
174
|
|
|
242
|
-
|
|
243
|
-
|
|
175
|
+
// Returns true if any of the route components has unsaved changes, e.g. a
|
|
176
|
+
// form with a dirty data model or a view with edited components, see
|
|
177
|
+
// `RouteMixin`.
|
|
178
|
+
hasUnsavedChanges() {
|
|
179
|
+
return this.appState.routeComponents.some(
|
|
180
|
+
routeComponent => routeComponent.hasUnsavedChanges
|
|
181
|
+
)
|
|
244
182
|
},
|
|
245
183
|
|
|
246
|
-
showDialog({ components, buttons, data, settings }) {
|
|
184
|
+
async showDialog({ components, buttons, data, settings }) {
|
|
247
185
|
// Shows a dito-dialog component and wraps it in a promise so that the
|
|
248
186
|
// buttons in the dialog can use `dialog.resolve()` and `dialog.reject()`
|
|
249
187
|
// to close the modal dialog and resolve / reject the promise at once.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
0
|
|
259
|
-
)
|
|
260
|
-
if (routes.length > 0) {
|
|
261
|
-
throw new Error(
|
|
262
|
-
'Dialogs do not support components that produce routes'
|
|
263
|
-
)
|
|
264
|
-
}
|
|
265
|
-
const key = `dialog-${++dialogId}`
|
|
266
|
-
this.dialogs[key] = {
|
|
267
|
-
components,
|
|
268
|
-
buttons,
|
|
269
|
-
data,
|
|
270
|
-
settings,
|
|
271
|
-
promise: { resolve, reject }
|
|
272
|
-
}
|
|
273
|
-
}
|
|
188
|
+
// Process components to resolve async schemas first, so that errors
|
|
189
|
+
// reject the returned promise.
|
|
190
|
+
const routes = []
|
|
191
|
+
await setupSchemaComponents(
|
|
192
|
+
this.api,
|
|
193
|
+
{ type: 'dialog', components },
|
|
194
|
+
routes,
|
|
195
|
+
0
|
|
274
196
|
)
|
|
197
|
+
if (routes.length > 0) {
|
|
198
|
+
throw new Error('Dialogs do not support components that produce routes')
|
|
199
|
+
}
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const key = `dialog-${++dialogId}`
|
|
202
|
+
this.dialogs[key] = {
|
|
203
|
+
components,
|
|
204
|
+
buttons,
|
|
205
|
+
data,
|
|
206
|
+
settings,
|
|
207
|
+
promise: { resolve, reject }
|
|
208
|
+
}
|
|
209
|
+
})
|
|
275
210
|
},
|
|
276
211
|
|
|
277
212
|
removeDialog(key) {
|
|
278
213
|
delete this.dialogs[key]
|
|
279
214
|
},
|
|
280
215
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
} = this.options.login || {}
|
|
287
|
-
const loginData = await this.showDialog({
|
|
216
|
+
// Shows the login dialog for `Session.login()`, and returns the entered
|
|
217
|
+
// credentials, or `null` if the user cancels.
|
|
218
|
+
showLoginDialog() {
|
|
219
|
+
const { additionalComponents } = this.options.login || {}
|
|
220
|
+
return this.showDialog({
|
|
288
221
|
components: {
|
|
289
222
|
username: {
|
|
290
223
|
type: 'text',
|
|
@@ -311,141 +244,11 @@ export default DitoComponent.component('DitoRoot', {
|
|
|
311
244
|
}
|
|
312
245
|
}
|
|
313
246
|
})
|
|
314
|
-
if (loginData) {
|
|
315
|
-
try {
|
|
316
|
-
const response = await this.sendRequest({
|
|
317
|
-
resource: this.api.users.login,
|
|
318
|
-
data: loginData,
|
|
319
|
-
internal: true
|
|
320
|
-
})
|
|
321
|
-
if (redirectAfterLogin) {
|
|
322
|
-
location.replace(redirectAfterLogin)
|
|
323
|
-
} else {
|
|
324
|
-
this.setUser(response.data.user)
|
|
325
|
-
await this.resolveViews()
|
|
326
|
-
}
|
|
327
|
-
} catch (err) {
|
|
328
|
-
const error = err.response?.data?.error || err
|
|
329
|
-
this.notify({
|
|
330
|
-
type: 'error',
|
|
331
|
-
error,
|
|
332
|
-
title: 'Authentication Error',
|
|
333
|
-
text: error
|
|
334
|
-
})
|
|
335
|
-
this.login()
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
navigateHome() {
|
|
341
|
-
return this.navigate('/')
|
|
342
|
-
},
|
|
343
|
-
|
|
344
|
-
async logout() {
|
|
345
|
-
try {
|
|
346
|
-
const response = await this.sendRequest({
|
|
347
|
-
resource: this.api.users.logout,
|
|
348
|
-
internal: true
|
|
349
|
-
})
|
|
350
|
-
if (response.data.success) {
|
|
351
|
-
this.setUser(null)
|
|
352
|
-
this.navigateHome()
|
|
353
|
-
}
|
|
354
|
-
} catch (err) {
|
|
355
|
-
console.error(err)
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
async fetchUser() {
|
|
360
|
-
let user = null
|
|
361
|
-
try {
|
|
362
|
-
const response = await this.sendRequest({
|
|
363
|
-
resource: this.api.users.session,
|
|
364
|
-
internal: true
|
|
365
|
-
})
|
|
366
|
-
user = response.data.user || null
|
|
367
|
-
} catch (err) {
|
|
368
|
-
const error = err.response?.data?.error || err
|
|
369
|
-
this.notify({
|
|
370
|
-
type: 'error',
|
|
371
|
-
error,
|
|
372
|
-
title: 'Authentication Error',
|
|
373
|
-
text: error
|
|
374
|
-
})
|
|
375
|
-
}
|
|
376
|
-
this.setUser(user)
|
|
377
|
-
return user
|
|
378
|
-
},
|
|
379
|
-
|
|
380
|
-
setUser(user) {
|
|
381
|
-
this.appState.user = (
|
|
382
|
-
user &&
|
|
383
|
-
Object.setPrototypeOf(user, DitoUser.prototype)
|
|
384
|
-
)
|
|
385
|
-
// Clear resolved views when user is logged out.
|
|
386
|
-
if (!user) {
|
|
387
|
-
this.resolvedViews = {}
|
|
388
|
-
this.navigateHome()
|
|
389
|
-
}
|
|
390
|
-
},
|
|
391
|
-
|
|
392
|
-
async ensureUser() {
|
|
393
|
-
if (!(await this.fetchUser())) {
|
|
394
|
-
await this.login()
|
|
395
|
-
}
|
|
396
|
-
},
|
|
397
|
-
|
|
398
|
-
async resolveViews() {
|
|
399
|
-
try {
|
|
400
|
-
this.resolvedViews = await resolveViews(this.unresolvedViews)
|
|
401
|
-
} catch (error) {
|
|
402
|
-
if (!error.request) {
|
|
403
|
-
console.error(error)
|
|
404
|
-
}
|
|
405
|
-
return this.login()
|
|
406
|
-
}
|
|
407
|
-
// Collect all routes from the root schema components
|
|
408
|
-
const routes = await mapConcurrently(
|
|
409
|
-
Object.entries(this.resolvedViews),
|
|
410
|
-
([name, schema]) => setupView(DitoView, this.api, schema, name)
|
|
411
|
-
)
|
|
412
|
-
// Now that the routes are loaded, replace all existing routes with the
|
|
413
|
-
// new routes, and restore the current path.
|
|
414
|
-
const { fullPath } = this.$route
|
|
415
|
-
this.removeRoutes?.()
|
|
416
|
-
this.removeRoutes = addRoutes(this.$router, [
|
|
417
|
-
{
|
|
418
|
-
name: 'root',
|
|
419
|
-
path: '/',
|
|
420
|
-
components: {}
|
|
421
|
-
},
|
|
422
|
-
...routes.flat()
|
|
423
|
-
])
|
|
424
|
-
this.$router.replace(fullPath)
|
|
425
|
-
},
|
|
426
|
-
|
|
427
|
-
onResizePage({ contentRect: { width } }) {
|
|
428
|
-
this.pageWidth = width
|
|
429
247
|
}
|
|
430
248
|
}
|
|
431
249
|
})
|
|
432
250
|
|
|
433
251
|
let dialogId = 0
|
|
434
|
-
|
|
435
|
-
function addRoutes(router, routes) {
|
|
436
|
-
const removers = []
|
|
437
|
-
for (const route of routes) {
|
|
438
|
-
removers.push(
|
|
439
|
-
router.addRoute(route)
|
|
440
|
-
)
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return () => {
|
|
444
|
-
for (const remove of removers) {
|
|
445
|
-
remove()
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
252
|
</script>
|
|
450
253
|
|
|
451
254
|
<style lang="scss">
|
|
@@ -459,6 +262,9 @@ function addRoutes(router, routes) {
|
|
|
459
262
|
}
|
|
460
263
|
|
|
461
264
|
.dito-page {
|
|
265
|
+
// For the layout of `DitoContainer` in narrow pages:
|
|
266
|
+
container: dito-page / inline-size;
|
|
267
|
+
|
|
462
268
|
--max-content-width: #{$content-width};
|
|
463
269
|
--max-page-width: calc(var(--max-content-width) + 2 * #{$content-padding});
|
|
464
270
|
|
|
@@ -473,9 +279,15 @@ function addRoutes(router, routes) {
|
|
|
473
279
|
}
|
|
474
280
|
}
|
|
475
281
|
|
|
476
|
-
.dito-account,
|
|
477
282
|
.dito-login {
|
|
478
|
-
|
|
283
|
+
padding: 0;
|
|
284
|
+
border: 0;
|
|
285
|
+
color: inherit;
|
|
286
|
+
background: none;
|
|
287
|
+
|
|
288
|
+
&:focus-visible {
|
|
289
|
+
box-shadow: $shadow-focus;
|
|
290
|
+
}
|
|
479
291
|
}
|
|
480
292
|
|
|
481
293
|
.dito-drag-overlay {
|
|
@@ -490,35 +302,16 @@ function addRoutes(router, routes) {
|
|
|
490
302
|
backdrop-filter: blur(8px);
|
|
491
303
|
}
|
|
492
304
|
|
|
493
|
-
.dito-drop-target {
|
|
494
|
-
--shadow-alpha: 0.25;
|
|
495
|
-
|
|
496
|
-
background: $content-color-background;
|
|
497
|
-
border-radius: $border-radius;
|
|
498
|
-
z-index: $z-index-drag-overlay + 1;
|
|
499
|
-
filter: drop-shadow(0 4px 8px rgb(0, 0, 0, var(--shadow-alpha)));
|
|
500
|
-
}
|
|
501
|
-
|
|
502
305
|
.dito-drag-enter-active,
|
|
503
306
|
.dito-drag-leave-active {
|
|
504
|
-
$duration: 0.15s;
|
|
505
|
-
|
|
506
307
|
transition:
|
|
507
|
-
opacity $duration,
|
|
508
|
-
backdrop-filter $duration;
|
|
509
|
-
|
|
510
|
-
~ * .dito-drop-target {
|
|
511
|
-
transition: filter $duration;
|
|
512
|
-
}
|
|
308
|
+
opacity $drag-overlay-duration,
|
|
309
|
+
backdrop-filter $drag-overlay-duration;
|
|
513
310
|
}
|
|
514
311
|
|
|
515
312
|
.dito-drag-enter-from,
|
|
516
313
|
.dito-drag-leave-to {
|
|
517
314
|
opacity: 0;
|
|
518
315
|
backdrop-filter: blur(0);
|
|
519
|
-
|
|
520
|
-
~ * .dito-drop-target {
|
|
521
|
-
--shadow-alpha: 0;
|
|
522
|
-
}
|
|
523
316
|
}
|
|
524
317
|
</style>
|