@raclettejs/core 0.1.32 → 0.1.33-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/services/backend/.yarn/install-state.gz +0 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchOwn.ts +1 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/user.service.ts +22 -1
- package/services/backend/yarn.lock +1 -1
- package/services/frontend/.yarn/install-state.gz +0 -0
- package/services/frontend/package.json +2 -2
- package/services/frontend/src/core/lib/data/dataApi.ts +1 -2
- package/services/frontend/src/orchestrator/ProductOrchestrator.vue +8 -1
- package/services/frontend/src/orchestrator/assets/styles/layers.css +1 -4
- package/services/frontend/src/orchestrator/assets/styles/vuetifyStyles.scss +4 -1
- package/services/frontend/src/orchestrator/components/dataExport/DataExporter.vue +303 -0
- package/services/frontend/src/orchestrator/components/dataImport/ImportSelectionDialog.vue +169 -0
- package/services/frontend/src/orchestrator/components/dataImport/ImportSummaryDialog.vue +211 -0
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +473 -0
- package/services/frontend/src/orchestrator/components/index.ts +10 -0
- package/services/frontend/src/orchestrator/components/input/FileUpload.vue +83 -0
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/useExport/formats/builtins.ts +124 -0
- package/services/frontend/src/orchestrator/composables/useExport/index.ts +176 -0
- package/services/frontend/src/orchestrator/composables/useExport/types.ts +141 -0
- package/services/frontend/src/orchestrator/i18n/de-DE.json +26 -1
- package/services/frontend/src/orchestrator/i18n/en-EU.json +26 -1
- package/services/frontend/src/orchestrator/i18n/sk.json +26 -1
- package/services/frontend/src/orchestrator/i18n/tl-TL.json +14 -0
- package/services/frontend/src/orchestrator/setup/vuetify.ts +0 -1
- package/services/frontend/yarn.lock +4854 -0
- package/types/index.ts +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Visually hidden; opened via openPicker() from a separate toolbar button -->
|
|
3
|
+
<input
|
|
4
|
+
ref="nativeInput"
|
|
5
|
+
type="file"
|
|
6
|
+
class="raclette-file-upload-input"
|
|
7
|
+
:accept="accept"
|
|
8
|
+
@change="handleNativeChange"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { useTemplateRef } from "vue"
|
|
14
|
+
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
onFileLoaded: {
|
|
17
|
+
type: Function,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
accept: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: "*/*",
|
|
23
|
+
},
|
|
24
|
+
readAs: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: "text",
|
|
27
|
+
validator: (value: string) => ["text", "arrayBuffer"].includes(value),
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const nativeInput = useTemplateRef<HTMLInputElement>("nativeInput")
|
|
32
|
+
|
|
33
|
+
const handleNativeChange = (event: Event) => {
|
|
34
|
+
const input = event.target as HTMLInputElement
|
|
35
|
+
const file = input.files?.[0]
|
|
36
|
+
if (!file) {
|
|
37
|
+
input.value = ""
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const reader = new FileReader()
|
|
42
|
+
|
|
43
|
+
reader.onload = () => {
|
|
44
|
+
;(props.onFileLoaded as (c: string | ArrayBuffer, f: File) => void)(
|
|
45
|
+
reader.result as string | ArrayBuffer,
|
|
46
|
+
file,
|
|
47
|
+
)
|
|
48
|
+
input.value = ""
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
reader.onerror = () => {
|
|
52
|
+
input.value = ""
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (props.readAs === "arrayBuffer") {
|
|
56
|
+
reader.readAsArrayBuffer(file)
|
|
57
|
+
} else {
|
|
58
|
+
reader.readAsText(file)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Programmatically open the file picker (toolbar button should call this). */
|
|
63
|
+
const openPicker = () => {
|
|
64
|
+
nativeInput.value?.click()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
defineExpose({ openPicker, trigger: openPicker })
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<style scoped>
|
|
71
|
+
.raclette-file-upload-input {
|
|
72
|
+
position: absolute;
|
|
73
|
+
width: 1px;
|
|
74
|
+
height: 1px;
|
|
75
|
+
padding: 0;
|
|
76
|
+
margin: -1px;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
clip: rect(0, 0, 0, 0);
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
border: 0;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ExportFormat } from "../types"
|
|
2
|
+
|
|
3
|
+
// ─── Optional peer dep: js-yaml ───────────────────────────────────────────────
|
|
4
|
+
// Vite/ESM environments don't support require(). We attempt a static import at
|
|
5
|
+
// module initialisation time. If js-yaml is not installed the import will throw
|
|
6
|
+
// and jsYamlDump stays undefined — the serializer then shows a clear error.
|
|
7
|
+
|
|
8
|
+
type JsYamlDump = (data: unknown, options?: object) => string
|
|
9
|
+
|
|
10
|
+
let jsYamlDump: JsYamlDump | undefined
|
|
11
|
+
|
|
12
|
+
const jsonReplacer = (_key: string, value: unknown) => {
|
|
13
|
+
// JSON doesn't support bigint values; preserve intent as a tagged string.
|
|
14
|
+
if (typeof value === "bigint") {
|
|
15
|
+
return `${value}n`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Static import string must be a literal so bundlers can analyse it.
|
|
23
|
+
// The `@vite-ignore` comment suppresses the "dynamic import" warning.
|
|
24
|
+
const mod = await import(/* @vite-ignore */ "js-yaml")
|
|
25
|
+
jsYamlDump = (mod.dump ?? mod.default?.dump) as JsYamlDump | undefined
|
|
26
|
+
} catch {
|
|
27
|
+
// js-yaml not installed — yamlFormat.serialize() will throw a helpful message
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── XML ──────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Recursively converts a plain JS object / array to XML element strings.
|
|
34
|
+
* - Object keys become element names (invalid XML chars are stripped).
|
|
35
|
+
* - Arrays emit repeated elements using the parent key name (singularised naively).
|
|
36
|
+
* - Primitive values become text content.
|
|
37
|
+
*/
|
|
38
|
+
function toXmlElements(value: unknown, tag: string, indent: string): string {
|
|
39
|
+
const pad = indent + " "
|
|
40
|
+
|
|
41
|
+
if (value === null || value === undefined) {
|
|
42
|
+
return `${indent}<${tag}/>`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
// Emit each item wrapped in a singular version of the tag.
|
|
47
|
+
const child = tag.replace(/s$/, "") || "item"
|
|
48
|
+
return value
|
|
49
|
+
.map((item) => toXmlElements(item, child, indent))
|
|
50
|
+
.join("\n")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof value === "object") {
|
|
54
|
+
const children = Object.entries(value as Record<string, unknown>)
|
|
55
|
+
.map(([k, v]) => toXmlElements(v, sanitiseTag(k), pad))
|
|
56
|
+
.join("\n")
|
|
57
|
+
return `${indent}<${tag}>\n${children}\n${indent}</${tag}>`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Primitive — escape XML special chars.
|
|
61
|
+
const escaped = String(value)
|
|
62
|
+
.replace(/&/g, "&")
|
|
63
|
+
.replace(/</g, "<")
|
|
64
|
+
.replace(/>/g, ">")
|
|
65
|
+
.replace(/"/g, """)
|
|
66
|
+
.replace(/'/g, "'")
|
|
67
|
+
return `${indent}<${tag}>${escaped}</${tag}>`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sanitiseTag(key: string): string {
|
|
71
|
+
// XML element names must start with a letter or underscore.
|
|
72
|
+
const cleaned = key.replace(/[^a-zA-Z0-9_.-]/g, "_")
|
|
73
|
+
return /^[^a-zA-Z_]/.test(cleaned) ? `_${cleaned}` : cleaned
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const xmlFormat: ExportFormat = {
|
|
77
|
+
id: "xml",
|
|
78
|
+
label: "XML",
|
|
79
|
+
extension: "xml",
|
|
80
|
+
mimeType: "application/xml",
|
|
81
|
+
serialize(data) {
|
|
82
|
+
const body = toXmlElements(data, "root", "")
|
|
83
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n${body}`
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── JSON ─────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
export const jsonFormat: ExportFormat = {
|
|
90
|
+
id: "json",
|
|
91
|
+
label: "JSON",
|
|
92
|
+
extension: "json",
|
|
93
|
+
mimeType: "application/json",
|
|
94
|
+
serialize(data) {
|
|
95
|
+
return JSON.stringify(data, jsonReplacer, 2)
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── YAML ─────────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
export const yamlFormat: ExportFormat = {
|
|
102
|
+
id: "yaml",
|
|
103
|
+
label: "YAML",
|
|
104
|
+
extension: "yaml",
|
|
105
|
+
mimeType: "application/yaml",
|
|
106
|
+
serialize(data) {
|
|
107
|
+
if (typeof jsYamlDump !== "function") {
|
|
108
|
+
throw new Error(
|
|
109
|
+
'The "js-yaml" package is required for YAML export.\n' +
|
|
110
|
+
"Install it with: npm install js-yaml\n" +
|
|
111
|
+
"Types (optional): npm install -D @types/js-yaml",
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
return jsYamlDump(data, { indent: 2, lineWidth: 120, noRefs: true })
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
export const BUILTIN_FORMATS: Record<string, ExportFormat> = {
|
|
121
|
+
json: jsonFormat,
|
|
122
|
+
yaml: yamlFormat,
|
|
123
|
+
xml: xmlFormat,
|
|
124
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { computed, ref, watch } from "vue"
|
|
2
|
+
import { useClipboard } from "@vueuse/core"
|
|
3
|
+
import { BUILTIN_FORMATS } from "./formats/builtins"
|
|
4
|
+
import type {
|
|
5
|
+
ExportFormat,
|
|
6
|
+
FormatEntry,
|
|
7
|
+
SerializeResult,
|
|
8
|
+
ExportSuccessPayload,
|
|
9
|
+
ExportErrorPayload,
|
|
10
|
+
} from "./types"
|
|
11
|
+
|
|
12
|
+
export * from "./types"
|
|
13
|
+
|
|
14
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
interface UseExportOptions {
|
|
17
|
+
data: () => unknown
|
|
18
|
+
formats: () => FormatEntry[]
|
|
19
|
+
defaultFormat: () => string | undefined
|
|
20
|
+
filename: () => string
|
|
21
|
+
enabled?: () => boolean
|
|
22
|
+
onSuccess: (payload: ExportSuccessPayload) => void
|
|
23
|
+
onError: (payload: ExportErrorPayload) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Composable ───────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export function useExport(options: UseExportOptions) {
|
|
29
|
+
const {
|
|
30
|
+
data,
|
|
31
|
+
formats,
|
|
32
|
+
defaultFormat,
|
|
33
|
+
filename,
|
|
34
|
+
enabled,
|
|
35
|
+
onSuccess,
|
|
36
|
+
onError,
|
|
37
|
+
} = options
|
|
38
|
+
|
|
39
|
+
// ── Resolve format list ────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const resolvedFormats = computed<ExportFormat[]>(() => {
|
|
42
|
+
return formats()
|
|
43
|
+
.map((entry): ExportFormat | null => {
|
|
44
|
+
if (typeof entry === "string") {
|
|
45
|
+
return BUILTIN_FORMATS[entry] ?? null
|
|
46
|
+
}
|
|
47
|
+
// Custom format — validate minimum required fields
|
|
48
|
+
if (
|
|
49
|
+
entry &&
|
|
50
|
+
typeof entry.id === "string" &&
|
|
51
|
+
typeof entry.serialize === "function"
|
|
52
|
+
) {
|
|
53
|
+
return entry as ExportFormat
|
|
54
|
+
}
|
|
55
|
+
console.warn("[RacletteExport] Invalid format entry, skipping:", entry)
|
|
56
|
+
return null
|
|
57
|
+
})
|
|
58
|
+
.filter((f): f is ExportFormat => f !== null)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// ── Active format ──────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
const activeFormatId = ref<string>("")
|
|
64
|
+
|
|
65
|
+
// Initialize / sync when formats or defaultFormat changes
|
|
66
|
+
watch(
|
|
67
|
+
[resolvedFormats, defaultFormat],
|
|
68
|
+
([fmts, dflt]) => {
|
|
69
|
+
if (!fmts.length) {
|
|
70
|
+
activeFormatId.value = ""
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
const preferred = dflt ?? ""
|
|
74
|
+
const found = fmts.find((f) => f.id === preferred)
|
|
75
|
+
// Keep current selection if still valid, otherwise fall back to first
|
|
76
|
+
const currentStillValid = fmts.some((f) => f.id === activeFormatId.value)
|
|
77
|
+
if (!currentStillValid) {
|
|
78
|
+
activeFormatId.value = found?.id ?? fmts[0].id
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{ immediate: true },
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const activeFormat = computed<ExportFormat | undefined>(() =>
|
|
85
|
+
resolvedFormats.value.find((f) => f.id === activeFormatId.value),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// ── Serialization ──────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const serializeResult = computed<SerializeResult>(() => {
|
|
91
|
+
if (enabled && !enabled()) {
|
|
92
|
+
// Defer serialization until explicitly enabled by the caller.
|
|
93
|
+
return { ok: true, value: "" }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const fmt = activeFormat.value
|
|
97
|
+
if (!fmt) return { ok: false, error: "No format selected." }
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const value = fmt.serialize(data())
|
|
101
|
+
if (typeof value !== "string") {
|
|
102
|
+
throw new TypeError(
|
|
103
|
+
`serialize() must return a string, got ${typeof value}`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, value }
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
109
|
+
onError({ formatId: fmt.id, action: "serialize", error: err })
|
|
110
|
+
return { ok: false, error: message }
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// ── Clipboard ──────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
const { copy, isSupported: clipboardSupported } = useClipboard()
|
|
117
|
+
const copyState = ref<"idle" | "success" | "error">("idle")
|
|
118
|
+
let copyResetTimer: ReturnType<typeof setTimeout> | null = null
|
|
119
|
+
|
|
120
|
+
async function copyToClipboard() {
|
|
121
|
+
const fmt = activeFormat.value
|
|
122
|
+
if (!fmt) return
|
|
123
|
+
if (serializeResult.value.ok === false) return
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
await copy(serializeResult.value.value)
|
|
127
|
+
copyState.value = "success"
|
|
128
|
+
onSuccess({ formatId: fmt.id, action: "copy" })
|
|
129
|
+
} catch (err) {
|
|
130
|
+
copyState.value = "error"
|
|
131
|
+
onError({ formatId: fmt.id, action: "copy", error: err })
|
|
132
|
+
} finally {
|
|
133
|
+
if (copyResetTimer) clearTimeout(copyResetTimer)
|
|
134
|
+
copyResetTimer = setTimeout(() => {
|
|
135
|
+
copyState.value = "idle"
|
|
136
|
+
}, 2000)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Download ───────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
function downloadFile() {
|
|
143
|
+
const fmt = activeFormat.value
|
|
144
|
+
if (!fmt) return
|
|
145
|
+
if (serializeResult.value.ok === false) return
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const blob = new Blob([serializeResult.value.value], {
|
|
149
|
+
type: `${fmt.mimeType};charset=utf-8`,
|
|
150
|
+
})
|
|
151
|
+
const url = URL.createObjectURL(blob)
|
|
152
|
+
const anchor = document.createElement("a")
|
|
153
|
+
anchor.href = url
|
|
154
|
+
anchor.download = `${filename() || "export"}.${fmt.extension}`
|
|
155
|
+
anchor.style.display = "none"
|
|
156
|
+
document.body.appendChild(anchor)
|
|
157
|
+
anchor.click()
|
|
158
|
+
document.body.removeChild(anchor)
|
|
159
|
+
URL.revokeObjectURL(url)
|
|
160
|
+
onSuccess({ formatId: fmt.id, action: "download" })
|
|
161
|
+
} catch (err) {
|
|
162
|
+
onError({ formatId: fmt.id, action: "download", error: err })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
resolvedFormats,
|
|
168
|
+
activeFormatId,
|
|
169
|
+
activeFormat,
|
|
170
|
+
serializeResult,
|
|
171
|
+
copyToClipboard,
|
|
172
|
+
downloadFile,
|
|
173
|
+
copyState,
|
|
174
|
+
clipboardSupported,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ─── Core Export Format Interface ────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Defines a data export format. Implement this to add custom formats.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The expected shape of the data (defaults to unknown for safety)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const xmlFormat: ExportFormat<Record<string, unknown>> = {
|
|
10
|
+
* id: 'xml',
|
|
11
|
+
* label: 'XML',
|
|
12
|
+
* extension: 'xml',
|
|
13
|
+
* mimeType: 'application/xml',
|
|
14
|
+
* serialize: (data) => toXml(data),
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
export interface ExportFormat<T = unknown> {
|
|
18
|
+
/** Unique identifier used to reference this format in the `formats` prop */
|
|
19
|
+
id: string
|
|
20
|
+
/** Display label shown in the format selector tabs */
|
|
21
|
+
label: string
|
|
22
|
+
/** File extension appended to the filename on download (without leading dot) */
|
|
23
|
+
extension: string
|
|
24
|
+
/** MIME type used for the downloaded Blob */
|
|
25
|
+
mimeType: string
|
|
26
|
+
/**
|
|
27
|
+
* Transform `data` into a string for clipboard copy or file download.
|
|
28
|
+
* May throw — the component catches all errors and shows them inline.
|
|
29
|
+
*/
|
|
30
|
+
serialize: (data: T) => string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ExportPayload<TItem = unknown, TMeta = unknown> {
|
|
34
|
+
items: TItem[]
|
|
35
|
+
meta?: TMeta
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ExportDataScope = "combined" | "items" | "meta"
|
|
39
|
+
|
|
40
|
+
// ─── Built-in Format IDs ──────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export type BuiltinFormatId = "json" | "yaml"
|
|
43
|
+
|
|
44
|
+
/** A format entry in the `formats` prop: either a builtin ID or a custom format object */
|
|
45
|
+
export type FormatEntry = BuiltinFormatId | ExportFormat
|
|
46
|
+
|
|
47
|
+
// ─── Component Props ──────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export interface RacletteExportProps {
|
|
50
|
+
/**
|
|
51
|
+
* The data to export. Accepts any value: object, array, string, number, etc.
|
|
52
|
+
* Each serializer handles edge cases gracefully.
|
|
53
|
+
*/
|
|
54
|
+
data: ExportPayload
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Base filename for downloads (no extension). Extension is appended automatically.
|
|
58
|
+
* @default 'export'
|
|
59
|
+
*/
|
|
60
|
+
filename?: string
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Ordered list of formats to offer. Mix builtin IDs and custom ExportFormat objects.
|
|
64
|
+
* This single prop controls both inclusion and order.
|
|
65
|
+
* @default ['json', 'yaml']
|
|
66
|
+
*/
|
|
67
|
+
formats?: FormatEntry[]
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* ID of the format that should be active when the popover opens.
|
|
71
|
+
* Falls back to the first format in the list if not found.
|
|
72
|
+
*/
|
|
73
|
+
defaultFormat?: string
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Label for the default trigger button (only used when no default slot is provided).
|
|
77
|
+
* @default 'Export'
|
|
78
|
+
*/
|
|
79
|
+
buttonLabel?: string
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Disables the trigger button and prevents the popover from opening.
|
|
83
|
+
* @default false
|
|
84
|
+
*/
|
|
85
|
+
disabled?: boolean
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether the preview pane starts collapsed.
|
|
89
|
+
* @default true
|
|
90
|
+
*/
|
|
91
|
+
previewCollapsed?: boolean
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Shows a scope switch inside the popover to choose whether to export:
|
|
95
|
+
* - the combined payload object
|
|
96
|
+
* - only items
|
|
97
|
+
* - only meta
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
showDataScopeSwitch?: boolean
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Component Emits ──────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
export interface ExportSuccessPayload {
|
|
106
|
+
/** ID of the format that was used */
|
|
107
|
+
formatId: string
|
|
108
|
+
/** Whether the user copied or downloaded */
|
|
109
|
+
action: "copy" | "download"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ExportErrorPayload {
|
|
113
|
+
/** ID of the format that failed */
|
|
114
|
+
formatId: string
|
|
115
|
+
/** Whether the copy or download was attempted */
|
|
116
|
+
action: "copy" | "download" | "serialize"
|
|
117
|
+
/** The caught error */
|
|
118
|
+
error: unknown
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface RacletteExportEmits {
|
|
122
|
+
(e: "export-success", payload: ExportSuccessPayload): void
|
|
123
|
+
(e: "export-error", payload: ExportErrorPayload): void
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── Slot Props ───────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
export interface TriggerSlotProps {
|
|
129
|
+
/** Opens the export popover */
|
|
130
|
+
open: () => void
|
|
131
|
+
/** Whether the popover is currently open */
|
|
132
|
+
isOpen: boolean
|
|
133
|
+
/** Mirrors the `disabled` prop */
|
|
134
|
+
disabled: boolean
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Serialization Result ─────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
export type SerializeResult =
|
|
140
|
+
| { ok: true; value: string }
|
|
141
|
+
| { ok: false; error: string }
|
|
@@ -33,6 +33,30 @@
|
|
|
33
33
|
"more_soonDescription": "Wir befinden uns noch in der Beta-Phase, aber liefern bereits! Schau dir unsere Roadmap an",
|
|
34
34
|
"roadmap": "Roadmap auf Gitlab"
|
|
35
35
|
},
|
|
36
|
+
"baseDataTable": {
|
|
37
|
+
"searchPlaceholder": "{dataName} durchsuchen",
|
|
38
|
+
"createDataButton": "{dataName} erstellen",
|
|
39
|
+
"deleteDataTitle": "{dataName} löschen",
|
|
40
|
+
"restoreDataTitle": "{dataName} widerherstellen",
|
|
41
|
+
"deleteDataConfirmMessage": "Bist du sicher, dass du {article} {dataName} <strong>{value}</strong> löschen willst?",
|
|
42
|
+
"deleteDataConfirmButton": "Löschen",
|
|
43
|
+
"deleteDataConfirmCancel": "Abbrechen"
|
|
44
|
+
},
|
|
45
|
+
"dataExporter": {
|
|
46
|
+
"lines": "Zeilen",
|
|
47
|
+
"title": "Daten exportieren",
|
|
48
|
+
"scopeLabel": "Daten",
|
|
49
|
+
"scopeCombined": "Kombiniert",
|
|
50
|
+
"scopeItems": "Elemente",
|
|
51
|
+
"scopeMeta": "Metadaten",
|
|
52
|
+
"noValidFormats": "Keine gueltigen Formate konfiguriert.",
|
|
53
|
+
"preview": "Vorschau",
|
|
54
|
+
"previewTruncatedAt": "Vorschau bei {count} Zeilen abgeschnitten",
|
|
55
|
+
"copied": "Kopiert!",
|
|
56
|
+
"copyFailed": "Fehlgeschlagen",
|
|
57
|
+
"copy": "Kopieren",
|
|
58
|
+
"download": "Herunterladen"
|
|
59
|
+
},
|
|
36
60
|
"dataSync": {
|
|
37
61
|
"update": {
|
|
38
62
|
"$docCountOf$docTypeHaveBeenHandled": "{docCount} Dokumente vom Typ {docType} wurden erfolgreich geupdated",
|
|
@@ -219,4 +243,5 @@
|
|
|
219
243
|
"sl-SL": "Slowenisch (Slowenien)"
|
|
220
244
|
}
|
|
221
245
|
}
|
|
222
|
-
}
|
|
246
|
+
}
|
|
247
|
+
|
|
@@ -33,6 +33,30 @@
|
|
|
33
33
|
"more_soonDescription": "We are still in Beta but on our way to deliver! Checkout our Roadmap",
|
|
34
34
|
"roadmap": "Roadmap on Gitlab"
|
|
35
35
|
},
|
|
36
|
+
"baseDataTable": {
|
|
37
|
+
"searchPlaceholder": "Search {dataName}",
|
|
38
|
+
"createDataButton": "Create {dataName}",
|
|
39
|
+
"deleteDataTitle": "Delete {dataName}",
|
|
40
|
+
"restoreDataTitle": "Restore {dataName}",
|
|
41
|
+
"deleteDataConfirmMessage": "Are you sure you want to delete {article} {dataName} <strong>{value}</strong>?",
|
|
42
|
+
"deleteDataConfirmButton": "Delete",
|
|
43
|
+
"deleteDataConfirmCancel": "Cancel"
|
|
44
|
+
},
|
|
45
|
+
"dataExporter": {
|
|
46
|
+
"lines": "Lines",
|
|
47
|
+
"title": "Export Data",
|
|
48
|
+
"scopeLabel": "Data",
|
|
49
|
+
"scopeCombined": "Combined",
|
|
50
|
+
"scopeItems": "Items",
|
|
51
|
+
"scopeMeta": "Meta",
|
|
52
|
+
"noValidFormats": "No valid formats configured.",
|
|
53
|
+
"preview": "Preview",
|
|
54
|
+
"previewTruncatedAt": "Preview truncated at {count} lines",
|
|
55
|
+
"copied": "Copied!",
|
|
56
|
+
"copyFailed": "Failed",
|
|
57
|
+
"copy": "Copy",
|
|
58
|
+
"download": "Download"
|
|
59
|
+
},
|
|
36
60
|
"dataSync": {
|
|
37
61
|
"update": {
|
|
38
62
|
"$docCountOf$docTypeHaveBeenHandled": "{docCount} documents of type {docType} were successfully updated",
|
|
@@ -219,4 +243,5 @@
|
|
|
219
243
|
"sl-SL": "Slovenian (Slovenia)"
|
|
220
244
|
}
|
|
221
245
|
}
|
|
222
|
-
}
|
|
246
|
+
}
|
|
247
|
+
|
|
@@ -33,6 +33,30 @@
|
|
|
33
33
|
"more_soonDescription": "Stále sme v beta verzii, ale pracujeme na tom, aby sme doručili! Pozrite si našu cestovnú mapu",
|
|
34
34
|
"roadmap": "Cestovná mapa na Gitlabe"
|
|
35
35
|
},
|
|
36
|
+
"baseDataTable": {
|
|
37
|
+
"searchPlaceholder": "Hľadať {dataName}",
|
|
38
|
+
"createDataButton": "Vytvoriť {dataName}",
|
|
39
|
+
"deleteDataTitle": "Odstrániť {dataName}",
|
|
40
|
+
"restoreDataTitle": "Obnoviť {dataName}",
|
|
41
|
+
"deleteDataConfirmMessage": "Naozaj chcete odstrániť {article} {dataName} <strong>{value}</strong>?",
|
|
42
|
+
"deleteDataConfirmButton": "Odstrániť",
|
|
43
|
+
"deleteDataConfirmCancel": "Zrušiť"
|
|
44
|
+
},
|
|
45
|
+
"dataExporter": {
|
|
46
|
+
"lines": "riadky",
|
|
47
|
+
"title": "Export údajov",
|
|
48
|
+
"scopeLabel": "Údaje",
|
|
49
|
+
"scopeCombined": "Kombinované",
|
|
50
|
+
"scopeItems": "Položky",
|
|
51
|
+
"scopeMeta": "Meta",
|
|
52
|
+
"noValidFormats": "Nie sú nakonfigurované žiadne platné formáty.",
|
|
53
|
+
"preview": "Náhľad",
|
|
54
|
+
"previewTruncatedAt": "Náhľad je skrátený na {count} riadkov",
|
|
55
|
+
"copied": "Skopírované!",
|
|
56
|
+
"copyFailed": "Zlyhalo",
|
|
57
|
+
"copy": "Kopírovať",
|
|
58
|
+
"download": "Stiahnuť"
|
|
59
|
+
},
|
|
36
60
|
"dataSync": {
|
|
37
61
|
"update": {
|
|
38
62
|
"$docCountOf$docTypeHaveBeenHandled": "{docCount} dokumentov typu {docType} bolo úspešne aktualizovaných",
|
|
@@ -219,4 +243,5 @@
|
|
|
219
243
|
"sl-SL": "Slovinčina (Slovinsko)"
|
|
220
244
|
}
|
|
221
245
|
}
|
|
222
|
-
}
|
|
246
|
+
}
|
|
247
|
+
|
|
@@ -33,6 +33,20 @@
|
|
|
33
33
|
"more_soonDescription": "Beta wItaH 'ach wIghoS – Roadmap yIlegh!",
|
|
34
34
|
"roadmap": "GitlabDaq Roadmap"
|
|
35
35
|
},
|
|
36
|
+
"dataExporter": {
|
|
37
|
+
"title": "Export Data",
|
|
38
|
+
"scopeLabel": "Data",
|
|
39
|
+
"scopeCombined": "Combined",
|
|
40
|
+
"scopeItems": "Items",
|
|
41
|
+
"scopeMeta": "Meta",
|
|
42
|
+
"noValidFormats": "No valid formats configured.",
|
|
43
|
+
"preview": "Preview",
|
|
44
|
+
"previewTruncatedAt": "Preview truncated at {count} lines",
|
|
45
|
+
"copied": "Copied!",
|
|
46
|
+
"copyFailed": "Failed",
|
|
47
|
+
"copy": "Copy",
|
|
48
|
+
"download": "Download"
|
|
49
|
+
},
|
|
36
50
|
"dataSync": {
|
|
37
51
|
"update": {
|
|
38
52
|
"$docCountOf$docTypeHaveBeenHandled": "{docCount} {docType} puS ghItlhmey Qapla’ vI’taH",
|