Skip to content
Snippets Groups Projects
Unverified Commit 872fc610 authored by Bekerle, Patrizio's avatar Bekerle, Patrizio :fire:
Browse files

Implement initial version of signing application

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 1256 additions and 0 deletions
{
"name": {
"de": "Amtssignature",
"en": "Offical Signature"
},
"short_name": {
"de": "Amtssignature",
"en": "Offical Signature"
},
"description": {
"de": "",
"en": ""
},
"routing_name": "signature",
"activities": [
{"path": "vpu-signature-pdf-upload.metadata.json"},
{"path": "vpu-signature-profile.metadata.json",
"visible": false}
],
"attributes": []
}
\ No newline at end of file
module.exports = {
input: [
'src/**/*.js',
],
output: './',
options: {
debug: false,
removeUnusedKeys: true,
lngs: ['en','de'],
func: {
list: ['i18n.t', 'i18nKey']
},
resource: {
loadPath: 'src/i18n/{{lng}}/{{ns}}.json',
savePath: 'src/i18n/{{lng}}/{{ns}}.json'
},
},
}
// Trick to use the auto-downloaded puppeteer chrome binary
process.env.CHROME_BIN = require('puppeteer').executablePath();
module.exports = function(config) {
config.set({
basePath: 'dist',
frameworks: ['mocha'],
client: {
mocha: {
ui: 'tdd',
},
},
files: [
{pattern: './*.js', included: true, watched: true, served: true, type: 'module'},
{pattern: './**/*', included: false, watched: true, served: true},
],
autoWatch: true,
browsers: ['ChromeHeadlessNoSandbox', 'FirefoxHeadless'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
singleRun: false,
logLevel: config.LOG_ERROR
});
}
This diff is collapsed.
{
"name": "vpu-signature",
"version": "1.0.0",
"main": "src/vpu-signature.js",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-modules": "^0.1.3",
"@babel/runtime-corejs3": "^7.8.4",
"@rollup/plugin-json": "^4.0.2",
"@rollup/plugin-replace": "^2.3.1",
"@rollup/plugin-url": "^4.0.2",
"acorn": "^6.1.0",
"babel-eslint": "^10.0.3",
"chai": "^4.2.0",
"chokidar": "^3.3.1",
"eslint": "^6.8.0",
"eslint-plugin-jsdoc": "^21.0.0",
"glob": "^7.1.6",
"i18next-scanner": "^2.10.3",
"karma": "^4.4.1",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-firefox-launcher": "^1.3.0",
"karma-mocha": "^1.3.0",
"license-checker": "^25.0.1",
"mocha": "^7.0.0",
"puppeteer": "^2.1.1",
"rollup": "^1.31.1",
"rollup-plugin-babel": "^5.0.0-alpha.1",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-consts": "^1.0.1",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-delete": "^1.2.0",
"rollup-plugin-emit-ejs": "^3.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-serve": "^1.0.1",
"rollup-plugin-terser": "^5.2.0",
"selfsigned": "^1.10.7"
},
"dependencies": {
"i18next": "^19.1.0",
"jquery": "^3.4.1",
"lit-element": "^2.2.1",
"lit-html": "^1.1.2",
"select2": "^4.0.13",
"source-sans-pro": "^2.45.0",
"suggestions": "^1.7.0",
"universal-router": "^8.3.0",
"vpu-auth": "file:./vendor/auth",
"vpu-common": "file:./vendor/common",
"vpu-language-select": "file:./vendor/language-select",
"vpu-notification": "file:./vendor/notification",
"vpu-person-profile": "file:./vendor/person-profile"
},
"scripts": {
"clean": "rm dist/* -R",
"build": "npm run build-local",
"build-local": "rollup -c",
"build-dev": "rollup -c --environment BUILD:development",
"build-prod": "rollup -c --environment BUILD:production",
"build-demo": "rollup -c --environment BUILD:demo",
"build-test": "rollup -c --environment BUILD:test",
"i18next": "i18next-scanner",
"watch": "npm run watch-local",
"watch-local": "rollup -c --watch",
"watch-dev": "rollup -c --watch --environment BUILD:development",
"test": "npm run build-test && karma start --singleRun",
"lint": "eslint .",
"license-checker": "license-checker --production --onlyAllow 'MIT;BSD-3-Clause;ISC;Apache-2.0;BSD-2-Clause' --excludePackages 'vpu-auth@1.0.0;vpu-common@1.0.1;vpu-data-table-view@1.0.0;vpu-knowledge-base-web-page-element-view@1.0.0;vpu-signature@1.0.0;vpu-language-select@1.0.0;vpu-signature-book-offer-select@1.0.0;vpu-notification@1.0.0;vpu-person-profile@1.0.0;vpu-person-select@1.0.0'"
}
}
import path from 'path';
import fs from 'fs';
import url from 'url';
import glob from 'glob';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import copy from 'rollup-plugin-copy';
import {terser} from "rollup-plugin-terser";
import json from '@rollup/plugin-json';
import replace from "@rollup/plugin-replace";
import serve from 'rollup-plugin-serve';
import urlPlugin from "@rollup/plugin-url";
import consts from 'rollup-plugin-consts';
import del from 'rollup-plugin-delete';
import emitEJS from 'rollup-plugin-emit-ejs'
import babel from 'rollup-plugin-babel'
import chai from 'chai';
import selfsigned from 'selfsigned';
// -------------------------------
// Some new web APIs are only available when HTTPS is active.
// Note that this only works with a Non-HTTPS API endpoint with Chrome,
// Firefox will emit CORS errors, see https://bugzilla.mozilla.org/show_bug.cgi?id=1488740
const USE_HTTPS = false;
// -------------------------------
const pkg = require('./package.json');
const build = (typeof process.env.BUILD !== 'undefined') ? process.env.BUILD : 'local';
console.log("build: " + build);
let basePath = '';
let entryPointURL = '';
let keyCloakServer = '';
let keyCloakBaseURL = '';
let matomoSiteId = 131;
let useTerser = true;
let useBabel = true;
switch (build) {
case 'local':
basePath = '/dist/';
entryPointURL = 'http://127.0.0.1:8000';
keyCloakServer = 'auth-dev.tugraz.at';
keyCloakBaseURL = 'https://' + keyCloakServer + '/auth';
useTerser = false;
break;
case 'development':
basePath = '/apps/signature/';
entryPointURL = 'https://mw-dev.tugraz.at';
keyCloakServer = 'auth-dev.tugraz.at';
keyCloakBaseURL = 'https://' + keyCloakServer + '/auth';
break;
case 'demo':
basePath = '/apps/signature/';
entryPointURL = 'https://signature-demo.tugraz.at';
keyCloakServer = 'auth-test.tugraz.at';
keyCloakBaseURL = 'https://' + keyCloakServer + '/auth';
break;
case 'production':
basePath = '/';
entryPointURL = 'https://signature.tugraz.at';
keyCloakServer = 'auth.tugraz.at';
keyCloakBaseURL = 'https://' + keyCloakServer + '/auth';
matomoSiteId = 130;
break;
case 'test':
basePath = '/apps/signature/';
entryPointURL = '';
keyCloakServer = '';
keyCloakBaseURL = '';
useTerser = false;
break;
default:
console.error('Unknown build environment: ' + build);
process.exit(1);
}
const CHUNK_BLACKLIST = [
'jszip', // jszip is a node module by default and rollup chunking is confused by that and emits warnings
'source-sans-pro'
];
/**
* Returns a list of chunks used for splitting up the bundle.
* We recursively use every dependency and ever internal dev dependency (starting with 'vpu-').
*/
function getManualChunks(pkg) {
let manualChunks = Object.keys(pkg.dependencies).reduce(function (acc, item) { acc[item] = [item]; return acc;}, {});
const vpu = Object.keys(pkg.devDependencies).reduce(function (acc, item) { if (item.startsWith('vpu-')) acc[item] = [item]; return acc;}, {});
for (const vpuName in vpu) {
const subPkg = require('./node_modules/' + vpuName + '/package.json');
manualChunks = Object.assign(manualChunks, getManualChunks(subPkg));
}
manualChunks = Object.assign(manualChunks, vpu);
for(const name of CHUNK_BLACKLIST) {
delete manualChunks[name];
}
return manualChunks;
}
/**
* Creates a server certificate and caches it in the .cert directory
*/
function generateTLSConfig() {
fs.mkdirSync('.cert', {recursive: true});
if (!fs.existsSync('.cert/server.key') || !fs.existsSync('.cert/server.cert')) {
const attrs = [{name: 'commonName', value: 'vpu-dev.localhost'}];
const pems = selfsigned.generate(attrs, {algorithm: 'sha256', days: 9999});
fs.writeFileSync('.cert/server.key', pems.private);
fs.writeFileSync('.cert/server.cert', pems.cert);
}
return {
key: fs.readFileSync('.cert/server.key'),
cert: fs.readFileSync('.cert/server.cert')
}
}
function getBuildInfo() {
const child_process = require('child_process');
const url = require('url');
let remote = child_process.execSync('git config --get remote.origin.url').toString().trim();
let commit = child_process.execSync('git rev-parse --short HEAD').toString().trim();
let parsed = url.parse(remote);
let newPath = parsed.path.slice(0, parsed.path.lastIndexOf('.'));
let newUrl = parsed.protocol + '//' + parsed.host + newPath + '/commit/' + commit;
return {
info: commit,
url: newUrl,
time: new Date().toISOString(),
env: build
}
}
export default {
input: (build != 'test') ? [
'src/vpu-signature.js',
'src/vpu-signature-pdf-upload.js',
'src/vpu-signature-create-loan.js',
'src/vpu-signature-return-book.js',
'src/vpu-signature-renew-loan.js',
'src/vpu-signature-book-list.js',
'src/vpu-signature-loan-list.js',
'src/vpu-signature-order-list.js',
'src/vpu-signature-profile.js',
] : glob.sync('test/**/*.js'),
output: {
dir: 'dist',
entryFileNames: '[name].js',
chunkFileNames: 'shared/[name].[hash].[format].js',
format: 'esm',
sourcemap: true
},
manualChunks: getManualChunks(pkg),
onwarn: function (warning, warn) {
// ignore "suggestions" warning re "use strict"
if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
return;
}
// ignore chai warnings
if (warning.code === 'CIRCULAR_DEPENDENCY') {
return;
}
// keycloak bundled code uses eval
if (warning.code === 'EVAL') {
return;
}
warn(warning);
},
watch: {
chokidar: {
usePolling: true
}
},
plugins: [
del({
targets: 'dist/*'
}),
consts({
environment: build,
buildinfo: getBuildInfo(),
}),
emitEJS({
src: 'assets',
include: ['**/*.ejs', '**/.*.ejs'],
data: {
geturl: (p) => {
return url.resolve(basePath, p);
},
entryPointURL: entryPointURL,
keyCloakServer: keyCloakServer,
keyCloakBaseURL: keyCloakBaseURL,
environment: build,
matomoSiteId: matomoSiteId,
buildinfo: getBuildInfo()
}
}),
resolve({
customResolveOptions: {
// ignore node_modules from vendored packages
moduleDirectory: path.join(process.cwd(), 'node_modules')
}
}),
commonjs({
include: 'node_modules/**',
namedExports: {
'chai': Object.keys(chai),
}
}),
json(),
urlPlugin({
limit: 0,
include: [
"node_modules/suggestions/**/*.css",
"node_modules/select2/**/*.css",
],
emitFiles: true,
fileName: 'shared/[name].[hash][extname]'
}),
replace({
"process.env.BUILD": '"' + build + '"',
}),
useTerser ? terser({output: {comments: false}}) : false,
copy({
targets: [
{src: 'assets/silent-check-sso.html', dest:'dist'},
{src: 'assets/htaccess-shared', dest: 'dist/shared/', rename: '.htaccess'},
{src: 'assets/*.css', dest: 'dist/local/' + pkg.name},
{src: 'assets/*.ico', dest: 'dist/local/' + pkg.name},
{src: 'assets/*.svg', dest: 'dist/local/' + pkg.name},
{src: 'node_modules/source-sans-pro/WOFF2/OTF/*', dest: 'dist/local/' + pkg.name + '/fonts'},
{src: 'node_modules/vpu-common/vpu-spinner.js', dest: 'dist/local/' + pkg.name, rename: 'spinner.js'},
{src: 'assets/browser-check.js', dest: 'dist/local/' + pkg.name},
{src: 'assets/icon-*.png', dest: 'dist/local/' + pkg.name},
{src: 'assets/manifest.json', dest: 'dist', rename: pkg.name + '.manifest.json'},
{src: 'assets/*.metadata.json', dest: 'dist'},
{src: 'node_modules/vpu-common/assets/icons/*.svg', dest: 'dist/local/vpu-common/icons'},
],
}),
useBabel && babel({
exclude: 'node_modules/**',
babelHelpers: 'runtime',
babelrc: false,
presets: [[
'@babel/preset-modules', {
loose: true
}
]],
plugins: [[
'@babel/plugin-transform-runtime', {
corejs: 3,
useESModules: true
}
],
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-import-meta']
}),
(process.env.ROLLUP_WATCH === 'true') ? serve({
contentBase: '.',
host: '127.0.0.1',
port: 8001,
historyApiFallback: basePath + pkg.name + '.html',
https: USE_HTTPS ? generateTLSConfig() : false,
headers: {
'Content-Security-Policy': `default-src 'self' 'unsafe-eval' 'unsafe-inline' analytics.tugraz.at ${keyCloakServer} ${entryPointURL}; img-src *`
},
}) : false
]
};
#!/usr/bin/env bash
###############################################################
# Updates all vendor repositories to the latest master branch
###############################################################
git submodule foreach "git checkout master; git pull"
import {html, LitElement, css} from 'lit-element';
import * as commonUtils from 'vpu-common/utils';
import * as commonStyles from 'vpu-common/styles';
import buildinfo from 'consts:buildinfo';
class VPUBuildInfo extends LitElement {
constructor() {
super();
}
static get styles() {
return css`
${commonStyles.getThemeCSS()}
${commonStyles.getGeneralCSS()}
${commonStyles.getTagCSS()}
:host {
display: inline-block;
}
`;
}
render() {
const date = new Date(buildinfo.time);
return html`
<a href="${buildinfo.url}" style="float: right">
<div class="tags has-addons" title="Build Time: ${date.toString()}">
<span class="tag is-light">build</span>
<span class="tag is-dark">${buildinfo.info} (${buildinfo.env})</span>
</div>
</a>
`;
}
}
commonUtils.defineCustomElement('vpu-build-info', VPUBuildInfo);
This diff is collapsed.
import UniversalRouter from 'universal-router';
import generateUrls from 'universal-router/generateUrls';
/**
* A wrapper around UniversalRouter which adds history integration
*/
export class Router {
/**
* @param {Array} routes The routes passed to UniversalRouter
* @param {object} options Options
* @param {string} options.routeName The main route name
* @param {Function} options.getState Function which should return the current state
* @param {Function} options.setState Function which gets passed the new state based on the route
* @param {object} unioptions options passed to UniversalRouter
*/
constructor(routes, options, unioptions) {
this.getState = options.getState;
this.setState = options.setState;
// XXX: We only have one route atm
// If we need more we need to pass the route name to each function
this.routeName = options.routeName;
console.assert(this.getState);
console.assert(this.setState);
console.assert(this.routeName);
// https://github.com/kriasoft/universal-router
this.router = new UniversalRouter(routes, unioptions);
window.addEventListener('popstate', (event) => {
this.setStateFromCurrentLocation();
this.dispatchLocationChanged();
});
}
/**
* In case something else has changed the location, update the app state accordingly.
*/
setStateFromCurrentLocation() {
const oldPathName = location.pathname;
this.router.resolve({pathname: oldPathName}).then(page => {
const newPathname = this.getPathname(page);
// In case of a router redirect, set the new location
if (newPathname !== oldPathName) {
const referrerUrl = location.href;
window.history.replaceState({}, '', newPathname);
this.dispatchLocationChanged(referrerUrl);
}
this.setState(page);
}).catch((e) => {
// In case we can't resolve the location, just leave things as is.
// This happens when a user enters a wrong URL or when testing with karma.
});
}
/**
* Update the router after some internal state change.
*/
update() {
// Queue updates so we can call this multiple times when changing state
// without it resulting in multiple location changes
setTimeout(() => {
const newPathname = this.getPathname();
const oldPathname = location.pathname;
if (newPathname === oldPathname)
return;
const referrerUrl = location.href;
window.history.pushState({}, '', newPathname);
this.dispatchLocationChanged(referrerUrl);
});
}
/**
* Given a new routing path set the location and the app state.
*
* @param {string} pathname
*/
updateFromPathname(pathname) {
this.router.resolve({pathname: pathname}).then(page => {
if (location.pathname === pathname)
return;
const referrerUrl = location.href;
window.history.pushState({}, '', pathname);
this.setState(page);
this.dispatchLocationChanged(referrerUrl);
}).catch((err) => {
throw new Error(`Route not found: ${pathname}: ${err}`);
});
}
/**
* Pass some new router state to get a new router path that can
* be passed to updateFromPathname() later on. If nothing is
* passed the current state is used.
*
* @param {object} [partialState] The optional partial new state
* @returns {string} The new path
*/
getPathname(partialState) {
const currentState = this.getState();
if (partialState === undefined)
partialState = {};
let combined = {...currentState, ...partialState};
return generateUrls(this.router)(this.routeName, combined);
}
dispatchLocationChanged(referrerUrl = "") {
// fire a locationchanged event
window.dispatchEvent(new CustomEvent('locationchanged', {
detail: {
referrerUrl: referrerUrl,
},
bubbles: true
}));
}
}
import {html, LitElement, css} from 'lit-element';
import * as commonUtils from 'vpu-common/utils';
import * as commonStyles from 'vpu-common/styles';
import {createI18nInstance} from '../i18n.js';
const i18n = createI18nInstance();
class VPUTUGrazLogo extends LitElement {
constructor() {
super();
this.lang = i18n.language;
}
static get properties() {
return {
lang: { type: String }
};
}
update(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
if (propName === "lang") {
i18n.changeLanguage(this.lang);
}
});
super.update(changedProperties);
}
static get styles() {
return css`
${commonStyles.getThemeCSS()}
${commonStyles.getGeneralCSS()}
:host {
display: inline-block;
}
#claim
{
font-size: 12px;
text-align: right;
padding: 0 17px 0 0;
line-height: 17px;
letter-spacing: 2px;
vertical-align: top;
text-transform: uppercase;
display: inline-block;
white-space: nowrap;
}
#img {
overflow: visible;
}
a:hover path, a:focus path {
fill:#000 !important;
transition:none;
}
* {
transition:fill 0.15s, stroke 0.15s;
}
`;
}
render() {
return html`
<a href="https://www.tugraz.at" title="TU Graz Home" target="_blank" rel="noopener">
<div id="claim">
<div class="int-header-logo-claim-single">${i18n.t('logo.word1')}</div>
<div class="int-header-logo-claim-single">${i18n.t('logo.word2')}</div>
<div class="int-header-logo-claim-single">${i18n.t('logo.word3')}</div>
</div>
<svg id="img" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="51.862" width="141.1" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 141.10001 51.862499"><g transform="matrix(1.25 0 0 -1.25 0 51.862)"><g transform="scale(.1)"><path style="fill:#e4154b" d="m0 103.73h207.45v207.46l-207.45 0.01v-207.47z"></path><path style="fill:#e4154b" d="m228.19 103.73h207.46v207.46h-207.46v-207.46z"></path><path style="fill:#e4154b" d="m456.41 103.73h207.44v207.46h-207.44v-207.46z"></path><path style="fill:#e4154b" d="m103.72 0h207.47v207.46h-207.47v-207.46z"></path><path style="fill:#e4154b" d="m352.68 207.46h207.44v207.46h-207.44v-207.46z"></path><path style="fill:#231f20" d="m751.04 277.91h-66.426v33.195h171.19v-33.195h-66.407v-173.73h-38.359v173.73"></path><path style="fill:#231f20" d="m1048.3 180.22c0-12.461-2.25-23.711-6.72-33.75-4.5-10.039-10.61-18.555-18.36-25.567-7.76-7.031-16.9-12.421-27.503-16.21-10.605-3.809-22.109-5.7036-34.551-5.7036-12.422 0-23.945 1.8946-34.551 5.7036-10.605 3.789-19.824 9.179-27.656 16.21-7.851 7.012-13.984 15.528-18.34 25.567-4.394 10.039-6.582 21.289-6.582 33.75v130.89h38.379v-129.59c0-5.039 0.801-10.351 2.442-15.898 1.64-5.547 4.336-10.664 8.125-15.332s8.789-8.516 15.039-11.523c6.211-3.008 13.926-4.512 23.144-4.512 9.199 0 16.914 1.504 23.145 4.512 6.23 3.007 11.25 6.855 15.039 11.523 3.77 4.668 6.48 9.785 8.12 15.332 1.63 5.547 2.45 10.859 2.45 15.898v129.59h38.38v-130.89"></path><path style="fill:#231f20" d="m832.56 75.664c-7.597 3.2812-17.46 4.8632-25.332 4.8632-22.929 0-35.605-14.434-35.605-33.184 0-18.613 12.383-32.637 33.34-32.637 5.351 0 9.59 0.5274 12.969 1.3086v23.867h-20.84v14.414h39.687v-49.297c-10.41-2.6172-21.25-4.707-31.816-4.707-31.797 0-53.906 14.805-53.906 45.742 0 31.348 20.566 48.906 53.906 48.906 11.406 0 20.41-1.4453 28.867-3.8086l-1.27-15.469"></path><path style="fill:#231f20" d="m856.2 69.375h16.758v-15.332h0.293c0.84 6.289 8.574 16.914 19.824 16.914 1.836 0 3.828 0 5.782-0.5273v-17.715c-1.68 0.918-5.059 1.4454-8.457 1.4454-15.333 0-15.333-17.832-15.333-27.52v-24.785h-18.867v67.52"></path><path style="fill:#231f20" d="m913.75 65.84c7.324 3.1446 17.187 5.1172 25.215 5.1172 22.09 0 31.23-8.5351 31.23-28.457v-8.6523c0-6.8165 0.156-11.934 0.293-16.914 0.137-5.1172 0.41-9.8242 0.84-15.078h-16.602c-0.703 3.5352-0.703 8.0078-0.839 10.098h-0.293c-4.36-7.4618-13.81-11.661-22.38-11.661-12.793 0-25.332 7.207-25.332 20.059 0 10.078 5.195 15.976 12.383 19.258 7.187 3.2812 16.464 3.9453 24.355 3.9453h10.41c0 10.879-5.195 14.551-16.328 14.551-8.008 0-16.035-2.8907-22.363-7.3438l-0.586 15.078zm22.11-52.715c5.782 0 10.274 2.3633 13.223 6.0352 3.105 3.8086 3.945 8.6523 3.945 13.906h-8.164c-8.437 0-20.957-1.3086-20.957-11.68 0-5.7617 5.195-8.2617 11.953-8.2617"></path><path style="fill:#231f20" d="m985.69 69.375h57.422v-14.414l-36.04-39.473h37.31v-13.633h-60.235v14.297l36.715 39.59h-35.172v13.633"></path><path style="fill:#e4154b" d="m1059.6 0h69.102v69.121h-69.102v-69.121z"></path></g></g></svg>
</a>
`;
}
}
commonUtils.defineCustomElement('vpu-tugraz-logo', VPUTUGrazLogo);
import {createInstance} from 'vpu-common/i18next.js';
import de from './i18n/de/translation.json';
import en from './i18n/en/translation.json';
const i18n = createInstance({en: en, de: de}, 'de', 'en');
export function createI18nInstance () {
return i18n.cloneInstance();
}
/**
* Dummy function to mark strings as i18next keys for i18next-scanner
*
* @param {string} key
* @returns {string} The key param as is
*/
export function i18nKey(key) {
return key;
}
\ No newline at end of file
{
"logo": {
"word1": "Wissen",
"word2": "Technik",
"word3": "Leidenschaft"
},
"person-select": {
"headline": "Person"
},
"signature-book-offer-select": {
"headline": "Buch"
},
"organization-select": {
"label": "Institut"
},
"pdf-upload": {
"button-refresh-title": "Aufstellung von {{name}} erneut laden"
},
"create-loan": {
"info-no-existing-loans-summary": "Keine bestehenden Entlehnungen",
"info-no-existing-loans-body": "Es wurden keine bestehenden Entlehnungen gefunden, das Buch kann entlehnt werden.",
"error-existing-loans-summary": "Bestehende Entlehnung!",
"error-existing-loans-body": "Es wurde eine bestehende Entlehnung gefunden, das Buch kann nicht entlehnt werden!",
"submit": "Buch entlehnen",
"mini-spinner-text": "Entlehnungen werden geladen...",
"success-summary": "Buchentlehnung erfolgreich",
"success-body": "Das Buch wurde erfolgreich entlehnt.",
"button-refresh-title": "Entlehnungen von {{name}} erneut überprüfen"
},
"return-book": {
"error-no-existing-loans-summary": "Keine bestehenden Entlehnungen",
"error-no-existing-loans-body": "Es wurden keine bestehenden Entlehnungen gefunden!",
"info-existing-loans-summary": "Bestehende Entlehnung!",
"info-existing-loans-body": "Es wurden eine bestehende Entlehnung gefunden, das Buch kann zurückgegeben werden!",
"submit": "Buch zurückgeben",
"mini-spinner-text": "Entlehnungen werden geladen...",
"success-summary": "Buchrückgabe erfolgreich",
"success-body": "Das Buch von {{personName}} wurde erfolgreich zurückgegeben.",
"borrower": "Entlehner",
"user-name-unknown": "Unbekannt",
"button-refresh-title": "Entlehnung von {{name}} erneut laden"
},
"renew-loan": {
"no-loans": "Es wurden keine bestehenden Entlehnungen gefunden!",
"book": "Buch",
"error-load-loans-summary": "Fehler beim Laden der Entlehnungen",
"end-date": "Rückgabedatum",
"loans": "Entlehnungen",
"renew-loan": "Entlehnung verlängern",
"mini-spinner-text": "Entlehnungen werden geladen...",
"error-renew-loan-summary": "Fehler beim Verlängern",
"info-renew-loan-success-summary": "Verlängern erfolgreich",
"info-renew-loan-success-body": "Das Verlängern der Entlehnung war erfolgreich.",
"error-renew-loan-date-in-past": "Enddatum darf nicht in der Vergangenheit liegen!",
"button-refresh-title": "Entlehnungen von {{personName}} erneut laden",
"contact-value": "EMail",
"contact-title": "Per EMail Kontakt mit {{personName}} aufnehmen",
"contact-subject": "Entlehnung von: {{bookName}}"
},
"book-list": {
"no-books": "Es sind keine Bücher vorhanden.",
"mini-spinner-text": "Bücher werden geladen...",
"books": "Bücher",
"export-name": "Buchliste {{organizationCode}}",
"book-title": "Titel",
"book-author": "Autor",
"book-isbn": "ISBN",
"book-barcode": "Barcode",
"book-location": "Ort",
"book-location-identifier": "Aufstellung",
"book-description": "Beschreibung",
"book-publication-year": "Erscheinungsjahr",
"book-publisher": "Verlag",
"book-availability-date": "Inventarisierungsdatum",
"location-identifier-select-placeholder": "Bitte wählen Sie eine Aufstellung aus um die Büchsterliste einzuschränken",
"book-inventory-year": "Inventarisierungsjahr",
"inventory-year-select-placeholder": "Bitte wählen Sie ein Inventarisierungsjahr aus um die Büchsterliste einzuschränken",
"error-load-books": "Liste der Bücher konnte nicht geladen werden",
"current-state": "Stand"
},
"loan-list": {
"no-loans": "Es sind keine Entlehnungen vorhanden.",
"mini-spinner-text": "Entlehnungen werden geladen...",
"loans": "Entlehnungen",
"export-name": "Entlehnungen {{organizationCode}}",
"overdue-only": "Nur überfällige Entlehnungen anzeigen",
"open-only": "Nur offene Entlehnungen anzeigen",
"borrower-name": "Entlehner",
"book-author": "Autor/Herausgeber",
"start-date": "Entlehndatum",
"due-date": "Fällig am",
"return-date": "Rückgegeben am",
"error-load-loans": "Liste der Entlehnungen konnte nicht geladen werden",
"error-load-loan": "Entlehnung konnte nicht geladen werden",
"current-state": "Stand"
},
"order-list": {
"export-name": "Buchbestellungen {{organizationCode}}",
"error-load-orders": "Liste der Bestellungen konnte nicht geladen werden",
"mini-spinner-text": "Bestellungen werden geladen...",
"order-date": "Bestelldatum",
"delivery-date": "Lieferdatum",
"order-status": "Status",
"order-number": "Bestellnummer",
"book-price": "Preis",
"receiving -note": "Eingangsnotiz",
"current-state": "Stand",
"sum-of-column": "Summe der Spalte"
},
"location-identifier": {
"headline": "Identifikator zur Aufstellung",
"placeholder": "Bitte geben Sie den Identifikator um das Buch aufzustellen ein",
"submit": "Abschicken"
},
"select-organization": {
"placeholder": "Bitte wählen Sie ein Institut aus",
"loading": "Wird geladen...",
"load-error": "Fehler beim Laden der Organisation"
},
"success-summary": "Buchaufstellung erfolgreich",
"success-body": "Das Buch \"{{name}}\" wurde erfolgreich aufgestellt.",
"error-summary": "Ein Fehler ist aufgetreten",
"error-permission-message": "Sie müssen Bibliotheksbeauftragte/r sein um diese Funktion nutzen zu können!",
"error-login-message": "Sie müssen eingeloggt sein um diese Funktion nutzen zu können!",
"privacy-policy": "Datenschutz",
"page-updated-needs-reload": "Die Applikation wurde aktualisiert. Bitte laden Sie die Seite neu."
}
{
"logo": {
"word1": "Science",
"word2": "Passion",
"word3": "Technology"
},
"person-select": {
"headline": "Person"
},
"signature-book-offer-select": {
"headline": "Book"
},
"organization-select": {
"label": "Institute"
},
"pdf-upload": {
"button-refresh-title": "Load pdf-upload from {{name}} again"
},
"create-loan": {
"info-no-existing-loans-summary": "No existing loans",
"info-no-existing-loans-body": "No existing loans were found, a new one can be created.",
"error-existing-loans-summary": "Existing loan was found!",
"error-existing-loans-body": "Existing loan was found, a new one cannot be created!",
"submit": "Loan book",
"mini-spinner-text": "Loading loans...",
"success-summary": "Book loan creation successful",
"success-body": "The book loan creation successful.",
"button-refresh-title": "Check loans of {{name}} again"
},
"return-book": {
"error-no-existing-loans-summary": "No existing loans",
"error-no-existing-loans-body": "No existing loans were found!",
"info-existing-loans-summary": "Existing loan was found!",
"info-existing-loans-body": "Existing loan was found, it can be returned!",
"submit": "Return book",
"mini-spinner-text": "Loading loans...",
"success-summary": "Book returned successfully",
"success-body": "The book from {{personName}} was successfully returned.",
"borrower": "Borrower",
"user-name-unknown": "Unknown",
"button-refresh-title": "Load loans of {{name}} again"
},
"renew-loan": {
"no-loans": "No existing loans were found!",
"book": "Book",
"error-load-loans-summary": "Error on loading loans",
"end-date": "Return date",
"loans": "Loans",
"renew-loan": "Renew loan",
"mini-spinner-text": "Loading loans...",
"error-renew-loan-summary": "Renewal failed",
"info-renew-loan-success-summary": "Renewal successfull",
"info-renew-loan-success-body": "The renewal of the loan was successful.",
"error-renew-loan-date-in-past": "End time may not be in the past!",
"button-refresh-title": "Load loans from {{personName}} again",
"contact-value": "EMail",
"contact-title": "Contact {{personName}} via email",
"contact-subject": "Loaning of: {{bookName}}"
},
"book-list": {
"no-books": "There are no books.",
"mini-spinner-text": "Loading books...",
"books": "Books",
"export-name": "Books {{organizationCode}}",
"book-title": "Title",
"book-author": "Author",
"book-isbn": "ISBN",
"book-barcode": "Barcode",
"book-location": "Place",
"book-location-identifier": "Location",
"book-description": "Description",
"book-publication-year": "Publication year",
"book-publisher": "Publisher",
"book-availability-date": "Availability date",
"location-identifier-select-placeholder": "Please select a location to restrict the book list",
"book-inventory-year": "Availability year",
"inventory-year-select-placeholder": "Please select a availability year to restrict the book list",
"error-load-books": "List of books could not be loaded",
"current-state": "State"
},
"loan-list": {
"no-loans": "There are no books.",
"mini-spinner-text": "Loading loans...",
"loans": "Loans",
"export-name": "Loans {{organizationCode}}",
"overdue-only": "Show overdue loans only",
"open-only": "Show open loans only",
"borrower-name": "Borrower",
"book-author": "Author/Publisher",
"start-date": "Start date",
"due-date": "Due date",
"return-date": "Return date",
"error-load-loans": "List of loans could not be loaded",
"error-load-loan": "Loan could not be loaded",
"current-state": "State"
},
"order-list": {
"export-name": "Orders {{organizationCode}}",
"error-load-orders": "List of orders could not be loaded",
"mini-spinner-text": "Loading order...",
"order-date": "Order date",
"delivery-date": "Delivery date",
"order-status": "Status",
"order-number": "Order number",
"book-price": "Price",
"receiving -note": "Receiving -note",
"current-state": "State",
"sum-of-column": "Sum of column"
},
"location-identifier": {
"headline": "Identifier for book placement",
"placeholder": "Please enter the identifier to place the book",
"submit": "Send"
},
"select-organization": {
"placeholder": "Please select an organization",
"loading": "Loading...",
"load-error": "Error loading the organization"
},
"success-summary": "Book lineup successful",
"success-body": "The book lineup for \"{{name}}\" was successful.",
"error-summary": "An error occurred",
"error-permission-message": "You need to be a signature officer to use this function!",
"error-login-message": "You need to be logged in to use this function!",
"privacy-policy": "Privacy Policy",
"page-updated-needs-reload": "The application has been updated. Please reload the page."
}
/**
* Finds an object in a JSON result by identifier
*
* @param identifier
* @param results
* @param identifierAttribute
*/
export const findObjectInApiResults = (identifier, results, identifierAttribute = "@id") => {
const members = results["hydra:member"];
if (members === undefined) {
return;
}
for (const object of members) {
if (object[identifierAttribute] === identifier) {
return object;
}
}
};
import {LitElement} from "lit-element";
import * as events from 'vpu-common/events.js';
export default class VPUSignatureLitElement extends LitElement {
_(selector) {
return this.shadowRoot === null ? this.querySelector(selector) : this.shadowRoot.querySelector(selector);
}
hasSignaturePermissions() {
// TODO: Roles need to be discussed
// return (window.VPUPerson && Array.isArray(window.VPUPerson.roles) && window.VPUPerson.roles.indexOf('ROLE_F_BIB_F') !== -1);
return window.VPUPerson && Array.isArray(window.VPUPerson.roles);
}
_updateAuth() {
if (this.isLoggedIn() && !this._loginCalled) {
this._loginCalled = true;
this.loginCallback();
}
}
connectedCallback() {
super.connectedCallback();
this._loginCalled = false;
this._subscriber = new events.EventSubscriber('vpu-auth-update', 'vpu-auth-update-request');
this._updateAuth = this._updateAuth.bind(this);
this._subscriber.subscribe(this._updateAuth);
}
disconnectedCallback() {
this._subscriber.unsubscribe(this._updateAuth);
delete this._subscriber;
super.disconnectedCallback();
}
isLoggedIn() {
return (window.VPUPerson !== undefined && window.VPUPerson !== null);
}
loginCallback() {
// Implement in subclass
this.requestUpdate();
}
getOrganization() {
const organizationSelect = this._("vpu-knowledge-base-organization-select");
if (organizationSelect) {
const objectText = organizationSelect.getAttribute("data-object");
if (objectText !== null) {
return JSON.parse(objectText);
}
}
return null;
}
getOrganizationCode() {
const organization = this.getOrganization();
return organization !== null ? organization.alternateName : "";
}
}
import $ from 'jquery';
import {createI18nInstance} from './i18n.js';
import {css, html} from 'lit-element';
import {send as notify} from 'vpu-notification';
import VPUSignatureLitElement from "./vpu-signature-lit-element";
import Suggestions from 'suggestions';
import 'vpu-signature-book-offer-select';
import * as commonUtils from 'vpu-common/utils';
import * as commonStyles from 'vpu-common/styles';
import suggestionsCSSPath from 'suggestions/dist/suggestions.css';
import * as errorUtils from "vpu-common/error";
import './vpu-organization-select.js';
import {classMap} from 'lit-html/directives/class-map.js';
const i18n = createI18nInstance();
class SignaturePdfUpload extends VPUSignatureLitElement {
constructor() {
super();
this.lang = i18n.language;
this.entryPointUrl = commonUtils.getAPiUrl();
this.organizationId = '';
}
static get properties() {
return {
lang: { type: String },
entryPointUrl: { type: String, attribute: 'entry-point-url' },
};
}
$(selector) {
return $(this._(selector));
}
connectedCallback() {
super.connectedCallback();
const that = this;
this.updateComplete.then(()=>{
});
}
update(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
if (propName === "lang") {
i18n.changeLanguage(this.lang);
}
});
super.update(changedProperties);
}
onLanguageChanged(e) {
this.lang = e.detail.lang;
}
static get styles() {
// language=css
return css`
${commonStyles.getThemeCSS()}
${commonStyles.getGeneralCSS()}
${commonStyles.getNotificationCSS()}
.hidden {
display: none;
}
#location-identifier-block { display: none; }
#location-identifier-block input {
width: 100%;
border-radius: var(--vpu-border-radius);
}
`;
}
render() {
const suggestionsCSS = commonUtils.getAssetURL(suggestionsCSSPath);
return html`
<link rel="stylesheet" href="${suggestionsCSS}">
<form class="${classMap({hidden: !this.isLoggedIn() || !this.hasSignaturePermissions()})}">
<div class="field">
<label class="label">${i18n.t('pdf-upload.label')}</label>
<div class="control">
<!--
<vpu-knowledge-base-organization-select lang="${this.lang}"
value="${this.organizationId}"
@change="${this.onOrgUnitCodeChanged}"></vpu-knowledge-base-organization-select>
-->
</div>
</div>
</form>
<div class="notification is-warning ${classMap({hidden: this.isLoggedIn()})}">
${i18n.t('error-login-message')}
</div>
<div class="notification is-danger ${classMap({hidden: this.hasSignaturePermissions() || !this.isLoggedIn()})}">
${i18n.t('error-permission-message')}
</div>
`;
}
}
commonUtils.defineCustomElement('vpu-signature-pdf-upload', SignaturePdfUpload);
import {createI18nInstance} from './i18n.js';
import {css, html} from 'lit-element';
import VPUSignatureLitElement from "./vpu-signature-lit-element";
import * as commonUtils from 'vpu-common/utils';
import * as commonStyles from 'vpu-common/styles';
import 'vpu-person-profile';
const i18n = createI18nInstance();
class SignatureProfile extends VPUSignatureLitElement {
constructor() {
super();
this.lang = i18n.language;
this._personId = window.VPUPersonId;
this.entryPointUrl = commonUtils.getAPiUrl();
}
static get properties() {
return {
lang: { type: String },
entryPointUrl: { type: String, attribute: 'entry-point-url' },
_personId: {type: String, attribute: false},
};
}
connectedCallback() {
super.connectedCallback();
window.addEventListener("vpu-auth-person-init", () => {
this._personId = window.VPUPersonId;
});
}
static get styles() {
// language=css
return css`
${commonStyles.getThemeCSS()}
${commonStyles.getGeneralCSS()}
`;
}
render() {
return html`
<vpu-person-profile value="${this._personId}" entry-point-url="${this.entryPointUrl}"" lang="${this.lang}"></vpu-person-profile>
`;
}
}
commonUtils.defineCustomElement('vpu-signature-profile', SignatureProfile);
import './app';
console.log("# Example book barcodes\n\n" +
"## F1490\n+F20313804, +F24048209, +F24084706\n\n" +
"## F2050\n+F21910103, +F36418703, +F34286303, +F47148902, +F2750200X, +F27006305, +F8160107\n\n" +
"## F2190\n+F5439740X, +F47139202, +F26813604, +F43692000, +F48530609, +F24978201, +F9301604");
\ No newline at end of file
import {assert} from 'chai';
import '../src/vpu-signature-pdf-upload';
import '../src/vpu-signature.js';
import {Router} from '../src/app/router.js';
suite('vpu-signature-pdf-upload basics', () => {
let node;
suiteSetup(async () => {
node = document.createElement('vpu-signature-pdf-upload');
document.body.appendChild(node);
await node.updateComplete;
});
suiteTeardown(() => {
node.remove();
});
test('should render', () => {
assert(node.shadowRoot !== undefined);
});
});
suite('vpu-signature-app basics', () => {
let node;
suiteSetup(async () => {
node = document.createElement('vpu-app');
document.body.appendChild(node);
await node.updateComplete;
});
suiteTeardown(() => {
node.remove();
});
test('should render', () => {
assert(node.shadowRoot !== undefined);
});
});
suite('router', () => {
test('basics', () => {
const routes = [
{
name: 'foo',
path: '',
action: (context) => {
return {};
}
},
];
const router = new Router(routes, {
routeName: 'foo',
getState: () => { return {}; },
setState: (state) => { },
});
router.setStateFromCurrentLocation();
router.update();
router.updateFromPathname("/");
assert.equal(router.getPathname(), '/');
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment