Skip to content
Snippets Groups Projects
Commit 1666281c authored by Kocher, Manuel's avatar Kocher, Manuel
Browse files

Merge branch 'master' into dbp-translation-component

parents 76fa848a 261da72d
No related branches found
No related tags found
No related merge requests found
Showing
with 175 additions and 67 deletions
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"author": "", "author": "",
"license": "LGPL-2.1-or-later", "license": "LGPL-2.1-or-later",
"devDependencies": { "devDependencies": {
"lerna": "^4.0.0" "lerna": "^5.0.0"
}, },
"dependencies": {} "dependencies": {}
} }
...@@ -80,9 +80,9 @@ class AppShellWelcome extends ScopedElementsMixin(LitElement) { ...@@ -80,9 +80,9 @@ class AppShellWelcome extends ScopedElementsMixin(LitElement) {
-webkit-mask-position: center center; -webkit-mask-position: center center;
mask-position: center center; mask-position: center center;
margin: 0 2px 0 0px; margin: 0 2px 0 0px;
padding: 0 0 0.25% 0; padding: 0;
-webkit-mask-size: 100%; -webkit-mask-size: 90%;
mask-size: 100%; mask-size: 90%;
} }
h2 a:hover::after { h2 a:hover::after {
......
...@@ -23,6 +23,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) { ...@@ -23,6 +23,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) {
this._i18n = createInstance(); this._i18n = createInstance();
this.lang = this._i18n.language; this.lang = this._i18n.language;
this.noAuth = false; this.noAuth = false;
this.langFile = '';
} }
static get scopedElements() { static get scopedElements() {
...@@ -48,6 +49,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) { ...@@ -48,6 +49,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) {
return { return {
lang: {type: String}, lang: {type: String},
noAuth: {type: Boolean, attribute: 'no-auth'}, noAuth: {type: Boolean, attribute: 'no-auth'},
langFile: {type: String, attribute: 'lang-file'},
}; };
} }
......
...@@ -31,6 +31,12 @@ export default class DBPLitElement extends AdapterLitElement { ...@@ -31,6 +31,12 @@ export default class DBPLitElement extends AdapterLitElement {
: this.shadowRoot.querySelector(selector); : this.shadowRoot.querySelector(selector);
} }
_a(selector) {
return this.shadowRoot === null
? this.querySelectorAll(selector)
: this.shadowRoot.querySelectorAll(selector);
}
firstUpdated() { firstUpdated() {
super.firstUpdated(); super.firstUpdated();
this._renderDone = true; this._renderDone = true;
......
...@@ -7,5 +7,6 @@ ...@@ -7,5 +7,6 @@
"api-documentation-server": "Verbindung zum apiDocumentation API Server {{apiDocUrl}} fehlgeschlagen!", "api-documentation-server": "Verbindung zum apiDocumentation API Server {{apiDocUrl}} fehlgeschlagen!",
"error-api-server": "Verbindung zum API Server {{apiUrl}} fehlgeschlagen!", "error-api-server": "Verbindung zum API Server {{apiUrl}} fehlgeschlagen!",
"error-hydra-documentation-url-not-set": "Hydra apiDocumentation URL wurden für server {{apiUrl}} nicht gesetzt!" "error-hydra-documentation-url-not-set": "Hydra apiDocumentation URL wurden für server {{apiUrl}} nicht gesetzt!"
} },
"toolkit-showcase": "Dieser Text wird mithilfe von i18n Englisch wenn man die Sprache auf Englisch stellt."
} }
...@@ -7,5 +7,6 @@ ...@@ -7,5 +7,6 @@
"api-documentation-server": "Connection to apiDocumentation server {{apiDocUrl}} failed!", "api-documentation-server": "Connection to apiDocumentation server {{apiDocUrl}} failed!",
"error-api-server": "Connection to api server {{apiUrl}} failed!", "error-api-server": "Connection to api server {{apiUrl}} failed!",
"error-hydra-documentation-url-not-set": "Hydra apiDocumentation url was not set for server {{apiUrl}}!" "error-hydra-documentation-url-not-set": "Hydra apiDocumentation url was not set for server {{apiUrl}}!"
} },
"toolkit-showcase": "This text will be translated to german using i18n when the user changes the language to german."
} }
...@@ -66,7 +66,7 @@ export class Translation extends DBPLitElement { ...@@ -66,7 +66,7 @@ export class Translation extends DBPLitElement {
const en = {}; const en = {};
de[this.key] = ""; de[this.key] = "";
en[this.key] = ""; en[this.key] = "";
// create i18n instance with given translations // create i18n instance with given translations
this._i18n = createInstanceGivenResources(en, de); this._i18n = createInstanceGivenResources(en, de);
......
...@@ -155,6 +155,29 @@ export class MatomoElement extends DBPLitElement { ...@@ -155,6 +155,29 @@ export class MatomoElement extends DBPLitElement {
that.pushEvent(['trackEvent', 'UnhandledRejection', name]); that.pushEvent(['trackEvent', 'UnhandledRejection', name]);
}); });
// https://developer.mozilla.org/en-US/docs/Web/API/Element/securitypolicyviolation_event
window.addEventListener('securitypolicyviolation', (e) => {
let attrs = [
'blockedURI',
'columnNumber',
'disposition',
'documentURI',
'effectiveDirective',
'lineNumber',
'originalPolicy',
'referrer',
'sample',
'sourceFile',
'statusCode',
'violatedDirective',
];
let payload = {};
for (let attr of attrs) {
payload[attr] = e[attr];
}
this.pushEvent(['trackEvent', 'SecurityPolicyViolation', JSON.stringify(payload)]);
});
this.isRunning = true; this.isRunning = true;
if (this.lastEvent.length > 0) { if (this.lastEvent.length > 0) {
console.log( console.log(
......
...@@ -46,10 +46,6 @@ after loaded. This attribute is also used to stop the QR code reader or if you d ...@@ -46,10 +46,6 @@ after loaded. This attribute is also used to stop the QR code reader or if you d
- `'code-detected'`: Outgoing Event which is fired if a QR code is detected. The data of the detected QR code is in `event.detail`. - `'code-detected'`: Outgoing Event which is fired if a QR code is detected. The data of the detected QR code is in `event.detail`.
- `'scan-started`: Fired after the first image is drawn. Can be used to scrolling or other layout dependent tasks. - `'scan-started`: Fired after the first image is drawn. Can be used to scrolling or other layout dependent tasks.
## Assets
- `qr-scanner/qr-scanner-worker.*` -> `dist/local/@dbp-toolkit/qr-code-scanner/`
## Local development ## Local development
```bash ```bash
...@@ -72,6 +68,13 @@ yarn run build ...@@ -72,6 +68,13 @@ yarn run build
Jump to <http://localhost:8002> and you should get a demo page. Jump to <http://localhost:8002> and you should get a demo page.
## Content Security Policy
The QR code detection worker is loaded via `blob:`, so your CSP needs to allow
`worker-src blob:`. Since Safari does not support this you also have to set
`child-src blob:`. Since `child-src` also affects other things make sure this
doesn't break things.
## Camera for local development ## Camera for local development
You can use your desktop as a camera, to test the qr code reader You can use your desktop as a camera, to test the qr code reader
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
"@open-wc/scoped-elements": "^2.1.0", "@open-wc/scoped-elements": "^2.1.0",
"async-mutex": "^0.3.0", "async-mutex": "^0.3.0",
"lit": "^2.0.0", "lit": "^2.0.0",
"qr-scanner": "^1.2.0" "qr-scanner": "^1.4.1"
}, },
"scripts": { "scripts": {
"clean": "rm dist/*", "clean": "rm dist/*",
......
...@@ -67,10 +67,6 @@ export default async () => { ...@@ -67,10 +67,6 @@ export default async () => {
src: await getPackagePath('@dbp-toolkit/common', 'assets/icons/*.svg'), src: await getPackagePath('@dbp-toolkit/common', 'assets/icons/*.svg'),
dest: 'dist/' + (await getDistPath('@dbp-toolkit/common', 'icons')), dest: 'dist/' + (await getDistPath('@dbp-toolkit/common', 'icons')),
}, },
{
src: await getPackagePath('qr-scanner', 'qr-scanner-worker.*'),
dest: 'dist/' + (await getDistPath(pkg.name)),
},
], ],
}), }),
process.env.ROLLUP_WATCH === 'true' process.env.ROLLUP_WATCH === 'true'
......
export class ScanResult {
constructor() {
this.data = null;
this.cornerPoints = null;
}
}
export class QrCodeScannerEngine {
constructor() {
this._engine = null;
this._canvas = document.createElement('canvas');
this._scanner = null;
}
/**
* Scan am image like thing for a QR code. Returns null if none is found.
* The region to scan in can be restricted via "options".
*
* @param {*} image
* @param {?object} options
* @param {number} options.x
* @param {number} options.y
* @param {number} options.width
* @param {number} options.height
* @returns {?ScanResult}
*/
async scanImage(image, options = null) {
if (this._scanner === null) {
this._scanner = (await import('qr-scanner')).default;
}
if (this._engine === null) {
this._engine = await this._scanner.createQrEngine();
}
try {
let tmp = await this._scanner.scanImage(image, {
scanRegion: options ?? null,
qrEngine: this._engine,
canvas: this._canvas,
});
let res = new ScanResult();
res.data = tmp.data;
res.cornerPoints = tmp.cornerPoints;
return res;
} catch (e) {
return null;
}
}
}
import {QrCodeScanner} from './qr-code-scanner.js'; export {QrCodeScanner} from './qr-code-scanner.js';
export {QrCodeScannerEngine, ScanResult} from './engine.js';
export {QrCodeScanner};
...@@ -5,10 +5,9 @@ import * as commonStyles from '@dbp-toolkit/common/styles'; ...@@ -5,10 +5,9 @@ import * as commonStyles from '@dbp-toolkit/common/styles';
import {ScopedElementsMixin} from '@open-wc/scoped-elements'; import {ScopedElementsMixin} from '@open-wc/scoped-elements';
import {Icon, MiniSpinner} from '@dbp-toolkit/common'; import {Icon, MiniSpinner} from '@dbp-toolkit/common';
import {classMap} from 'lit/directives/class-map.js'; import {classMap} from 'lit/directives/class-map.js';
import * as commonUtils from '@dbp-toolkit/common/utils';
import {Mutex} from 'async-mutex'; import {Mutex} from 'async-mutex';
import {name as pkgName} from './../package.json';
import {getIconSVGURL} from '@dbp-toolkit/common'; import {getIconSVGURL} from '@dbp-toolkit/common';
import {QrCodeScannerEngine, ScanResult} from './engine.js';
/** /**
* Returns the ID for the most important device * Returns the ID for the most important device
...@@ -118,39 +117,6 @@ async function createVideoElement(deviceId) { ...@@ -118,39 +117,6 @@ async function createVideoElement(deviceId) {
return null; return null;
} }
class QRScanner {
constructor() {
this._engine = null;
this._canvas = document.createElement('canvas');
this._scanner = null;
}
async scan(canvas, x, y, width, height) {
if (this._scanner === null) {
this._scanner = (await import('qr-scanner')).default;
this._scanner.WORKER_PATH = commonUtils.getAssetURL(
pkgName,
'qr-scanner-worker.min.js'
);
}
if (this._engine === null) {
this._engine = await this._scanner.createQrEngine(this._scanner.WORKER_PATH);
}
try {
return {
data: await this._scanner.scanImage(
canvas,
{x: x, y: y, width: width, height: height},
this._engine,
this._canvas
),
};
} catch (e) {
return null;
}
}
}
export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
constructor() { constructor() {
super(); super();
...@@ -272,10 +238,11 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { ...@@ -272,10 +238,11 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
} }
this._askPermission = false; this._askPermission = false;
/** @type {?ScanResult} */
let lastCode = null; let lastCode = null;
let lastSentData = null; let lastSentData = null;
let detector = new QRScanner(); let detector = new QrCodeScannerEngine();
let detectorRunning = false; let detectorRunning = false;
const tick = () => { const tick = () => {
...@@ -307,7 +274,12 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { ...@@ -307,7 +274,12 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
if (!detectorRunning) { if (!detectorRunning) {
detectorRunning = true; detectorRunning = true;
detector detector
.scan(canvasElement, maskStartX, maskStartY, maskWidth, maskHeight) .scanImage(canvasElement, {
x: maskStartX,
y: maskStartY,
width: maskWidth,
height: maskHeight,
})
.then((code) => { .then((code) => {
detectorRunning = false; detectorRunning = false;
// if we got restarted then the video element is new, so stop then. // if we got restarted then the video element is new, so stop then.
...@@ -315,17 +287,18 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) { ...@@ -315,17 +287,18 @@ export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
lastCode = code; lastCode = code;
if (code) { if (code) {
if (lastSentData !== code.data) { let currentData = code.data;
this._outputData = code.data; if (lastSentData !== currentData) {
this._outputData = currentData;
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('code-detected', { new CustomEvent('code-detected', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: {code: code.data}, detail: {code: currentData},
}) })
); );
} }
lastSentData = code.data; lastSentData = currentData;
} else { } else {
this._outputData = null; this._outputData = null;
lastSentData = null; lastSentData = null;
......
import {assert} from '@esm-bundle/chai'; import {assert} from '@esm-bundle/chai';
import '../src/dbp-qr-code-scanner'; import '../src/dbp-qr-code-scanner';
import {QrCodeScannerEngine} from '../src';
suite('dbp-qr-code-scanner basics', () => { suite('dbp-qr-code-scanner basics', () => {
let node; let node;
...@@ -19,3 +20,30 @@ suite('dbp-qr-code-scanner basics', () => { ...@@ -19,3 +20,30 @@ suite('dbp-qr-code-scanner basics', () => {
assert.isNotNull(node.shadowRoot); assert.isNotNull(node.shadowRoot);
}); });
}); });
suite('scan image', () => {
test('should detect', async () => {
let engine = new QrCodeScannerEngine();
let image = new Image();
image.setAttribute(
'src',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmAQMAAACS83vtAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9bi1IqDu0gopChdbIgKuKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE0clJ0UVK/F9SaBHjwXE/3t173L0D/M0qU82ecUDVLCOTSgq5/KrQ+4ogIghhBHGJmfqcKKbhOb7u4ePrXYJneZ/7c/QrBZMBPoF4lumGRbxBPL1p6Zz3iaOsLCnE58RjBl2Q+JHrsstvnEsO+3lm1Mhm5omjxEKpi+UuZmVDJZ4ijimqRvn+nMsK5y3OarXO2vfkLwwXtJVlrtMcRgqLWIIIATLqqKAKCwlaNVJMZGg/6eEfcvwiuWRyVcDIsYAaVEiOH/wPfndrFicn3KRwEgi+2PZHHOjdBVoN2/4+tu3WCRB4Bq60jr/WBGY+SW90tNgRMLANXFx3NHkPuNwBBp90yZAcKUDTXywC72f0TXkgcguE1tze2vs4fQCy1FX6Bjg4BEZLlL3u8e6+7t7+PdPu7wdo/XKjkhoyogAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB+YFEwogFupCMRsAAAAGUExURQAAAP///6XZn90AAAABYktHRAH/Ai3eAAAAuUlEQVQI12P4DwR/GDDJD1KfFWwYvt/ctn4Pw5fYnRpAMrz1BZCM6wKS3y97vN/D8EHUa4ENw//PrPZ/GH7e7FwKJA19l9ow/Lv4extQzdPvjEBzBH+31jB80jyVu4fh//09JjDyn1Ef5x+Gz7caltswfM2xOruH4Y8Qd8Ueho8rnHhrGP6yWjPWAE0+uNOG4YMM+wqgaTe3WtYwfInoZQaS0b1BQDKcZRLQhZf99gJtkSieuIcBh18ArRODGZrlUXYAAAAASUVORK5CYII='
);
let res;
res = await engine.scanImage(image);
assert.strictEqual(res.data, 'http://en.m.wikipedia.org');
// the second time the same
res = await engine.scanImage(image);
assert.strictEqual(res.data, 'http://en.m.wikipedia.org');
// if we don't scan the whole thing then it fails
res = await engine.scanImage(image, {x: 0, y: 0, width: 5, height: 5});
assert.isNull(res);
// if we pass the right size, then everything is OK again
res = await engine.scanImage(image, {x: 0, y: 0, width: image.width, height: image.height});
assert.strictEqual(res.data, 'http://en.m.wikipedia.org');
});
});
...@@ -80,15 +80,37 @@ Or you can include the JS files directly via CDN: ...@@ -80,15 +80,37 @@ Or you can include the JS files directly via CDN:
<script type="module" src="https://unpkg.com/@dbp-toolkit/theme-switcher@0.0.1/dist/theme-switcher.js"></script> <script type="module" src="https://unpkg.com/@dbp-toolkit/theme-switcher@0.0.1/dist/theme-switcher.js"></script>
``` ```
## Theme Switcher ## Usage
### Usage
```html ```html
<dbp-theme-switcher></dbp-theme-switcher> <dbp-theme-switcher></dbp-theme-switcher>
``` ```
### Attributes ## Usage in an application with appshell
If you want to use multiple themes in the appshell, then you have to define the classes inside the `<head>` tag inside the `<style>` tag.
Then add the themes attribute with your themes to the application tag in the `<body>` tag.
### Themes attribute
The themes attribute is organized as an array with objects. The objects have following properties: class(name of the class added to the style tag),
icon (name of an icon, which is displayed infront of the Theme name) and name(Friendly name of your theme).
Attention! Currently we don't support translation of the friendly name, so choose an across languages name.
```html
themes='[{"class": "name-of-your-class", "icon": "name-of-the-icon", "name": "Friendly name of your theme"},
{"class": "name-of-another-class", "icon": "name-of-the-icon", "name": "Friendly name of another theme"}]'
```
### Example
A full example can be found in each application in the `index.html` of the `app-template` folder. (E.g. [Greenlight app-template](https://gitlab.tugraz.at/dbp/greenlight/greenlight/-/blob/main/app-template/index.html))
## Attributes
- `lang` (optional, default: `de`): set to `de` or `en` for German or English - `lang` (optional, default: `de`): set to `de` or `en` for German or English
- example `<dbp-file-source lang="de"></dbp-file-source>` - example `<dbp-file-source lang="de"></dbp-file-source>`
...@@ -108,7 +130,7 @@ Or you can include the JS files directly via CDN: ...@@ -108,7 +130,7 @@ Or you can include the JS files directly via CDN:
detects if the browser uses the dark mode and handles the given string as dark mode class detects if the browser uses the dark mode and handles the given string as dark mode class
### Note ## Note
The classes should be defined outside of the body tag. The classes should be defined outside of the body tag.
## Local development ## Local development
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
"rollup-plugin-delete": "^2.0.0", "rollup-plugin-delete": "^2.0.0",
"rollup-plugin-serve": "^1.0.1", "rollup-plugin-serve": "^1.0.1",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.0", "rollup-plugin-typescript2": "^0.32.0",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"tslib": "^2.0.3", "tslib": "^2.0.3",
"typescript": "^4.1.2" "typescript": "^4.1.2"
......
...@@ -14,5 +14,5 @@ ...@@ -14,5 +14,5 @@
"de": "Gemeinsame Web Components", "de": "Gemeinsame Web Components",
"en": "Common web components" "en": "Common web components"
}, },
"subscribe": "lang,entry-point-url" "subscribe": "lang,entry-point-url,lang-file"
} }
...@@ -13,6 +13,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) { ...@@ -13,6 +13,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) {
super(); super();
this.lang = 'en'; this.lang = 'en';
this.entryPointUrl = ''; this.entryPointUrl = '';
this.langFile = '';
} }
static get scopedElements() { static get scopedElements() {
...@@ -25,6 +26,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) { ...@@ -25,6 +26,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) {
return { return {
...super.properties, ...super.properties,
lang: {type: String}, lang: {type: String},
langFile: {type: String, attribute: 'lang-file'},
entryPointUrl: {type: String, attribute: 'entry-point-url'}, entryPointUrl: {type: String, attribute: 'entry-point-url'},
}; };
} }
...@@ -63,6 +65,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) { ...@@ -63,6 +65,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) {
<dbp-common-demo <dbp-common-demo
id="demo" id="demo"
lang="${this.lang}" lang="${this.lang}"
lang-file="${this.langFile}"
entry-point-url="${this.entryPointUrl}"></dbp-common-demo> entry-point-url="${this.entryPointUrl}"></dbp-common-demo>
`; `;
} }
......
{
"toolkit-showcase": "Dieser Text wird mithilfe von i18n aus einer benutzerdefinierten Sprachdatei gelesen und ins Englische übersetzt wenn man die Sprache auf Englisch stellt."
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment