Skip to content
This repository was archived by the owner on Aug 7, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/oui-angular/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Datagrid from "@ovh-ui/oui-datagrid";
import Dropdown from "@ovh-ui/oui-dropdown";
import DualList from "@ovh-ui/oui-dual-list";
import Field from "@ovh-ui/oui-field";
import File from "@ovh-ui/oui-file";
import FormActions from "@ovh-ui/oui-form-actions";
import GuideMenu from "@ovh-ui/oui-guide-menu";
import HeaderTabs from "@ovh-ui/oui-header-tabs";
Expand Down Expand Up @@ -56,6 +57,7 @@ export default angular
Dropdown,
DualList,
Field,
File,
FormActions,
GuideMenu,
HeaderTabs,
Expand Down
1 change: 1 addition & 0 deletions packages/oui-angular/src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ loadTests(require.context("../../oui-datagrid/src/", true, /.*((\.spec)|(index))
loadTests(require.context("../../oui-dropdown/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-dual-list/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-field/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-file/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-form-actions/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-guide-menu/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-header-tabs/src/", true, /.*((\.spec)|(index))$/));
Expand Down
2 changes: 1 addition & 1 deletion packages/oui-field/src/field.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
ng-if="$ctrl.isErrorVisible()">
<span ng-messages="$ctrl.getFirstError()" role="alert">
<span class="oui-icon oui-icon-error" aria-hidden="true"></span>
<span data-ng-repeat="errorKey in $ctrl.getMessagesOrder() track by $index"
<span data-ng-repeat="errorKey in $ctrl.getMessagesOrder()"
ng-message="{{ :: errorKey }}"
ng-bind="$ctrl.getErrorMessage(errorKey)">
</span>
Expand Down
1 change: 1 addition & 0 deletions packages/oui-field/src/field.provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class {
max: "Too high ({{max}} max).",
minlength: "Too short ({{minlength}} characters min).",
maxlength: "Too high ({{maxlength}} characters max).",
maxsize: "This file exceeds the size limit",
pattern: "Invalid format."
}
};
Expand Down
118 changes: 118 additions & 0 deletions packages/oui-file/README.md
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&lt;file&gt; | = | no | n/a | n/a | model bound to component
| `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
| `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",
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" }
]);
});
```
7 changes: 7 additions & 0 deletions packages/oui-file/package.json
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"
}
24 changes: 24 additions & 0 deletions packages/oui-file/src/file.component.js
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
};
205 changes: 205 additions & 0 deletions packages/oui-file/src/file.controller.js
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);
}
}

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(".");
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));
}
});
}
});
}
}
Loading