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.
Files changed (114) hide show
  1. package/README.md +79 -0
  2. package/index.d.ts +351 -0
  3. package/index.js +46 -0
  4. package/package.json +84 -0
  5. package/src/components/Browser/Browser.vue +496 -0
  6. package/src/components/Browser/TreeNav.vue +408 -0
  7. package/src/components/Browser/ViurBrowser.d.vue.ts +64 -0
  8. package/src/components/Browser/ViurBrowser.vue +271 -0
  9. package/src/components/Browser/dragSession.ts +134 -0
  10. package/src/components/Browser/index.ts +14 -0
  11. package/src/components/Browser/types.ts +30 -0
  12. package/src/components/DataTable/ColumnFilterPanel.vue +225 -0
  13. package/src/components/DataTable/DataTable.vue +3155 -0
  14. package/src/components/DataTable/ViurDataTable.d.vue.ts +72 -0
  15. package/src/components/DataTable/ViurDataTable.vue +331 -0
  16. package/src/components/DataTable/columns.ts +400 -0
  17. package/src/components/DataTable/index.ts +60 -0
  18. package/src/components/DataTable/rows.ts +446 -0
  19. package/src/components/DataTable/types.ts +267 -0
  20. package/src/components/DataTable/useColumnLayout.ts +294 -0
  21. package/src/components/DataTable/useGridNavigation.ts +193 -0
  22. package/src/components/DataTable/useVirtualRows.ts +126 -0
  23. package/src/components/DataTable/viurColumns.ts +330 -0
  24. package/src/forms/ViForm.d.vue.ts +106 -0
  25. package/src/forms/ViForm.vue +592 -0
  26. package/src/forms/ViFormCategory.vue +57 -0
  27. package/src/forms/bones/Bone.vue +1023 -0
  28. package/src/forms/bones/BoneActions.vue +127 -0
  29. package/src/forms/bones/BoneLabel.vue +122 -0
  30. package/src/forms/bones/BoneMultipleList.vue +91 -0
  31. package/src/forms/bones/BoneMultipleWrapper.vue +105 -0
  32. package/src/forms/bones/BoneNestedWrapper.vue +155 -0
  33. package/src/forms/bones/BoneSingleEntry.vue +51 -0
  34. package/src/forms/bones/FilterBone.vue +694 -0
  35. package/src/forms/bones/actionbar/defaultBar.vue +98 -0
  36. package/src/forms/bones/actionbar/fileBar.vue +165 -0
  37. package/src/forms/bones/actionbar/relationalBar.vue +125 -0
  38. package/src/forms/bones/actions/ActionDialog.vue +70 -0
  39. package/src/forms/bones/actions/Debug.vue +106 -0
  40. package/src/forms/bones/actions/Delete.vue +13 -0
  41. package/src/forms/bones/actions/DescribeImage.vue +201 -0
  42. package/src/forms/bones/actions/Download.vue +27 -0
  43. package/src/forms/bones/actions/Placeholder.vue +14 -0
  44. package/src/forms/bones/actions/Translation.vue +238 -0
  45. package/src/forms/bones/actions/index.js +280 -0
  46. package/src/forms/bones/bone.d.ts +45 -0
  47. package/src/forms/bones/country_information.json +779 -0
  48. package/src/forms/bones/index.js +225 -0
  49. package/src/forms/bones/uploadAdapter.js +46 -0
  50. package/src/forms/bones/utils.js +284 -0
  51. package/src/forms/bones/view/base_item.vue +35 -0
  52. package/src/forms/bones/view/boneLogic.js +202 -0
  53. package/src/forms/bones/view/index.js +5 -0
  54. package/src/forms/bones/widgets/booleanBone.vue +83 -0
  55. package/src/forms/bones/widgets/booleanBoneChoose.vue +57 -0
  56. package/src/forms/bones/widgets/booleanBoneSelect.vue +58 -0
  57. package/src/forms/bones/widgets/codeBone.vue +214 -0
  58. package/src/forms/bones/widgets/colorBone.vue +75 -0
  59. package/src/forms/bones/widgets/dateBone.vue +114 -0
  60. package/src/forms/bones/widgets/emailBone.vue +100 -0
  61. package/src/forms/bones/widgets/fileBone.vue +273 -0
  62. package/src/forms/bones/widgets/jsonBone.vue +63 -0
  63. package/src/forms/bones/widgets/keyBone.vue +71 -0
  64. package/src/forms/bones/widgets/numericBone.vue +125 -0
  65. package/src/forms/bones/widgets/passwordBone.vue +215 -0
  66. package/src/forms/bones/widgets/phoneBone.vue +173 -0
  67. package/src/forms/bones/widgets/rawBone.vue +94 -0
  68. package/src/forms/bones/widgets/recordBone.vue +64 -0
  69. package/src/forms/bones/widgets/relationalBone.vue +164 -0
  70. package/src/forms/bones/widgets/relationalBoneSelect.vue +120 -0
  71. package/src/forms/bones/widgets/selectBone.vue +122 -0
  72. package/src/forms/bones/widgets/selectBoneChoose.vue +104 -0
  73. package/src/forms/bones/widgets/spatialBone.vue +111 -0
  74. package/src/forms/bones/widgets/stringBone.vue +139 -0
  75. package/src/forms/bones/widgets/textBone.vue +641 -0
  76. package/src/forms/install.d.ts +4 -0
  77. package/src/forms/install.js +43 -0
  78. package/src/forms/layouts/LayloutList2Columns.vue +57 -0
  79. package/src/forms/layouts/LayoutCategory.vue +38 -0
  80. package/src/forms/layouts/LayoutList.vue +31 -0
  81. package/src/forms/utils.js +601 -0
  82. package/src/public/assets/icons/fontawesome-manifest.json +8 -0
  83. package/src/public/assets/icons/regular/file.svg +1 -0
  84. package/src/public/assets/icons/solid/arrows-left-right.svg +1 -0
  85. package/src/public/assets/icons/solid/bug.svg +1 -0
  86. package/src/public/assets/icons/solid/check.svg +1 -0
  87. package/src/public/assets/icons/solid/circle-info.svg +1 -0
  88. package/src/public/assets/icons/solid/compress.svg +1 -0
  89. package/src/public/assets/icons/solid/download.svg +1 -0
  90. package/src/public/assets/icons/solid/expand.svg +1 -0
  91. package/src/public/assets/icons/solid/grip-vertical.svg +1 -0
  92. package/src/public/assets/icons/solid/plus.svg +1 -0
  93. package/src/public/assets/icons/solid/question.svg +1 -0
  94. package/src/public/assets/icons/solid/triangle-exclamation.svg +1 -0
  95. package/src/public/assets/icons/solid/upload.svg +1 -0
  96. package/src/public/assets/icons/solid/xmark.svg +1 -0
  97. package/src/request/cache.js +183 -0
  98. package/src/request/configStore.js +516 -0
  99. package/src/request/envelope.js +211 -0
  100. package/src/request/index.js +35 -0
  101. package/src/request/messageStore.js +201 -0
  102. package/src/request/request.js +752 -0
  103. package/src/request/skel.js +90 -0
  104. package/src/theme/colorStore.js +168 -0
  105. package/src/translations/de.js +178 -0
  106. package/src/translations/en.js +177 -0
  107. package/src/user/LoginScreen.d.vue.ts +54 -0
  108. package/src/user/LoginScreen.vue +627 -0
  109. package/src/user/google.js +75 -0
  110. package/src/user/index.js +3 -0
  111. package/src/user/store.d.ts +59 -0
  112. package/src/user/store.js +183 -0
  113. package/src/webawesomeCompat.css +72 -0
  114. package/src/webawesomeConfig.js +61 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Convert structure to object format.
3
+ * ViUR can return structure as array [[name, bone], ...] or object {name: bone}.
4
+ * bone["using"] (relational/record sub-structure) may still use array format.
5
+ */
6
+ export function normalizeStructure(structure) {
7
+ if (!structure) return {}
8
+ if (Array.isArray(structure)) {
9
+ return Object.fromEntries(structure.map(([name, bone]) => [name, bone]))
10
+ }
11
+ return structure
12
+ }
13
+
14
+ /**
15
+ * Convert skel values + structure to a flat array of {fieldName: value} objects
16
+ * suitable for building a FormData or JSON payload for ViUR edit/add endpoints.
17
+ *
18
+ * Handles: multilingual bones, multiple bones, record bones, relational bones,
19
+ * spatial bones, raw.json bones.
20
+ *
21
+ * @param {Record<string, any>} skel - skel values (e.g. from view response)
22
+ * @param {Record<string, any>} structure - normalized structure object
23
+ * @returns {{ [field: string]: any }[]}
24
+ */
25
+ export function skelDataToFormData(skel, structure) {
26
+ function handleEntry(result, fieldName, bone, val) {
27
+ if (bone["type"].startsWith("record")) {
28
+ const struct = normalizeStructure(bone["using"])
29
+ for (const [subField, subBone] of Object.entries(struct)) {
30
+ result = result.concat(boneToForm(`${fieldName}.${subField}`, subBone, val?.[subField]))
31
+ }
32
+ } else if (val === Object(val) && bone["using"]) {
33
+ // relational bone with sub-structure
34
+ if (val["dest"]?.["key"]) {
35
+ const struct = normalizeStructure(bone["using"])
36
+ for (const [subField, subBone] of Object.entries(struct)) {
37
+ result = result.concat(boneToForm(`${fieldName}.${subField}`, subBone, val["rel"]?.[subField]))
38
+ }
39
+ result.push({ [`${fieldName}.key`]: val["dest"]["key"] })
40
+ } else {
41
+ result.push({ [fieldName]: null })
42
+ }
43
+ } else if (bone["type"].startsWith("spatial") && val) {
44
+ result.push({ [`${fieldName}.lat`]: val[0] })
45
+ result.push({ [`${fieldName}.lng`]: val[1] })
46
+ } else if (bone["type"].startsWith("raw.json") && val) {
47
+ result.push({ [fieldName]: JSON.stringify(val) })
48
+ } else if (val === Object(val)) {
49
+ // plain relational bone (no using)
50
+ result.push({ [fieldName]: val["dest"]?.["key"] ?? null })
51
+ } else {
52
+ result.push({ [fieldName]: val })
53
+ }
54
+ return result
55
+ }
56
+
57
+ function boneToForm(fieldName, bone, value) {
58
+ let result = []
59
+ const indexBone = bone["type"].startsWith("record")
60
+ const languages = bone["languages"] || ["none"]
61
+ let languageValue = value
62
+
63
+ for (const lang of languages) {
64
+ let currentFieldName = fieldName
65
+ if (lang !== "none") {
66
+ currentFieldName += `.${lang}`
67
+ value = languageValue?.[lang]
68
+ }
69
+
70
+ if (bone["multiple"]) {
71
+ const values = value ?? []
72
+ for (const [idx, val] of values.entries()) {
73
+ const indexedName =
74
+ indexBone || val?.["rel"] || (bone["using"] && val?.["rel"] !== null)
75
+ ? `${currentFieldName}.${idx}`
76
+ : currentFieldName
77
+ result = handleEntry(result, indexedName, bone, val)
78
+ }
79
+ if (values.length === 0) {
80
+ result.push({ [currentFieldName]: null })
81
+ }
82
+ } else {
83
+ result = handleEntry(result, currentFieldName, bone, value)
84
+ }
85
+ }
86
+ return result
87
+ }
88
+
89
+ return Object.entries(structure).flatMap(([fieldName, bone]) => boneToForm(fieldName, bone, skel[fieldName]))
90
+ }
@@ -0,0 +1,168 @@
1
+ import { defineStore } from "pinia"
2
+ import { reactive, computed, watch } from "vue"
3
+ import { useConfigStore } from "../request/configStore.js"
4
+
5
+ // --------------------------------------------------------------------------- //
6
+ // Color store — brand color from the configuration //
7
+ // --------------------------------------------------------------------------- //
8
+ //
9
+ // Same principle as the colorStore of vi-vue-components (old admin): the
10
+ // configured colors (`admin.color.primary` / `admin.color.secondary`, part of
11
+ // the public /vi/config) are converted to HSL; hue and saturation stay, the
12
+ // lightness is fixed per step. The old admin fed Shoelace's
13
+ // `--sl-color-primary-50…950` that way. Here the same table feeds the eleven
14
+ // steps of the WebAwesome brand palette (`--wa-color-brand-95…05`), in the same
15
+ // order; the theme maps them onto its semantic tokens (fill / border / on, light
16
+ // and dark) as it does for any built-in brand color.
17
+
18
+ /** Lightness (HSL, %) per brand step: the old admin's table (50 → 83 %, …,
19
+ * 950 → 3 %), step by step on WebAwesome's steps (95 … 05). */
20
+ export const BRAND_LIGHTNESS = [
21
+ ["95", 95],
22
+ ["90", 78],
23
+ ["80", 68],
24
+ ["70", 58],
25
+ ["60", 48],
26
+ ["50", 43],
27
+ ["40", 38],
28
+ ["30", 28],
29
+ ["20", 18],
30
+ ["10", 8],
31
+ ["05", 3],
32
+ ]
33
+ const STEP_LIGHTNESS = Object.fromEntries(BRAND_LIGHTNESS)
34
+
35
+ /**
36
+ * "#rrggbb" / "#rgb" → [h, s, l] (degrees, %, %) — the old admin's conversion,
37
+ * rounding included. Anything else → null.
38
+ */
39
+ export function hexToHsl(color) {
40
+ let hex = typeof color === "string" ? color.trim().toLowerCase() : ""
41
+ if (/^#[0-9a-f]{3}$/.test(hex)) hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`
42
+ if (!/^#[0-9a-f]{6}$/.test(hex)) return null
43
+
44
+ const r = parseInt(hex.slice(1, 3), 16) / 255
45
+ const g = parseInt(hex.slice(3, 5), 16) / 255
46
+ const b = parseInt(hex.slice(5, 7), 16) / 255
47
+ const cmin = Math.min(r, g, b)
48
+ const cmax = Math.max(r, g, b)
49
+ const delta = cmax - cmin
50
+
51
+ let h = 0
52
+ if (delta === 0) h = 0
53
+ else if (cmax === r) h = ((g - b) / delta) % 6
54
+ else if (cmax === g) h = (b - r) / delta + 2
55
+ else h = (r - g) / delta + 4
56
+ h = Math.round(h * 60)
57
+ if (h < 0) h += 360
58
+
59
+ const l = (cmax + cmin) / 2
60
+ const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1))
61
+ return [h, +(s * 100).toFixed(1), +(l * 100).toFixed(1)]
62
+ }
63
+
64
+ /** CSS color from [h, s, l]; `lightness` replaces l (0 keeps it, as before). */
65
+ function hslString([h, s, l], lightness = 0, alpha = null) {
66
+ const light = lightness || l
67
+ return alpha === null ? `hsl(${h},${s}%,${light}%)` : `hsla(${h},${s}%,${light}%,${alpha}%)`
68
+ }
69
+
70
+ /** Relative luminance (WCAG) of [h, s, l]. */
71
+ function luminance([h, s, l]) {
72
+ const a = (s / 100) * Math.min(l / 100, 1 - l / 100)
73
+ const channel = (n) => {
74
+ const k = (n + h / 30) % 12
75
+ const c = l / 100 - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))
76
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
77
+ }
78
+ return 0.2126 * channel(0) + 0.7152 * channel(8) + 0.0722 * channel(4)
79
+ }
80
+
81
+ /** Text on the brand's key step (50): white, or its darkest step (10) when
82
+ * that contrasts better — light hues such as lime or yellow. */
83
+ function brandOnColor(h, s) {
84
+ const key = luminance([h, s, STEP_LIGHTNESS["50"]])
85
+ const dark = luminance([h, s, STEP_LIGHTNESS["10"]])
86
+ return 1.05 / (key + 0.05) >= (key + 0.05) / (dark + 0.05) ? "white" : "var(--wa-color-brand-10)"
87
+ }
88
+
89
+ export const useColorStore = defineStore("viurColor", () => {
90
+ const configStore = useConfigStore()
91
+
92
+ const state = reactive({
93
+ // set explicitly to override the configuration; null → `admin.color.*`
94
+ primaryColor: null,
95
+ secondaryColor: null,
96
+ })
97
+
98
+ const primaryHsl = computed(() => hexToHsl(state.primaryColor ?? configStore.get("color.primary")))
99
+ const secondaryHsl = computed(() => hexToHsl(state.secondaryColor ?? configStore.get("color.secondary")))
100
+
101
+ /** WebAwesome brand tokens (custom property → value); empty without a
102
+ * valid primary color, so WebAwesome's default brand stays. */
103
+ const brandColors = computed(() => {
104
+ const color = primaryHsl.value
105
+ if (!color) return {}
106
+ const colors = {}
107
+ for (const [step, lightness] of BRAND_LIGHTNESS) {
108
+ colors[`--wa-color-brand-${step}`] = hslString(color, lightness)
109
+ }
110
+ colors["--wa-color-brand"] = "var(--wa-color-brand-50)"
111
+ colors["--wa-color-brand-on"] = brandOnColor(color[0], color[1])
112
+ return colors
113
+ })
114
+
115
+ // ── the old admin's helpers (null without a valid color) ────────────────────
116
+ function getPrimaryColor(lightness = 0) {
117
+ return primaryHsl.value ? hslString(primaryHsl.value, lightness) : null
118
+ }
119
+
120
+ function getSecondaryColor(lightness = 0) {
121
+ return secondaryHsl.value ? hslString(secondaryHsl.value, lightness) : null
122
+ }
123
+
124
+ /** `color` is "primaryColor" or "secondaryColor", `alpha` in %. */
125
+ function getAlphaColor(color, lightness, alpha) {
126
+ const hsl = color === "secondaryColor" ? secondaryHsl.value : primaryHsl.value
127
+ return hsl ? hslString(hsl, lightness, alpha) : null
128
+ }
129
+
130
+ /**
131
+ * Write the brand palette into `doc` and keep it in sync with the
132
+ * configuration. It goes into a `<style>` in the head as an unlayered
133
+ * `:root` rule: that wins over WebAwesome's layered palette / brand rules,
134
+ * reaches every shadow root, and windows that mirror the head's styles (the
135
+ * admin's detached panes) pick it up too. Returns a function that stops the
136
+ * sync.
137
+ */
138
+ function applyBrandColor(doc = globalThis.document) {
139
+ if (!doc?.head) return () => {}
140
+ return watch(
141
+ brandColors,
142
+ (colors) => {
143
+ let style = doc.head.querySelector("style[data-viur-brand-color]")
144
+ const entries = Object.entries(colors)
145
+ if (!entries.length) {
146
+ style?.remove()
147
+ return
148
+ }
149
+ if (!style) {
150
+ style = doc.createElement("style")
151
+ style.dataset.viurBrandColor = ""
152
+ doc.head.append(style)
153
+ }
154
+ style.textContent = `:root {\n${entries.map(([name, value]) => ` ${name}: ${value};`).join("\n")}\n}`
155
+ },
156
+ { immediate: true }
157
+ )
158
+ }
159
+
160
+ return {
161
+ state,
162
+ brandColors,
163
+ getPrimaryColor,
164
+ getSecondaryColor,
165
+ getAlphaColor,
166
+ applyBrandColor,
167
+ }
168
+ })
@@ -0,0 +1,178 @@
1
+ export default {
2
+ bone: {
3
+ add: "Hinzufügen",
4
+ del: "Entfernen",
5
+ delAll: "Alle Entfernen",
6
+ upload: "Hochladen",
7
+ download: "Herunterladen",
8
+ chooseFile: "Datei auswählen",
9
+ dragFile: "Dateien hier hinziehen",
10
+ dropzone: "Datei auswählen oder hierher ziehen",
11
+ editFile: "Datei bearbeiten",
12
+ list: "Auflisten",
13
+ addEmpty: "Neuer Eintrag",
14
+ no_entries: "Keine Einträge",
15
+ },
16
+ lang: "Sprache",
17
+ bones: {
18
+ numeric: {
19
+ min: "Minimum: {val}",
20
+ max: "Maximum: {val}",
21
+ precision: "Genauigkeit: {val} Stellen",
22
+ },
23
+ bool: {
24
+ true: "Ja",
25
+ false: "Nein",
26
+ },
27
+ phone: {
28
+ placeholder: "Telefon/Fax",
29
+ },
30
+ },
31
+
32
+ errors: {
33
+ "Field not set": "Feld nicht ausgefüllt",
34
+ "No value entered": "Feld nicht ausgefüllt",
35
+ "invalid value entered": "Wert ist ungültig",
36
+ "Field not submitted": "Feld wurde nicht übertragen",
37
+ "Too few items": "Zuwenige Elemente",
38
+ "Too many items": "Zuviele Elemente",
39
+ "Duplicate items": "Doppelte Elemente",
40
+ "No Captche given!": "Captcha Code fehlt!",
41
+ "Incomplete data": "Daten unvollständig",
42
+ "No value selected": "Kein Wert ausgewählt",
43
+ "Invalid value selected": "Auswahl ist ungültig",
44
+ "Wrong OTP Token": "Falscher OTP Code",
45
+ "Maximum length exceeded": "Eingabe zu lang",
46
+ "Invalid email entered": "Ungültige E-Mail Adresse",
47
+ },
48
+ preview: "Vorschau",
49
+ de: "DE",
50
+ en: "EN",
51
+ fr: "FR",
52
+ nl: "NL",
53
+ ch: "CH",
54
+ es: "ES",
55
+ "de-x-simple": "Einfache Sprache",
56
+ dataTable: {
57
+ structureNotice: "Die Struktur des Moduls kam nicht an — die Spalten sind aus den Daten abgeleitet.",
58
+ search: "Suchen …",
59
+ columns: "Spalten",
60
+ resetColumns: "Spalten zurücksetzen",
61
+ filterFor: "Filter: {column}",
62
+ columnOptions: "Spaltenoptionen",
63
+ sortAsc: "Aufsteigend sortieren",
64
+ sortDesc: "Absteigend sortieren",
65
+ pinLeft: "Links anpinnen",
66
+ pinRight: "Rechts anpinnen",
67
+ unpin: "Loslösen",
68
+ autoSize: "Breite anpassen",
69
+ hideColumn: "Ausblenden",
70
+ pinnedLeft: "Links angepinnt",
71
+ pinnedRight: "Rechts angepinnt",
72
+ selectAllPage: "Alle Zeilen dieser Seite auswählen",
73
+ selectRow: "Zeile {row} auswählen",
74
+ dragToReorder: "Zum Umsortieren ziehen",
75
+ reorderDisabled: "Umsortieren nur in der festen Reihenfolge (Sortierung aufheben)",
76
+ noChildren: "Keine Unterebene",
77
+ collapseRow: "Zeile einklappen",
78
+ expandRow: "Zeile ausklappen",
79
+ noData: "Keine Daten",
80
+ noResults: "Keine Treffer",
81
+ filterFrom: "von",
82
+ filterTo: "bis",
83
+ searchValues: "Werte suchen …",
84
+ perPage: "{count} / Seite",
85
+ perPageAria: "Einträge pro Seite",
86
+ rowsPerPageAria: "Zeilen pro Seite",
87
+ loadMore: "Mehr laden",
88
+ loadAll: "Alles laden",
89
+ loadPages: "Die nächsten {pages} Seiten laden",
90
+ loaded: "{loaded} Einträge",
91
+ loadedOne: "1 Eintrag",
92
+ loadedOf: "{loaded} von {total} Einträgen",
93
+ filteredOf: "{filtered} Treffer von {loaded} Einträgen",
94
+ pageInfo: "{rows} Zeilen, Seite {page} von {pages}",
95
+ firstPage: "Erste Seite",
96
+ prevPage: "Vorige Seite",
97
+ nextPage: "Nächste Seite",
98
+ lastPage: "Letzte Seite",
99
+ rowsSelected: "{count} Zeilen ausgewählt",
100
+ selectionCleared: "Auswahl aufgehoben",
101
+ rowsCopied: "{count} Zeilen kopiert",
102
+ rowMoved: "Zeile auf Position {position} verschoben",
103
+ rowMovedLevel: "Zeile in eine andere Ebene verschoben, Position {position}",
104
+ rowsMoved: "{count} Zeilen verschoben, ab Position {position}",
105
+ rowsMovedLevel: "{count} Zeilen verschoben, ab Position {position}, teils in eine andere Ebene",
106
+ },
107
+ browser: {
108
+ path: "Pfad",
109
+ navigation: "Navigation",
110
+ uploadHint: "Dateien hier ablegen",
111
+ },
112
+ form: {
113
+ next: "Weiter",
114
+ submit: "Absenden",
115
+ back: "Zurück",
116
+ },
117
+ actions: {
118
+ close: "Schließen",
119
+ aiHint:
120
+ "* Hierbei kommt künstliche Intelligenz zum Einsatz. Die Ergebnisse können daher fehlerhaft sein. Bitte prüfen Sie stets das Ergebnis.",
121
+ translate: {
122
+ original: "Original Sprache ({lang}):",
123
+ target: "Zielsprache ({lang}):",
124
+ run: "Übersetzen",
125
+ applyNext: "Übernehmen und nächste leere Sprache",
126
+ apply: "Ziel für {lang} übernehmen",
127
+ },
128
+ describeImage: {
129
+ label: "Beschreibung ({lang}):",
130
+ instructions: "weitere Anweisungen",
131
+ run: "Beschreibung erstellen",
132
+ apply: "Beschreibung übernehmen",
133
+ },
134
+ },
135
+ messages: {
136
+ request: {
137
+ default: {
138
+ success: "Aktion erfolgreich ausgeführt.",
139
+ rejected: "Die Eingaben wurden nicht akzeptiert.",
140
+ error: "Die Anfrage ist fehlgeschlagen ({status}).",
141
+ info: "",
142
+ warning: "",
143
+ },
144
+ add: { success: "Eintrag wurde angelegt.", rejected: "Eintrag konnte nicht angelegt werden." },
145
+ edit: { success: "Änderungen wurden gespeichert.", rejected: "Änderungen konnten nicht gespeichert werden." },
146
+ delete: { success: "Eintrag wurde gelöscht.", rejected: "Eintrag konnte nicht gelöscht werden." },
147
+ clone: { success: "Eintrag wurde dupliziert.", rejected: "Eintrag konnte nicht dupliziert werden." },
148
+ move: { success: "Eintrag wurde verschoben.", rejected: "Eintrag konnte nicht verschoben werden." },
149
+ login: { success: "Anmeldung erfolgreich.", rejected: "Anmeldung fehlgeschlagen." },
150
+ logout: { success: "Sie wurden abgemeldet." },
151
+ },
152
+ },
153
+ login: {
154
+ login: "Anmelden",
155
+ logout: "Abmelden",
156
+ otp: "Bestätigen",
157
+ otp_label: "Einmal-Code",
158
+ choose_method: "Anmeldemethode wählen",
159
+ choose_second_factor: "Zweiten Faktor wählen",
160
+ second_factor: "Zwei-Faktor-Bestätigung",
161
+ lost_password: "Passwort vergessen?",
162
+ with_google: "Mit Google anmelden",
163
+ waiting: "lade...",
164
+ or: "oder",
165
+ login_error: "Benutzername oder Passwort falsch.",
166
+ google_error: "Ein Fehler ist beim Login aufgetreten.",
167
+ otp_error: "Die Eingabe war ungültig.",
168
+ back: "zurück",
169
+ recovery_start: "Passwort zurücksetzen",
170
+ recovery_error: "Ein Fehler beim zurücksetzen des Passwortes ist aufgetreten.",
171
+ password: "Passwort",
172
+ email: "E-Mail Adresse",
173
+ },
174
+ "viur.core.login.error": "Benutzername oder Passwort falsch.",
175
+ "viur.core.login.waiting": "lade...",
176
+ "viur.core.login.back": "zurück",
177
+ "viur.core.modules.user.btn.send": "Absenden",
178
+ }
@@ -0,0 +1,177 @@
1
+ export default {
2
+ bone: {
3
+ add: "Add",
4
+ del: "Remove",
5
+ delAll: "Remove all",
6
+ upload: "Upload",
7
+ download: "Download",
8
+ chooseFile: "Choose file",
9
+ dragFile: "Drag files here",
10
+ dropzone: "Select a file or drag it here",
11
+ editFile: "Edit file",
12
+ list: "List",
13
+ addEmpty: "New entry",
14
+ no_entries: "No entries",
15
+ },
16
+ lang: "Language",
17
+ bones: {
18
+ numeric: {
19
+ min: "Minimum: {val}",
20
+ max: "Maximum: {val}",
21
+ precision: "Precision: {val} digits",
22
+ },
23
+ bool: {
24
+ true: "Yes",
25
+ false: "No",
26
+ },
27
+ phone: {
28
+ placeholder: "Phone/Fax",
29
+ },
30
+ },
31
+
32
+ errors: {
33
+ "Field not set": "Field not set",
34
+ "No value entered": "No value entered",
35
+ "invalid value entered": "Invalid value entered",
36
+ "Field not submitted": "Field not submitted",
37
+ "Too few items": "Too few items",
38
+ "Too many items": "Too many items",
39
+ "Duplicate items": "Duplicate items",
40
+ "No Captche given!": "Captcha code missing!",
41
+ "Incomplete data": "Incomplete data",
42
+ "No value selected": "No value selected",
43
+ "Invalid value selected": "Invalid value selected",
44
+ "Wrong OTP Token": "Wrong OTP token",
45
+ "Maximum length exceeded": "Maximum length exceeded",
46
+ "Invalid email entered": "Invalid email address",
47
+ },
48
+ preview: "Preview",
49
+ de: "DE",
50
+ en: "EN",
51
+ fr: "FR",
52
+ nl: "NL",
53
+ ch: "CH",
54
+ es: "ES",
55
+ "de-x-simple": "Simple language",
56
+ dataTable: {
57
+ structureNotice: "The module's structure did not arrive — the columns are derived from the data.",
58
+ search: "Search …",
59
+ columns: "Columns",
60
+ resetColumns: "Reset columns",
61
+ filterFor: "Filter: {column}",
62
+ columnOptions: "Column options",
63
+ sortAsc: "Sort ascending",
64
+ sortDesc: "Sort descending",
65
+ pinLeft: "Pin left",
66
+ pinRight: "Pin right",
67
+ unpin: "Unpin",
68
+ autoSize: "Auto-fit width",
69
+ hideColumn: "Hide",
70
+ pinnedLeft: "Pinned left",
71
+ pinnedRight: "Pinned right",
72
+ selectAllPage: "Select all rows on this page",
73
+ selectRow: "Select row {row}",
74
+ dragToReorder: "Drag to reorder",
75
+ reorderDisabled: "Reordering requires the fixed order (clear sorting)",
76
+ noChildren: "No sub-level",
77
+ collapseRow: "Collapse row",
78
+ expandRow: "Expand row",
79
+ noData: "No data",
80
+ noResults: "No matches",
81
+ filterFrom: "from",
82
+ filterTo: "to",
83
+ searchValues: "Search values …",
84
+ perPage: "{count} / page",
85
+ perPageAria: "Entries per page",
86
+ rowsPerPageAria: "Rows per page",
87
+ loadMore: "Load more",
88
+ loadAll: "Load all",
89
+ loadPages: "Load the next {pages} pages",
90
+ loaded: "{loaded} entries",
91
+ loadedOne: "1 entry",
92
+ loadedOf: "{loaded} of {total} entries",
93
+ filteredOf: "{filtered} matches of {loaded} entries",
94
+ pageInfo: "{rows} rows, page {page} of {pages}",
95
+ firstPage: "First page",
96
+ prevPage: "Previous page",
97
+ nextPage: "Next page",
98
+ lastPage: "Last page",
99
+ rowsSelected: "{count} rows selected",
100
+ selectionCleared: "Selection cleared",
101
+ rowsCopied: "{count} rows copied",
102
+ rowMoved: "Row moved to position {position}",
103
+ rowMovedLevel: "Row moved to another level, position {position}",
104
+ rowsMoved: "{count} rows moved, starting at position {position}",
105
+ rowsMovedLevel: "{count} rows moved, starting at position {position}, partly to another level",
106
+ },
107
+ browser: {
108
+ path: "Path",
109
+ navigation: "Navigation",
110
+ uploadHint: "Drop files here",
111
+ },
112
+ form: {
113
+ next: "Next",
114
+ submit: "Submit",
115
+ back: "Back",
116
+ },
117
+ actions: {
118
+ close: "Close",
119
+ aiHint: "* Artificial intelligence is used here. Results may contain errors — please always review them.",
120
+ translate: {
121
+ original: "Source language ({lang}):",
122
+ target: "Target language ({lang}):",
123
+ run: "Translate",
124
+ applyNext: "Apply and go to next empty language",
125
+ apply: "Apply target for {lang}",
126
+ },
127
+ describeImage: {
128
+ label: "Description ({lang}):",
129
+ instructions: "additional instructions",
130
+ run: "Generate description",
131
+ apply: "Apply description",
132
+ },
133
+ },
134
+ messages: {
135
+ request: {
136
+ default: {
137
+ success: "Action completed.",
138
+ rejected: "The input was not accepted.",
139
+ error: "The request failed ({status}).",
140
+ info: "",
141
+ warning: "",
142
+ },
143
+ add: { success: "Entry created.", rejected: "Entry could not be created." },
144
+ edit: { success: "Changes saved.", rejected: "Changes could not be saved." },
145
+ delete: { success: "Entry deleted.", rejected: "Entry could not be deleted." },
146
+ clone: { success: "Entry duplicated.", rejected: "Entry could not be duplicated." },
147
+ move: { success: "Entry moved.", rejected: "Entry could not be moved." },
148
+ login: { success: "Signed in.", rejected: "Sign-in failed." },
149
+ logout: { success: "You have been signed out." },
150
+ },
151
+ },
152
+ login: {
153
+ login: "Sign in",
154
+ logout: "Sign out",
155
+ otp: "Verify",
156
+ otp_label: "One-time code",
157
+ choose_method: "Choose a sign-in method",
158
+ choose_second_factor: "Choose a second factor",
159
+ second_factor: "Two-factor verification",
160
+ lost_password: "Forgot your password?",
161
+ with_google: "Sign in with Google",
162
+ waiting: "loading...",
163
+ or: "or",
164
+ login_error: "Incorrect username or password.",
165
+ google_error: "An error occurred during login.",
166
+ otp_error: "The code was invalid.",
167
+ back: "Back",
168
+ recovery_start: "Reset password",
169
+ recovery_error: "An error occurred while resetting the password.",
170
+ password: "Password",
171
+ email: "E-Mail",
172
+ },
173
+ "viur.core.login.error": "Incorrect username or password.",
174
+ "viur.core.login.waiting": "loading...",
175
+ "viur.core.login.back": "Back",
176
+ "viur.core.modules.user.btn.send": "Send",
177
+ }
@@ -0,0 +1,54 @@
1
+ import type { DefineComponent } from "vue"
2
+
3
+ /** Props von LoginScreen.vue (siehe defineProps dort). */
4
+ export interface LoginScreenProps {
5
+ /** Überschrift der Karte; per `header`-Slot ersetzbar. */
6
+ title?: string
7
+ /**
8
+ * Endpunkt unterhalb des `user`-Moduls, der den Ablauf startet. Envelope v2
9
+ * fährt die ganze Kette ab "login". viur-core 3.9 (klassisches v1) antwortet
10
+ * auf "login" mit der Provider-Auswahl; die Kette führt der LoginScreen dann
11
+ * selbst wie vi-vue-utils (Passwort, Passwort vergessen, zweiter Faktor).
12
+ */
13
+ action?: string
14
+ /** Renderer für die Requests; Standard: VITE_DEFAULT_RENDERER oder "json". */
15
+ renderer?: string | null
16
+ /** Protokollversion festnageln; null → unversioniert (Server wählt v2). */
17
+ version?: string | number | null
18
+ /**
19
+ * OAuth-Client-ID für „Sign in with Google". Ohne Angabe wird die vom Server
20
+ * per `/vi/config` ("admin.user.google.clientID") gelieferte ID genutzt;
21
+ * `false` blendet den Knopf immer aus.
22
+ */
23
+ googleClientId?: string | false | null
24
+ /**
25
+ * Hintergrundbild der App, falls die Konfiguration keines setzt
26
+ * ("admin.login.background" leer). Wie dort: absolute URLs bleiben, "/…" ist
27
+ * ein Pfad auf dem Backend, alles andere ein Asset der App (unter BASE_URL).
28
+ */
29
+ backgroundImage?: string | null
30
+ /** Logo über dem Login, falls "admin.login.logo" leer ist (Auflösung wie oben). */
31
+ logo?: string | null
32
+ /** Fortschrittsleiste des Multistep-Formulars zeigen. */
33
+ showSteps?: boolean
34
+ /** Beim Mounten eine bestehende Session prüfen und den Login überspringen. */
35
+ autoStart?: boolean
36
+ /**
37
+ * Ziel nach erfolgreicher Anmeldung (auch wenn `autoStart` eine bestehende
38
+ * Session findet). Läuft über vue-router, falls die Anwendung einen hat,
39
+ * sonst über `location`. Ohne Angabe bleibt die Ansicht stehen und der
40
+ * Aufrufer entscheidet in `@success`.
41
+ */
42
+ redirect?: string
43
+ }
44
+
45
+ /** Ereignisse als `on*`-Props, damit `@success` / `@error` im Template typen. */
46
+ export interface LoginScreenEmitProps {
47
+ /** Angemeldet — mit den Werten des UserSkel. */
48
+ onSuccess?: (user: Record<string, any> | null) => void
49
+ /** Login fehlgeschlagen (Formularfehler, Transport, "google"). */
50
+ onError?: (error: unknown) => void
51
+ }
52
+
53
+ declare const LoginScreen: DefineComponent<LoginScreenProps & LoginScreenEmitProps, {}, any>
54
+ export default LoginScreen