@radio-garden/ditojs-admin 2.85.2-0.5067ad799
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/README.md +180 -0
- package/dist/dito-admin.css +1 -0
- package/dist/dito-admin.es.js +12106 -0
- package/dist/dito-admin.umd.js +7 -0
- package/package.json +96 -0
- package/src/DitoAdmin.js +293 -0
- package/src/DitoComponent.js +34 -0
- package/src/DitoContext.js +318 -0
- package/src/DitoTypeComponent.js +42 -0
- package/src/DitoUser.js +12 -0
- package/src/appState.js +12 -0
- package/src/components/DitoAccount.vue +60 -0
- package/src/components/DitoAffix.vue +68 -0
- package/src/components/DitoAffixes.vue +200 -0
- package/src/components/DitoButtons.vue +80 -0
- package/src/components/DitoClipboard.vue +186 -0
- package/src/components/DitoContainer.vue +374 -0
- package/src/components/DitoCreateButton.vue +146 -0
- package/src/components/DitoDialog.vue +242 -0
- package/src/components/DitoDraggable.vue +117 -0
- package/src/components/DitoEditButtons.vue +135 -0
- package/src/components/DitoErrors.vue +83 -0
- package/src/components/DitoForm.vue +521 -0
- package/src/components/DitoFormInner.vue +26 -0
- package/src/components/DitoFormNested.vue +17 -0
- package/src/components/DitoHeader.vue +84 -0
- package/src/components/DitoLabel.vue +200 -0
- package/src/components/DitoMenu.vue +186 -0
- package/src/components/DitoNavigation.vue +40 -0
- package/src/components/DitoNotifications.vue +170 -0
- package/src/components/DitoPagination.vue +42 -0
- package/src/components/DitoPane.vue +334 -0
- package/src/components/DitoPanel.vue +256 -0
- package/src/components/DitoPanels.vue +61 -0
- package/src/components/DitoRoot.vue +524 -0
- package/src/components/DitoSchema.vue +846 -0
- package/src/components/DitoSchemaInlined.vue +97 -0
- package/src/components/DitoScopes.vue +76 -0
- package/src/components/DitoSidebar.vue +50 -0
- package/src/components/DitoSpinner.vue +95 -0
- package/src/components/DitoTableCell.vue +64 -0
- package/src/components/DitoTableHead.vue +121 -0
- package/src/components/DitoTabs.vue +103 -0
- package/src/components/DitoTrail.vue +124 -0
- package/src/components/DitoTreeItem.vue +420 -0
- package/src/components/DitoUploadFile.vue +199 -0
- package/src/components/DitoVNode.vue +14 -0
- package/src/components/DitoView.vue +143 -0
- package/src/components/index.js +42 -0
- package/src/directives/resize.js +83 -0
- package/src/index.js +1 -0
- package/src/mixins/ContextMixin.js +68 -0
- package/src/mixins/DataMixin.js +131 -0
- package/src/mixins/DitoMixin.js +591 -0
- package/src/mixins/DomMixin.js +29 -0
- package/src/mixins/EmitterMixin.js +158 -0
- package/src/mixins/ItemMixin.js +144 -0
- package/src/mixins/LoadingMixin.js +23 -0
- package/src/mixins/NumberMixin.js +118 -0
- package/src/mixins/OptionsMixin.js +304 -0
- package/src/mixins/PulldownMixin.js +63 -0
- package/src/mixins/ResourceMixin.js +398 -0
- package/src/mixins/RouteMixin.js +190 -0
- package/src/mixins/SchemaParentMixin.js +33 -0
- package/src/mixins/SortableMixin.js +49 -0
- package/src/mixins/SourceMixin.js +734 -0
- package/src/mixins/TextMixin.js +26 -0
- package/src/mixins/TypeMixin.js +280 -0
- package/src/mixins/ValidationMixin.js +119 -0
- package/src/mixins/ValidatorMixin.js +57 -0
- package/src/mixins/ValueMixin.js +31 -0
- package/src/styles/_base.scss +17 -0
- package/src/styles/_button.scss +191 -0
- package/src/styles/_imports.scss +3 -0
- package/src/styles/_info.scss +19 -0
- package/src/styles/_layout.scss +19 -0
- package/src/styles/_pulldown.scss +38 -0
- package/src/styles/_scroll.scss +13 -0
- package/src/styles/_settings.scss +88 -0
- package/src/styles/_table.scss +223 -0
- package/src/styles/_tippy.scss +45 -0
- package/src/styles/style.scss +9 -0
- package/src/types/DitoTypeButton.vue +143 -0
- package/src/types/DitoTypeCheckbox.vue +27 -0
- package/src/types/DitoTypeCheckboxes.vue +65 -0
- package/src/types/DitoTypeCode.vue +199 -0
- package/src/types/DitoTypeColor.vue +272 -0
- package/src/types/DitoTypeComponent.vue +31 -0
- package/src/types/DitoTypeComputed.vue +50 -0
- package/src/types/DitoTypeDate.vue +99 -0
- package/src/types/DitoTypeLabel.vue +23 -0
- package/src/types/DitoTypeList.vue +364 -0
- package/src/types/DitoTypeMarkup.vue +700 -0
- package/src/types/DitoTypeMultiselect.vue +522 -0
- package/src/types/DitoTypeNumber.vue +66 -0
- package/src/types/DitoTypeObject.vue +136 -0
- package/src/types/DitoTypePanel.vue +18 -0
- package/src/types/DitoTypeProgress.vue +40 -0
- package/src/types/DitoTypeRadio.vue +45 -0
- package/src/types/DitoTypeSection.vue +80 -0
- package/src/types/DitoTypeSelect.vue +133 -0
- package/src/types/DitoTypeSlider.vue +66 -0
- package/src/types/DitoTypeSpacer.vue +11 -0
- package/src/types/DitoTypeSwitch.vue +40 -0
- package/src/types/DitoTypeText.vue +101 -0
- package/src/types/DitoTypeTextarea.vue +48 -0
- package/src/types/DitoTypeTreeList.vue +193 -0
- package/src/types/DitoTypeUpload.vue +503 -0
- package/src/types/index.js +30 -0
- package/src/utils/SchemaGraph.js +147 -0
- package/src/utils/accessor.js +75 -0
- package/src/utils/agent.js +47 -0
- package/src/utils/data.js +92 -0
- package/src/utils/filter.js +266 -0
- package/src/utils/math.js +14 -0
- package/src/utils/options.js +48 -0
- package/src/utils/path.js +5 -0
- package/src/utils/resource.js +44 -0
- package/src/utils/route.js +53 -0
- package/src/utils/schema.js +1121 -0
- package/src/utils/type.js +81 -0
- package/src/utils/uid.js +15 -0
- package/src/utils/units.js +5 -0
- package/src/validators/_creditcard.js +6 -0
- package/src/validators/_decimals.js +11 -0
- package/src/validators/_domain.js +6 -0
- package/src/validators/_email.js +6 -0
- package/src/validators/_hostname.js +6 -0
- package/src/validators/_integer.js +6 -0
- package/src/validators/_max.js +6 -0
- package/src/validators/_min.js +6 -0
- package/src/validators/_password.js +5 -0
- package/src/validators/_range.js +6 -0
- package/src/validators/_required.js +9 -0
- package/src/validators/_url.js +6 -0
- package/src/validators/index.js +12 -0
- package/src/verbs.js +17 -0
- package/types/index.d.ts +3298 -0
- package/types/tests/admin.test-d.ts +27 -0
- package/types/tests/component-buttons.test-d.ts +44 -0
- package/types/tests/component-list.test-d.ts +159 -0
- package/types/tests/component-misc.test-d.ts +137 -0
- package/types/tests/component-object.test-d.ts +69 -0
- package/types/tests/component-section.test-d.ts +174 -0
- package/types/tests/component-select.test-d.ts +107 -0
- package/types/tests/components.test-d.ts +81 -0
- package/types/tests/context.test-d.ts +31 -0
- package/types/tests/fixtures.ts +24 -0
- package/types/tests/form.test-d.ts +109 -0
- package/types/tests/instance.test-d.ts +20 -0
- package/types/tests/schema-features.test-d.ts +402 -0
- package/types/tests/variance.test-d.ts +125 -0
- package/types/tests/view.test-d.ts +146 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { toRaw } from 'vue'
|
|
2
|
+
import { getValueAtDataPath, isFunction } from '@ditojs/utils'
|
|
3
|
+
import {
|
|
4
|
+
getItemDataPath,
|
|
5
|
+
getParentItemDataPath,
|
|
6
|
+
getParentItem,
|
|
7
|
+
getItem,
|
|
8
|
+
getLastDataPathName,
|
|
9
|
+
getLastDataPathIndex
|
|
10
|
+
} from './utils/data.js'
|
|
11
|
+
|
|
12
|
+
const { hasOwnProperty } = Object.prototype
|
|
13
|
+
|
|
14
|
+
// See also `ContextMixin`: `DitoContext` instances are a thin wrapper around
|
|
15
|
+
// raw `context` objects, which themselves actually inherit from the linked
|
|
16
|
+
// `component` instance, so that they only need to provide the values that
|
|
17
|
+
// should be different than in the underlying component. In order to not expose
|
|
18
|
+
// all fields from the component, the wrapper is introduced.
|
|
19
|
+
|
|
20
|
+
// Use WeakMap for the raw `context` objects, so we don't have to pollute the
|
|
21
|
+
// actual `DitoContext` instance with it.
|
|
22
|
+
const contexts = new WeakMap()
|
|
23
|
+
|
|
24
|
+
function toObject(context) {
|
|
25
|
+
const rawStart = toRaw(context)
|
|
26
|
+
let raw = rawStart
|
|
27
|
+
let object = null
|
|
28
|
+
// In case `DitoContext.extend()` was used, we need to find the actual context
|
|
29
|
+
// object from the object's the inheritance chain:
|
|
30
|
+
do {
|
|
31
|
+
object = contexts.get(raw)
|
|
32
|
+
if (object) break
|
|
33
|
+
raw = Object.getPrototypeOf(raw)
|
|
34
|
+
} while (raw)
|
|
35
|
+
if (object && raw !== rawStart) {
|
|
36
|
+
// Assign the passed context with the original object as well, so we don't
|
|
37
|
+
// have to search for it again:
|
|
38
|
+
contexts.set(raw, object)
|
|
39
|
+
}
|
|
40
|
+
return object
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function get(context, key, defaultValue) {
|
|
44
|
+
const object = toObject(context)
|
|
45
|
+
const value = key in object ? object[key] : undefined
|
|
46
|
+
// If `object` explicitly sets the key to `undefined`, return it.
|
|
47
|
+
return value !== undefined || hasOwnProperty.call(object, key)
|
|
48
|
+
? value
|
|
49
|
+
: isFunction(defaultValue)
|
|
50
|
+
? defaultValue()
|
|
51
|
+
: defaultValue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function set(context, key, value) {
|
|
55
|
+
toObject(context)[key] = value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default class DitoContext {
|
|
59
|
+
constructor(component, context) {
|
|
60
|
+
// Use the provided params object / function, or create a new one:
|
|
61
|
+
context = context
|
|
62
|
+
? isFunction(context)
|
|
63
|
+
? context()
|
|
64
|
+
: { ...context }
|
|
65
|
+
: {}
|
|
66
|
+
// If not explicitly set (to false), default to true so we don't fall back
|
|
67
|
+
// to `component` for its value.
|
|
68
|
+
context.nested ??= true
|
|
69
|
+
context.component = component
|
|
70
|
+
// Have `object` inherit from the `component` instance, so it can override
|
|
71
|
+
// its values and still retrieve from it, and associate it with `this`
|
|
72
|
+
// through `contexts` map:
|
|
73
|
+
const object = Object.setPrototypeOf(context, component)
|
|
74
|
+
// No need for `toRaw(this)` here as it's always raw inside the constructor.
|
|
75
|
+
contexts.set(this, object)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static get(component, context) {
|
|
79
|
+
return context instanceof DitoContext
|
|
80
|
+
? context
|
|
81
|
+
: new DitoContext(component, context)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
extend(object) {
|
|
85
|
+
// Create a copy of this context that inherits from the real one, but
|
|
86
|
+
// overrides some properties with the ones from the passed `object`.
|
|
87
|
+
return Object.setPrototypeOf(object, this)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// `nested` is `true` when the data-path points a value inside an item, and
|
|
91
|
+
// `false` when it points to the item itself.
|
|
92
|
+
get nested() {
|
|
93
|
+
return get(this, 'nested', true)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get schema() {
|
|
97
|
+
return get(this, 'schema', null)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get value() {
|
|
101
|
+
return get(
|
|
102
|
+
this,
|
|
103
|
+
'value',
|
|
104
|
+
() =>
|
|
105
|
+
// If the value is not defined on the underlying object, it's not a type
|
|
106
|
+
// component. If it is nested, we can still get the value from the root.
|
|
107
|
+
this.nested
|
|
108
|
+
? getValueAtDataPath(this.rootItem, this.dataPath)
|
|
109
|
+
: undefined
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get dataPath() {
|
|
114
|
+
return get(this, 'dataPath', '')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get name() {
|
|
118
|
+
return get(this, 'name', () => getLastDataPathName(this.dataPath))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get index() {
|
|
122
|
+
return get(this, 'index', () => getLastDataPathIndex(this.dataPath))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get itemDataPath() {
|
|
126
|
+
return getItemDataPath(this.dataPath, this.nested)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get parentItemDataPath() {
|
|
130
|
+
return getParentItemDataPath(this.dataPath, this.nested)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get itemIndex() {
|
|
134
|
+
return getLastDataPathIndex(this.itemDataPath)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get parentItemIndex() {
|
|
138
|
+
return getLastDataPathIndex(this.parentItemDataPath)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// NOTE: While internally, we speak of `data`, in the API surface the
|
|
142
|
+
// term `item` is used for the data that relates to editing objects:
|
|
143
|
+
get item() {
|
|
144
|
+
return get(
|
|
145
|
+
this,
|
|
146
|
+
'data',
|
|
147
|
+
// If `data` isn't provided, we can determine it from rootData & dataPath:
|
|
148
|
+
() => getItem(this.rootItem, this.dataPath, this.nested) || null
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// NOTE: `parentItem` isn't the closest data parent to `item`, it's the
|
|
153
|
+
// closest parent that isn't an array, e.g. for relations or nested JSON data.
|
|
154
|
+
// This is why the term `item` was chosen over `data`, e.g. VS the use of
|
|
155
|
+
// `parentData` in server-sided validation, which is the closest parent. If
|
|
156
|
+
// needed, we could expose this data here too, as we can do all sorts of data
|
|
157
|
+
// processing with `rootData` and `dataPath`.
|
|
158
|
+
get parentItem() {
|
|
159
|
+
const item = (
|
|
160
|
+
getParentItem(this.rootItem, this.dataPath, this.nested) || null
|
|
161
|
+
)
|
|
162
|
+
return item !== this.item ? item : null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get rootItem() {
|
|
166
|
+
return get(this, 'rootData', null)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get processedItem() {
|
|
170
|
+
return get(this, 'processedData', null)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get processedRootItem() {
|
|
174
|
+
return get(this, 'processedRootData', null)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get clipboardItem() {
|
|
178
|
+
return get(this, 'clipboardData', null)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
get user() {
|
|
182
|
+
return get(this, 'user', null)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get api() {
|
|
186
|
+
return get(this, 'api', null)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
get views() {
|
|
190
|
+
return get(this, 'views', null)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get flattenedViews() {
|
|
194
|
+
return get(this, 'flattenedViews', null)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
get itemLabel() {
|
|
198
|
+
return get(this, 'itemLabel', null)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get formLabel() {
|
|
202
|
+
return get(this, 'formLabel', null)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// NOTE: Like 'options', the associated component doesn't always exist, e.g.
|
|
206
|
+
// in nested forms during `processData()`. But `schema` is guaranteed to
|
|
207
|
+
// always be available.
|
|
208
|
+
get component() {
|
|
209
|
+
return get(this, 'component', null)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// TODO: Add `componentSchema` getter for the schema of the current component,
|
|
213
|
+
// even when the component isn't actually instantiated. Consider adding
|
|
214
|
+
// `sourceSchema` as well?
|
|
215
|
+
|
|
216
|
+
// TODO: Fix unclear naming: Which schema is this, that of the component or of
|
|
217
|
+
// its parent? Isn't exposing `formComponent` and `viewComponent` enough, once
|
|
218
|
+
// we offer access to their components there through `getComponent()` & co. on
|
|
219
|
+
// `DitoMixin` perhaps? Also, there could be a `tabComponent` getter for
|
|
220
|
+
// schemas in tabs?
|
|
221
|
+
get schemaComponent() {
|
|
222
|
+
return get(this, 'schemaComponent', null)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
get formComponent() {
|
|
226
|
+
return get(this, 'formComponent', null)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get viewComponent() {
|
|
230
|
+
return get(this, 'viewComponent', null)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
get dialogComponent() {
|
|
234
|
+
return get(this, 'dialogComponent', null)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
get panelComponent() {
|
|
238
|
+
return get(this, 'panelComponent', null)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get resourceComponent() {
|
|
242
|
+
return get(this, 'resourceComponent', null)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
get sourceComponent() {
|
|
246
|
+
return get(this, 'sourceComponent', null)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// When used in OptionsMixin for `schema.options.value()`,
|
|
250
|
+
// `schema.options.label()` and `schema.search.filter()` callbacks:
|
|
251
|
+
get option() {
|
|
252
|
+
return get(this, 'option', undefined)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
get options() {
|
|
256
|
+
return get(this, 'options', undefined)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
get open() {
|
|
260
|
+
return get(this, 'open', undefined)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// For search term in selects:
|
|
264
|
+
get searchTerm() {
|
|
265
|
+
return get(this, 'searchTerm', undefined)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// The error field is only populated in the context of buttons that send
|
|
269
|
+
// requests, see `ResourceMixin.emitButtonEvent()`:
|
|
270
|
+
get error() {
|
|
271
|
+
return get(this, 'error', undefined)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get wasNotified() {
|
|
275
|
+
return get(this, 'wasNotified', false)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get isRunning() {
|
|
279
|
+
return get(this, 'isRunning', false)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
set isRunning(value) {
|
|
283
|
+
set(this, 'isRunning', value)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
get query() {
|
|
287
|
+
return this.component.$route.query
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Helper Methods
|
|
291
|
+
|
|
292
|
+
get request() {
|
|
293
|
+
return options => this.component.request(options)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
get format() {
|
|
297
|
+
return (value, options) => this.component.format(value, options)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
get navigate() {
|
|
301
|
+
return location => this.component.navigate(location)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
get download() {
|
|
305
|
+
return options => this.component.download(options)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
get getResourceUrl() {
|
|
309
|
+
return resource => this.component.getResourceUrl(resource)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
get notify() {
|
|
313
|
+
return options => {
|
|
314
|
+
this.component.notify(options)
|
|
315
|
+
set(this, 'wasNotified', true)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// DitoTypeComponent is the abstract base component for all other type
|
|
2
|
+
// components inside the types/ folder. There's also a separate concrete
|
|
3
|
+
// `DitoTypeComponent.vue` component, use to render `{ type: 'component' }`
|
|
4
|
+
import { asArray, camelize } from '@ditojs/utils'
|
|
5
|
+
import DitoComponent from './DitoComponent.js'
|
|
6
|
+
import TypeMixin from './mixins/TypeMixin.js'
|
|
7
|
+
import { registerTypeComponent, getTypeComponent } from './utils/schema.js'
|
|
8
|
+
|
|
9
|
+
// @vue/component
|
|
10
|
+
export default {
|
|
11
|
+
extends: DitoComponent,
|
|
12
|
+
mixins: [TypeMixin],
|
|
13
|
+
|
|
14
|
+
nativeField: false,
|
|
15
|
+
textField: false,
|
|
16
|
+
// Set reasonable defaults for all of these that are used by most type
|
|
17
|
+
// components. These only need defining in sub-classes when they differ.
|
|
18
|
+
defaultValue: null,
|
|
19
|
+
defaultNested: true,
|
|
20
|
+
defaultVisible: true,
|
|
21
|
+
defaultMultiple: false,
|
|
22
|
+
generateLabel: true,
|
|
23
|
+
excludeValue: false,
|
|
24
|
+
ignoreMissingValue: null,
|
|
25
|
+
omitSpacing: false,
|
|
26
|
+
|
|
27
|
+
component: DitoComponent.component,
|
|
28
|
+
|
|
29
|
+
get: getTypeComponent,
|
|
30
|
+
|
|
31
|
+
register(types, definition = {}) {
|
|
32
|
+
types = asArray(types)
|
|
33
|
+
const component = this.component(
|
|
34
|
+
`DitoType${camelize(types[0], true)}`,
|
|
35
|
+
definition
|
|
36
|
+
)
|
|
37
|
+
for (const type of types) {
|
|
38
|
+
registerTypeComponent(type, component)
|
|
39
|
+
}
|
|
40
|
+
return component
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/DitoUser.js
ADDED
package/src/appState.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { reactive } from 'vue'
|
|
2
|
+
import { parseUserAgent } from './utils/agent'
|
|
3
|
+
|
|
4
|
+
export default reactive({
|
|
5
|
+
title: '',
|
|
6
|
+
routeComponents: [],
|
|
7
|
+
user: null,
|
|
8
|
+
agent: parseUserAgent(navigator.userAgent || ''),
|
|
9
|
+
loadCache: {}, // See TypeMixin.load()
|
|
10
|
+
activeLabel: null,
|
|
11
|
+
clipboardData: null
|
|
12
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.dito-account
|
|
3
|
+
a(
|
|
4
|
+
@mousedown.stop="onPulldownMouseDown()"
|
|
5
|
+
)
|
|
6
|
+
span {{ user.username }}
|
|
7
|
+
ul.dito-pulldown(:class="{ 'dito-pulldown--open': pulldown.open }")
|
|
8
|
+
li(
|
|
9
|
+
v-for="(label, value) of items"
|
|
10
|
+
)
|
|
11
|
+
a.dito-pulldown__item(
|
|
12
|
+
@mousedown.stop="onPulldownMouseDown(value)"
|
|
13
|
+
@mouseup="onPulldownMouseUp(value)"
|
|
14
|
+
) {{ label }}
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import DitoComponent from '../DitoComponent.js'
|
|
19
|
+
import PulldownMixin from '../mixins/PulldownMixin.js'
|
|
20
|
+
|
|
21
|
+
// @vue/component
|
|
22
|
+
export default DitoComponent.component('DitoAccount', {
|
|
23
|
+
mixins: [PulldownMixin],
|
|
24
|
+
|
|
25
|
+
data() {
|
|
26
|
+
return {
|
|
27
|
+
items: {
|
|
28
|
+
settings: 'Settings',
|
|
29
|
+
logout: 'Logout'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
methods: {
|
|
35
|
+
onPulldownSelect(value) {
|
|
36
|
+
switch (value) {
|
|
37
|
+
case 'logout':
|
|
38
|
+
this.rootComponent.logout()
|
|
39
|
+
break
|
|
40
|
+
case 'settings':
|
|
41
|
+
console.info('TODO: Implement Settings')
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<style lang="scss">
|
|
50
|
+
@import '../styles/_imports';
|
|
51
|
+
|
|
52
|
+
.dito-account {
|
|
53
|
+
position: relative;
|
|
54
|
+
display: inline-block;
|
|
55
|
+
|
|
56
|
+
.dito-pulldown {
|
|
57
|
+
top: $pulldown-padding-ver;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
component(
|
|
3
|
+
v-if="item.type === 'text'"
|
|
4
|
+
:is="item.as || 'div'"
|
|
5
|
+
) {{ item.text }}
|
|
6
|
+
component(
|
|
7
|
+
v-else-if="item.type === 'html'"
|
|
8
|
+
:is="item.as || 'div'"
|
|
9
|
+
v-html="item.html"
|
|
10
|
+
)
|
|
11
|
+
DitoSpinner(
|
|
12
|
+
v-else-if="item.type === 'spinner'"
|
|
13
|
+
:size="item.size"
|
|
14
|
+
:color="item.color"
|
|
15
|
+
)
|
|
16
|
+
DitoIcon(
|
|
17
|
+
v-else-if="item.type === 'icon'"
|
|
18
|
+
:name="item.name"
|
|
19
|
+
:disabled="item.disabled"
|
|
20
|
+
)
|
|
21
|
+
component(
|
|
22
|
+
v-else-if="item.type === 'component'"
|
|
23
|
+
:is="item.component"
|
|
24
|
+
v-bind="item.props"
|
|
25
|
+
)
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import DitoComponent from '../DitoComponent.js'
|
|
30
|
+
import DitoSpinner from './DitoSpinner.vue'
|
|
31
|
+
import { DitoIcon } from '@ditojs/ui/src'
|
|
32
|
+
|
|
33
|
+
export default DitoComponent.component('DitoAffix', {
|
|
34
|
+
components: { DitoSpinner, DitoIcon },
|
|
35
|
+
|
|
36
|
+
props: {
|
|
37
|
+
item: { type: Object, required: true },
|
|
38
|
+
parentContext: { type: Object, required: true }
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
computed: {
|
|
42
|
+
// Override DitoMixin's context with the parent's context
|
|
43
|
+
context() {
|
|
44
|
+
return this.parentContext
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style lang="scss">
|
|
51
|
+
@import '../styles/_imports';
|
|
52
|
+
|
|
53
|
+
.dito-affix {
|
|
54
|
+
&--html,
|
|
55
|
+
&--text {
|
|
56
|
+
display: inline-block;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&--ellipsis {
|
|
60
|
+
@include ellipsis;
|
|
61
|
+
|
|
62
|
+
flex: 1;
|
|
63
|
+
display: block;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
width: 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.dito-affixes(
|
|
3
|
+
v-if="hasContent"
|
|
4
|
+
:class="classes"
|
|
5
|
+
)
|
|
6
|
+
slot(name="prepend")
|
|
7
|
+
DitoAffix.dito-affix(
|
|
8
|
+
v-for="(item, index) in visibleItems"
|
|
9
|
+
:key="index"
|
|
10
|
+
:class="getItemClasses(item)"
|
|
11
|
+
:style="item.style"
|
|
12
|
+
:item="item"
|
|
13
|
+
:parentContext="parentContext"
|
|
14
|
+
)
|
|
15
|
+
button.dito-affixes__clear(
|
|
16
|
+
v-if="clearable"
|
|
17
|
+
type="button"
|
|
18
|
+
title="Clear"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
@click.stop="$emit('clear')"
|
|
21
|
+
@mousedown.stop
|
|
22
|
+
)
|
|
23
|
+
slot(name="append")
|
|
24
|
+
.dito-info(
|
|
25
|
+
v-if="inlineInfo"
|
|
26
|
+
:data-info="inlineInfo"
|
|
27
|
+
)
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
import DitoComponent from '../DitoComponent.js'
|
|
32
|
+
import DitoAffix from './DitoAffix.vue'
|
|
33
|
+
import { asArray, isString } from '@ditojs/utils'
|
|
34
|
+
import { hasSlotContent } from '@ditojs/ui/src'
|
|
35
|
+
import { shouldRenderSchema } from '../utils/schema.js'
|
|
36
|
+
|
|
37
|
+
export default DitoComponent.component('DitoAffixes', {
|
|
38
|
+
components: { DitoAffix },
|
|
39
|
+
emits: ['clear'],
|
|
40
|
+
|
|
41
|
+
props: {
|
|
42
|
+
items: { type: [String, Object, Array], default: null },
|
|
43
|
+
mode: { type: String, default: null },
|
|
44
|
+
position: { type: String, default: null },
|
|
45
|
+
absolute: { type: Boolean, default: false },
|
|
46
|
+
clearable: { type: Boolean, default: false },
|
|
47
|
+
disabled: { type: Boolean, default: false },
|
|
48
|
+
inlineInfo: { type: String, default: null },
|
|
49
|
+
parentContext: { type: Object, required: true }
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
computed: {
|
|
53
|
+
// Override DitoMixin's context with the parent's context
|
|
54
|
+
context() {
|
|
55
|
+
return this.parentContext
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
processedItems() {
|
|
59
|
+
return asArray(this.items)
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.map(item =>
|
|
62
|
+
isString(item)
|
|
63
|
+
? { type: 'text', text: item }
|
|
64
|
+
: item.type
|
|
65
|
+
? item
|
|
66
|
+
: item.text != null
|
|
67
|
+
? { type: 'text', ...item }
|
|
68
|
+
: item.html != null
|
|
69
|
+
? { type: 'html', ...item }
|
|
70
|
+
: item
|
|
71
|
+
)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
visibleItems() {
|
|
75
|
+
return this.processedItems.filter(item =>
|
|
76
|
+
shouldRenderSchema(item, this.context)
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
classes() {
|
|
81
|
+
const prefix = 'dito-affixes'
|
|
82
|
+
return {
|
|
83
|
+
[`${prefix}--${this.position}`]: this.position,
|
|
84
|
+
[`${prefix}--${this.mode}`]: this.mode,
|
|
85
|
+
[`${prefix}--absolute`]: this.absolute
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
hasContent() {
|
|
90
|
+
return (
|
|
91
|
+
this.visibleItems.length > 0 ||
|
|
92
|
+
this.clearable ||
|
|
93
|
+
this.inlineInfo ||
|
|
94
|
+
hasSlotContent(this.$slots.prepend) ||
|
|
95
|
+
hasSlotContent(this.$slots.append)
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
methods: {
|
|
101
|
+
getItemClasses(item) {
|
|
102
|
+
const prefix = 'dito-affix'
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
[`${prefix}--${item.type}`]: item.type,
|
|
106
|
+
[`${prefix}--ellipsis`]: (
|
|
107
|
+
this.mode === 'ellipsis' && ['text', 'html'].includes(item.type)
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
item.class
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<style lang="scss">
|
|
118
|
+
@import '../styles/_imports';
|
|
119
|
+
|
|
120
|
+
.dito-affixes {
|
|
121
|
+
$self: &;
|
|
122
|
+
|
|
123
|
+
@include user-select(none);
|
|
124
|
+
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: $input-padding-hor;
|
|
128
|
+
|
|
129
|
+
&--absolute {
|
|
130
|
+
position: absolute;
|
|
131
|
+
inset: $border-width;
|
|
132
|
+
margin: $input-padding-ver $input-padding-hor;
|
|
133
|
+
pointer-events: none;
|
|
134
|
+
|
|
135
|
+
> * {
|
|
136
|
+
pointer-events: auto;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&#{$self}--prefix {
|
|
140
|
+
right: unset;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
&#{$self}--suffix {
|
|
144
|
+
left: unset;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&--ellipsis {
|
|
149
|
+
flex: 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&--input {
|
|
153
|
+
color: $color-grey;
|
|
154
|
+
|
|
155
|
+
.dito-icon--disabled {
|
|
156
|
+
color: $color-light;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@at-root .dito-component:not(:has(.dito-component)):focus-within & {
|
|
160
|
+
color: $color-active;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&__clear {
|
|
165
|
+
--size: #{calc($input-height - 4 * $border-width)};
|
|
166
|
+
|
|
167
|
+
display: none;
|
|
168
|
+
position: relative;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
width: var(--size);
|
|
171
|
+
height: var(--size);
|
|
172
|
+
border-radius: $border-radius;
|
|
173
|
+
margin: (-$input-padding-ver + $border-width)
|
|
174
|
+
(-$input-padding-hor + $border-width);
|
|
175
|
+
padding: 0;
|
|
176
|
+
border: 0;
|
|
177
|
+
|
|
178
|
+
@at-root .dito-component:not(:has(.dito-component)):hover & {
|
|
179
|
+
display: block;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
&::before {
|
|
183
|
+
@extend %icon-clear;
|
|
184
|
+
|
|
185
|
+
color: $color-grey;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
&:hover::before {
|
|
189
|
+
color: $color-black;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Hide other affixes when clear button is shown
|
|
194
|
+
// prettier-ignore
|
|
195
|
+
@at-root .dito-component:hover:not(:has(.dito-component))
|
|
196
|
+
#{$self}:has(#{$self}__clear) > *:not(#{$self}__clear) {
|
|
197
|
+
display: none;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</style>
|