tantee-nuxt-commons 0.0.169 → 0.0.171
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/dist/module.json +1 -1
- package/dist/module.mjs +6 -0
- package/dist/runtime/components/Alert.vue +0 -1
- package/dist/runtime/components/form/ActionPad.vue +1 -1
- package/dist/runtime/components/form/Dialog.vue +1 -1
- package/dist/runtime/components/form/EditPad.vue +1 -1
- package/dist/runtime/components/form/File.vue +189 -126
- package/dist/runtime/components/form/Iterator.vue +2 -0
- package/dist/runtime/components/form/Pad.vue +3 -72
- package/dist/runtime/components/form/SignPad.vue +126 -141
- package/dist/runtime/components/form/images/Field.vue +269 -219
- package/dist/runtime/components/label/DateCount.vue +13 -117
- package/dist/runtime/components/pdf/View.vue +32 -42
- package/dist/runtime/composables/assetFile.d.ts +31 -0
- package/dist/runtime/composables/assetFile.js +134 -0
- package/dist/runtime/composables/document/templateFormTable.js +1 -4
- package/package.json +1 -1
|
@@ -2,143 +2,39 @@
|
|
|
2
2
|
import { DateTime } from "luxon";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
|
|
5
|
-
type
|
|
6
|
-
type Locale = 'th' | 'en' | 'en-US' | 'th-TH';
|
|
5
|
+
type Locale = "th" | "en" | "en-US" | "th-TH";
|
|
7
6
|
|
|
8
7
|
interface Props {
|
|
9
8
|
modelValue: DateTime;
|
|
10
9
|
endDate?: DateTime;
|
|
11
10
|
locale?: Locale;
|
|
12
|
-
|
|
13
|
-
units?: Unit[];
|
|
14
|
-
zeroBase?: boolean; // true = start at 0, false = start at 1 (inclusive)
|
|
11
|
+
zeroBase?: boolean; // true = start at 0, false = start at 1
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
-
locale:
|
|
19
|
-
showSuffix: true,
|
|
15
|
+
locale: "th",
|
|
20
16
|
zeroBase: true,
|
|
21
17
|
});
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (locale.startsWith('en')) return 'en';
|
|
26
|
-
if (locale.startsWith('th')) return 'th';
|
|
27
|
-
return 'th'; // default fallback
|
|
28
|
-
};
|
|
19
|
+
const normalizeLocale = (locale: Locale): "en" | "th" =>
|
|
20
|
+
locale.startsWith("en") ? "en" : "th";
|
|
29
21
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
months: 'months',
|
|
33
|
-
days: 'days',
|
|
34
|
-
hours: 'hours',
|
|
35
|
-
minutes: 'minutes',
|
|
36
|
-
seconds: 'seconds',
|
|
37
|
-
};
|
|
38
|
-
const labelsEnSingular: Record<Unit, string> = {
|
|
39
|
-
years: 'year',
|
|
40
|
-
months: 'month',
|
|
41
|
-
days: 'day',
|
|
42
|
-
hours: 'hour',
|
|
43
|
-
minutes: 'minute',
|
|
44
|
-
seconds: 'second',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const localizedLabels: Record<'en' | 'th', Record<Unit, string>> = {
|
|
48
|
-
en: labelsEnPlural, // จะสลับเป็น singular ตามค่าจริงตอน render
|
|
49
|
-
th: {
|
|
50
|
-
years: 'ปี',
|
|
51
|
-
months: 'เดือน',
|
|
52
|
-
days: 'วัน',
|
|
53
|
-
hours: 'ชั่วโมง',
|
|
54
|
-
minutes: 'นาที',
|
|
55
|
-
seconds: 'วินาที',
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const localizedSuffix: Record<'en' | 'th', string> = {
|
|
60
|
-
en: 'ago',
|
|
61
|
-
th: 'ที่ผ่านมา',
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const outputText = computed(() => {
|
|
65
|
-
const base = props.endDate ?? DateTime.now(); // ใช้ endDate ถ้ามี ไม่งั้น now
|
|
22
|
+
const countDate = computed(() => {
|
|
23
|
+
const base = props.endDate ?? DateTime.now();
|
|
66
24
|
const baseLocale = normalizeLocale(props.locale);
|
|
67
25
|
|
|
68
|
-
|
|
69
|
-
? props.units
|
|
70
|
-
: ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];
|
|
71
|
-
|
|
72
|
-
const diffObj = base.diff(props.modelValue, units).toObject();
|
|
73
|
-
|
|
74
|
-
// helper: คืน label ตาม singular/plural (เฉพาะ en)
|
|
75
|
-
const labelFor = (unit: Unit, value: number) => {
|
|
76
|
-
if (baseLocale === 'en') {
|
|
77
|
-
return value === 1 ? labelsEnSingular[unit] : labelsEnPlural[unit];
|
|
78
|
-
}
|
|
79
|
-
return localizedLabels[baseLocale][unit];
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// ---------- โหมด single unit ----------
|
|
83
|
-
if (!props.units) {
|
|
84
|
-
const foundUnit = units.find((unit) => (diffObj[unit] ?? 0) >= 1);
|
|
85
|
-
if (foundUnit) {
|
|
86
|
-
const raw = Math.floor(diffObj[foundUnit] ?? 0); // >= 1
|
|
87
|
-
const value = props.zeroBase ? raw : raw + 1; // inclusive (+1)
|
|
88
|
-
const label = labelFor(foundUnit, value);
|
|
89
|
-
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
90
|
-
return `${value} ${label}${suffix ? ` ${suffix}` : ''}`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ถ้าไม่มีหน่วยใด >= 1 ให้ใช้หน่วยเล็กสุด
|
|
94
|
-
const lastUnit = units.at(-1)!;
|
|
95
|
-
const raw = Math.max(0, Math.floor(diffObj[lastUnit] ?? 0));
|
|
96
|
-
const value = props.zeroBase ? raw : 1; // inclusive: อย่างน้อย 1
|
|
97
|
-
const label = labelFor(lastUnit, value);
|
|
98
|
-
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
99
|
-
return `${value} ${label}${suffix ? ` ${suffix}` : ''}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ---------- โหมด multi-unit ----------
|
|
103
|
-
// เก็บค่าแบบตัวเลขก่อน แล้วค่อยเรนเดอร์
|
|
104
|
-
const values = units.map((unit) => ({
|
|
105
|
-
unit,
|
|
106
|
-
raw: Math.max(0, Math.floor(diffObj[unit] ?? 0)),
|
|
107
|
-
}));
|
|
108
|
-
|
|
109
|
-
// เลือกหน่วยที่เล็กที่สุด
|
|
110
|
-
const smallest = values[values.length - 1];
|
|
111
|
-
|
|
112
|
-
// ถ้ามีค่าอย่างน้อยหนึ่งหน่วย > 0 และเป็น inclusive (zeroBase=false) => +1 ที่หน่วยเล็กสุด
|
|
113
|
-
const anyPositive = values.some((v) => v.raw > 0);
|
|
114
|
-
const adjusted = values.map((v, idx) => {
|
|
115
|
-
if (!props.zeroBase && anyPositive && idx === values.length - 1) {
|
|
116
|
-
return { ...v, raw: v.raw + 1 };
|
|
117
|
-
}
|
|
118
|
-
return v;
|
|
119
|
-
});
|
|
26
|
+
let days = Math.floor(base.diff(props.modelValue, "days").days ?? 0);
|
|
120
27
|
|
|
121
|
-
|
|
122
|
-
const parts = adjusted
|
|
123
|
-
.filter((v) => v.raw > 0)
|
|
124
|
-
.map((v) => `${v.raw} ${labelFor(v.unit, v.raw)}`);
|
|
28
|
+
if (!props.zeroBase) days += 1;
|
|
125
29
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const minValue = props.zeroBase ? 0 : 1;
|
|
129
|
-
const label = labelFor(smallest.unit, minValue);
|
|
130
|
-
const suffix = props.showSuffix ? localizedSuffix[baseLocale] : '';
|
|
131
|
-
return `${minValue} ${label}${suffix ? ` ${suffix}` : ''}`;
|
|
30
|
+
if (baseLocale === "en") {
|
|
31
|
+
return days === 1 ? "1 day" : `${days} days`;
|
|
132
32
|
}
|
|
133
33
|
|
|
134
|
-
|
|
135
|
-
return `${parts.join(' ')}${suffix ? ` ${suffix}` : ''}`;
|
|
34
|
+
return `${days} วัน`;
|
|
136
35
|
});
|
|
137
36
|
</script>
|
|
138
37
|
|
|
139
38
|
<template>
|
|
140
|
-
<span>
|
|
141
|
-
<i v-if="props.zeroBase" class="mdi mdi-clock-time-twelve-outline"></i>
|
|
142
|
-
<span v-if="props.zeroBase"> </span>{{ outputText }}
|
|
143
|
-
</span>
|
|
39
|
+
<span>{{ countDate }}</span>
|
|
144
40
|
</template>
|
|
@@ -15,12 +15,12 @@ interface Props extends /* @vue-ignore */ InstanceType<typeof PDF['$props']> {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
-
base64String:
|
|
19
|
-
title:
|
|
20
|
-
fileName:
|
|
18
|
+
base64String: "",
|
|
19
|
+
title: "",
|
|
20
|
+
fileName: "",
|
|
21
21
|
disabled: false,
|
|
22
22
|
isPrint: false,
|
|
23
|
-
showBackToTopBtn: false
|
|
23
|
+
showBackToTopBtn: false
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
const emit = defineEmits(['closeDialog'])
|
|
@@ -33,34 +33,25 @@ const generateUniqueId = (): string => {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const downloadPdf = (): void => {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const byteString = atob(props.base64String || '')
|
|
37
|
+
const byteArray = new Uint8Array(byteString.length)
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
for (let i = 0; i < byteString.length; i++) {
|
|
43
|
-
byteArray[i] = byteString.charCodeAt(i)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const blob = new Blob([byteArray], { type: 'application/pdf' })
|
|
47
|
-
const link = URL.createObjectURL(blob)
|
|
48
|
-
const anchorElement = document.createElement('a')
|
|
49
|
-
anchorElement.style.display = 'none'
|
|
50
|
-
anchorElement.href = link
|
|
51
|
-
anchorElement.download = `${generateUniqueId()}.pdf`
|
|
52
|
-
|
|
53
|
-
document.body.appendChild(anchorElement)
|
|
54
|
-
anchorElement.click()
|
|
55
|
-
URL.revokeObjectURL(link)
|
|
56
|
-
document.body.removeChild(anchorElement)
|
|
57
|
-
base64.value = ''
|
|
58
|
-
|
|
59
|
-
alert?.addAlert({ message: 'Download success', alertType: 'success' })
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
alert?.addAlert({ message: `Download unsuccess : ${error}`, alertType: 'error' })
|
|
39
|
+
for (let i = 0; i < byteString.length; i++) {
|
|
40
|
+
byteArray[i] = byteString.charCodeAt(i)
|
|
63
41
|
}
|
|
42
|
+
|
|
43
|
+
const blob = new Blob([byteArray], { type: 'application/pdf' })
|
|
44
|
+
const link = URL.createObjectURL(blob)
|
|
45
|
+
const anchorElement = document.createElement('a')
|
|
46
|
+
anchorElement.style.display = 'none'
|
|
47
|
+
anchorElement.href = link
|
|
48
|
+
anchorElement.download = `${generateUniqueId()}.pdf`
|
|
49
|
+
|
|
50
|
+
document.body.appendChild(anchorElement)
|
|
51
|
+
anchorElement.click()
|
|
52
|
+
URL.revokeObjectURL(link)
|
|
53
|
+
document.body.removeChild(anchorElement)
|
|
54
|
+
base64.value = ''
|
|
64
55
|
}
|
|
65
56
|
|
|
66
57
|
const printPdf = () => {
|
|
@@ -69,7 +60,7 @@ const printPdf = () => {
|
|
|
69
60
|
type: 'pdf',
|
|
70
61
|
base64: true,
|
|
71
62
|
onPrintDialogClose: endLoadPdf,
|
|
72
|
-
onError: (error) => {
|
|
63
|
+
onError: (error: any) => {
|
|
73
64
|
alert?.addAlert({ message: error, alertType: 'error' })
|
|
74
65
|
},
|
|
75
66
|
})
|
|
@@ -82,19 +73,18 @@ const endLoadPdf = () => {
|
|
|
82
73
|
}
|
|
83
74
|
|
|
84
75
|
const isMobile = () => {
|
|
85
|
-
return /Android|Mobi|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Macintosh/i.test(navigator.userAgent)
|
|
76
|
+
return /Android|Mobi|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Macintosh/i.test(navigator.userAgent);
|
|
86
77
|
}
|
|
87
78
|
|
|
88
79
|
const checkMobileAndPrint = computed(() => {
|
|
89
|
-
return !isMobile() && props.isPrint
|
|
90
|
-
})
|
|
80
|
+
return !isMobile() && props.isPrint;
|
|
81
|
+
});
|
|
91
82
|
|
|
92
83
|
const setWidthPdf = computed(() => {
|
|
93
84
|
if (isMobile()) {
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return '100dvh'
|
|
85
|
+
return "100%"
|
|
86
|
+
} else {
|
|
87
|
+
return "100dvh"
|
|
98
88
|
}
|
|
99
89
|
})
|
|
100
90
|
</script>
|
|
@@ -124,10 +114,10 @@ const setWidthPdf = computed(() => {
|
|
|
124
114
|
</v-toolbar>
|
|
125
115
|
<v-card-text class="justify-center h-screen">
|
|
126
116
|
<PDF
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
117
|
+
v-bind="$attrs"
|
|
118
|
+
:pdf-width="setWidthPdf"
|
|
119
|
+
:src="base64"
|
|
120
|
+
:show-back-to-top-btn="props.showBackToTopBtn"
|
|
131
121
|
/>
|
|
132
122
|
</v-card-text>
|
|
133
123
|
</v-card>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Core format you wanted to standardize around */
|
|
2
|
+
export interface Base64String {
|
|
3
|
+
base64String?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Base64Asset extends Base64String {
|
|
6
|
+
id?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Base64File extends Base64Asset {
|
|
9
|
+
/** Display or server filename; when converting from a File, we use File.name */
|
|
10
|
+
fileName: string;
|
|
11
|
+
originalFileName?: string;
|
|
12
|
+
/** Parsed or inferred MIME type */
|
|
13
|
+
fileType?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface Base64Image {
|
|
16
|
+
imageData: Base64Asset;
|
|
17
|
+
imageTitle?: string;
|
|
18
|
+
imageProps?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/** Ensure we have a well-formed data URL; if raw base64 is provided, wrap it */
|
|
21
|
+
declare function ensureDataUrl(base64Data: string, contentType?: string): string;
|
|
22
|
+
export declare const useAssetFile: () => {
|
|
23
|
+
hydrateAssetFile: <T extends Base64Asset>(assetFile: T) => Promise<T>;
|
|
24
|
+
base64ToFile: (base64Data: string, filename: string, defaultContentType?: string) => File | undefined;
|
|
25
|
+
fileToBase64: (file: File, maxFileSizeMB?: number) => Promise<Base64File>;
|
|
26
|
+
fileToBase64String: (file: File, maxFileSizeMB?: number) => Promise<Base64String>;
|
|
27
|
+
downloadBase64File: (base64Data: string, filename: string) => void;
|
|
28
|
+
downloadAsset: (input: string | Base64File | Base64Asset, filename?: string, defaultContentType?: string) => void;
|
|
29
|
+
ensureDataUrl: typeof ensureDataUrl;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useGraphQlOperation } from "./graphqlOperation.js";
|
|
2
|
+
const DATA_URL_RE = /^data:([^;]+);base64,(.+)$/i;
|
|
3
|
+
function parseDataUrl(input) {
|
|
4
|
+
const match = input.match(DATA_URL_RE);
|
|
5
|
+
if (!match) return null;
|
|
6
|
+
const [, contentType, payload] = match;
|
|
7
|
+
return { contentType, payload };
|
|
8
|
+
}
|
|
9
|
+
function base64ToBytes(base64Payload) {
|
|
10
|
+
const normalized = base64Payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
11
|
+
const binaryStr = atob(normalized);
|
|
12
|
+
const len = binaryStr.length;
|
|
13
|
+
const bytes = new Uint8Array(len);
|
|
14
|
+
const CHUNK = 32768;
|
|
15
|
+
for (let i = 0; i < len; i += CHUNK) {
|
|
16
|
+
const end = Math.min(i + CHUNK, len);
|
|
17
|
+
for (let j = i; j < end; j++) {
|
|
18
|
+
bytes[j] = binaryStr.charCodeAt(j);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return bytes;
|
|
22
|
+
}
|
|
23
|
+
function bytesToFile(bytes, filename, contentType) {
|
|
24
|
+
try {
|
|
25
|
+
return new File([bytes], filename, { type: contentType });
|
|
26
|
+
} catch {
|
|
27
|
+
return new Blob([bytes], { type: contentType });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function ensureDataUrl(base64Data, contentType = "application/octet-stream") {
|
|
31
|
+
if (DATA_URL_RE.test(base64Data)) return base64Data;
|
|
32
|
+
return `data:${contentType};base64,${base64Data}`;
|
|
33
|
+
}
|
|
34
|
+
function extensionFromMime(mime) {
|
|
35
|
+
const map = {
|
|
36
|
+
"image/png": "png",
|
|
37
|
+
"image/jpeg": "jpg",
|
|
38
|
+
"image/jpg": "jpg",
|
|
39
|
+
"image/gif": "gif",
|
|
40
|
+
"image/webp": "webp",
|
|
41
|
+
"application/pdf": "pdf",
|
|
42
|
+
"text/plain": "txt"
|
|
43
|
+
};
|
|
44
|
+
return map[mime];
|
|
45
|
+
}
|
|
46
|
+
export const useAssetFile = () => {
|
|
47
|
+
async function hydrateAssetFile(assetFile) {
|
|
48
|
+
if (!assetFile?.base64String && assetFile?.id != null) {
|
|
49
|
+
const result = await useGraphQlOperation(
|
|
50
|
+
"Query",
|
|
51
|
+
"assetFileById",
|
|
52
|
+
["id", "base64String"],
|
|
53
|
+
{ id: assetFile.id },
|
|
54
|
+
false
|
|
55
|
+
);
|
|
56
|
+
if (result?.base64String) assetFile.base64String = result.base64String;
|
|
57
|
+
}
|
|
58
|
+
return assetFile;
|
|
59
|
+
}
|
|
60
|
+
function base64ToFile(base64Data, filename, defaultContentType = "application/octet-stream") {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = parseDataUrl(base64Data);
|
|
63
|
+
const contentType = parsed?.contentType ?? defaultContentType;
|
|
64
|
+
const payload = parsed?.payload ?? base64Data;
|
|
65
|
+
const bytes = base64ToBytes(payload);
|
|
66
|
+
return bytesToFile(bytes, filename, contentType);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error("Invalid base64 data", error);
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function fileToBase64(file, maxFileSizeMB = 5) {
|
|
73
|
+
const maxSize = maxFileSizeMB * 1048576;
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
if (file.size > maxSize) {
|
|
76
|
+
reject(`File (${file.name}) size exceeds the ${maxFileSizeMB} MB limit.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const reader = new FileReader();
|
|
80
|
+
reader.onload = (event) => {
|
|
81
|
+
const dataUrl = event.target?.result;
|
|
82
|
+
resolve({
|
|
83
|
+
fileName: file.name,
|
|
84
|
+
originalFileName: file.name,
|
|
85
|
+
base64String: dataUrl,
|
|
86
|
+
fileType: file.type || parseDataUrl(dataUrl)?.contentType
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
reader.onerror = reject;
|
|
90
|
+
reader.readAsDataURL(file);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function fileToBase64String(file, maxFileSizeMB = 5) {
|
|
94
|
+
const res = await fileToBase64(file, maxFileSizeMB);
|
|
95
|
+
return { base64String: res.base64String };
|
|
96
|
+
}
|
|
97
|
+
function downloadBase64File(base64Data, filename) {
|
|
98
|
+
const file = base64ToFile(base64Data, filename);
|
|
99
|
+
if (!file) return;
|
|
100
|
+
const url = URL.createObjectURL(file);
|
|
101
|
+
const link = document.createElement("a");
|
|
102
|
+
link.href = url;
|
|
103
|
+
link.download = filename;
|
|
104
|
+
document.body.appendChild(link);
|
|
105
|
+
link.click();
|
|
106
|
+
document.body.removeChild(link);
|
|
107
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
108
|
+
}
|
|
109
|
+
function downloadAsset(input, filename, defaultContentType = "application/octet-stream") {
|
|
110
|
+
let base64 = typeof input === "string" ? input : input.base64String;
|
|
111
|
+
if (!base64) return;
|
|
112
|
+
if (!filename) {
|
|
113
|
+
const parsed2 = parseDataUrl(base64);
|
|
114
|
+
const ext = parsed2 ? extensionFromMime(parsed2.contentType) : void 0;
|
|
115
|
+
filename = typeof input !== "string" && input.fileName || `download${ext ? "." + ext : ""}`;
|
|
116
|
+
}
|
|
117
|
+
const parsed = parseDataUrl(base64);
|
|
118
|
+
if (!parsed) {
|
|
119
|
+
base64 = ensureDataUrl(base64, defaultContentType);
|
|
120
|
+
}
|
|
121
|
+
downloadBase64File(base64, filename);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
// core
|
|
125
|
+
hydrateAssetFile,
|
|
126
|
+
base64ToFile,
|
|
127
|
+
fileToBase64,
|
|
128
|
+
fileToBase64String,
|
|
129
|
+
downloadBase64File,
|
|
130
|
+
downloadAsset,
|
|
131
|
+
// helpers (exported in case you want them elsewhere)
|
|
132
|
+
ensureDataUrl
|
|
133
|
+
};
|
|
134
|
+
};
|
|
@@ -34,16 +34,13 @@ function guessWidth(width) {
|
|
|
34
34
|
export function processTemplateFormTable(item, parentTemplates, dataVariable) {
|
|
35
35
|
let tableOptions = Object.assign({ title: item.inputLabel || "", formTemplate: "" }, item.inputOptions);
|
|
36
36
|
let tableHeader = tableOptions.headers || [];
|
|
37
|
+
if (!tableHeader.some((h) => h.key === "action")) tableHeader.push({ title: "Action", key: "action", width: "100px" });
|
|
37
38
|
let tableItemTemplate = "";
|
|
38
39
|
tableHeader.forEach((h) => {
|
|
39
40
|
if (h.template) {
|
|
40
41
|
tableItemTemplate += `\r
|
|
41
42
|
<template #item.${h.key}="props">${h.template}</template>`;
|
|
42
|
-
} else {
|
|
43
|
-
tableItemTemplate += `\r
|
|
44
|
-
<template #item.${h.key}="props">{{autoSanitizedDisplay(props.item.${h.key})}}</template>`;
|
|
45
43
|
}
|
|
46
44
|
});
|
|
47
|
-
if (!tableHeader.some((h) => h.key === "action")) tableHeader.push({ title: "Action", key: "action", width: "100px" });
|
|
48
45
|
return processDefaultTemplate(item, `<template #form="{data,rules}">${useDocumentTemplate(tableOptions.formTemplate)}</template>${tableItemTemplate}`, `title="${tableOptions.title}" :headers='${escapeObjectForInlineBinding(tableHeader)}'`, void 0, dataVariable);
|
|
49
46
|
}
|