This repository was archived by the owner on Aug 7, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
feat(oui-file): add file component #324
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| # File | ||
|
|
||
| <component-status cx-design="complete" ux="rc"></component-status> | ||
|
|
||
| ## Selector | ||
|
|
||
| ### Basic | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelBasic"></oui-file> | ||
| ``` | ||
|
|
||
| ### Placeholder | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelPlaceholder" placeholder="Placeholder example"></oui-file> | ||
| ``` | ||
|
|
||
| ### Disabled | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelDisabled" disabled></oui-file> | ||
| ``` | ||
|
|
||
| ### File restriction | ||
|
|
||
| <oui-message type="info" dismissable="false">`maxsize` support form validators</oui-message> | ||
|
|
||
| ```html:preview | ||
| <form novalidate name="fileForm"> | ||
| <oui-field label="Upload file" help-text="image/jpeg, image/png, image/gif (max size 150 KB)" size="xl"> | ||
| <oui-file name="fileUpload" model="$ctrl.modelField" | ||
| accept="image/jpeg,image/png,image/gif" | ||
| maxsize="150000" | ||
| required> | ||
| </oui-file> | ||
| </oui-field> | ||
| <p><strong><code class="oui-doc-codespan">fileForm.$valid</code> value:</strong> {{fileForm.$valid | json}}</p> | ||
| <p><strong><code class="oui-doc-codespan">fileForm.$submitted</code> value:</strong> {{fileForm.$submitted | json}}</p> | ||
| <p><strong><code class="oui-doc-codespan">fileForm["fileUpload"].$error</code> value:</strong> {{fileForm["fileUpload"].$error | json}}</p> | ||
| <oui-button type="submit">Submit</oui-button> | ||
| </form> | ||
| ``` | ||
|
|
||
| ## Multiple files | ||
|
|
||
| ### Basic | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelMultiple" maxsize="150000" multiple></oui-file> | ||
| ``` | ||
|
|
||
| ### With preview | ||
|
|
||
| <oui-message type="info" dismissable="false">Preview works only with `image/*` files.</oui-message> | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelMultiplePreview" maxsize="150000" multiple preview></oui-file> | ||
| ``` | ||
|
|
||
| ## Drag & Drop area | ||
|
|
||
| ### Basic | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelDroparea" maxsize="150000" droparea></oui-file> | ||
| ``` | ||
|
|
||
| ### With preview | ||
|
|
||
| <oui-message type="info" dismissable="false">Preview works only with `image/*` files.</oui-message> | ||
|
|
||
| ```html:preview | ||
| <oui-file model="$ctrl.modelDropareaPreview" maxsize="150000" droparea preview></oui-file> | ||
| ``` | ||
|
|
||
| ## API | ||
|
|
||
| | Attribute | Type | Binding | One-time binding | Values | Default | Description | ||
| | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ||
| | `model` | array<file> | = | no | n/a | n/a | model bound to component | ||
AxelPeter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| | `id` | string | @? | yes | n/a | n/a | id attribute of form input | ||
| | `name` | string | @? | yes | n/a | n/a | name attribute of form input | ||
| | `placeholder` | string | @? | yes | n/a | n/a | placeholder text | ||
| | `accept` | string | @? | yes | n/a | n/a | accept attribute of file input | ||
| | `maxsize` | number | <? | no | n/a | n/a | maximum file size in byte | ||
AxelPeter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| | `disabled` | boolean | <? | no | `true`, `false` | `false` | disabled flag | ||
| | `required` | boolean | <? | no | `true`, `false` | `false` | required flag | ||
| | `multiple` | boolean | <? | yes | `true`, `false` | `false` | multiple flag | ||
| | `droparea` | boolean | <? | yes | `true`, `false` | `false` | droparea flag | ||
| | `preview` | boolean | <? | yes | `true`, `false` | `false` | preview flag | ||
| | `on-select` | function | & | no | n/a | n/a | handler triggered when files are selected | ||
|
|
||
| ## Configuration | ||
|
|
||
| The file component can be globally configured with a provider. | ||
|
|
||
| ```js | ||
| angular.module("myModule", [ | ||
| "oui.file" | ||
| ]).config(ouiFileConfigurationProvider => { | ||
| ouiFileConfigurationProvider.setTranslations({ // default translations | ||
| attachmentsHeading: "Attachment(s)", | ||
| dropArea: "Attach document(s) by drap and drop or", | ||
AxelPeter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| dropAreaSelector: "select a file", | ||
| fileSelector: "Select file", | ||
| filesSelector: "Select file(s)...", | ||
| maxsizeError: "This file exceeds the size limit", | ||
| removeFile: "Remove file from selector" | ||
| }); | ||
| ouiFileConfigurationProvider.setUnits([ // default units | ||
| { size: 1000000000, suffix: "GB" }, | ||
| { size: 1000000, suffix: "MB" }, | ||
| { size: 1000, suffix: "KB" }, | ||
| { size: 1, suffix: "B" } | ||
| ]); | ||
| }); | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "name": "@ovh-ui/oui-file", | ||
| "version": "1.0.0", | ||
| "main": "./src/index.js", | ||
| "license": "BSD-3-Clause", | ||
| "author": "OVH SAS" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import controller from "./file.controller"; | ||
| import template from "./file.html"; | ||
|
|
||
| export default { | ||
| require: { | ||
| form: "?^^form" | ||
| }, | ||
| bindings: { | ||
| model: "=", | ||
| id: "@?", | ||
| name: "@?", | ||
| placeholder: "@?", | ||
| accept: "@?", | ||
| maxsize: "<?", | ||
| disabled: "<?", | ||
| required: "<?", | ||
| multiple: "<?", | ||
| droparea: "<?", | ||
| preview: "<?", | ||
| onSelect: "&" | ||
| }, | ||
| controller, | ||
| template | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| import { addBooleanParameter, addDefaultParameter } from "@ovh-ui/common/component-utils"; | ||
| import find from "lodash/find"; | ||
| import isEmpty from "lodash/isEmpty"; | ||
| import remove from "lodash/remove"; | ||
|
|
||
| export default class { | ||
| constructor ($attrs, $compile, $element, $filter, $scope, $timeout, $window, ouiFileConfiguration) { | ||
| "ngInject"; | ||
|
|
||
| this.$attrs = $attrs; | ||
| this.$compile = $compile; | ||
| this.$element = $element; | ||
| this.$filter = $filter; | ||
| this.$scope = $scope; | ||
| this.$timeout = $timeout; | ||
| this.$window = $window; | ||
|
|
||
| this.translations = ouiFileConfiguration.translations; | ||
| this.units = this.$filter("orderBy")(ouiFileConfiguration.units, "-size"); | ||
| } | ||
|
|
||
| checkFileValidity (file) { | ||
| if (file) { | ||
| file.errors = {}; | ||
|
|
||
| // Check maxsize | ||
| if (this.maxsize && file.size > this.maxsize) { | ||
| file.errors.maxsize = true; | ||
| } | ||
|
|
||
| // Set form validation | ||
| if (this.form && this.form[this.name]) { | ||
| this.form[this.name].$setValidity("maxsize", !file.errors.maxsize); | ||
| this.form[this.name].$setDirty(); | ||
| } | ||
|
|
||
| // Clean errors | ||
| if (isEmpty(file.errors)) { | ||
| delete file.errors; | ||
| } | ||
| } | ||
|
|
||
| return file; | ||
| } | ||
|
|
||
| loadFilePreview (file) { | ||
| // Load preview only if image | ||
| if (this.preview && !file.errors && file.type && file.type.search(/^image\//) !== -1) { | ||
| file.loading = true; | ||
|
|
||
| file.reader = new this.$window.FileReader(); | ||
| file.reader.readAsDataURL(file); | ||
| file.reader.onload = () => { | ||
| file.preview = `url("${file.reader.result}")`; | ||
|
|
||
| this.$scope.$apply(); | ||
| }; | ||
| } | ||
|
|
||
| return file; | ||
| } | ||
|
|
||
| addFile (file) { | ||
| this.getFileInfos(file); | ||
| this.checkFileValidity(file); | ||
|
|
||
| this.model = [file]; | ||
| this.onSelect({ modelValue: this.model }); | ||
| this.$scope.$apply(); | ||
|
|
||
| // Set back focus on fake selector | ||
| this.$element[0].querySelector(".oui-file-selector__label").focus(); | ||
| } | ||
|
|
||
| addFiles (files) { | ||
| if (!this.model) { | ||
| this.model = []; | ||
| } | ||
|
|
||
| if (angular.isArray(files)) { | ||
| files.forEach((file) => { | ||
| // Check for duplicate before adding | ||
| if (!find(this.model, (item) => file.name === item.name)) { | ||
| this.getFileInfos(file); | ||
| this.checkFileValidity(file); | ||
| this.loadFilePreview(file); | ||
| this.model.push(file); | ||
| } | ||
| }); | ||
|
|
||
| this.onSelect({ modelValue: this.model }); | ||
| this.$scope.$apply(); | ||
| } | ||
| } | ||
|
|
||
| removeFile (file) { | ||
| if (angular.isArray(this.model)) { | ||
| remove(this.model, (item) => item === file); | ||
AxelPeter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| resetFile () { | ||
| this.model = undefined; | ||
| this.fileSelector[0].value = ""; | ||
|
|
||
| if (this.form && this.form[this.name]) { | ||
| this.form[this.name].$setValidity("maxsize", true); | ||
| } | ||
| } | ||
|
|
||
| openFileSelector () { | ||
| // triggerHandler("click") don't work here | ||
| this.fileSelector[0].click(); | ||
| } | ||
|
|
||
| getFileInfos (file) { | ||
| const parts = file.name.split("."); | ||
AxelPeter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const extension = parts.length > 1 ? parts.pop() : undefined; | ||
| const name = parts.join("."); | ||
|
|
||
| file.infos = { | ||
| extension, | ||
| name, | ||
| size: this.getFileSize(file) | ||
| }; | ||
|
|
||
| return file; | ||
| } | ||
|
|
||
| getFileSize (file) { | ||
| let size; | ||
|
|
||
| // Get best extension for file size | ||
| for (const unit of this.units) { | ||
| size = Math.floor(file.size / unit.size); | ||
| if (size > 1) { | ||
| size = `(${size} ${unit.suffix})`; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return size; | ||
| } | ||
|
|
||
| setInputTouched () { | ||
| if (this.form && this.form[this.name]) { | ||
| // Set input[hidden] to touched for form validation | ||
| this.form[this.name].$setTouched(); | ||
| } | ||
| } | ||
|
|
||
| $onInit () { | ||
| addBooleanParameter(this, "disabled"); | ||
| addBooleanParameter(this, "required"); | ||
| addBooleanParameter(this, "multiple"); | ||
| addBooleanParameter(this, "droparea"); | ||
| addBooleanParameter(this, "preview"); | ||
|
|
||
| addDefaultParameter(this, "id", `ouiFile${this.$scope.$id}`); | ||
| addDefaultParameter(this, "name", `ouiFile${this.$scope.$id}`); | ||
|
|
||
| this.selectorId = `${this.id}Selector`; | ||
| this.dropareaId = `${this.id}Droparea`; | ||
| this.attachments = Boolean(this.multiple || this.droparea || this.preview); | ||
| } | ||
|
|
||
| $postLink () { | ||
| this.$timeout(() => { | ||
| this.$element | ||
| .addClass("oui-file") | ||
| .removeAttr("id") | ||
| .removeAttr("name"); | ||
|
|
||
| // ngChange don't work on input file | ||
| this.fileSelector = angular.element(this.$element[0].querySelector(`#${this.selectorId}`)); | ||
| this.fileSelector.on("change", (e) => { | ||
| if (this.attachments) { | ||
| // FileList from input file is read-only | ||
| // Needed to be port as an array for manipulation | ||
| this.addFiles(Array.from(e.target.files)); | ||
| } else { | ||
| this.addFile(e.target.files[0]); | ||
| } | ||
| }); | ||
|
|
||
| if (this.droparea) { | ||
| this.fileDroparea = angular.element(this.$element[0].querySelector(`#${this.dropareaId}`)); | ||
| this.fileDroparea | ||
| .on("drag dragstart dragend dragover dragenter dragleave drop", (e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| }) | ||
| .on("dragover dragenter", () => this.fileDroparea.addClass("oui-file-droparea_dragover")) | ||
| .on("dragleave dragend drop", () => this.fileDroparea.removeClass("oui-file-droparea_dragover")) | ||
| .on("drop", (e) => { | ||
| // FileList from input file is read-only | ||
| // Needed to be port as an array for manipulation | ||
| if (e.dataTransfer) { | ||
| this.addFiles(Array.from(e.dataTransfer.files)); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.