Skip to content

Commit

Permalink
feat(file-upload): add max-size and more custom labels
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Jul 29, 2024
1 parent a85436a commit b494e14
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,16 @@ $ods-file-upload-spacing: 8px;
font-size: font.px-to-rem(32px);
}

&__file-format {
font-size: font.px-to-rem(12px);
&__rules {
display: flex;
flex-flow: column;
align-self: flex-start;

&__file-format,
&__max-file,
&__max-size {
font-size: font.px-to-rem(12px);
}
}

&__error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Component, Event, type EventEmitter, type FunctionalComponent, Host, Prop, State, h } from '@stencil/core';
import { getRandomHTMLId } from '../../../../../utils/dom';
import { getFilesFromDataTransfer } from '../../../../../utils/file';
import { formatBytes, getFilesFromDataTransfer } from '../../../../../utils/file';
import { ODS_BUTTON_VARIANT } from '../../../../button/src';
import { ODS_ICON_NAME } from '../../../../icon/src';
import { ODS_FILE_REJECTION_CAUSE } from '../../constants/file-error';
import { filterMaxFiles, filterValidFiles } from '../../controller/ods-file-upload';
import { filterMaxFiles, filterMaxSize, filterValidFiles } from '../../controller/ods-file-upload';
import { type OdsFile } from '../../interfaces/attributes';
import { type OdsFileChangeEventDetail, type OdsFileRejectedEventDetail } from '../../interfaces/events';

Expand All @@ -19,13 +19,16 @@ export class OdsFileUpload {
@State() isDragging: boolean = false;

@Prop({ reflect: true }) public accept: string = '';
@Prop({ reflect: true }) public acceptedFileLabel: string = 'Accepted files:';
@Prop({ reflect: true }) public acceptedFileLabel?: string;
@Prop({ reflect: true }) public browseFileLabel: string = 'Browse files';
@Prop({ reflect: true }) public dropzoneLabel: string = 'Drag & drop a file';
@Prop({ reflect: true }) public error: string = '';
@Prop({ reflect: false }) public files: OdsFile[] = [];
@Prop({ reflect: true }) public isDisabled: boolean = false;
@Prop({ reflect: true }) public maxFile?: number;
@Prop({ reflect: true }) public maxFileLabel?: string;
@Prop({ reflect: true }) public maxSize?: number;
@Prop({ reflect: true }) public maxSizeLabel?: string;
@Prop({ reflect: true }) public uploadSuccessLabel: string = 'File uploaded';

@Event() odsFileCancel!: EventEmitter<OdsFile>;
Expand All @@ -46,11 +49,20 @@ export class OdsFileUpload {
});
}

const { rejectedFiles, validFiles: remainingFiles } = filterMaxFiles(validFiles, this.files.length, this.maxFile);
const { rejectedFiles: oversizedFiles, validFiles: fittedFiles } = filterMaxSize(validFiles, this.maxSize);

if (rejectedFiles.length) {
if (oversizedFiles.length) {
this.odsFileRejected.emit({
files: rejectedFiles,
files: oversizedFiles,
reason: ODS_FILE_REJECTION_CAUSE.sizeTooLarge,
});
}

const { rejectedFiles: exceedingFiles, validFiles: remainingFiles } = filterMaxFiles(fittedFiles, this.files.length, this.maxFile);

if (exceedingFiles.length) {
this.odsFileRejected.emit({
files: exceedingFiles,
reason: ODS_FILE_REJECTION_CAUSE.maxFileReached,
});
}
Expand All @@ -62,7 +74,7 @@ export class OdsFileUpload {
file.odsId = getRandomHTMLId();
return file;
}),
noError: invalidFiles.length === 0 && rejectedFiles.length === 0,
noError: invalidFiles.length === 0 && oversizedFiles.length === 0 && exceedingFiles.length === 0,
});
}
}
Expand Down Expand Up @@ -126,19 +138,35 @@ export class OdsFileUpload {

<ods-icon
class="ods-file-upload__dropzone__icon"
name={ ODS_ICON_NAME.file }>
name={ ODS_ICON_NAME.filePlus }>
</ods-icon>

<span>
{ this.dropzoneLabel }
</span>

{
this.accept &&
<span class="ods-file-upload__dropzone__file-format">
{ this.acceptedFileLabel }&nbsp;{ this.accept }
</span>
}
<div class="ods-file-upload__dropzone__rules">
{
this.maxFileLabel && typeof this.maxFile === 'number' && this.maxFile >= 0 &&
<span class="ods-file-upload__dropzone__rules__max-file">
{ this.maxFileLabel }&nbsp;{ this.maxFile }
</span>
}

{
this.maxSizeLabel && typeof this.maxSize === 'number' && this.maxSize >= 0 &&
<span class="ods-file-upload__dropzone__rules__max-size">
{ this.maxSizeLabel }&nbsp;{ formatBytes(this.maxSize) }
</span>
}

{
this.acceptedFileLabel &&
<span class="ods-file-upload__dropzone__rules__file-format">
{ this.acceptedFileLabel }
</span>
}
</div>

<ods-button
icon={ ODS_ICON_NAME.upload }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
enum ODS_FILE_REJECTION_CAUSE {
maxFileReached = 'max-file-reached',
sizeTooLarge = 'size-too-large',
wrongFormat = 'wrong-format',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ function filterMaxFiles(newFiles: OdsFile[], currentFileCount: number, maxFile?:
};
}

function filterMaxSize(files: OdsFile[], maxSize?: number): FileValidationResult {
if (!maxSize && maxSize !== 0) {
return {
rejectedFiles: [],
validFiles: files,
};
}

return files.reduce((res, file) => {
if (file.size > maxSize) {
res.rejectedFiles.push(file);
} else {
res.validFiles.push(file);
}
return res;
}, {
rejectedFiles: [],
validFiles: [],
} as FileValidationResult);
}

function filterValidFiles(files: OdsFile[], accept: string): FileValidationResult {
return files.reduce((res, file) => {
if (isFileAccepted(file, accept)) {
Expand All @@ -36,5 +57,6 @@ function filterValidFiles(files: OdsFile[], accept: string): FileValidationResul

export {
filterMaxFiles,
filterMaxSize,
filterValidFiles,
};
26 changes: 24 additions & 2 deletions packages/ods/src/components/file-upload/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@

<p>Max 2 files</p>
<ods-file-upload id="maxFile"
max-file="2">
max-file="2"
max-file-label="Max file:">
</ods-file-upload>
<script>
const maxFileFileUpload = document.querySelector('#maxFile');
Expand All @@ -71,6 +72,23 @@
});
</script>

<p>Max 1Mb</p>
<ods-file-upload id="maxSize"
max-size="1048576"
max-size-label="Max size per file:">
</ods-file-upload>
<script>
const maxSizeFileUpload = document.querySelector('#maxSize');

maxSizeFileUpload.addEventListener('odsFileChange', ({ detail }) => {
maxSizeFileUpload.files = (maxSizeFileUpload.files || []).concat(detail.files);
});

maxSizeFileUpload.addEventListener('odsFileRejected', ({ detail }) => {
maxSizeFileUpload.error = `${detail.files.length} file(s) bigger than 1 mb were rejected`;
});
</script>

<p>Upload progress</p>
<ods-file-upload id="progress">
</ods-file-upload>
Expand Down Expand Up @@ -135,10 +153,14 @@

<p>Custom labels</p>
<ods-file-upload accept="image/*"
accepted-file-label="Formats acceptés :"
accepted-file-label="Formats acceptés : image"
browse-file-label="Parcourir les fichiers"
dropzone-label="Glisser-déposer des fichiers"
id="labels"
max-file="2"
max-file-label="Nombre max de fichier :"
max-size="1048576"
max-size-label="Taille maximale :"
upload-success-label="Fichier uploadé">
</ods-file-upload>
<script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
jest.mock('../../../../utils/file');

import { isFileAccepted } from '../../../../utils/file';
import { filterMaxFiles, filterValidFiles } from '../../src/controller/ods-file-upload';
import { filterMaxFiles, filterMaxSize, filterValidFiles } from '../../src/controller/ods-file-upload';

describe('ods-file-upload controller', () => {
beforeEach(jest.clearAllMocks);
Expand Down Expand Up @@ -34,6 +34,34 @@ describe('ods-file-upload controller', () => {
});
});

describe('filterMaxSize', () => {
const dummyFiles = [
{ name: 'dummy file 1', size: 2222 } as File,
{ name: 'dummy file 2', size: 54323 } as File,
];

it('should return all files as valid if no max size is set', () => {
expect(filterMaxSize(dummyFiles)).toEqual({
rejectedFiles: [],
validFiles: dummyFiles,
});
});

it('should return all files as valid if max size is not reached', () => {
expect(filterMaxSize(dummyFiles, 987654321)).toEqual({
rejectedFiles: [],
validFiles: dummyFiles,
});
});

it('should return split files if max size is reached', () => {
expect(filterMaxSize(dummyFiles, 30000)).toEqual({
rejectedFiles: [dummyFiles[1]],
validFiles: [dummyFiles[0]],
});
});
});

describe('filterValidFiles', () => {
const dummyAccept = 'dummy/*';
const dummyFiles = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,36 @@ describe('ods-file-upload rendering', () => {
expect((await dropzone.getComputedStyle()).getPropertyValue('background-color')).not.toBe(baseBackgroundColor);
});

it('should render the accept labels if set', async() => {
const dummyAccept = 'images/*';
const dummyAcceptLabel = 'File formats:';
await setup(`<ods-file-upload accept="${dummyAccept}" accepted-file-label="${dummyAcceptLabel}"></ods-file-upload>`);
it('should render the max file label if set', async() => {
const dummyMaxFile = 3;
const dummyMaxFileLabel = 'Max file:';
await setup(`<ods-file-upload max-file="${dummyMaxFile}" max-file-label="${dummyMaxFileLabel}"></ods-file-upload>`);

const acceptElement = await page.find('ods-file-upload >>> .ods-file-upload__dropzone__file-format');
const acceptElement = await page.find('ods-file-upload >>> .ods-file-upload__dropzone__rules__max-file');

expect(acceptElement).not.toBeNull();
expect(acceptElement.innerText).toBe(`${dummyAcceptLabel} ${dummyAccept}`); // eslint-disable-line no-irregular-whitespace
expect(acceptElement.innerText).toBe(`${dummyMaxFileLabel} ${dummyMaxFile}`); // eslint-disable-line no-irregular-whitespace
});

it('should render the max size label if set', async() => {
const dummyMaxSize = 1024;
const dummyMaxSizeLabel = 'Max size:';
await setup(`<ods-file-upload max-size="${dummyMaxSize}" max-size-label="${dummyMaxSizeLabel}"></ods-file-upload>`);

const acceptElement = await page.find('ods-file-upload >>> .ods-file-upload__dropzone__rules__max-size');

expect(acceptElement).not.toBeNull();
expect(acceptElement.innerText).toBe(`${dummyMaxSizeLabel} 1 kb`); // eslint-disable-line no-irregular-whitespace
});

it('should render the accept label if set', async() => {
const dummyAcceptLabel = 'File formats: images';
await setup(`<ods-file-upload accepted-file-label="${dummyAcceptLabel}"></ods-file-upload>`);

const acceptElement = await page.find('ods-file-upload >>> .ods-file-upload__dropzone__rules__file-format');

expect(acceptElement).not.toBeNull();
expect(acceptElement.innerText).toBe(dummyAcceptLabel);
});

it('should render the global error if set', async() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ describe('ods-file-upload rendering', () => {
expect(root?.getAttribute('accepted-file-label')).toBe(dummyValue);
});

it('should render with expected default value', async() => {
it('should not be set by default', async() => {
await setup('<ods-file-upload></ods-file-upload>');

expect(root?.getAttribute('accepted-file-label')).toBe('Accepted files:');
expect(root?.getAttribute('accepted-file-label')).toBeNull();
});
});

Expand Down Expand Up @@ -121,15 +121,63 @@ describe('ods-file-upload rendering', () => {
it('should be reflected', async() => {
const dummyValue = 42;

await setup(`<ods-file-upload max-files="${dummyValue}"></ods-file-upload>`);
await setup(`<ods-file-upload max-file="${dummyValue}"></ods-file-upload>`);

expect(root?.getAttribute('max-file')).toBe(`${dummyValue}`);
});

it('should not be set by default', async() => {
await setup('<ods-file-upload></ods-file-upload>');

expect(root?.getAttribute('max-file')).toBeNull();
});
});

describe('maxFileLabel', () => {
it('should be reflected', async() => {
const dummyValue = 'dummy value';

await setup(`<ods-file-upload max-file-label="${dummyValue}"></ods-file-upload>`);

expect(root?.getAttribute('max-file-label')).toBe(dummyValue);
});

it('should not be set by default', async() => {
await setup('<ods-file-upload></ods-file-upload>');

expect(root?.getAttribute('max-file-label')).toBeNull();
});
});

describe('maxSize', () => {
it('should be reflected', async() => {
const dummyValue = 42;

await setup(`<ods-file-upload max-size="${dummyValue}"></ods-file-upload>`);

expect(root?.getAttribute('max-size')).toBe(`${dummyValue}`);
});

it('should not be set by default', async() => {
await setup('<ods-file-upload></ods-file-upload>');

expect(root?.getAttribute('max-size')).toBeNull();
});
});

describe('maxSizeLabel', () => {
it('should be reflected', async() => {
const dummyValue = 'dummy value';

await setup(`<ods-file-upload max-size-label="${dummyValue}"></ods-file-upload>`);

expect(root?.getAttribute('max-files')).toBe(`${dummyValue}`);
expect(root?.getAttribute('max-size-label')).toBe(dummyValue);
});

it('should not be set by default', async() => {
await setup('<ods-file-upload></ods-file-upload>');

expect(root?.getAttribute('max-files')).toBeNull();
expect(root?.getAttribute('max-size-label')).toBeNull();
});
});

Expand Down
6 changes: 5 additions & 1 deletion packages/ods/src/utils/file.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
function formatBytes(bytes: number, decimals = 0): string {
if (!bytes || typeof bytes !== 'number') {
if (typeof bytes !== 'number' || bytes < 0) {
return '';
}

if (bytes === 0) {
return '0 b';
}

const sizes = ['b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'];
const decimal = decimals < 0 ? 0 : decimals;
const k = 1024;
Expand Down
Loading

0 comments on commit b494e14

Please sign in to comment.
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy