tantee-nuxt-commons 0.0.169 → 0.0.170

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.
@@ -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
- try {
37
- if (!props.base64String) alert?.addAlert({ message: 'No Base64 provided', alertType: 'error' })
36
+ const byteString = atob(props.base64String || '')
37
+ const byteArray = new Uint8Array(byteString.length)
38
38
 
39
- const byteString = atob(props.base64String || '')
40
- const byteArray = new Uint8Array(byteString.length)
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 '100%'
95
- }
96
- else {
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
- v-bind="$attrs"
128
- :pdf-width="setWidthPdf"
129
- :src="base64"
130
- :show-back-to-top-btn="props.showBackToTopBtn"
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tantee-nuxt-commons",
3
- "version": "0.0.169",
3
+ "version": "0.0.170",
4
4
  "description": "Ramathibodi Nuxt modules for common components",
5
5
  "repository": {
6
6
  "type": "git",