From 72d26fa019e835ba8f7c32ebc921495a5dd0a94c Mon Sep 17 00:00:00 2001 From: Christoph Reiter <reiter.christoph@gmail.com> Date: Mon, 20 Apr 2020 17:18:47 +0200 Subject: [PATCH] Port to scoped elements --- packages/file-handling/package.json | 1 + packages/file-handling/rollup.config.js | 2 +- packages/file-handling/src/demo.js | 11 +- packages/file-handling/src/fileupload.js | 243 +++++++++++++++++++ packages/file-handling/src/index.js | 4 +- packages/file-handling/src/vpu-fileupload.js | 238 +----------------- packages/file-handling/vendor/common | 2 +- 7 files changed, 260 insertions(+), 241 deletions(-) create mode 100644 packages/file-handling/src/fileupload.js diff --git a/packages/file-handling/package.json b/packages/file-handling/package.json index fd79b40a..5d40d4f6 100644 --- a/packages/file-handling/package.json +++ b/packages/file-handling/package.json @@ -24,6 +24,7 @@ "vpu-common": "file:./vendor/common" }, "dependencies": { + "@open-wc/scoped-elements": "^1.0.8", "i18next": "^19.4.2", "lit-element": "^2.1.0", "lit-html": "^1.1.1", diff --git a/packages/file-handling/rollup.config.js b/packages/file-handling/rollup.config.js index 036b7b8c..8fbc8e44 100644 --- a/packages/file-handling/rollup.config.js +++ b/packages/file-handling/rollup.config.js @@ -13,7 +13,7 @@ const build = (typeof process.env.BUILD !== 'undefined') ? process.env.BUILD : ' console.log("build: " + build); export default { - input: (build !== 'test') ? 'src/demo.js' : glob.sync('test/**/*.js'), + input: (build !== 'test') ? ['src/demo.js', 'src/vpu-fileupload.js'] : glob.sync('test/**/*.js'), output: { dir: 'dist', entryFileNames: '[name].js', diff --git a/packages/file-handling/src/demo.js b/packages/file-handling/src/demo.js index 75fbaafb..cdbb663a 100644 --- a/packages/file-handling/src/demo.js +++ b/packages/file-handling/src/demo.js @@ -1,16 +1,23 @@ import {i18n} from './i18n'; import {html, LitElement} from 'lit-element'; import {unsafeHTML} from 'lit-html/directives/unsafe-html.js'; -import './vpu-fileupload'; +import {ScopedElementsMixin} from '@open-wc/scoped-elements'; +import {FileUpload} from './fileupload.js'; import * as commonUtils from 'vpu-common/utils'; -class FileUploadDemo extends LitElement { +class FileUploadDemo extends ScopedElementsMixin(LitElement) { constructor() { super(); this.lang = 'de'; this.url = ''; } + static get scopedElements() { + return { + 'vpu-fileupload': FileUpload, + }; + } + static get properties() { return { lang: { type: String }, diff --git a/packages/file-handling/src/fileupload.js b/packages/file-handling/src/fileupload.js new file mode 100644 index 00000000..f4073bcb --- /dev/null +++ b/packages/file-handling/src/fileupload.js @@ -0,0 +1,243 @@ +import {i18n} from './i18n'; +import {css, html} from 'lit-element'; +import {ifDefined} from 'lit-html/directives/if-defined'; +import {ScopedElementsMixin} from '@open-wc/scoped-elements'; +// import JSONLD from 'vpu-common/jsonld'; +import VPULitElement from 'vpu-common/vpu-lit-element' +import "vpu-common/vpu-mini-spinner.js"; +import * as commonUtils from "vpu-common/utils"; +import {Icon} from 'vpu-common'; +import * as commonStyles from 'vpu-common/styles'; + +/** + * KnowledgeBaseWebPageElementView web component + */ +export class FileUpload extends ScopedElementsMixin(VPULitElement) { + constructor() { + super(); + this.lang = 'de'; + this.url = ''; + this.dropArea = null; + this.accept = ''; + this.text = ''; + this.buttonLabel = ''; + this.uploadInProgress = false; + } + + static get scopedElements() { + return { + 'vpu-icon': Icon, + }; + } + + /** + * See: https://lit-element.polymer-project.org/guide/properties#initialize + */ + static get properties() { + return { + lang: { type: String }, + url: { type: String }, + accept: { type: String }, + text: { type: String }, + buttonLabel: { type: String, attribute: 'button-label'}, + uploadInProgress: { type: Boolean, attribute: false}, + }; + } + + + update(changedProperties) { + changedProperties.forEach((oldValue, propName) => { + if (propName === "lang") { + i18n.changeLanguage(this.lang); + } + }); + + super.update(changedProperties); + } + + connectedCallback() { + super.connectedCallback(); + + this.updateComplete.then(() => { + this.dropArea = this._('#dropArea'); + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { + this.dropArea.addEventListener(eventName, this.preventDefaults, false) + }); + ['dragenter', 'dragover'].forEach(eventName => { + this.dropArea.addEventListener(eventName, this.highlight.bind(this), false) + }); + ['dragleave', 'drop'].forEach(eventName => { + this.dropArea.addEventListener(eventName, this.unhighlight.bind(this), false) + }); + this.dropArea.addEventListener('drop', this.handleDrop.bind(this), false); + this._('#fileElem').addEventListener('change', this.handleChange.bind(this)); + }); + } + + preventDefaults (e) { + e.preventDefault(); + e.stopPropagation(); + } + + highlight(e) { + if (this.uploadInProgress) { + return; + } + + this.dropArea.classList.add('highlight') + } + + unhighlight(e) { + this.dropArea.classList.remove('highlight') + } + + handleDrop(e) { + if (this.uploadInProgress) { + return; + } + + let dt = e.dataTransfer; + console.dir(dt); + let files = dt.files; + + this.handleFiles(files); + } + + async handleChange(e) { + let fileElem = this._('#fileElem'); + + if (fileElem.files.length === 0) { + return; + } + + await this.handleFiles(fileElem.files); + + // reset the element's value so the user can upload the same file(s) again + fileElem.value = ''; + } + + async handleFiles(files) { + console.log('handleFiles: files.length = ' + files.length); + + this.dispatchEvent(new CustomEvent("vpu-fileupload-all-start", + { "detail": {}, bubbles: true, composed: true })); + + // we need to wait for each upload until we start the next one + await commonUtils.asyncArrayForEach(files, async (file) => this.uploadFile(file)); + + this.dispatchEvent(new CustomEvent("vpu-fileupload-all-finished", + { "detail": {}, bubbles: true, composed: true })); + } + + async sendFinishedEvent(response, file, sendFile = false) { + if (response === undefined) { + return; + } + + let data = { + fileName: file.name, + status: response.status, + json: {"hydra:description": ""} + }; + + try { + await response.json().then((json) => { + data.json = json; + }); + } catch (e) {} + + if (sendFile) { + data.file = file; + } + + const event = new CustomEvent("vpu-fileupload-file-finished", { "detail": data, bubbles: true, composed: true }); + this.dispatchEvent(event); + } + + sendStartEvent(file) { + let data = { + fileName: file.name, + fileSize: file.size, + }; + + this.dispatchEvent(new CustomEvent("vpu-fileupload-file-start", + { "detail": data, bubbles: true, composed: true })); + } + + /** + * @param file + * @returns {Promise<void>} + */ + async uploadFile(file) { + this.uploadInProgress = true; + this.sendStartEvent(file); + let url = this.url; + let formData = new FormData(); + + formData.append('file', file); + + // I got a 60s timeout in Google Chrome and found no way to increase that + await fetch(url, { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + window.VPUAuthToken, + }, + body: formData + }) + .then((response) => { + /* Done. Inform the user */ + console.log(`Status: ${response.status} for file ${file.name}`); + this.sendFinishedEvent(response, file, response.status !== 201); + }) + .catch((response) => { + /* Error. Inform the user */ + console.log(`Error status: ${response.status} for file ${file.name}`); + this.sendFinishedEvent(response, file, true); + }); + + this.uploadInProgress = false; + } + + static get styles() { + // language=css + return css` + ${commonStyles.getButtonCSS()} + + #dropArea { + border: var(--FUBorderWidth, 2px) var(--FUBorderStyle, dashed) var(--FUBBorderColor, black); + border-radius: var(--FUBorderRadius, 0); + width: var(--FUWidth, auto); + margin: var(--FUMargin, 0px); + padding: var(--FUPadding, 20px); + } + + #dropArea.highlight { + border-color: var(--FUBorderColorHighlight, purple); + } + + p { + margin-top: 0; + } + + .my-form { + margin-bottom: 10px; + } + + #fileElem { + display: none; + } + `; + } + + render() { + return html` + <div id="dropArea"> + <div class="my-form" title="${this.uploadInProgress ? i18n.t('upload-disabled-title') : ''}"> + <p>${this.text || i18n.t('intro')}</p> + <input ?disabled="${this.uploadInProgress}" type="file" id="fileElem" multiple accept="${ifDefined(this.accept)}" name='file'> + <label class="button is-primary" for="fileElem"><vpu-icon style="display: ${this.uploadInProgress ? "inline-block" : "none"}" name="lock"></vpu-icon> ${this.buttonLabel || i18n.t('upload-label')}</label> + </div> + </div> + `; + } +} \ No newline at end of file diff --git a/packages/file-handling/src/index.js b/packages/file-handling/src/index.js index c986b9a9..a71fa7f9 100644 --- a/packages/file-handling/src/index.js +++ b/packages/file-handling/src/index.js @@ -1 +1,3 @@ -import './vpu-fileupload'; +import {FileUpload} from './fileupload'; + +export {FileUpload}; \ No newline at end of file diff --git a/packages/file-handling/src/vpu-fileupload.js b/packages/file-handling/src/vpu-fileupload.js index 53b422c9..d1b82789 100644 --- a/packages/file-handling/src/vpu-fileupload.js +++ b/packages/file-handling/src/vpu-fileupload.js @@ -1,238 +1,4 @@ -import {i18n} from './i18n'; -import {css, html} from 'lit-element'; -import {ifDefined} from 'lit-html/directives/if-defined'; -// import JSONLD from 'vpu-common/jsonld'; -import VPULitElement from 'vpu-common/vpu-lit-element' -import "vpu-common/vpu-mini-spinner.js"; import * as commonUtils from "vpu-common/utils"; -import 'vpu-common/vpu-icon.js'; -import * as commonStyles from 'vpu-common/styles'; +import {FileUpload} from './fileupload'; -/** - * KnowledgeBaseWebPageElementView web component - */ -class VPUFileUpload extends VPULitElement { - constructor() { - super(); - this.lang = 'de'; - this.url = ''; - this.dropArea = null; - this.accept = ''; - this.text = ''; - this.buttonLabel = ''; - this.uploadInProgress = false; - } - - /** - * See: https://lit-element.polymer-project.org/guide/properties#initialize - */ - static get properties() { - return { - lang: { type: String }, - url: { type: String }, - accept: { type: String }, - text: { type: String }, - buttonLabel: { type: String, attribute: 'button-label'}, - uploadInProgress: { type: Boolean, attribute: false}, - }; - } - - - update(changedProperties) { - changedProperties.forEach((oldValue, propName) => { - if (propName === "lang") { - i18n.changeLanguage(this.lang); - } - }); - - super.update(changedProperties); - } - - connectedCallback() { - super.connectedCallback(); - - this.updateComplete.then(() => { - this.dropArea = this._('#dropArea'); - ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { - this.dropArea.addEventListener(eventName, this.preventDefaults, false) - }); - ['dragenter', 'dragover'].forEach(eventName => { - this.dropArea.addEventListener(eventName, this.highlight.bind(this), false) - }); - ['dragleave', 'drop'].forEach(eventName => { - this.dropArea.addEventListener(eventName, this.unhighlight.bind(this), false) - }); - this.dropArea.addEventListener('drop', this.handleDrop.bind(this), false); - this._('#fileElem').addEventListener('change', this.handleChange.bind(this)); - }); - } - - preventDefaults (e) { - e.preventDefault(); - e.stopPropagation(); - } - - highlight(e) { - if (this.uploadInProgress) { - return; - } - - this.dropArea.classList.add('highlight') - } - - unhighlight(e) { - this.dropArea.classList.remove('highlight') - } - - handleDrop(e) { - if (this.uploadInProgress) { - return; - } - - let dt = e.dataTransfer; - console.dir(dt); - let files = dt.files; - - this.handleFiles(files); - } - - async handleChange(e) { - let fileElem = this._('#fileElem'); - - if (fileElem.files.length === 0) { - return; - } - - await this.handleFiles(fileElem.files); - - // reset the element's value so the user can upload the same file(s) again - fileElem.value = ''; - } - - async handleFiles(files) { - console.log('handleFiles: files.length = ' + files.length); - - this.dispatchEvent(new CustomEvent("vpu-fileupload-all-start", - { "detail": {}, bubbles: true, composed: true })); - - // we need to wait for each upload until we start the next one - await commonUtils.asyncArrayForEach(files, async (file) => this.uploadFile(file)); - - this.dispatchEvent(new CustomEvent("vpu-fileupload-all-finished", - { "detail": {}, bubbles: true, composed: true })); - } - - async sendFinishedEvent(response, file, sendFile = false) { - if (response === undefined) { - return; - } - - let data = { - fileName: file.name, - status: response.status, - json: {"hydra:description": ""} - }; - - try { - await response.json().then((json) => { - data.json = json; - }); - } catch (e) {} - - if (sendFile) { - data.file = file; - } - - const event = new CustomEvent("vpu-fileupload-file-finished", { "detail": data, bubbles: true, composed: true }); - this.dispatchEvent(event); - } - - sendStartEvent(file) { - let data = { - fileName: file.name, - fileSize: file.size, - }; - - this.dispatchEvent(new CustomEvent("vpu-fileupload-file-start", - { "detail": data, bubbles: true, composed: true })); - } - - /** - * @param file - * @returns {Promise<void>} - */ - async uploadFile(file) { - this.uploadInProgress = true; - this.sendStartEvent(file); - let url = this.url; - let formData = new FormData(); - - formData.append('file', file); - - // I got a 60s timeout in Google Chrome and found no way to increase that - await fetch(url, { - method: 'POST', - headers: { - 'Authorization': 'Bearer ' + window.VPUAuthToken, - }, - body: formData - }) - .then((response) => { - /* Done. Inform the user */ - console.log(`Status: ${response.status} for file ${file.name}`); - this.sendFinishedEvent(response, file, response.status !== 201); - }) - .catch((response) => { - /* Error. Inform the user */ - console.log(`Error status: ${response.status} for file ${file.name}`); - this.sendFinishedEvent(response, file, true); - }); - - this.uploadInProgress = false; - } - - static get styles() { - // language=css - return css` - ${commonStyles.getButtonCSS()} - - #dropArea { - border: var(--FUBorderWidth, 2px) var(--FUBorderStyle, dashed) var(--FUBBorderColor, black); - border-radius: var(--FUBorderRadius, 0); - width: var(--FUWidth, auto); - margin: var(--FUMargin, 0px); - padding: var(--FUPadding, 20px); - } - - #dropArea.highlight { - border-color: var(--FUBorderColorHighlight, purple); - } - - p { - margin-top: 0; - } - - .my-form { - margin-bottom: 10px; - } - - #fileElem { - display: none; - } - `; - } - - render() { - return html` - <div id="dropArea"> - <div class="my-form" title="${this.uploadInProgress ? i18n.t('upload-disabled-title') : ''}"> - <p>${this.text || i18n.t('intro')}</p> - <input ?disabled="${this.uploadInProgress}" type="file" id="fileElem" multiple accept="${ifDefined(this.accept)}" name='file'> - <label class="button is-primary" for="fileElem"><vpu-icon style="display: ${this.uploadInProgress ? "inline-block" : "none"}" name="lock"></vpu-icon> ${this.buttonLabel || i18n.t('upload-label')}</label> - </div> - </div> - `; - } -} - -commonUtils.defineCustomElement('vpu-fileupload', VPUFileUpload); +commonUtils.defineCustomElement('vpu-fileupload', FileUpload); diff --git a/packages/file-handling/vendor/common b/packages/file-handling/vendor/common index 4f1d5c50..010b54fa 160000 --- a/packages/file-handling/vendor/common +++ b/packages/file-handling/vendor/common @@ -1 +1 @@ -Subproject commit 4f1d5c504ad5935b8903ab37abcbaa2b588112bd +Subproject commit 010b54fa455829d732152b6794c7a92a874249aa -- GitLab