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,72 @@
|
|
|
1
|
+
import type { DefineComponent, Ref } from "vue"
|
|
2
|
+
import type { Row, RowKey } from "./types"
|
|
3
|
+
|
|
4
|
+
/** Props von ViurDataTable.vue (siehe defineProps dort). */
|
|
5
|
+
export interface ViurDataTableProps {
|
|
6
|
+
/** Modulname bzw. Punkt-Pfad ("user", "shop.order"). */
|
|
7
|
+
module: string
|
|
8
|
+
/** Renderer der Requests; Standard: VITE_DEFAULT_RENDERER. */
|
|
9
|
+
renderer?: string | null
|
|
10
|
+
/** Untertyp gruppierter Listen (Handler `list.grouped`). */
|
|
11
|
+
group?: string | null
|
|
12
|
+
/** Zusätzliche Request-Parameter — bei jedem Abruf gespreadet. */
|
|
13
|
+
params?: Record<string, unknown>
|
|
14
|
+
/** Spalten; ohne Angabe aus der viewSkel-Struktur abgeleitet. */
|
|
15
|
+
columns?: unknown[] | null
|
|
16
|
+
/** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
|
|
17
|
+
bones?: string[] | null
|
|
18
|
+
label?: string | null
|
|
19
|
+
selectable?: false | "single" | "multiple"
|
|
20
|
+
/** Einträge je Nachladen (ViUR `limit`). */
|
|
21
|
+
pageSize?: number
|
|
22
|
+
/** Obergrenze des Lokal-Modus (alles geladen → Client-Pipeline). */
|
|
23
|
+
localModeLimit?: number
|
|
24
|
+
/** X-VIUR-BONELIST senden, wenn Felder abgewählt sind (Server braucht ein "*"-Subskel). */
|
|
25
|
+
bonelist?: boolean
|
|
26
|
+
/** localStorage-Key der Feld-Sichtbarkeit; null = keine Persistenz. */
|
|
27
|
+
storageKey?: string | null
|
|
28
|
+
language?: string | null
|
|
29
|
+
/** Steht bei language="*" vorn (üblicherweise die UI-Sprache). */
|
|
30
|
+
primaryLanguage?: string | null
|
|
31
|
+
locale?: string | null
|
|
32
|
+
/** Spaltenbreite ziehbar (Default true — anders als die nackte DataTable). */
|
|
33
|
+
resizable?: boolean
|
|
34
|
+
/** Spalten per Drag umsortierbar (Default true). */
|
|
35
|
+
reorderable?: boolean
|
|
36
|
+
/** Spalten anpinnbar (Default true). */
|
|
37
|
+
pinnable?: boolean
|
|
38
|
+
/** Wird an die DataTable durchgereicht. */
|
|
39
|
+
tableProps?: Record<string, unknown>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ViurDataTableEmitProps<R extends Row = Row> {
|
|
43
|
+
/** Doppelklick auf eine Zeile. */
|
|
44
|
+
onOpen?: (payload: { row: R; key: RowKey }) => void
|
|
45
|
+
/** v-model:selectedKeys */
|
|
46
|
+
"onUpdate:selectedKeys"?: (value: RowKey[]) => void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Was ViurDataTable per defineExpose freigibt. */
|
|
50
|
+
export interface ViurDataTableApi<R extends Row = Row> {
|
|
51
|
+
/** Der ListRequest (useListFetch): data, structure, cursor, execute, … */
|
|
52
|
+
list: Record<string, unknown>
|
|
53
|
+
/** Template-Ref auf die DataTable (DataTableApi). */
|
|
54
|
+
table: Ref<unknown>
|
|
55
|
+
/** Von vorn laden — mit den aktuellen props.params. */
|
|
56
|
+
refresh: () => Promise<unknown>
|
|
57
|
+
/** Suche: serverseitig (ViUR `search`) bzw. lokal, wenn alles geladen. */
|
|
58
|
+
search: (term: string) => void
|
|
59
|
+
/** Feld-Sichtbarkeit (z.B. für einen „Felder"-Dialog). */
|
|
60
|
+
columns: {
|
|
61
|
+
all: () => { id: string; label: string; hidden: boolean }[]
|
|
62
|
+
apply: (visibleIds: string[]) => void
|
|
63
|
+
}
|
|
64
|
+
/** Beschriftung eines Eintrags (erster Textwert seiner Spalten). */
|
|
65
|
+
entryTitle: (key: RowKey) => string
|
|
66
|
+
/** Alles geladen → Sortieren/Filtern/Suchen laufen lokal. */
|
|
67
|
+
allLoaded: Ref<boolean>
|
|
68
|
+
rows: Ref<R[]>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare const ViurDataTable: DefineComponent<ViurDataTableProps & ViurDataTableEmitProps>
|
|
72
|
+
export default ViurDataTable
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vi-vdt">
|
|
3
|
+
<!-- Struktur kam nicht an (Recht fehlt, unerwartete Antwort): Spalten sind
|
|
4
|
+
dann aus den Daten abgeleitet — Hinweis bleibt stehen. -->
|
|
5
|
+
<wa-callout
|
|
6
|
+
v-if="notice"
|
|
7
|
+
class="vi-vdt-notice"
|
|
8
|
+
variant="warning"
|
|
9
|
+
>
|
|
10
|
+
{{ notice }}
|
|
11
|
+
</wa-callout>
|
|
12
|
+
|
|
13
|
+
<DataTable
|
|
14
|
+
ref="tableEl"
|
|
15
|
+
v-model:sort="sort"
|
|
16
|
+
v-model:selected-keys="selectedKeys"
|
|
17
|
+
v-model:page-size="limit"
|
|
18
|
+
v-model:search="clientSearch"
|
|
19
|
+
v-model:filters="clientFilters"
|
|
20
|
+
class="vi-vdt-table"
|
|
21
|
+
:server="!allLoaded"
|
|
22
|
+
:columns="effectiveColumns"
|
|
23
|
+
:data="rows"
|
|
24
|
+
:label="label ?? module"
|
|
25
|
+
row-key="key"
|
|
26
|
+
:selectable="selectable"
|
|
27
|
+
:loading="list.isFetching.value"
|
|
28
|
+
:has-more="Boolean(list.cursor.value)"
|
|
29
|
+
:resizable="resizable"
|
|
30
|
+
:reorderable="reorderable"
|
|
31
|
+
:pinnable="pinnable"
|
|
32
|
+
v-bind="tableProps"
|
|
33
|
+
@data-request="onRequest"
|
|
34
|
+
@load-more="onLoadMore"
|
|
35
|
+
@page-change="onPageSizeChange"
|
|
36
|
+
@row-dblclick="$emit('open', { row: $event.row, key: $event.key })"
|
|
37
|
+
>
|
|
38
|
+
<template
|
|
39
|
+
v-for="(_, slotName) in $slots"
|
|
40
|
+
#[slotName]="scope"
|
|
41
|
+
>
|
|
42
|
+
<slot
|
|
43
|
+
:name="slotName"
|
|
44
|
+
v-bind="scope"
|
|
45
|
+
></slot>
|
|
46
|
+
</template>
|
|
47
|
+
</DataTable>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup>
|
|
52
|
+
import { computed, ref, watch } from "vue"
|
|
53
|
+
import { useListFetch } from "../../request/index.js"
|
|
54
|
+
import { useI18n } from "vue-i18n"
|
|
55
|
+
import DataTable from "./DataTable.vue"
|
|
56
|
+
import { columnsFromStructure } from "./viurColumns"
|
|
57
|
+
|
|
58
|
+
// ViurDataTable: dünner ViUR-Wrapper um die DataTable für LIST-Module
|
|
59
|
+
// (Pendant zum ViurBrowser für Baum-Module): `module` rein, fertig. Enthält die komplette
|
|
60
|
+
// Listen-Mechanik — Server-Sortierung/-Suche, Cursor-Nachladen, Lokal-Modus
|
|
61
|
+
// (alles geladen → Sortieren/Filtern/Suchen clientseitig), Spalten aus der
|
|
62
|
+
// Struktur, Feld-Sichtbarkeit inkl. optionaler Persistenz und optionalem
|
|
63
|
+
// X-VIUR-BONELIST (Server serialisiert nur die sichtbaren Felder).
|
|
64
|
+
|
|
65
|
+
const props = defineProps({
|
|
66
|
+
module: { type: String, required: true },
|
|
67
|
+
renderer: { type: String, default: null },
|
|
68
|
+
/** Untertyp gruppierter Listen (Handler `list.grouped`). */
|
|
69
|
+
group: { type: String, default: null },
|
|
70
|
+
/** Zusätzliche Request-Parameter — bei JEDEM Abruf gespreadet (z.B. der
|
|
71
|
+
* Server-Context als @-Parameter). Änderungen greifen ab dem nächsten
|
|
72
|
+
* Abruf; sofort neu laden macht `refresh()`. */
|
|
73
|
+
params: { type: Object, default: () => ({}) },
|
|
74
|
+
/** Spalten; ohne Angabe aus der viewSkel-Struktur abgeleitet. */
|
|
75
|
+
columns: { type: Array, default: null },
|
|
76
|
+
/** Nur diese Bones als Spalten (bei abgeleiteten Spalten). */
|
|
77
|
+
bones: { type: Array, default: null },
|
|
78
|
+
/** Zugängliche Beschriftung; Standard: Modulname. */
|
|
79
|
+
label: { type: String, default: null },
|
|
80
|
+
selectable: { type: [String, Boolean], default: "multiple" },
|
|
81
|
+
/** Einträge je Nachladen (ViUR `limit`). */
|
|
82
|
+
pageSize: { type: Number, default: 50 },
|
|
83
|
+
/**
|
|
84
|
+
* Obergrenze für den Lokal-Modus: darüber bleiben Sortieren/Filtern/Suchen
|
|
85
|
+
* beim Server (der erste lokale Suchlauf schickt jede Zeile durch den
|
|
86
|
+
* Bone-Formatter — fünfstellig friert das den Main-Thread ein).
|
|
87
|
+
*/
|
|
88
|
+
localModeLimit: { type: Number, default: 5_000 },
|
|
89
|
+
/** X-VIUR-BONELIST senden, wenn Felder abgewählt sind. NUR aktivieren, wenn
|
|
90
|
+
* das Modul es serverseitig trägt ("*"-Subskel) — sonst antwortet der
|
|
91
|
+
* Core mit 400. */
|
|
92
|
+
bonelist: { type: Boolean, default: false },
|
|
93
|
+
/** localStorage-Key der Feld-Sichtbarkeit (gespeichert wird die
|
|
94
|
+
* HIDDEN-Liste: neue Server-Felder erscheinen automatisch); null = keine
|
|
95
|
+
* Persistenz. */
|
|
96
|
+
storageKey: { type: String, default: null },
|
|
97
|
+
/** Sprache/Locale für Bone-Darstellung in den Spalten. */
|
|
98
|
+
language: { type: String, default: null },
|
|
99
|
+
/** Steht bei language="*" vorn (üblicherweise die UI-Sprache). */
|
|
100
|
+
primaryLanguage: { type: String, default: null },
|
|
101
|
+
locale: { type: String, default: null },
|
|
102
|
+
/** Spalten per Griff in der Kopfzeile breiter/schmaler ziehen. Anders als
|
|
103
|
+
* bei der nackten DataTable (dort Opt-in) hier standardmäßig AN — die
|
|
104
|
+
* ViurDataTable ist das Komfort-Widget. */
|
|
105
|
+
resizable: { type: Boolean, default: true },
|
|
106
|
+
/** Spalten per Drag umsortieren. */
|
|
107
|
+
reorderable: { type: Boolean, default: true },
|
|
108
|
+
/** Spalten links/rechts anpinnen. */
|
|
109
|
+
pinnable: { type: Boolean, default: true },
|
|
110
|
+
/** Wird an die DataTable durchgereicht (überschreibt Einzel-Props). */
|
|
111
|
+
tableProps: { type: Object, default: () => ({}) },
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
defineEmits(["open"])
|
|
115
|
+
|
|
116
|
+
const { t } = useI18n()
|
|
117
|
+
const tableEl = ref(null)
|
|
118
|
+
const selectedKeys = defineModel("selectedKeys", { type: Array, default: () => [] })
|
|
119
|
+
|
|
120
|
+
const sort = ref([])
|
|
121
|
+
const limit = ref(Math.min(99, Number(props.params?.limit ?? props.pageSize) || 50))
|
|
122
|
+
/** Serverseitiger Suchbegriff (ViUR `search`); leer = aus. */
|
|
123
|
+
const serverSearch = ref("")
|
|
124
|
+
/** Client-Suche/-Filter — greifen NUR im vollständig geladenen Zustand. */
|
|
125
|
+
const clientSearch = ref("")
|
|
126
|
+
const clientFilters = ref([])
|
|
127
|
+
/** Ausgeblendete Spalten (Feldnamen). */
|
|
128
|
+
const hiddenFields = ref(loadHiddenFields())
|
|
129
|
+
|
|
130
|
+
const list = useListFetch(props.module, {
|
|
131
|
+
group: props.group ?? null,
|
|
132
|
+
params: { limit: limit.value },
|
|
133
|
+
immediate: false,
|
|
134
|
+
...(props.renderer ? { renderer: props.renderer } : {}),
|
|
135
|
+
headers: () => bonelistHeader(),
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const rows = computed(() => list.data.value)
|
|
139
|
+
|
|
140
|
+
/** Alles geladen und klein genug → Lokal-Modus (Client-Pipeline der Tabelle). */
|
|
141
|
+
const allLoaded = computed(
|
|
142
|
+
() =>
|
|
143
|
+
rows.value.length > 0 &&
|
|
144
|
+
rows.value.length <= props.localModeLimit &&
|
|
145
|
+
list.complete.value &&
|
|
146
|
+
!serverSearch.value
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
const effectiveColumns = computed(() => {
|
|
150
|
+
if (props.columns) return props.columns
|
|
151
|
+
const derived = columnsFromStructure(list.structure.value, {
|
|
152
|
+
...(props.bones ? { bones: props.bones } : {}),
|
|
153
|
+
...(props.language ? { language: props.language } : {}),
|
|
154
|
+
...(props.primaryLanguage ? { primaryLanguage: props.primaryLanguage } : {}),
|
|
155
|
+
...(props.locale ? { locale: props.locale } : {}),
|
|
156
|
+
exclude: ["key"],
|
|
157
|
+
local: allLoaded.value,
|
|
158
|
+
})
|
|
159
|
+
if (derived.length) {
|
|
160
|
+
return derived.map((column) => ({
|
|
161
|
+
...column,
|
|
162
|
+
hidden: hiddenFields.value.includes(String(column.field ?? column.id)),
|
|
163
|
+
}))
|
|
164
|
+
}
|
|
165
|
+
return fallbackColumns(rows.value[0])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const notice = computed(() =>
|
|
169
|
+
rows.value.length && !columnsFromStructure(list.structure.value).length
|
|
170
|
+
? t("dataTable.structureNotice")
|
|
171
|
+
: ""
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
/** Notfall-Spalten: die Felder des ersten Datensatzes, unformatiert. */
|
|
175
|
+
function fallbackColumns(row) {
|
|
176
|
+
if (!row) return []
|
|
177
|
+
return Object.keys(row)
|
|
178
|
+
.filter((field) => field !== "key")
|
|
179
|
+
.map((field) => ({ field, label: field, sortable: false, minWidth: 120, flex: 1 }))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Abrufparameter: props.params (Context etc.) + Suche + Sortierung + limit. */
|
|
183
|
+
function requestParams(sortEntries = sort.value) {
|
|
184
|
+
const params = { ...props.params, limit: limit.value }
|
|
185
|
+
if (serverSearch.value) params.search = serverSearch.value
|
|
186
|
+
const [first] = sortEntries
|
|
187
|
+
if (first) {
|
|
188
|
+
params.orderby = first.id
|
|
189
|
+
if (first.desc) params.orderdir = "1"
|
|
190
|
+
}
|
|
191
|
+
return params
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function onRequest(request) {
|
|
195
|
+
if (request.pageSize) limit.value = Math.min(99, request.pageSize)
|
|
196
|
+
void list.filter(requestParams(request.sort)).catch(() => null)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function onPageSizeChange({ pageSize }) {
|
|
200
|
+
if (!pageSize || pageSize === limit.value) return
|
|
201
|
+
limit.value = Math.min(99, pageSize)
|
|
202
|
+
void list.filter(requestParams()).catch(() => null)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function onLoadMore({ pages }) {
|
|
206
|
+
if (pages === "all") {
|
|
207
|
+
await list.nextAll().catch(() => null)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
await list.next(pages).catch(() => null)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Zurück im Server-Modus (Reload, neuer Cursor, Server-Suche): lokale
|
|
214
|
+
// Verfeinerungen zurücksetzen und Sortierungen entfernen, die der Server
|
|
215
|
+
// nicht kann (nur indizierte, einwertige Bones).
|
|
216
|
+
watch(allLoaded, (local) => {
|
|
217
|
+
if (local) return
|
|
218
|
+
clientSearch.value = ""
|
|
219
|
+
clientFilters.value = []
|
|
220
|
+
sort.value = sort.value.filter((entry) => {
|
|
221
|
+
const bone = list.structure.value?.[entry.id]
|
|
222
|
+
return bone?.indexed === true && !bone?.multiple && !bone?.languages?.length
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// ── Feld-Sichtbarkeit (+ optionale Persistenz, + optionale Bonelist) ─────────
|
|
227
|
+
function loadHiddenFields() {
|
|
228
|
+
if (!props.storageKey) return []
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(localStorage.getItem(props.storageKey) ?? "[]")
|
|
231
|
+
return Array.isArray(parsed) ? parsed.map(String) : []
|
|
232
|
+
} catch {
|
|
233
|
+
return []
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function bonelistHeader() {
|
|
238
|
+
if (!props.bonelist || !hiddenFields.value.length) return null
|
|
239
|
+
const fields = Object.keys(list.structure.value ?? {}).filter(
|
|
240
|
+
(name) => !hiddenFields.value.includes(name)
|
|
241
|
+
)
|
|
242
|
+
if (!fields.length) return null
|
|
243
|
+
return { "X-VIUR-BONELIST": ["key", ...fields].join(",") }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const columnsApi = {
|
|
247
|
+
/** Alle ein-/ausblendbaren Spalten mit aktuellem Zustand. */
|
|
248
|
+
all: () =>
|
|
249
|
+
effectiveColumns.value
|
|
250
|
+
.filter((column) => column.hideable !== false)
|
|
251
|
+
.map((column) => ({
|
|
252
|
+
id: String(column.field ?? column.id),
|
|
253
|
+
label: String(column.label ?? column.field ?? column.id),
|
|
254
|
+
hidden: column.hidden === true,
|
|
255
|
+
})),
|
|
256
|
+
/** Sichtbare Spalten setzen (persistiert; mit Bonelist folgt ein Reload). */
|
|
257
|
+
apply: (visibleIds) => {
|
|
258
|
+
const visible = new Set(visibleIds)
|
|
259
|
+
hiddenFields.value = effectiveColumns.value
|
|
260
|
+
.map((column) => String(column.field ?? column.id))
|
|
261
|
+
.filter((id) => !visible.has(id))
|
|
262
|
+
if (props.storageKey) {
|
|
263
|
+
try {
|
|
264
|
+
localStorage.setItem(props.storageKey, JSON.stringify(hiddenFields.value))
|
|
265
|
+
} catch {
|
|
266
|
+
// localStorage kann fehlen (Privatmodus) — dann eben nicht persistent.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (props.bonelist) void list.filter(requestParams()).catch(() => null)
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Serverseitige Suche — bzw. lokal, wenn alles geladen ist. */
|
|
274
|
+
function search(term) {
|
|
275
|
+
const trimmed = String(term ?? "").trim()
|
|
276
|
+
if (allLoaded.value) {
|
|
277
|
+
clientSearch.value = trimmed
|
|
278
|
+
} else {
|
|
279
|
+
serverSearch.value = trimmed
|
|
280
|
+
void list.filter(requestParams()).catch(() => null)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Von vorn laden — mit den AKTUELLEN props.params (Context etc.). */
|
|
285
|
+
function refresh() {
|
|
286
|
+
return list.filter(requestParams())
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Beschriftung eines Eintrags: erster Textwert seiner Spalten, sonst der Key. */
|
|
290
|
+
function entryTitle(key) {
|
|
291
|
+
const row = rows.value.find((entry) => String(entry.key) === String(key))
|
|
292
|
+
if (!row) return String(key)
|
|
293
|
+
for (const column of effectiveColumns.value) {
|
|
294
|
+
const field = column.field
|
|
295
|
+
if (!field) continue
|
|
296
|
+
const text = column.formatter
|
|
297
|
+
? column.formatter(row[field], row)
|
|
298
|
+
: row[field] === null || row[field] === undefined
|
|
299
|
+
? ""
|
|
300
|
+
: String(row[field])
|
|
301
|
+
if (text) return text
|
|
302
|
+
}
|
|
303
|
+
return String(key)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
defineExpose({
|
|
307
|
+
list,
|
|
308
|
+
table: tableEl,
|
|
309
|
+
refresh,
|
|
310
|
+
search,
|
|
311
|
+
columns: columnsApi,
|
|
312
|
+
entryTitle,
|
|
313
|
+
allLoaded,
|
|
314
|
+
rows,
|
|
315
|
+
})
|
|
316
|
+
</script>
|
|
317
|
+
|
|
318
|
+
<style scoped>
|
|
319
|
+
.vi-vdt {
|
|
320
|
+
display: flex;
|
|
321
|
+
flex-direction: column;
|
|
322
|
+
gap: var(--wa-space-s);
|
|
323
|
+
flex: 1;
|
|
324
|
+
min-block-size: 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.vi-vdt-table {
|
|
328
|
+
flex: 1;
|
|
329
|
+
min-block-size: 0;
|
|
330
|
+
}
|
|
331
|
+
</style>
|