mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 20:06:18 +08:00
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:
parent
366238bb81
commit
27b97e4f64
|
@ -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();
|
||||
},
|
||||
});
|
|
@ -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}}
|
|
@ -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);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
|
@ -17,6 +17,7 @@
|
|||
@import "ignored-user-list";
|
||||
@import "keyboard_shortcuts";
|
||||
@import "navs";
|
||||
@import "pick-files-button";
|
||||
@import "relative-time-picker";
|
||||
@import "share-and-invite-modal";
|
||||
@import "svg";
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
.pick-files-button {
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -3784,6 +3784,9 @@ en:
|
|||
leader: "leader"
|
||||
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
|
||||
admin_js:
|
||||
type_to_filter: "type to filter..."
|
||||
|
|
Loading…
Reference in New Issue
Block a user