@nuralyui/file-upload 0.0.1 → 0.0.2
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/demo/file-demo.d.ts +18 -0
- package/demo/file-demo.d.ts.map +1 -0
- package/demo/file-demo.js +157 -0
- package/demo/file-demo.js.map +1 -0
- package/file.component.d.ts +36 -0
- package/file.component.d.ts.map +1 -0
- package/file.component.js +327 -0
- package/file.component.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2023 Google Laabidi Aymen
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { LitElement } from 'lit';
|
|
7
|
+
import "../file-upload.component";
|
|
8
|
+
export declare class ElMeenuElement extends LitElement {
|
|
9
|
+
float: string;
|
|
10
|
+
uploadProcesses: Map<string, number>;
|
|
11
|
+
firstUpdated(): void;
|
|
12
|
+
private _handleFileSelect;
|
|
13
|
+
private _handleFileRemove;
|
|
14
|
+
private _handleFileExceed;
|
|
15
|
+
protected render(): import("lit").TemplateResult<1>;
|
|
16
|
+
static styles: import("lit").CSSResult[];
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=file-demo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-demo.d.ts","sourceRoot":"","sources":["../../../../src/components/file-upload/demo/file-demo.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,0BAA0B,CAAC;AAGlC,qBACa,cAAe,SAAQ,UAAU;IACnC,KAAK,SAAU;IACf,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAEjD,YAAY;IAYrB,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,iBAAiB;cAMN,MAAM;IA0BzB,OAAgB,MAAM,4BA6CpB;CACH"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
|
+
/**
|
|
9
|
+
* @license
|
|
10
|
+
* Copyright 2023 Google Laabidi Aymen
|
|
11
|
+
* SPDX-License-Identifier: MIT
|
|
12
|
+
*/
|
|
13
|
+
import { LitElement, css, html } from 'lit';
|
|
14
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
15
|
+
import "../file-upload.component";
|
|
16
|
+
let ElMeenuElement = class ElMeenuElement extends LitElement {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.float = 'left';
|
|
20
|
+
this.uploadProcesses = new Map();
|
|
21
|
+
}
|
|
22
|
+
firstUpdated() {
|
|
23
|
+
var _a;
|
|
24
|
+
// Get reference to the file upload component
|
|
25
|
+
const fileUpload = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('nr-file-upload');
|
|
26
|
+
if (fileUpload) {
|
|
27
|
+
// Add event listeners to the component
|
|
28
|
+
fileUpload.addEventListener('file-select', this._handleFileSelect.bind(this));
|
|
29
|
+
fileUpload.addEventListener('file-remove', this._handleFileRemove.bind(this));
|
|
30
|
+
fileUpload.addEventListener('file-exceed', this._handleFileExceed.bind(this));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
_handleFileSelect(event) {
|
|
34
|
+
var _a;
|
|
35
|
+
const { files } = event.detail;
|
|
36
|
+
const fileUpload = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('nr-file-upload');
|
|
37
|
+
if (!fileUpload)
|
|
38
|
+
return;
|
|
39
|
+
files.forEach((file) => {
|
|
40
|
+
// Update status to 'uploading'
|
|
41
|
+
fileUpload.updateFileStatus(file.uid, 'uploading', 0);
|
|
42
|
+
// Simulate upload process with interval
|
|
43
|
+
const intervalId = window.setInterval(() => {
|
|
44
|
+
const currentPercentage = file.percentage || 0;
|
|
45
|
+
const percentage = Math.min(100, currentPercentage + 10);
|
|
46
|
+
fileUpload.updateFileStatus(file.uid, 'uploading', percentage);
|
|
47
|
+
if (percentage === 100) {
|
|
48
|
+
window.clearInterval(intervalId);
|
|
49
|
+
// Set file as successfully uploaded after a small delay
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
fileUpload.updateFileStatus(file.uid, 'success');
|
|
52
|
+
}, 300);
|
|
53
|
+
// Remove from tracking
|
|
54
|
+
this.uploadProcesses.delete(file.uid);
|
|
55
|
+
}
|
|
56
|
+
}, 500);
|
|
57
|
+
// Store the interval ID for potential cancellation
|
|
58
|
+
this.uploadProcesses.set(file.uid, intervalId);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
_handleFileRemove(event) {
|
|
62
|
+
const { file } = event.detail;
|
|
63
|
+
console.log(`File removed: ${file.name}`);
|
|
64
|
+
// Cancel upload process if it's still running
|
|
65
|
+
if (this.uploadProcesses.has(file.uid)) {
|
|
66
|
+
window.clearInterval(this.uploadProcesses.get(file.uid));
|
|
67
|
+
this.uploadProcesses.delete(file.uid);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
_handleFileExceed(event) {
|
|
71
|
+
const { files } = event.detail;
|
|
72
|
+
console.warn(`Upload limit exceeded. Tried to upload ${files.length} additional files.`);
|
|
73
|
+
// Could show a notification or alert here
|
|
74
|
+
}
|
|
75
|
+
render() {
|
|
76
|
+
return html `
|
|
77
|
+
<div class="demo-container">
|
|
78
|
+
<h2>File Upload Demo</h2>
|
|
79
|
+
<nr-file-upload
|
|
80
|
+
accept="image/*"
|
|
81
|
+
multiple
|
|
82
|
+
drag
|
|
83
|
+
limit="5"
|
|
84
|
+
tip="JPG/PNG files up to 500kb">
|
|
85
|
+
</nr-file-upload>
|
|
86
|
+
|
|
87
|
+
<div class="usage-info">
|
|
88
|
+
<h3>Usage:</h3>
|
|
89
|
+
<ul>
|
|
90
|
+
<li>Drag & drop files here</li>
|
|
91
|
+
<li>Or click to select files</li>
|
|
92
|
+
<li>Files will automatically start uploading</li>
|
|
93
|
+
<li>Use the X button to remove files</li>
|
|
94
|
+
<li>Click on eye icon to preview images</li>
|
|
95
|
+
</ul>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
ElMeenuElement.styles = [
|
|
102
|
+
css `
|
|
103
|
+
:host {
|
|
104
|
+
width: 800px;
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
align-items: center;
|
|
108
|
+
font-family: sans-serif;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.demo-container {
|
|
112
|
+
width: 100%;
|
|
113
|
+
max-width: 700px;
|
|
114
|
+
padding: 20px;
|
|
115
|
+
border-radius: 8px;
|
|
116
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
117
|
+
background-color: #fff;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
h2 {
|
|
121
|
+
color: #333;
|
|
122
|
+
margin-top: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.usage-info {
|
|
126
|
+
margin-top: 30px;
|
|
127
|
+
padding: 15px;
|
|
128
|
+
background-color: #f5f5f5;
|
|
129
|
+
border-radius: 6px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.usage-info h3 {
|
|
133
|
+
margin-top: 0;
|
|
134
|
+
color: #555;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ul {
|
|
138
|
+
padding-left: 20px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
li {
|
|
142
|
+
margin-bottom: 8px;
|
|
143
|
+
color: #666;
|
|
144
|
+
}
|
|
145
|
+
`,
|
|
146
|
+
];
|
|
147
|
+
__decorate([
|
|
148
|
+
state()
|
|
149
|
+
], ElMeenuElement.prototype, "float", void 0);
|
|
150
|
+
__decorate([
|
|
151
|
+
state()
|
|
152
|
+
], ElMeenuElement.prototype, "uploadProcesses", void 0);
|
|
153
|
+
ElMeenuElement = __decorate([
|
|
154
|
+
customElement('hy-file-demo')
|
|
155
|
+
], ElMeenuElement);
|
|
156
|
+
export { ElMeenuElement };
|
|
157
|
+
//# sourceMappingURL=file-demo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-demo.js","sourceRoot":"","sources":["../../../../src/components/file-upload/demo/file-demo.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * @license\n * Copyright 2023 Google Laabidi Aymen\n * SPDX-License-Identifier: MIT\n */\nimport { LitElement, css, html } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\nimport \"../file-upload.component\";\nimport { UploadFile } from '../types';\n\n@customElement('hy-file-demo')\nexport class ElMeenuElement extends LitElement {\n @state() float = 'left';\n @state() uploadProcesses: Map<string, number> = new Map();\n\n override firstUpdated() {\n // Get reference to the file upload component\n const fileUpload : any = this.shadowRoot?.querySelector('nr-file-upload');\n \n if (fileUpload) {\n // Add event listeners to the component\n fileUpload.addEventListener('file-select', this._handleFileSelect.bind(this));\n fileUpload.addEventListener('file-remove', this._handleFileRemove.bind(this));\n fileUpload.addEventListener('file-exceed', this._handleFileExceed.bind(this));\n }\n }\n\n private _handleFileSelect(event: CustomEvent) {\n const { files } = event.detail;\n const fileUpload : any= this.shadowRoot?.querySelector('nr-file-upload');\n \n if (!fileUpload) return;\n \n files.forEach((file: UploadFile) => {\n // Update status to 'uploading'\n fileUpload.updateFileStatus(file.uid, 'uploading', 0);\n \n // Simulate upload process with interval\n const intervalId = window.setInterval(() => {\n const currentPercentage = file.percentage || 0;\n const percentage = Math.min(100, currentPercentage + 10);\n \n fileUpload.updateFileStatus(file.uid, 'uploading', percentage);\n \n if (percentage === 100) {\n window.clearInterval(intervalId);\n // Set file as successfully uploaded after a small delay\n setTimeout(() => {\n fileUpload.updateFileStatus(file.uid, 'success');\n }, 300);\n \n // Remove from tracking\n this.uploadProcesses.delete(file.uid);\n }\n }, 500);\n \n // Store the interval ID for potential cancellation\n this.uploadProcesses.set(file.uid, intervalId);\n });\n }\n\n private _handleFileRemove(event: CustomEvent) {\n const { file } = event.detail;\n console.log(`File removed: ${file.name}`);\n \n // Cancel upload process if it's still running\n if (this.uploadProcesses.has(file.uid)) {\n window.clearInterval(this.uploadProcesses.get(file.uid));\n this.uploadProcesses.delete(file.uid);\n }\n }\n\n private _handleFileExceed(event: CustomEvent) {\n const { files } = event.detail;\n console.warn(`Upload limit exceeded. Tried to upload ${files.length} additional files.`);\n // Could show a notification or alert here\n }\n\n protected override render() {\n return html`\n <div class=\"demo-container\">\n <h2>File Upload Demo</h2>\n <nr-file-upload\n accept=\"image/*\"\n multiple\n drag\n limit=\"5\"\n tip=\"JPG/PNG files up to 500kb\">\n </nr-file-upload>\n \n <div class=\"usage-info\">\n <h3>Usage:</h3>\n <ul>\n <li>Drag & drop files here</li>\n <li>Or click to select files</li>\n <li>Files will automatically start uploading</li>\n <li>Use the X button to remove files</li>\n <li>Click on eye icon to preview images</li>\n </ul>\n </div>\n </div>\n `;\n }\n\n static override styles = [\n css`\n :host {\n width: 800px;\n display: flex;\n flex-direction: column;\n align-items: center;\n font-family: sans-serif;\n }\n \n .demo-container {\n width: 100%;\n max-width: 700px;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n background-color: #fff;\n }\n \n h2 {\n color: #333;\n margin-top: 0;\n }\n \n .usage-info {\n margin-top: 30px;\n padding: 15px;\n background-color: #f5f5f5;\n border-radius: 6px;\n }\n \n .usage-info h3 {\n margin-top: 0;\n color: #555;\n }\n \n ul {\n padding-left: 20px;\n }\n \n li {\n margin-bottom: 8px;\n color: #666;\n }\n `,\n ];\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LitElement, PropertyValues } from 'lit';
|
|
2
|
+
import { UploadFile } from './types';
|
|
3
|
+
export declare class FileUpload extends LitElement {
|
|
4
|
+
static styles: import("lit").CSSResult;
|
|
5
|
+
accept: string;
|
|
6
|
+
multiple: boolean;
|
|
7
|
+
drag: boolean;
|
|
8
|
+
tip: string;
|
|
9
|
+
limit: number;
|
|
10
|
+
preview: boolean;
|
|
11
|
+
fileList: UploadFile[];
|
|
12
|
+
isDragOver: boolean;
|
|
13
|
+
showDragArea: boolean;
|
|
14
|
+
inputElement: HTMLInputElement | null;
|
|
15
|
+
dragCounter: number;
|
|
16
|
+
previewImage: string | null;
|
|
17
|
+
connectedCallback(): void;
|
|
18
|
+
disconnectedCallback(): void;
|
|
19
|
+
firstUpdated(_changedProperties: PropertyValues): void;
|
|
20
|
+
private _onDocumentDragEnter;
|
|
21
|
+
private _onDocumentDragLeave;
|
|
22
|
+
private _onDocumentDrop;
|
|
23
|
+
private _onDocumentDragOver;
|
|
24
|
+
private _onDrop;
|
|
25
|
+
private _onClick;
|
|
26
|
+
private _onChange;
|
|
27
|
+
private _handleFiles;
|
|
28
|
+
updateFileStatus(uid: string, status: 'ready' | 'uploading' | 'success' | 'error', percentage?: number): void;
|
|
29
|
+
private _updateFile;
|
|
30
|
+
private _removeFile;
|
|
31
|
+
private _dispatchEvent;
|
|
32
|
+
private _showPreview;
|
|
33
|
+
private _closePreview;
|
|
34
|
+
render(): import("lit").TemplateResult<1>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=file.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.component.d.ts","sourceRoot":"","sources":["../../../src/components/file-upload/file.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,cAAc,EAAE,MAAM,KAAK,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIrC,qBACa,UAAW,SAAQ,UAAU;IACxC,OAAgB,MAAM,0BAAU;IAEJ,MAAM,EAAE,MAAM,CAAM;IACnB,QAAQ,EAAE,OAAO,CAAS;IAC1B,IAAI,EAAE,OAAO,CAAQ;IACtB,GAAG,EAAE,MAAM,CAAM;IACjB,KAAK,EAAE,MAAM,CAAK;IACjB,OAAO,EAAE,OAAO,CAAQ;IAE5C,QAAQ,EAAE,UAAU,EAAE,CAAM;IAC5B,UAAU,EAAE,OAAO,CAAS;IAC5B,YAAY,EAAE,OAAO,CAAS;IAC9B,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IAC7C,WAAW,EAAE,MAAM,CAAK;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEnC,iBAAiB;IAUjB,oBAAoB;IAUpB,YAAY,CAAC,kBAAkB,EAAE,cAAc;IAKxD,OAAO,CAAC,oBAAoB,CAO1B;IAEF,OAAO,CAAC,oBAAoB,CAQ1B;IAEF,OAAO,CAAC,eAAe,CAOrB;IAEF,OAAO,CAAC,mBAAmB,CAYzB;IAEF,OAAO,CAAC,OAAO,CAWb;IAEF,OAAO,CAAC,QAAQ,CAEd;IAEF,OAAO,CAAC,SAAS,CAMf;YAEY,YAAY;IAmCnB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM;IAY7G,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,aAAa;IAIZ,MAAM;CAwGhB"}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
import { LitElement, html } from 'lit';
|
|
17
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
18
|
+
import { styles } from './styles';
|
|
19
|
+
import { fileUtils } from './utils';
|
|
20
|
+
let FileUpload = class FileUpload extends LitElement {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
this.accept = '';
|
|
24
|
+
this.multiple = false;
|
|
25
|
+
this.drag = true;
|
|
26
|
+
this.tip = '';
|
|
27
|
+
this.limit = 0;
|
|
28
|
+
this.preview = true;
|
|
29
|
+
this.fileList = [];
|
|
30
|
+
this.isDragOver = false;
|
|
31
|
+
this.showDragArea = false;
|
|
32
|
+
this.inputElement = null;
|
|
33
|
+
this.dragCounter = 0;
|
|
34
|
+
this.previewImage = null;
|
|
35
|
+
this._onDocumentDragEnter = (e) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
this.dragCounter++;
|
|
38
|
+
if (this.dragCounter === 1) {
|
|
39
|
+
this.showDragArea = true;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
this._onDocumentDragLeave = (e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
this.dragCounter--;
|
|
45
|
+
if (this.dragCounter === 0) {
|
|
46
|
+
this.showDragArea = false;
|
|
47
|
+
this.isDragOver = false;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
this._onDocumentDrop = (e) => {
|
|
51
|
+
if (e.target !== this && !this.contains(e.target)) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
this.dragCounter = 0;
|
|
54
|
+
this.showDragArea = false;
|
|
55
|
+
this.isDragOver = false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
this._onDocumentDragOver = (e) => {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
// Determine if the drag is over our component
|
|
61
|
+
const path = e.composedPath();
|
|
62
|
+
const isOverComponent = path.includes(this);
|
|
63
|
+
if (isOverComponent) {
|
|
64
|
+
this.isDragOver = true;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.isDragOver = false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
this._onDrop = (e) => {
|
|
71
|
+
var _a;
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
this.dragCounter = 0;
|
|
75
|
+
this.isDragOver = false;
|
|
76
|
+
this.showDragArea = false;
|
|
77
|
+
if ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) {
|
|
78
|
+
this._handleFiles(e.dataTransfer.files);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
this._onClick = () => {
|
|
82
|
+
var _a;
|
|
83
|
+
(_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.click();
|
|
84
|
+
};
|
|
85
|
+
this._onChange = (e) => {
|
|
86
|
+
const target = e.target;
|
|
87
|
+
if (target.files) {
|
|
88
|
+
this._handleFiles(target.files);
|
|
89
|
+
target.value = ''; // Reset so same file can be selected again
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
connectedCallback() {
|
|
94
|
+
super.connectedCallback();
|
|
95
|
+
if (this.drag) {
|
|
96
|
+
document.addEventListener('dragenter', this._onDocumentDragEnter);
|
|
97
|
+
document.addEventListener('dragleave', this._onDocumentDragLeave);
|
|
98
|
+
document.addEventListener('drop', this._onDocumentDrop);
|
|
99
|
+
document.addEventListener('dragover', this._onDocumentDragOver);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
disconnectedCallback() {
|
|
103
|
+
super.disconnectedCallback();
|
|
104
|
+
if (this.drag) {
|
|
105
|
+
document.removeEventListener('dragenter', this._onDocumentDragEnter);
|
|
106
|
+
document.removeEventListener('dragleave', this._onDocumentDragLeave);
|
|
107
|
+
document.removeEventListener('drop', this._onDocumentDrop);
|
|
108
|
+
document.removeEventListener('dragover', this._onDocumentDragOver);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
firstUpdated(_changedProperties) {
|
|
112
|
+
var _a;
|
|
113
|
+
super.firstUpdated(_changedProperties);
|
|
114
|
+
this.inputElement = ((_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('input[type="file"]')) || null;
|
|
115
|
+
}
|
|
116
|
+
_handleFiles(files) {
|
|
117
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
if (this.limit > 0 && this.fileList.length + files.length > this.limit) {
|
|
119
|
+
this._dispatchEvent('exceed', { files });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const newFiles = [];
|
|
123
|
+
for (const file of Array.from(files)) {
|
|
124
|
+
const isImage = fileUtils.isImageFile(file);
|
|
125
|
+
const fileObj = {
|
|
126
|
+
name: file.name,
|
|
127
|
+
size: fileUtils.formatFileSize(file.size),
|
|
128
|
+
raw: file,
|
|
129
|
+
status: 'ready',
|
|
130
|
+
percentage: 0,
|
|
131
|
+
uid: Date.now() + Math.random().toString(36).substring(2),
|
|
132
|
+
isImage
|
|
133
|
+
};
|
|
134
|
+
// Create preview URL for images if preview is enabled
|
|
135
|
+
if (this.preview && isImage) {
|
|
136
|
+
fileObj.url = yield fileUtils.createFilePreview(file);
|
|
137
|
+
}
|
|
138
|
+
this.fileList = [...this.fileList, fileObj];
|
|
139
|
+
newFiles.push(fileObj);
|
|
140
|
+
}
|
|
141
|
+
this.requestUpdate();
|
|
142
|
+
// Emit file-selected event with the new files
|
|
143
|
+
this._dispatchEvent('select', { files: newFiles, fileList: this.fileList });
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
updateFileStatus(uid, status, percentage) {
|
|
147
|
+
const file = this.fileList.find(f => f.uid === uid);
|
|
148
|
+
if (file) {
|
|
149
|
+
file.status = status;
|
|
150
|
+
if (percentage !== undefined) {
|
|
151
|
+
file.percentage = percentage;
|
|
152
|
+
}
|
|
153
|
+
this._updateFile(file);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
_updateFile(updatedFile) {
|
|
157
|
+
this.fileList = this.fileList.map(file => file.uid === updatedFile.uid ? updatedFile : file);
|
|
158
|
+
this.requestUpdate();
|
|
159
|
+
}
|
|
160
|
+
_removeFile(uid) {
|
|
161
|
+
const file = this.fileList.find(file => file.uid === uid);
|
|
162
|
+
this.fileList = this.fileList.filter(file => file.uid !== uid);
|
|
163
|
+
if (file) {
|
|
164
|
+
this._dispatchEvent('remove', { file });
|
|
165
|
+
}
|
|
166
|
+
this.requestUpdate();
|
|
167
|
+
}
|
|
168
|
+
_dispatchEvent(name, data) {
|
|
169
|
+
this.dispatchEvent(new CustomEvent(`file-${name}`, {
|
|
170
|
+
detail: data,
|
|
171
|
+
bubbles: true,
|
|
172
|
+
composed: true
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
_showPreview(url) {
|
|
176
|
+
this.previewImage = url;
|
|
177
|
+
}
|
|
178
|
+
_closePreview() {
|
|
179
|
+
this.previewImage = null;
|
|
180
|
+
}
|
|
181
|
+
render() {
|
|
182
|
+
return html `
|
|
183
|
+
<div class="upload" @drop=${this._onDrop} @dragover=${(e) => e.preventDefault()}>
|
|
184
|
+
<input
|
|
185
|
+
type="file"
|
|
186
|
+
class="hidden"
|
|
187
|
+
accept=${this.accept}
|
|
188
|
+
?multiple=${this.multiple}
|
|
189
|
+
@change=${this._onChange}
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
${this.showDragArea ? html `
|
|
193
|
+
<div
|
|
194
|
+
class="upload-dragger ${this.isDragOver ? 'is-dragover' : ''}"
|
|
195
|
+
@click=${this._onClick}
|
|
196
|
+
>
|
|
197
|
+
<div class="upload-icon">
|
|
198
|
+
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
199
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
200
|
+
<polyline points="17 8 12 3 7 8"></polyline>
|
|
201
|
+
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
202
|
+
</svg>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="upload-text">Drop file here or click to upload</div>
|
|
205
|
+
${this.tip ? html `<div class="upload-tip">${this.tip}</div>` : ''}
|
|
206
|
+
</div>
|
|
207
|
+
` : html `
|
|
208
|
+
<button class="upload-button" @click=${this._onClick}>Upload File</button>
|
|
209
|
+
${this.tip ? html `<div class="upload-tip">${this.tip}</div>` : ''}
|
|
210
|
+
`}
|
|
211
|
+
|
|
212
|
+
<div class="file-list">
|
|
213
|
+
${this.fileList.map(file => html `
|
|
214
|
+
<div class="file-item">
|
|
215
|
+
${file.isImage ? html `
|
|
216
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
217
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
218
|
+
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
219
|
+
<polyline points="21 15 16 10 5 21"></polyline>
|
|
220
|
+
</svg>
|
|
221
|
+
` : html `
|
|
222
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
223
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
224
|
+
<polyline points="14 2 14 8 20 8"></polyline>
|
|
225
|
+
</svg>
|
|
226
|
+
`}
|
|
227
|
+
<div class="file-name">${file.name}</div>
|
|
228
|
+
<div class="file-size">${file.size}</div>
|
|
229
|
+
<div class="file-status">
|
|
230
|
+
${file.status === 'success' ? html `
|
|
231
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#67c23a" stroke-width="2">
|
|
232
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
233
|
+
</svg>
|
|
234
|
+
` : file.status === 'error' ? html `
|
|
235
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#f56c6c" stroke-width="2">
|
|
236
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
237
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
238
|
+
</svg>
|
|
239
|
+
` : file.status === 'uploading' ? html `${file.percentage}%` : ''}
|
|
240
|
+
</div>
|
|
241
|
+
<div class="file-actions">
|
|
242
|
+
${file.isImage && file.url ? html `
|
|
243
|
+
<button class="preview-icon" @click=${() => this._showPreview(file.url)}>
|
|
244
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
245
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
|
246
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
247
|
+
</svg>
|
|
248
|
+
</button>
|
|
249
|
+
` : ''}
|
|
250
|
+
<button @click=${() => this._removeFile(file.uid)}>
|
|
251
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
252
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
253
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
254
|
+
</svg>
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
${file.status === 'uploading' ? html `
|
|
258
|
+
<div class="progress-bar">
|
|
259
|
+
<div class="progress-inner" style="width: ${file.percentage}%"></div>
|
|
260
|
+
</div>
|
|
261
|
+
` : ''}
|
|
262
|
+
</div>
|
|
263
|
+
${this.preview && file.isImage && file.url ? html `
|
|
264
|
+
<div class="file-preview">
|
|
265
|
+
<img
|
|
266
|
+
class="image-preview"
|
|
267
|
+
src="${file.url}"
|
|
268
|
+
alt="${file.name}"
|
|
269
|
+
@click=${() => this._showPreview(file.url)}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
` : ''}
|
|
273
|
+
`)}
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
${this.previewImage ? html `
|
|
277
|
+
<div class="preview-modal" @click=${this._closePreview}>
|
|
278
|
+
<button class="preview-close">×</button>
|
|
279
|
+
<img src="${this.previewImage}" alt="Preview" />
|
|
280
|
+
</div>
|
|
281
|
+
` : ''}
|
|
282
|
+
</div>
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
FileUpload.styles = styles;
|
|
287
|
+
__decorate([
|
|
288
|
+
property({ type: String })
|
|
289
|
+
], FileUpload.prototype, "accept", void 0);
|
|
290
|
+
__decorate([
|
|
291
|
+
property({ type: Boolean })
|
|
292
|
+
], FileUpload.prototype, "multiple", void 0);
|
|
293
|
+
__decorate([
|
|
294
|
+
property({ type: Boolean })
|
|
295
|
+
], FileUpload.prototype, "drag", void 0);
|
|
296
|
+
__decorate([
|
|
297
|
+
property({ type: String })
|
|
298
|
+
], FileUpload.prototype, "tip", void 0);
|
|
299
|
+
__decorate([
|
|
300
|
+
property({ type: Number })
|
|
301
|
+
], FileUpload.prototype, "limit", void 0);
|
|
302
|
+
__decorate([
|
|
303
|
+
property({ type: Boolean })
|
|
304
|
+
], FileUpload.prototype, "preview", void 0);
|
|
305
|
+
__decorate([
|
|
306
|
+
state()
|
|
307
|
+
], FileUpload.prototype, "fileList", void 0);
|
|
308
|
+
__decorate([
|
|
309
|
+
state()
|
|
310
|
+
], FileUpload.prototype, "isDragOver", void 0);
|
|
311
|
+
__decorate([
|
|
312
|
+
state()
|
|
313
|
+
], FileUpload.prototype, "showDragArea", void 0);
|
|
314
|
+
__decorate([
|
|
315
|
+
state()
|
|
316
|
+
], FileUpload.prototype, "inputElement", void 0);
|
|
317
|
+
__decorate([
|
|
318
|
+
state()
|
|
319
|
+
], FileUpload.prototype, "dragCounter", void 0);
|
|
320
|
+
__decorate([
|
|
321
|
+
state()
|
|
322
|
+
], FileUpload.prototype, "previewImage", void 0);
|
|
323
|
+
FileUpload = __decorate([
|
|
324
|
+
customElement('nr-file-upload')
|
|
325
|
+
], FileUpload);
|
|
326
|
+
export { FileUpload };
|
|
327
|
+
//# sourceMappingURL=file.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.component.js","sourceRoot":"","sources":["../../../src/components/file-upload/file.component.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGpC,IAAa,UAAU,GAAvB,MAAa,UAAW,SAAQ,UAAU;IAA1C;;QAG8B,WAAM,GAAW,EAAE,CAAC;QACnB,aAAQ,GAAY,KAAK,CAAC;QAC1B,SAAI,GAAY,IAAI,CAAC;QACtB,QAAG,GAAW,EAAE,CAAC;QACjB,UAAK,GAAW,CAAC,CAAC;QACjB,YAAO,GAAY,IAAI,CAAC;QAE5C,aAAQ,GAAiB,EAAE,CAAC;QAC5B,eAAU,GAAY,KAAK,CAAC;QAC5B,iBAAY,GAAY,KAAK,CAAC;QAC9B,iBAAY,GAA4B,IAAI,CAAC;QAC7C,gBAAW,GAAW,CAAC,CAAC;QACxB,iBAAY,GAAkB,IAAI,CAAC;QA2BpC,yBAAoB,GAAG,CAAC,CAAY,EAAE,EAAE;YAC9C,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,EAAE,CAAC;YAEnB,IAAI,IAAI,CAAC,WAAW,KAAK,CAAC,EAAE;gBAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;aAC1B;QACH,CAAC,CAAC;QAEM,yBAAoB,GAAG,CAAC,CAAY,EAAE,EAAE;YAC9C,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,EAAE,CAAC;YAEnB,IAAI,IAAI,CAAC,WAAW,KAAK,CAAC,EAAE;gBAC1B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;gBAC1B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aACzB;QACH,CAAC,CAAC;QAEM,oBAAe,GAAG,CAAC,CAAY,EAAE,EAAE;YACzC,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC,EAAE;gBACzD,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;gBACrB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;gBAC1B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aACzB;QACH,CAAC,CAAC;QAEM,wBAAmB,GAAG,CAAC,CAAY,EAAE,EAAE;YAC7C,CAAC,CAAC,cAAc,EAAE,CAAC;YAEnB,8CAA8C;YAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC;YAC9B,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,eAAe,EAAE;gBACnB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;aACxB;iBAAM;gBACL,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;aACzB;QACH,CAAC,CAAC;QAEM,YAAO,GAAG,CAAC,CAAY,EAAE,EAAE;;YACjC,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,CAAC,CAAC,eAAe,EAAE,CAAC;YAEpB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;YACrB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAE1B,IAAI,MAAA,CAAC,CAAC,YAAY,0CAAE,KAAK,EAAE;gBACzB,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;aACzC;QACH,CAAC,CAAC;QAEM,aAAQ,GAAG,GAAG,EAAE;;YACtB,MAAA,IAAI,CAAC,YAAY,0CAAE,KAAK,EAAE,CAAC;QAC7B,CAAC,CAAC;QAEM,cAAS,GAAG,CAAC,CAAQ,EAAE,EAAE;YAC/B,MAAM,MAAM,GAAG,CAAC,CAAC,MAA0B,CAAC;YAC5C,IAAI,MAAM,CAAC,KAAK,EAAE;gBAChB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,2CAA2C;aAC/D;QACH,CAAC,CAAC;IAyLJ,CAAC;IAnRU,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,IAAI,EAAE;YACb,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAClE,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAClE,QAAQ,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YACxD,QAAQ,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;SACjE;IACH,CAAC;IAEQ,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,IAAI,EAAE;YACb,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACrE,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACrE,QAAQ,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YAC3D,QAAQ,CAAC,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;SACpE;IACH,CAAC;IAEQ,YAAY,CAAC,kBAAkC;;QACtD,KAAK,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC;QACvC,IAAI,CAAC,YAAY,GAAG,CAAA,MAAA,IAAI,CAAC,UAAU,0CAAE,aAAa,CAAC,oBAAoB,CAAC,KAAI,IAAI,CAAC;IACnF,CAAC;IAqEa,YAAY,CAAC,KAAe;;YACxC,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE;gBACtE,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzC,OAAO;aACR;YAED,MAAM,QAAQ,GAAiB,EAAE,CAAC;YAElC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBACpC,MAAM,OAAO,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;gBAC5C,MAAM,OAAO,GAAe;oBAC1B,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;oBACzC,GAAG,EAAE,IAAI;oBACT,MAAM,EAAE,OAAO;oBACf,UAAU,EAAE,CAAC;oBACb,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;oBACzD,OAAO;iBACR,CAAC;gBAEF,sDAAsD;gBACtD,IAAI,IAAI,CAAC,OAAO,IAAI,OAAO,EAAE;oBAC3B,OAAO,CAAC,GAAG,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;iBACvD;gBAED,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC5C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aACxB;YAED,IAAI,CAAC,aAAa,EAAE,CAAC;YAErB,8CAA8C;YAC9C,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC9E,CAAC;KAAA;IAEM,gBAAgB,CAAC,GAAW,EAAE,MAAmD,EAAE,UAAmB;QAC3G,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QAEpD,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,IAAI,UAAU,KAAK,SAAS,EAAE;gBAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;aAC9B;YACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;SACxB;IACH,CAAC;IAEO,WAAW,CAAC,WAAuB;QACzC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CACvC,IAAI,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAClD,CAAC;QACF,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,WAAW,CAAC,GAAW;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QAC/D,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;SACzC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,cAAc,CAAC,IAAY,EAAE,IAAS;QAC5C,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,IAAI,EAAE,EAAE;YACjD,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC,CAAC;IACN,CAAC;IAEO,YAAY,CAAC,GAAW;QAC9B,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC;IAC1B,CAAC;IAEO,aAAa;QACnB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;kCACmB,IAAI,CAAC,OAAO,cAAc,CAAC,CAAY,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE;;;;mBAI7E,IAAI,CAAC,MAAM;sBACR,IAAI,CAAC,QAAQ;oBACf,IAAI,CAAC,SAAS;;;UAGxB,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;;oCAEE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE;qBACnD,IAAI,CAAC,QAAQ;;;;;;;;;;cAUpB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,2BAA2B,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE;;SAEpE,CAAC,CAAC,CAAC,IAAI,CAAA;iDACiC,IAAI,CAAC,QAAQ;YAClD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,2BAA2B,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE;SAClE;;;YAGG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAA;;gBAE1B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;;;;;;eAMpB,CAAC,CAAC,CAAC,IAAI,CAAA;;;;;eAKP;uCACwB,IAAI,CAAC,IAAI;uCACT,IAAI,CAAC,IAAI;;kBAE9B,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;;;;iBAIjC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;;;;;iBAKjC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE;;;kBAG9D,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;wDACO,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAI,CAAC;;;;;;iBAMzE,CAAC,CAAC,CAAC,EAAE;iCACW,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;gBAOjD,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA;;8DAEY,IAAI,CAAC,UAAU;;eAE9D,CAAC,CAAC,CAAC,EAAE;;cAEN,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;;;;yBAIpC,IAAI,CAAC,GAAG;yBACR,IAAI,CAAC,IAAI;2BACP,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAI,CAAC;;;aAGhD,CAAC,CAAC,CAAC,EAAE;WACP,CAAC;;;UAGF,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;8CACY,IAAI,CAAC,aAAa;;wBAExC,IAAI,CAAC,YAAY;;SAEhC,CAAC,CAAC,CAAC,EAAE;;KAET,CAAC;IACJ,CAAC;CACF,CAAA;AAnSiB,iBAAM,GAAG,MAAO,CAAA;AAEJ;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CAAqB;AACnB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;4CAA2B;AAC1B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;wCAAsB;AACtB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;uCAAkB;AACjB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCAAmB;AACjB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAyB;AAE5C;IAAR,KAAK,EAAE;4CAA6B;AAC5B;IAAR,KAAK,EAAE;8CAA6B;AAC5B;IAAR,KAAK,EAAE;gDAA+B;AAC9B;IAAR,KAAK,EAAE;gDAA8C;AAC7C;IAAR,KAAK,EAAE;+CAAyB;AACxB;IAAR,KAAK,EAAE;gDAAoC;AAfjC,UAAU;IADtB,aAAa,CAAC,gBAAgB,CAAC;GACnB,UAAU,CAoStB;SApSY,UAAU","sourcesContent":["import { LitElement, html, PropertyValues } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { UploadFile } from './types';\nimport { styles } from './styles';\nimport { fileUtils } from './utils';\n\n@customElement('nr-file-upload')\nexport class FileUpload extends LitElement {\n static override styles = styles;\n\n @property({ type: String }) accept: string = '';\n @property({ type: Boolean }) multiple: boolean = false;\n @property({ type: Boolean }) drag: boolean = true;\n @property({ type: String }) tip: string = '';\n @property({ type: Number }) limit: number = 0;\n @property({ type: Boolean }) preview: boolean = true;\n\n @state() fileList: UploadFile[] = [];\n @state() isDragOver: boolean = false;\n @state() showDragArea: boolean = false;\n @state() inputElement: HTMLInputElement | null = null;\n @state() dragCounter: number = 0;\n @state() previewImage: string | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n if (this.drag) {\n document.addEventListener('dragenter', this._onDocumentDragEnter);\n document.addEventListener('dragleave', this._onDocumentDragLeave);\n document.addEventListener('drop', this._onDocumentDrop);\n document.addEventListener('dragover', this._onDocumentDragOver);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n if (this.drag) {\n document.removeEventListener('dragenter', this._onDocumentDragEnter);\n document.removeEventListener('dragleave', this._onDocumentDragLeave);\n document.removeEventListener('drop', this._onDocumentDrop);\n document.removeEventListener('dragover', this._onDocumentDragOver);\n }\n }\n\n override firstUpdated(_changedProperties: PropertyValues) {\n super.firstUpdated(_changedProperties);\n this.inputElement = this.shadowRoot?.querySelector('input[type=\"file\"]') || null;\n }\n\n private _onDocumentDragEnter = (e: DragEvent) => {\n e.preventDefault();\n this.dragCounter++;\n \n if (this.dragCounter === 1) {\n this.showDragArea = true;\n }\n };\n\n private _onDocumentDragLeave = (e: DragEvent) => {\n e.preventDefault();\n this.dragCounter--;\n \n if (this.dragCounter === 0) {\n this.showDragArea = false;\n this.isDragOver = false;\n }\n };\n\n private _onDocumentDrop = (e: DragEvent) => {\n if (e.target !== this && !this.contains(e.target as Node)) {\n e.preventDefault();\n this.dragCounter = 0;\n this.showDragArea = false;\n this.isDragOver = false;\n }\n };\n\n private _onDocumentDragOver = (e: DragEvent) => {\n e.preventDefault();\n \n // Determine if the drag is over our component\n const path = e.composedPath();\n const isOverComponent = path.includes(this);\n \n if (isOverComponent) {\n this.isDragOver = true;\n } else {\n this.isDragOver = false;\n }\n };\n\n private _onDrop = (e: DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n \n this.dragCounter = 0;\n this.isDragOver = false;\n this.showDragArea = false;\n\n if (e.dataTransfer?.files) {\n this._handleFiles(e.dataTransfer.files);\n }\n };\n\n private _onClick = () => {\n this.inputElement?.click();\n };\n\n private _onChange = (e: Event) => {\n const target = e.target as HTMLInputElement;\n if (target.files) {\n this._handleFiles(target.files);\n target.value = ''; // Reset so same file can be selected again\n }\n };\n\n private async _handleFiles(files: FileList) {\n if (this.limit > 0 && this.fileList.length + files.length > this.limit) {\n this._dispatchEvent('exceed', { files });\n return;\n }\n\n const newFiles: UploadFile[] = [];\n\n for (const file of Array.from(files)) {\n const isImage = fileUtils.isImageFile(file);\n const fileObj: UploadFile = {\n name: file.name,\n size: fileUtils.formatFileSize(file.size),\n raw: file,\n status: 'ready',\n percentage: 0,\n uid: Date.now() + Math.random().toString(36).substring(2),\n isImage\n };\n\n // Create preview URL for images if preview is enabled\n if (this.preview && isImage) {\n fileObj.url = await fileUtils.createFilePreview(file);\n }\n\n this.fileList = [...this.fileList, fileObj];\n newFiles.push(fileObj);\n }\n\n this.requestUpdate();\n\n // Emit file-selected event with the new files\n this._dispatchEvent('select', { files: newFiles, fileList: this.fileList });\n }\n\n public updateFileStatus(uid: string, status: 'ready' | 'uploading' | 'success' | 'error', percentage?: number) {\n const file = this.fileList.find(f => f.uid === uid);\n \n if (file) {\n file.status = status;\n if (percentage !== undefined) {\n file.percentage = percentage;\n }\n this._updateFile(file);\n }\n }\n\n private _updateFile(updatedFile: UploadFile) {\n this.fileList = this.fileList.map(file =>\n file.uid === updatedFile.uid ? updatedFile : file\n );\n this.requestUpdate();\n }\n\n private _removeFile(uid: string) {\n const file = this.fileList.find(file => file.uid === uid);\n this.fileList = this.fileList.filter(file => file.uid !== uid);\n if (file) {\n this._dispatchEvent('remove', { file });\n }\n this.requestUpdate();\n }\n\n private _dispatchEvent(name: string, data: any) {\n this.dispatchEvent(new CustomEvent(`file-${name}`, {\n detail: data,\n bubbles: true,\n composed: true\n }));\n }\n\n private _showPreview(url: string) {\n this.previewImage = url;\n }\n\n private _closePreview() {\n this.previewImage = null;\n }\n\n override render() {\n return html`\n <div class=\"upload\" @drop=${this._onDrop} @dragover=${(e: DragEvent) => e.preventDefault()}>\n <input \n type=\"file\" \n class=\"hidden\" \n accept=${this.accept}\n ?multiple=${this.multiple}\n @change=${this._onChange}\n />\n\n ${this.showDragArea ? html`\n <div \n class=\"upload-dragger ${this.isDragOver ? 'is-dragover' : ''}\" \n @click=${this._onClick}\n >\n <div class=\"upload-icon\">\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"17 8 12 3 7 8\"></polyline>\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"></line>\n </svg>\n </div>\n <div class=\"upload-text\">Drop file here or click to upload</div>\n ${this.tip ? html`<div class=\"upload-tip\">${this.tip}</div>` : ''}\n </div>\n ` : html`\n <button class=\"upload-button\" @click=${this._onClick}>Upload File</button>\n ${this.tip ? html`<div class=\"upload-tip\">${this.tip}</div>` : ''}\n `}\n\n <div class=\"file-list\">\n ${this.fileList.map(file => html`\n <div class=\"file-item\">\n ${file.isImage ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect>\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"></circle>\n <polyline points=\"21 15 16 10 5 21\"></polyline>\n </svg>\n ` : html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path>\n <polyline points=\"14 2 14 8 20 8\"></polyline>\n </svg>\n `}\n <div class=\"file-name\">${file.name}</div>\n <div class=\"file-size\">${file.size}</div>\n <div class=\"file-status\">\n ${file.status === 'success' ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#67c23a\" stroke-width=\"2\">\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n ` : file.status === 'error' ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f56c6c\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n ` : file.status === 'uploading' ? html`${file.percentage}%` : ''}\n </div>\n <div class=\"file-actions\">\n ${file.isImage && file.url ? html`\n <button class=\"preview-icon\" @click=${() => this._showPreview(file.url!)}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n </button>\n ` : ''}\n <button @click=${() => this._removeFile(file.uid)}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n ${file.status === 'uploading' ? html`\n <div class=\"progress-bar\">\n <div class=\"progress-inner\" style=\"width: ${file.percentage}%\"></div>\n </div>\n ` : ''}\n </div>\n ${this.preview && file.isImage && file.url ? html`\n <div class=\"file-preview\">\n <img \n class=\"image-preview\" \n src=\"${file.url}\" \n alt=\"${file.name}\" \n @click=${() => this._showPreview(file.url!)}\n />\n </div>\n ` : ''}\n `)}\n </div>\n\n ${this.previewImage ? html`\n <div class=\"preview-modal\" @click=${this._closePreview}>\n <button class=\"preview-close\">×</button>\n <img src=\"${this.previewImage}\" alt=\"Preview\" />\n </div>\n ` : ''}\n </div>\n `;\n }\n}"]}
|