diff --git a/apps/documentation/pages/docs/design/components/form.mdx b/apps/documentation/pages/docs/design/components/form.mdx index c8a012fc..0db29b68 100644 --- a/apps/documentation/pages/docs/design/components/form.mdx +++ b/apps/documentation/pages/docs/design/components/form.mdx @@ -94,7 +94,9 @@ This property is used to render the fields in the form. Fields can be passed as | arrayOptions.defaultInstances | `false` | `null` | `number` | Sets a default number of instances for fields of type `array` Only applies to fields of type `array` | | arrayOptions.max | `false` | `null` | `number` | Sets a maximum number of instances for fields of type `array` Only applies to fields of type `array` | | arrayOptions.min | `false` | `null` | `number` | Sets a minimum number of instances for fields of type `array` Only applies to fields of type `array` arrayOptions.defaultInstances must also be set and must be a number greater than arrayOptions.min | -| groupOptions.collapsible | `false` | `null` | `number` | Determines if a field of type `group` will be collapsible | +| groupOptions.collapsible | `false` | `null` | `number` +| fileOptions.protocol | `false` | `native`| `string` | Sets a minimum number of instances for fields of type `array` Only applies to fields of type `array` arrayOptions.defaultInstances must also be set and must be a number greater than arrayOptions.min | +| fileOptions.tusOptions | `false` | `null` | `Object ({ endpoint: string })` | Determines if a field of type `group` will be collapsible | ## Events @@ -255,6 +257,17 @@ const fields: Fields[] = [ tooltip: 'Your ID document', }, }, + { + key: "id2", + label: "ID", + type: "file", + fileOptions: { + protocol: "tus", + tusOptions: { + endpoint: "https://tusd.tusdemo.net/files/", + }, + } + }, ]; `` diff --git a/package-lock.json b/package-lock.json index 9d642303..ea97f8f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1154,6 +1154,23 @@ "node": ">=6.0.0" } }, + "node_modules/@anurag_gupta/tus-js-client": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@anurag_gupta/tus-js-client/-/tus-js-client-4.2.10.tgz", + "integrity": "sha512-BMhsiHR3TF0NCADI7k+HYzM+jas4Gj9BrUNsWLDtrjWEC5z2etQGaok+vNHIMRy01TiECH3KpTjrP6jKhAEKkw==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.1.2", + "combine-errors": "^3.0.3", + "is-stream": "^2.0.0", + "js-base64": "^3.7.2", + "lodash.throttle": "^4.1.1", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", @@ -8675,8 +8692,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/builtin-modules": { "version": "3.3.0", @@ -9387,6 +9403,15 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==", + "dependencies": { + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -10192,6 +10217,12 @@ "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==", "dev": true }, + "node_modules/custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==", + "license": "ISC" + }, "node_modules/cytoscape": { "version": "3.28.1", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.28.1.tgz", @@ -15514,7 +15545,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -15800,6 +15830,12 @@ "node": ">=10" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "license": "BSD-3-Clause" + }, "node_modules/js-levenshtein-esm": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", @@ -16321,6 +16357,52 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==", + "license": "MIT", + "dependencies": { + "lodash._stringtopath": "~4.8.0" + } + }, + "node_modules/lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==", + "license": "MIT" + }, + "node_modules/lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==", + "license": "MIT", + "dependencies": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "node_modules/lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==", + "license": "MIT" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", + "license": "MIT" + }, + "node_modules/lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==", + "license": "MIT", + "dependencies": { + "lodash._basetostring": "~4.12.0" + } + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -16350,12 +16432,28 @@ "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, + "node_modules/lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==", + "license": "MIT", + "dependencies": { + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" + } + }, "node_modules/log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -19930,6 +20028,26 @@ "react-is": "^16.13.1" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/property-information": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", @@ -28767,6 +28885,7 @@ "version": "0.1.1", "license": "Apache-2.0", "dependencies": { + "@anurag_gupta/tus-js-client": "^4.2.10", "@shoelace-style/shoelace": "^2.8.0", "ace-builds": "^1.35.0", "lit": "^2.8.0", diff --git a/packages/ecc-utils-design/demo/form/index.html b/packages/ecc-utils-design/demo/form/index.html index 2b2464da..a0abc3d0 100644 --- a/packages/ecc-utils-design/demo/form/index.html +++ b/packages/ecc-utils-design/demo/form/index.html @@ -134,6 +134,17 @@ tooltip: "Your ID document", }, }, + { + key: "id2", + label: "ID", + type: "file", + fileOptions: { + protocol: "tus", + tusOptions: { + endpoint: "https://tusd.tusdemo.net/files/", + }, + }, + }, { key: "gender", label: "Gender", diff --git a/packages/ecc-utils-design/package.json b/packages/ecc-utils-design/package.json index a0a7003c..2244270a 100644 --- a/packages/ecc-utils-design/package.json +++ b/packages/ecc-utils-design/package.json @@ -66,6 +66,7 @@ "typescript": "*" }, "dependencies": { + "@anurag_gupta/tus-js-client": "^4.2.10", "@shoelace-style/shoelace": "^2.8.0", "ace-builds": "^1.35.0", "lit": "^2.8.0", diff --git a/packages/ecc-utils-design/src/components/form/form.styles.ts b/packages/ecc-utils-design/src/components/form/form.styles.ts index 39a368d4..0a429b8e 100644 --- a/packages/ecc-utils-design/src/components/form/form.styles.ts +++ b/packages/ecc-utils-design/src/components/form/form.styles.ts @@ -167,6 +167,21 @@ const styles = css` box-shadow: 0 0 0 var(--ecc-focus-ring-width) var(--ecc-input-focus-ring-color); } + .progress-bar-container { + width: 100%; + height: 20px; + background-color: #e0e0e0; + border-radius: 5px; + overflow: hidden; + } + .progress-bar { + height: 100%; + background-color: #76c7c0; + transition: width 0.2s ease; + } + .upload-percentage { + text-align: center; + } /* Submit Button */ .submit-button { margin-top: var(--ecc-spacing-large); diff --git a/packages/ecc-utils-design/src/components/form/form.ts b/packages/ecc-utils-design/src/components/form/form.ts index 6e3a0ac8..15c3dfe7 100644 --- a/packages/ecc-utils-design/src/components/form/form.ts +++ b/packages/ecc-utils-design/src/components/form/form.ts @@ -51,6 +51,12 @@ export interface Field { groupOptions?: { collapsible: boolean; }; + fileOptions?: { + protocol?: "native" | "tus"; + tusOptions?: { + endpoint: string; + }; + }; error?: string; children?: Array; } @@ -151,6 +157,60 @@ export default class EccUtilsDesignForm extends LitElement { `; } + private uploadPercentage = 0; + + private handleTusFileUpload = async ( + e: Event, + field: Field + ): Promise => { + const file = (e.target as HTMLInputElement).files?.[0]; + + if (!file) { + console.error("No file selected for upload."); + return; + } + + try { + const { Upload } = await import("@anurag_gupta/tus-js-client"); + + const upload = new Upload(file, { + endpoint: field.fileOptions?.tusOptions?.endpoint, + retryDelays: [0, 3000, 5000, 10000, 20000], + metadata: { + filename: file.name, + filetype: file.type, + }, + onError: (error) => { + console.error(`Upload failed because: ${error.message}`); + }, + onProgress: (bytesUploaded, bytesTotal) => { + const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); + this.uploadPercentage = Number(percentage); + console.log( + `Uploaded: ${bytesUploaded} bytes of ${bytesTotal} bytes (${percentage}%)` + ); + this.requestUpdate(); + }, + onSuccess: () => { + if ("name" in upload.file) { + console.log("Download %s from %s", upload.file.name, upload.url); + } else { + console.log("Download file from %s", upload.url); + } + }, + }); + + const previousUploads = await upload.findPreviousUploads(); + if (previousUploads.length > 0) { + upload.resumeFromPreviousUpload(previousUploads[0]); + } + + upload.start(); + } catch (error) { + console.error("An error occurred while initializing the upload:", error); + } + }; + renderInputTemplate(field: Field, path: string): TemplateResult { if ( field.type === "array" || @@ -179,21 +239,43 @@ export default class EccUtilsDesignForm extends LitElement { ${field.label} ${field.fieldOptions?.required ? "*" : ""} `} - { - const { files } = e.target as HTMLInputElement; - _.set(this.form, path, files); - this.requestUpdate(); - this.alertFieldChange(field.key, files); - }} - /> + ${field.fileOptions?.protocol === "tus" && + html` + { + await this.handleTusFileUpload(e, field); + }} + /> +
+
+
+
+ ${this.uploadPercentage.toFixed(2)}% +
+ `} + ${(!field.fileOptions?.protocol || + field.fileOptions?.protocol === "native") && + html` + { + const { files } = e.target as HTMLInputElement; + _.set(this.form, path, files); + this.requestUpdate(); + }} + /> + `} `; } diff --git a/scripts/build.js b/scripts/build.js index 048233b5..5edcbd2b 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -114,6 +114,7 @@ nextTask("Building source", async () => { const config = { format: "esm", target: "es2017", + platform: "browser", entry: [ normalizePath(`${sourceDir}/index.ts`), ...(await fg(normalizePath(`${sourceDir}/components/**/!(*.(test)).ts`), { @@ -134,6 +135,7 @@ nextTask("Building source", async () => { ...config, esbuildOptions(buildOptions) { buildOptions.chunkNames = "chunks/[name].[hash]"; + buildOptions.platform = "browser"; }, }) .catch((err) => {