viur-ui 0.0.1
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 +79 -0
- package/index.d.ts +351 -0
- package/index.js +46 -0
- package/package.json +84 -0
- package/src/components/Browser/Browser.vue +496 -0
- package/src/components/Browser/TreeNav.vue +408 -0
- package/src/components/Browser/ViurBrowser.d.vue.ts +64 -0
- package/src/components/Browser/ViurBrowser.vue +271 -0
- package/src/components/Browser/dragSession.ts +134 -0
- package/src/components/Browser/index.ts +14 -0
- package/src/components/Browser/types.ts +30 -0
- package/src/components/DataTable/ColumnFilterPanel.vue +225 -0
- package/src/components/DataTable/DataTable.vue +3155 -0
- package/src/components/DataTable/ViurDataTable.d.vue.ts +72 -0
- package/src/components/DataTable/ViurDataTable.vue +331 -0
- package/src/components/DataTable/columns.ts +400 -0
- package/src/components/DataTable/index.ts +60 -0
- package/src/components/DataTable/rows.ts +446 -0
- package/src/components/DataTable/types.ts +267 -0
- package/src/components/DataTable/useColumnLayout.ts +294 -0
- package/src/components/DataTable/useGridNavigation.ts +193 -0
- package/src/components/DataTable/useVirtualRows.ts +126 -0
- package/src/components/DataTable/viurColumns.ts +330 -0
- package/src/forms/ViForm.d.vue.ts +106 -0
- package/src/forms/ViForm.vue +592 -0
- package/src/forms/ViFormCategory.vue +57 -0
- package/src/forms/bones/Bone.vue +1023 -0
- package/src/forms/bones/BoneActions.vue +127 -0
- package/src/forms/bones/BoneLabel.vue +122 -0
- package/src/forms/bones/BoneMultipleList.vue +91 -0
- package/src/forms/bones/BoneMultipleWrapper.vue +105 -0
- package/src/forms/bones/BoneNestedWrapper.vue +155 -0
- package/src/forms/bones/BoneSingleEntry.vue +51 -0
- package/src/forms/bones/FilterBone.vue +694 -0
- package/src/forms/bones/actionbar/defaultBar.vue +98 -0
- package/src/forms/bones/actionbar/fileBar.vue +165 -0
- package/src/forms/bones/actionbar/relationalBar.vue +125 -0
- package/src/forms/bones/actions/ActionDialog.vue +70 -0
- package/src/forms/bones/actions/Debug.vue +106 -0
- package/src/forms/bones/actions/Delete.vue +13 -0
- package/src/forms/bones/actions/DescribeImage.vue +201 -0
- package/src/forms/bones/actions/Download.vue +27 -0
- package/src/forms/bones/actions/Placeholder.vue +14 -0
- package/src/forms/bones/actions/Translation.vue +238 -0
- package/src/forms/bones/actions/index.js +280 -0
- package/src/forms/bones/bone.d.ts +45 -0
- package/src/forms/bones/country_information.json +779 -0
- package/src/forms/bones/index.js +225 -0
- package/src/forms/bones/uploadAdapter.js +46 -0
- package/src/forms/bones/utils.js +284 -0
- package/src/forms/bones/view/base_item.vue +35 -0
- package/src/forms/bones/view/boneLogic.js +202 -0
- package/src/forms/bones/view/index.js +5 -0
- package/src/forms/bones/widgets/booleanBone.vue +83 -0
- package/src/forms/bones/widgets/booleanBoneChoose.vue +57 -0
- package/src/forms/bones/widgets/booleanBoneSelect.vue +58 -0
- package/src/forms/bones/widgets/codeBone.vue +214 -0
- package/src/forms/bones/widgets/colorBone.vue +75 -0
- package/src/forms/bones/widgets/dateBone.vue +114 -0
- package/src/forms/bones/widgets/emailBone.vue +100 -0
- package/src/forms/bones/widgets/fileBone.vue +273 -0
- package/src/forms/bones/widgets/jsonBone.vue +63 -0
- package/src/forms/bones/widgets/keyBone.vue +71 -0
- package/src/forms/bones/widgets/numericBone.vue +125 -0
- package/src/forms/bones/widgets/passwordBone.vue +215 -0
- package/src/forms/bones/widgets/phoneBone.vue +173 -0
- package/src/forms/bones/widgets/rawBone.vue +94 -0
- package/src/forms/bones/widgets/recordBone.vue +64 -0
- package/src/forms/bones/widgets/relationalBone.vue +164 -0
- package/src/forms/bones/widgets/relationalBoneSelect.vue +120 -0
- package/src/forms/bones/widgets/selectBone.vue +122 -0
- package/src/forms/bones/widgets/selectBoneChoose.vue +104 -0
- package/src/forms/bones/widgets/spatialBone.vue +111 -0
- package/src/forms/bones/widgets/stringBone.vue +139 -0
- package/src/forms/bones/widgets/textBone.vue +641 -0
- package/src/forms/install.d.ts +4 -0
- package/src/forms/install.js +43 -0
- package/src/forms/layouts/LayloutList2Columns.vue +57 -0
- package/src/forms/layouts/LayoutCategory.vue +38 -0
- package/src/forms/layouts/LayoutList.vue +31 -0
- package/src/forms/utils.js +601 -0
- package/src/public/assets/icons/fontawesome-manifest.json +8 -0
- package/src/public/assets/icons/regular/file.svg +1 -0
- package/src/public/assets/icons/solid/arrows-left-right.svg +1 -0
- package/src/public/assets/icons/solid/bug.svg +1 -0
- package/src/public/assets/icons/solid/check.svg +1 -0
- package/src/public/assets/icons/solid/circle-info.svg +1 -0
- package/src/public/assets/icons/solid/compress.svg +1 -0
- package/src/public/assets/icons/solid/download.svg +1 -0
- package/src/public/assets/icons/solid/expand.svg +1 -0
- package/src/public/assets/icons/solid/grip-vertical.svg +1 -0
- package/src/public/assets/icons/solid/plus.svg +1 -0
- package/src/public/assets/icons/solid/question.svg +1 -0
- package/src/public/assets/icons/solid/triangle-exclamation.svg +1 -0
- package/src/public/assets/icons/solid/upload.svg +1 -0
- package/src/public/assets/icons/solid/xmark.svg +1 -0
- package/src/request/cache.js +183 -0
- package/src/request/configStore.js +516 -0
- package/src/request/envelope.js +211 -0
- package/src/request/index.js +35 -0
- package/src/request/messageStore.js +201 -0
- package/src/request/request.js +752 -0
- package/src/request/skel.js +90 -0
- package/src/theme/colorStore.js +168 -0
- package/src/translations/de.js +178 -0
- package/src/translations/en.js +177 -0
- package/src/user/LoginScreen.d.vue.ts +54 -0
- package/src/user/LoginScreen.vue +627 -0
- package/src/user/google.js +75 -0
- package/src/user/index.js +3 -0
- package/src/user/store.d.ts +59 -0
- package/src/user/store.js +183 -0
- package/src/webawesomeCompat.css +72 -0
- package/src/webawesomeConfig.js +61 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import rawBone from "./widgets/rawBone.vue"
|
|
2
|
+
import keyBone from "./widgets/keyBone.vue"
|
|
3
|
+
import stringBone from "./widgets/stringBone.vue"
|
|
4
|
+
import emailBone from "./widgets/emailBone.vue"
|
|
5
|
+
import dateBone from "./widgets/dateBone.vue"
|
|
6
|
+
import selectBone from "./widgets/selectBone.vue"
|
|
7
|
+
import booleanBone from "./widgets/booleanBone.vue"
|
|
8
|
+
import passwordBone from "./widgets/passwordBone.vue"
|
|
9
|
+
import recordBone from "./widgets/recordBone.vue"
|
|
10
|
+
import colorBone from "./widgets/colorBone.vue"
|
|
11
|
+
import numericBone from "./widgets/numericBone.vue"
|
|
12
|
+
import relationalBone from "./widgets/relationalBone.vue"
|
|
13
|
+
import fileBone from "./widgets/fileBone.vue"
|
|
14
|
+
import spatialBone from "./widgets/spatialBone.vue"
|
|
15
|
+
|
|
16
|
+
import booleanBoneSelect from "./widgets/booleanBoneSelect.vue"
|
|
17
|
+
import booleanBoneChoose from "./widgets/booleanBoneChoose.vue"
|
|
18
|
+
import selectBoneChoose from "./widgets/selectBoneChoose.vue"
|
|
19
|
+
import relationalBoneSelect from "./widgets/relationalBoneSelect.vue"
|
|
20
|
+
|
|
21
|
+
import defaultBar from "./actionbar/defaultBar.vue"
|
|
22
|
+
import relationalBar from "./actionbar/relationalBar.vue"
|
|
23
|
+
import fileBar from "./actionbar/fileBar.vue"
|
|
24
|
+
|
|
25
|
+
import { reactive, shallowRef, defineAsyncComponent } from "vue"
|
|
26
|
+
import { defineStore } from "pinia"
|
|
27
|
+
|
|
28
|
+
// Heavy bones — lazy loaded so consumers don't pay for their deps unless used.
|
|
29
|
+
// json-editor-vue (~60KB+), libphonenumber-js (~150KB), CodeMirror suite (~200KB+),
|
|
30
|
+
// CKEditor5 suite (~400KB+) only land in the bundle when a form actually renders
|
|
31
|
+
// the corresponding bone type.
|
|
32
|
+
const jsonBone = defineAsyncComponent(() => import("./widgets/jsonBone.vue"))
|
|
33
|
+
const phoneBone = defineAsyncComponent(() => import("./widgets/phoneBone.vue"))
|
|
34
|
+
const codeBone = defineAsyncComponent(() => import("./widgets/codeBone.vue"))
|
|
35
|
+
const textBone = defineAsyncComponent(() => import("./widgets/textBone.vue"))
|
|
36
|
+
|
|
37
|
+
export const useBoneStore = defineStore("boneStore", () => {
|
|
38
|
+
const state = reactive({
|
|
39
|
+
additionalBones: shallowRef({}),
|
|
40
|
+
defaultBones: shallowRef({
|
|
41
|
+
rawBone,
|
|
42
|
+
keyBone,
|
|
43
|
+
stringBone,
|
|
44
|
+
emailBone,
|
|
45
|
+
dateBone,
|
|
46
|
+
booleanBone,
|
|
47
|
+
selectBone,
|
|
48
|
+
passwordBone,
|
|
49
|
+
recordBone,
|
|
50
|
+
numericBone,
|
|
51
|
+
colorBone,
|
|
52
|
+
relationalBone,
|
|
53
|
+
jsonBone,
|
|
54
|
+
fileBone,
|
|
55
|
+
textBone,
|
|
56
|
+
spatialBone,
|
|
57
|
+
phoneBone,
|
|
58
|
+
booleanBoneSelect,
|
|
59
|
+
booleanBoneChoose,
|
|
60
|
+
selectBoneChoose,
|
|
61
|
+
relationalBoneSelect,
|
|
62
|
+
codeBone,
|
|
63
|
+
}),
|
|
64
|
+
actionbars: shallowRef({
|
|
65
|
+
"relational.tree.leaf.file.file": fileBar,
|
|
66
|
+
"relational.": relationalBar,
|
|
67
|
+
}),
|
|
68
|
+
multibones: shallowRef(["select", "select."]),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
function addBoneWidget(boneType, widget) {
|
|
72
|
+
state.additionalBones[boneType] = widget
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function importWidgets() {
|
|
76
|
+
let bones = state.defaultBones
|
|
77
|
+
for (const [k, v] of Object.entries(state.additionalBones)) {
|
|
78
|
+
bones.add(v)
|
|
79
|
+
}
|
|
80
|
+
return bones
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getBoneWidget(boneType) {
|
|
84
|
+
if (Object.keys(state.additionalBones).includes(boneType)) {
|
|
85
|
+
//exact match
|
|
86
|
+
return state.additionalBones[boneType]
|
|
87
|
+
} else {
|
|
88
|
+
let typeParts = boneType.split(".") //prefix match
|
|
89
|
+
let matchingPrefixes = Object.entries(state.additionalBones).filter((prefix) =>
|
|
90
|
+
prefix[0].startsWith(typeParts[0] + ".")
|
|
91
|
+
)
|
|
92
|
+
if (matchingPrefixes.length > 0) {
|
|
93
|
+
matchingPrefixes.sort((a, b) => b.length - a.length)
|
|
94
|
+
for (let prefix of matchingPrefixes) {
|
|
95
|
+
if (boneType.startsWith(prefix[0])) {
|
|
96
|
+
return state.additionalBones[prefix[0]]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (boneType === "date" || boneType.startsWith("date.")) {
|
|
103
|
+
return dateBone
|
|
104
|
+
} else if (boneType === "key") {
|
|
105
|
+
return keyBone
|
|
106
|
+
} else if (boneType === "str.email") {
|
|
107
|
+
return emailBone
|
|
108
|
+
} else if (boneType === "str.phone") {
|
|
109
|
+
return phoneBone
|
|
110
|
+
} else if (boneType === "str" || boneType.startsWith("str.")) {
|
|
111
|
+
return stringBone
|
|
112
|
+
} else if (boneType === "select.choose") {
|
|
113
|
+
return selectBoneChoose
|
|
114
|
+
} else if (boneType === "select" || boneType.startsWith("select.")) {
|
|
115
|
+
return selectBone
|
|
116
|
+
} else if (boneType === "bool.choose") {
|
|
117
|
+
return booleanBoneChoose
|
|
118
|
+
} else if (boneType === "bool.select") {
|
|
119
|
+
return booleanBoneSelect
|
|
120
|
+
} else if (boneType === "bool" || boneType.startsWith("bool.")) {
|
|
121
|
+
return booleanBone
|
|
122
|
+
} else if (boneType === "password" || boneType.startsWith("password.")) {
|
|
123
|
+
return passwordBone
|
|
124
|
+
} else if (boneType === "record" || boneType.startsWith("record.")) {
|
|
125
|
+
return recordBone
|
|
126
|
+
} else if (boneType === "numeric" || boneType.startsWith("numeric.")) {
|
|
127
|
+
return numericBone
|
|
128
|
+
} else if (boneType === "relational.tree.leaf.file.file" || boneType.startsWith("relational.tree.leaf.file.")) {
|
|
129
|
+
return fileBone
|
|
130
|
+
} else if (boneType === "relational.select" || boneType.startsWith("relational.select.")) {
|
|
131
|
+
return relationalBoneSelect
|
|
132
|
+
} else if (boneType === "relational" || boneType.startsWith("relational.")) {
|
|
133
|
+
return relationalBone
|
|
134
|
+
} else if (boneType === "raw.json") {
|
|
135
|
+
return jsonBone
|
|
136
|
+
} else if (boneType.startsWith("raw.code.")) {
|
|
137
|
+
return codeBone
|
|
138
|
+
} else if (boneType === "color" || boneType.startsWith("color.")) {
|
|
139
|
+
return colorBone
|
|
140
|
+
} else if (boneType === "text" || boneType.startsWith("text.")) {
|
|
141
|
+
return textBone
|
|
142
|
+
} else if (boneType === "spatial" || boneType.startsWith("spatial.")) {
|
|
143
|
+
return spatialBone
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return rawBone
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function addBoneActionbar(boneType, widget) {
|
|
150
|
+
state.actionbars[boneType] = widget
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getBoneActionbar(boneType) {
|
|
154
|
+
if (Object.keys(state.actionbars).includes(boneType)) {
|
|
155
|
+
//exact match
|
|
156
|
+
return state.actionbars[boneType]
|
|
157
|
+
} else {
|
|
158
|
+
let typeParts = boneType.split(".") //prefix match
|
|
159
|
+
let matchingPrefixes = Object.entries(state.actionbars).filter((prefix) =>
|
|
160
|
+
prefix[0].startsWith(typeParts[0] + ".")
|
|
161
|
+
)
|
|
162
|
+
if (matchingPrefixes.length > 0) {
|
|
163
|
+
matchingPrefixes.sort((a, b) => b.length - a.length)
|
|
164
|
+
for (let prefix of matchingPrefixes) {
|
|
165
|
+
if (boneType.startsWith(prefix[0])) {
|
|
166
|
+
return state.actionbars[prefix[0]]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return defaultBar
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
state,
|
|
177
|
+
addBoneWidget,
|
|
178
|
+
getBoneWidget,
|
|
179
|
+
importWidgets,
|
|
180
|
+
|
|
181
|
+
addBoneActionbar,
|
|
182
|
+
getBoneActionbar,
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
export function addBoneActionbar(boneType, widget) {
|
|
187
|
+
const boneStore = useBoneStore()
|
|
188
|
+
return boneStore.addBoneActionbar(boneType, widget)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getBoneActionbar(boneType) {
|
|
192
|
+
const boneStore = useBoneStore()
|
|
193
|
+
return boneStore.getBoneActionbar(boneType)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getBoneWidget(boneType) {
|
|
197
|
+
const boneStore = useBoneStore()
|
|
198
|
+
return boneStore.getBoneWidget(boneType)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function addBoneWidget(boneType, widget) {
|
|
202
|
+
const boneStore = useBoneStore()
|
|
203
|
+
boneStore.addBoneWidget(boneType, widget)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function BoneHasMultipleHandling(boneType) {
|
|
207
|
+
const boneStore = useBoneStore()
|
|
208
|
+
if (boneStore.state.multibones.includes(boneType)) {
|
|
209
|
+
return true
|
|
210
|
+
} else {
|
|
211
|
+
let typeParts = boneType.split(".") //prefix match
|
|
212
|
+
let matchingPrefixes = Object.entries(boneStore.state.multibones).filter((prefix) =>
|
|
213
|
+
prefix[1].startsWith(typeParts[0] + ".")
|
|
214
|
+
)
|
|
215
|
+
if (matchingPrefixes.length > 0) {
|
|
216
|
+
matchingPrefixes.sort((a, b) => b.length - a.length)
|
|
217
|
+
for (let prefix of matchingPrefixes) {
|
|
218
|
+
if (boneType.startsWith(prefix[1])) {
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { downloadUrlFor, uploadFile } from "../../request/index.js"
|
|
2
|
+
|
|
3
|
+
class ViURUploadAdapter {
|
|
4
|
+
constructor(loader, api_url) {
|
|
5
|
+
this.loader = loader
|
|
6
|
+
this.api_url = api_url
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async upload() {
|
|
10
|
+
this.loader.uploadTotal = 100
|
|
11
|
+
this.loader.uploaded = 0
|
|
12
|
+
return this.loader.file.then(
|
|
13
|
+
async (file) =>
|
|
14
|
+
new Promise((resolve, reject) => {
|
|
15
|
+
this.loader.uploaded = 10
|
|
16
|
+
uploadFile(file, { renderer: "vi" })
|
|
17
|
+
.then((uploadedData) => {
|
|
18
|
+
this.loader.uploaded = 75
|
|
19
|
+
this.loader.uploaded = 100
|
|
20
|
+
resolve({
|
|
21
|
+
// Die URL landet im HTML des Text-Bones: als /file/download/…
|
|
22
|
+
// (nur absolut gemacht) — die erkennt viur-core wieder
|
|
23
|
+
// (Blob-Referenz, srcset) und sie liegt nicht hinter /vi.
|
|
24
|
+
default: downloadUrlFor(uploadedData["downloadUrl"]),
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
.catch((error) => {
|
|
28
|
+
reject(error)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Aborts the upload process.
|
|
35
|
+
abort() {
|
|
36
|
+
if (this.xhr) {
|
|
37
|
+
this.xhr.abort()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function ViURUploadAdapterPlugin(editor) {
|
|
43
|
+
editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
|
|
44
|
+
return new ViURUploadAdapter(loader, editor.config.get("viur_api_url"))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
export default class Utils {
|
|
2
|
+
static objectEmpty(obj) {
|
|
3
|
+
return Object.keys(obj).length === 0 && obj.constructor === Object
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static getDescr(bone, value) {
|
|
7
|
+
try {
|
|
8
|
+
return bone["values"].filter((option) => option[0] === value)[0][1]
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return "-"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static stripHtml(html) {
|
|
15
|
+
const div = document.createElement("div")
|
|
16
|
+
div.innerHTML = html
|
|
17
|
+
return div.textContent || div.innerText || ""
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static unescape(value) {
|
|
21
|
+
if (!value) value = ""
|
|
22
|
+
return String(value)
|
|
23
|
+
.replace(/</g, "<")
|
|
24
|
+
.replace(/>/g, ">")
|
|
25
|
+
.replace(/"/g, '"')
|
|
26
|
+
.replace(/'/g, "'")
|
|
27
|
+
.replace(/(/g, "(")
|
|
28
|
+
.replace(/)/g, ")")
|
|
29
|
+
.replace(/=/g, "=")
|
|
30
|
+
.replace(/'/g, "'")
|
|
31
|
+
.replace(/(/g, "(")
|
|
32
|
+
.replace(/)/g, ")")
|
|
33
|
+
.replace(/=/g, "=")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static formatString(formatstr, boneValue, fallback = "-") {
|
|
37
|
+
function getpathListFromFormatstring(formatstr) {
|
|
38
|
+
let output = []
|
|
39
|
+
let formatList = []
|
|
40
|
+
let regstr = /\$\((.*?)\)/g
|
|
41
|
+
|
|
42
|
+
while (formatList) {
|
|
43
|
+
formatList = regstr.exec(formatstr)
|
|
44
|
+
if (!formatList) {
|
|
45
|
+
formatList = false
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
output.push(formatList[1])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return output
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readValue(pathstr, avalue) {
|
|
56
|
+
let path = pathstr.split(".")
|
|
57
|
+
let restPath = pathstr.split(".")
|
|
58
|
+
let aval = avalue
|
|
59
|
+
for (let entry of path) {
|
|
60
|
+
restPath.shift()
|
|
61
|
+
if (aval && aval !== "-" && Object.keys(aval).includes(entry)) {
|
|
62
|
+
if (Array.isArray(aval[entry])) {
|
|
63
|
+
let resVal = []
|
|
64
|
+
for (let val of aval[entry]) {
|
|
65
|
+
resVal.push(readValue(restPath.join("."), val))
|
|
66
|
+
}
|
|
67
|
+
aval = resVal.join(", ")
|
|
68
|
+
} else {
|
|
69
|
+
aval = readValue(restPath.join("."), aval[entry])
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof aval === "object" && !Array.isArray(aval) && aval !== null) {
|
|
73
|
+
aval = Object.entries(aval)
|
|
74
|
+
.map((x) => x[1])
|
|
75
|
+
.join(", ")
|
|
76
|
+
}
|
|
77
|
+
} else if (!aval || (typeof aval[entry] === "object" && !aval[entry])) {
|
|
78
|
+
aval = "-"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return aval
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let pathlist = getpathListFromFormatstring(formatstr)
|
|
85
|
+
|
|
86
|
+
let finalStrList = []
|
|
87
|
+
|
|
88
|
+
if (!Array.isArray(boneValue)) {
|
|
89
|
+
boneValue = [boneValue]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (let avalue of boneValue) {
|
|
93
|
+
let finalstr = formatstr
|
|
94
|
+
for (let pathstr of pathlist) {
|
|
95
|
+
let aval = readValue(pathstr, avalue)
|
|
96
|
+
// Auch Zahlen und Wahrheitswerte gehören in den Text: vorher wurde
|
|
97
|
+
// JEDER Nicht-String durch den Fallback ersetzt, `$(dest.kdnr)` mit 42
|
|
98
|
+
// verschwand also. Leeres, Objekte und der Platzhalter "-" bleiben der
|
|
99
|
+
// Fallback.
|
|
100
|
+
if (aval === null || aval === undefined || aval === "-" || typeof aval === "object") {
|
|
101
|
+
aval = fallback
|
|
102
|
+
} else {
|
|
103
|
+
aval = String(aval)
|
|
104
|
+
}
|
|
105
|
+
aval = Utils.unescape(aval)
|
|
106
|
+
finalstr = finalstr.replace("$(" + pathstr + ")", aval)
|
|
107
|
+
}
|
|
108
|
+
finalStrList.push(finalstr)
|
|
109
|
+
}
|
|
110
|
+
return finalStrList.join(", ")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── ViUR-Werte als Text ───────────────────────────────────────────────
|
|
114
|
+
//
|
|
115
|
+
// EINE Stelle, die jeden Bone-Wert in eine Zeichenkette übersetzt — für
|
|
116
|
+
// Tabellen, Detailansichten, Export. Denn ein Bone-Wert ist selten ein
|
|
117
|
+
// Skalar: mehrsprachige Bones sind ein Objekt je Sprache, `multiple` eine
|
|
118
|
+
// Liste, relationale Bones tragen `{dest, rel}` und ihren eigenen
|
|
119
|
+
// Formatstring, `text` enthält HTML.
|
|
120
|
+
|
|
121
|
+
/** Typfamilie eines Bones: "str.email" → "str". */
|
|
122
|
+
static boneFamily(type) {
|
|
123
|
+
return String(type ?? "").split(".")[0]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** select-Wert → Beschriftung. Akzeptiert Objekt- UND Paar-Schreibweise. */
|
|
127
|
+
static optionLabel(bone, value) {
|
|
128
|
+
const values = bone?.["values"]
|
|
129
|
+
if (Array.isArray(values)) {
|
|
130
|
+
const hit = values.find((entry) => String(entry?.[0]) === String(value))
|
|
131
|
+
return hit ? String(hit[1]) : String(value)
|
|
132
|
+
}
|
|
133
|
+
if (values && typeof values === "object") {
|
|
134
|
+
const hit = values[String(value)]
|
|
135
|
+
return hit === undefined ? String(value) : String(hit)
|
|
136
|
+
}
|
|
137
|
+
return String(value)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Ein EINZELNER Wert (ohne Sprachen/multiple — das macht boneToString). */
|
|
141
|
+
static singleValueToString(value, bone = {}, options = {}) {
|
|
142
|
+
if (value === null || value === undefined || value === "") return ""
|
|
143
|
+
switch (Utils.boneFamily(bone["type"])) {
|
|
144
|
+
case "bool": {
|
|
145
|
+
const [yes, no] = options.boolTexts ?? ["Ja", "Nein"]
|
|
146
|
+
return value ? yes : no
|
|
147
|
+
}
|
|
148
|
+
case "select":
|
|
149
|
+
return Utils.optionLabel(bone, value)
|
|
150
|
+
case "numeric": {
|
|
151
|
+
const numeric = Number(value)
|
|
152
|
+
if (!Number.isFinite(numeric)) return String(value)
|
|
153
|
+
const digits = typeof bone["precision"] === "number" ? bone["precision"] : undefined
|
|
154
|
+
return new Intl.NumberFormat(options.locale, {
|
|
155
|
+
minimumFractionDigits: digits,
|
|
156
|
+
maximumFractionDigits: digits,
|
|
157
|
+
}).format(numeric)
|
|
158
|
+
}
|
|
159
|
+
case "date": {
|
|
160
|
+
const parsed = new Date(String(value))
|
|
161
|
+
if (Number.isNaN(parsed.getTime())) return String(value)
|
|
162
|
+
// ViUR setzt die Flags `date`/`time` (beide standardmäßig an).
|
|
163
|
+
const wantsDate = bone["date"] !== false
|
|
164
|
+
const wantsTime = bone["time"] !== false
|
|
165
|
+
const format = {}
|
|
166
|
+
if (wantsDate || !wantsTime) Object.assign(format, { year: "numeric", month: "2-digit", day: "2-digit" })
|
|
167
|
+
if (wantsTime) Object.assign(format, { hour: "2-digit", minute: "2-digit" })
|
|
168
|
+
return new Intl.DateTimeFormat(options.locale, format).format(parsed)
|
|
169
|
+
}
|
|
170
|
+
case "relational":
|
|
171
|
+
case "record": {
|
|
172
|
+
// Relationale Bones bringen ihren Formatstring mit ("$(dest.name)").
|
|
173
|
+
const formatstr = bone["format"] ?? bone["params"]?.["format"] ?? "$(dest.name)"
|
|
174
|
+
const text = Utils.formatString(formatstr, value, "")
|
|
175
|
+
if (text && text !== "-") return Utils.stripHtml(String(text))
|
|
176
|
+
// Ohne Format ist der Schlüssel besser als nichts.
|
|
177
|
+
return String(value?.["dest"]?.["key"] ?? value?.["key"] ?? "")
|
|
178
|
+
}
|
|
179
|
+
case "text":
|
|
180
|
+
// Textbones enthalten HTML — in einer Zelle stört das.
|
|
181
|
+
return Utils.stripHtml(String(value))
|
|
182
|
+
case "raw":
|
|
183
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value)
|
|
184
|
+
default:
|
|
185
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Ein Wert OHNE Sprachdimension (multiple-Listen inklusive) als Text. */
|
|
190
|
+
static monolingualToString(value, bone = {}, options = {}) {
|
|
191
|
+
if (bone["multiple"]) {
|
|
192
|
+
const list = Array.isArray(value) ? value : value === null || value === undefined ? [] : [value]
|
|
193
|
+
return list.map((entry) => Utils.singleValueToString(entry, bone, options)).join(options.separator ?? ", ")
|
|
194
|
+
}
|
|
195
|
+
return Utils.singleValueToString(value, bone, options)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Flagge einer Sprache — pflegefrei über die CLDR-Daten des Browsers:
|
|
200
|
+
* Sprachcode → wahrscheinlichste Region (`Intl.Locale.maximize`, de→DE,
|
|
201
|
+
* en→US, ja→JP) → Regional-Indicator-Emoji. `null`, wenn keine Region
|
|
202
|
+
* bestimmbar ist.
|
|
203
|
+
*/
|
|
204
|
+
static languageFlag(code) {
|
|
205
|
+
try {
|
|
206
|
+
const region = new Intl.Locale(String(code)).maximize().region
|
|
207
|
+
if (!region || !/^[A-Z]{2}$/.test(region)) return null
|
|
208
|
+
return String.fromCodePoint(...[...region].map((char) => 0x1f1a5 + char.charCodeAt(0)))
|
|
209
|
+
} catch {
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Präfix, das einen Wert seiner Sprache zuordnet: Flagge, sonst Kürzel. */
|
|
215
|
+
static languageTag(code) {
|
|
216
|
+
return Utils.languageFlag(code) ?? `${code}:`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Bone-Wert eines Datensatzes als Text.
|
|
221
|
+
*
|
|
222
|
+
* Mehrsprachige Bones (`bone.languages`) steuert `options.language`:
|
|
223
|
+
* - `"*"` — ALLE befüllten Sprachen, je mit Flaggen-Präfix (`🇩🇪 Wert`),
|
|
224
|
+
* verkettet mit `languageSeparator` (Standard `", "`).
|
|
225
|
+
* `options.primaryLanguage` steht dabei vorn, die übrigen folgen in der
|
|
226
|
+
* Reihenfolge des Bones.
|
|
227
|
+
* - Sprachcode — der Wert dieser Sprache. Ist er leer, springt die Anzeige
|
|
228
|
+
* auf die erste befüllte Sprache und kennzeichnet das mit deren Flagge —
|
|
229
|
+
* eine leere Zelle verschweigt sonst, dass der Eintrag in anderer Sprache
|
|
230
|
+
* sehr wohl Inhalt hat.
|
|
231
|
+
* - nicht gesetzt — die erste Sprache des Bones, ohne Präfix (stabiles
|
|
232
|
+
* Verhalten für Formatstrings und andere Bestandsnutzer).
|
|
233
|
+
*
|
|
234
|
+
* @param {*} value Wert aus dem Skel
|
|
235
|
+
* @param {object} bone Bone-Beschreibung aus der `structure`
|
|
236
|
+
* @param {{ language?: string, primaryLanguage?: string, locale?: string, boolTexts?: [string, string], separator?: string, languageSeparator?: string }} [options]
|
|
237
|
+
*/
|
|
238
|
+
static boneToString(value, bone = {}, options = {}) {
|
|
239
|
+
const languages = bone["languages"]
|
|
240
|
+
if (Array.isArray(languages) && languages.length && value && typeof value === "object") {
|
|
241
|
+
const isEmpty = (entry) =>
|
|
242
|
+
entry === null || entry === undefined || entry === "" || (Array.isArray(entry) && entry.length === 0)
|
|
243
|
+
if (options.language === "*") {
|
|
244
|
+
const primary = options.primaryLanguage
|
|
245
|
+
const ordered =
|
|
246
|
+
primary && languages.includes(primary) ? [primary, ...languages.filter((lang) => lang !== primary)] : languages
|
|
247
|
+
return ordered
|
|
248
|
+
.filter((lang) => !isEmpty(value[lang]))
|
|
249
|
+
.map((lang) => `${Utils.languageTag(lang)} ${Utils.monolingualToString(value[lang], bone, options)}`)
|
|
250
|
+
.join(options.languageSeparator ?? ", ")
|
|
251
|
+
}
|
|
252
|
+
let lang = options.language && languages.includes(options.language) ? options.language : languages[0]
|
|
253
|
+
if (options.language && isEmpty(value[lang])) {
|
|
254
|
+
lang = languages.find((other) => !isEmpty(value[other])) ?? lang
|
|
255
|
+
}
|
|
256
|
+
const text = Utils.monolingualToString(value[lang], bone, options)
|
|
257
|
+
// Präfix nur, wenn eine BESTIMMTE Sprache gewünscht war und eine andere kam.
|
|
258
|
+
return options.language && options.language !== lang && !isEmpty(value[lang])
|
|
259
|
+
? `${Utils.languageTag(lang)} ${text}`
|
|
260
|
+
: text
|
|
261
|
+
}
|
|
262
|
+
return Utils.monolingualToString(value, bone, options)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function normalizeBonePathSegment(segment) {
|
|
267
|
+
return String(segment)
|
|
268
|
+
.trim()
|
|
269
|
+
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
|
270
|
+
.replace(/^-+|-+$/g, "")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function buildBoneDomId(pathSegments, suffix = null) {
|
|
274
|
+
const normalizedSegments = pathSegments
|
|
275
|
+
.filter((segment) => segment !== null && segment !== undefined && segment !== "")
|
|
276
|
+
.map(normalizeBonePathSegment)
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
|
|
279
|
+
if (suffix) {
|
|
280
|
+
normalizedSegments.push(normalizeBonePathSegment(suffix))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return ["vi-bone", ...normalizedSegments].join("--")
|
|
284
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
{{ getBoneValue(boneName, state.option) }}
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
import boneLogic from "./boneLogic"
|
|
7
|
+
import { computed, defineComponent, reactive } from "vue"
|
|
8
|
+
import { useI18n } from "vue-i18n"
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
boneName: String,
|
|
12
|
+
skel: Object,
|
|
13
|
+
structure: Object,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const state = reactive({
|
|
17
|
+
option: computed(() => {
|
|
18
|
+
if (props.structure[props.boneName]["type"] === "date") {
|
|
19
|
+
if (props.structure[props.boneName]["date"] && !props.structure[props.boneName]["time"]) {
|
|
20
|
+
return "date"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (props.structure[props.boneName]["time"] && !props.structure[props.boneName]["date"]) {
|
|
24
|
+
return "time"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null
|
|
28
|
+
}),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const { t } = useI18n()
|
|
32
|
+
const { getBoneValue, bones_state } = boneLogic(props.skel, props.structure, false, null, t)
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style scoped></style>
|