Skip to content
Commits on Source (24)
......@@ -29,14 +29,16 @@ the version number in its `package.json` is higher than the version number on np
## Reserved attributes
| Attribute | Description |
| ------------------------ | ------------------------------------------------------------------- |
| `subscribe` | Used in all components to subscribe to attributes set by a provider |
| `unsubscribe` | Reserved for future use |
| `auth` | Authentication information, set by the authentication component |
| `lang` | Currently selected language, set by the language selector |
| `entry-point-url` | Entry point url for all api requests |
| `requested-login-status` | Used by the login buttons to trigger a login in auth components |
| Attribute | Description |
| ----------------------------- | --------------------------------------------------------------------------------- |
| `subscribe` | Used in all components to subscribe to attributes set by a provider |
| `unsubscribe` | Reserved for future use |
| `auth` | Authentication information, set by the authentication component |
| `lang` | Currently selected language, set by the language selector |
| `entry-point-url` | Entry point url for all api requests |
| `requested-login-status` | Used by the login buttons to trigger a login in auth components |
| `initial-file-handling-state` | Used by the file-handling component to sync file source/sink at first time open |
| `analytics-event` | Used to send analytics events to the Matomo component |
## Reserved events
......
......@@ -19,7 +19,7 @@ npm i @dbp-toolkit/app-shell
## Attributes
- `lang` (optional, default: `de`): set to `de` or `en` for German or English
- example `<dbp-app-shell lang="de" </dbp-app-shell>`
- example `<dbp-app-shell lang="de"></dbp-app-shell>`
- `src`: The path to a topic metadata file (json)
- `base-path` (optional, default: `/`): An absolute base path for routing
- `entry-point-url`: Entry point URL to access the API
......@@ -29,6 +29,13 @@ npm i @dbp-toolkit/app-shell
- `matomo-site-id` (optional): set to your site id (required only for tracking)
- example `<dbp-app-shell matomo-site-id="456789"></dbp-app-shell>`
### Emitted attributes
The component emits `dbp-set-property` events for these attributes:
- `lang` to propagate a language change (possible values `en`, `de`)
- `requested-login-status` (possible values `logged-in`, `logged-out`)
## Topic Metadata
```json
......
{
"name": "@dbp-toolkit/app-shell",
"homepage": "https://gitlab.tugraz.at/dbp/web-components/toolkit/-/tree/master/packages/app-shell",
"version": "0.2.0",
"version": "0.2.1",
"main": "src/index.js",
"license": "LGPL-2.1-or-later",
"repository": {
......@@ -34,12 +34,12 @@
"rollup-plugin-serve": "^1.0.1"
},
"dependencies": {
"@dbp-toolkit/auth": "^0.2.0",
"@dbp-toolkit/common": "^0.2.0",
"@dbp-toolkit/language-select": "^0.2.0",
"@dbp-toolkit/matomo": "^0.2.0",
"@dbp-toolkit/notification": "^0.2.0",
"@dbp-toolkit/person-profile": "^0.2.0",
"@dbp-toolkit/auth": "^0.2.1",
"@dbp-toolkit/common": "^0.2.1",
"@dbp-toolkit/language-select": "^0.2.1",
"@dbp-toolkit/matomo": "^0.2.1",
"@dbp-toolkit/notification": "^0.2.1",
"@dbp-toolkit/person-profile": "^0.2.1",
"@open-wc/scoped-elements": "^1.3.2",
"i18next": "^19.8.4",
"lit-element": "^2.4.0",
......
......@@ -64,7 +64,6 @@ export class AppShell extends ScopedElementsMixin(AdapterLitElement) {
this.matomoUrl = '';
this.matomoSiteId = -1;
this.matomo = null;
this._attrObserver = new MutationObserver(this.onAttributeObserved);
......@@ -259,10 +258,6 @@ export class AppShell extends ScopedElementsMixin(AdapterLitElement) {
if (this.src)
this.fetchMetadata(this.src);
this.initRouter();
this.updateComplete.then(()=> {
this.matomo = this.shadowRoot.querySelector(this.constructor.getScopedTagName('dbp-matomo'));
});
}
/**
......@@ -738,9 +733,7 @@ export class AppShell extends ScopedElementsMixin(AdapterLitElement) {
}
track(action, message) {
if (this.matomo !== null) {
this.matomo.track(action, message);
}
this.sendSetPropertyEvent('analytics-event', {'category': action, 'action': message});
}
_renderActivity() {
......@@ -812,7 +805,7 @@ export class AppShell extends ScopedElementsMixin(AdapterLitElement) {
return html`
<slot class="${slotClassMap}"></slot>
<dbp-auth-keycloak subscribe="requested-login-status" lang="${this.lang}" entry-point-url="${this.entryPointUrl}" url="${kc.url}" realm="${kc.realm}" client-id="${kc.clientId}" silent-check-sso-redirect-uri="${kc.silentCheckSsoRedirectUri || ''}" scope="${kc.scope || ''}" idp-hint="${kc.idpHint || ''}" load-person ?force-login="${kc.forceLogin}" ?try-login="${!kc.forceLogin}"></dbp-auth-keycloak>
<dbp-matomo subscribe="auth" endpoint="${this.matomoUrl}" site-id="${this.matomoSiteId}" git-info="${this.gitInfo}"></dbp-matomo>
<dbp-matomo subscribe="auth,analytics-event" endpoint="${this.matomoUrl}" site-id="${this.matomoSiteId}" git-info="${this.gitInfo}"></dbp-matomo>
<div class="${mainClassMap}">
<div id="main">
<dbp-notification lang="${this.lang}"></dbp-notification>
......
......@@ -39,7 +39,7 @@ npm i @dbp-toolkit/auth
- `silent-check-sso-redirect-uri` (optional): URI or path to a separate page for checking the login session in an iframe, see https://www.keycloak.org/docs/latest/securing_apps/#_javascript_adapter
- `scope` (optional): Space separated list of scopes to request. These scopes get added in addition to the default ones, assuming the scope is in the optional scopes list of the Keycloak client in use.
### Emitted attribute
### Emitted attributes
The component emits a `dbp-set-property` event for the attribute `auth`:
......@@ -68,6 +68,10 @@ The component emits a `dbp-set-property` event for the attribute `auth`:
- example auth property: `{token: "THE_BEARER_TOKEN"}`
- note: most often this should be an attribute that is not set directly, but subscribed at a provider
### Emitted attributes
The component emits a `dbp-set-property` event for the attribute `requested-login-status` (possible values `logged-in`, `logged-out`).
## Local development
```bash
......
......@@ -7,20 +7,6 @@
</head>
<body>
<dbp-provider auth requested-login-status>
<dbp-auth-keycloak subscribe="requested-login-status" lang="de" entry-point-url="http://127.0.0.1:8000"
url="https://auth-dev.tugraz.at/auth"
realm="tugraz"
client-id="auth-dev-mw-frontend-local"
silent-check-sso-redirect-uri="${silentCheckSsoUri}"
scope="optional-test-scope"
load-person
try-login></dbp-auth-keycloak>
<dbp-login-button subscribe="auth" lang="de" show-image></dbp-login-button>
<dbp-auth-demo lang="de" entry-point-url="http://127.0.0.1:8000" subscribe="auth"></dbp-auth-demo>
</dbp-provider>
<dbp-auth-demo auth requested-login-status lang="de" entry-point-url="http://127.0.0.1:8000" subscribe="auth"></dbp-auth-demo>
</body>
</html>
{
"name": "@dbp-toolkit/auth",
"homepage": "https://gitlab.tugraz.at/dbp/web-components/toolkit/-/tree/master/packages/auth",
"version": "0.2.0",
"version": "0.2.1",
"main": "src/index.js",
"license": "LGPL-2.1-or-later",
"repository": {
......@@ -37,7 +37,7 @@
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"@dbp-toolkit/common": "^0.2.0",
"@dbp-toolkit/common": "^0.2.1",
"@open-wc/scoped-elements": "^1.3.2",
"event-target-shim": "^6.0.0",
"lit-element": "^2.4.0"
......
......@@ -39,11 +39,11 @@ export default (async () => {
(build !== 'local' && build !== 'test') ? terser() : false,
copy({
targets: [
{src: 'assets/index.html', dest:'dist'},
{src: 'assets/index.html', dest: 'dist'},
{src: 'assets/silent-check-sso.html', dest: 'dist/' + await getDistPath(pkg.name)},
{src: 'assets/favicon.ico', dest:'dist'},
{src: 'assets/favicon.ico', dest: 'dist'},
{src: await getPackagePath('@dbp-toolkit/common', 'assets/icons/*.svg'), dest: 'dist/' + await getDistPath('@dbp-toolkit/common', 'icons')},
]
],
}),
(process.env.ROLLUP_WATCH === 'true') ? serve({contentBase: 'dist', host: '127.0.0.1', port: 8002}) : false
]
......
......@@ -6,7 +6,6 @@ import {LoginButton} from './login-button.js';
import * as commonUtils from '@dbp-toolkit/common/utils';
import {name as pkgName} from './../package.json';
import DBPLitElement from "@dbp-toolkit/common/dbp-lit-element";
import {Provider} from '@dbp-toolkit/provider';
export class DbpAuthDemo extends ScopedElementsMixin(DBPLitElement) {
constructor() {
......@@ -14,6 +13,7 @@ export class DbpAuthDemo extends ScopedElementsMixin(DBPLitElement) {
this.lang = 'de';
this.entryPointUrl = '';
this.auth = {};
this.noAuth = false;
}
static get scopedElements() {
......@@ -29,6 +29,7 @@ export class DbpAuthDemo extends ScopedElementsMixin(DBPLitElement) {
lang: { type: String },
entryPointUrl: { type: String, attribute: 'entry-point-url' },
auth: { type: Object },
noAuth: { type: Boolean, attribute: 'no-auth' },
};
}
......@@ -77,6 +78,18 @@ export class DbpAuthDemo extends ScopedElementsMixin(DBPLitElement) {
div.innerHTML = this.auth.token;
}
getAuthComponentHtml() {
//const silentCheckSsoUri = commonUtils.getAssetURL(pkgName, 'silent-check-sso.html');
return this.noAuth ? html`<dbp-login-button subscribe="auth" lang="${this.lang}" show-image></dbp-login-button>` : html`
<div class="container">
<dbp-auth-keycloak subscribe="requested-login-status" lang="${this.lang}" entry-point-url="${this.entryPointUrl}" silent-check-sso-redirect-uri="/dist/silent-check-sso.html"
url="https://auth-dev.tugraz.at/auth" realm="tugraz"
client-id="auth-dev-mw-frontend-local" load-person try-login></dbp-auth-keycloak>
<dbp-login-button subscribe="auth" lang="${this.lang}" show-image></dbp-login-button>
</div>
`;
}
render() {
const silentCheckSsoUri = commonUtils.getAssetURL(pkgName, 'silent-check-sso.html');
return html`
......@@ -100,27 +113,11 @@ export class DbpAuthDemo extends ScopedElementsMixin(DBPLitElement) {
max-width: 100%;
}
</style>
<slot></slot>
<section class="section">
<div class="container">
<h1 class="title">Auth-Demo</h1>
</div>
<div class="container">
<p>In any App/page an <b>Auth component</b> will be used like:</p>
<pre>
&lt;dbp-auth-keycloak lang="${this.lang}" entry-point-url="${this.entryPointUrl}"
url="https://auth-dev.tugraz.at/auth"
realm="tugraz"
client-id="auth-dev-mw-frontend-local"
silent-check-sso-redirect-uri="${silentCheckSsoUri}"
scope="optional-test-scope"
load-person
try-login&gt;&lt;/dbp-auth-keycloak&gt;
&lt;dbp-login-button lang="${this.lang}" show-image&gt;&lt;/dbp-login-button&gt;
</pre>
<p>but in this demo we actually have already such a component at the top of this page.</p>
<p>Please use the login button on the top of the page!</p>
</div>
${this.getAuthComponentHtml()}
<div class="container">
<input type="button" value="Fetch userinfo" @click="${this._onUserInfoClick}">
<input type="button" value="Show token" @click="${this._onShowToken}">
......@@ -134,5 +131,4 @@ export class DbpAuthDemo extends ScopedElementsMixin(DBPLitElement) {
}
}
commonUtils.defineCustomElement('dbp-provider', Provider);
commonUtils.defineCustomElement('dbp-auth-demo', DbpAuthDemo);
{
"name": "@dbp-toolkit/check-in-place-select",
"homepage": "https://gitlab.tugraz.at/dbp/web-components/toolkit/-/tree/master/packages/check-in-place-select",
"version": "0.2.0",
"version": "0.2.1",
"main": "src/index.js",
"license": "LGPL-2.1-or-later",
"repository": {
......@@ -35,8 +35,8 @@
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"@dbp-toolkit/auth": "^0.2.0",
"@dbp-toolkit/common": "^0.2.0",
"@dbp-toolkit/auth": "^0.2.1",
"@dbp-toolkit/common": "^0.2.1",
"@open-wc/scoped-elements": "^1.3.2",
"jquery": "^3.4.1",
"lit-element": "^2.4.0",
......
......@@ -24,6 +24,7 @@ export class CheckInPlaceSelect extends ScopedElementsMixin(AdapterLitElement) {
constructor() {
super();
Object.assign(CheckInPlaceSelect.prototype, errorUtils.errorMixin);
this.lang = 'de';
this.entryPointUrl = '';
this.jsonld = null;
......@@ -187,7 +188,7 @@ export class CheckInPlaceSelect extends ScopedElementsMixin(AdapterLitElement) {
results: results
};
},
error: errorUtils.handleXhrError,
error: (jqXHR, textStatus, errorThrown) => { this.handleXhrError(jqXHR, textStatus, errorThrown); },
complete: (jqXHR, textStatus) => {
that.isSearching = false;
}
......
import {send as notify} from './notification';
import {i18n} from "./i18n";
/**
* Error handling for XHR errors
*
* @param jqXHR
* @param textStatus
* @param errorThrown
* @param icon
*/
export const handleXhrError = (jqXHR, textStatus, errorThrown, icon = "sad") => {
// return if user aborted the request
if (textStatus === "abort") {
return;
}
let body;
if (jqXHR.responseJSON !== undefined && jqXHR.responseJSON["hydra:description"] !== undefined) {
// response is a JSON-LD
body = jqXHR.responseJSON["hydra:description"];
} else if (jqXHR.responseJSON !== undefined && jqXHR.responseJSON['detail'] !== undefined) {
// response is a plain JSON
body = jqXHR.responseJSON['detail'];
} else {
// no description available
body = textStatus;
}
// if the server is not reachable at all
if (jqXHR.status === 0) {
body = i18n.t('error.connection-to-server-refused');
}
notify({
"summary": i18n.t('error.summary'),
"body": escapeHTML(stripHTML(body)),
"icon": icon,
"type": "danger",
});
if (window._paq !== undefined) {
window._paq.push(['trackEvent', 'XhrError', body]);
}
};
/**
* Error handling for fetch errors
*
* @param error
* @param summary
* @param icon
*/
export const handleFetchError = async (error, summary = "", icon = "sad") => {
// return if user aborted the request
if (error.name === "AbortError") {
return;
}
let body;
try {
await error.json().then((json) => {
if (json["hydra:description"] !== undefined) {
// response is a JSON-LD and possibly also contains HTML!
body = json["hydra:description"];
} else if (json['detail'] !== undefined) {
// response is a plain JSON
body = json['detail'];
} else {
// no description available
body = error.statusText;
}
}).catch(() => {
body = error.statusText !== undefined ? error.statusText : error;
});
} catch (e) {
// a TypeError means the connection to the server was refused most of the times
if (error.name === "TypeError") {
body = error.message !== "" ? error.message : i18n.t('error.connection-to-server-refused');
}
}
notify({
"summary": summary === "" ? i18n.t('error.summary') : summary,
"body": escapeHTML(stripHTML(body)),
"icon": icon,
"type": "danger",
});
if (window._paq !== undefined) {
window._paq.push(['trackEvent', 'FetchError', summary === "" ? body : summary + ": " + body]);
}
};
/**
* Escapes html
*
......@@ -120,3 +27,108 @@ export const stripHTML = (string) => {
return div.textContent || div.innerText || "";
};
/**
* We need this mixin so we can use this.sendSetPropertyEvent to post analytics events
*/
export const errorMixin = {
/**
* Error handling for XHR errors
*
* @param jqXHR
* @param textStatus
* @param errorThrown
* @param icon
*/
handleXhrError(jqXHR, textStatus, errorThrown, icon = "sad") {
// return if user aborted the request
if (textStatus === "abort") {
return;
}
let body;
if (jqXHR.responseJSON !== undefined && jqXHR.responseJSON["hydra:description"] !== undefined) {
// response is a JSON-LD
body = jqXHR.responseJSON["hydra:description"];
} else if (jqXHR.responseJSON !== undefined && jqXHR.responseJSON['detail'] !== undefined) {
// response is a plain JSON
body = jqXHR.responseJSON['detail'];
} else {
// no description available
body = textStatus;
if (errorThrown) {
body += ' - ' + errorThrown;
}
}
// if the server is not reachable at all
if (jqXHR.status === 0) {
body = i18n.t('error.connection-to-server-refused');
}
notify({
"summary": i18n.t('error.summary'),
"body": escapeHTML(stripHTML(body)),
"icon": icon,
"type": "danger",
});
if (this.sendSetPropertyEvent !== undefined) {
this.sendSetPropertyEvent('analytics-event', {'category': 'XhrError', 'action': body});
}
},
/**
* Error handling for fetch errors
*
* @param error
* @param summary
* @param icon
*/
handleFetchError: async function (error, summary = "", icon = "sad") {
// return if user aborted the request
if (error.name === "AbortError") {
return;
}
let body;
try {
await error.json().then((json) => {
if (json["hydra:description"] !== undefined) {
// response is a JSON-LD and possibly also contains HTML!
body = json["hydra:description"];
} else if (json['detail'] !== undefined) {
// response is a plain JSON
body = json['detail'];
} else {
// no description available
body = error.statusText;
}
}).catch(() => {
body = error.statusText !== undefined ? error.statusText : error;
});
} catch (e) {
// a TypeError means the connection to the server was refused most of the times
if (error.name === "TypeError") {
body = error.message !== "" ? error.message : i18n.t('error.connection-to-server-refused');
}
}
notify({
"summary": summary === "" ? i18n.t('error.summary') : summary,
"body": escapeHTML(stripHTML(body)),
"icon": icon,
"type": "danger",
});
if (this.sendSetPropertyEvent !== undefined) {
this.sendSetPropertyEvent('analytics-event', {
'category': 'FetchError',
'action': summary === "" ? body : summary + ": " + body
});
}
}
};
......@@ -4,11 +4,6 @@ import {send as notify} from './notification';
import * as utils from "./utils";
import {i18n} from "./i18n";
let instances = {};
let successFunctions = {};
let failureFunctions = {};
let initStarted = {};
// "module.exports = class JSONLD" doesn't work with rollup because of above "import"
export default class JSONLD {
constructor(baseApiUrl, entities) {
......@@ -30,19 +25,19 @@ export default class JSONLD {
}
// if init api call was already successfully finished execute the success function
if (instances[apiUrl] !== undefined) {
if (typeof successFnc == 'function') successFnc(instances[apiUrl]);
if (JSONLD.instances[apiUrl] !== undefined) {
if (typeof successFnc == 'function') successFnc(JSONLD.instances[apiUrl]);
return;
}
// init the arrays
if (successFunctions[apiUrl] === undefined) successFunctions[apiUrl] = [];
if (failureFunctions[apiUrl] === undefined) failureFunctions[apiUrl] = [];
if (JSONLD.successFunctions[apiUrl] === undefined) JSONLD.successFunctions[apiUrl] = [];
if (JSONLD.failureFunctions[apiUrl] === undefined) JSONLD.failureFunctions[apiUrl] = [];
// add success and failure functions
if (typeof successFnc == 'function') successFunctions[apiUrl].push(successFnc);
if (typeof failureFnc == 'function') failureFunctions[apiUrl].push(failureFnc);
if (typeof successFnc == 'function') JSONLD.successFunctions[apiUrl].push(successFnc);
if (typeof failureFnc == 'function') JSONLD.failureFunctions[apiUrl].push(failureFnc);
}
/**
......@@ -55,11 +50,11 @@ export default class JSONLD {
// console.log("doInitializationOnce", apiUrl, token);
// check if token is not set or api call was already started
if (!apiUrl || !token || initStarted[apiUrl] !== undefined) {
if (!apiUrl || !token || JSONLD.initStarted[apiUrl] !== undefined) {
return;
}
initStarted[apiUrl] = true;
JSONLD.initStarted[apiUrl] = true;
JSONLD.doInitialization(apiUrl, token);
// console.log("doInitializationOnce Done", apiUrl, token);
}
......@@ -145,11 +140,11 @@ export default class JSONLD {
});
const instance = new JSONLD(baseUrl, entities);
instances[apiUrl] = instance;
JSONLD.instances[apiUrl] = instance;
// return the initialized JSONLD object
for (const fnc of successFunctions[apiUrl]) if (typeof fnc == 'function') fnc(instance);
successFunctions[apiUrl] = [];
for (const fnc of JSONLD.successFunctions[apiUrl]) if (typeof fnc == 'function') fnc(instance);
JSONLD.successFunctions[apiUrl] = [];
}
/**
......@@ -159,8 +154,8 @@ export default class JSONLD {
* @param message
*/
static executeFailureFunctions(apiUrl, message = "") {
for (const fnc of failureFunctions[apiUrl]) if (typeof fnc == 'function') fnc();
failureFunctions[apiUrl] = [];
for (const fnc of JSONLD.failureFunctions[apiUrl]) if (typeof fnc == 'function') fnc();
JSONLD.failureFunctions[apiUrl] = [];
if (message !== "") {
notify({
......@@ -172,7 +167,7 @@ export default class JSONLD {
}
static getInstance(apiUrl) {
return instances[apiUrl];
return JSONLD.instances[apiUrl];
}
getEntityForIdentifier(identifier) {
......@@ -283,3 +278,8 @@ export default class JSONLD {
return results;
}
}
JSONLD.instances = {};
JSONLD.successFunctions = {};
JSONLD.failureFunctions = {};
JSONLD.initStarted = {};
{
"name": "@dbp-toolkit/common",
"homepage": "https://gitlab.tugraz.at/dbp/web-components/toolkit/-/tree/master/packages/common",
"version": "0.2.0",
"version": "0.2.1",
"module": "index.js",
"license": "LGPL-2.1-or-later",
"repository": {
......@@ -37,7 +37,7 @@
},
"dependencies": {
"@open-wc/scoped-elements": "^1.3.2",
"@sentry/browser": "^5.27.4",
"@sentry/browser": "^6.0.0",
"i18next": "^19.8.4",
"lit-element": "^2.4.0"
}
......
{
"name": "@dbp-toolkit/data-table-view",
"homepage": "https://gitlab.tugraz.at/dbp/web-components/toolkit/-/tree/master/packages/data-table-view",
"version": "0.2.0",
"version": "0.2.1",
"main": "src/index.js",
"license": "LGPL-2.1-or-later",
"repository": {
......@@ -34,8 +34,8 @@
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"@dbp-toolkit/auth": "^0.2.0",
"@dbp-toolkit/common": "^0.2.0",
"@dbp-toolkit/auth": "^0.2.1",
"@dbp-toolkit/common": "^0.2.1",
"@open-wc/scoped-elements": "^1.3.2",
"datatables.net-buttons": "^1.6.1",
"datatables.net-buttons-dt": "^1.6.1",
......
......@@ -57,9 +57,9 @@ files from a [Nextcloud](https://nextcloud.com/) instance.
- example `<dbp-file-source allowed-mime-types='image/*'></dbp-file-source>` ... images (of all sub types) only
- example `<dbp-file-source allowed-mime-types='image/png,text/plain'></dbp-file-source>` ... PNGs or TXTs only
- example `<dbp-file-source allowed-mime-types='*/*'></dbp-file-source>` ... all file types (default)
- `enabled-sources` (optional, default: `local`): sets which sources are enabled
- `enabled-targets` (optional, default: `local`): sets which sources are enabled
- you can use `local` and `nextcloud`
- example `<dbp-file-source enabled-sources="local,nextcloud"></dbp-file-source>`
- example `<dbp-file-source enabled-targets="local,nextcloud"></dbp-file-source>`
- `disabled` (optional): disable input control
- example `<dbp-file-source disabled></dbp-file-source>`
- `decompress-zip` (optional): decompress zip file and send the contained files (including files in folders)
......@@ -79,6 +79,17 @@ files from a [Nextcloud](https://nextcloud.com/) instance.
- example `<dbp-file-source text="Please select some files"></dbp-file-source>`
- `button-label` (optional): the text that is shown on the button to select files
- example `<dbp-file-source button-label="Select files"></dbp-file-source>`
- `initial-file-handling-state` (optional): An object: `initial-file-handling-state' = {target: "", path: ""}` for initial opening behaviour.
This is supported by the provider! Use this object to sync file source and file sink on one page at first time open.
- example `<dbp-file-source initial-file-handling-state="{target: 'local', path:'my/server/path'}"></dbp-file-source>`
- example provider `<dbp-file-source subscribe="initial-file-handling-state"></dbp-file-source>`
### Emitted attributes
The component emits a `dbp-set-property` event for the attribute `initial-file-handling-state`:
- `initial-file-handling-state.target`: Target that should be selected the first time (possible values `local`, `nextcloud`)
- `initial-file-handling-state.path`: Path to initially jump to (only supported by target `nextcloud`)
### Outgoing Events
......@@ -103,9 +114,9 @@ files to a [Nextcloud](https://nextcloud.com/) instance.
- `lang` (optional, default: `de`): set to `de` or `en` for German or English
- example `<dbp-file-sink lang="de"></dbp-file-sink>`
- `enabled-destinations` (optional, default: `local`): sets which destination are enabled
- `enabled-targets` (optional, default: `local`): sets which destination are enabled
- you can use `local` and `nextcloud`
- example `<dbp-file-sink enabled-destinations="local,nextcloud"></dbp-file-sink>`
- example `<dbp-file-sink enabled-targets="local,nextcloud"></dbp-file-sink>`
- `filename` (optional, default: `files.zip`): sets a file name to use for downloading the zip file
- example `<dbp-file-sink filename="signed-documents.zip"></dbp-file-sink>`
- `nextcloud-auth-url` (optional): Nextcloud Auth Url to use with the Nextcloud file picker
......@@ -120,6 +131,17 @@ files to a [Nextcloud](https://nextcloud.com/) instance.
- example `<dbp-file-sink text="Download files as ZIP-file"></dbp-file-sink>`
- `button-label` (optional): the text that is shown on the button to download the zip file
- example `<dbp-file-sink button-label="Download files"></dbp-file-sink>`
- `initial-file-handling-state` (optional): An object: `initial-file-handling-state' = {target: "", path: ""}` for initial opening behaviour.
This is supported by the provider! Use this object to sync file source and file sink on one page at first time open.
- example `<dbp-file-source initial-file-handling-state="{target: 'local', path:'my/server/path'}"></dbp-file-source>`
- example provider `<dbp-file-source subscribe="initial-file-handling-state"></dbp-file-source>`
### Emitted attributes
The component emits a `dbp-set-property` event for the attribute `initial-file-handling-state`:
- `initial-file-handling-state.target`: Target that should be selected the first time (possible values `local`, `nextcloud`)
- `initial-file-handling-state.path`: Path to initially jump to (only supported by target `nextcloud`)
### Properties
......
{
"name": "@dbp-toolkit/file-handling",
"homepage": "https://gitlab.tugraz.at/dbp/web-components/toolkit/-/tree/master/packages/file-handling",
"version": "0.2.0",
"version": "0.2.1",
"main": "src/index.js",
"license": "LGPL-2.1-or-later",
"repository": {
......@@ -33,7 +33,7 @@
"rollup-plugin-terser": "^7.0.2"
},
"dependencies": {
"@dbp-toolkit/common": "^0.2.0",
"@dbp-toolkit/common": "^0.2.1",
"@open-wc/scoped-elements": "^1.3.2",
"file-saver": "^2.0.2",
"i18next": "^19.8.4",
......
......@@ -323,6 +323,9 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) {
* @param path
*/
loadDirectory(path) {
if ( typeof this.directoryPath === 'undefined' ) {
this.directoryPath = '';
}
console.log("load nextcloud directory", path);
this.selectAllButton = true;
this.loading = true;
......@@ -865,6 +868,9 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) {
addFolder() {
if (this._('#new-folder').value !== "") {
let folderName = this._('#new-folder').value;
if ( typeof this.directoryPath === 'undefined' ) {
this.directoryPath = '';
}
let folderPath = this.directoryPath + "/" + folderName;
this.webDavClient.createDirectory(folderPath).then(contents => {
// this.loadDirectory(this.directoryPath);
......@@ -913,6 +919,9 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) {
* @returns {string} parent directory path
*/
getParentDirectoryPath() {
if ( typeof this.directoryPath === 'undefined' ) {
this.directoryPath = '';
}
let path = this.directoryPath.replace(/\/$/, "");
path = path.replace(path.split("/").pop(), "").replace(/\/$/, "");
......@@ -925,6 +934,9 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) {
* @returns {string} clickable breadcrumb path
*/
getBreadcrumb() {
if ( typeof this.directoryPath === 'undefined' ) {
this.directoryPath = '';
}
let htmlpath = [];
htmlpath[0] = html`<span class="breadcrumb"><a class="home-link" @click="${() => { this.loadDirectory(""); }}" title="${i18n.t('nextcloud-file-picker.folder-home')}"><dbp-icon name="home"></dbp-icon> </a></span>`;
const directories = this.directoryPath.split('/');
......
......@@ -91,7 +91,7 @@ export class FileSourceDemo extends ScopedElementsMixin(LitElement) {
allowed-mime-types="*/*"
subscribe="nextcloud-auth-url:nextcloud-auth-url,nextcloud-web-dav-url:nextcloud-web-dav-url,nextcloud-name:nextcloud-name,nextcloud-file-url:nextcloud-file-url"
lang="en"
enabled-sources="local,nextcloud"></dbp-file-source>
enabled-targets="local,nextcloud"></dbp-file-source>
<p>Only images are allowed here (JPG, PNG, GIF, TIF, ...):</p>
<button @click="${() => { this._("#file-source2").setAttribute("dialog-open", ""); }}"
......@@ -101,7 +101,7 @@ export class FileSourceDemo extends ScopedElementsMixin(LitElement) {
<dbp-file-source id="file-source2" lang="en" url="${this.url}"
allowed-mime-types="image/*"
subscribe="nextcloud-auth-url:nextcloud-auth-url,nextcloud-web-dav-url:nextcloud-web-dav-url,nextcloud-name:nextcloud-name,nextcloud-file-url:nextcloud-file-url"
enabled-sources="local,nextcloud"
enabled-targets="local,nextcloud"
text="Please select images"></dbp-file-source>
<p>This is for PDF only:</p>
......@@ -112,7 +112,7 @@ export class FileSourceDemo extends ScopedElementsMixin(LitElement) {
<dbp-file-source id="file-source3" lang="en" url="${this.url}"
allowed-mime-types="application/pdf"
subscribe="nextcloud-auth-url:nextcloud-auth-url,nextcloud-web-dav-url:nextcloud-web-dav-url,nextcloud-name:nextcloud-name,nextcloud-file-url:nextcloud-file-url"
enabled-sources="local,nextcloud"
enabled-targets="local,nextcloud"
text="Submit only PDF files" button-label="PDF auswählen"></dbp-file-source>
<p>Text and images (JPG, PNG, GIF, TIF, ...) :</p>
......@@ -123,7 +123,7 @@ export class FileSourceDemo extends ScopedElementsMixin(LitElement) {
<dbp-file-source id="file-source4" lang="en" url="${this.url}"
allowed-mime-types="text/plain,image/*"
subscribe="nextcloud-auth-url:nextcloud-auth-url,nextcloud-web-dav-url:nextcloud-web-dav-url,nextcloud-name:nextcloud-name,nextcloud-file-url:nextcloud-file-url"
enabled-sources="local,nextcloud"
enabled-targets="local,nextcloud"
text="Please select text or images"></dbp-file-source>
<p>PDFs also in ZIPS :</p>
......@@ -135,7 +135,7 @@ export class FileSourceDemo extends ScopedElementsMixin(LitElement) {
allowed-mime-types="application/pdf"
decompress-zip
subscribe="nextcloud-auth-url:nextcloud-auth-url,nextcloud-web-dav-url:nextcloud-web-dav-url,nextcloud-name:nextcloud-name,nextcloud-file-url:nextcloud-file-url"
enabled-sources="local,nextcloud"
enabled-targets="local,nextcloud"
text="Please select PDF(s) or ZIP(s) with PDF(s)"></dbp-file-source>
<dbp-file-sink lang="en"></dbp-file-sink>
......
......@@ -24,18 +24,18 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
this.nextcloudAuthUrl = '';
this.nextcloudWebDavUrl = '';
this.nextcloudName ='Nextcloud';
this.nextcloudDefaultDir = '';
this.nextcloudDir = '';
this.nextcloudPath = '';
this.nextcloudFileURL = '';
this.text = '';
this.buttonLabel = '';
this.filename = "files.zip";
this.files = [];
this.activeDestination = 'local';
this.activeTarget = 'local';
this.isDialogOpen = false;
this.enabledDestinations = 'local';
this.defaultSink = 'a';
this.enabledTargets = 'local';
this.firstOpen = true;
this.initialFileHandlingState = {target: '', path: ''};
}
static get scopedElements() {
......@@ -56,7 +56,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
lang: {type: String},
filename: {type: String},
files: {type: Array, attribute: false},
enabledDestinations: {type: String, attribute: 'enabled-destinations'},
enabledTargets: {type: String, attribute: 'enabled-targets'},
nextcloudAuthUrl: {type: String, attribute: 'nextcloud-auth-url'},
nextcloudWebDavUrl: {type: String, attribute: 'nextcloud-web-dav-url'},
nextcloudName: {type: String, attribute: 'nextcloud-name'},
......@@ -64,12 +64,12 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
text: {type: String},
buttonLabel: {type: String, attribute: 'button-label'},
isDialogOpen: {type: Boolean, attribute: false},
activeDestination: {type: String, attribute: 'active-destination'},
defaultSink: {type: String, attribute: 'default-sink'},
activeTarget: {type: String, attribute: 'active-target'},
firstOpen: {type: Boolean, attribute: false},
nextcloudDefaultDir: {type: String, attribute: 'nextcloud-default'},
nextcloudDir: {type: String, attribute: false},
nextcloudPath: {type: String, attribute: false},
initialFileHandlingState: {type: Object, attribute: 'initial-file-handling-state'},
};
}
......@@ -79,6 +79,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
this.updateComplete.then(() => {
console.log("initialFileHandlingState", this.initialFileHandlingState);
});
}
......@@ -123,9 +124,9 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
case "lang":
i18n.changeLanguage(this.lang);
break;
case "enabledDestinations":
if (!this.hasEnabledDestination(this.activeDestination)) {
this.activeDestination = this.enabledDestinations.split(",")[0];
case "enabledTargets":
if (!this.hasEnabledDestination(this.activeTargets)) {
this.activeTargets = this.enabledTargets.split(",")[0];
}
break;
case "files":
......@@ -133,10 +134,10 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
this.openDialog();
}
break;
case "nextcloudDefaultDir":
case "initialFileHandlingState":
//check if default destination is set
if (this.firstOpen) {
this.nextcloudDir = this.nextcloudDefaultDir;
this.nextcloudPath = this.initialFileHandlingState.path;
}
break;
}
......@@ -146,7 +147,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
}
hasEnabledDestination(source) {
return this.enabledDestinations.split(',').includes(source);
return this.enabledTargets.split(',').includes(source);
}
async uploadToNextcloud(directory) {
......@@ -170,14 +171,13 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
sendDestination() {
let data = {};
if (this.activeDestination == 'nextcloud') {
data = {"source": this.activeDestination, "nextcloud": this._("#nextcloud-file-picker").directoryPath};
if (this.activeTarget === 'nextcloud') {
data = {"target": this.activeTarget, "path": this._("#nextcloud-file-picker").directoryPath};
} else {
data = {"source": this.activeDestination};
data = {"target": this.activeTarget};
}
const event = new CustomEvent("dbp-file-sink-switched", { "detail": data, bubbles: true, composed: true });
this.dispatchEvent(event);
this.sendSetPropertyEvent('initial-file-handling-state', data);
}
......@@ -187,6 +187,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
}
loadWebdavDirectory() {
if (this._('#nextcloud-file-picker').webDavClient !== null) {
this._('#nextcloud-file-picker').loadDirectory(this._('#nextcloud-file-picker').directoryPath);
}
......@@ -199,14 +200,15 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
onClose: modal => { this.isDialogOpen = false; },
});
console.log("initialFileHandlingState", this.initialFileHandlingState);
//check if default destination is set
if (this.defaultSink !== '' && typeof this.defaultSink !== 'undefined' && this.firstOpen) {
this.activeDestination = this.defaultSink;
this.nextcloudDir = this.nextcloudDefaultDir;
if (this.initialFileHandlingState.target !== '' && typeof this.initialFileHandlingState.target !== 'undefined' && this.firstOpen) {
this.activeTarget = this.initialFileHandlingState.target;
this.nextcloudPath = this.initialFileHandlingState.path;
if (this._('#nextcloud-file-picker').webDavClient !== null) {
this._('#nextcloud-file-picker').loadDirectory(this.nextcloudDefaultDir);
console.log("load default nextcloud sink", this.nextcloudDefaultDir);
this._('#nextcloud-file-picker').loadDirectory(this.initialFileHandlingState.path);
console.log("load default nextcloud sink", this.initialFileHandlingState.path);
}
this.firstOpen = false;
}
......@@ -249,14 +251,14 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
<div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-picker-title">
<nav class="modal-nav">
<div title="${i18n.t('file-sink.nav-local')}"
@click="${() => { this.activeDestination = "local"; }}"
class="${classMap({"active": this.activeDestination === "local", hidden: !this.hasEnabledDestination("local")})}">
@click="${() => { this.activeTarget = "local"; }}"
class="${classMap({"active": this.activeTarget === "local", hidden: !this.hasEnabledDestination("local")})}">
<dbp-icon class="nav-icon" name="laptop"></dbp-icon>
<p>${i18n.t('file-source.nav-local')}</p>
</div>
<div title="${this.nextcloudName}"
@click="${() => { this.activeDestination = "nextcloud"; this.loadWebdavDirectory();}}"
class="${classMap({"active": this.activeDestination === "nextcloud", hidden: !this.hasEnabledDestination("nextcloud") || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}">
@click="${() => { this.activeTarget = "nextcloud"; this.loadWebdavDirectory();}}"
class="${classMap({"active": this.activeTarget === "nextcloud", hidden: !this.hasEnabledDestination("nextcloud") || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}">
<dbp-icon class="nav-icon" name="cloud"></dbp-icon>
<p> ${this.nextcloudName} </p>
</div>
......@@ -270,7 +272,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
<main class="modal-content" id="modal-picker-content">
<div class="source-main ${classMap({"hidden": this.activeDestination !== "local"})}">
<div class="source-main ${classMap({"hidden": this.activeTarget !== "local"})}">
<div id="zip-download-block">
<div class="block">
${this.text || i18n.t('file-sink.local-intro', {'count': this.files.length})}
......@@ -282,7 +284,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
</button>
</div>
</div>
<div class="source-main ${classMap({"hidden": this.activeDestination !== "nextcloud" || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}">
<div class="source-main ${classMap({"hidden": this.activeTarget !== "nextcloud" || this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}">
<dbp-nextcloud-file-picker id="nextcloud-file-picker"
class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"
directories-only
......@@ -293,7 +295,7 @@ export class FileSink extends ScopedElementsMixin(DBPLitElement) {
auth-url="${this.nextcloudAuthUrl}"
web-dav-url="${this.nextcloudWebDavUrl}"
nextcloud-name="${this.nextcloudName}"
directory-path="${this.nextcloudDir}"
directory-path="${this.nextcloudPath}"
nextcloud-file-url="${this.nextcloudFileURL}"
@dbp-nextcloud-file-picker-file-uploaded="${(event) => {
this.uploadToNextcloud(event.detail);
......