DEV: add pick-files-button component (#13764)

* DEV: add pick-files-button component
* Scope querySelector to the component, add removeEventListener, fix formatting
This commit is contained in:
Andrei Prigorshnev 2021-07-16 21:50:50 +04:00 committed by GitHub
parent 366238bb81
commit 27b97e4f64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 0 deletions

View File

@ -0,0 +1,106 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { empty } from "@ember/object/computed";
import { bind, default as computed } from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Component.extend({
classNames: ["pick-files-button"],
acceptedFileTypes: null,
acceptAnyFile: empty("acceptedFileTypes"),
didInsertElement() {
this._super(...arguments);
const fileInput = this.element.querySelector("input");
this.set("fileInput", fileInput);
fileInput.addEventListener("change", this.onChange, false);
},
willDestroyElement() {
this._super(...arguments);
this.fileInput.removeEventListener("change", this.onChange);
},
@bind
onChange() {
const files = this.fileInput.files;
this._filesPicked(files);
},
@computed
acceptedFileTypesString() {
if (!this.acceptedFileTypes) {
return null;
}
return this.acceptedFileTypes.join(",");
},
@computed
acceptedExtensions() {
if (!this.acceptedFileTypes) {
return null;
}
return this.acceptedFileTypes
.filter((type) => type.startsWith("."))
.map((type) => type.substring(1));
},
@computed
acceptedMimeTypes() {
if (!this.acceptedFileTypes) {
return null;
}
return this.acceptedFileTypes.filter((type) => !type.startsWith("."));
},
@action
openSystemFilePicker() {
this.fileInput.click();
},
_filesPicked(files) {
if (!files || !files.length) {
return;
}
if (!this._haveAcceptedTypes(files)) {
const message = I18n.t("pick_files_button.unsupported_file_picked", {
types: this.acceptedFileTypesString,
});
bootbox.alert(message);
return;
}
this.onFilesPicked(files);
},
_haveAcceptedTypes(files) {
for (const file of files) {
if (
!(this._hasAcceptedExtension(file) && this._hasAcceptedMimeType(file))
) {
return false;
}
}
return true;
},
_hasAcceptedExtension(file) {
const extension = this._fileExtension(file.name);
return (
!this.acceptedExtensions || this.acceptedExtensions.includes(extension)
);
},
_hasAcceptedMimeType(file) {
return (
!this.acceptedMimeTypes || this.acceptedMimeTypes.includes(file.type)
);
},
_fileExtension(fileName) {
return fileName.split(".").pop();
},
});

View File

@ -0,0 +1,6 @@
{{d-button action=(action "openSystemFilePicker") label=label icon=icon}}
{{#if acceptAnyFile}}
<input type="file">
{{else}}
<input type="file" accept={{acceptedFileTypesString}}>
{{/if}}

View File

@ -0,0 +1,78 @@
import componentTest, {
setupRenderingTest,
} from "discourse/tests/helpers/component-test";
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
import { triggerEvent } from "@ember/test-helpers";
import sinon from "sinon";
function createBlob(mimeType, extension) {
const blob = new Blob(["content"], {
type: mimeType,
});
blob.name = `filename${extension}`;
return blob;
}
discourseModule(
"Integration | Component | pick-files-button",
function (hooks) {
setupRenderingTest(hooks);
componentTest(
"it shows alert if a file with an unsupported extension was chosen",
{
skip: true,
template: hbs`
{{pick-files-button
acceptedFileTypes=this.acceptedFileTypes
onFilesChosen=this.onFilesChosen}}`,
beforeEach() {
const expectedExtension = ".json";
this.set("acceptedFileTypes", [expectedExtension]);
this.set("onFilesChosen", () => {});
},
async test(assert) {
sinon.stub(bootbox, "alert");
const wrongExtension = ".txt";
const file = createBlob("text/json", wrongExtension);
await triggerEvent("input#file-input", "change", { files: [file] });
assert.ok(bootbox.alert.calledOnce);
},
}
);
componentTest(
"it shows alert if a file with an unsupported MIME type was chosen",
{
skip: true,
template: hbs`
{{pick-files-button
acceptedFileTypes=this.acceptedFileTypes
onFilesChosen=this.onFilesChosen}}`,
beforeEach() {
const expectedMimeType = "text/json";
this.set("acceptedFileTypes", [expectedMimeType]);
this.set("onFilesChosen", () => {});
},
async test(assert) {
sinon.stub(bootbox, "alert");
const wrongMimeType = "text/plain";
const file = createBlob(wrongMimeType, ".json");
await triggerEvent("input#file-input", "change", { files: [file] });
assert.ok(bootbox.alert.calledOnce);
},
}
);
}
);

View File

@ -17,6 +17,7 @@
@import "ignored-user-list"; @import "ignored-user-list";
@import "keyboard_shortcuts"; @import "keyboard_shortcuts";
@import "navs"; @import "navs";
@import "pick-files-button";
@import "relative-time-picker"; @import "relative-time-picker";
@import "share-and-invite-modal"; @import "share-and-invite-modal";
@import "svg"; @import "svg";

View File

@ -0,0 +1,5 @@
.pick-files-button {
input[type="file"] {
display: none;
}
}

View File

@ -3784,6 +3784,9 @@ en:
leader: "leader" leader: "leader"
detailed_name: "%{level}: %{name}" detailed_name: "%{level}: %{name}"
pick_files_button:
unsupported_file_picked: "You have picked an unsupported file. Supported file types %{types}."
# This section is exported to the javascript for i18n in the admin section # This section is exported to the javascript for i18n in the admin section
admin_js: admin_js:
type_to_filter: "type to filter..." type_to_filter: "type to filter..."