Newer
Older
import {createInstance} from './i18n';
import {ScopedElementsMixin} from '@open-wc/scoped-elements';
import * as commonUtils from '@dbp-toolkit/common/utils';
import * as commonStyles from '@dbp-toolkit/common/styles';
import * as fileHandlingStyles from '@dbp-toolkit/file-handling/src/styles';
import {Icon, getShadowRootDocument} from '@dbp-toolkit/common';
import {TabulatorFull as Tabulator} from 'tabulator-tables';
import {humanFileSize} from '@dbp-toolkit/common/i18next';
import {name as pkgName} from '@dbp-toolkit/file-handling/package.json';
import {send} from '@dbp-toolkit/common/notification';
import {AdapterLitElement} from '@dbp-toolkit/provider/src/adapter-lit-element';
import {classMap} from 'lit/directives/class-map.js';
const MODE_TABLE_ONLY = 'table-only';
const MODE_FILE_SINK = 'file-sink';
const MODE_FILE_SOURCE = 'file-source';
export class Clipboard extends ScopedElementsMixin(AdapterLitElement) {
constructor() {
super();
this._i18n = createInstance();
this.lang = this._i18n.language;
this.clipboardFiles = {files: ''};
this.clipboardSelectBtnDisabled = true;
this.tabulatorTable = null;
this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this);
this.boundSelectHandler = this.selectAllFiles.bind(this);
this.filesToSave = [];
this.numberOfSelectedFiles = 0;
this.enabledTargets = 'local';
this.countUploadFiles = 0;
this.buttonsDisabled = false;
this.nextcloudWebAppPasswordURL = '';
this.nextcloudWebDavURL = '';
this.nextcloudName = '';
this.nextcloudFileURL = '';
this.nextcloudStoreSession = false;
// To avoid a cyclic dependency
import('./file-sink').then(({FileSink}) =>
this.defineScopedElement('dbp-file-sink', FileSink)
);
import('./file-source').then(({FileSource}) =>
this.defineScopedElement('dbp-file-source', FileSource)
);
}
static get scopedElements() {
return {
'dbp-icon': Icon,
};
}
static get properties() {
return {
...super.properties,
lang: {type: String},
allowedMimeTypes: {type: String, attribute: 'allowed-mime-types'},
clipboardSelectBtnDisabled: {type: Boolean},
clipboardFiles: {type: Object, attribute: 'clipboard-files'},
filesToSave: {type: Array, attribute: 'files-to-save'},
numberOfSelectedFiles: {type: Number, attribute: false},
enabledTargets: {type: String, attribute: 'enabled-targets'},
nextcloudWebAppPasswordURL: {type: String, attribute: 'nextcloud-auth-url'},
nextcloudWebDavURL: {type: String, attribute: 'nextcloud-web-dav-url'},
nextcloudName: {type: String, attribute: 'nextcloud-name'},
nextcloudFileURL: {type: String, attribute: 'nextcloud-file-url'},
nextcloudAuthInfo: {type: String, attribute: 'nextcloud-auth-info'},
nextcloudStoreSession: {type: Boolean, attribute: 'nextcloud-store-session'},
mode: {type: String, attribute: 'mode'},
allowNesting: {type: Boolean, attribute: 'allow-nesting'},
return this.shadowRoot === null
? this.querySelector(selector)
: this.shadowRoot.querySelector(selector);
Steinwender, Tamara
committed
_a(selector) {
return this.shadowRoot === null
? this.querySelectorAll(selector)
: this.shadowRoot.querySelectorAll(selector);
Steinwender, Tamara
committed
}
update(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
switch (propName) {
this._i18n.changeLanguage(this.lang);
if (this.tabulatorTable)
this.generateClipboardTable();
break;
}
});
super.update(changedProperties);
}
Steinwender, Tamara
committed
toggleCollapse(e) {
const table = this.tabulatorTable;
// give a chance to draw the table
// this is for getting more hight in tabulator table, when toggle is called
Steinwender, Tamara
committed
table.redraw();
}, 0);
}
const i18n = this._i18n;
super.connectedCallback();
const that = this;
this.updateComplete.then(() => {
// see: http://tabulator.info/docs/4.7
this.tabulatorTable = new Tabulator(this._('#clipboard-content-table'), {
layout: 'fitColumns',
responsiveLayout: 'collapse',
responsiveLayoutCollapseStartOpen: false,
title:
'<label class="button-container select-all-icon">' +
'<input type="checkbox" id="select_all" name="select_all" value="select_all">' +
'<span class="checkmark" id="select_all_checkmark"></span>' +
'</label>',
responsive: 1,
formatter: (cell, formatterParams, onRendered) => {
const icon_tag = that.getScopedTagName('dbp-icon');
let icon =
`<${icon_tag} name="empty-file" class="nextcloud-picker-icon ` +
`"></${icon_tag}>`;
let div = getShadowRootDocument(this).createElement('div');
div.innerHTML = icon;
return div;
responsive: 0,
widthGrow: 5,
minWidth: 150,
var data = cell.getValue();
let div = getShadowRootDocument(this).createElement('div');
div.classList.add('filename');
div.innerHTML = cell.getValue();
return div;
formatter: (cell, formatterParams, onRendered) => {
return humanFileSize(cell.getValue());
formatter: (cell, formatterParams, onRendered) => {
if (typeof cell.getValue() === 'undefined') {
}
const [, fileSubType] = cell.getValue().split('/');
return fileSubType;
responsive: 3,
widthGrow: 1,
minWidth: 150,
sorter: (a, b, aRow, bRow, column, dir, sorterParams) => {
const a_timestamp = Date.parse(a);
const b_timestamp = Date.parse(b);
return a_timestamp - b_timestamp;
},
formatter: function (cell, formatterParams, onRendered) {
const timestamp = new Date(cell.getValue());
const year = timestamp.getFullYear();
const month = ('0' + (timestamp.getMonth() + 1)).slice(-2);
const date = ('0' + timestamp.getDate()).slice(-2);
const hours = ('0' + timestamp.getHours()).slice(-2);
const minutes = ('0' + timestamp.getMinutes()).slice(-2);
return date + '.' + month + '.' + year + ' ' + hours + ':' + minutes;
},
{column: 'name', dir: 'asc'},
{column: 'type', dir: 'asc'},
this.tabulatorTable.on("tableBuilt", this.tableBuiltFunction.bind(this));
this.tabulatorTable.on("rowClick", this.rowClickFunction.bind(this));
this.tabulatorTable.on("rowSelectionChanged", this.rowSelectionChangedFunction.bind(this));
this.tabulatorTable.on("dataLoaded", this.dataLoadedFunction.bind(this));
//Register only one beforeunload Event for the clipboard warning
if (!window.clipboardWarning) {
window.addEventListener('beforeunload', this._onReceiveBeforeUnload, false);
window.clipboardWarning = true;
}
tableBuiltFunction() {
this.tabulatorTable.addRow([{}]).then(function (row) {
row.delete();
});
this.generateClipboardTable();
if (this._('#select_all')) {
this._('#select_all').addEventListener('click', this.boundSelectHandler);
}
}
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
rowClickFunction(e, row) {
this.numberOfSelectedFiles =
this.tabulatorTable !== null
? this.tabulatorTable.getSelectedRows().length
: 0;
if (
this.tabulatorTable !== null &&
this.tabulatorTable.getSelectedRows().length ===
this.tabulatorTable
.getRows()
.filter((row) => this.checkFileType(row.getData())).length
) {
this._('#select_all').checked = true;
} else {
this._('#select_all').checked = false;
}
}
rowSelectionChangedFunction(data, rows) {
const i18n = this._i18n;
if (this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0) {
this.clipboardSelectBtnDisabled = false;
} else {
this.clipboardSelectBtnDisabled = true;
}
if (this._('#select_all_checkmark')) {
this._('#select_all_checkmark').title = this.checkAllSelected()
? i18n.t('clipboard.select-nothing')
: i18n.t('clipboard.select-all');
}
}
dataLoadedFunction() {
if (this.tabulatorTable !== null) {
const that = this;
setTimeout(function () {
if (that._('.tabulator-responsive-collapse-toggle-open')) {
that._a('.tabulator-responsive-collapse-toggle-open').forEach(
(element) =>
element.addEventListener(
'click',
that.toggleCollapse.bind(that)
)
);
}
if (that._('.tabulator-responsive-collapse-toggle-close')) {
that._a('.tabulator-responsive-collapse-toggle-close').forEach(
(element) =>
element.addEventListener(
'click',
that.toggleCollapse.bind(that)
)
);
}
}, 0);
disconnectedCallback() {
//We doesn't want to deregister this event, because we want to use this event over activities
//window.removeEventListener('beforeunload', this._onReceiveBeforeUnload);
super.disconnectedCallback();
this.tabulatorTable.off("tableBuilt");
this.tabulatorTable.off("rowClick");
this.tabulatorTable.off("rowSelectionChanged");
this.tabulatorTable.off("dataLoaded");
}
/**
* Select or deselect all files from tabulator table
*
*/
selectAllFiles() {
let allSelected = this.checkAllSelected();
if (allSelected) {
this.tabulatorTable.getSelectedRows().forEach((row) => row.deselect());
this.numberOfSelectedFiles = 0;
} else {
this.tabulatorTable.selectRow(
this.tabulatorTable
.getRows()
.filter(
(row) =>
row.getData().type != 'directory' &&
this.checkFileType(row.getData(), this.allowedMimeTypes)
)
);
this.numberOfSelectedFiles = this.tabulatorTable.getSelectedRows().length;
/**
* Checks if all files are already selected
* Returns true if all files are selected
*
checkAllSelected() {
if (this.tabulatorTable) {
let maxSelected = this.tabulatorTable
.getRows()
.filter(
(row) =>
row.getData().type != 'directory' &&
this.checkFileType(row.getData(), this.allowedMimeTypes)
).length;
let selected = this.tabulatorTable.getSelectedRows().length;
if (selected === maxSelected) {
return true;
}
}
return false;
}
/**
* Check mime type of a file, returns true if this.allowedMimeTypes contains the mime type of the file
*
* @param file
// check if file is allowed
const [fileMainType, fileSubType] = file.type.split('/');
const mimeTypes = this.allowedMimeTypes.split(',');
let deny = true;
mimeTypes.forEach((str) => {
const [mainType, subType] = str.split('/');
deny =
deny &&
((mainType !== '*' && mainType !== fileMainType) ||
(subType !== '*' && subType !== fileSubType));
console.log(
`mime type ${file.type} of file '${file.name}' is not compatible with ${this.allowedMimeTypes}`
);
return false;
}
return true;
}
/**
* If clipboard files and the tabulator table exists, then clear the table and sets the new data
*
*/
if (this.clipboardFiles.files && this.tabulatorTable) {
for (let i = 0; i < this.clipboardFiles.files.length; i++) {
data[i] = {
name: this.clipboardFiles.files[i].name,
size: this.clipboardFiles.files[i].size,
type: this.clipboardFiles.files[i].type,
lastModified: this.clipboardFiles.files[i].lastModified,
this.tabulatorTable.clearData();
this.tabulatorTable.setData(data);
if (this._('#select_all')) {
this._('#select_all').checked = false;
/**
* Sends the files to a provider and throws a notification
*
* @param files
*/
const i18n = this._i18n;
await this.sendFileEvent(files[i].file);
}
this.tabulatorTable.deselectRow();
summary: i18n.t('clipboard.saved-files-title', {count: files.length}),
body: i18n.t('clipboard.saved-files-body', {count: files.length}),
type: 'success',
timeout: 5,
}
async sendFileEvent(file) {
const data = {file: file, data: file};
const event = new CustomEvent('dbp-clipboard-file-picker-file-downloaded', {
detail: data,
bubbles: true,
composed: true,
});
this.dispatchEvent(event);
}
/**
* Decides if the "beforeunload" event needs to be canceled
*
* @param event
*/
const i18n = this._i18n;
// we don't need to stop if there are no signed files
if (this.clipboardFiles.files.length === 0) {
return;
}
// we need to handle custom events ourselves
if (event.target && event.target.activeElement && event.target.activeElement.nodeName) {
summary: i18n.t('clipboard.file-warning'),
body: i18n.t('clipboard.file-warning-body', {
count: this.clipboardFiles.files.length,
}),
type: 'warning',
timeout: 5,
});
if (!event.isTrusted) {
// note that this only works with custom event since calls of "confirm" are ignored
// in the non-custom event, see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
// don't stop the page leave if the user wants to leave
if (result) {
return;
}
}
// Cancel the event as stated by the standard
event.preventDefault();
// Chrome requires returnValue to be set
event.returnValue = '';
}
}
/**
* Saves files from an event to the clipboard
*
* @param event
*/
//save it
let data = {};
let files = [];
if (this.clipboardFiles && this.clipboardFiles.files.length !== 0) {
files = files.concat(this.clipboardFiles.files);
files = files.concat(event.detail.file);
files = files.concat(event.detail.file);
}
this.filesToSave = files;
if (files && files.length !== 0) {
this.sendSetPropertyEvent('clipboard-files', data);
const event = new CustomEvent('dbp-clipboard-file-picker-file-uploaded', {
bubbles: true,
composed: true,
});
this.countUploadFiles += 1;
if (this.countUploadFiles === event.detail.maxUpload) {
this.buttonsDisabled = false;
this.countUploadFiles = 0;
} else {
this.buttonsDisabled = true;
}
/**
* Saves all files from this.filesToSave in clipboard and throws a notification
*
*/
const i18n = this._i18n;
//save it
let data = {};
let files = [];
if (this.clipboardFiles && this.clipboardFiles.files.length !== 0) {
files = files.concat(this.clipboardFiles.files);
files = files.concat(this.filesToSave);
} else {
files = files.concat(this.filesToSave);
}
if (this.filesToSave && this.filesToSave.length !== 0) {
this.sendSetPropertyEvent('clipboard-files', data);
const event = new CustomEvent('dbp-clipboard-file-picker-file-uploaded', {
bubbles: true,
composed: true,
});
summary: i18n.t('clipboard.saved-files-title', {count: this.filesToSave.length}),
body: i18n.t('clipboard.saved-files-body', {count: this.filesToSave.length}),
type: 'success',
timeout: 5,
/**
* Throws a finish notification with the count from the event.detail
*
* @param event
*/
finishedSaveFilesToClipboard(event) {
const i18n = this._i18n;
summary: i18n.t('clipboard.saved-files-title', {count: event.detail.count}),
body: i18n.t('clipboard.saved-files-body', {count: event.detail.count}),
type: 'success',
timeout: 5,
/**
* Open the file sink with clipboardfiles
*
*/
openFileSink() {
const fileSink = this._('#file-sink-clipboard');
if (fileSink) {
let files = Array();
if (this.tabulatorTable.getSelectedData().length > 0) {
this.tabulatorTable.getSelectedData().forEach((fileObject) => {
files.push(fileObject.file);
});
} else {
this._('#file-sink-clipboard').files = Object.create(files);
this._('#file-sink-clipboard').openDialog();
* Open the file source with clipboardfiles
*
const fileSource = this._('#file-source-clipboard');
/**
* Delete all or only selected files from clipboard and throws a notification
*
*/
const i18n = this._i18n;
if (this.tabulatorTable && this.tabulatorTable.getSelectedData().length > 0) {
let count = this.tabulatorTable.getSelectedData().length;
this.tabulatorTable.deleteRow(this.tabulatorTable.getSelectedRows());
let data = {files: []};
this.tabulatorTable.getRows().forEach((row) => data.files.push(row.getData().file));
this.sendSetPropertyEvent('clipboard-files', data);
send({
summary: i18n.t('clipboard.clear-count-clipboard-title', {count: count}),
body: i18n.t('clipboard.clear-count-clipboard-body', {count: count}),
type: 'success',
timeout: 5,
});
this.numberOfSelectedFiles = 0;
} else {
this.sendSetPropertyEvent('clipboard-files', data);
send({
summary: i18n.t('clipboard.clear-clipboard-title'),
body: i18n.t('clipboard.clear-clipboard-body'),
type: 'success',
timeout: 5,
/**
* Get the additional clipboard buttons
* If this.mode === MODE_FILE_SINK or MODE_FILE_SOURCE then there are only delete and save files buttons available
* Else there are the add, delete and save files buttons available
*
const i18n = this._i18n;
let buttonsAreDisabled =
this.clipboardFiles.files.length === 0 ? true : this.clipboardSelectBtnDisabled;
buttonsAreDisabled = this.buttonsDisabled ? true : buttonsAreDisabled;
<div class="flex-container additional-button-container">
<div class="btn-flex-container-mobile">
<button
id="clipboard-add-files-button"
@click="${() => {
this.openFileSource();
}}"
class="button ${classMap({
hidden: this.mode === MODE_FILE_SINK || this.mode === MODE_FILE_SOURCE,
})}"
title="${i18n.t('clipboard.add-files')}"
?disabled="${this.buttonsDisabled}">
<dbp-icon class="nav-icon" name="clipboard"></dbp-icon>
${i18n.t('clipboard.add-files-btn')}
</button>
<button
id="clipboard-remove-files-button"
@click="${() => {
this.clearClipboard();
}}"
class="button"
title="${this.numberOfSelectedFiles > 0
? i18n.t('clipboard.remove-count', {count: this.numberOfSelectedFiles})
: i18n.t('clipboard.remove-all')}"
?disabled="${buttonsAreDisabled}">
? i18n.t('clipboard.remove-count-btn', {
count: this.numberOfSelectedFiles,
})
: i18n.t('clipboard.remove-all-btn')}
</button>
</div>
<div class="btn-flex-container-mobile">
<button
id="clipboard-save-files-button"
@click="${() => {
this.openFileSink();
}}"
?disabled="${buttonsAreDisabled}"
class="button"
title="${this.numberOfSelectedFiles > 0
? i18n.t('clipboard.save-count', {count: this.numberOfSelectedFiles})
: i18n.t('clipboard.save-all')}">
? i18n.t('clipboard.save-count-btn', {
count: this.numberOfSelectedFiles,
})
: i18n.t('clipboard.save-all-btn')}
</button>
</div>
</div>
<dbp-file-source
id="file-source-clipboard"
context="${i18n.t('clipboard.add-files')}"
allowed-mime-types="${this.allowedMimeTypes}"
nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}"
nextcloud-web-dav-url="${this.nextcloudWebDavURL}"
nextcloud-name="${this.nextcloudName}"
nextcloud-file-url="${this.nextcloudFileURL}"
nexcloud-auth-info="${this.nextcloudAuthInfo}"
?nextcloud-store-session="${this.nextcloudStoreSession}"
enabled-targets="${this.allowNesting
? this.enabledTargets
: this.enabledTargets.replace('clipboard', '')}"
decompress-zip
lang="${this.lang}"
text="${i18n.t('clipboard.upload-area-text')}"
button-label="${i18n.t('clipboard.upload-button-label')}"
@dbp-file-source-file-selected="${this.saveFilesToClipboardEvent}"
@dbp-nextcloud-file-picker-number-files="${this.finishedSaveFilesToClipboard}"
@dbp-file-source-file-upload-finished="${this
.finishedSaveFilesToClipboard}"></dbp-file-source>
id="file-sink-clipboard"
context="${this.numberOfSelectedFiles > 0
? i18n.t('clipboard.save-count', {count: this.numberOfSelectedFiles})
: i18n.t('clipboard.save-all')}"
filename="clipboard-documents.zip"
allowed-mime-types="${this.allowedMimeTypes}"
enabled-targets="${this.allowNesting
? this.enabledTargets
: this.enabledTargets.replace('clipboard', '')}"
nextcloud-auth-url="${this.nextcloudWebAppPasswordURL}"
nextcloud-web-dav-url="${this.nextcloudWebDavURL}"
nextcloud-name="${this.nextcloudName}"
nextcloud-file-url="${this.nextcloudFileURL}"
nexcloud-auth-info="${this.nextcloudAuthInfo}"
?nextcloud-store-session="${this.nextcloudStoreSession}"
lang="${this.lang}"></dbp-file-sink>
/**
* Get the clipboard sink html
*
const i18n = this._i18n;
const tabulatorCss = commonUtils.getAssetURL(
pkgName,
'tabulator-tables/css/tabulator.min.css'
);
<div class="wrapper">
<div class="content">
<h3>${i18n.t('clipboard.sink-title')}</h3>
<div class="warning-container">
<dbp-icon name="warning-high" class="warning-icon"></dbp-icon>
<p>${i18n.t('clipboard.warning')}</p>
</div>
<link rel="stylesheet" href="${tabulatorCss}"/>
<div class="table-wrapper">
<table id="clipboard-content-table" class="force-no-select"></table>
</div>
</div>
</div>
<div class="clipboard-footer">
class="button select-button is-primary"
title="${i18n.t('clipboard.sink-btn', {count: this.filesToSave.length})}"
@click="${() => {
this.saveFilesToClipboard();
}}">
<dbp-icon class="nav-icon" name="clipboard"></dbp-icon>
${i18n.t('clipboard.sink-btn', {count: this.filesToSave.length})}
/**
* Get the clipboard source html
*
const tabulatorCss = commonUtils.getAssetURL(
pkgName,
'tabulator-tables/css/tabulator.min.css'
);
const i18n = this._i18n;
<div class="wrapper">
<div class="content">
<h3>${i18n.t('clipboard.source-title')}</h3>
<div class="warning-container">
<dbp-icon name="warning-high" class="warning-icon"></dbp-icon>
<p>${i18n.t('clipboard.warning')}</p>
</div>
<link rel="stylesheet" href="${tabulatorCss}"/>
<div class="table-wrapper">
<table id="clipboard-content-table" class="force-no-select"></table>
</div>
</div>
</div>
<div class="clipboard-footer">
class="button select-button is-primary"
?disabled="${this.clipboardSelectBtnDisabled}"
@click="${() => {
this.sendClipboardFiles(this.tabulatorTable.getSelectedData());
}}">
${this.tabulatorTable && this.tabulatorTable.getSelectedRows().length > 0
? i18n.t('clipboard.source-btn', {
count: this.tabulatorTable
? this.tabulatorTable.getSelectedRows().length
: 0,
})
: i18n.t('clipboard.source-btn-none')}
</div>
`;
}
static get styles() {
// language=css
return css`
${commonStyles.getThemeCSS()}
${commonStyles.getGeneralCSS(false)}
${commonStyles.getButtonCSS()}
${commonStyles.getTextUtilities()}
${commonStyles.getModalDialogCSS()}
${commonStyles.getRadioAndCheckboxCss()}
${fileHandlingStyles.getFileHandlingCss()}
a {
color: var(--dbp-hover-color, var(--dbp-content));
background-color: var(--dbp-hover-background-color);
h2:first-child {
margin-top: 0;
margin-bottom: 0px;
}
font-style: italic;
padding-left: 2em;
margin-top: -1px;
margin-bottom: 1.2em;
}
display: flex;
flex-direction: inherit;
align-items: center;
}
display: flex;
justify-content: space-between;
}
.tabulator .tabulator-tableHolder .tabulator-placeholder span {
left: 8px;
top: 3px;
width: 4px;
height: 11px;
}
width: 100%;
padding-top: 10px;
display: flex;
align-items: end;
flex-direction: column;
}
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
@media only screen and (orientation: portrait) and (max-width: 768px) {
.flex-container {
justify-content: space-between;
display: flex;
}
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
width: 100%;
display: flex;
justify-content: end;
float: none;
}