Skip to content

Commit

Permalink
Feat(web-twig): Hiding input when the queue is full #DS-730
Browse files Browse the repository at this point in the history
  • Loading branch information
tomassychra committed May 18, 2023
1 parent 159152b commit 2612484
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,11 @@ export const useFileUploaderInput = (props: UseFileUploaderInputProps): UseFileU
};

const updateDropZoneVisibility = (queue: FileQueueMapType) => {
console.log('queueLimitBehavior', queueLimitBehavior);
if (!queueLimitBehavior) {
return;
}

setDropZoneHidden(queue.size >= maxUploadedFiles);
setDropZoneHidden(queue?.size >= maxUploadedFiles);
};

const clearQueueHandler = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const useFileUploaderStyleProps = (props?: FileUploaderStyleProps): FileU
validationText: fileUploaderInputValidationTextClass,
dropZone: {
root: classNames(fileUploaderInputDropZoneClass, {
[fileUploaderInputDropZoneHiddenClass]: props?.isDropZoneHidden && props.queueLimitBehavior === 'hide', // TODO: Modify when the disabled state is ready for the entire component
[fileUploaderInputDropZoneHiddenClass]: props?.isDropZoneHidden && props.queueLimitBehavior === 'hide', // TODO: Modify when the disabled state is ready for the entire component (https://jira.lmc.cz/browse/DS-772)
}),
label: fileUploaderInputDropZoneLabelClass,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
{%- set _isLabelHidden = props.isLabelHidden | default(false) | boolprop -%}
{%- set _isRequired = props.isRequired | default(false) | boolprop -%}
{%- set _label = props.label | default(null) -%}
{%- set _maxFileSize = props.maxFileSize | default(null) -%}
{%- set _maxUploadedFiles = props.maxUploadedFiles | default(null) -%}
{%- set _name = props.name | default(null) -%}
{%- set _pickAFileText = props.pickAFileText | default('Upload your file') -%}
{%- set _queueLimitBehavior = props.queueLimitBehavior | default(null) -%}
{%- set _unsafeHelperText = props.UNSAFE_helperText | default(null) -%}
{%- set _unsafeLabel = props.UNSAFE_label | default(null) -%}
{%- set _unsafeValidationText = props.UNSAFE_validationText | default(null) -%}
Expand All @@ -32,6 +35,9 @@
{# Attributes #}
{%- set _nameAttr = _name ? 'name="' ~ _name | escape('html_attr') ~ '"' : null -%}
{%- set _requiredAttr = _isRequired ? 'required' : null -%}
{%- set _dataMaxFileSizeAttr = _maxFileSize ? 'data-spirit-max-file-size=' ~ _maxFileSize : null -%}
{%- set _dataMaxUploadedFilesAttr = _maxUploadedFiles ? 'data-spirit-file-queue-limit=' ~ _maxUploadedFiles : null -%}
{%- set _dataQueueLimitBehaviorAttr = _queueLimitBehavior ? 'data-spirit-queue-limit-behavior=' ~ _queueLimitBehavior : null -%}

{# Miscellaneous #}
{%- set _styleProps = useStyleProps(props) -%}
Expand All @@ -44,6 +50,9 @@
{{ mainProps(_mainPropsWithoutReservedAttributes) }}
{{ classProp(_rootClassNames) }}
{{ styleProp(_styleProps) }}
{{ _dataMaxFileSizeAttr }}
{{ _dataMaxUploadedFilesAttr }}
{{ _dataQueueLimitBehaviorAttr }}
data-spirit-element="wrapper"
>
<label for="{{ _id }}" {{ classProp(_labelClassNames) }}>
Expand Down
42 changes: 30 additions & 12 deletions packages/web-twig/src/Resources/components/FileUploader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,6 @@ By adding the `isFluid` attribute, FileUploader can take up all the available ho
</div>
```

### Maximum File Size (JavaScript)

The maximum size of the uploaded file that is validated by the JavaScript plugin can be adjusted. The default value is
10 MB. To increase the limit for example to 20 MB, add the `data-max-file-size` attribute:

```twig
<FileUploader data-toggle="fileUploader" data-max-file-size="20000000">
<!-- FileUploaderInput -->
<!-- FileUploaderList -->
</FileUploader>
```

### API

| Prop name | Type | Default | Required | Description |
Expand All @@ -86,6 +74,33 @@ If supported by the device, FileUploaderInput automatically turns on the drag-an
/>
```

### Maximum File Size (JavaScript)

The maximum size of the uploaded file that is validated by the JavaScript plugin can be adjusted. The default value is
10 MB. To increase the limit for example to 20 MB, add the `maxFileSize` attribute:

```twig
<FileUploaderInput maxFileSize={20000000} />
```

### Maximum number of files in queue (JavaScript)

Counter of the maximum number of uploaded files. The default value is 10, but any value can be set with
the `maxUploadedFiles` attribute:

```twig
<FileUploaderInput maxUploadedFiles={2} />
```

### Input behavior when the queue is filled (JavaScript)

You can set the input/drop zone to be hidden or disabled when the file queue limit is reached.
When you set `queueLimitBehavior` together with the desired limit for the queue:

```twig
<FileUploaderInput maxUploadedFiles={2} queueLimitBehavior="hide" />
```

### Uploading Multiple Files

To pick more than one file, just add the [`multiple`][mdn-multiple] attribute that will be transferred to the native
Expand Down Expand Up @@ -154,8 +169,11 @@ When validated on server:
| `isLabelHidden` | `bool` | `false` | no | If true, label is hidden |
| `isRequired` | `bool` | `false` | no | If true, input is required |
| `label` | `string` | `null` | no\* | Label text |
| `maxFileSize` | `number` | `1000000` | no | The maximum size of the uploaded file |
| `maxUploadedFiles` | `number` | `10` | no | Maximum file upload queue size |
| `name` | `string` | `null` | no | Input name |
| `pickAFileText` | `string` | `Upload your file` | no | Text shown in the drop zone |
| `queueLimitBehavior` | `'hide', 'disable', 'none'` | `none` | no | Input behavior when the file queue is filled |
| `UNSAFE_helperText` | `string` | `null` | no\*\* | Unescaped custom helper text |
| `UNSAFE_label` | `string` | `null` | no\* | Unescaped label text (allows HTML) |
| `UNSAFE_validationText` | `string` | `null` | no\*\* | Unescaped validation text |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<section class="docs-Section">

<h2 class="docs-Heading">FileUploader Multiple with File Queue Behavior Control</h2>

<FileUploader data-toggle="fileUploader">
<template data-spirit-snippet="item">
<FileUploaderAttachment />
</template>
<FileUploaderInput
helperText="Max size of each file is 10 MB"
id="example-input-multiple-queue-control"
label="Label"
multiple
name="example-input-multiple-queue-control"
pickAFileText="Upload your file(s)"
maxUploadedFiles={2}
queueLimitBehavior="hide"
/>
<FileUploaderList />
</FileUploader>

</section>
35 changes: 27 additions & 8 deletions packages/web/src/js/FileUploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import BaseComponent from './BaseComponent';
import { enableToggleAutoloader } from './utils';

const NAME = 'fileUploader';
const EVENT_KEY = `.${NAME}`;
const EVENT_QUEUE_FILE = `queueFile${EVENT_KEY}`;
const EVENT_QUEUED_FILE = `queuedFile${EVENT_KEY}`;
const EVENT_UNQUEUE_FILE = `unqueueFile${EVENT_KEY}`;
const EVENT_UNQUEUED_FILE = `unqueuedFile${EVENT_KEY}`;
const EVENT_ERROR = `error${EVENT_KEY}`;
const IS_DRAGGABLE_CLASS_NAME = 'has-drag-and-drop';
const IS_DRAGGING_CLASS_NAME = 'is-dragging';
const DROP_ZONE_HIDDEN_CLASS_NAME = 'd-none';
const DROP_ZONE_DISABLED_CLASS_NAME = 'd-none'; // TODO
const DROP_ZONE_DISABLED_CLASS_NAME = 'd-none'; // TODO: Modify when the disabled state is ready for the entire component (https://jira.lmc.cz/browse/DS-772)
const WRAPPER_ELEMENT_SELECTOR = '[data-spirit-element="wrapper"]';
const INPUT_ELEMENT_SELECTOR = '[data-spirit-element="input"]';
const LIST_ELEMENT_SELECTOR = '[data-spirit-element="list"]';
Expand All @@ -16,6 +22,12 @@ const TEMPLATE_ELEMENT_SLOT_NAME = 'data-spirit-populate-field';
const DATA_DISMISS_ATTRIBUTE = 'data-dismiss';
const DEFAULT_FILE_SIZE_LIMIT = 10000000; // = 10 MB
const DEFAULT_FILE_QUEUE_LIMIT = 10;
const errorMessages = {
errorMaxFileSize: 'The file size limit has been exceeded',
errorFileDuplicity: 'This file already exists in the queue',
errorMaxUploadedFiles: 'You have exceeded the number of files allowed in the queue',
errorFileNotSupported: 'is not a supported file. Please ensure you are uploading a supported file format.',
};

class FileUploader extends BaseComponent {
wrapper: HTMLElement;
Expand Down Expand Up @@ -85,19 +97,19 @@ class FileUploader extends BaseComponent {

checkAllowedFileSize(file: File) {
if (file.size > this.fileSizeLimit) {
throw new Error('The file size limit has been exceeded');
throw new Error(errorMessages.errorMaxFileSize);
}
}

checkFileQueueDuplicity(file: File) {
if (this.fileQueue.has(FileUploader.getUpdatedFileName(file.name))) {
throw new Error('This file already exists in the queue');
throw new Error(errorMessages.errorFileDuplicity);
}
}

checkQueueLimit() {
if (this.fileQueue.size >= this.fileQueueLimit) {
throw new Error('You have exceeded the number of files allowed in the queue');
throw new Error(errorMessages.errorMaxUploadedFiles);
}
}

Expand Down Expand Up @@ -132,9 +144,7 @@ class FileUploader extends BaseComponent {
}

if (!isTypeSupported) {
throw new Error(
`The file "${file.name}" is not supported. Please ensure you are uploading a supported file format.`,
);
throw new Error(`"${file.name}": ${errorMessages.errorFileNotSupported}`);
}
}

Expand All @@ -145,6 +155,8 @@ class FileUploader extends BaseComponent {

const dropZoneClassName =
this.queueLimitBehavior === 'hide' ? DROP_ZONE_HIDDEN_CLASS_NAME : DROP_ZONE_DISABLED_CLASS_NAME;

// This is forcing the callback to run last
setTimeout(() => {
this.dropZone?.classList.toggle(dropZoneClassName, this.fileQueue.size === this.fileQueueLimit);
}, 0);
Expand Down Expand Up @@ -239,23 +251,27 @@ class FileUploader extends BaseComponent {

addToQueue(file: File) {
try {
EventHandler.trigger(this.wrapper, EVENT_QUEUE_FILE, { fileQueue: this.fileQueue });
this.checkAllowedFileSize(file);
this.checkFileQueueDuplicity(file);
this.checkAllowedFileType(file);
this.checkQueueLimit();
this.appendToList(file);
this.updateDropZoneVisibility();
EventHandler.trigger(this.wrapper, EVENT_QUEUED_FILE, { fileQueue: this.fileQueue });
} catch (error) {
console.warn(error);
EventHandler.trigger(this.wrapper, EVENT_ERROR, error);
}
}

removeFromQueue(name: string) {
if (this.fileQueue.has(name)) {
EventHandler.trigger(this.wrapper, EVENT_UNQUEUE_FILE, { fileQueue: this.fileQueue });
const itemElement = SelectorEngine.findOne(`li#${name}`);
this.fileQueue.delete(name);
itemElement?.remove();
this.updateDropZoneVisibility();
EventHandler.trigger(this.wrapper, EVENT_UNQUEUED_FILE, { fileQueue: this.fileQueue });
}
}

Expand All @@ -278,8 +294,10 @@ class FileUploader extends BaseComponent {

if (overLimit) {
this.checkQueueLimit();
EventHandler.trigger(this.wrapper, EVENT_ERROR, new Error(errorMessages.errorMaxUploadedFiles));
}

// This is forcing the callback to run last
setTimeout(() => {
target.value = '';
target.blur();
Expand Down Expand Up @@ -342,6 +360,7 @@ class FileUploader extends BaseComponent {

if (overLimit) {
this.checkQueueLimit();
EventHandler.trigger(this.wrapper, EVENT_ERROR, new Error(errorMessages.errorMaxUploadedFiles));
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/js/__tests__/FileUploader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('FileUploader', () => {
it('should throw an error for an unsupported file type', () => {
const file = new File(['content'], 'example.txt', { type: 'text/plain' });
expect(() => fileUploader.checkAllowedFileType(file)).toThrowError(
`The file "example.txt" is not supported. Please ensure you are uploading a supported file format.`,
`"example.txt": is not a supported file. Please ensure you are uploading a supported file format`,
);
});
});
Expand All @@ -157,7 +157,7 @@ describe('FileUploader', () => {
jest.clearAllMocks();
});

it('should hides the drop zone when the file queue limit is reached', async () => {
it('should hide the drop zone when the file queue limit is reached', async () => {
const dropZone = fixtureEl.querySelector('[data-spirit-element="dropZone"]') as HTMLElement;
const file1 = new File([''], 'test1.txt', { type: 'text/plain', lastModified: 1684228128062 });
const file2 = new File([''], 'test2.txt', { type: 'text/plain', lastModified: 1684228128062 });
Expand Down
21 changes: 17 additions & 4 deletions packages/web/src/scss/components/FileUploader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ the available horizontal space:
</div>
```

In this way, the error message will stop being displayed when the limit of the number
of files in the queue is exceeded.

## FileUploaderInput

FileUploaderInput is a file picker built around the native HTML
Expand Down Expand Up @@ -110,10 +107,16 @@ the `data-spirit-file-queue-limit` attribute:

You can set the input/drop zone to be hidden or disabled when the file queue limit is reached.
When you set `data-spirit-queue-limit-behavior` together with the desired limit for the queue.
The options to choose from are 'hide', 'disable' and 'none', which is set as the default

```html
<div class="FileUploader" data-toggle="fileUploader">
<div class="FileUploaderInput" data-spirit-element="wrapper" data-spirit-queue-limit-behavior="hide">
<div
class="FileUploaderInput"
data-spirit-element="wrapper"
data-spirit-file-queue-limit="2"
data-spirit-queue-limit-behavior="hide"
>
<!-- ... -->
</div>
<!-- FileUploaderList -->
Expand Down Expand Up @@ -362,6 +365,16 @@ const fileList: File[] = myUploaderInstance.getFileQueue();
const input = myUploaderInstance.inputElement; // Returns an input element, for further use
```

### JavaScript Events

| Method | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `queueFile.fileUploader` | This event is fired just before the file was added to the queue. |
| `queuedFile.fileUploader` | This event is fired after the file was added to queue. |
| `unqueueFile.fileUploader` | This event is fired just before the file was removed from the queue. |
| `unqueuedFile.fileUploader` | This event is fired after the file was removed from queue. |
| `error.fileUploader` | This event is fired when an error occurs when adding files to the queue. A specific error message is also returned together with the event. |

[web-readme]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web/README.md
[mdn-input-file]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file
[mdn-multiple]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#multiple
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/scss/components/FileUploader/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ <h2 class="docs-Heading">FileUploader Multiple with File Queue Behavior Control<
the file upload field will be hidden or disabled. When the value for `data-spirit-queue-limit-behavior`
is set to 'hide', the input disappears completely. If you set it to 'disable', the input will only be disabled.
After removing a file from the queue, the input will be restored.
<br />
<br />
</p>
<p>
In this case we set the `data-spirit-file-queue-limit="2"` and the `data-spirit-queue-limit-behavior="hide"`.
</p>

Expand Down

0 comments on commit 2612484

Please sign in to comment.