update file upload
This commit is contained in:
@ -51,85 +51,151 @@ export const demoFunc = () => html`
|
||||
</style>
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic File Upload'} .subtitle=${'Simple file upload with drag and drop support'}>
|
||||
<dees-panel .title=${'1. Basic File Upload'} .subtitle=${'Simple file upload with drag and drop support'}>
|
||||
<dees-input-fileupload
|
||||
.label=${'Attachments'}
|
||||
.description=${'Upload files by clicking or dragging'}
|
||||
.description=${'Upload any files by clicking or dragging them here'}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Resume'}
|
||||
.description=${'Upload your CV in PDF format'}
|
||||
.buttonText=${'Choose Resume...'}
|
||||
.label=${'Single File Only'}
|
||||
.description=${'Only one file can be uploaded at a time'}
|
||||
.multiple=${false}
|
||||
.buttonText=${'Choose File'}
|
||||
></dees-input-fileupload>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Multiple Upload Areas'} .subtitle=${'Different upload zones for various file types'}>
|
||||
<dees-panel .title=${'2. File Type Restrictions'} .subtitle=${'Upload areas with specific file type requirements'}>
|
||||
<div class="upload-grid">
|
||||
<div class="upload-box">
|
||||
<h4>Profile Picture</h4>
|
||||
<h4>Images Only</h4>
|
||||
<dees-input-fileupload
|
||||
.label=${'Avatar'}
|
||||
.description=${'JPG, PNG or GIF'}
|
||||
.buttonText=${'Select Image...'}
|
||||
.label=${'Profile Picture'}
|
||||
.description=${'JPG, PNG or GIF (max 5MB)'}
|
||||
.accept=${'image/jpeg,image/png,image/gif'}
|
||||
.maxSize=${5 * 1024 * 1024}
|
||||
.multiple=${false}
|
||||
.buttonText=${'Select Image'}
|
||||
></dees-input-fileupload>
|
||||
</div>
|
||||
|
||||
<div class="upload-box">
|
||||
<h4>Cover Image</h4>
|
||||
<h4>Documents Only</h4>
|
||||
<dees-input-fileupload
|
||||
.label=${'Banner'}
|
||||
.description=${'Recommended: 1200x400px'}
|
||||
.buttonText=${'Select Banner...'}
|
||||
.label=${'Resume'}
|
||||
.description=${'PDF or Word documents only'}
|
||||
.accept=${".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"}
|
||||
.buttonText=${'Select Document'}
|
||||
></dees-input-fileupload>
|
||||
</div>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Required & Disabled States'} .subtitle=${'Different upload states for validation'}>
|
||||
<dees-panel .title=${'3. Validation & Limits'} .subtitle=${'File size limits and validation examples'}>
|
||||
<dees-input-fileupload
|
||||
.label=${'Identity Document'}
|
||||
.description=${'Required for verification'}
|
||||
.required=${true}
|
||||
.buttonText=${'Upload Document...'}
|
||||
.label=${'Small Files Only'}
|
||||
.description=${'Maximum file size: 1MB'}
|
||||
.maxSize=${1024 * 1024}
|
||||
.buttonText=${'Upload Small File'}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'System Files'}
|
||||
.description=${'File upload is disabled'}
|
||||
.disabled=${true}
|
||||
.value=${[]}
|
||||
.label=${'Limited Upload'}
|
||||
.description=${'Maximum 3 files, each up to 2MB'}
|
||||
.maxFiles=${3}
|
||||
.maxSize=${2 * 1024 * 1024}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Required Upload'}
|
||||
.description=${'This field is required'}
|
||||
.required=${true}
|
||||
></dees-input-fileupload>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Application Form'} .subtitle=${'Complete form with file upload integration'}>
|
||||
<dees-panel .title=${'4. States & Styling'} .subtitle=${'Different states and validation feedback'}>
|
||||
<dees-input-fileupload
|
||||
.label=${'Disabled Upload'}
|
||||
.description=${'File upload is currently disabled'}
|
||||
.disabled=${true}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Pre-filled Example'}
|
||||
.description=${'Component with pre-loaded files'}
|
||||
.value=${[
|
||||
new File(['Hello World'], 'example.txt', { type: 'text/plain' }),
|
||||
new File(['Test Data'], 'data.json', { type: 'application/json' })
|
||||
]}
|
||||
></dees-input-fileupload>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'5. Form Integration'} .subtitle=${'Complete form with various file upload scenarios'}>
|
||||
<dees-form>
|
||||
<dees-input-text .label=${'Full Name'} .required=${true}></dees-input-text>
|
||||
<dees-input-text .label=${'Email'} .inputType=${'email'} .required=${true}></dees-input-text>
|
||||
<h3 style="margin-top: 0; margin-bottom: 24px; color: ${cssManager.bdTheme('#333', '#fff')};">Job Application Form</h3>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Full Name'}
|
||||
.required=${true}
|
||||
.key=${'fullName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Email'}
|
||||
.inputType=${'email'}
|
||||
.required=${true}
|
||||
.key=${'email'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Resume'}
|
||||
.description=${'Upload your CV (PDF preferred)'}
|
||||
.description=${'Required: PDF format only (max 10MB)'}
|
||||
.required=${true}
|
||||
.accept=${'application/pdf'}
|
||||
.maxSize=${10 * 1024 * 1024}
|
||||
.multiple=${false}
|
||||
.key=${'resume'}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Portfolio'}
|
||||
.description=${'Optional: Upload work samples'}
|
||||
.description=${'Optional: Upload up to 5 work samples (images or PDFs, max 5MB each)'}
|
||||
.accept=${'image/*,application/pdf'}
|
||||
.maxFiles=${5}
|
||||
.maxSize=${5 * 1024 * 1024}
|
||||
.key=${'portfolio'}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'References'}
|
||||
.description=${'Upload reference letters (optional)'}
|
||||
.accept=${".pdf,.doc,.docx"}
|
||||
.key=${'references'}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Cover Letter'}
|
||||
.label=${'Additional Comments'}
|
||||
.inputType=${'textarea'}
|
||||
.description=${'Tell us why you would be a great fit'}
|
||||
.description=${'Any additional information you would like to share'}
|
||||
.key=${'comments'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-form-submit .text=${'Submit Application'}></dees-form-submit>
|
||||
</dees-form>
|
||||
|
||||
<div class="info-section">
|
||||
<h4>Features:</h4>
|
||||
<ul>
|
||||
<li>Click to select files or drag & drop</li>
|
||||
<li>Multiple file selection support</li>
|
||||
<li>Visual feedback for drag operations</li>
|
||||
<li>Right-click files to remove them</li>
|
||||
<li>Integrates seamlessly with forms</li>
|
||||
<h4 style="margin-top: 0;">Enhanced Features:</h4>
|
||||
<ul style="margin: 0; padding-left: 20px;">
|
||||
<li>Drag & drop with visual feedback</li>
|
||||
<li>File type restrictions via accept attribute</li>
|
||||
<li>File size validation with custom limits</li>
|
||||
<li>Maximum file count restrictions</li>
|
||||
<li>Image preview thumbnails</li>
|
||||
<li>File type-specific icons</li>
|
||||
<li>Clear all button for multiple files</li>
|
||||
<li>Proper validation states and messages</li>
|
||||
<li>Keyboard accessible</li>
|
||||
<li>Single or multiple file modes</li>
|
||||
</ul>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
@ -42,6 +42,21 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
})
|
||||
public buttonText: string = 'Upload File...';
|
||||
|
||||
@property({ type: String })
|
||||
public accept: string = '';
|
||||
|
||||
@property({ type: Boolean })
|
||||
public multiple: boolean = true;
|
||||
|
||||
@property({ type: Number })
|
||||
public maxSize: number = 0; // 0 means no limit
|
||||
|
||||
@property({ type: Number })
|
||||
public maxFiles: number = 0; // 0 means no limit
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
public validationState: 'valid' | 'invalid' | 'warn' | 'pending' = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@ -52,7 +67,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: grid;
|
||||
display: block;
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
|
||||
@ -60,13 +75,42 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
padding: 8px;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#222222')};
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')};
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
border-top: 1px solid #ffffff10;
|
||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.maincontainer:hover {
|
||||
border-color: ${cssManager.bdTheme('#ccc', '#444')};
|
||||
}
|
||||
|
||||
:host([disabled]) .maincontainer {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host([validationState="invalid"]) .maincontainer {
|
||||
border-color: #e74c3c;
|
||||
}
|
||||
|
||||
:host([validationState="valid"]) .maincontainer {
|
||||
border-color: #27ae60;
|
||||
}
|
||||
|
||||
:host([validationState="warn"]) .maincontainer {
|
||||
border-color: #f39c12;
|
||||
}
|
||||
|
||||
.maincontainer::after {
|
||||
@ -78,115 +122,385 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
border: 2px dashed rgba(255, 255, 255, 0);
|
||||
transition: all 0.2s;
|
||||
border: 2px dashed transparent;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: none;
|
||||
background: #00000000;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.maincontainer.dragOver {
|
||||
border-color: ${cssManager.bdTheme('#0084ff', '#0084ff')};
|
||||
background: ${cssManager.bdTheme('#f0f8ff', '#001933')};
|
||||
}
|
||||
|
||||
.maincontainer.dragOver::after {
|
||||
transform: scale3d(1, 1, 1);
|
||||
border: 2px dashed rgba(255, 255, 255, 0.3);
|
||||
background: #00000080;
|
||||
border: 2px dashed ${cssManager.bdTheme('#0084ff', '#0084ff')};
|
||||
}
|
||||
|
||||
.uploadButton {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
max-width: 600px;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#333333')};
|
||||
border-radius: 3px;
|
||||
padding: 12px 24px;
|
||||
background: ${cssManager.bdTheme('#0084ff', '#0084ff')};
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
cursor: default;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.uploadButton:hover {
|
||||
color: #fff;
|
||||
background: ${unsafeCSS(colors.dark.blue)};
|
||||
background: ${cssManager.bdTheme('#0073e6', '#0073e6')};
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 132, 255, 0.3);
|
||||
}
|
||||
|
||||
.uploadButton:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uploadButton dees-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.files-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.uploadCandidate {
|
||||
display: grid;
|
||||
grid-template-columns: 48px auto;
|
||||
background: #333;
|
||||
padding: 8px 8px 8px 0px;
|
||||
margin-bottom: 8px;
|
||||
grid-template-columns: 40px 1fr auto;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#2a2a2a')};
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-radius: 3px;
|
||||
color: ${cssManager.bdTheme('#666', '#ccc')};
|
||||
font-family: 'Geist Sans', sans-serif;
|
||||
border-radius: 6px;
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
cursor: default;
|
||||
transition: all 0.2s;
|
||||
border-top: 1px solid #ffffff10;
|
||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uploadCandidate:last-child {
|
||||
margin-bottom: 8px;
|
||||
.uploadCandidate:hover {
|
||||
background: ${cssManager.bdTheme('#f5f5f5', '#333')};
|
||||
border-color: ${cssManager.bdTheme('#ccc', '#444')};
|
||||
}
|
||||
|
||||
.uploadCandidate .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-size: 20px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
}
|
||||
|
||||
.uploadCandidate:hover {
|
||||
background: #393939;
|
||||
.uploadCandidate.image-file .icon {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.uploadCandidate.pdf-file .icon {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.uploadCandidate.doc-file .icon {
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.uploadCandidate .description {
|
||||
.uploadCandidate .info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.uploadCandidate .filename {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-left: 1px solid #ffffff10;
|
||||
padding-left: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uploadCandidate .filesize {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
}
|
||||
|
||||
.uploadCandidate .actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
}
|
||||
|
||||
.remove-button:hover {
|
||||
background: ${cssManager.bdTheme('#fee', '#4a1c1c')};
|
||||
color: ${cssManager.bdTheme('#e74c3c', '#ff6b6b')};
|
||||
}
|
||||
|
||||
.clear-all-button {
|
||||
margin-bottom: 8px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clear-all-button button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.clear-all-button button:hover {
|
||||
background: ${cssManager.bdTheme('#fee', '#4a1c1c')};
|
||||
color: ${cssManager.bdTheme('#e74c3c', '#ff6b6b')};
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.drop-hint {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: ${cssManager.bdTheme('#999', '#666')};
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.drop-hint dees-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
const hasFiles = this.value.length > 0;
|
||||
const showClearAll = hasFiles && this.value.length > 1;
|
||||
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description}></dees-label>
|
||||
${this.label ? html`
|
||||
<dees-label .label=${this.label}></dees-label>
|
||||
` : ''}
|
||||
<div class="hidden">
|
||||
<input type="file">
|
||||
<input
|
||||
type="file"
|
||||
?multiple=${this.multiple}
|
||||
accept="${this.accept}"
|
||||
>
|
||||
</div>
|
||||
<div class="maincontainer ${this.state === 'dragOver' ? 'dragOver' : ''}">
|
||||
${this.value.map(
|
||||
(fileArg) => html`
|
||||
<div class="uploadCandidate" @contextmenu=${eventArg => {
|
||||
DeesContextmenu.openContextMenuWithOptions(eventArg, [{
|
||||
iconName: 'trash',
|
||||
name: 'Remove',
|
||||
action: async () => {
|
||||
this.value.splice(this.value.indexOf(fileArg), 1);
|
||||
this.requestUpdate();
|
||||
}
|
||||
}]);
|
||||
}}>
|
||||
<div class="icon">
|
||||
<dees-icon .iconFA=${'paperclip'}></dees-icon>
|
||||
${hasFiles ? html`
|
||||
${showClearAll ? html`
|
||||
<div class="clear-all-button">
|
||||
<button @click=${this.clearAll}>Clear All</button>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="files-container">
|
||||
${this.value.map((fileArg) => {
|
||||
const fileType = this.getFileType(fileArg);
|
||||
const isImage = fileType === 'image';
|
||||
return html`
|
||||
<div class="uploadCandidate ${fileType}-file">
|
||||
<div class="icon">
|
||||
${isImage && this.canShowPreview(fileArg) ? html`
|
||||
<img class="image-preview" src="${URL.createObjectURL(fileArg)}" alt="${fileArg.name}">
|
||||
` : html`
|
||||
<dees-icon .iconName=${this.getFileIcon(fileArg)}></dees-icon>
|
||||
`}
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="filename" title="${fileArg.name}">${fileArg.name}</div>
|
||||
<div class="filesize">${this.formatFileSize(fileArg.size)}</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
class="remove-button"
|
||||
@click=${() => this.removeFile(fileArg)}
|
||||
title="Remove file"
|
||||
>
|
||||
<dees-icon .iconName=${'lucide:x'}></dees-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
<div class="description">
|
||||
<span style="font-weight: 600">${fileArg.name}</span><br />
|
||||
<span style="font-weight: 400">${fileArg.size}</span>
|
||||
` : html`
|
||||
<div class="drop-hint">
|
||||
<dees-icon .iconName=${'lucide:cloud-upload'}></dees-icon>
|
||||
<div>Drag files here or click to browse</div>
|
||||
</div>
|
||||
</div> `
|
||||
)}
|
||||
<div class="uploadButton" @click=${
|
||||
this.openFileSelector
|
||||
}>
|
||||
${this.buttonText}
|
||||
`}
|
||||
<div class="uploadButton" @click=${this.openFileSelector}>
|
||||
<dees-icon .iconName=${'lucide:upload'}></dees-icon>
|
||||
${this.buttonText}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.description ? html`
|
||||
<div class="description-text">${this.description}</div>
|
||||
` : ''}
|
||||
${this.validationState === 'invalid' && this.validationMessage ? html`
|
||||
<div class="validation-message">${this.validationMessage}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private validationMessage: string = '';
|
||||
|
||||
// Utility methods
|
||||
private formatFileSize(bytes: number): string {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
private getFileType(file: File): string {
|
||||
const type = file.type.toLowerCase();
|
||||
if (type.startsWith('image/')) return 'image';
|
||||
if (type === 'application/pdf') return 'pdf';
|
||||
if (type.includes('word') || type.includes('document')) return 'doc';
|
||||
if (type.includes('sheet') || type.includes('excel')) return 'spreadsheet';
|
||||
if (type.includes('presentation') || type.includes('powerpoint')) return 'presentation';
|
||||
if (type.startsWith('video/')) return 'video';
|
||||
if (type.startsWith('audio/')) return 'audio';
|
||||
if (type.includes('zip') || type.includes('compressed')) return 'archive';
|
||||
return 'file';
|
||||
}
|
||||
|
||||
private getFileIcon(file: File): string {
|
||||
const type = this.getFileType(file);
|
||||
const iconMap = {
|
||||
'image': 'lucide:image',
|
||||
'pdf': 'lucide:file-text',
|
||||
'doc': 'lucide:file-text',
|
||||
'spreadsheet': 'lucide:table',
|
||||
'presentation': 'lucide:presentation',
|
||||
'video': 'lucide:video',
|
||||
'audio': 'lucide:music',
|
||||
'archive': 'lucide:archive',
|
||||
'file': 'lucide:file'
|
||||
};
|
||||
return iconMap[type] || 'lucide:file';
|
||||
}
|
||||
|
||||
private canShowPreview(file: File): boolean {
|
||||
return file.type.startsWith('image/') && file.size < 5 * 1024 * 1024; // 5MB limit for previews
|
||||
}
|
||||
|
||||
private validateFile(file: File): boolean {
|
||||
// Check file size
|
||||
if (this.maxSize > 0 && file.size > this.maxSize) {
|
||||
this.validationMessage = `File "${file.name}" exceeds maximum size of ${this.formatFileSize(this.maxSize)}`;
|
||||
this.validationState = 'invalid';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check file type
|
||||
if (this.accept) {
|
||||
const acceptedTypes = this.accept.split(',').map(s => s.trim());
|
||||
let isAccepted = false;
|
||||
|
||||
for (const acceptType of acceptedTypes) {
|
||||
if (acceptType.startsWith('.')) {
|
||||
// Extension check
|
||||
if (file.name.toLowerCase().endsWith(acceptType.toLowerCase())) {
|
||||
isAccepted = true;
|
||||
break;
|
||||
}
|
||||
} else if (acceptType.endsWith('/*')) {
|
||||
// MIME type wildcard check
|
||||
const mimePrefix = acceptType.slice(0, -2);
|
||||
if (file.type.startsWith(mimePrefix)) {
|
||||
isAccepted = true;
|
||||
break;
|
||||
}
|
||||
} else if (file.type === acceptType) {
|
||||
// Exact MIME type check
|
||||
isAccepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAccepted) {
|
||||
this.validationMessage = `File type not accepted. Please upload: ${this.accept}`;
|
||||
this.validationState = 'invalid';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async openFileSelector() {
|
||||
if (this.disabled) return;
|
||||
const inputFile: HTMLInputElement = this.shadowRoot.querySelector('input[type="file"]');
|
||||
inputFile.click();
|
||||
this.state = 'idle';
|
||||
this.buttonText = 'Upload more files...';
|
||||
}
|
||||
|
||||
private removeFile(file: File) {
|
||||
const index = this.value.indexOf(file);
|
||||
if (index > -1) {
|
||||
this.value.splice(index, 1);
|
||||
this.requestUpdate();
|
||||
this.validate();
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
}
|
||||
|
||||
private clearAll() {
|
||||
this.value = [];
|
||||
this.requestUpdate();
|
||||
this.validate();
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
public async updateValue(eventArg: Event) {
|
||||
@ -198,52 +512,131 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
||||
super.firstUpdated(_changedProperties);
|
||||
const inputFile: HTMLInputElement = this.shadowRoot.querySelector('input[type="file"]');
|
||||
inputFile.addEventListener('change', (event: Event) => {
|
||||
inputFile.addEventListener('change', async (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
for (const file of Array.from(target.files)) {
|
||||
this.value.push(file);
|
||||
}
|
||||
this.requestUpdate();
|
||||
console.log(`Got ${this.value.length} files!`);
|
||||
const newFiles = Array.from(target.files);
|
||||
await this.addFiles(newFiles);
|
||||
// Reset the input value to allow selecting the same file again if needed
|
||||
target.value = '';
|
||||
});
|
||||
|
||||
// lets handle drag and drop
|
||||
// Handle drag and drop
|
||||
const dropArea = this.shadowRoot.querySelector('.maincontainer');
|
||||
const handlerFunction = (eventArg: DragEvent) => {
|
||||
const handlerFunction = async (eventArg: DragEvent) => {
|
||||
eventArg.preventDefault();
|
||||
eventArg.stopPropagation();
|
||||
|
||||
switch (eventArg.type) {
|
||||
case 'dragenter':
|
||||
case 'dragover':
|
||||
this.state = 'dragOver';
|
||||
this.buttonText = 'release to upload file...';
|
||||
break;
|
||||
case 'dragleave':
|
||||
this.state = 'idle';
|
||||
this.buttonText = 'Upload File...';
|
||||
// Check if we're actually leaving the drop area
|
||||
const rect = dropArea.getBoundingClientRect();
|
||||
const x = eventArg.clientX;
|
||||
const y = eventArg.clientY;
|
||||
if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
|
||||
this.state = 'idle';
|
||||
}
|
||||
break;
|
||||
case 'drop':
|
||||
this.state = 'idle';
|
||||
this.buttonText = 'Upload more files...';
|
||||
const files = Array.from(eventArg.dataTransfer.files);
|
||||
await this.addFiles(files);
|
||||
break;
|
||||
}
|
||||
console.log(eventArg);
|
||||
for (const file of Array.from(eventArg.dataTransfer.files)) {
|
||||
this.value.push(file);
|
||||
this.requestUpdate();
|
||||
}
|
||||
console.log(`Got ${this.value.length} files!`);
|
||||
};
|
||||
|
||||
dropArea.addEventListener('dragenter', handlerFunction, false);
|
||||
dropArea.addEventListener('dragleave', handlerFunction, false);
|
||||
dropArea.addEventListener('dragover', handlerFunction, false);
|
||||
dropArea.addEventListener('drop', handlerFunction, false);
|
||||
}
|
||||
|
||||
private async addFiles(files: File[]) {
|
||||
const filesToAdd: File[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (this.validateFile(file)) {
|
||||
filesToAdd.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToAdd.length === 0) return;
|
||||
|
||||
// Check max files limit
|
||||
if (this.maxFiles > 0) {
|
||||
const totalFiles = this.value.length + filesToAdd.length;
|
||||
if (totalFiles > this.maxFiles) {
|
||||
const allowedCount = this.maxFiles - this.value.length;
|
||||
if (allowedCount <= 0) {
|
||||
this.validationMessage = `Maximum ${this.maxFiles} files allowed`;
|
||||
this.validationState = 'invalid';
|
||||
return;
|
||||
}
|
||||
filesToAdd.splice(allowedCount);
|
||||
this.validationMessage = `Only ${allowedCount} more file(s) can be added`;
|
||||
this.validationState = 'warn';
|
||||
}
|
||||
}
|
||||
|
||||
// Add files
|
||||
if (!this.multiple && filesToAdd.length > 0) {
|
||||
this.value = [filesToAdd[0]];
|
||||
} else {
|
||||
this.value.push(...filesToAdd);
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
this.validate();
|
||||
this.changeSubject.next(this);
|
||||
|
||||
// Update button text
|
||||
if (this.value.length > 0) {
|
||||
this.buttonText = this.multiple ? 'Add more files' : 'Replace file';
|
||||
}
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
this.validationMessage = '';
|
||||
|
||||
if (this.required && this.value.length === 0) {
|
||||
this.validationState = 'invalid';
|
||||
this.validationMessage = 'Please select at least one file';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate all files
|
||||
for (const file of this.value) {
|
||||
if (!this.validateFile(file)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.validationState = 'valid';
|
||||
return true;
|
||||
}
|
||||
|
||||
public getValue(): File[] {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public setValue(value: File[]): void {
|
||||
this.value = value;
|
||||
this.requestUpdate();
|
||||
if (value.length > 0) {
|
||||
this.buttonText = this.multiple ? 'Add more files' : 'Replace file';
|
||||
} else {
|
||||
this.buttonText = 'Upload File...';
|
||||
}
|
||||
}
|
||||
|
||||
public updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('value')) {
|
||||
this.validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user