Merge branch 'master' into 476-interface-fur-klausurkorrekturen
This commit is contained in:
commit
f619b0a36c
@ -1,4 +0,0 @@
|
||||
((haskell-mode . ((haskell-indent-spaces . 4)
|
||||
(haskell-process-use-ghci . t)))
|
||||
(hamlet-mode . ((hamlet/basic-offset . 4)
|
||||
(haskell-process-use-ghci . t))))
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,7 +1,4 @@
|
||||
dist*
|
||||
static/bundles/
|
||||
static/tmp/
|
||||
static/combined/
|
||||
node_modules/
|
||||
*.hi
|
||||
*.o
|
||||
@ -36,3 +33,6 @@ tags
|
||||
test.log
|
||||
*.dump-splices
|
||||
/.stack-work.lock
|
||||
/.npmrc
|
||||
/config/webpack.yml
|
||||
static/wp-*/
|
||||
|
||||
248
.gitlab-ci.yml
Normal file
248
.gitlab-ci.yml
Normal file
@ -0,0 +1,248 @@
|
||||
default:
|
||||
image:
|
||||
name: fpco/stack-build:lts-13.21
|
||||
cache:
|
||||
paths:
|
||||
- node_modules
|
||||
- .stack
|
||||
- .stack-work
|
||||
|
||||
variables:
|
||||
STACK_ROOT: "${CI_PROJECT_DIR}/.stack"
|
||||
CHROME_BIN: "/usr/bin/chromium-browser"
|
||||
POSTGRES_DB: uniworx_test
|
||||
POSTGRES_USER: uniworx
|
||||
POSTGRES_PASSWORD: uniworx
|
||||
|
||||
stages:
|
||||
- setup
|
||||
- frontend:build
|
||||
- yesod:build
|
||||
- lint
|
||||
- test
|
||||
- deploy
|
||||
|
||||
npm install:
|
||||
stage: setup
|
||||
script:
|
||||
- ./.npmrc.gup
|
||||
- npm install
|
||||
before_script: &npm
|
||||
- apt-get update -y
|
||||
- npm install -g n
|
||||
- n stable
|
||||
- npm install -g npm
|
||||
- hash -r
|
||||
- apt-get install openssh-client -y
|
||||
- install -v -m 0700 -d ~/.ssh
|
||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules/
|
||||
name: "${CI_JOB_NAME}"
|
||||
expire_in: "1 day"
|
||||
retry: 2
|
||||
|
||||
frontend:build:
|
||||
stage: frontend:build
|
||||
script:
|
||||
- npm run frontend:build
|
||||
before_script: *npm
|
||||
needs:
|
||||
- npm install
|
||||
artifacts:
|
||||
paths:
|
||||
- static
|
||||
- config/webpack.yml
|
||||
name: "${CI_JOB_NAME}"
|
||||
expire_in: "1 day"
|
||||
dependencies:
|
||||
- npm install
|
||||
retry: 2
|
||||
|
||||
frontend:lint:
|
||||
stage: lint
|
||||
script:
|
||||
- npm run frontend:lint
|
||||
before_script: *npm
|
||||
needs:
|
||||
- npm install
|
||||
dependencies:
|
||||
- npm install
|
||||
retry: 2
|
||||
|
||||
yesod:build:dev:
|
||||
stage: yesod:build
|
||||
script:
|
||||
- stack build --copy-bins --local-bin-path $(pwd)/bin --fast --flag uniworx:-library-only --flag uniworx:dev --flag uniworx:pedantic
|
||||
needs:
|
||||
- frontend:build
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends locales-all
|
||||
- ln -s $(which g++-7) $(dirname $(which g++-7))/g++
|
||||
- apt-get install openssh-client -y
|
||||
- install -v -m 0700 -d ~/.ssh
|
||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
||||
artifacts:
|
||||
paths:
|
||||
- bin/
|
||||
name: "${CI_JOB_NAME}"
|
||||
expire_in: "1 week"
|
||||
dependencies:
|
||||
- frontend:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME !~ /^v[0-9].*/
|
||||
retry: 2
|
||||
|
||||
yesod:build:
|
||||
stage: yesod:build
|
||||
script:
|
||||
- stack build --copy-bins --local-bin-path $(pwd)/bin --flag uniworx:-library-only --flag uniworx:-dev --flag uniworx:pedantic
|
||||
needs:
|
||||
- frontend:build
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends locales-all
|
||||
- ln -s $(which g++-7) $(dirname $(which g++-7))/g++
|
||||
- apt-get install -y --no-install-recommends openssh-client
|
||||
- install -v -m 0700 -d ~/.ssh
|
||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
||||
artifacts:
|
||||
paths:
|
||||
- bin/
|
||||
name: "${CI_JOB_NAME}"
|
||||
dependencies:
|
||||
- frontend:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME =~ /^v[0-9].*/
|
||||
retry: 2
|
||||
|
||||
frontend:test:
|
||||
stage: test
|
||||
script:
|
||||
- npm run frontend:test
|
||||
needs:
|
||||
- npm install
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- npm install -g n
|
||||
- n stable
|
||||
- npm install -g npm
|
||||
- hash -r
|
||||
- apt-get install -y --no-install-recommends chromium-browser
|
||||
dependencies:
|
||||
- npm install
|
||||
retry: 2
|
||||
|
||||
hlint:dev:
|
||||
stage: lint
|
||||
script:
|
||||
- stack test --fast --flag uniworx:-library-only --flag uniworx:dev --flag uniworx:pedantic uniworx:test:hlint
|
||||
needs:
|
||||
- frontend:build
|
||||
- yesod:build:dev # For caching
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends locales-all
|
||||
- ln -s $(which g++-7) $(dirname $(which g++-7))/g++
|
||||
dependencies:
|
||||
- frontend:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME !~ /^v[0-9].*/
|
||||
retry: 2
|
||||
|
||||
yesod:test:dev:
|
||||
services:
|
||||
- name: postgres:10.10
|
||||
alias: postgres
|
||||
|
||||
stage: test
|
||||
script:
|
||||
- stack test --coverage --fast --flag uniworx:-library-only --flag uniworx:dev --flag uniworx:pedantic --skip hlint
|
||||
needs:
|
||||
- frontend:build
|
||||
- yesod:build:dev # For caching
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends locales-all
|
||||
- ln -s $(which g++-7) $(dirname $(which g++-7))/g++
|
||||
dependencies:
|
||||
- frontend:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME !~ /^v[0-9].*/
|
||||
retry: 2
|
||||
|
||||
hlint:
|
||||
stage: lint
|
||||
script:
|
||||
- stack test --flag uniworx:-library-only --flag uniworx:-dev --flag uniworx:pedantic uniworx:test:hlint
|
||||
needs:
|
||||
- frontend:build
|
||||
- yesod:build # For caching
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends locales-all
|
||||
- ln -s $(which g++-7) $(dirname $(which g++-7))/g++
|
||||
dependencies:
|
||||
- frontend:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME =~ /^v[0-9].*/
|
||||
retry: 2
|
||||
|
||||
yesod:test:
|
||||
services:
|
||||
- name: postgres:10.10
|
||||
alias: postgres
|
||||
|
||||
stage: test
|
||||
script:
|
||||
- stack test --coverage --flag uniworx:-library-only --flag uniworx:-dev --flag uniworx:pedantic --skip hlint
|
||||
needs:
|
||||
- frontend:build
|
||||
- yesod:build # For caching
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends locales-all
|
||||
- ln -s $(which g++-7) $(dirname $(which g++-7))/g++
|
||||
dependencies:
|
||||
- frontend:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME =~ /^v[0-9].*/
|
||||
retry: 2
|
||||
|
||||
deploy:uniworx3:
|
||||
stage: deploy
|
||||
script:
|
||||
- ssh -i ~/.ssh/id root@uniworx3.ifi.lmu.de <bin/uniworx
|
||||
needs:
|
||||
- yesod:build
|
||||
- yesod:test # For sanity
|
||||
- hlint # For sanity
|
||||
before_script:
|
||||
- apt-get update -y
|
||||
- apt-get install -y --no-install-recommends openssh-client
|
||||
- install -v -m 0700 -d ~/.ssh
|
||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||
- install -v -T -m 0400 ${SSH_PRIVATE_KEY_UNIWORX3} ~/.ssh/uniworx3; echo "IdentityFile ~/.ssh/uniworx3" >> ~/.ssh/config;
|
||||
dependencies:
|
||||
- yesod:build
|
||||
|
||||
only:
|
||||
variables:
|
||||
- $CI_COMMIT_REF_NAME =~ /^v[0-9].*/
|
||||
12
.npmrc.gup
Executable file
12
.npmrc.gup
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
if command -V gup 1>&- 2>&-; then
|
||||
gup --always
|
||||
fi
|
||||
|
||||
cat >${1:-.npmrc} <<EOF
|
||||
@fortawesome:registry=https://npm.fontawesome.com/
|
||||
//npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
|
||||
EOF
|
||||
1471
CHANGELOG.md
1471
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
64
clean.sh
64
clean.sh
@ -4,39 +4,39 @@ set -e
|
||||
|
||||
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || :
|
||||
|
||||
case $1 in
|
||||
"")
|
||||
exec -- stack clean
|
||||
;;
|
||||
*)
|
||||
target=".stack-work-${1}"
|
||||
shift
|
||||
if [[ -n "${1}" ]]; then
|
||||
target=".stack-work-${1}"
|
||||
else
|
||||
target=".stack-work"
|
||||
fi
|
||||
shift
|
||||
|
||||
if [[ ! -d "${target}" ]]; then
|
||||
printf "%s does not exist or is no directory\n" "${target}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -e .stack-work-clean ]]; then
|
||||
printf ".stack-work-clean exists\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -d "${target}" ]]; then
|
||||
printf "%s does not exist or is no directory\n" "${target}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
move-back() {
|
||||
if [[ -d .stack-work ]]; then
|
||||
mv -v .stack-work "${target}"
|
||||
else
|
||||
mkdir -v "${target}"
|
||||
fi
|
||||
[[ -d .stack-work-clean ]] && mv -v .stack-work-clean .stack-work
|
||||
}
|
||||
if [[ "${target}" != ".stack-work" ]]; then
|
||||
if [[ -e .stack-work-clean ]]; then
|
||||
printf ".stack-work-clean exists\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv -v .stack-work .stack-work-clean
|
||||
mv -v "${target}" .stack-work
|
||||
trap move-back EXIT
|
||||
move-back() {
|
||||
if [[ -d .stack-work ]]; then
|
||||
mv -v .stack-work "${target}"
|
||||
else
|
||||
mkdir -v "${target}"
|
||||
fi
|
||||
[[ -d .stack-work-clean ]] && mv -v .stack-work-clean .stack-work
|
||||
}
|
||||
|
||||
(
|
||||
set -ex
|
||||
stack clean $@
|
||||
)
|
||||
;;
|
||||
esac
|
||||
mv -v .stack-work .stack-work-clean
|
||||
mv -v "${target}" .stack-work
|
||||
trap move-back EXIT
|
||||
fi
|
||||
|
||||
(
|
||||
set -ex
|
||||
stack clean $@
|
||||
)
|
||||
|
||||
@ -706,6 +706,8 @@ model/x3d+xml x3dz x3d
|
||||
text/cache-manifest manifest appcache
|
||||
text/calendar ifb ics
|
||||
text/css less css
|
||||
text/x-sass sass
|
||||
text/x-scss scss
|
||||
text/csv csv
|
||||
text/html shtml html htm
|
||||
text/mathml mml
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
||||
|
||||
static-dir: "_env:STATIC_DIR:static"
|
||||
webpack-manifest: "_env:WEBPACK_MANIFEST:config/webpack.yml"
|
||||
host: "_env:HOST:*4" # any IPv4 host
|
||||
port: "_env:PORT:3000"
|
||||
ip-from-header: "_env:IP_FROM_HEADER:false"
|
||||
@ -30,6 +31,8 @@ session-timeout: 7200
|
||||
jwt-expiration: 604800
|
||||
jwt-encoding: HS256
|
||||
maximum-content-length: "_env:MAX_UPLOAD_SIZE:134217728"
|
||||
session-files-expire: 3600
|
||||
prune-unreferenced-files: 86400
|
||||
health-check-interval:
|
||||
matching-cluster-config: "_env:HEALTHCHECK_INTERVAL_MATCHING_CLUSTER_CONFIG:600"
|
||||
http-reachable: "_env:HEALTHCHECK_INTERVAL_HTTP_REACHABLE:600"
|
||||
@ -130,6 +133,7 @@ user-defaults:
|
||||
time-format: "%R"
|
||||
download-files: false
|
||||
warning-days: 1209600
|
||||
show-sex: false
|
||||
|
||||
# During central allocations lecturer-given ratings of applications (as
|
||||
# ExamGrades) are combined with a central priority.
|
||||
|
||||
9
frontend/src/_common.sass
Normal file
9
frontend/src/_common.sass
Normal file
@ -0,0 +1,9 @@
|
||||
@use "~@fortawesome/fontawesome-pro/scss/fontawesome" with ( $fa-font-path: "~@fortawesome/fontawesome-pro/webfonts" )
|
||||
|
||||
@forward "~@fortawesome/fontawesome-pro/scss/fontawesome"
|
||||
|
||||
@use "~@fortawesome/fontawesome-pro/scss/solid"
|
||||
|
||||
@use "~typeface-roboto" as roboto
|
||||
|
||||
@use "~typeface-source-sans-pro" as source-sans-pro
|
||||
@ -4,8 +4,7 @@ import { I18n } from './services/i18n/i18n';
|
||||
import { UtilRegistry } from './services/util-registry/util-registry';
|
||||
import { isValidUtility } from './core/utility';
|
||||
|
||||
// load window.fetch polyfill
|
||||
import 'whatwg-fetch';
|
||||
import './app.sass';
|
||||
|
||||
export class App {
|
||||
httpClient = new HttpClient();
|
||||
|
||||
1200
frontend/src/app.sass
Normal file
1200
frontend/src/app.sass
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
||||
/* eslint-env node */
|
||||
|
||||
import { App } from './app';
|
||||
import { Utility } from './core/utility';
|
||||
|
||||
@ -13,52 +15,50 @@ const TEST_UTILS = [
|
||||
];
|
||||
|
||||
describe('App', () => {
|
||||
let app;
|
||||
|
||||
beforeEach(() => {
|
||||
app = new App();
|
||||
global.App = new App();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(app).toBeTruthy();
|
||||
expect(global.App).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setup all utlites when page is done loading', () => {
|
||||
spyOn(app.utilRegistry, 'setupAll');
|
||||
spyOn(global.App.utilRegistry, 'setupAll');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
expect(app.utilRegistry.setupAll).toHaveBeenCalled();
|
||||
expect(global.App.utilRegistry.setupAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('provides services', () => {
|
||||
it('HttpClient as httpClient', () => {
|
||||
expect(app.httpClient).toBeTruthy();
|
||||
expect(global.App.httpClient).toBeTruthy();
|
||||
});
|
||||
|
||||
it('HtmlHelpers as htmlHelpers', () => {
|
||||
expect(app.htmlHelpers).toBeTruthy();
|
||||
expect(global.App.htmlHelpers).toBeTruthy();
|
||||
});
|
||||
|
||||
it('I18n as i18n', () => {
|
||||
expect(app.i18n).toBeTruthy();
|
||||
expect(global.App.i18n).toBeTruthy();
|
||||
});
|
||||
|
||||
it('UtilRegistry as utilRegistry', () => {
|
||||
expect(app.utilRegistry).toBeTruthy();
|
||||
expect(global.App.utilRegistry).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerUtilities()', () => {
|
||||
it('should register the given utilities', () => {
|
||||
spyOn(app.utilRegistry, 'register');
|
||||
app.registerUtilities(TEST_UTILS);
|
||||
expect(app.utilRegistry.register.calls.count()).toBe(TEST_UTILS.length);
|
||||
expect(app.utilRegistry.register.calls.argsFor(0)).toEqual([TEST_UTILS[0]]);
|
||||
expect(app.utilRegistry.register.calls.argsFor(1)).toEqual([TEST_UTILS[1]]);
|
||||
spyOn(global.App.utilRegistry, 'register');
|
||||
global.App.registerUtilities(TEST_UTILS);
|
||||
expect(global.App.utilRegistry.register.calls.count()).toBe(TEST_UTILS.length);
|
||||
expect(global.App.utilRegistry.register.calls.argsFor(0)).toEqual([TEST_UTILS[0]]);
|
||||
expect(global.App.utilRegistry.register.calls.argsFor(1)).toEqual([TEST_UTILS[1]]);
|
||||
});
|
||||
|
||||
it('should throw an error if not passed an array of utilities', () => {
|
||||
expect(() => {
|
||||
app.registerUtilities({});
|
||||
global.App.registerUtilities({});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
223
frontend/src/lib/storage-manager/storage-manager.js
Normal file
223
frontend/src/lib/storage-manager/storage-manager.js
Normal file
@ -0,0 +1,223 @@
|
||||
/* global global:writable */
|
||||
|
||||
import * as semver from 'semver';
|
||||
|
||||
export const LOCATION = {
|
||||
LOCAL: 'local',
|
||||
WINDOW: 'window',
|
||||
};
|
||||
|
||||
const LOCATION_SHADOWING = [ LOCATION.WINDOW, LOCATION.LOCAL ];
|
||||
|
||||
export class StorageManager {
|
||||
|
||||
namespace;
|
||||
version;
|
||||
_options;
|
||||
_global;
|
||||
|
||||
constructor(namespace, version, options) {
|
||||
this.namespace = namespace;
|
||||
this.version = semver.valid(version);
|
||||
|
||||
if (!namespace) {
|
||||
throw new Error('Cannot setup StorageManager without namespace');
|
||||
}
|
||||
if (!this.version) {
|
||||
throw new Error('Cannot setup StorageManager without valid semver version');
|
||||
}
|
||||
|
||||
if (options !== undefined) {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
if (global !== undefined)
|
||||
this._global = global;
|
||||
else if (window !== undefined)
|
||||
this._global = window;
|
||||
else
|
||||
throw new Error('Cannot setup StorageManager without window or global');
|
||||
}
|
||||
|
||||
save(key, value, options=this._options) {
|
||||
if (!key) {
|
||||
throw new Error('StorageManager.save called with invalid key');
|
||||
}
|
||||
|
||||
if (options && options.location !== undefined && !Object.values(LOCATION).includes(options.location)) {
|
||||
throw new Error('StorageManager.save called with unsupported location option');
|
||||
}
|
||||
|
||||
const location = options && options.location !== undefined ? options.location : LOCATION_SHADOWING[0];
|
||||
|
||||
switch (location) {
|
||||
case LOCATION.LOCAL: {
|
||||
this._saveToLocalStorage({ ...this._getFromLocalStorage(), [key]: value });
|
||||
break;
|
||||
}
|
||||
case LOCATION.WINDOW: {
|
||||
this._saveToWindow({ ...this._getFromLocalStorage(), [key]: value });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error('StorageManager.save cannot save item with unsupported location');
|
||||
}
|
||||
}
|
||||
|
||||
load(key, options=this._options) {
|
||||
if (options && options.location !== undefined && !Object.values(LOCATION).includes(options.location)) {
|
||||
throw new Error('StorageManager.load called with unsupported location option');
|
||||
}
|
||||
|
||||
let locations = options && options.location !== undefined ? [options.location] : LOCATION_SHADOWING;
|
||||
|
||||
while (locations.length > 0) {
|
||||
const location = locations.shift();
|
||||
let val;
|
||||
|
||||
switch (location) {
|
||||
case LOCATION.LOCAL: {
|
||||
val = this._getFromLocalStorage()[key];
|
||||
break;
|
||||
}
|
||||
case LOCATION.WINDOW: {
|
||||
val = this._getFromWindow()[key];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error('StorageManager.load cannot load item with unsupported location');
|
||||
}
|
||||
|
||||
if (val !== undefined || locations.length === 0) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(key, options=this._options) {
|
||||
if (options && options.location !== undefined && !Object.values(LOCATION).includes(options.location)) {
|
||||
throw new Error('StorageManager.load called with unsupported location option');
|
||||
}
|
||||
|
||||
const locations = options && options.location !== undefined ? [options.location] : LOCATION_SHADOWING;
|
||||
|
||||
for (const location of locations) {
|
||||
switch (location) {
|
||||
case LOCATION.LOCAL: {
|
||||
let val = this._getFromLocalStorage();
|
||||
|
||||
delete val[key];
|
||||
|
||||
return this._saveToLocalStorage(val);
|
||||
}
|
||||
case LOCATION.WINDOW: {
|
||||
let val = this._getFromWindow();
|
||||
|
||||
delete val[key];
|
||||
|
||||
return this._saveToWindow(val);
|
||||
}
|
||||
default:
|
||||
console.error('StorageManager.load cannot load item with unsupported location');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(options) {
|
||||
if (options && options.location !== undefined && !Object.values(LOCATION).includes(options.location)) {
|
||||
throw new Error('StorageManager.clear called with unsupported location option');
|
||||
}
|
||||
|
||||
const locations = options && options.location !== undefined ? [options.location] : LOCATION_SHADOWING;
|
||||
|
||||
for (const location of locations) {
|
||||
switch (location) {
|
||||
case LOCATION.LOCAL:
|
||||
return this._clearLocalStorage();
|
||||
case LOCATION.WINDOW:
|
||||
return this._clearWindow();
|
||||
default:
|
||||
console.error('StorageManager.clear cannot clear with unsupported location');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_getFromLocalStorage() {
|
||||
let state;
|
||||
|
||||
try {
|
||||
state = JSON.parse(window.localStorage.getItem(this.namespace));
|
||||
} catch {
|
||||
state = null;
|
||||
}
|
||||
|
||||
if (state === null || !state.version || !semver.satisfies(this.version, `^${state.version}`)) {
|
||||
// remove item from localStorage if it stores an invalid state
|
||||
this._clearLocalStorage();
|
||||
return {};
|
||||
}
|
||||
|
||||
if ('state' in state)
|
||||
return state.state;
|
||||
else {
|
||||
delete state.version;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
_saveToLocalStorage(state) {
|
||||
if (!state)
|
||||
return this._clearLocalStorage();
|
||||
|
||||
let versionedState;
|
||||
|
||||
if ('version' in state || 'state' in state) {
|
||||
versionedState = { version: this.version, state: state };
|
||||
} else {
|
||||
versionedState = { version: this.version, ...state };
|
||||
}
|
||||
|
||||
window.localStorage.setItem(this.namespace, JSON.stringify(versionedState));
|
||||
}
|
||||
|
||||
_clearLocalStorage() {
|
||||
window.localStorage.removeItem(this.namespace);
|
||||
}
|
||||
|
||||
|
||||
_getFromWindow() {
|
||||
if (!this._global || !this._global.App)
|
||||
return {};
|
||||
|
||||
if (!this._global.App.Storage)
|
||||
this._global.App.Storage = {};
|
||||
|
||||
return this._global.App.Storage[this.namespace] || {};
|
||||
}
|
||||
|
||||
_saveToWindow(value) {
|
||||
if (!this._global || !this._global.App) {
|
||||
throw new Error('StorageManager._saveToWindow called when window.App is not available');
|
||||
}
|
||||
|
||||
if (!value)
|
||||
return this._clearWindow();
|
||||
|
||||
if (!this._global.App.Storage)
|
||||
this._global.App.Storage = {};
|
||||
|
||||
this._global.App.Storage[this.namespace] = value;
|
||||
}
|
||||
|
||||
_clearWindow() {
|
||||
if (!this._global || !this._global.App) {
|
||||
throw new Error('StorageManager._saveToWindow called when window.App is not available');
|
||||
}
|
||||
|
||||
if (this._global.App.Storage) {
|
||||
delete this._global.App.Storage[this.namespace];
|
||||
}
|
||||
}
|
||||
}
|
||||
4
frontend/src/polyfill.js
Normal file
4
frontend/src/polyfill.js
Normal file
@ -0,0 +1,4 @@
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import { ResizeObserver as Polyfill } from '@juggle/resize-observer';
|
||||
window.ResizeObserver = window.ResizeObserver || Polyfill;
|
||||
@ -1,3 +1,7 @@
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/en-ie.js';
|
||||
import 'moment/locale/de.js';
|
||||
|
||||
/**
|
||||
* I18n
|
||||
*
|
||||
@ -13,10 +17,15 @@
|
||||
|
||||
export class I18n {
|
||||
|
||||
translations = {};
|
||||
_translations = {};
|
||||
_datetimeLocale = undefined;
|
||||
|
||||
add(id, translation) {
|
||||
this.translations[id] = translation;
|
||||
if (!this._translations[id]) {
|
||||
this._translations[id] = translation;
|
||||
} else {
|
||||
throw new Error('I18N Error: Attempting to set translation multiple times for »' + id + '«!');
|
||||
}
|
||||
}
|
||||
|
||||
addMany(manyTranslations) {
|
||||
@ -24,9 +33,27 @@ export class I18n {
|
||||
}
|
||||
|
||||
get(id) {
|
||||
if (!this.translations[id]) {
|
||||
if (!this._translations[id]) {
|
||||
throw new Error('I18N Error: Translation missing for »' + id + '«!');
|
||||
}
|
||||
return this.translations[id];
|
||||
return this._translations[id];
|
||||
}
|
||||
|
||||
|
||||
setDatetimeLocale(locale) {
|
||||
if (!this._datetimeLocale) {
|
||||
moment.locale(locale);
|
||||
this._datetimeLocale = locale;
|
||||
} else {
|
||||
throw new Error('I18N Error: Attempting to set datetime locale multiple times!');
|
||||
}
|
||||
}
|
||||
|
||||
getDatetimeLocale() {
|
||||
if (!this._datetimeLocale) {
|
||||
throw new Error('I18N Error: Attempting to access datetime locale when it has not been set!');
|
||||
}
|
||||
|
||||
return this._datetimeLocale;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ describe('I18n', () => {
|
||||
|
||||
// helper function
|
||||
function expectTranslation(id, value) {
|
||||
expect(i18n.translations[id]).toMatch(value);
|
||||
expect(i18n.get(id)).toMatch(value);
|
||||
}
|
||||
|
||||
it('should create', () => {
|
||||
@ -38,7 +38,7 @@ describe('I18n', () => {
|
||||
|
||||
describe('get()', () => {
|
||||
it('should return stored translations', () => {
|
||||
i18n.translations.id1 = 'something';
|
||||
i18n.add('id1', 'something');
|
||||
expect(i18n.get('id1')).toMatch('something');
|
||||
});
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './alerts.scss';
|
||||
import './alerts.sass';
|
||||
|
||||
const ALERTS_INITIALIZED_CLASS = 'alerts--initialized';
|
||||
const ALERTS_ELEVATED_CLASS = 'alerts--elevated';
|
||||
|
||||
186
frontend/src/utils/alerts/alerts.sass
Normal file
186
frontend/src/utils/alerts/alerts.sass
Normal file
@ -0,0 +1,186 @@
|
||||
@use "../../common" as *
|
||||
|
||||
.alerts
|
||||
position: fixed
|
||||
bottom: 0
|
||||
right: 5%
|
||||
z-index: 20
|
||||
text-align: right
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.alerts__toggler
|
||||
width: 40px
|
||||
height: 40px
|
||||
position: absolute
|
||||
top: 400px
|
||||
left: 50%
|
||||
transform: translateX(-50%)
|
||||
cursor: pointer
|
||||
|
||||
&::before
|
||||
@extend .fas
|
||||
|
||||
content: fa-content($fa-var-chevron-up)
|
||||
position: absolute
|
||||
left: 50%
|
||||
top: 0
|
||||
height: 30px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
width: 30px
|
||||
color: var(--color-grey)
|
||||
font-size: 30px
|
||||
transform: translateX(-50%)
|
||||
|
||||
&:hover::before
|
||||
color: var(--color-grey-medium)
|
||||
|
||||
.alerts--elevated
|
||||
z-index: 1000
|
||||
|
||||
.alerts__toggler--visible
|
||||
top: -40px
|
||||
opacity: 1
|
||||
transition: top 0.5s cubic-bezier(0.73, 1.25, 0.61, 1), opacity 0.5s cubic-bezier(0.73, 1.25, 0.61, 1)
|
||||
|
||||
@media (max-width: 425px)
|
||||
.alerts
|
||||
left: 5%
|
||||
|
||||
.alert
|
||||
position: relative
|
||||
display: block
|
||||
background-color: var(--color-lightblack)
|
||||
font-size: 1rem
|
||||
color: var(--color-lightwhite)
|
||||
z-index: 0
|
||||
padding: 0 50px
|
||||
padding-right: 60px
|
||||
animation: slide-in-alert .2s ease-out forwards
|
||||
margin-bottom: 10px
|
||||
transition: margin-bottom .2s ease-out
|
||||
|
||||
.alert a
|
||||
color: var(--color-lightwhite)
|
||||
|
||||
&:hover
|
||||
color: var(--color-grey)
|
||||
|
||||
@keyframes slide-in-alert
|
||||
from
|
||||
transform: translateY(120%)
|
||||
|
||||
to
|
||||
transform: translateY(0)
|
||||
|
||||
@keyframes slide-out-alert
|
||||
from
|
||||
transform: translateY(0)
|
||||
max-height: 200px
|
||||
|
||||
to
|
||||
transform: translateY(250%)
|
||||
opacity: 0
|
||||
max-height: 0
|
||||
overflow: hidden
|
||||
|
||||
@media (min-width: 425px)
|
||||
.alert
|
||||
max-width: 400px
|
||||
|
||||
.alert--invisible
|
||||
animation: slide-out-alert .2s ease-out forwards
|
||||
margin-bottom: 0
|
||||
|
||||
.alert__content
|
||||
padding: 8px 0
|
||||
min-height: 40px
|
||||
position: relative
|
||||
display: flex
|
||||
font-weight: 600
|
||||
align-items: center
|
||||
text-align: left
|
||||
|
||||
.alert__icon
|
||||
text-align: right
|
||||
position: absolute
|
||||
left: 0px
|
||||
top: 0
|
||||
width: 50px
|
||||
height: 100%
|
||||
z-index: 40
|
||||
|
||||
&::before
|
||||
position: absolute
|
||||
font-size: 24px
|
||||
top: 50%
|
||||
left: 50%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
transform: translate(-50%, -50%)
|
||||
border-radius: 50%
|
||||
width: 30px
|
||||
height: 30px
|
||||
|
||||
.alert__closer
|
||||
cursor: pointer
|
||||
text-align: right
|
||||
position: absolute
|
||||
right: 0px
|
||||
top: 0
|
||||
width: 60px
|
||||
height: 100%
|
||||
transition: all .3s ease
|
||||
z-index: 40
|
||||
|
||||
&:hover
|
||||
transform: scale(1.05, 1.05)
|
||||
|
||||
&::before
|
||||
box-shadow: 0 0 4px white
|
||||
background-color: rgba(255, 255, 255, 0.1)
|
||||
color: white
|
||||
|
||||
&::before
|
||||
@extend .fas
|
||||
|
||||
content: fa-content($fa-var-times)
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
transform: translate(-50%, -50%)
|
||||
border-radius: 50%
|
||||
width: 30px
|
||||
height: 30px
|
||||
transition: all .15s ease
|
||||
|
||||
@media (max-width: 768px)
|
||||
.alert__closer
|
||||
width: 40px
|
||||
|
||||
.alert-success
|
||||
background-color: var(--color-success)
|
||||
|
||||
// .alert__icon::before {
|
||||
// --alert-icon-default: '\f058';
|
||||
// }
|
||||
|
||||
.alert-warning
|
||||
background-color: var(--color-warning)
|
||||
|
||||
// .alert__icon::before {
|
||||
// --alert-icon-default: '\f06a';
|
||||
// }
|
||||
|
||||
.alert-error
|
||||
background-color: var(--color-error)
|
||||
|
||||
// .alert__icon::before {
|
||||
// --alert-icon-default: '\f071';
|
||||
// }
|
||||
@ -1,221 +0,0 @@
|
||||
.alerts {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 5%;
|
||||
z-index: 20;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerts__toggler {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
top: 400px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '\f077';
|
||||
position: absolute;
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
left: 50%;
|
||||
top: 0;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
color: var(--color-grey);
|
||||
font-size: 30px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
color: var(--color-grey-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.alerts--elevated {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.alerts__toggler--visible {
|
||||
top: -40px;
|
||||
opacity: 1;
|
||||
transition: top .5s cubic-bezier(0.73, 1.25, 0.61, 1),
|
||||
opacity .5s cubic-bezier(0.73, 1.25, 0.61, 1);
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
|
||||
.alerts {
|
||||
left: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
position: relative;
|
||||
display: block;
|
||||
background-color: var(--color-lightblack);
|
||||
font-size: 1rem;
|
||||
color: var(--color-lightwhite);
|
||||
z-index: 0;
|
||||
padding: 0 50px;
|
||||
padding-right: 60px;
|
||||
animation: slide-in-alert .2s ease-out forwards;
|
||||
margin-bottom: 10px;
|
||||
transition: margin-bottom .2s ease-out;
|
||||
}
|
||||
|
||||
.alert a {
|
||||
color: var(--color-lightwhite);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-grey);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-in-alert {
|
||||
from {
|
||||
transform: translateY(120%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-out-alert {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
max-height: 200px;
|
||||
}
|
||||
to {
|
||||
transform: translateY(250%);
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 425px) {
|
||||
|
||||
.alert {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.alert--invisible {
|
||||
animation: slide-out-alert .2s ease-out forwards;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.alert__content {
|
||||
padding: 8px 0;
|
||||
min-height: 40px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.alert__icon {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
z-index: 40;
|
||||
|
||||
&::before {
|
||||
/* content: var(--alert-icon, var(--alert-icon-default, '\f05a')); */
|
||||
position: absolute;
|
||||
/* font-family: 'Font Awesome 5 Free'; */
|
||||
font-size: 24px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.alert__closer {
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
transition: all .3s ease;
|
||||
z-index: 40;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05, 1.05);
|
||||
|
||||
&::before {
|
||||
box-shadow: 0 0 4px white;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '\f00d';
|
||||
position: absolute;
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
transition: all .15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.alert__closer {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--color-success);
|
||||
|
||||
/* .alert__icon::before {
|
||||
* --alert-icon-default: '\f058';
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--color-warning);
|
||||
|
||||
/* .alert__icon::before {
|
||||
* --alert-icon-default: '\f06a';
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: var(--color-error);
|
||||
|
||||
/* .alert__icon::before {
|
||||
* --alert-icon-default: '\f071';
|
||||
* }
|
||||
*/
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './asidenav.scss';
|
||||
import './asidenav.sass';
|
||||
|
||||
const FAVORITES_BTN_CLASS = 'navbar__list-item--favorite';
|
||||
const FAVORITES_BTN_ACTIVE_CLASS = 'navbar__list-item--active';
|
||||
|
||||
330
frontend/src/utils/asidenav/asidenav.sass
Normal file
330
frontend/src/utils/asidenav/asidenav.sass
Normal file
@ -0,0 +1,330 @@
|
||||
.main__aside
|
||||
position: fixed
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3)
|
||||
z-index: 1
|
||||
top: 0
|
||||
left: 0
|
||||
width: var(--asidenav-width-lg, 20%)
|
||||
height: 100%
|
||||
flex: 0 0 0
|
||||
flex-basis: var(--asidenav-width-lg, 20%)
|
||||
transition: all .2s ease-out
|
||||
|
||||
&::before
|
||||
position: absolute
|
||||
z-index: -1
|
||||
left: 0
|
||||
top: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
background-color: var(--color-dark)
|
||||
opacity: 0.05
|
||||
|
||||
&::after
|
||||
content: ''
|
||||
position: absolute
|
||||
z-index: -2
|
||||
left: 0
|
||||
top: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
background-color: var(--color-grey-light)
|
||||
|
||||
@media (max-width: 425px)
|
||||
.main__aside
|
||||
position: fixed
|
||||
top: var(--header-height-collapsed)
|
||||
left: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
height: 100% !important
|
||||
width: 100%
|
||||
z-index: 5
|
||||
overflow: hidden
|
||||
transform: translateX(-110%)
|
||||
transition: transform .2s ease-out
|
||||
|
||||
&.main__aside--expanded
|
||||
transform: translateX(0%)
|
||||
|
||||
.asidenav__box-subtitle
|
||||
display: inherit
|
||||
|
||||
.asidenav__box-title
|
||||
font-size: 18px
|
||||
padding-left: 10px
|
||||
|
||||
.asidenav__box-subtitle
|
||||
display: none
|
||||
|
||||
@media (min-width: 1200px)
|
||||
.main__aside
|
||||
width: var(--asidenav-width-xl, 250px)
|
||||
|
||||
.asidenav
|
||||
color: var(--color-font)
|
||||
min-height: calc(100% - var(--header-height))
|
||||
height: 400px
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
|
||||
&::-webkit-scrollbar
|
||||
width: 0
|
||||
|
||||
.asidenav__box
|
||||
transition: opacity .2s ease
|
||||
|
||||
+ .asidenav__box
|
||||
margin-top: 10px
|
||||
|
||||
.asidenav__box-title
|
||||
padding: 7px 13px
|
||||
margin-top: 30px
|
||||
background-color: transparent
|
||||
transition: all .2s ease
|
||||
padding: 10px 13px
|
||||
margin: 0
|
||||
border-bottom: 1px solid var(--color-grey)
|
||||
|
||||
.asidenav-term-identifier--long
|
||||
display: inherit
|
||||
|
||||
.asidenav-term-identifier--short
|
||||
display: none
|
||||
|
||||
.asidenav__box-subtitle
|
||||
color: var(--color-fontsec)
|
||||
font-size: 0.9rem
|
||||
font-weight: 600
|
||||
padding: 0 13px
|
||||
margin: 3px 0
|
||||
|
||||
// LOGO
|
||||
|
||||
.asidenav__logo
|
||||
height: var(--header-height)
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
@media (max-width: 768px)
|
||||
.asidenav__logo
|
||||
display: none
|
||||
|
||||
.asidenav__logo-link
|
||||
flex: 1
|
||||
top: 10px
|
||||
left: 20px
|
||||
height: 80px
|
||||
padding: 0 20px
|
||||
display: flex
|
||||
flex-basis: var(--asidenav-width-xl, 250px)
|
||||
font-size: 16px
|
||||
align-items: center
|
||||
color: var(--color-dark)
|
||||
transform-origin: left
|
||||
|
||||
&:hover
|
||||
color: var(--color-primary)
|
||||
|
||||
.asidenav__logo-lmu
|
||||
width: 80px
|
||||
height: 100%
|
||||
|
||||
.asidenav__logo-uni2work
|
||||
display: flex
|
||||
align-items: flex-end
|
||||
min-width: 70px
|
||||
margin-left: 12px
|
||||
text-transform: uppercase
|
||||
width: 100%
|
||||
height: 100%
|
||||
padding: 2px 4px
|
||||
border: 1px solid currentColor
|
||||
letter-spacing: 2px
|
||||
background-color: white
|
||||
transition: background-color .3s ease
|
||||
|
||||
@media (max-width: 1199px)
|
||||
.asidenav__logo-link
|
||||
flex-basis: var(--asidenav-width-lg, 20%)
|
||||
font-size: 16px
|
||||
|
||||
.asidenav__logo-lmu
|
||||
display: none
|
||||
|
||||
.asidenav__logo-uni2work
|
||||
margin-left: 0
|
||||
|
||||
// SEAL
|
||||
|
||||
.asidenav__sigillum
|
||||
position: absolute
|
||||
bottom: -40px
|
||||
right: 25px
|
||||
opacity: 0.1
|
||||
|
||||
> img
|
||||
width: 350px
|
||||
|
||||
@media (max-width: 768px)
|
||||
.asidenav__sigillum
|
||||
right: auto
|
||||
left: 50%
|
||||
transform: translateX(-50%)
|
||||
|
||||
// LIST-ITEM
|
||||
|
||||
.asidenav__list-item
|
||||
color: var(--color-font)
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: flex-start
|
||||
align-items: center
|
||||
|
||||
&:not(.asidenav__list-item--active):hover
|
||||
background-color: var(--color-lightwhite)
|
||||
|
||||
> .asidenav__link-wrapper
|
||||
color: var(--color-font)
|
||||
|
||||
&:hover
|
||||
.asidenav__link-shorthand
|
||||
// transform: scale(1.05, 1.0);
|
||||
// transform-origin: right;
|
||||
text-shadow: none
|
||||
|
||||
.asidenav__nested-list-wrapper
|
||||
display: block
|
||||
|
||||
// small list-item-padding for medium to large screens
|
||||
@media (min-width: 769px)
|
||||
.asidenav__list-item
|
||||
padding-left: 10px
|
||||
|
||||
.asidenav__list-item--active
|
||||
background-color: var(--color-lightwhite)
|
||||
|
||||
.asidenav__link-wrapper
|
||||
color: var(--color-link)
|
||||
|
||||
.asidenav__link-shorthand
|
||||
transform: scale(1.05, 1)
|
||||
transform-origin: right
|
||||
text-shadow: none
|
||||
|
||||
.asidenav__link-wrapper
|
||||
position: relative
|
||||
display: flex
|
||||
flex: 1
|
||||
align-items: center
|
||||
padding: 8px 3px
|
||||
justify-content: flex-start
|
||||
color: var(--color-font)
|
||||
width: 100%
|
||||
z-index: 1
|
||||
|
||||
.asidenav__link-shorthand
|
||||
display: none
|
||||
|
||||
.asidenav__link-label
|
||||
line-height: 1
|
||||
|
||||
// hover sub-menus
|
||||
.asidenav__nested-list-wrapper
|
||||
position: absolute
|
||||
z-index: 10
|
||||
display: none
|
||||
color: var(--color-font)
|
||||
background-color: var(--color-grey-light)
|
||||
box-shadow: 1px 1px 1px 0px var(--color-grey)
|
||||
|
||||
.asidenav__nested-list
|
||||
min-width: 200px
|
||||
|
||||
@media (max-width: 425px)
|
||||
.asidenav__list-item
|
||||
padding-left: 10px
|
||||
|
||||
.asidenav__nested-list
|
||||
display: none
|
||||
|
||||
.asidenav__nested-list-item
|
||||
position: relative
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-lightwhite)
|
||||
|
||||
.asidenav__link-wrapper
|
||||
padding-left: 13px
|
||||
padding-right: 13px
|
||||
transition: all .2s ease
|
||||
color: var(--color-font)
|
||||
|
||||
// TABLET
|
||||
@media (min-width: 426px) and (max-width: 768px)
|
||||
.main__aside
|
||||
width: var(--asidenav-width-md, 50px)
|
||||
flex-basis: var(--asidenav-width-md, 50px)
|
||||
overflow: hidden
|
||||
min-height: calc(100% - var(--header-height-collapsed))
|
||||
top: var(--header-height-collapsed)
|
||||
|
||||
.asidenav__box-title
|
||||
width: var(--asidenav-width-md, 50px)
|
||||
font-size: 18px
|
||||
text-align: center
|
||||
padding: 10px 1px
|
||||
word-break: break-all
|
||||
background-color: var(--color-dark)
|
||||
color: var(--color-lightwhite)
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-darker)
|
||||
|
||||
&::before
|
||||
display: none
|
||||
|
||||
.asidenav-term-identifier--long
|
||||
display: none
|
||||
|
||||
.asidenav-term-identifier--short
|
||||
display: inherit
|
||||
|
||||
.asidenav__box-subtitle
|
||||
padding: 0 3px
|
||||
font-size: 0.85rem
|
||||
|
||||
.asidenav__link-shorthand
|
||||
display: flex
|
||||
position: static
|
||||
height: 50px
|
||||
width: var(--asidenav-width-md, 50px)
|
||||
text-align: center
|
||||
opacity: 1
|
||||
font-size: 15px
|
||||
line-height: 1em
|
||||
margin-right: 13px
|
||||
flex-shrink: 0
|
||||
padding: 1px
|
||||
outline: 1px solid white
|
||||
word-break: break-all
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
.asidenav__list-item
|
||||
padding-left: 0
|
||||
|
||||
+ .asidenav__list-item
|
||||
margin: 0
|
||||
|
||||
.asidenav__link-wrapper
|
||||
color: var(--color-font)
|
||||
padding: 0
|
||||
|
||||
.asidenav__nested-list,
|
||||
.asidenav__link-label
|
||||
display: none
|
||||
|
||||
.asidenav__list-item--active
|
||||
.asidenav__link-wrapper
|
||||
background-color: var(--color-lightwhite)
|
||||
@ -1,384 +0,0 @@
|
||||
.main__aside {
|
||||
position: fixed;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--asidenav-width-lg, 20%);
|
||||
height: 100%;
|
||||
flex: 0 0 0;
|
||||
flex-basis: var(--asidenav-width-lg, 20%);
|
||||
transition: all .2s ease-out;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-dark);
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -2;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-grey-light);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
|
||||
.main__aside {
|
||||
position: fixed;
|
||||
top: var(--header-height-collapsed);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100% !important;
|
||||
width: 100%;
|
||||
z-index: 5;
|
||||
overflow: hidden;
|
||||
transform: translateX(-110%);
|
||||
transition: transform .2s ease-out;
|
||||
|
||||
&.main__aside--expanded {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
.asidenav__box-title {
|
||||
font-size: 18px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.asidenav__box-subtitle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.main__aside {
|
||||
width: var(--asidenav-width-xl, 250px)
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav {
|
||||
color: var(--color-font);
|
||||
min-height: calc(100% - var(--header-height));
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__box {
|
||||
transition: opacity .2s ease;
|
||||
|
||||
+ .asidenav__box {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__box-title {
|
||||
padding: 7px 13px;
|
||||
margin-top: 30px;
|
||||
background-color: transparent;
|
||||
transition: all .2s ease;
|
||||
padding: 10px 13px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid var(--color-grey);
|
||||
}
|
||||
|
||||
.asidenav__box-subtitle {
|
||||
color: var(--color-fontsec);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
padding: 0 13px;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
/* LOGO */
|
||||
|
||||
.asidenav__logo {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.asidenav__logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__logo-link {
|
||||
flex: 1;
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
height: 80px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
flex-basis: var(--asidenav-width-xl, 250px);
|
||||
font-size: 16px;
|
||||
align-items: center;
|
||||
color: var(--color-dark);
|
||||
transform-origin: left;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__logo-lmu {
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.asidenav__logo-uni2work {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
min-width: 70px;
|
||||
margin-left: 12px;
|
||||
text-transform: uppercase;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid currentColor;
|
||||
letter-spacing: 2px;
|
||||
background-color: white;
|
||||
transition: background-color .3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
|
||||
.asidenav__logo-link {
|
||||
flex-basis: var(--asidenav-width-lg, 20%);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.asidenav__logo-lmu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.asidenav__logo-uni2work {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* SEAL */
|
||||
|
||||
.asidenav__sigillum {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
right: 25px;
|
||||
opacity: 0.1;
|
||||
|
||||
> img {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.asidenav__sigillum {
|
||||
right: auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* LIST-ITEM */
|
||||
|
||||
.asidenav__list-item {
|
||||
color: var(--color-font);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&:not(.asidenav__list-item--active):hover {
|
||||
background-color: var(--color-lightwhite);
|
||||
|
||||
> .asidenav__link-wrapper {
|
||||
color: var(--color-font);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
.asidenav__link-shorthand {
|
||||
transform: scale(1.05, 1.0);
|
||||
transform-origin: right;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.asidenav__nested-list-wrapper {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* small list-item-padding for medium to large screens */
|
||||
@media (min-width: 769px) {
|
||||
|
||||
.asidenav__list-item {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__list-item--active {
|
||||
background-color: var(--color-lightwhite);
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.asidenav__link-shorthand {
|
||||
transform: scale(1.05, 1.0);
|
||||
transform-origin: right;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
padding: 8px 3px;
|
||||
justify-content: flex-start;
|
||||
color: var(--color-font);
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.asidenav__link-shorthand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.asidenav__link-label {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* hover sub-menus */
|
||||
.asidenav__nested-list-wrapper {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
display: none;
|
||||
color: var(--color-font);
|
||||
background-color: var(--color-grey-light);
|
||||
box-shadow: 1px 1px 1px 0px var(--color-grey);
|
||||
}
|
||||
|
||||
.asidenav__nested-list {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
|
||||
.asidenav__nested-list {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__nested-list-item {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-lightwhite);
|
||||
}
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
padding-left: 13px;
|
||||
padding-right: 13px;
|
||||
transition: all .2s ease;
|
||||
color: var(--color-font);
|
||||
}
|
||||
}
|
||||
|
||||
/* TABLET */
|
||||
@media (min-width: 426px) and (max-width: 768px) {
|
||||
|
||||
.main__aside {
|
||||
width: var(--asidenav-width-md, 50px);
|
||||
flex-basis: var(--asidenav-width-md, 50px);
|
||||
overflow: hidden;
|
||||
min-height: calc(100% - var(--header-height-collapsed));
|
||||
top: var(--header-height-collapsed);
|
||||
|
||||
.asidenav__box-title {
|
||||
width: var(--asidenav-width-md, 50px);
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
padding: 10px 1px;
|
||||
word-break: break-all;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-lightwhite);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-darker);
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__box-subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.asidenav__link-shorthand {
|
||||
display: flex;
|
||||
position: static;
|
||||
height: 50px;
|
||||
width: var(--asidenav-width-md, 50px);
|
||||
text-align: center;
|
||||
opacity: 1;
|
||||
font-size: 15px;
|
||||
line-height: 1em;
|
||||
margin-right: 13px;
|
||||
flex-shrink: 0;
|
||||
padding: 1px;
|
||||
outline: 1px solid white;
|
||||
word-break: break-all;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.asidenav__list-item {
|
||||
padding-left: 0;
|
||||
|
||||
+ .asidenav__list-item {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
color: var(--color-font);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.asidenav__nested-list,
|
||||
.asidenav__link-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.asidenav__list-item--active {
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
background-color: var(--color-lightwhite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { Datepicker } from '../form/datepicker';
|
||||
import './async-form.scss';
|
||||
import './async-form.sass';
|
||||
|
||||
const ASYNC_FORM_INITIALIZED_CLASS = 'check-all--initialized';
|
||||
const ASYNC_FORM_RESPONSE_CLASS = 'async-form__response';
|
||||
|
||||
71
frontend/src/utils/async-form/async-form.sass
Normal file
71
frontend/src/utils/async-form/async-form.sass
Normal file
@ -0,0 +1,71 @@
|
||||
.async-form__response
|
||||
margin: 20px 0
|
||||
position: relative
|
||||
width: 100%
|
||||
font-size: 18px
|
||||
text-align: center
|
||||
padding-top: 60px
|
||||
|
||||
.async-form__response::before,
|
||||
.async-form__response::after
|
||||
position: absolute
|
||||
top: 0px
|
||||
left: 50%
|
||||
display: block
|
||||
|
||||
.async-form__response--success::before
|
||||
content: ''
|
||||
width: 17px
|
||||
height: 28px
|
||||
border: solid #069e04
|
||||
border-width: 0 5px 5px 0
|
||||
transform: translateX(-50%) rotate(45deg)
|
||||
|
||||
.async-form__response--info::before
|
||||
content: ''
|
||||
width: 5px
|
||||
height: 30px
|
||||
top: 10px
|
||||
background-color: #777
|
||||
transform: translateX(-50%)
|
||||
|
||||
.async-form__response--info::after
|
||||
content: ''
|
||||
width: 5px
|
||||
height: 5px
|
||||
background-color: #777
|
||||
transform: translateX(-50%)
|
||||
|
||||
.async-form__response--warning::before
|
||||
content: ''
|
||||
width: 5px
|
||||
height: 30px
|
||||
background-color: rgb(255, 187, 0)
|
||||
transform: translateX(-50%)
|
||||
|
||||
.async-form__response--warning::after
|
||||
content: ''
|
||||
width: 5px
|
||||
height: 5px
|
||||
top: 35px
|
||||
background-color: rgb(255, 187, 0)
|
||||
transform: translateX(-50%)
|
||||
|
||||
.async-form__response--error::before
|
||||
content: ''
|
||||
width: 5px
|
||||
height: 40px
|
||||
background-color: #940d0d
|
||||
transform: translateX(-50%) rotate(-45deg)
|
||||
|
||||
.async-form__response--error::after
|
||||
content: ''
|
||||
width: 5px
|
||||
height: 40px
|
||||
background-color: #940d0d
|
||||
transform: translateX(-50%) rotate(45deg)
|
||||
|
||||
.async-form--loading
|
||||
opacity: 0.1
|
||||
transition: opacity 800ms ease-out
|
||||
pointer-events: none
|
||||
@ -1,78 +0,0 @@
|
||||
.async-form__response {
|
||||
margin: 20px 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.async-form__response::before,
|
||||
.async-form__response::after {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 50%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.async-form__response--success::before {
|
||||
content: '';
|
||||
width: 17px;
|
||||
height: 28px;
|
||||
border: solid #069e04;
|
||||
border-width: 0 5px 5px 0;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.async-form__response--info::before {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 30px;
|
||||
top: 10px;
|
||||
background-color: #777;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.async-form__response--info::after {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background-color: #777;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.async-form__response--warning::before {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 30px;
|
||||
background-color: rgb(255, 187, 0);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.async-form__response--warning::after {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
top: 35px;
|
||||
background-color: rgb(255, 187, 0);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.async-form__response--error::before {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 40px;
|
||||
background-color: #940d0d;
|
||||
transform: translateX(-50%) rotate(-45deg);
|
||||
}
|
||||
.async-form__response--error::after {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 40px;
|
||||
background-color: #940d0d;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.async-form--loading {
|
||||
opacity: 0.1;
|
||||
transition: opacity 800ms ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
4
frontend/src/utils/async-table/async-table-filter.sass
Normal file
4
frontend/src/utils/async-table/async-table-filter.sass
Normal file
@ -0,0 +1,4 @@
|
||||
.async-table-filter--loading
|
||||
opacity: 0.7
|
||||
pointer-events: none
|
||||
transition: opacity 400ms ease-out
|
||||
@ -1,5 +0,0 @@
|
||||
.async-table-filter--loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
transition: opacity 400ms ease-out;
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
|
||||
import { Datepicker } from '../form/datepicker';
|
||||
import { HttpClient } from '../../services/http-client/http-client';
|
||||
import * as debounce from 'lodash.debounce';
|
||||
import './async-table-filter.scss';
|
||||
import './async-table.scss';
|
||||
import './async-table-filter.sass';
|
||||
import './async-table.sass';
|
||||
|
||||
const INPUT_DEBOUNCE = 600;
|
||||
const HEADER_HEIGHT = 80;
|
||||
@ -40,6 +41,8 @@ export class AsyncTable {
|
||||
};
|
||||
_ignoreRequest = false;
|
||||
|
||||
_storageManager = new StorageManager(ASYNC_TABLE_LOCAL_STORAGE_KEY, '1.0.0', { location: LOCATION.WINDOW });
|
||||
|
||||
constructor(element, app) {
|
||||
if (!element) {
|
||||
throw new Error('Async Table utility cannot be setup without an element!');
|
||||
@ -81,10 +84,10 @@ export class AsyncTable {
|
||||
this._setupPageSizeSelect();
|
||||
this._setupTableFilter();
|
||||
|
||||
this._processLocalStorage();
|
||||
this._processStorage();
|
||||
|
||||
// clear currentTableUrl from previous requests
|
||||
setLocalStorageParameter('currentTableUrl', null);
|
||||
this._storageManager.remove('currentTableUrl');
|
||||
|
||||
// mark initialized
|
||||
this._element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||
@ -100,7 +103,7 @@ export class AsyncTable {
|
||||
|
||||
this._ths.forEach((th) => {
|
||||
th.clickHandler = (event) => {
|
||||
setLocalStorageParameter('horizPos', (this._scrollTable || {}).scrollLeft);
|
||||
this._storageManager.save('horizPos', (this._scrollTable || {}).scrollLeft);
|
||||
this._linkClickHandler(event);
|
||||
};
|
||||
th.element.addEventListener('click', th.clickHandler);
|
||||
@ -122,7 +125,7 @@ export class AsyncTable {
|
||||
left: this._scrollTable.offsetLeft || 0,
|
||||
behavior: 'smooth',
|
||||
};
|
||||
setLocalStorageParameter('scrollTo', scrollTo);
|
||||
this._storageManager.save('scrollTo', scrollTo);
|
||||
}
|
||||
this._linkClickHandler(event);
|
||||
};
|
||||
@ -225,7 +228,7 @@ export class AsyncTable {
|
||||
const prefix = findCssIdPrefix(focusedInput.id);
|
||||
const focusId = focusedInput.id.replace(prefix, '');
|
||||
callback = function(wrapper) {
|
||||
const idPrefix = getLocalStorageParameter('cssIdPrefix');
|
||||
const idPrefix = this._storageManager.load('cssIdPrefix');
|
||||
const toBeFocused = wrapper.querySelector('#' + idPrefix + focusId);
|
||||
if (toBeFocused) {
|
||||
toBeFocused.focus();
|
||||
@ -238,7 +241,7 @@ export class AsyncTable {
|
||||
}
|
||||
|
||||
_serializeTableFilterToURL(tableFilterForm) {
|
||||
const url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href);
|
||||
const url = new URL(this._storageManager.load('currentTableUrl') || window.location.href);
|
||||
|
||||
// create new FormData and format any date values
|
||||
const formData = Datepicker.unformatAll(this._massInputForm, new FormData(tableFilterForm));
|
||||
@ -254,18 +257,18 @@ export class AsyncTable {
|
||||
return url;
|
||||
}
|
||||
|
||||
_processLocalStorage() {
|
||||
const scrollTo = getLocalStorageParameter('scrollTo');
|
||||
_processStorage() {
|
||||
const scrollTo = this._storageManager.load('scrollTo');
|
||||
if (scrollTo && this._scrollTable) {
|
||||
window.scrollTo(scrollTo);
|
||||
}
|
||||
setLocalStorageParameter('scrollTo', null);
|
||||
this._storageManager.remove('scrollTo');
|
||||
|
||||
const horizPos = getLocalStorageParameter('horizPos');
|
||||
const horizPos = this._storageManager.load('horizPos');
|
||||
if (horizPos && this._scrollTable) {
|
||||
this._scrollTable.scrollLeft = horizPos;
|
||||
}
|
||||
setLocalStorageParameter('horizPos', null);
|
||||
this._storageManager.remove('horizPos');
|
||||
}
|
||||
|
||||
_removeListeners() {
|
||||
@ -300,7 +303,7 @@ export class AsyncTable {
|
||||
}
|
||||
|
||||
_changePagesizeHandler = () => {
|
||||
const url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href);
|
||||
const url = new URL(this._storageManager.load('currentTableUrl') || window.location.href);
|
||||
|
||||
// create new FormData and format any date values
|
||||
const formData = Datepicker.unformatAll(this._pagesizeForm, new FormData(this._pagesizeForm));
|
||||
@ -336,7 +339,7 @@ export class AsyncTable {
|
||||
return false;
|
||||
}
|
||||
|
||||
setLocalStorageParameter('currentTableUrl', url.href);
|
||||
this._storageManager.save('currentTableUrl', url.href);
|
||||
// reset table
|
||||
this._removeListeners();
|
||||
this._element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||
@ -346,9 +349,9 @@ export class AsyncTable {
|
||||
this._app.utilRegistry.setupAll(this._element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
setLocalStorageParameter('cssIdPrefix', response.idPrefix);
|
||||
this._storageManager.save('cssIdPrefix', response.idPrefix);
|
||||
callback(this._element);
|
||||
setLocalStorageParameter('cssIdPrefix', '');
|
||||
this._storageManager.remove('cssIdPrefix');
|
||||
}
|
||||
}).catch((err) => console.error(err)
|
||||
).finally(() => this._element.classList.remove(ASYNC_TABLE_LOADING_CLASS));
|
||||
@ -365,17 +368,3 @@ function findCssIdPrefix(id) {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function setLocalStorageParameter(key, value) {
|
||||
const currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
|
||||
if (value !== null) {
|
||||
currentLSState[key] = value;
|
||||
} else {
|
||||
delete currentLSState[key];
|
||||
}
|
||||
window.localStorage.setItem(ASYNC_TABLE_LOCAL_STORAGE_KEY, JSON.stringify(currentLSState));
|
||||
}
|
||||
function getLocalStorageParameter(key) {
|
||||
const currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
|
||||
return currentLSState[key];
|
||||
}
|
||||
|
||||
4
frontend/src/utils/async-table/async-table.sass
Normal file
4
frontend/src/utils/async-table/async-table.sass
Normal file
@ -0,0 +1,4 @@
|
||||
.async-table--loading
|
||||
opacity: 0.7
|
||||
pointer-events: none
|
||||
transition: opacity 400ms ease-out
|
||||
@ -1,5 +0,0 @@
|
||||
.async-table--loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
transition: opacity 400ms ease-out;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './course-teaser.scss';
|
||||
import './course-teaser.sass';
|
||||
|
||||
var COURSE_TEASER_INITIALIZED_CLASS = 'course-teaser--initialized';
|
||||
|
||||
|
||||
246
frontend/src/utils/course-teaser/course-teaser.sass
Normal file
246
frontend/src/utils/course-teaser/course-teaser.sass
Normal file
@ -0,0 +1,246 @@
|
||||
[uw-course-teaser]
|
||||
--course-border-color: var(--color-grey)
|
||||
--course-padding-hori: 10px
|
||||
--course-padding-vert: 12px
|
||||
display: grid
|
||||
position: relative
|
||||
grid-gap: 5px 7px
|
||||
grid-template-columns: 130px 30px 1fr 60px
|
||||
grid-template-areas: 'shrthnd . title chevron' 'shrthnd smstr school chevron' '. . rgstrd . ' 'tutor tutor name . ' 'duedate duedate date . ' 'dscrptn dscrptn dscrptn dscrptn'
|
||||
padding: var(--course-padding-vert) var(--course-padding-hori)
|
||||
transition: background-color .1s ease-out
|
||||
cursor: pointer
|
||||
|
||||
&:hover
|
||||
background-color: var(--course-bg-color)
|
||||
|
||||
+ [uw-course-teaser]
|
||||
border-top: 1px solid var(--course-border-color)
|
||||
|
||||
@media (max-width: 768px)
|
||||
grid-template-columns: 140px 1fr 30px
|
||||
grid-template-areas: 'shrthnd title chevron' 'shrthnd title . ' 'smstr school school ' '. rgstrd rgstrd ' 'tutor name name ' 'duedate date date ' 'dscrptn dscrptn dscrptn'
|
||||
|
||||
@media (max-width: 426px)
|
||||
grid-template-columns: 1fr
|
||||
grid-template-areas: 'shrthnd' 'title' 'smstr' 'school' 'rgstrd' 'tutor' 'name' 'duedate' 'date' 'dscrptnlbl' 'dscrptn' 'chevron'
|
||||
|
||||
.course-teaser__not-expandable
|
||||
cursor: initial
|
||||
|
||||
// chevron
|
||||
.course-teaser__chevron
|
||||
position: relative
|
||||
padding: 10px
|
||||
grid-area: chevron
|
||||
justify-self: center
|
||||
align-self: center
|
||||
width: 100%
|
||||
height: 100%
|
||||
cursor: pointer
|
||||
|
||||
&::before
|
||||
content: ''
|
||||
position: absolute
|
||||
display: block
|
||||
margin-top: -7.35px
|
||||
margin-left: -7.35px
|
||||
|
||||
// visually centered
|
||||
border-width: 0 3px 3px 0
|
||||
width: 8px
|
||||
height: 8px
|
||||
top: 50%
|
||||
left: 50%
|
||||
border-color: var(--color-fontsec)
|
||||
border-style: solid
|
||||
transform: rotate(135deg)
|
||||
transform-origin: 7.25px 7.25px
|
||||
|
||||
// rotate about visual center
|
||||
transition: all .2s ease-out
|
||||
|
||||
&:hover::before
|
||||
transform: scale(1.4) rotate(45deg)
|
||||
|
||||
@media (max-width: 768px)
|
||||
justify-self: end
|
||||
width: auto
|
||||
|
||||
&::before
|
||||
position: initial
|
||||
|
||||
@media (max-width: 426px)
|
||||
&::before
|
||||
transform: rotate(45deg)
|
||||
margin-left: -7.35px
|
||||
|
||||
&:hover::before
|
||||
transform: scale(1.4) rotate(45deg)
|
||||
|
||||
// semester
|
||||
.course-teaser__semester
|
||||
grid-area: smstr
|
||||
justify-self: end
|
||||
|
||||
a
|
||||
color: var(--color-fontsec)
|
||||
|
||||
@media (max-width: 768px)
|
||||
justify-self: initial
|
||||
|
||||
// shorthand
|
||||
.course-teaser__shorthand
|
||||
position: relative
|
||||
grid-area: shrthnd
|
||||
font-size: 2rem
|
||||
line-height: 1.25
|
||||
min-height: calc(2rem * 1.25)
|
||||
|
||||
> a
|
||||
position: absolute
|
||||
height: 100%
|
||||
width: 100%
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
word-break: break-any
|
||||
text-decoration: none !important
|
||||
|
||||
// sry.
|
||||
font-weight: 600
|
||||
color: var(--color-grey-medium)
|
||||
|
||||
// @media (max-width: 768px) {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
// title
|
||||
.course-teaser__title
|
||||
grid-area: title
|
||||
font-size: 1.2rem
|
||||
align-self: baseline
|
||||
|
||||
// registration
|
||||
.course-teaser__registration
|
||||
grid-area: rgstrd
|
||||
color: var(--color-fontsec)
|
||||
font-weight: bold
|
||||
|
||||
// school
|
||||
.course-teaser__school
|
||||
grid-area: school
|
||||
|
||||
a
|
||||
color: var(--color-fontsec)
|
||||
|
||||
// duedate
|
||||
.course-teaser__duedate
|
||||
grid-area: date
|
||||
|
||||
// lecturer
|
||||
.course-teaser__lecturer
|
||||
grid-area: name
|
||||
|
||||
// description
|
||||
.course-teaser__description
|
||||
grid-area: dscrptn
|
||||
max-height: 75vh
|
||||
overflow: auto
|
||||
|
||||
// show description only as dots (overflow text-overflow) and only show when expanded. No "hidden fiddling"
|
||||
|
||||
// labels
|
||||
.course-teaser__lecturer-label
|
||||
grid-area: tutor
|
||||
|
||||
.course-teaser__duedate-label
|
||||
grid-area: duedate
|
||||
|
||||
.course-teaser__description-label
|
||||
grid-area: dscrptnlbl
|
||||
|
||||
.course-teaser__lecturer-label,
|
||||
.course-teaser__description-label,
|
||||
.course-teaser__duedate-label
|
||||
justify-self: end
|
||||
color: var(--color-fontsec)
|
||||
font-style: italic
|
||||
|
||||
@media (max-width: 768px)
|
||||
justify-self: initial
|
||||
|
||||
@media (max-width: 426px)
|
||||
margin-top: 7px
|
||||
font-weight: bold
|
||||
font-style: initial
|
||||
|
||||
// hidden in closed state
|
||||
.course-teaser__description,
|
||||
.course-teaser__description-label
|
||||
display: none
|
||||
|
||||
// expanded courses
|
||||
.course-teaser__expanded
|
||||
cursor: initial
|
||||
|
||||
.course-teaser__chevron
|
||||
&::before
|
||||
transform: rotate(45deg)
|
||||
|
||||
&:hover::before
|
||||
transform: scale(1.4) rotate(135deg)
|
||||
|
||||
@media (max-width: 426px)
|
||||
&::before
|
||||
transform: rotate(225deg)
|
||||
|
||||
&:hover::before
|
||||
transform: scale(1.4) rotate(225deg)
|
||||
|
||||
.course-teaser__school-label,
|
||||
.course-teaser__school,
|
||||
.course-teaser__duedate-label,
|
||||
.course-teaser__duedate,
|
||||
.course-teaser__description
|
||||
display: block
|
||||
|
||||
@media (max-width: 426px)
|
||||
.course-teaser__description-label
|
||||
display: block
|
||||
|
||||
// course teaser: header styling
|
||||
.course-teaser-header
|
||||
padding-top: 10px
|
||||
padding-bottom: 20px
|
||||
line-height: 1.4
|
||||
max-width: 85vw
|
||||
|
||||
.course-header
|
||||
float: left
|
||||
background-color: var(--color-dark)
|
||||
position: relative
|
||||
font-size: 16px
|
||||
color: #fff
|
||||
padding-top: 10px
|
||||
padding-bottom: 10px
|
||||
padding-left: 10px
|
||||
padding-right: 35px
|
||||
margin-bottom: 10px
|
||||
font-weight: bold
|
||||
text-align: left
|
||||
border-radius: 20px 20px 20px 20px / 50% 50% 50% 50%
|
||||
margin-right: 30px
|
||||
|
||||
.course-header-link
|
||||
color: white
|
||||
font-weight: bold
|
||||
text-decoration: none
|
||||
|
||||
&:hover
|
||||
color: inherit
|
||||
|
||||
.course-teaser-header:after
|
||||
content: ""
|
||||
display: table
|
||||
clear: both
|
||||
@ -1,317 +0,0 @@
|
||||
[uw-course-teaser] {
|
||||
--course-border-color: var(--color-grey);
|
||||
--course-padding-hori: 10px;
|
||||
--course-padding-vert: 12px;
|
||||
|
||||
display: grid;
|
||||
position: relative;
|
||||
grid-gap: 5px 7px;
|
||||
grid-template-columns: 130px 30px 1fr 60px;
|
||||
grid-template-areas:
|
||||
'shrthnd . title chevron'
|
||||
'shrthnd smstr school chevron'
|
||||
'. . rgstrd . '
|
||||
'tutor tutor name . '
|
||||
'duedate duedate date . '
|
||||
'dscrptn dscrptn dscrptn dscrptn';
|
||||
padding: var(--course-padding-vert) var(--course-padding-hori);
|
||||
transition: background-color .1s ease-out;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--course-bg-color);
|
||||
}
|
||||
|
||||
+ [uw-course-teaser] {
|
||||
border-top: 1px solid var(--course-border-color);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 140px 1fr 30px;
|
||||
grid-template-areas:
|
||||
'shrthnd title chevron'
|
||||
'shrthnd title . '
|
||||
'smstr school school '
|
||||
'. rgstrd rgstrd '
|
||||
'tutor name name '
|
||||
'duedate date date '
|
||||
'dscrptn dscrptn dscrptn';
|
||||
}
|
||||
|
||||
@media (max-width: 426px) {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas:
|
||||
'shrthnd'
|
||||
'title'
|
||||
'smstr'
|
||||
'school'
|
||||
'rgstrd'
|
||||
'tutor'
|
||||
'name'
|
||||
'duedate'
|
||||
'date'
|
||||
'dscrptnlbl'
|
||||
'dscrptn'
|
||||
'chevron';
|
||||
}
|
||||
}
|
||||
|
||||
.course-teaser__not-expandable {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
/* chevron */
|
||||
.course-teaser__chevron {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
grid-area: chevron;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
margin-top: -7.35px;
|
||||
margin-left: -7.35px; /* visually centered */
|
||||
border-width: 0 3px 3px 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-color: var(--color-fontsec);
|
||||
border-style: solid;
|
||||
transform: rotate(135deg);
|
||||
transform-origin: 7.25px 7.25px; /* rotate about visual center */
|
||||
transition: all .2s ease-out;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
transform: scale(1.4) rotate(45deg);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-self: end;
|
||||
width: auto;
|
||||
|
||||
&::before {
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 426px) {
|
||||
&::before {
|
||||
transform: rotate(45deg);
|
||||
margin-left: -7.35px;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
transform: scale(1.4) rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* semester */
|
||||
.course-teaser__semester {
|
||||
grid-area: smstr;
|
||||
justify-self: end;
|
||||
a {
|
||||
color: var(--color-fontsec);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-self: initial;
|
||||
}
|
||||
}
|
||||
|
||||
/* shorthand */
|
||||
.course-teaser__shorthand {
|
||||
position: relative;
|
||||
grid-area: shrthnd;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
min-height: calc(2rem * 1.25);
|
||||
|
||||
> a {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-any;
|
||||
|
||||
text-decoration: none !important; /* sry. */
|
||||
font-weight: 600;
|
||||
color: var(--color-grey-medium);
|
||||
}
|
||||
|
||||
/* @media (max-width: 768px) {
|
||||
* position: initial;
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
/* title */
|
||||
.course-teaser__title {
|
||||
grid-area: title;
|
||||
font-size: 1.2rem;
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
/* registration */
|
||||
.course-teaser__registration {
|
||||
grid-area: rgstrd;
|
||||
color: var(--color-fontsec);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* school */
|
||||
.course-teaser__school {
|
||||
grid-area: school;
|
||||
a {
|
||||
color: var(--color-fontsec);
|
||||
}
|
||||
}
|
||||
|
||||
/* duedate */
|
||||
.course-teaser__duedate {
|
||||
grid-area: date;
|
||||
}
|
||||
|
||||
/* lecturer */
|
||||
.course-teaser__lecturer {
|
||||
grid-area: name;
|
||||
}
|
||||
|
||||
/* description */
|
||||
.course-teaser__description {
|
||||
grid-area: dscrptn;
|
||||
max-height: 75vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* show description only as dots (overflow text-overflow) and only show when expanded. No "hidden fiddling" */
|
||||
|
||||
/* labels */
|
||||
.course-teaser__lecturer-label {
|
||||
grid-area: tutor;
|
||||
}
|
||||
|
||||
.course-teaser__duedate-label {
|
||||
grid-area: duedate;
|
||||
}
|
||||
|
||||
.course-teaser__description-label {
|
||||
grid-area: dscrptnlbl;
|
||||
}
|
||||
|
||||
.course-teaser__lecturer-label,
|
||||
.course-teaser__description-label,
|
||||
.course-teaser__duedate-label {
|
||||
justify-self: end;
|
||||
color: var(--color-fontsec);
|
||||
font-style: italic;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-self: initial;
|
||||
}
|
||||
|
||||
@media (max-width: 426px) {
|
||||
margin-top: 7px;
|
||||
font-weight: bold;
|
||||
font-style: initial;
|
||||
}
|
||||
}
|
||||
|
||||
/* hidden in closed state */
|
||||
.course-teaser__description,
|
||||
.course-teaser__description-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* expanded courses */
|
||||
.course-teaser__expanded {
|
||||
cursor: initial;
|
||||
|
||||
.course-teaser__chevron {
|
||||
&::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
transform: scale(1.4) rotate(135deg);
|
||||
}
|
||||
|
||||
@media (max-width: 426px) {
|
||||
&::before {
|
||||
transform: rotate(225deg);
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
transform: scale(1.4) rotate(225deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-teaser__school-label,
|
||||
.course-teaser__school,
|
||||
.course-teaser__duedate-label,
|
||||
.course-teaser__duedate,
|
||||
.course-teaser__description {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 426px) {
|
||||
.course-teaser__description-label {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
course teaser: header styling
|
||||
*/
|
||||
.course-teaser-header {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
line-height: 1.4;
|
||||
max-width: 85vw;
|
||||
|
||||
.course-header {
|
||||
float: left;
|
||||
background-color: var(--color-dark);
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 35px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
border-radius: 20px 20px 20px 20px / 50% 50% 50% 50%;
|
||||
margin-right: 30px;
|
||||
|
||||
.course-header-link {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-teaser-header:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import datetime from 'tail.datetime';
|
||||
import './datepicker.css';
|
||||
import { Utility } from '../../core/utility';
|
||||
import moment from 'moment';
|
||||
|
||||
@ -33,7 +34,7 @@ const FORM_DATE_FORMAT_MOMENT = {
|
||||
* @param {*} formatOut format string of the desired output date string
|
||||
*/
|
||||
function reformatDateString(dateStr, formatIn, formatOut) {
|
||||
const parsedMomentDate = moment(dateStr, formatIn);
|
||||
const parsedMomentDate = moment(dateStr, [formatIn, formatOut]);
|
||||
return parsedMomentDate.isValid() ? parsedMomentDate.format(formatOut) : dateStr;
|
||||
}
|
||||
|
||||
@ -48,9 +49,6 @@ const DATEPICKER_CONFIG = {
|
||||
timeMinutes: 0,
|
||||
timeSeconds: 0,
|
||||
|
||||
// german settings
|
||||
// TODO: hardcoded, get from current language / settings
|
||||
locale: 'de',
|
||||
weekStart: 1,
|
||||
dateFormat: FORM_DATE_FORMAT_DATE_DT,
|
||||
timeFormat: FORM_DATE_FORMAT_TIME_DT,
|
||||
@ -86,6 +84,7 @@ export class Datepicker {
|
||||
datepickerInstance;
|
||||
_element;
|
||||
elementType;
|
||||
_locale;
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
@ -96,6 +95,8 @@ export class Datepicker {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._locale = window.App.i18n.getDatetimeLocale();
|
||||
|
||||
// initialize datepickerCollections singleton if not already done
|
||||
if (!Datepicker.datepickerCollections) {
|
||||
Datepicker.datepickerCollections = new Map();
|
||||
@ -123,11 +124,22 @@ export class Datepicker {
|
||||
throw new Error('Datepicker utility called on unsupported element!');
|
||||
}
|
||||
|
||||
// format any existing dates to fancy display format on pageload
|
||||
this.formatElementValue(true);
|
||||
// FIXME dirty hack below; fix tail.datetime instead
|
||||
|
||||
// initialize tail.datetime (datepicker) instance
|
||||
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig });
|
||||
// get date object from internal format before datetime does nasty things with it
|
||||
var parsedMomentDate = moment(this._element.value, [ FORM_DATE_FORMAT[this.elementType], FORM_DATE_FORMAT_MOMENT[this.elementType] ], true);
|
||||
if (parsedMomentDate && parsedMomentDate.isValid()) {
|
||||
parsedMomentDate = parsedMomentDate.toDate();
|
||||
} else {
|
||||
parsedMomentDate = undefined;
|
||||
}
|
||||
|
||||
// initialize tail.datetime (datepicker) instance and let it do weird stuff with the element value
|
||||
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig, locale: this._locale });
|
||||
|
||||
// reset date to something sane
|
||||
if (parsedMomentDate)
|
||||
this.datepickerInstance.selectDate(parsedMomentDate);
|
||||
|
||||
// insert the datepicker element (dt) after the form
|
||||
this._element.form.parentNode.insertBefore(this.datepickerInstance.dt, this._element.form.nextSibling);
|
||||
@ -169,14 +181,27 @@ export class Datepicker {
|
||||
// change the selected date in the tail.datetime instance if the value of the input element is changed
|
||||
this._element.addEventListener('change', setDatepickerDate, { once: true });
|
||||
|
||||
// close the instance if something other than the instance was clicked (i.e. if the target is not within the datepicker instance and if any previously clicked calendar view was replaced (is not in the window anymore) because it was clicked). YES, I KNOW
|
||||
window.addEventListener('click', event => {
|
||||
if (!this.datepickerInstance.dt.contains(event.target) && window.document.contains(event.target)) {
|
||||
// close the instance on focusout of any element if another input is focussed that is neither the timepicker nor _element
|
||||
window.addEventListener('focusout', event => {
|
||||
const hasFocus = event.relatedTarget !== null;
|
||||
const focussedIsNotTimepicker = !this.datepickerInstance.dt.contains(event.relatedTarget);
|
||||
const focussedIsNotElement = event.relatedTarget !== this._element;
|
||||
const focussedIsInDocument = window.document.contains(event.relatedTarget);
|
||||
if (hasFocus && focussedIsNotTimepicker && focussedIsNotElement && focussedIsInDocument)
|
||||
this.datepickerInstance.close();
|
||||
});
|
||||
|
||||
// close the instance on click on any element outside of the datepicker (except the input element itself)
|
||||
window.addEventListener('click', event => {
|
||||
const targetIsOutside = !this.datepickerInstance.dt.contains(event.target)
|
||||
&& event.target !== this.datepickerInstance.dt;
|
||||
const targetIsInDocument = window.document.contains(event.target);
|
||||
const targetIsNotElement = event.target !== this._element;
|
||||
if (targetIsOutside && targetIsInDocument && targetIsNotElement)
|
||||
this.datepickerInstance.close();
|
||||
}
|
||||
});
|
||||
|
||||
// close the datepicker on escape keydown events
|
||||
// close the instance on escape keydown events
|
||||
this._element.addEventListener('keydown', event => {
|
||||
if (event.keyCode === KEYCODE_ESCAPE) {
|
||||
this.datepickerInstance.close();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import './form.scss';
|
||||
import './form.sass';
|
||||
import { AutoSubmitButton } from './auto-submit-button';
|
||||
import { AutoSubmitInput } from './auto-submit-input';
|
||||
import { Datepicker } from './datepicker';
|
||||
|
||||
35
frontend/src/utils/form/form.sass
Normal file
35
frontend/src/utils/form/form.sass
Normal file
@ -0,0 +1,35 @@
|
||||
fieldset
|
||||
border: 0
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
legend
|
||||
display: none
|
||||
|
||||
@media (min-width: 769px)
|
||||
.form-group__input
|
||||
grid-column: 2
|
||||
|
||||
[uw-auto-submit-button][type='submit']
|
||||
animation: fade-in 500ms ease-in-out backwards
|
||||
animation-delay: 500ms
|
||||
|
||||
@keyframes fade-in
|
||||
from
|
||||
opacity: 0
|
||||
|
||||
.hidden
|
||||
visibility: hidden !important
|
||||
height: 0 !important
|
||||
width: 0 !important
|
||||
opacity: 0 !important
|
||||
margin: 0 !important
|
||||
padding: 0 !important
|
||||
min-width: 0 !important
|
||||
|
||||
.select--pagesize
|
||||
width: 5em
|
||||
min-width: 75px
|
||||
|
||||
.label-pagesize
|
||||
margin-right: 13px
|
||||
@ -1,45 +0,0 @@
|
||||
fieldset {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
legend {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.form-group__input {
|
||||
grid-column: 2;
|
||||
}
|
||||
}
|
||||
|
||||
[uw-auto-submit-button][type='submit'] {
|
||||
animation: fade-in 500ms ease-in-out backwards;
|
||||
animation-delay: 500ms;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden !important;
|
||||
height: 0 !important;
|
||||
width: 0 !important;
|
||||
opacity: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.select--pagesize {
|
||||
width: 5em;
|
||||
min-width: 75px;
|
||||
}
|
||||
|
||||
.label-pagesize {
|
||||
margin-right: 13px;
|
||||
}
|
||||
312
frontend/src/utils/hide-columns/hide-columns.js
Normal file
312
frontend/src/utils/hide-columns/hide-columns.js
Normal file
@ -0,0 +1,312 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
|
||||
import './hide-columns.sass';
|
||||
|
||||
const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns';
|
||||
const TABLE_HEADER_IDENT = 'uw-hide-column-header';
|
||||
|
||||
const TABLE_UTILS_ATTR = 'table-utils';
|
||||
const TABLE_UTILS_CONTAINER_SELECTOR = `[${TABLE_UTILS_ATTR}]`;
|
||||
|
||||
const TABLE_HIDER_CLASS = 'table-hider';
|
||||
const TABLE_HIDER_VISIBLE_CLASS = 'table-hider--visible';
|
||||
|
||||
const TABLE_PILL_CLASS = 'table-pill';
|
||||
|
||||
const CELL_HIDDEN_CLASS = 'hide-columns--hidden-cell';
|
||||
const CELL_ORIGINAL_COLSPAN = 'uw-hide-column-original-colspan';
|
||||
|
||||
@Utility({
|
||||
selector: `[${HIDE_COLUMNS_CONTAINER_IDENT}] table`,
|
||||
})
|
||||
export class HideColumns {
|
||||
|
||||
_storageManager = new StorageManager('HIDE_COLUMNS', '1.0.0', { location: LOCATION.LOCAL });
|
||||
|
||||
_element;
|
||||
_elementWrapper;
|
||||
_tableUtilContainer;
|
||||
|
||||
_autoHide;
|
||||
|
||||
headerToHider = new Map();
|
||||
hiderToHeader = new Map();
|
||||
|
||||
addHeaderHider(th, hider) {
|
||||
this.headerToHider.set(th, hider);
|
||||
this.hiderToHeader.set(hider, th);
|
||||
}
|
||||
|
||||
constructor(element) {
|
||||
this._autoHide = this._storageManager.load('autoHide', {}) || false;
|
||||
|
||||
if (!element) {
|
||||
throw new Error('Hide Columns utility cannot be setup without an element!');
|
||||
}
|
||||
|
||||
// do not provide hide-column ability in tables inside modals or async forms with response
|
||||
if (element.closest('[uw-modal], .async-form__response')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._element = element;
|
||||
|
||||
const hideColumnsContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}]`);
|
||||
if (!hideColumnsContainer) {
|
||||
throw new Error('Hide Columns utility needs to be setup on a table inside a hide columns container!');
|
||||
}
|
||||
this._elementWrapper = hideColumnsContainer;
|
||||
|
||||
// get or create table utils container
|
||||
this._tableUtilContainer = hideColumnsContainer.querySelector(TABLE_UTILS_CONTAINER_SELECTOR);
|
||||
if (!this._tableUtilContainer) {
|
||||
this._tableUtilContainer = document.createElement('div');
|
||||
this._tableUtilContainer.setAttribute(TABLE_UTILS_ATTR, '');
|
||||
const tableContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}] > *`);
|
||||
hideColumnsContainer.insertBefore(this._tableUtilContainer, tableContainer);
|
||||
}
|
||||
|
||||
this._element.querySelectorAll('th').forEach(th => this.setupHideButton(th));
|
||||
}
|
||||
|
||||
setupHideButton(th) {
|
||||
const preHidden = this.isHiddenColumn(th);
|
||||
|
||||
const hider = document.createElement('span');
|
||||
|
||||
const hiderIcon = document.createElement('i');
|
||||
hiderIcon.classList.add('fas', 'fa-fw');
|
||||
hider.appendChild(hiderIcon);
|
||||
|
||||
const hiderContent = document.createElement('span');
|
||||
hiderContent.classList.add('table-hider__label');
|
||||
hiderContent.innerHTML = th.innerText;
|
||||
hider.appendChild(hiderContent);
|
||||
|
||||
this.addHeaderHider(th, hider);
|
||||
|
||||
th.addEventListener('mouseover', () => {
|
||||
hider.classList.add(TABLE_HIDER_VISIBLE_CLASS);
|
||||
});
|
||||
th.addEventListener('mouseout', () => {
|
||||
if (hider.classList.contains(TABLE_HIDER_CLASS)) {
|
||||
hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
|
||||
}
|
||||
});
|
||||
|
||||
hider.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.switchColumnDisplay(th, hider);
|
||||
// this._tableHiderContainer.getElementsByClassName(TABLE_HIDER_CLASS).forEach(hider => this.hideHiderBehindHeader(hider));
|
||||
});
|
||||
|
||||
hider.addEventListener('mouseover', () => {
|
||||
hider.classList.add(TABLE_HIDER_VISIBLE_CLASS);
|
||||
const currentlyHidden = this.isHiddenColumn(th);
|
||||
this.updateHiderIcon(hider, !currentlyHidden);
|
||||
});
|
||||
hider.addEventListener('mouseout', () => {
|
||||
if (hider.classList.contains(TABLE_HIDER_CLASS)) {
|
||||
hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
|
||||
}
|
||||
const currentlyHidden = this.isHiddenColumn(th);
|
||||
this.updateHiderIcon(hider, currentlyHidden);
|
||||
});
|
||||
|
||||
new ResizeObserver(() => { this.repositionHider(hider); }).observe(th);
|
||||
|
||||
// reposition hider on each window resize event
|
||||
// window.addEventListener('resize', () => this.repositionHider(hider));
|
||||
|
||||
this.updateColumnDisplay(this.colIndex(th), preHidden);
|
||||
this.updateHider(hider, preHidden);
|
||||
|
||||
if (preHidden) {
|
||||
this._tableUtilContainer.appendChild(hider);
|
||||
} else {
|
||||
this.hideHiderBehindHeader(hider);
|
||||
}
|
||||
}
|
||||
|
||||
switchColumnDisplay(th, hider) {
|
||||
const hidden = !this.isHiddenColumn(th);
|
||||
const originalColspan = Math.max(1, th.getAttribute(CELL_ORIGINAL_COLSPAN)) || 1;
|
||||
const colspan = Math.max(1, th.colSpan) || 1;
|
||||
const columnIndex = this.colIndex(th);
|
||||
|
||||
for (var i = 0; i < Math.max(colspan, originalColspan); i++) {
|
||||
this.updateColumnDisplay(columnIndex + i, hidden);
|
||||
}
|
||||
this.updateHider(hider, hidden);
|
||||
|
||||
// persist new hidden setting for column
|
||||
if ((hidden && this.isEmptyColumn(columnIndex) && this._autoHide) || (!hidden && (!this.isEmptyColumn(columnIndex) || !this._autoHide))) {
|
||||
this._storageManager.remove(this.getStorageKey(th));
|
||||
} else {
|
||||
this._storageManager.save(this.getStorageKey(th), hidden);
|
||||
}
|
||||
}
|
||||
|
||||
updateColumnDisplay(columnIndex, hidden) {
|
||||
this._element.getElementsByTagName('tr').forEach(row => {
|
||||
const cell = this.getCol(row, columnIndex);
|
||||
|
||||
if (cell) {
|
||||
const originalColspan = cell.getAttribute(CELL_ORIGINAL_COLSPAN);
|
||||
const colspan = Math.max(1, cell.colSpan) || 1;
|
||||
|
||||
if (hidden) {
|
||||
if (colspan > 1) {
|
||||
if (!originalColspan) {
|
||||
cell.setAttribute(CELL_ORIGINAL_COLSPAN, colspan);
|
||||
}
|
||||
cell.colSpan--;
|
||||
} else {
|
||||
cell.classList.add(CELL_HIDDEN_CLASS);
|
||||
}
|
||||
} else {
|
||||
if (cell.classList.contains(CELL_HIDDEN_CLASS)) {
|
||||
cell.classList.remove(CELL_HIDDEN_CLASS);
|
||||
} else if (originalColspan && colspan < originalColspan) {
|
||||
cell.colSpan++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateHider(hider, hidden) {
|
||||
if (hidden) {
|
||||
hider.classList.remove(TABLE_HIDER_CLASS);
|
||||
hider.classList.add(TABLE_PILL_CLASS);
|
||||
this._tableUtilContainer.appendChild(hider);
|
||||
} else {
|
||||
hider.classList.remove(TABLE_PILL_CLASS);
|
||||
hider.classList.add(TABLE_HIDER_CLASS);
|
||||
this.hideHiderBehindHeader(hider);
|
||||
}
|
||||
this.updateHiderIcon(hider, hidden);
|
||||
}
|
||||
|
||||
updateHiderIcon(hider, hidden) {
|
||||
hider.getElementsByClassName('fas').forEach(hiderIcon => {
|
||||
hiderIcon.classList.remove(hidden ? 'fa-eye' : 'fa-eye-slash');
|
||||
hiderIcon.classList.add(hidden ? 'fa-eye-slash' : 'fa-eye');
|
||||
});
|
||||
}
|
||||
|
||||
hideHiderBehindHeader(hider) {
|
||||
if (!this.hiderToHeader.get(hider).contains(hider)) {
|
||||
this.hiderToHeader.get(hider).appendChild(hider);
|
||||
}
|
||||
|
||||
this.repositionHider(hider);
|
||||
|
||||
// remove visible class if necessary
|
||||
hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
|
||||
}
|
||||
|
||||
repositionHider(hider) {
|
||||
const thR = this.hiderToHeader.get(hider).getBoundingClientRect(),
|
||||
hR = hider.getBoundingClientRect();
|
||||
|
||||
hider.style.left = (thR.width/2 - hR.width/2) + 'px';
|
||||
hider.style.top = thR.height + 'px';
|
||||
}
|
||||
|
||||
getStorageKey(th) {
|
||||
// get handler name
|
||||
const handlerIdent = document.querySelector('[uw-handler]').getAttribute('uw-handler');
|
||||
|
||||
// get hide-columns container ident (if not present, use table index in document as fallback)
|
||||
let tIdent = this._elementWrapper.getAttribute(HIDE_COLUMNS_CONTAINER_IDENT);
|
||||
if (!tIdent) {
|
||||
const tablesInDocument = document.getElementsByTagName('TABLE');
|
||||
for (let i = 0; i < tablesInDocument.length; i++) {
|
||||
if (tablesInDocument[i] === this._element) {
|
||||
tIdent = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for unique table header ident from backend (if not present, use cell index as fallback)
|
||||
let thIdent = th.getAttribute(TABLE_HEADER_IDENT);
|
||||
if (!thIdent) {
|
||||
thIdent = this.colIndex(th);
|
||||
}
|
||||
|
||||
return `${handlerIdent}__${tIdent}__${thIdent}`;
|
||||
}
|
||||
|
||||
isEmptyColumn(columnIndex) {
|
||||
for (let row of this._element.getElementsByTagName('tr')) {
|
||||
const cell = this.getCol(row, columnIndex);
|
||||
if (cell.matches('th'))
|
||||
continue;
|
||||
if (cell.querySelector('.table__td-content')) {
|
||||
for (let child of cell.children) {
|
||||
if (!isEmptyElement(child))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return isEmptyElement(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isHiddenColumn(th) {
|
||||
const hidden = this._storageManager.load(this.getStorageKey(th)),
|
||||
emptyColumn = this.isEmptyColumn(this.colIndex(th));
|
||||
return hidden === true || hidden === undefined && emptyColumn && this._autoHide;
|
||||
}
|
||||
|
||||
colSpan(cell) {
|
||||
if (!cell)
|
||||
return 1;
|
||||
|
||||
const originalColspan = cell.getAttribute(CELL_ORIGINAL_COLSPAN);
|
||||
const colspan = Math.max(1, cell.colSpan) || 1;
|
||||
|
||||
return originalColspan ? Math.max(colspan, originalColspan) : colspan;
|
||||
}
|
||||
|
||||
colIndex(cell) {
|
||||
if (!cell)
|
||||
return 0;
|
||||
|
||||
const rowParent = cell.closest('tr');
|
||||
|
||||
if (!rowParent)
|
||||
return 0;
|
||||
|
||||
var i = 0;
|
||||
for (const sibling of Array.from(rowParent.cells).slice(0, cell.cellIndex)) {
|
||||
i += this.colSpan(sibling);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
getCol(row, columnIndex) {
|
||||
var c = 0;
|
||||
|
||||
for (const cell of row.cells) {
|
||||
c += cell ? this.colSpan(cell) : 1;
|
||||
|
||||
if (columnIndex < c)
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isEmptyElement(element) {
|
||||
for (let child of element.childNodes) {
|
||||
if (child.nodeName !== '#comment')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
61
frontend/src/utils/hide-columns/hide-columns.sass
Normal file
61
frontend/src/utils/hide-columns/hide-columns.sass
Normal file
@ -0,0 +1,61 @@
|
||||
.table-hider
|
||||
background-color: #fff
|
||||
color: var(--color-link)
|
||||
padding: 10px
|
||||
cursor: pointer
|
||||
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6)
|
||||
position: absolute
|
||||
overflow: hidden
|
||||
transition: transform .2s ease
|
||||
transform: scaleY(0)
|
||||
transform-origin: top
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-grey-light)
|
||||
|
||||
.table-hider__label
|
||||
display: none
|
||||
|
||||
&.table-hider--visible
|
||||
transform: scaleY(0.4)
|
||||
transition: none
|
||||
|
||||
// TODO find better way to prevent transition on icons
|
||||
|
||||
.fas
|
||||
transform: scaleY(2.5)
|
||||
|
||||
&:hover
|
||||
transform: scaleY(1)
|
||||
|
||||
.fas
|
||||
transform: scaleY(1)
|
||||
|
||||
[table-utils]
|
||||
max-width: 85vw
|
||||
margin-bottom: 10px
|
||||
min-height: 0
|
||||
line-height: 1.4
|
||||
|
||||
.table-pill
|
||||
background-color: var(--color-dark)
|
||||
float: left
|
||||
color: #fff
|
||||
padding: 10px
|
||||
border-radius: 20px / 50%
|
||||
margin-right: 20px
|
||||
margin-bottom: 10px
|
||||
cursor: pointer
|
||||
|
||||
.table-hider__label
|
||||
font-size: 16px
|
||||
font-weight: bold
|
||||
margin-left: 5px
|
||||
|
||||
&:after
|
||||
content: ""
|
||||
display: block
|
||||
clear: both
|
||||
|
||||
.hide-columns--hidden-cell
|
||||
display: none
|
||||
@ -1,11 +1,11 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './checkbox.scss';
|
||||
import './checkbox.sass';
|
||||
|
||||
var CHECKBOX_CLASS = 'checkbox';
|
||||
var CHECKBOX_INITIALIZED_CLASS = 'checkbox--initialized';
|
||||
|
||||
@Utility({
|
||||
selector: 'input[type="checkbox"]',
|
||||
selector: 'input[type="checkbox"]:not([uw-no-checkbox])',
|
||||
})
|
||||
export class Checkbox {
|
||||
|
||||
|
||||
71
frontend/src/utils/inputs/checkbox.sass
Normal file
71
frontend/src/utils/inputs/checkbox.sass
Normal file
@ -0,0 +1,71 @@
|
||||
// CUSTOM CHECKBOXES
|
||||
// Completely replaces legacy checkbox
|
||||
.checkbox [type='checkbox'], #lang-checkbox
|
||||
position: fixed
|
||||
top: -1px
|
||||
left: -1px
|
||||
width: 1px
|
||||
height: 1px
|
||||
overflow: hidden
|
||||
display: none
|
||||
|
||||
.checkbox
|
||||
position: relative
|
||||
display: inline-block
|
||||
|
||||
label
|
||||
display: block
|
||||
height: 20px
|
||||
width: 20px
|
||||
background-color: #f3f3f3
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05)
|
||||
border: 2px solid var(--color-primary)
|
||||
border-radius: 4px
|
||||
color: white
|
||||
cursor: pointer
|
||||
|
||||
label::before,
|
||||
label::after
|
||||
position: absolute
|
||||
display: block
|
||||
top: 12px
|
||||
left: 8px
|
||||
height: 2px
|
||||
width: 8px
|
||||
background-color: var(--color-font)
|
||||
|
||||
\:checked + label
|
||||
background-color: var(--color-primary)
|
||||
|
||||
[type='checkbox']:focus + label
|
||||
border-color: #3273dc
|
||||
box-shadow: 0 0 0 0.125em rgba(50, 115, 220, 0.25)
|
||||
outline: 0
|
||||
|
||||
\:checked + label::before,
|
||||
:checked + label::after
|
||||
content: ''
|
||||
|
||||
\:checked + label::before
|
||||
background-color: white
|
||||
transform: rotate(45deg)
|
||||
left: 2px
|
||||
top: 11px
|
||||
|
||||
\:checked + label::after
|
||||
background-color: white
|
||||
transform: rotate(-45deg)
|
||||
top: 9px
|
||||
width: 12px
|
||||
left: 7px
|
||||
|
||||
[disabled] + label
|
||||
pointer-events: none
|
||||
border: none
|
||||
opacity: 0.6
|
||||
filter: grayscale(1)
|
||||
|
||||
// special treatment for checkboxes in table headers
|
||||
th .checkbox
|
||||
margin-right: 7px
|
||||
vertical-align: bottom
|
||||
@ -1,82 +0,0 @@
|
||||
|
||||
/* CUSTOM CHECKBOXES */
|
||||
/* Completely replaces legacy checkbox */
|
||||
.checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
[type='checkbox'] {
|
||||
position: fixed;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: #f3f3f3;
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05);
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label::before,
|
||||
label::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 12px;
|
||||
left: 8px;
|
||||
height: 2px;
|
||||
width: 8px;
|
||||
background-color: var(--color-font);
|
||||
}
|
||||
|
||||
:checked + label {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
[type='checkbox']:focus + label {
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
:checked + label::before,
|
||||
:checked + label::after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
:checked + label::before {
|
||||
background-color: white;
|
||||
transform: rotate(45deg);
|
||||
left: 2px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
:checked + label::after {
|
||||
background-color: white;
|
||||
transform: rotate(-45deg);
|
||||
top: 9px;
|
||||
width: 12px;
|
||||
left: 7px;
|
||||
}
|
||||
|
||||
[disabled] + label {
|
||||
pointer-events: none;
|
||||
border: none;
|
||||
opacity: 0.6;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* special treatment for checkboxes in table headers */
|
||||
th .checkbox {
|
||||
margin-right: 7px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './file-input.scss';
|
||||
import './file-input.sass';
|
||||
|
||||
const FILE_INPUT_CLASS = 'file-input';
|
||||
const FILE_INPUT_INITIALIZED_CLASS = 'file-input--initialized';
|
||||
|
||||
2
frontend/src/utils/inputs/file-input.sass
Normal file
2
frontend/src/utils/inputs/file-input.sass
Normal file
@ -0,0 +1,2 @@
|
||||
.file-input__list:empty
|
||||
display: none
|
||||
@ -1,3 +0,0 @@
|
||||
.file-input__list:empty {
|
||||
display: none;
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import { Checkbox } from './checkbox';
|
||||
import { FileInput } from './file-input';
|
||||
|
||||
import './inputs.scss';
|
||||
import './radio.scss';
|
||||
import './inputs.sass';
|
||||
import './radio.sass';
|
||||
|
||||
export const InputUtils = [
|
||||
Checkbox,
|
||||
|
||||
211
frontend/src/utils/inputs/inputs.sass
Normal file
211
frontend/src/utils/inputs/inputs.sass
Normal file
@ -0,0 +1,211 @@
|
||||
// GENERAL STYLES FOR FORMS
|
||||
|
||||
// FORM GROUPS
|
||||
.form-section-title
|
||||
color: var(--color-fontsec)
|
||||
margin: 0
|
||||
|
||||
+ .form-group
|
||||
margin-top: 11px
|
||||
|
||||
.form-group
|
||||
position: relative
|
||||
display: flex
|
||||
display: grid
|
||||
grid-template-columns: 1fr 3fr
|
||||
grid-gap: 5px
|
||||
justify-content: flex-start
|
||||
align-items: flex-start
|
||||
|
||||
+ .form-group, + .form-section-legend, + .form-section-notification
|
||||
margin-top: 11px
|
||||
|
||||
+ .form-section-title
|
||||
margin-top: 40px
|
||||
|
||||
.form-section-legend
|
||||
color: var(--color-fontsec)
|
||||
margin: 7px 0
|
||||
|
||||
.form-group__hint, .form-section-title__hint
|
||||
color: var(--color-fontsec)
|
||||
font-size: 0.9rem
|
||||
font-weight: 600
|
||||
|
||||
.form-section-title__hint
|
||||
margin-top: 7px
|
||||
|
||||
+ .form-group
|
||||
margin-top: 11px
|
||||
|
||||
.form-group-label
|
||||
font-weight: 600
|
||||
padding-top: 6px
|
||||
|
||||
.form-group-label__hint
|
||||
margin-top: 7px
|
||||
color: var(--color-fontsec)
|
||||
font-size: 0.9rem
|
||||
|
||||
.form-group--required .form-group-label__caption::after, .form-group__required-marker::before
|
||||
content: ' *'
|
||||
color: var(--color-error)
|
||||
font-weight: 600
|
||||
|
||||
.form-group--submit .form-group__input
|
||||
grid-column: 2
|
||||
|
||||
@media (max-width: 768px)
|
||||
.form-group--submit .form-group__input
|
||||
grid-column: 1
|
||||
|
||||
.form-group--has-error
|
||||
background-color: rgba(255, 0, 0, 0.1)
|
||||
|
||||
input, textarea
|
||||
border-color: var(--color-error) !important
|
||||
|
||||
.form-error
|
||||
display: block
|
||||
|
||||
.form-error
|
||||
display: none
|
||||
|
||||
@media (max-width: 768px)
|
||||
.form-group
|
||||
grid-template-columns: 1fr
|
||||
align-items: baseline
|
||||
margin-top: 17px
|
||||
flex-direction: column
|
||||
|
||||
// TEXT INPUTS
|
||||
input[type='text'],
|
||||
input[type='search'],
|
||||
input[type='password'],
|
||||
input[type='url'],
|
||||
input[type='number'],
|
||||
input[type='email'],
|
||||
input[type*='date'],
|
||||
input[type*='time'],
|
||||
select
|
||||
// from bulma.css
|
||||
color: #363636
|
||||
border-color: #dbdbdb
|
||||
background-color: #f3f3f3
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05)
|
||||
width: 100%
|
||||
max-width: 600px
|
||||
align-items: center
|
||||
border: 1px solid transparent
|
||||
border-radius: 4px
|
||||
font-size: 1rem
|
||||
font-family: var(--font-base)
|
||||
line-height: 1.5
|
||||
padding: 4px 13px
|
||||
|
||||
input[type='number']
|
||||
width: 100px
|
||||
|
||||
input[type*='date'],
|
||||
input[type*='time'],
|
||||
.flatpickr-input[type='text']
|
||||
width: 50%
|
||||
width: 250px
|
||||
|
||||
// BUTTON STYLE SEE default-layout.lucius
|
||||
|
||||
// TEXTAREAS
|
||||
textarea
|
||||
width: 100%
|
||||
height: 170px
|
||||
max-width: 600px
|
||||
line-height: 1.5
|
||||
color: #363636
|
||||
background-color: #f3f3f3
|
||||
padding: 4px 13px
|
||||
font-size: 1rem
|
||||
font-family: var(--font-base)
|
||||
appearance: none
|
||||
border: 1px solid #dbdbdb
|
||||
border-radius: 2px
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05)
|
||||
vertical-align: top
|
||||
|
||||
// SHARED STATE RELATED STYLES
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea
|
||||
&:focus
|
||||
border-color: #3273dc
|
||||
box-shadow: 0 0 0 0.125em rgba(50, 115, 220, 0.25)
|
||||
outline: 0
|
||||
|
||||
&[disabled]
|
||||
background-color: #f3f3f3
|
||||
color: #7a7a7a
|
||||
box-shadow: none
|
||||
border-color: #dbdbdb
|
||||
|
||||
&[readonly]
|
||||
background-color: #f5f5f5
|
||||
border-color: #dbdbdb
|
||||
|
||||
// OPTIONS
|
||||
|
||||
select[size="1"], select:not([size])
|
||||
appearance: menulist
|
||||
|
||||
select,
|
||||
option
|
||||
font-size: 1rem
|
||||
line-height: 1.5
|
||||
padding: 4px 13px
|
||||
border: 1px solid #dbdbdb
|
||||
border-radius: 2px
|
||||
outline: 0
|
||||
color: #363636
|
||||
min-width: 250px
|
||||
width: auto
|
||||
background-color: #f3f3f3
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05)
|
||||
|
||||
@media (max-width: 425px)
|
||||
select, option
|
||||
width: 100%
|
||||
|
||||
// FILE INPUT
|
||||
.file-input
|
||||
display: none
|
||||
|
||||
.file-input__label
|
||||
cursor: pointer
|
||||
display: inline-block
|
||||
background-color: var(--color-primary)
|
||||
color: white
|
||||
padding: 10px 17px
|
||||
border-radius: 3px
|
||||
|
||||
.file-input__info
|
||||
font-size: .9rem
|
||||
font-style: italic
|
||||
margin: 10px 0
|
||||
color: var(--color-fontsec)
|
||||
|
||||
.file-input__list
|
||||
margin-left: 40px
|
||||
margin-top: 10px
|
||||
font-weight: 600
|
||||
|
||||
// PREVIOUSLY UPLOADED FILES
|
||||
|
||||
.file-uploads-label
|
||||
margin-bottom: 10px
|
||||
|
||||
.file-container
|
||||
display: flex
|
||||
align-items: center
|
||||
margin-bottom: 10px
|
||||
|
||||
.checkbox
|
||||
margin-left: 12px
|
||||
@ -1,254 +0,0 @@
|
||||
/* GENERAL STYLES FOR FORMS */
|
||||
|
||||
/* FORM GROUPS */
|
||||
.form-section-title {
|
||||
color: var(--color-fontsec);
|
||||
margin: 0;
|
||||
|
||||
+ .form-group {
|
||||
margin-top: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-gap: 5px;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
+ .form-group, + .form-section-legend, + .form-section-notification {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
+ .form-section-title {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-section-legend {
|
||||
color: var(--color-fontsec);
|
||||
margin: 7px 0;
|
||||
}
|
||||
|
||||
.form-group__hint, .form-section-title__hint {
|
||||
color: var(--color-fontsec);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-section-title__hint {
|
||||
margin-top: 7px;
|
||||
|
||||
+ .form-group {
|
||||
margin-top: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group-label {
|
||||
font-weight: 600;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.form-group-label__hint {
|
||||
margin-top: 7px;
|
||||
color: var(--color-fontsec);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group--required .form-group-label__caption::after, .form-group__required-marker::before {
|
||||
content: ' *';
|
||||
color: var(--color-error);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-group--submit .form-group__input {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-group--submit .form-group__input {
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group--has-error {
|
||||
background-color: rgba(255, 0, 0, 0.1);
|
||||
|
||||
input, textarea {
|
||||
border-color: var(--color-error) !important;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.form-error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-group {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: baseline;
|
||||
margin-top: 17px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* TEXT INPUTS */
|
||||
input[type='text'],
|
||||
input[type='search'],
|
||||
input[type='password'],
|
||||
input[type='url'],
|
||||
input[type='number'],
|
||||
input[type='email'],
|
||||
input[type*='date'],
|
||||
input[type*='time'],
|
||||
select {
|
||||
/* from bulma.css */
|
||||
color: #363636;
|
||||
border-color: #dbdbdb;
|
||||
background-color: #f3f3f3;
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05);
|
||||
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
align-items: center;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-base);
|
||||
line-height: 1.5;
|
||||
padding: 4px 13px;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
input[type*='date'],
|
||||
input[type*='time'],
|
||||
.flatpickr-input[type='text'] {
|
||||
width: 50%;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
/* BUTTON STYLE SEE default-layout.lucius */
|
||||
|
||||
/* TEXTAREAS */
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
max-width: 600px;
|
||||
line-height: 1.5;
|
||||
color: #363636;
|
||||
background-color: #f3f3f3;
|
||||
padding: 4px 13px;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-base);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 2px;
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* SHARED STATE RELATED STYLES */
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
&:focus {
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
background-color: #f3f3f3;
|
||||
color: #7a7a7a;
|
||||
box-shadow: none;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
background-color: #f5f5f5;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
}
|
||||
|
||||
/* OPTIONS */
|
||||
|
||||
select {
|
||||
-webkit-appearance: menulist;
|
||||
}
|
||||
|
||||
select,
|
||||
option {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
padding: 4px 13px;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 2px;
|
||||
outline: 0;
|
||||
color: #363636;
|
||||
min-width: 250px;
|
||||
width: auto;
|
||||
background-color: #f3f3f3;
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05);
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
|
||||
select, option {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* FILE INPUT */
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-input__label {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: 10px 17px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.file-input__info {
|
||||
font-size: .9rem;
|
||||
font-style: italic;
|
||||
margin: 10px 0;
|
||||
color: var(--color-fontsec);
|
||||
}
|
||||
|
||||
.file-input__list {
|
||||
margin-left: 40px;
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* PREVIOUSLY UPLOADED FILES */
|
||||
|
||||
.file-uploads-label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.checkbox {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
55
frontend/src/utils/inputs/radio.sass
Normal file
55
frontend/src/utils/inputs/radio.sass
Normal file
@ -0,0 +1,55 @@
|
||||
// CUSTOM RADIO BOXES
|
||||
// Completely replaces native radiobox
|
||||
|
||||
.radio-group
|
||||
display: flex
|
||||
|
||||
.radio
|
||||
position: relative
|
||||
display: inline-block
|
||||
|
||||
[type='radio']
|
||||
position: fixed
|
||||
top: -1px
|
||||
left: -1px
|
||||
width: 1px
|
||||
height: 1px
|
||||
overflow: hidden
|
||||
|
||||
label
|
||||
display: block
|
||||
height: 34px
|
||||
min-width: 42px
|
||||
line-height: 34px
|
||||
text-align: center
|
||||
padding: 0 13px
|
||||
background-color: #f3f3f3
|
||||
box-shadow: inset 2px 1px 2px 1px rgba(50, 50, 50, 0.05)
|
||||
color: var(--color-font)
|
||||
cursor: pointer
|
||||
|
||||
\:checked + label
|
||||
background-color: var(--color-primary)
|
||||
color: var(--color-lightwhite)
|
||||
box-shadow: inset -2px -1px 2px 1px rgba(255, 255, 255, 0.15)
|
||||
|
||||
\:focus + label
|
||||
border-color: #3273dc
|
||||
box-shadow: 0 0 0.125em 0 rgba(50, 115, 220, 0.8)
|
||||
outline: 0
|
||||
|
||||
[disabled] + label
|
||||
pointer-events: none
|
||||
border: none
|
||||
opacity: 0.6
|
||||
filter: grayscale(1)
|
||||
|
||||
.radio:first-child
|
||||
label
|
||||
border-top-left-radius: 4px
|
||||
border-bottom-left-radius: 4px
|
||||
|
||||
.radio:last-child
|
||||
label
|
||||
border-top-right-radius: 4px
|
||||
border-bottom-right-radius: 4px
|
||||
@ -1,66 +0,0 @@
|
||||
/* CUSTOM RADIO BOXES */
|
||||
/* Completely replaces native radiobox */
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.radio {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
[type='radio'] {
|
||||
position: fixed;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
height: 34px;
|
||||
min-width: 42px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
padding: 0 13px;
|
||||
background-color: #f3f3f3;
|
||||
box-shadow: inset 2px 1px 2px 1px rgba(50, 50, 50, 0.05);
|
||||
color: var(--color-font);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:checked + label {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-lightwhite);
|
||||
box-shadow: inset -2px -1px 2px 1px rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
:focus + label {
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0.125em 0 rgba(50,115,220,0.8);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
[disabled] + label {
|
||||
pointer-events: none;
|
||||
border: none;
|
||||
opacity: 0.6;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.radio:first-child {
|
||||
label {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.radio:last-child {
|
||||
label {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { Datepicker } from '../form/datepicker';
|
||||
import './mass-input.scss';
|
||||
import './mass-input.sass';
|
||||
|
||||
const MASS_INPUT_CELL_SELECTOR = '.massinput__cell';
|
||||
const MASS_INPUT_ADD_CELL_SELECTOR = '.massinput__cell--add';
|
||||
|
||||
14
frontend/src/utils/mass-input/mass-input.sass
Normal file
14
frontend/src/utils/mass-input/mass-input.sass
Normal file
@ -0,0 +1,14 @@
|
||||
.massinput-list__wrapper, .massinput-list__cell
|
||||
display: grid
|
||||
grid: auto / auto 50px
|
||||
max-width: 600px
|
||||
grid-gap: 7px
|
||||
|
||||
.massinput-list__field
|
||||
grid-column: 1
|
||||
|
||||
.massinput-list__add, .massinput-list__delete
|
||||
grid-column: 2
|
||||
|
||||
.massinput-list__cell
|
||||
grid-column: 1 / 3
|
||||
@ -1,18 +0,0 @@
|
||||
.massinput-list__wrapper, .massinput-list__cell {
|
||||
display: grid;
|
||||
grid: auto / auto 50px;
|
||||
max-width: 600px;
|
||||
grid-gap: 7px;
|
||||
}
|
||||
|
||||
.massinput-list__field {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.massinput-list__add, .massinput-list__delete {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.massinput-list__cell {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './modal.scss';
|
||||
import './modal.sass';
|
||||
|
||||
const MODAL_HEADERS = {
|
||||
'Is-Modal': 'True',
|
||||
|
||||
103
frontend/src/utils/modal/modal.sass
Normal file
103
frontend/src/utils/modal/modal.sass
Normal file
@ -0,0 +1,103 @@
|
||||
@use "../../common" as *
|
||||
|
||||
.modals-wrapper
|
||||
position: fixed
|
||||
left: 0
|
||||
top: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
z-index: -1
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
&.modals-wrapper--open
|
||||
z-index: 200
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.modal
|
||||
position: relative
|
||||
display: none
|
||||
background-color: rgba(255, 255, 255, 1)
|
||||
min-width: 60vw
|
||||
max-width: 70vw
|
||||
min-height: 100px
|
||||
max-height: calc(100vh - 30px)
|
||||
border-radius: 2px
|
||||
z-index: -1
|
||||
color: var(--color-font)
|
||||
overflow: auto
|
||||
overscroll-behavior: contain
|
||||
pointer-events: none
|
||||
opacity: 0
|
||||
|
||||
&.modal--open
|
||||
display: flex
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
z-index: 200
|
||||
transition: opacity .2s .1s ease-in-out, transform .3s ease-in-out
|
||||
|
||||
@media (max-width: 1024px)
|
||||
.modal
|
||||
min-width: 80vw
|
||||
|
||||
@media (max-width: 768px)
|
||||
.modal
|
||||
min-width: 90vw
|
||||
|
||||
@media (max-width: 425px)
|
||||
.modal
|
||||
min-width: calc(100vw - 20px)
|
||||
|
||||
.modal__overlay
|
||||
position: fixed
|
||||
left: 0
|
||||
top: 0
|
||||
height: 100%
|
||||
width: 100%
|
||||
background-color: transparent
|
||||
z-index: -1
|
||||
transition: all .2s ease
|
||||
display: none
|
||||
|
||||
&.modal__overlay--open
|
||||
display: block
|
||||
z-index: 199
|
||||
opacity: 1
|
||||
background-color: rgba(0, 0, 0, 0.4)
|
||||
|
||||
.modal__trigger
|
||||
cursor: pointer
|
||||
|
||||
div.modal__trigger
|
||||
display: inline-block
|
||||
|
||||
.modal__trigger-label
|
||||
font-style: italic
|
||||
text-decoration: underline
|
||||
|
||||
.modal__closer
|
||||
position: absolute
|
||||
top: 20px
|
||||
right: 20px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
width: 30px
|
||||
height: 30px
|
||||
background-color: var(--color-darker)
|
||||
border-radius: 2px
|
||||
cursor: pointer
|
||||
z-index: 20
|
||||
|
||||
&::before
|
||||
@extend .fas
|
||||
|
||||
content: fa-content($fa-var-times)
|
||||
color: white
|
||||
|
||||
.modal__content
|
||||
margin: 20px 40px
|
||||
width: 100%
|
||||
@ -1,118 +0,0 @@
|
||||
.modals-wrapper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.modals-wrapper--open {
|
||||
z-index: 200;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
display: none;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
min-width: 60vw;
|
||||
max-width: 70vw;
|
||||
min-height: 100px;
|
||||
max-height: calc(100vh - 30px);
|
||||
border-radius: 2px;
|
||||
z-index: -1;
|
||||
color: var(--color-font);
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
&.modal--open {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
z-index: 200;
|
||||
transition:
|
||||
opacity .2s .1s ease-in-out,
|
||||
transform .3s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.modal {
|
||||
min-width: 80vw;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.modal {
|
||||
min-width: 90vw;
|
||||
}
|
||||
}
|
||||
@media (max-width: 425px) {
|
||||
.modal {
|
||||
min-width: calc(100vw - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
.modal__overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
z-index: -1;
|
||||
transition: all .2s ease;
|
||||
display: none;
|
||||
|
||||
&.modal__overlay--open {
|
||||
display: block;
|
||||
z-index: 199;
|
||||
opacity: 1;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.modal__trigger {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modal__trigger {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.modal__trigger-label {
|
||||
font-style: italic;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.modal__closer {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: var(--color-darker);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
|
||||
&::before {
|
||||
content: '\f00d';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
margin: 20px 40px;
|
||||
width: 100%;
|
||||
}
|
||||
48
frontend/src/utils/navbar/navbar.js
Normal file
48
frontend/src/utils/navbar/navbar.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './navbar.sass';
|
||||
|
||||
|
||||
export const LANGUAGE_SELECT_UTIL_SELECTOR = '[uw-language-select]';
|
||||
const LANGUAGE_SELECT_INITIALIZED_CLASS = 'language-select--initialized';
|
||||
|
||||
|
||||
@Utility({
|
||||
selector: LANGUAGE_SELECT_UTIL_SELECTOR,
|
||||
})
|
||||
export class LanguageSelectUtil {
|
||||
_element;
|
||||
checkbox;
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error('Language Select utility needs to be passed an element!');
|
||||
}
|
||||
|
||||
if (element.classList.contains(LANGUAGE_SELECT_INITIALIZED_CLASS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._element = element;
|
||||
this.checkbox = element.querySelector('#lang-checkbox');
|
||||
|
||||
window.addEventListener('click', event => this.close(event));
|
||||
|
||||
element.classList.add(LANGUAGE_SELECT_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
close(event) {
|
||||
if (!this._element.contains(event.target) && window.document.contains(event.target)) {
|
||||
this.checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const NavbarUtils = [
|
||||
LanguageSelectUtil,
|
||||
];
|
||||
228
frontend/src/utils/navbar/navbar.sass
Normal file
228
frontend/src/utils/navbar/navbar.sass
Normal file
@ -0,0 +1,228 @@
|
||||
.navbar-container
|
||||
position: relative
|
||||
|
||||
.navbar-shadow
|
||||
position: fixed
|
||||
right: 0
|
||||
top: 0
|
||||
height: var(--header-height-collapsed)
|
||||
width: 20px
|
||||
z-index: 50
|
||||
background-image: linear-gradient(to left, rgba(0, 0, 0, 0.4), transparent)
|
||||
transition: height 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
|
||||
@media (min-width: 768px)
|
||||
.navbar-shadow
|
||||
height: var(--header-height)
|
||||
|
||||
@media (min-width: 1025px)
|
||||
.navbar-shadow
|
||||
display: none
|
||||
|
||||
.navbar
|
||||
position: fixed
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
justify-content: flex-start
|
||||
right: 0
|
||||
top: 0
|
||||
left: var(--asidenav-width-xl)
|
||||
height: var(--header-height)
|
||||
background-color: var(--color-primary)
|
||||
color: white
|
||||
z-index: 20
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2)
|
||||
overflow: auto
|
||||
transition: all 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
|
||||
@media (max-width: 1199px)
|
||||
.navbar
|
||||
left: var(--asidenav-width-lg)
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar
|
||||
left: 0
|
||||
|
||||
// links
|
||||
.navbar__link-wrapper
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: flex-end
|
||||
align-items: center
|
||||
height: 80px
|
||||
min-width: 90px
|
||||
color: var(--color-lightwhite)
|
||||
transition: height 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
overflow: hidden
|
||||
cursor: pointer
|
||||
|
||||
.navbar__link-icon
|
||||
opacity: 0.7
|
||||
transition: opacity 0.2s ease
|
||||
margin-bottom: 7px
|
||||
|
||||
.navbar__link-label
|
||||
transition: opacity .2s ease
|
||||
padding: 2px 4px
|
||||
text-transform: uppercase
|
||||
font-weight: 600
|
||||
|
||||
@media (min-width: 769px)
|
||||
.navbar__link-wrapper
|
||||
border: 1px solid rgba(255, 255, 255, 0.7)
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar__link-wrapper
|
||||
box-shadow: none
|
||||
min-width: 0
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
.navbar__link-label
|
||||
padding: 0 7px
|
||||
|
||||
.navbar__link-icon
|
||||
transform: scale(0.65)
|
||||
margin-bottom: 0
|
||||
|
||||
// navbar list
|
||||
.navbar__list
|
||||
white-space: nowrap
|
||||
|
||||
+ .navbar__list
|
||||
margin-left: 12px
|
||||
|
||||
@media (min-width: 769px)
|
||||
.navbar__list:last-of-type
|
||||
padding-right: 40px
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar__list
|
||||
+ .navbar__list
|
||||
margin-left: 0
|
||||
padding-right: 40px
|
||||
|
||||
// list item
|
||||
.navbar__list-item
|
||||
position: relative
|
||||
transition: background-color .1s ease
|
||||
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper
|
||||
margin-left: 12px
|
||||
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item
|
||||
margin-left: 12px
|
||||
|
||||
@media (max-width: 500px)
|
||||
.navbar__list-item
|
||||
min-width: 60px
|
||||
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item
|
||||
margin-left: 0
|
||||
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper
|
||||
margin-left: 0
|
||||
|
||||
.navbar__list-left
|
||||
flex: 5
|
||||
padding-left: 40px
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar__list-left
|
||||
padding-left: 0
|
||||
|
||||
// "Favorites" list item, only visible on small screens and logged in
|
||||
.navbar__list-item
|
||||
&.navbar__list-item--favorite
|
||||
display: none
|
||||
|
||||
.navbar__list-item--favorite
|
||||
display: none
|
||||
background-color: var(--color-primary)
|
||||
|
||||
.logged-in
|
||||
.navbar__list
|
||||
li.navbar__list-item--favorite,
|
||||
.navbar__list-item--favorite
|
||||
display: inline-block
|
||||
|
||||
@media (min-width: 426px)
|
||||
.logged-in
|
||||
.navbar__list
|
||||
.navbar__list-item--favorite
|
||||
display: none !important
|
||||
|
||||
.navbar__list-item--active
|
||||
background-color: var(--color-lightwhite)
|
||||
color: var(--color-dark)
|
||||
|
||||
.navbar__link-wrapper
|
||||
color: var(--color-dark)
|
||||
|
||||
.navbar__list-item--active .navbar__link-wrapper
|
||||
color: var(--color-dark)
|
||||
|
||||
.navbar .navbar__list-item:not(.navbar__list-item--active):not(.navbar__list-item--favorite):hover .navbar__link-wrapper, #lang-checkbox:checked ~ * .navbar__link-wrapper
|
||||
background-color: var(--color-dark)
|
||||
color: var(--color-lightwhite)
|
||||
|
||||
.navbar__link-icon
|
||||
opacity: 1
|
||||
|
||||
// sticky state
|
||||
.navbar--sticky
|
||||
height: var(--header-height-collapsed)
|
||||
z-index: 100
|
||||
|
||||
.navbar__link-wrapper
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
.navbar__pushdown
|
||||
height: var(--header-height)
|
||||
transition: height 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
|
||||
|
||||
.navbar--sticky + .navbar__pushdown
|
||||
display: block
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
@media (max-width: 768px)
|
||||
.navbar,
|
||||
.navbar__pushdown
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
.navbar__link-wrapper
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
@media (max-height: 500px)
|
||||
.navbar,
|
||||
.navbar__pushdown
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
.navbar__link-wrapper
|
||||
height: var(--header-height-collapsed)
|
||||
|
||||
#lang-dropdown
|
||||
display: none
|
||||
position: fixed
|
||||
top: var(--header-height)
|
||||
right: 0
|
||||
min-width: 200px
|
||||
z-index: 10
|
||||
background-color: white
|
||||
border-radius: 2px
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3)
|
||||
|
||||
select
|
||||
display: block
|
||||
|
||||
button
|
||||
display: block
|
||||
width: 100%
|
||||
|
||||
#lang-checkbox:checked ~ #lang-dropdown
|
||||
display: block
|
||||
|
||||
@media (max-width: 768px)
|
||||
#lang-dropdown
|
||||
top: var(--header-height-collapsed)
|
||||
@ -1,5 +1,6 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './show-hide.scss';
|
||||
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
|
||||
import './show-hide.sass';
|
||||
|
||||
const SHOW_HIDE_LOCAL_STORAGE_KEY = 'SHOW_HIDE';
|
||||
const SHOW_HIDE_INITIALIZED_CLASS = 'show-hide--initialized';
|
||||
@ -15,6 +16,8 @@ export class ShowHide {
|
||||
_showHideId;
|
||||
_element;
|
||||
|
||||
_storageManager = new StorageManager(SHOW_HIDE_LOCAL_STORAGE_KEY, '1.0.0', { location: LOCATION.LOCAL });
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error('ShowHide utility cannot be setup without an element!');
|
||||
@ -41,9 +44,9 @@ export class ShowHide {
|
||||
}
|
||||
|
||||
if (this._showHideId) {
|
||||
let localStorageCollapsed = this._getLocalStorage()[this._showHideId];
|
||||
if (typeof localStorageCollapsed !== 'undefined') {
|
||||
collapsed = localStorageCollapsed;
|
||||
let storageCollapsed = this._storageManager.load(this._showHideId);
|
||||
if (typeof storageCollapsed !== 'undefined') {
|
||||
collapsed = storageCollapsed;
|
||||
}
|
||||
}
|
||||
this._element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS, collapsed);
|
||||
@ -70,18 +73,7 @@ export class ShowHide {
|
||||
const newState = this._element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS);
|
||||
|
||||
if (this._showHideId) {
|
||||
this._setLocalStorage(this._showHideId, newState);
|
||||
this._storageManager.save(this._showHideId, newState);
|
||||
}
|
||||
}
|
||||
|
||||
// maybe move these to a LocalStorageHelper?
|
||||
_setLocalStorage(id, state) {
|
||||
const lsData = this._getLocalStorage();
|
||||
lsData[id] = state;
|
||||
window.localStorage.setItem(SHOW_HIDE_LOCAL_STORAGE_KEY, JSON.stringify(lsData));
|
||||
}
|
||||
|
||||
_getLocalStorage() {
|
||||
return JSON.parse(window.localStorage.getItem(SHOW_HIDE_LOCAL_STORAGE_KEY)) || {};
|
||||
}
|
||||
}
|
||||
|
||||
44
frontend/src/utils/show-hide/show-hide.sass
Normal file
44
frontend/src/utils/show-hide/show-hide.sass
Normal file
@ -0,0 +1,44 @@
|
||||
$show-hide-toggle-size: 6px
|
||||
|
||||
.show-hide__toggle
|
||||
position: relative
|
||||
cursor: pointer
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-grey-lighter)
|
||||
cursor: pointer
|
||||
|
||||
.show-hide__toggle::before
|
||||
content: ''
|
||||
position: absolute
|
||||
width: $show-hide-toggle-size
|
||||
height: $show-hide-toggle-size
|
||||
left: -15px
|
||||
top: 50%
|
||||
color: var(--color-primary)
|
||||
border-right: 2px solid currentColor
|
||||
border-top: 2px solid currentColor
|
||||
transition: transform .2s ease
|
||||
transform: translateY(-50%) rotate(-45deg)
|
||||
|
||||
@media (max-width: 768px)
|
||||
left: auto
|
||||
right: 20px
|
||||
color: var(--color-font)
|
||||
|
||||
.show-hide__toggle--right::before
|
||||
left: auto
|
||||
right: 20px
|
||||
color: var(--color-font)
|
||||
|
||||
.show-hide--collapsed
|
||||
.show-hide__toggle::before
|
||||
transform: translateY(-50%) rotate(135deg)
|
||||
|
||||
& > :not(.show-hide__toggle)
|
||||
display: block
|
||||
height: 0
|
||||
margin: 0
|
||||
padding: 0
|
||||
max-height: 0
|
||||
overflow-y: hidden
|
||||
@ -1,54 +0,0 @@
|
||||
$show-hide-toggle-size: 6px;
|
||||
|
||||
.show-hide__toggle {
|
||||
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-grey-lighter);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.show-hide__toggle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: $show-hide-toggle-size;
|
||||
height: $show-hide-toggle-size;
|
||||
left: -15px;
|
||||
top: 50%;
|
||||
color: var(--color-primary);
|
||||
border-right: 2px solid currentColor;
|
||||
border-top: 2px solid currentColor;
|
||||
transition: transform .2s ease;
|
||||
transform: translateY(-50%) rotate(-45deg);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
color: var(--color-font);
|
||||
}
|
||||
}
|
||||
|
||||
.show-hide__toggle--right::before {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
color: var(--color-font);
|
||||
}
|
||||
|
||||
.show-hide--collapsed {
|
||||
|
||||
.show-hide__toggle::before {
|
||||
transform: translateY(-50%) rotate(135deg);
|
||||
}
|
||||
|
||||
:not(.show-hide__toggle) {
|
||||
display: block;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import './tabber.scss';
|
||||
import './tabber.sass';
|
||||
|
||||
(function($) {
|
||||
|
||||
|
||||
35
frontend/src/utils/tabber/tabber.sass
Normal file
35
frontend/src/utils/tabber/tabber.sass
Normal file
@ -0,0 +1,35 @@
|
||||
.tab-group
|
||||
border-top: 2px solid #dcdcdc
|
||||
padding-top: 30px
|
||||
|
||||
.tab-group-openers
|
||||
display: flex
|
||||
justify-content: stretch
|
||||
line-height: 40px
|
||||
font-size: 14px
|
||||
margin-bottom: 40px
|
||||
|
||||
.tab-opener
|
||||
display: inline-block
|
||||
flex: 1
|
||||
text-align: center
|
||||
padding: 0 13px
|
||||
margin: 0 2px
|
||||
background-color: var(--color-dark)
|
||||
color: white
|
||||
font-size: 16px
|
||||
text-transform: uppercase
|
||||
font-weight: 600
|
||||
transition: all .1s ease
|
||||
border-bottom: 5px solid rgba(100, 100, 100, 0.2)
|
||||
|
||||
.tab-opener:not(.tab-visible):hover
|
||||
cursor: pointer
|
||||
background-color: transparent
|
||||
color: rgb(52, 48, 58)
|
||||
border-bottom-color: grey
|
||||
|
||||
.tab-opener.tab-visible
|
||||
background-color: transparent
|
||||
color: rgb(52, 48, 58)
|
||||
border-bottom-color: var(--color-primary)
|
||||
@ -1,39 +0,0 @@
|
||||
.tab-group {
|
||||
border-top: 2px solid #dcdcdc;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.tab-group-openers {
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
line-height: 40px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.tab-opener {
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 0 13px;
|
||||
margin: 0 2px;
|
||||
background-color: var(--color-dark);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
transition: all .1s ease;
|
||||
border-bottom: 5px solid rgba(100, 100, 100, 0.2);
|
||||
}
|
||||
.tab-opener:not(.tab-visible):hover {
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
color: rgb(52, 48, 58);
|
||||
border-bottom-color: grey;
|
||||
}
|
||||
|
||||
.tab-opener.tab-visible {
|
||||
background-color: transparent;
|
||||
color: rgb(52, 48, 58);
|
||||
border-bottom-color: var(--color-primary);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './tooltips.scss';
|
||||
import './tooltips.sass';
|
||||
|
||||
// empty 'shell' to be able to load styles
|
||||
@Utility({
|
||||
|
||||
92
frontend/src/utils/tooltips/tooltips.sass
Normal file
92
frontend/src/utils/tooltips/tooltips.sass
Normal file
@ -0,0 +1,92 @@
|
||||
.tooltip
|
||||
position: relative
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
|
||||
&:hover .tooltip__content
|
||||
display: inline-block
|
||||
|
||||
.tooltip__handle
|
||||
color: var(--color-light)
|
||||
height: 1.5rem
|
||||
line-height: 1.5rem
|
||||
font-size: 1.2rem
|
||||
display: inline-block
|
||||
text-align: center
|
||||
margin: 0 10px
|
||||
cursor: default
|
||||
position: relative
|
||||
|
||||
&::before
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
top: 50%
|
||||
left: 50%
|
||||
transform: translate(-50%, -50%)
|
||||
font-size: 15px
|
||||
|
||||
&.tooltip__handle.urgency__success
|
||||
color: var(--color-success)
|
||||
|
||||
&.tooltip__handle.urgency__warning
|
||||
color: var(--color-warning)
|
||||
|
||||
&.tooltip__handle.urgency__error
|
||||
color: var(--color-error)
|
||||
|
||||
&:hover
|
||||
color: var(--color-dark)
|
||||
|
||||
&.tooltip__handle.urgency__success
|
||||
color: var(--color-success-dark)
|
||||
|
||||
&.tooltip__handle.urgency__warning
|
||||
color: var(--color-warning-dark)
|
||||
|
||||
&.tooltip__handle.urgency__error
|
||||
color: var(--color-error-dark)
|
||||
|
||||
.tooltip.tooltip__inline
|
||||
.tooltip__handle
|
||||
height: 1.0rem
|
||||
line-height: 1.0rem
|
||||
font-size: 1.0rem
|
||||
|
||||
.tooltip__content
|
||||
position: absolute
|
||||
display: none
|
||||
top: -10px
|
||||
transform: translateY(-100%)
|
||||
left: 3px
|
||||
width: 275px
|
||||
z-index: 10
|
||||
background-color: #fafafa
|
||||
border-radius: 4px
|
||||
padding: 13px 17px
|
||||
box-shadow: 0 0 20px 4px rgba(0, 0, 0, 0.1)
|
||||
|
||||
&::after
|
||||
content: ''
|
||||
width: 16px
|
||||
height: 16px
|
||||
background-color: #fafafa
|
||||
transform: rotate(45deg)
|
||||
position: absolute
|
||||
left: 10px
|
||||
bottom: -8px
|
||||
|
||||
@media (max-width: 768px)
|
||||
.tooltip
|
||||
display: block
|
||||
margin-top: 10px
|
||||
|
||||
.tooltip__content
|
||||
left: 3px
|
||||
right: 3px
|
||||
width: auto
|
||||
|
||||
// fix font color when used in tableheaders
|
||||
th .tooltip__content
|
||||
color: var(--color-font)
|
||||
font-weight: normal
|
||||
@ -1,109 +0,0 @@
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover .tooltip__content {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip__handle {
|
||||
color: var(--color-light);
|
||||
height: 1.5rem;
|
||||
line-height: 1.5rem;
|
||||
font-size: 1.2rem;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
margin: 0 10px;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&.tooltip__handle.urgency__success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
&.tooltip__handle.urgency__warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
&.tooltip__handle.urgency__error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-dark);
|
||||
|
||||
&.tooltip__handle.urgency__success {
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
&.tooltip__handle.urgency__warning {
|
||||
color: var(--color-warning-dark);
|
||||
}
|
||||
&.tooltip__handle.urgency__error {
|
||||
color: var(--color-error-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip.tooltip__inline {
|
||||
.tooltip__handle {
|
||||
height: 1.0rem;
|
||||
line-height: 1.0rem;
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip__content {
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: -10px;
|
||||
transform: translateY(-100%);
|
||||
left: 3px;
|
||||
width: 275px;
|
||||
z-index: 10;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
padding: 13px 17px;
|
||||
box-shadow: 0 0 20px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #fafafa;
|
||||
transform: rotate(45deg);
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.tooltip {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
|
||||
.tooltip__content {
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* fix font color when used in tableheaders */
|
||||
th .tooltip__content {
|
||||
color: var(--color-font);
|
||||
font-weight: normal;
|
||||
}
|
||||
@ -10,6 +10,8 @@ import { MassInput } from './mass-input/mass-input';
|
||||
import { Modal } from './modal/modal';
|
||||
import { Tooltip } from './tooltips/tooltips';
|
||||
import { CourseTeaser } from './course-teaser/course-teaser';
|
||||
import { NavbarUtils } from './navbar/navbar';
|
||||
import { HideColumns } from './hide-columns/hide-columns';
|
||||
|
||||
export const Utils = [
|
||||
Alerts,
|
||||
@ -25,4 +27,6 @@ export const Utils = [
|
||||
ShowHide,
|
||||
Tooltip,
|
||||
CourseTeaser,
|
||||
...NavbarUtils,
|
||||
HideColumns,
|
||||
];
|
||||
|
||||
5
frontend/vendor/fontawesome.css
vendored
5
frontend/vendor/fontawesome.css
vendored
File diff suppressed because one or more lines are too long
2
frontend/vendor/main.js
vendored
2
frontend/vendor/main.js
vendored
@ -1,2 +0,0 @@
|
||||
import './fontawesome.css';
|
||||
import './datetime.css';
|
||||
@ -44,7 +44,7 @@ module.exports = function(config) {
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(css|scss)$/i,
|
||||
test: /\.(css|scss|sass)$/i,
|
||||
loader:'null-loader',
|
||||
}
|
||||
]
|
||||
|
||||
3
messages/button/en.msg
Normal file
3
messages/button/en.msg
Normal file
@ -0,0 +1,3 @@
|
||||
AmbiguousButtons: Multiple active submit buttons
|
||||
WrongButtonValue: Submit button has wrong value
|
||||
MultipleButtonValues: Submit button has multiple values
|
||||
@ -1,5 +1,6 @@
|
||||
CampusIdentPlaceholder: Vorname.Nachname@campus.lmu.de
|
||||
CampusIdent: Campus-Kennung
|
||||
CampusPassword: Passwort
|
||||
CampusPasswordPlaceholder: Passwort
|
||||
CampusSubmit: Abschicken
|
||||
CampusInvalidCredentials: Ungültige Logindaten
|
||||
6
messages/campus/en.msg
Normal file
6
messages/campus/en.msg
Normal file
@ -0,0 +1,6 @@
|
||||
CampusIdentPlaceholder: First.Last@campus.lmu.de
|
||||
CampusIdent: Campus account
|
||||
CampusPassword: Password
|
||||
CampusPasswordPlaceholder: Password
|
||||
CampusSubmit: Send
|
||||
CampusInvalidCredentials: Invalid login
|
||||
@ -1,2 +1,3 @@
|
||||
DummyIdent: Nutzer-Kennung
|
||||
DummyIdent: Identifikation
|
||||
DummyIdentPlaceholder: Identifikation
|
||||
DummyNoFormData: Keine Formulardaten empfangen
|
||||
3
messages/dummy/en.msg
Normal file
3
messages/dummy/en.msg
Normal file
@ -0,0 +1,3 @@
|
||||
DummyIdent: Identification
|
||||
DummyIdentPlaceholder: Identification
|
||||
DummyNoFormData: No form data received
|
||||
@ -1,4 +1,4 @@
|
||||
FilesSelected: Dateien ausgewählt
|
||||
SelectFile: Datei auswählen
|
||||
SelectFiles: Datei(en) auswählen
|
||||
AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicke uns gerne eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für deine Hilfe!
|
||||
AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicken Sie uns bitte eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für Ihre Hilfe!
|
||||
4
messages/frontend/en.msg
Normal file
4
messages/frontend/en.msg
Normal file
@ -0,0 +1,4 @@
|
||||
FilesSelected: Files selected
|
||||
SelectFile: Select file
|
||||
SelectFiles: Select file(s)
|
||||
AsyncFormFailure: Something went wrong, we are sorry. If this error occurs again, please let us know by clicking the Support button in the upper right corner. Thank you very much!
|
||||
@ -1,2 +1,4 @@
|
||||
PWHashIdent: Identifikation
|
||||
PWHashPassword: Passwort
|
||||
PWHashIdentPlaceholder: Identifikation
|
||||
PWHashPassword: Passwort
|
||||
PWHashPasswordPlaceholder: Passwort
|
||||
4
messages/pw-hash/en.msg
Normal file
4
messages/pw-hash/en.msg
Normal file
@ -0,0 +1,4 @@
|
||||
PWHashIdent: Identification
|
||||
PWHashIdentPlaceholder: Identification
|
||||
PWHashPassword: Password
|
||||
PWHashPasswordPlaceholder: Password
|
||||
@ -1,5 +1,7 @@
|
||||
PrintDebugForStupid name@Text: Debug message "#{name}"
|
||||
|
||||
Logo: Uni2work
|
||||
|
||||
BtnSubmit: Senden
|
||||
BtnAbort: Abbrechen
|
||||
BtnDelete: Löschen
|
||||
@ -15,9 +17,13 @@ BtnHijack: Sitzung übernehmen
|
||||
BtnSave: Speichern
|
||||
PressSaveToSave: Änderungen werden erst durch Drücken des Knopfes "Speichern" gespeichert.
|
||||
BtnHandIn: Abgeben
|
||||
BtnCandidatesInfer: Studienfachzuordnung automatisch lernen
|
||||
BtnCandidatesDeleteConflicts: Konflikte löschen
|
||||
BtnCandidatesDeleteAll: Alle Beobachtungen löschen
|
||||
BtnNameCandidatesInfer: Studienfach-Namens-Zuordnung automatisch lernen
|
||||
BtnNameCandidatesDeleteConflicts: Namenskonflikte löschen
|
||||
BtnNameCandidatesDeleteAll: Alle Namens-Beobachtungen löschen
|
||||
BtnParentCandidatesInfer: Unterstudiengangs-Zuordnung automatisch lernen
|
||||
BtnParentCandidatesDeleteAll: Alle Unterstudiengangs-Beobachtungen löschen
|
||||
BtnStandaloneCandidatesDeleteAll: Alle Einzelstudiengangs-Beobachtungen löschen
|
||||
BtnStandaloneCandidatesDeleteRedundant: Redundante Einzelstudiengangs-Beobachtungen löschen
|
||||
BtnResetTokens: Authorisierungs-Tokens invalidieren
|
||||
BtnLecInvAccept: Annehmen
|
||||
BtnLecInvDecline: Ablehnen
|
||||
@ -30,6 +36,7 @@ Aborted: Abgebrochen
|
||||
Remarks: Hinweise
|
||||
Registered: Angemeldet
|
||||
RegisteredSince: Angemeldet seit
|
||||
NotRegistered: Sie sind zu diesem Kurs nicht angemeldet.
|
||||
Registration: Anmeldung
|
||||
RegisterFrom: Anmeldungen von
|
||||
RegisterTo: Anmeldungen bis
|
||||
@ -70,9 +77,9 @@ Term: Semester
|
||||
TermPlaceholder: W/S + vierstellige Jahreszahl
|
||||
|
||||
TermStartDay: Erster Tag
|
||||
TermStartDayTooltip: Üblicherweise immer 1.April oder 1.Oktober
|
||||
TermStartDayTooltip: Üblicherweise immer 1. April oder 1. Oktober
|
||||
TermEndDay: Letzter Tag
|
||||
TermEndDayTooltip: Üblicherweise immer 30.September oder 31.März
|
||||
TermEndDayTooltip: Üblicherweise immer 30. September oder 31. März
|
||||
TermHolidays: Feiertage
|
||||
TermHolidayPlaceholder: Feiertag
|
||||
TermLectureStart: Beginn Vorlesungen
|
||||
@ -97,6 +104,7 @@ CourseRegistration: Kursanmeldung
|
||||
CourseRegisterOpen: Anmeldung möglich
|
||||
CourseRegisterOk: Erfolgreich zum Kurs angemeldet
|
||||
CourseDeregisterOk: Erfolgreich vom Kurs abgemeldet
|
||||
CourseApply: Zum Kurs bewerben
|
||||
CourseApplyOk: Erfolgreich zum Kurs beworben
|
||||
CourseRetractApplyOk: Bewerbung zum Kurs erfolgreich zurückgezogen
|
||||
CourseDeregisterLecturerTip: Wenn Sie den Teilnehmer vom Kurs abmelden kann es sein, dass sie Zugriff auf diese Daten verlieren
|
||||
@ -108,8 +116,8 @@ CourseTutorial: Tutorium
|
||||
CourseSecretWrong: Falsches Passwort
|
||||
CourseSecret: Zugangspasswort
|
||||
CourseEditOk tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} wurde erfolgreich geändert.
|
||||
CourseNewDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester.
|
||||
CourseEditDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester.
|
||||
CourseNewDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester und Institut.
|
||||
CourseEditDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester und Institut.
|
||||
FFSheetName: Name
|
||||
TermCourseListHeading tid@TermId: Kursübersicht #{tid}
|
||||
TermSchoolCourseListHeading tid@TermId school@SchoolName: Kursübersicht #{tid} für #{school}
|
||||
@ -127,7 +135,7 @@ CourseMembersCountLimited n@Int max@Int: #{n}/#{max}
|
||||
CourseMembersCountOf n@Int mbNum@IntMaybe: #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"}
|
||||
CourseName: Name
|
||||
CourseDescription: Beschreibung
|
||||
CourseDescriptionTip: Beliebiges HTML-Markup ist gestattet
|
||||
CourseDescriptionTip: Beliebiges Html-Markup ist gestattet
|
||||
CourseHomepageExternal: Externe Homepage
|
||||
CourseShorthand: Kürzel
|
||||
CourseShorthandUnique: Muss nur innerhalb Institut und Semester eindeutig sein. Wird verbatim in die Url der Kursseite übernommen.
|
||||
@ -141,15 +149,20 @@ CourseRegisterToTip: Darf auch unbegrenzt offen bleiben
|
||||
CourseDeregisterUntilTip: Abmeldung ist ab "Anmeldungen von" bis zu diesem Zeitpunkt erlaubt. Die Abmeldung darf auch unbegrenzt erlaubt bleiben.
|
||||
CourseFilterSearch: Volltext-Suche
|
||||
CourseFilterRegistered: Registriert
|
||||
CourseFilterNone: Egal
|
||||
CourseFilterNone: —
|
||||
BoolIrrelevant: —
|
||||
CourseDeleteQuestion: Wollen Sie den unten aufgeführten Kurs wirklich löschen?
|
||||
CourseDeleted: Kurs gelöscht
|
||||
CourseUserTutorials: Angemeldete Tutorien
|
||||
CourseUserNote: Notiz
|
||||
CourseUserNoteTooltip: Nur für Dozenten dieses Kurses einsehbar
|
||||
CourseUserNoteTooltip: Nur für Verwalter dieses Kurses einsehbar
|
||||
CourseUserNoteSaved: Notizänderungen gespeichert
|
||||
CourseUserNoteDeleted: Teilnehmernotiz gelöscht
|
||||
CourseUserRegister: Zum Kurs anmelden
|
||||
CourseUserDeregister: Vom Kurs abmelden
|
||||
CourseUsersDeregistered count@Int64: #{show count} Teilnehmer vom Kurs abgemeldet
|
||||
CourseUserRegisterTutorial: Zu einem Tutorium anmelden
|
||||
CourseUsersTutorialRegistered count@Int64: #{show count} Teilnehmer zum Tutorium angemeldet
|
||||
CourseUserSendMail: Mitteilung verschicken
|
||||
TutorialUserDeregister: Vom Tutorium Abmelden
|
||||
TutorialUserSendMail: Mitteilung verschicken
|
||||
@ -180,7 +193,7 @@ CourseApplication: Bewerbung
|
||||
CourseApplicationIsParticipant: Kursteilnehmer
|
||||
|
||||
CourseApplicationExists: Sie haben sich bereits für diesen Kurs beworben
|
||||
CourseApplicationInvalidAction: Angegeben Aktion kann nicht durchgeführt werden
|
||||
CourseApplicationInvalidAction: Angegebene Aktion kann nicht durchgeführt werden
|
||||
CourseApplicationCreated csh@CourseShorthand: Erfolgreich zu #{csh} beworben
|
||||
CourseApplicationEdited csh@CourseShorthand: Bewerbung zu #{csh} erfolgreich angepasst
|
||||
CourseApplicationNotEdited csh@CourseShorthand: Bewerbung zu #{csh} hat sich nicht verändert
|
||||
@ -267,6 +280,8 @@ SheetSubmissionMode: Abgabe-Modus
|
||||
SheetExercise: Aufgabenstellung
|
||||
SheetHint: Hinweis
|
||||
SheetHintFrom: Hinweis ab
|
||||
SheetHintFromPlaceholder: Datum, sonst nur für Korrektoren
|
||||
SheetSolutionFromPlaceholder: Datum, sonst nur für Korrektoren
|
||||
SheetSolution: Lösung
|
||||
SheetSolutionFrom: Lösung ab
|
||||
SheetMarking: Hinweise für Korrektoren
|
||||
@ -278,9 +293,15 @@ SheetDescription: Hinweise für Teilnehmer
|
||||
SheetGroup: Gruppenabgabe
|
||||
SheetVisibleFrom: Sichtbar für Teilnehmer ab
|
||||
SheetVisibleFromTip: Ohne Datum nie sichtbar und keine Abgabe möglich; nur für unfertige Blätter leer lassen, deren Bewertung/Fristen sich noch ändern können
|
||||
SheetActiveFrom: Beginn Abgabezeitraum
|
||||
SheetActiveFromTip: Download der Aufgabenstellung erst ab diesem Datum möglich
|
||||
SheetActiveTo: Ende Abgabezeitraum
|
||||
SheetActiveFrom: Aktiv ab/Beginn Abgabezeitraum
|
||||
SheetActiveFromParticipant: Beginn Abgabezeitraum
|
||||
SheetActiveFromParticipantNoSubmit: Herausgabe der Aufgabestellung
|
||||
SheetActiveFromTip: Download der Aufgabenstellung und Abgabe erst ab diesem Datum möglich. Ohne Datum keine Abgabe und keine Herausgabe der Aufgabenstellung
|
||||
SheetActiveFromUnset: Nie
|
||||
SheetActiveTo: Aktiv bis/Ende Abgabezeitraum
|
||||
SheetActiveToParticipant: Ende Abgabezeitraum
|
||||
SheetActiveToTip: Abgabe nur bis zu diesem Datum möglich. Ohne Datum unbeschränkte Abgabe möglich (soweit gefordert).
|
||||
SheetActiveToUnset: Nie
|
||||
SheetHintFromTip: Ohne Datum nie für Teilnehmer sichtbar, Korrektoren können diese Dateien immer herunterladen
|
||||
SheetSolutionFromTip: Ohne Datum nie für Teilnehmer sichtbar, Korrektoren können diese Dateien immer herunterladen
|
||||
SheetMarkingTip: Hinweise zur Korrektur, sichtbar nur für Korrektoren
|
||||
@ -311,6 +332,7 @@ SubmissionEditHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetNa
|
||||
CorrectionHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName cid@CryptoFileNameSubmission: #{tid}-#{ssh}-#{csh} #{sheetName}: Korrektur
|
||||
SubmissionMembers: Abgebende
|
||||
SubmissionMember: Abgebende(r)
|
||||
CosubmittorTip: Einladungen per E-Mail erhalten genau jene Adressen, für die nicht gesichert werden kann, dass sie mit der dahinter stehenden Person schon einmal für diesen Kurs abgegeben haben. Wenn eine angegebene Adresse einer Person zugeordnet werden kann, mit der Sie in diesem Kurs schon einmal zusammen abgegeben haben, wird der Name der Person angezeigt und die Abgabe erfolgt sofort auch im Namen jener Person.
|
||||
SubmissionArchive: Zip-Archiv der Abgabedatei(en)
|
||||
SubmissionFile: Datei zur Abgabe
|
||||
SubmissionFiles: Abgegebene Dateien
|
||||
@ -322,6 +344,8 @@ NoOpenSubmissions: Keine unkorrigierten Abgaben vorhanden
|
||||
SubmissionsDeleteQuestion n@Int: Wollen Sie #{pluralDE n "die unten aufgeführte Abgabe" "die unten aufgeführten Abgaben"} wirklich löschen?
|
||||
SubmissionsDeleted n@Int: #{pluralDE n "Abgabe gelöscht" "Abgaben gelöscht"}
|
||||
|
||||
SubmissionDeleteCosubmittorsWarning n@Int: Bei #{pluralDE n "der oben aufgeführte Abgabe" "einer der oben aufgeführten Abgaben"} gibt es, außer Ihnen, noch weitere Mitabgeber. Stellen Sie sicher, dass sie Abgaben nur in Absprache mit Ihren Mitabgebern löschen oder verlassen Sie die Abgabe, indem Sie sich selbst aus der Liste der Abgebenden entfernen!
|
||||
|
||||
SubmissionGroupName: Gruppenname
|
||||
|
||||
CorrectionsTitle: Zugewiesene Korrekturen
|
||||
@ -329,6 +353,8 @@ CourseCorrectionsTitle: Korrekturen für diesen Kurs
|
||||
CorrectorsHead sheetName@SheetName: Korrektoren für #{sheetName}
|
||||
CorrectorAssignTitle: Korrektor zuweisen
|
||||
|
||||
CorrectionsGrade: Korrekturen eintragen
|
||||
|
||||
MaterialName: Name
|
||||
MaterialType: Art
|
||||
MaterialTypePlaceholder: Folien, Code, Beispiel, ...
|
||||
@ -366,6 +392,8 @@ UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig.
|
||||
UnauthorizedTokenInvalid: Ihr Authorisierungs-Token konnte nicht verarbeitet werden.
|
||||
UnauthorizedTokenInvalidRoute: Ihr Authorisierungs-Token ist auf dieser Unterseite nicht gültig.
|
||||
UnauthorizedTokenInvalidAuthority: Ihr Authorisierungs-Token basiert auf den Rechten eines Nutzers, der nicht mehr existiert.
|
||||
UnauthorizedTokenInvalidAuthorityGroup: Ihr Authorisierungs-Token basiert auf den Rechten einer Gruppe von Nutzern, die nicht mehr existiert.
|
||||
UnauthorizedTokenInvalidAuthorityValue: Ihr Authorisierungs-Token basiert auf Rechten, deren Spezifikation nicht interpretiert werden konnte.
|
||||
UnauthorizedToken404: Authorisierungs-Tokens können nicht auf Fehlerseiten ausgewertet werden.
|
||||
UnauthorizedSiteAdmin: Sie sind kein System-weiter Administrator.
|
||||
UnauthorizedSchoolAdmin: Sie sind nicht als Administrator für dieses Institut eingetragen.
|
||||
@ -377,12 +405,15 @@ UnauthorizedLecturer: Sie sind nicht als Veranstalter für diese Veranstaltung e
|
||||
UnauthorizedAllocationLecturer: Sie sind nicht als Veranstalter für eine Veranstaltung dieser Zentralanmeldung eingetragen.
|
||||
UnauthorizedCorrector: Sie sind nicht als Korrektor für diese Veranstaltung eingetragen.
|
||||
UnauthorizedSheetCorrector: Sie sind nicht als Korrektor für dieses Übungsblatt eingetragen.
|
||||
UnauthorizedExamCorrector: Sie sind nicht als Korrektor für diese Prüfung eingetragen.
|
||||
UnauthorizedCorrectorAny: Sie sind nicht als Korrektor für eine Veranstaltung eingetragen.
|
||||
UnauthorizedRegistered: Sie sind nicht als Teilnehmer für diese Veranstaltung registriert.
|
||||
UnauthorizedAllocationRegistered: Sie sind nicht als Teilnehmer für diese Zentralanmeldung registriert.
|
||||
UnauthorizedExamResult: Sie haben keine Ergebnisse in dieser Prüfung.
|
||||
UnauthorizedParticipant: Angegebener Benutzer ist nicht als Teilnehmer dieser Veranstaltung registriert.
|
||||
UnauthorizedCourseNewsParticipant: Sie sind kein Teilnehmer dieser Veranstaltung.
|
||||
UnauthorizedParticipantSelf: Sie sind kein Teilnehmer dieser Veranstaltung.
|
||||
UnauthorizedApplicant: Angegebener Benutzer hat sich nicht für diese Veranstaltung beworben.
|
||||
UnauthorizedApplicantSelf: Sie sind kein Bewerber für diese Veranstaltung.
|
||||
UnauthorizedCourseTime: Dieses Kurs erlaubt momentan keine Anmeldungen.
|
||||
UnauthorizedAllocationRegisterTime: Diese Zentralanmeldung erlaubt momentan keine Bewerbungen.
|
||||
UnauthorizedSheetTime: Dieses Übungsblatt ist momentan nicht freigegeben.
|
||||
@ -409,6 +440,7 @@ UnknownAuthPredicate tag@String: Authorisierungsprädikat "#{tag}" ist dem Syste
|
||||
UnauthorizedRedirect: Die angeforderte Seite existiert nicht oder Sie haben keine Berechtigung, die angeforderte Seite zu sehen.
|
||||
UnauthorizedSelf: Aktueller Nutzer ist nicht angegebener Benutzer.
|
||||
UnauthorizedTutorialTutor: Sie sind nicht Tutor für dieses Tutorium.
|
||||
UnauthorizedTutorialTutorControl: Tutoren dürfen dieses Tutorium nicht editieren.
|
||||
UnauthorizedCourseTutor: Sie sind nicht Tutor für diesen Kurs.
|
||||
UnauthorizedTutor: Sie sind nicht Tutor.
|
||||
UnauthorizedTutorialRegisterGroup: Sie sind bereits in einem Tutorium mit derselben Registrierungs-Gruppe.
|
||||
@ -459,8 +491,7 @@ ProfileFor: Benutzereinstellungen für
|
||||
ProfileDataHeading: Gespeicherte Benutzerdaten
|
||||
InfoHeading: Informationen
|
||||
VersionHeading: Versionsgeschichte
|
||||
ImpressumHeading: Impressum
|
||||
DataProtHeading: Datenschutzerklärung
|
||||
LegalHeading: Rechtliche Informationen
|
||||
SystemMessageHeading: Uni2work Statusmeldung
|
||||
SystemMessageListHeading: Uni2work Statusmeldungen
|
||||
NotificationSettingsHeading displayName@Text: Benachrichtigungs-Einstellungen für #{displayName}
|
||||
@ -471,7 +502,7 @@ HomeOpenAllocations: Offene Zentralanmeldungen
|
||||
HomeUpcomingSheets: Anstehende Übungsblätter
|
||||
HomeUpcomingExams: Bevorstehende Prüfungen
|
||||
|
||||
NumCourses num@Int64: #{num} Kurse
|
||||
NumCourses num@Int64: #{num} #{pluralDE num "Kurs" "Kurse"}
|
||||
CloseAlert: Schliessen
|
||||
|
||||
Name: Name
|
||||
@ -512,15 +543,15 @@ NatField name@Text: #{name} muss eine natürliche Zahl sein!
|
||||
JSONFieldDecodeFailure aesonFailure@String: Konnte JSON nicht parsen: #{aesonFailure}
|
||||
SecretJSONFieldDecryptFailure: Konnte versteckte vertrauliche Daten nicht entschlüsseln
|
||||
|
||||
SubmissionsAlreadyAssigned num@Int64: #{num} Abgaben waren bereits einem Korrektor zugeteilt und wurden nicht verändert:
|
||||
SubmissionsAssignUnauthorized num@Int64: #{num} Abgaben können momentan nicht einem Korrektor zugeteilt werden (z.B. weil die Abgabe noch offen ist):
|
||||
UpdatedAssignedCorrectorSingle num@Int64: #{num} Abgaben wurden dem neuen Korrektor zugeteilt.
|
||||
SubmissionsAlreadyAssigned num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} waren bereits einem Korrektor zugeteilt und wurden nicht verändert:
|
||||
SubmissionsAssignUnauthorized num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} können momentan nicht einem Korrektor zugeteilt werden (z.B. weil die Abgabe noch offen ist):
|
||||
UpdatedAssignedCorrectorSingle num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} wurden dem neuen Korrektor zugeteilt.
|
||||
NoCorrector: Kein Korrektor
|
||||
RemovedCorrections num@Int64: Korrektur-Daten wurden von #{num} Abgaben entfernt.
|
||||
UpdatedAssignedCorrectorsAuto num@Int64: #{num} Abgaben wurden unter den Korrektoren aufgeteilt.
|
||||
RemovedCorrections num@Int64: Korrektur-Daten wurden von #{num} #{pluralDE num "Abgabe" "Abgaben"} entfernt.
|
||||
UpdatedAssignedCorrectorsAuto num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} wurden unter den Korrektoren aufgeteilt.
|
||||
UpdatedSheetCorrectorsAutoAssigned n@Int: #{n} #{pluralDE n "Abgabe wurde einem Korrektor" "Abgaben wurden Korrektoren"} zugteilt.
|
||||
UpdatedSheetCorrectorsAutoFailed n@Int: #{n} #{pluralDE n "Abgabe konnte" "Abgaben konnten"} nicht automatisch zugewiesen werden.
|
||||
CouldNotAssignCorrectorsAuto num@Int64: #{num} Abgaben konnten nicht automatisch zugewiesen werden:
|
||||
CouldNotAssignCorrectorsAuto num@Int64: #{num} #{pluralDE num "Abgabe konnte" "Abgaben konnten"} nicht automatisch zugewiesen werden:
|
||||
SelfCorrectors num@Int64: #{num} Abgaben wurden Abgebenden als eigenem Korrektor zugeteilt!
|
||||
|
||||
SubmissionOriginal: Original
|
||||
@ -597,20 +628,21 @@ RatingNotUnicode uexc@UnicodeException: Bewertungsdatei nicht in UTF-8 kodiert:
|
||||
RatingMissingSeparator: Präambel der Bewertungsdatei konnte nicht identifziert werden
|
||||
RatingMultiple: Bewertungen enthält mehrere Punktzahlen für die gleiche Abgabe
|
||||
RatingInvalid parseErr@Text: Bewertungspunktzahl konnte nicht als Zahl verstanden werden: #{parseErr}
|
||||
RatingFileIsDirectory: Unerwarteter Fehler: Datei ist unerlaubterweise ein Verzeichnis
|
||||
RatingFileIsDirectory: Bewertungsdatei ist unerlaubterweise ein Verzeichnis
|
||||
RatingNegative: Bewertungspunkte dürfen nicht negativ sein
|
||||
RatingExceedsMax: Bewertung übersteigt die erlaubte Maximalpunktzahl
|
||||
RatingNotExpected: Keine Bewertungen erlaubt
|
||||
RatingBinaryExpected: Bewertung muss 0 (=durchgefallen) oder 1 (=bestanden) sein
|
||||
RatingPointsRequired: Bewertung erfordert für dieses Blatt eine Punktzahl
|
||||
RatingFile: Bewertungsdatei
|
||||
|
||||
SubmissionSinkExceptionDuplicateFileTitle file@FilePath: Dateiname #{show file} kommt mehrfach im Zip-Archiv vor
|
||||
SubmissionSinkExceptionDuplicateRating: Mehr als eine Bewertung gefunden.
|
||||
SubmissionSinkExceptionRatingWithoutUpdate: Bewertung gefunden, es ist hier aber keine Bewertung der Abgabe möglich.
|
||||
SubmissionSinkExceptionForeignRating smid@CryptoFileNameSubmission: Fremde Bewertung für Abgabe #{toPathPiece smid} enthalten. Bewertungen müssen sich immer auf die gleiche Abgabe beziehen!
|
||||
SubmissionSinkExceptionInvalidFileTitleExtension file@FilePath: Dateiname #{show file} hat keine der für dieses Übungsblatt zulässigen Dateiendungen.
|
||||
SubmissionSinkExceptionInvalidFileTitleExtension file@FilePath: Dateiname „#{show file}“ hat keine der für dieses Übungsblatt zulässigen Dateiendungen.
|
||||
|
||||
MultiSinkException name@Text error@Text: In Abgabe #{name} ist ein Fehler aufgetreten: #{error}
|
||||
MultiSinkException name@Text error@Text: In Abgabe „#{name}“ ist ein Fehler aufgetreten: #{error}
|
||||
|
||||
NoTableContent: Kein Tabelleninhalt
|
||||
NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
|
||||
@ -620,7 +652,7 @@ AdminHeading: Administration
|
||||
AdminUserHeading: Benutzeradministration
|
||||
AdminUserRightsHeading: Benutzerrechte
|
||||
AdminUserAuthHeading: Benutzer-Authentifizierung
|
||||
AdminUserHeadingFor: Benuterprofil für
|
||||
AdminUserHeadingFor: Benutzerprofil für
|
||||
AdminFor: Administrator
|
||||
LecturerFor: Dozent
|
||||
LecturersFor: Dozenten
|
||||
@ -628,7 +660,6 @@ AssistantFor: Assistent
|
||||
AssistantsFor: Assistenten
|
||||
TutorsFor n@Int: #{pluralDE n "Tutor" "Tutoren"}
|
||||
CorrectorsFor n@Int: #{pluralDE n "Korrektor" "Korrektoren"}
|
||||
ForSchools n@Int: für #{pluralDE n "Institut" "Institute"}
|
||||
UserListTitle: Komprehensive Benutzerliste
|
||||
AccessRightsSaved: Berechtigungen erfolgreich verändert
|
||||
AccessRightsNotChanged: Berechtigungen wurden nicht verändert
|
||||
@ -640,7 +671,7 @@ DateTimeFormat: Datums- und Uhrzeitformat
|
||||
DateFormat: Datumsformat
|
||||
TimeFormat: Uhrzeitformat
|
||||
DownloadFiles: Dateien automatisch herunterladen
|
||||
DownloadFilesTip: Wenn gesetzt werden Dateien von Abgaben und Übungsblättern automatisch als Download behandelt, ansonsten ist das Verhalten browserabhängig (es können z.B. PDFs im Browser geöffnet werden).
|
||||
DownloadFilesTip: Wenn gesetzt werden Dateien automatisch als Download behandelt, ansonsten ist das Verhalten browserabhängig (es können z.B. PDFs im Browser geöffnet werden).
|
||||
WarningDays: Fristen-Vorschau
|
||||
WarningDaysTip: Wie viele Tage im Voraus sollen Fristen von Klausuren etc. auf Ihrer Startseite angezeigt werden?
|
||||
NotificationSettings: Erwünschte Benachrichtigungen
|
||||
@ -652,6 +683,10 @@ FormCosmetics: Oberfläche
|
||||
FormPersonalAppearance: Öffentliche Daten
|
||||
FormFieldRequiredTip: Gekennzeichnete Pflichtfelder sind immer auszufüllen
|
||||
|
||||
PersonalInfoExamAchievementsWip: Die Anzeige von Prüfungsergebnissen wird momentan an dieser Stelle leider noch nicht unterstützt.
|
||||
PersonalInfoOwnTutorialsWip: Die Anzeige von Tutorien, zu denen Sie als Tutor eingetragen sind wird momentan an dieser Stelle leider noch nicht unterstützt.
|
||||
PersonalInfoTutorialsWip: Die Anzeige von Tutorien, zu denen Sie angemeldet sind wird momentan an dieser Stelle leider noch nicht unterstützt.
|
||||
|
||||
ActiveAuthTags: Aktivierte Authorisierungsprädikate
|
||||
|
||||
InvalidDateTimeFormat: Ungültiges Datums- und Zeitformat, JJJJ-MM-TTTHH:MM[:SS] Format erwartet
|
||||
@ -683,6 +718,7 @@ CampusUserInvalidTitle: Konnte anhand des Campus-Logins keinen akademischen Tite
|
||||
CampusUserInvalidMatriculation: Konnte anhand des Campus-Logins keine Matrikelnummer ermitteln
|
||||
CampusUserInvalidFeaturesOfStudy parseErr@Text: Konnte anhand des Campus-Logins keine Studiengänge ermitteln
|
||||
CampusUserInvalidAssociatedSchools parseErr@Text: Konnte anhand des Campus-Logins keine Institute ermitteln
|
||||
CampusUserInvalidSex: Konnte anhand des Campus-Logins kein Geschlecht ermitteln
|
||||
|
||||
CorrectorNormal: Normal
|
||||
CorrectorMissing: Abwesend
|
||||
@ -717,8 +753,8 @@ UploadSpecificFileRequired: Zur Abgabe erforderlich
|
||||
|
||||
NoSubmissions: Keine Abgabe
|
||||
CorrectorSubmissions: Abgabe extern mit Pseudonym
|
||||
UserSubmissions: Direkte Abgabe
|
||||
BothSubmissions: Abgabe direkt & extern mit Pseudonym
|
||||
UserSubmissions: Direkte Abgabe in Uni2work
|
||||
BothSubmissions: Abgabe direkt in Uni2work & extern mit Pseudonym
|
||||
|
||||
SheetCorrectorSubmissionsTip: Abgabe erfolgt über ein Uni2work-externes Verfahren (zumeist in Papierform durch Einwurf) unter Angabe eines persönlichen Pseudonyms. Korrektoren können mithilfe des Pseudonyms später Korrekturergebnisse in Uni2work eintragen, damit Sie sie einsehen können.
|
||||
|
||||
@ -730,8 +766,16 @@ SubmissionUpdated: Abgabe erfolgreich ersetzt
|
||||
AdminFeaturesHeading: Studiengänge
|
||||
StudyTerms: Studiengänge
|
||||
StudyTerm: Studiengang
|
||||
NoStudyTermsKnown: Nicht eingeschrieben
|
||||
StudyFeatureInference: Studiengangschlüssel-Inferenz
|
||||
NoStudyTermsKnown: Keine Studiengänge bekannt
|
||||
StudyFeaturesDegrees: Abschlüsse
|
||||
StudyFeaturesTerms: Studiengänge
|
||||
StudyFeaturesNameCandidates: Namens-Kandidaten
|
||||
StudyFeaturesParentCandidates: Kandidaten für Unterstudiengänge
|
||||
StudyFeaturesStandaloneCandidates: Kandidaten für Einzelstudiengänge
|
||||
StudyFeatureNameInference: Studiengangschlüssel-Inferenz
|
||||
StudyFeatureParentInference: Unterstudiengang-Inferenz
|
||||
StudyFeatureInferenceNoNameConflicts: Keine Konflikte beobachtet
|
||||
StudyFeatureInferenceNameConflictsHeading: Studiengangseinträge mit beobachteten Konflikten
|
||||
StudyFeatureAge: Fachsemester
|
||||
StudyFeatureDegree: Abschluss
|
||||
FieldPrimary: Hauptfach
|
||||
@ -749,14 +793,20 @@ DegreeShort: Abschlusskürzel
|
||||
StudyTermsKey: Studiengangschlüssel
|
||||
StudyTermsName: Studiengang
|
||||
StudyTermsShort: Studiengangkürzel
|
||||
StudyTermsChangeSuccess: Zuordnung Abschlüsse aktualisiert
|
||||
StudyDegreeChangeSuccess: Zuordnung Studiengänge aktualisiert
|
||||
StudyCandidateIncidence: Anmeldevorgang
|
||||
AmbiguousCandidatesRemoved n@Int: #{show n} #{pluralDE n "uneindeutiger Kandidat" "uneindeutige Kandiaten"} entfernt
|
||||
RedundantCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Kandidat" "bekannte Kandiaten"} entfernt
|
||||
CandidatesInferred n@Int: #{show n} neue #{pluralDE n "Studiengangszuordnung" "Studiengangszuordnungen"} inferiert
|
||||
NoCandidatesInferred: Keine neuen Studienganszuordnungen inferiert
|
||||
AllIncidencesDeleted: Alle Beobachtungen wurden gelöscht.
|
||||
StudyTermsChangeSuccess: Zuordnung Studiengänge aktualisiert
|
||||
StudyDegreeChangeSuccess: Zuordnung Abschlüsse aktualisiert
|
||||
StudyCandidateIncidence: Synchronisation
|
||||
AmbiguousNameCandidatesRemoved n@Int: #{show n} #{pluralDE n "uneindeutiger Kandidat" "uneindeutige Kandiaten"} entfernt
|
||||
RedundantNameCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Namenskandidat" "bekannte Namenskandiaten"} entfernt
|
||||
RedundantParentCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Elternkandidat" "bekannte Elternkandiaten"} entfernt
|
||||
RedundantStandaloneCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Einzelstudiengangskandidat" "bekannte Einzelstudiengangskandiaten"} entfernt
|
||||
NameCandidatesInferred n@Int: #{show n} neue #{pluralDE n "Studiengangszuordnung" "Studiengangszuordnungen"} inferiert
|
||||
NoNameCandidatesInferred: Keine neuen Studienganszuordnungen inferiert
|
||||
ParentCandidatesInferred n@Int: #{show n} #{pluralDE n "neuer Unterstudiengang" "neue Unterstudiengänge"} inferiert
|
||||
NoParentCandidatesInferred: Keine neuen Unterstudiengänge inferiert
|
||||
AllNameIncidencesDeleted: Alle Namens-Beobachtungen wurden gelöscht.
|
||||
AllParentIncidencesDeleted: Alle Unterstudiengang-Beobachtungen wurden gelöscht.
|
||||
AllStandaloneIncidencesDeleted: Alle Einzelstudiengang-Beobachtungen wurden gelöscht.
|
||||
IncidencesDeleted n@Int: #{show n} #{pluralDE n "Beobachtung" "Beobachtungen"} gelöscht
|
||||
StudyTermIsNew: Neu
|
||||
StudyFeatureConflict: Es wurden Konflikte in der Studiengang-Zuordnung gefunden
|
||||
@ -770,10 +820,27 @@ MailTestDateTime: Test der Datumsformattierung:
|
||||
|
||||
German: Deutsch
|
||||
GermanGermany: Deutsch (Deutschland)
|
||||
English: Englisch
|
||||
EnglishEurope: Englisch (Europa)
|
||||
|
||||
MailSubjectSubmissionRated csh@CourseShorthand: Ihre #{csh}-Abgabe wurde korrigiert
|
||||
MailSubmissionRatedIntro courseName@Text termDesc@Text: Ihre Abgabe im Kurs #{courseName} (#{termDesc}) wurde korrigiert.
|
||||
|
||||
MailSubjectSubmissionEdited csh@CourseShorthand shn@SheetName: Ihre Abgabe für #{shn} im Kurs #{csh} wurde verändert
|
||||
MailSubmissionEditedIntro coursen@CourseName shn@SheetName termDesc@Text displayName@Text: #{displayName} hat Ihre Abgabe für #{shn} im Kurs #{coursen} (#{termDesc}) verändert.
|
||||
|
||||
MailSubjectSubmissionUserCreated csh@CourseShorthand shn@SheetName: Sie wurden als Mitabgebender zu einer Abgabe für #{shn} im Kurs #{csh} hinzugefügt
|
||||
MailSubjectSubmissionUserCreatedOther displayName@Text csh@CourseShorthand shn@SheetName: Es wurde ein Mitabgebender zu einer Abgabe für #{shn} im Kurs #{csh} hinzugefügt
|
||||
|
||||
MailSubmissionUserCreatedIntro coursen@CourseName shn@SheetName termDesc@Text: Sie wurden als Mitabgebender zu einer Abgabe für #{shn} im Kurs #{coursen} (#{termDesc}) hinzugefügt.
|
||||
MailSubmissionUserCreatedOtherIntro displayName@UserDisplayName coursen@CourseName shn@SheetName termDesc@Text: #{displayName} wurde als Mitabgebender zu einer Abgabe für #{shn} im Kurs #{coursen} (#{termDesc}) hinzugefügt.
|
||||
|
||||
MailSubjectSubmissionUserDeleted csh@CourseShorthand shn@SheetName: Sie wurden als Mitabgebender von Ihrer Abgabe für #{shn} im Kurs #{csh} entfernt
|
||||
MailSubjectSubmissionUserDeletedOther displayName@Text csh@CourseShorthand shn@SheetName: Es wurde ein Mitabgebender von einer Abgabe für #{shn} im Kurs #{csh} entfernt
|
||||
|
||||
MailSubmissionUserDeletedIntro coursen@CourseName shn@SheetName termDesc@Text: Sie wurden als Mitabgebender von Ihrer Abgabe für #{shn} im Kurs #{coursen} (#{termDesc}) entfernt.
|
||||
MailSubmissionUserDeletedOtherIntro displayName@UserDisplayName coursen@CourseName shn@SheetName termDesc@Text: #{displayName} wurde als Mitabgebender von einer Abgabe für #{shn} im Kurs #{coursen} (#{termDesc}) entfernt.
|
||||
|
||||
MailSubjectSheetActive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} wurde herausgegeben
|
||||
MailSheetActiveIntro courseName@Text termDesc@Text sheetName@SheetName: Sie können nun #{sheetName} im Kurs #{courseName} (#{termDesc}) herunterladen.
|
||||
|
||||
@ -851,9 +918,9 @@ MailSubjectExamOfficeUserInvitation displayName@Text: Berücksichtigung von Prü
|
||||
MailSubjectPasswordReset: Uni2work-Passwort ändern bzw. setzen
|
||||
|
||||
SheetGrading: Bewertung
|
||||
SheetGradingPoints maxPoints@Points: #{maxPoints} Punkte
|
||||
SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{passingPoints} von #{maxPoints} Punkten
|
||||
SheetGradingPassBinary: Bestanden/Nicht Bestanden
|
||||
SheetGradingPoints maxPoints@Points: #{maxPoints} #{pluralDE maxPoints "Punkt" "Punkte"}
|
||||
SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{passingPoints} von #{maxPoints} #{pluralDE maxPoints "Punkt" "Punkten"}
|
||||
SheetGradingPassBinary: Bestanden/Nicht Bestanden
|
||||
SheetGradingInfo: "Bestanden nach Punkten" zählt sowohl zur maximal erreichbaren Gesamtpunktzahl also auch zur Anzahl der zu bestehenden Blätter.
|
||||
|
||||
SheetGradingCount': Anzahl
|
||||
@ -907,14 +974,17 @@ NotificationTriggerExamRegistrationSoonInactive: Ich kann mich bald nicht mehr f
|
||||
NotificationTriggerExamDeregistrationSoonInactive: Ich kann mich bald nicht mehr von einer Prüfung abmelden
|
||||
NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen
|
||||
NotificationTriggerAllocationStaffRegister: Ich kann Kurse bei einer neuen Zentralanmeldung eintragen
|
||||
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldung-Bewerbungen für einen meiner Kurse bewerten
|
||||
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldungs-Bewerbungen für einen meiner Kurse bewerten
|
||||
NotificationTriggerAllocationRegister: Ich kann mich bei einer neuen Zentralanmeldung bewerben
|
||||
NotificationTriggerAllocationOutdatedRatings: Zentralanmeldung-Bewerbungen für einen meiner Kurse wurden verändert, nachdem sie bewertet wurden
|
||||
NotificationTriggerAllocationUnratedApplications: Bewertungen zu Zentralanmeldung-Bewerbungen für einen meiner Kurse stehen aus
|
||||
NotificationTriggerAllocationOutdatedRatings: Zentralanmeldungs-Bewerbungen für einen meiner Kurse wurden verändert, nachdem sie bewertet wurden
|
||||
NotificationTriggerAllocationUnratedApplications: Bewertungen zu Zentralanmeldungs-Bewerbungen für einen meiner Kurse stehen aus
|
||||
NotificationTriggerAllocationResults: Plätze wurden für eine meiner Zentralanmeldungen verteilt
|
||||
NotificationTriggerExamOfficeExamResults: Ich kann neue Prüfungsergebnisse einsehen
|
||||
NotificationTriggerExamOfficeExamResultsChanged: Prüfungsergebnisse wurden verändert
|
||||
NotificationTriggerCourseRegistered: Ein Kursverwalter hat mich zu einem Kurs angemeldet
|
||||
NotificationTriggerSubmissionUserCreated: Ich wurde als Mitabgebender zu einer Übungsblatt-Abgabe hinzugefügt
|
||||
NotificationTriggerSubmissionEdited: Eine meiner Übungsblatt-Abgaben wurde verändert
|
||||
NotificationTriggerSubmissionUserDeleted: Ich wurde als Mitabgebender von einer Übungsblatt-Abgabe entfernt
|
||||
|
||||
NotificationTriggerKindAll: Für alle Benutzer
|
||||
NotificationTriggerKindCourseParticipant: Für Kursteilnehmer
|
||||
@ -927,8 +997,9 @@ NotificationTriggerKindExamOffice: Für das Prüfungsamt
|
||||
NotificationTriggerKindEvaluation: Für Vorlesungsumfragen
|
||||
NotificationTriggerKindAllocationStaff: Für Zentralanmeldungen (Dozenten)
|
||||
NotificationTriggerKindAllocationParticipant: Für Zentralanmeldungen
|
||||
NotificationTriggerKindSubmissionUser: Für Mitabgebende einer Übungsblatt-Abgabe
|
||||
|
||||
CorrCreate: Abgaben erstellen
|
||||
CorrCreate: Abgaben registrieren
|
||||
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
|
||||
InvalidPseudonym pseudonym@Text: Invalides Pseudonym "#{pseudonym}"
|
||||
InvalidPseudonymSubmissionIgnored oPseudonyms@Text iPseudonym@Text: Abgabe mit Pseudonymen „#{oPseudonyms}“ wurde ignoriert, da „#{iPseudonym}“ nicht automatisiert zu einem validen Pseudonym korrigiert werden konnte.
|
||||
@ -948,6 +1019,10 @@ SheetCreateExisting: Folgende Pseudonyme haben bereits abgegeben:
|
||||
CorrGrade: Korrekturen eintragen
|
||||
|
||||
UserAccountDeleted name@Text: Konto für #{name} wurde gelöscht!
|
||||
UserSubmissionsDeleted n@Int: #{tshow n} Abgaben wurden unwiderruflich gelöscht.
|
||||
UserGroupSubmissionsKept n@Int: #{tshow n} Gruppenabgaben verbleiben in der Datenbank, aber die Zuordnung zum Benutzer wurde gelöscht. Gruppenabgaben können dadurch zu Einzelabgaben werden, die dann mit dem letzten Benutzer gelöscht werden.
|
||||
UserSubmissionGroupsDeleted count@Int64: #{tshow count} benannte Abgabengruppen wurden gelöscht, da sie ohne den Nutzer leer wären.
|
||||
UserAccountDeleteWarning: Achtung, dies löscht den kompletten Benutzer unwiderruflich und mit allen assoziierten Daten aus der Datenbank. Prüfungsdaten müssen jedoch langfristig gespeichert bleiben!
|
||||
|
||||
HelpTitle : Hilfe
|
||||
HelpAnswer: Antworten an
|
||||
@ -1020,7 +1095,6 @@ EncodedSecretBoxCouldNotDecodeNonce: Konnte secretbox-nonce nicht dekodieren
|
||||
EncodedSecretBoxCouldNotOpenSecretBox: Konnte libsodium-secretbox nicht öffnen (Verschlüsselte Daten sind nicht authentisch)
|
||||
EncodedSecretBoxCouldNotDecodePlaintext aesonErr@String: Konnte Klartext nicht JSON-dekodieren: #{aesonErr}
|
||||
ErrMsgHeading: Fehlermeldung entschlüsseln
|
||||
ErrorCryptoIdMismatch: Verschlüsselte Id der Abgabe passte nicht zu anderen Daten
|
||||
|
||||
InvalidRoute: Konnte URL nicht interpretieren
|
||||
|
||||
@ -1028,8 +1102,11 @@ MenuOpenCourses: Kurse mit offener Registrierung
|
||||
MenuOpenAllocations: Aktive Zentralanmeldungen
|
||||
MenuHome: Aktuell
|
||||
MenuInformation: Informationen
|
||||
MenuImpressum: Impressum
|
||||
MenuDataProt: Datenschutz
|
||||
MenuLegal: Rechtliche Informationen
|
||||
MenuDataProt: Datenschutzerklärung
|
||||
MenuTermsUse: Nutzungsbedingungen
|
||||
MenuCopyright: Urheberrecht
|
||||
MenuImprint: Impressum
|
||||
MenuVersion: Versionsgeschichte
|
||||
MenuInstance: Instanz-Identifikation
|
||||
MenuHealth: Instanz-Zustand
|
||||
@ -1057,6 +1134,7 @@ MenuProfileData: Persönliche Daten
|
||||
MenuTermCreate: Neues Semester anlegen
|
||||
MenuCourseNew: Neuen Kurs anlegen
|
||||
MenuTermEdit: Semester editieren
|
||||
MenuTermCurrent: Aktuelles Semester
|
||||
MenuCorrection: Korrektur
|
||||
MenuCorrections: Korrekturen
|
||||
MenuCorrectionsOwn: Meine Korrekturen
|
||||
@ -1084,9 +1162,9 @@ MenuSheetClone: Als neues Übungsblatt klonen
|
||||
MenuCorrectionsUpload: Korrekturen hochladen
|
||||
MenuCorrectionsDownload: Offene Abgaben herunterladen
|
||||
MenuCorrectionsCreate: Abgaben registrieren
|
||||
MenuCorrectionsGrade: Abgaben online korrigieren
|
||||
MenuCorrectionsAssign: Zuteilung Korrekturen
|
||||
MenuCorrectionsAssignSheet name@Text: Zuteilung Korrekturen von #{name}
|
||||
MenuCorrectionsGrade: Korrekturen eintragen
|
||||
MenuCorrectionsAssign: Zuteilung der Korrekturen
|
||||
MenuCorrectionsAssignSheet name@Text: Zuteilung der Korrekturen von #{name}
|
||||
MenuAuthPreds: Authorisierungseinstellungen
|
||||
MenuTutorialDelete: Tutorium löschen
|
||||
MenuTutorialEdit: Tutorium editieren
|
||||
@ -1097,10 +1175,11 @@ MenuExamEdit: Bearbeiten
|
||||
MenuExamUsers: Teilnehmer
|
||||
MenuExamGrades: Prüfungsleistungen
|
||||
MenuExamAddMembers: Prüfungsteilnehmer hinzufügen
|
||||
MenuExamCorrect: Prüfungsergebnisse eintragen
|
||||
MenuExamOfficeExams: Prüfungen
|
||||
MenuExamOfficeFields: Fächer
|
||||
MenuExamOfficeUsers: Benutzer
|
||||
MenuLecturerInvite: Dozenten hinzufügen
|
||||
MenuLecturerInvite: Funktionäre hinzufügen
|
||||
MenuAllocationInfo: Hinweise zum Ablauf einer Zentralanmeldung
|
||||
MenuCourseApplicationsFiles: Dateien aller Bewerbungen
|
||||
MenuSchoolList: Institute
|
||||
@ -1110,6 +1189,62 @@ MenuCourseNewsEdit: Kursnachricht bearbeiten
|
||||
MenuCourseEventNew: Neuer Kurstermin
|
||||
MenuCourseEventEdit: Kurstermin bearbeiten
|
||||
|
||||
BreadcrumbSubmissionFile: Datei
|
||||
BreadcrumbSubmissionUserInvite: Einladung zur Abgabe
|
||||
BreadcrumbCryptoIDDispatch: CryptoID-Weiterleitung
|
||||
BreadcrumbCourseAppsFiles: Bewerbungsdateien
|
||||
BreadcrumbCourseNotes: Kursnotizen
|
||||
BreadcrumbHiWis: Korrektoren
|
||||
BreadcrumbMaterial: Material
|
||||
BreadcrumbSheet: Übungsblatt
|
||||
BreadcrumbTutorial: Tutorium
|
||||
BreadcrumbExam: Prüfung
|
||||
BreadcrumbApplicant: Bewerber
|
||||
BreadcrumbCourseRegister: Anmelden
|
||||
BreadcrumbCourseRegisterTemplate: Bewerbungsvorlagen
|
||||
BreadcrumbCourseFavourite: Favorisieren
|
||||
BreadcrumbCourse: Kurs
|
||||
BreadcrumbAllocationRegister: Teilnahme registrieren
|
||||
BreadcrumbAllocation: Zentralanmeldung
|
||||
BreadcrumbTerm: Semester
|
||||
BreadcrumbSchool: Institut
|
||||
BreadcrumbUser: Benutzer
|
||||
BreadcrumbStatic: Statische Resource
|
||||
BreadcrumbFavicon: Favicon
|
||||
BreadcrumbRobots: robots.txt
|
||||
BreadcrumbMetrics: Metriken
|
||||
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
|
||||
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen
|
||||
BreadcrumbFunctionaryInvite: Einladung zum Instituts-Funktionär
|
||||
BreadcrumbUserDelete: Nutzer-Account löschen
|
||||
BreadcrumbUserHijack: Nutzer-Sitzung übernehmen
|
||||
BreadcrumbSystemMessage: Statusmeldung
|
||||
BreadcrumbSubmission: Abgabe
|
||||
BreadcrumbCourseNews: Kursnachricht
|
||||
BreadcrumbCourseNewsDelete: Kursnachricht löschen
|
||||
BreadcrumbCourseEventDelete: Kurstermin löschen
|
||||
BreadcrumbProfile: Einstellungen
|
||||
BreadcrumbAllocationInfo: Ablauf einer Zentralanmeldung
|
||||
BreadcrumbCourseParticipantInvitation: Einladung zum Kursteilnehmer
|
||||
BreadcrumbMaterialArchive: Archiv
|
||||
BreadcrumbMaterialFile: Datei
|
||||
BreadcrumbSheetArchive: Dateien
|
||||
BreadcrumbSheetIsCorrector: Korrektor-Überprüfung
|
||||
BreadcrumbSheetPseudonym: Pseudonym
|
||||
BreadcrumbSheetCorrectorInvite: Einladung zum Korrektor
|
||||
BreadcrumbSheetFile: Datei
|
||||
BreadcrumbTutorialRegister: Anmelden
|
||||
BreadcrumbTutorInvite: Einladung zum Tutor
|
||||
BreadcrumbExamCorrectorInvite: Einladung zum Prüfungskorrektor
|
||||
BreadcrumbExamParticipantInvite: Einladung zum Prüfungsteilnehmer
|
||||
BreadcrumbExamRegister: Anmelden
|
||||
BreadcrumbExamCorrect: Eintragen von Prüfungsergebnissen
|
||||
BreadcrumbApplicationFiles: Bewerbungsdateien
|
||||
BreadcrumbCourseNewsArchive: Archiv
|
||||
BreadcrumbCourseNewsFile: Datei
|
||||
|
||||
TitleMetrics: Metriken
|
||||
|
||||
AuthPredsInfo: Um eigene Veranstaltungen aus Sicht der Teilnehmer anzusehen, können Veranstalter und Korrektoren hier die Prüfung ihrer erweiterten Berechtigungen temporär deaktivieren. Abgewählte Prädikate schlagen immer fehl. Abgewählte Prädikate werden also nicht geprüft um Zugriffe zu gewähren, welche andernfalls nicht erlaubt wären. Diese Einstellungen gelten nur temporär bis Ihre Sitzung abgelaufen ist, d.h. bis ihr Browser-Cookie abgelaufen ist. Durch Abwahl von Prädikaten kann man sich höchstens temporär aussperren.
|
||||
AuthPredsActive: Aktive Authorisierungsprädikate
|
||||
AuthPredsActiveChanged: Authorisierungseinstellungen für aktuelle Sitzung gespeichert
|
||||
@ -1122,7 +1257,9 @@ AuthTagDeprecated: Seite ist nicht überholt
|
||||
AuthTagDevelopment: Seite ist nicht in Entwicklung
|
||||
AuthTagLecturer: Nutzer ist Dozent
|
||||
AuthTagCorrector: Nutzer ist Korrektor
|
||||
AuthTagExamCorrector: Nutzer ist Klausurkorrektor
|
||||
AuthTagTutor: Nutzer ist Tutor
|
||||
AuthTagTutorControl: Tutoren haben Kontrolle über ihre Tutorium
|
||||
AuthTagTime: Zeitliche Einschränkungen sind erfüllt
|
||||
AuthTagStaffTime: Zeitliche Einschränkungen für Lehrbeteiligte sind erfüllt
|
||||
AuthTagAllocationTime: Zeitliche Einschränkungen durch Zentralanmeldung sind erfüllt
|
||||
@ -1132,6 +1269,7 @@ AuthTagTutorialRegistered: Nutzer ist Tutoriumsteilnehmer
|
||||
AuthTagExamRegistered: Nutzer ist Prüfungsteilnehmer
|
||||
AuthTagExamResult: Nutzer hat Prüfungsergebnisse
|
||||
AuthTagParticipant: Nutzer ist mit Kurs assoziiert
|
||||
AuthTagApplicant: Nutzer ist mit Bewerber zum Kurs
|
||||
AuthTagRegisterGroup: Nutzer ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe
|
||||
AuthTagCapacity: Kapazität ist ausreichend
|
||||
AuthTagEmpty: Kurs hat keine Teilnehmer
|
||||
@ -1164,7 +1302,7 @@ CommBody: Nachricht
|
||||
CommBodyTip: Das Eingabefeld akzeptiert derzeit ausschließlich Html. U.A. Zeilumbrüche werden dementsprechend ignoriert und müssen manuell mit <br> eingefügt werden.
|
||||
CommRecipients: Empfänger
|
||||
CommRecipientsTip: Sie selbst erhalten immer eine Kopie der Nachricht
|
||||
CommRecipientsList: Die an Sie selbst verschickte Kopie der Nachricht wird, zu Archivierungszwecken, eine vollständige Liste aller Empfänger enthalten. Die Empfängerliste wird im CSV-Format and die E-Mail angehängt. Andere Empfänger erhalten die Liste nicht. Bitte entfernen Sie dementsprechend den Anhang bevor Sie die E-Mail weiterleiten oder anderweitig mit Dritten teilen.
|
||||
CommRecipientsList: Die an Sie selbst verschickte Kopie der Nachricht wird, zu Archivierungszwecken, eine vollständige Liste aller Empfänger enthalten. Die Empfängerliste wird im CSV-Format an die E-Mail angehängt. Andere Empfänger erhalten die Liste nicht. Bitte entfernen Sie dementsprechend den Anhang bevor Sie die E-Mail weiterleiten oder anderweitig mit Dritten teilen.
|
||||
CommDuplicateRecipients n@Int: #{n} #{pluralDE n "doppelter" "doppelte"} Empfänger ignoriert
|
||||
CommSuccess n@Int: Nachricht wurde an #{n} Empfänger versandt
|
||||
CommUndisclosedRecipients: Verborgene Empfänger
|
||||
@ -1186,7 +1324,7 @@ RGTutorialParticipants: Tutorium-Teilnehmer
|
||||
|
||||
MultiSelectFieldTip: Mehrfach-Auswahl ist möglich (Umschalt bzw. Strg)
|
||||
MultiEmailFieldTip: Es sind mehrere, Komma-separierte, E-Mail-Addressen möglich
|
||||
EmailInvitationWarning: Dem System ist kein Nutzer mit dieser Addresse bekannt. Es wird eine Einladung per E-Mail versandt.
|
||||
EmailInvitationWarning: Diese Adresse konnte mit Ihren aktuellen Rechten keinem Uni2work-Benutzer zugeordnet werden (ggf. unter gewissen Einschränkungen). Es wird eine Einladung per E-Mail versandt.
|
||||
|
||||
LecturerInvitationAccepted lType@Text csh@CourseShorthand: Sie wurden als #{lType} für #{csh} eingetragen
|
||||
LecturerInvitationDeclined csh@CourseShorthand: Sie haben die Einladung, Kursverwalter für #{csh} zu werden, abgelehnt
|
||||
@ -1259,6 +1397,8 @@ ExceptionExists: Diese Ausnahme existiert bereits
|
||||
ExceptionNoOccurAt: Termin
|
||||
|
||||
TutorialType: Typ
|
||||
TutorialTypePlaceholder: Tutorium, Zentralübung, ...
|
||||
TutorialTypeTip: Dient nur der Information der Studierenden
|
||||
TutorialName: Bezeichnung
|
||||
TutorialParticipants: Teilnehmer
|
||||
TutorialCapacity: Kapazität
|
||||
@ -1273,9 +1413,11 @@ TutorialDeregisterUntil: Abmeldungen bis
|
||||
TutorialsHeading: Tutorien
|
||||
TutorialEdit: Bearbeiten
|
||||
TutorialDelete: Löschen
|
||||
TutorialTutorControlled: Tutoren dürfen Tutorium editieren
|
||||
TutorialTutorControlledTip: Sollen Tutoren beliebige Aspekte dieses Tutoriums (Name, Registrierungs-Gruppe, Raum, Zeit, andere Tutoren, ...) beliebig editieren dürfen?
|
||||
|
||||
CourseExams: Prüfungen
|
||||
CourseTutorials: Übungen
|
||||
CourseTutorials: Tutorien
|
||||
|
||||
ParticipantsN n@Int: #{n} Teilnehmer
|
||||
TutorialDeleteQuestion: Wollen Sie das unten aufgeführte Tutorium wirklich löschen?
|
||||
@ -1284,7 +1426,7 @@ TutorialDeleted: Tutorium gelöscht
|
||||
TutorialRegisteredSuccess tutn@TutorialName: Erfolgreich zum Tutorium #{tutn} angemeldet
|
||||
TutorialDeregisteredSuccess tutn@TutorialName: Erfolgreich vom Tutorium #{tutn} abgemeldet
|
||||
|
||||
TutorialNameTip: Muss eindeutig sein
|
||||
TutorialNameTip: Muss innerhalb des Kurses eindeutig sein
|
||||
TutorialCapacityNonPositive: Kapazität muss größer oder gleich null sein
|
||||
TutorialCapacityTip: Beschränkt wieviele Studenten sich zu diesem Tutorium anmelden können
|
||||
TutorialRegGroupTip: Studenten können sich in jeweils maximal einem Tutorium pro Registrierungs-Gruppe anmelden. Ist bei zwei oder mehr Tutorien keine Registrierungs-Gruppe gesetzt zählen diese als in verschiedenen Registrierungs-Gruppen
|
||||
@ -1292,6 +1434,9 @@ TutorialRoomPlaceholder: Raum
|
||||
TutorialTutors: Tutoren
|
||||
TutorialTutorAlreadyAdded: Ein Tutor mit dieser E-Mail ist bereits für dieses Tutorium eingetragen
|
||||
|
||||
OccurrenceNoneScheduled: (Noch) keine planmäßigen Termine
|
||||
OccurrenceNoneExceptions: (Noch) keine Termin-Ausnahmen
|
||||
|
||||
TutorialNew: Neues Tutorium
|
||||
|
||||
TutorialNameTaken tutn@TutorialName: Es existiert bereits anderes Tutorium mit Namen #{tutn}
|
||||
@ -1395,7 +1540,7 @@ ExamBonusRoundNonPositive: Vielfaches, auf das gerundet werden soll, muss positi
|
||||
ExamBonusRoundTip: Bonuspunkte werden kaufmännisch auf ein Vielfaches der angegeben Zahl gerundet.
|
||||
|
||||
ExamAutomaticOccurrenceAssignment: Automatische Termin- bzw. Raumzuteilung
|
||||
ExamAutomaticOccurrenceAssignmentTip: Sollen Prüfungsteilnehmer zum Zeitpunkt der Bekanntgabe der Raum- bzw. Terminzuteilung automatisch auf die zur Verfügung stehenden Räume bzw. Termine verteilt werden? Manuelle Umverteilung bzw. vorheriges Festlegen von Zuteilungen einzelner Teilnehmer ist trotzdem möglich.
|
||||
ExamAutomaticOccurrenceAssignmentTip: Sollen Prüfungsteilnehmer automatisch auf die zur Verfügung stehenden Räume bzw. Termine verteilt werden? Manuelle Umverteilung bzw. vorheriges Festlegen von Zuteilungen einzelner Teilnehmer ist trotzdem möglich.
|
||||
ExamOccurrenceRule: Verfahren
|
||||
ExamOccurrenceRuleParticipant: Termin- bzw. Raumzuteilungsverfahren
|
||||
ExamRoomManual': Keine automatische Zuteilung
|
||||
@ -1441,7 +1586,7 @@ ExamPartName: Titel
|
||||
ExamPartNameTip: Wird den Studierenden angezeigt
|
||||
ExamPartMaxPoints: Maximalpunktzahl
|
||||
ExamPartWeight: Gewichtung
|
||||
ExamPartWeightTip: Wird vor Anzeige oder Notenberechnung mit der erreichten Punktzahl und der Maximalpunktzahl multipliziert; Änderungen hier passen auch bestehende Korrekturergebnisse an
|
||||
ExamPartWeightTip: Wird vor Anzeige oder automatischen Notenberechnung mit der erreichten Punktzahl und der Maximalpunktzahl multipliziert; Änderungen hier passen also auch bestehende Korrekturergebnisse an (derart geänderte Noten müssen erneut manuell übernommen werden)
|
||||
ExamPartResultPoints: Erreichte Punkte
|
||||
|
||||
ExamNameTaken exam@ExamName: Es existiert bereits eine Prüfung mit Namen #{exam}
|
||||
@ -1511,6 +1656,8 @@ ExamUserMarkedSynchronised n@Int: #{n} #{pluralDE n "Prüfungsleistung" "Prüfun
|
||||
ExamOfficeExamUsersHeading: Prüfungsleistungen
|
||||
|
||||
CsvFile: CSV-Datei
|
||||
CsvImport: CSV-Import
|
||||
CsvExport: CSV-Export
|
||||
CsvModifyExisting: Existierende Einträge angleichen
|
||||
CsvAddNew: Neue Einträge einfügen
|
||||
CsvDeleteMissing: Fehlende Einträge entfernen
|
||||
@ -1553,6 +1700,7 @@ CsvColumnExamUserCourseNote: Notizen zum Teilnehmer
|
||||
|
||||
CsvColumnUserName: Voller Name des Teilnehmers
|
||||
CsvColumnUserMatriculation: Matrikelnummer des Teilnehmers
|
||||
CsvColumnUserSex: Geschlecht
|
||||
CsvColumnUserEmail: E-Mail Addresse des Teilnehmers
|
||||
CsvColumnUserStudyFeatures: Alle aktiven Studiendaten des Teilnehmers als Semikolon (;) separierte Liste
|
||||
CsvColumnUserField: Studienfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat
|
||||
@ -1560,6 +1708,7 @@ CsvColumnUserDegree: Abschluss, den der Teilnehmer im assoziierten Studienfach a
|
||||
CsvColumnUserSemester: Fachsemester des Teilnehmers im assoziierten Studienfach
|
||||
CsvColumnUserRegistration: Zeitpunkt der Anmeldung zum Kurs (ISO 8601)
|
||||
CsvColumnUserNote: Notizen zum Teilnehmer
|
||||
CsvColumnUserTutorial: Tutorien zu denen der Teilnehmer angemeldet ist, als Semikolon (;) separierte Liste. Für Registrierungs-Gruppen unter den Tutorien gibt es jeweils eine weitere Spalte. Die Registrierungs-Gruppen-Spalten enthalten jeweils maximal ein Tutorium pro Teilnehmer. Sind alle Tutorien in Registrierungs-Gruppen, so gibt es keine Spalte "tutorial".
|
||||
|
||||
CsvColumnExamOfficeExamUserOccurrenceStart: Prüfungstermin (ISO 8601)
|
||||
|
||||
@ -1663,6 +1812,7 @@ SchoolFunctionInvitationAccepted school@SchoolName renderedFunction@Text: #{rend
|
||||
AllocationActive: Aktiv
|
||||
AllocationName: Name
|
||||
AllocationAvailableCourses: Kurse
|
||||
AllocationApplication: Bewerbung
|
||||
AllocationAppliedCourses: Bewerbungen
|
||||
AllocationNumCoursesAvailableApplied available@Int applied@Int: Sie haben sich bisher für #{applied}/#{available} #{pluralDE applied "Kurs" "Kursen"} beworben
|
||||
AllocationTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName: #{termText} - #{ssh'}: #{allocation}
|
||||
@ -1762,7 +1912,9 @@ SchoolExamOffice: Prüfungsamt
|
||||
ApplicationEditTip: Während des Bewerbungszeitraums können eigene Bewerbungen beliebig angepasst und auch wieder zurückgezogen werden.
|
||||
|
||||
UserLdapSync: LDAP-Synchronisieren
|
||||
AllUsersLdapSync: Alle LDAP-Synchronisieren
|
||||
SynchroniseLdapUserQueued n@Int: LDAP-Synchronisation von #{n} #{pluralDE n "Benutzer" "Benutzern"} angestoßen
|
||||
SynchroniseLdapAllUsersQueued: LDAP-Synchronisation von allen Benutzern angestoßen
|
||||
UserHijack: Sitzung übernehmen
|
||||
|
||||
MailSubjectAllocationStaffRegister allocation@AllocationName: Sie können nun Kurse für die Zentralameldung „#{allocation}“ registrieren
|
||||
@ -1813,6 +1965,7 @@ AdminUserDisplayEmail: Anzeige-E-Mail
|
||||
AdminUserIdent: Identifikation
|
||||
AdminUserAuth: Authentifizierung
|
||||
AdminUserMatriculation: Matrikelnummer
|
||||
AdminUserSex: Geschlecht
|
||||
AuthKindLDAP: Campus-Kennung
|
||||
AuthKindPWHash: Uni2work-Kennung
|
||||
UserAdded: Benutzer erfolgreich angelegt
|
||||
@ -1858,7 +2011,10 @@ AcceptApplicationsSecondaryRandom: Zufällig
|
||||
AcceptApplicationsSecondaryTime: Nach Zeitpunkt der Bewerbung
|
||||
|
||||
CsvOptions: CSV-Optionen
|
||||
CsvOptionsTip: Diese Einstellungen betreffen nur den CSV-Export; beim Import werden die verwendeten Einstellungen automatisch ermittelt.
|
||||
CsvOptionsTip: Diese Einstellungen betreffen primär den CSV-Export; beim Import werden die meisten Einstellungen automatisch ermittelt. Als Zeichenkodierung wird beim Import die selbe Kodierung wie beim Export erwartet.
|
||||
CsvFormatOptions: Dateiformat
|
||||
CsvTimestamp: Zeitstempel
|
||||
CsvTimestampTip: Soll an den Namen jeder exportierten CSV-Datei ein Zeitstempel vorne angehängt werden?
|
||||
CsvPresetRFC: Standard-Konform (RFC 4180)
|
||||
CsvPresetExcel: Excel-Kompatibel
|
||||
CsvCustom: Benutzerdefiniert
|
||||
@ -1866,10 +2022,15 @@ CsvDelimiter: Trennzeichen
|
||||
CsvUseCrLf: Zeilenumbrüche
|
||||
CsvQuoting: Quoting
|
||||
CsvQuotingTip: Wann sollen Anführungszeichen (") um Felder platziert werden, um Interpretation von im Feld enthaltenen Zeichen als Trennzeichen zu verhindern?
|
||||
CsvEncoding: Encoding
|
||||
CsvEncodingTip: CSV-Dateien können in einer anderen Zeichenkodierung als dem vom System standardmäßig verwendeten UTF-8 exportiert werden. Beachten Sie dass es bei nicht-UTF-8 Zeichenkodierungen wmgl. zu Darstellungsproblemen mit Sonderzeichen kommt.
|
||||
CsvUTF8: UTF-8 (Unicode)
|
||||
CsvCP1252: Windows CP-1252 ("ANSI")
|
||||
CsvDelimiterNull: Null-Byte
|
||||
CsvDelimiterTab: Tabulator
|
||||
CsvDelimiterComma: Komma
|
||||
CsvDelimiterColon: Doppelpunkt
|
||||
CsvDelimiterSemicolon: Strichpunkt
|
||||
CsvDelimiterBar: Senkrechter Strich
|
||||
CsvDelimiterSpace: Leerzeichen
|
||||
CsvDelimiterUnitSep: Teilgruppentrennzeichen
|
||||
@ -1912,11 +2073,17 @@ CourseDeregistrationAllocationShouldLog: Selbstverschuldet
|
||||
CourseDeregistrationAllocationShouldLogTip: Falls der Platz des Studierenden, der abgemeldet wird, aus einer Zentralanmeldung stammt, ist vorgesehen einen permanenten Eintrag im System zu speichern, der den Studierenden u.U. bei zukünftigen Zentralanmeldungen benachteiligt. Als Kursverwalter haben Sie die Möglichkeit dies zu unterbinden, wenn der Studierende gute Gründe vorweisen kann, warum seine Abmeldung nicht selbstverschuldet ist.
|
||||
|
||||
MailSubjectAllocationResults allocation@AllocationName: Plätze für Zentralanmeldung „#{allocation}“ wurden verteilt
|
||||
AllocationResultsLecturer: Es wurden Plätze zugewiesen, wie folgt:
|
||||
AllocationResultLecturer csh@CourseShorthand count@Int64: #{count} Teilnehmer für #{csh}
|
||||
AllocationResultsLecturer: Im Rahmen der oben genannten Zentralanmeldung wurden Plätze zugewiesen, wie folgt:
|
||||
AllocationResultLecturer csh@CourseShorthand count@Int64 count2@Int64: #{count} Teilnehmer (von insgesamt #{count2}) für #{csh}
|
||||
AllocationResultLecturerAll csh@CourseShorthand count@Int64: #{count} Teilnehmer für #{csh}
|
||||
AllocationResultLecturerNone csh@CourseShorthand: Keine Teilnehmer für #{csh}
|
||||
AllocationResultsStudent: Sie haben Plätze erhalten in:
|
||||
AllocationNoResultsStudent: Sie haben leider keine Plätze erhalten.
|
||||
AllocationResultStudent csh@CourseShorthand: Sie haben einen Platz in #{csh} erhalten.
|
||||
AllocationResultsTip: Die folgenden Informationen entsprechen dem aktuellen Stand der Zentralanmeldung und können sich, z.B. durch die Verteilung von Plätzen an Nachrücker, noch ändern. Über zukünftige Änderungen, die Sie betreffen, werden Sie gesondert informiert.
|
||||
AllocationResultsStudentTip: Unten aufgeführt sind alle Plätze, die Sie im Rahmen der genannten Zentralanmeldung erhalten haben und von denen Sie seit dem weder abgemeldet wurden, noch sich selbst abgemeldet haben. Plätze, über die Sie ggf. bereits informiert wurden, können also erneut aufgeführt sein.
|
||||
AllocationResultStudentRegistrationTip: Sie sind zu oben genanntem Kurs in Uni2work angemeldet.
|
||||
AllocationResultsStudentRegistrationTip: Sie sind zu den oben genannten Kursen in Uni2work angemeldet.
|
||||
|
||||
FavouriteVisited: Kürzlich besucht
|
||||
FavouriteParticipant: Ihre Kurse
|
||||
@ -1938,4 +2105,87 @@ CourseEventDeleteQuestion: Wollen Sie den unten aufgeführten Termin wirklich l
|
||||
CourseEventDeleted: Kurstermin erfolgreich gelöscht
|
||||
|
||||
UserSimplifiedFeaturesOfStudyCsv: Vereinfachte Studiengangsdaten
|
||||
UserSimplifiedFeaturesOfStudyCsvTip: Sollen Abschluss, Studiengang und Semester zur einfacheren Verarbeitung als separate Spalten exportiert werden? Es wird dann nur jeweils das Fach exportiert, dass der Student bei der Anmeldung ausgewählt hat.
|
||||
UserSimplifiedFeaturesOfStudyCsvTip: Sollen Abschluss, Studiengang und Semester zur einfacheren Verarbeitung als separate Spalten exportiert werden? Es wird dann nur jeweils das Fach exportiert, dass der Student bei der Anmeldung ausgewählt hat.
|
||||
|
||||
Sex: Geschlecht
|
||||
SexNotKnown: Unbekannt
|
||||
SexMale: Männlich
|
||||
SexFemale: Weiblich
|
||||
SexNotApplicable: Keine Angabe
|
||||
|
||||
ShortSexNotKnown: unb.
|
||||
ShortSexMale: m
|
||||
ShortSexFemale: w
|
||||
ShortSexNotApplicable: k.A.
|
||||
|
||||
ShowSex: Geschlechter anderer Nutzer anzeigen
|
||||
ShowSexTip: Sollen in Kursteilnehmer-Tabellen u.Ä. die Geschlechter der Nutzer angezeigt werden?
|
||||
|
||||
StudySubTermsChildKey: Kind
|
||||
StudySubTermsChildName: Kindname
|
||||
StudySubTermsParentKey: Elter
|
||||
StudySubTermsParentName: Eltername
|
||||
StudyTermsDefaultDegree: Default Abschluss
|
||||
StudyTermsDefaultFieldType: Default Typ
|
||||
|
||||
MenuLanguage: Sprache
|
||||
LanguageChanged: Sprache erfolgreich geändert
|
||||
|
||||
ProfileCorrector: Korrektor
|
||||
ProfileCourses: Eigene Kurse
|
||||
ProfileCourseParticipations: Kursanmeldungen
|
||||
ProfileCourseExamResults: Prüfungsleistungen
|
||||
ProfileTutorials: Eigene Tutorien
|
||||
ProfileTutorialParticipations: Tutorien
|
||||
ProfileSubmissionGroups: Abgabegruppen
|
||||
ProfileSubmissions: Abgaben
|
||||
ProfileRemark: Hinweis
|
||||
ProfileGroupSubmissionDates: Bei Gruppenabgaben wird kein Datum angezeigt, wenn Sie die Gruppenabgabe nie selbst hochgeladen haben.
|
||||
ProfileCorrectorRemark: Die oberhalb angezeigte Tabelle zeigt nur prinzipielle Einteilungen als Korrektor zu einem Übungsblatt. Auch ohne Einteilung können Korrekturen einzeln zugewiesen werden, welche hier dann nicht aufgeführt werden.
|
||||
ProfileCorrections: Auflistung aller zugewiesenen Korrekturen
|
||||
|
||||
GroupSizeNotNatural: „Gruppengröße“ muss eine natürliche Zahl sein
|
||||
AmbiguousEmail: E-Mail Adresse nicht eindeutig
|
||||
CourseDescriptionPlaceholder: Bitte mindestens die Modulbeschreibung angeben
|
||||
CourseHomepageExternalPlaceholder: Optionale externe URL
|
||||
PointsPlaceholder: Punktezahl
|
||||
RFC1766: RFC1766-Sprachcode
|
||||
|
||||
TermShort: Kürzel
|
||||
TermCourseCount: Kurse
|
||||
TermStart: Semesteranfang
|
||||
TermEnd: Semesterende
|
||||
TermStartMustMatchName: Jahreszahl im Namenskürzel stimmt nicht mit Semesterbeginn überein.
|
||||
TermEndMustBeAfterStart: Semester darf nicht enden, bevor es beginnt.
|
||||
TermLectureEndMustBeAfterStart: Vorlesungszeit muss vor ihrem Ende anfgangen.
|
||||
TermStartMustBeBeforeLectureStart: Semester muss vor der Vorlesungszeit beginnen.
|
||||
TermEndMustBeAfterLectureEnd: Vorlesungszeit muss vor dem Semester enden.
|
||||
AdminPageEmpty: Diese Seite soll eine Übersichtsseite für Administratoren werden. Aktuell finden sich hier nur Links zu wichtigen Administrator-Funktionalitäten.
|
||||
HaveCorrectorAccess sheetName@SheetName: Sie haben Korrektor-Zugang zu #{original sheetName}.
|
||||
FavouritesPlaceholder: Anzahl Favoriten
|
||||
FavouritesNotNatural: Anzahl der Favoriten muss eine natürliche Zahl sein!
|
||||
FavouritesSemestersPlaceholder: Anzahl Semester
|
||||
FavouritesSemestersNotNatural: Anzahl der Favoriten-Semester muss eine natürliche Zahl sein!
|
||||
|
||||
ProfileTitle: Benutzereinstellungen
|
||||
|
||||
GlossaryTitle: Begriffsverzeichnis
|
||||
MenuGlossary: Begriffsverzeichnis
|
||||
|
||||
Applicant: Bewerber
|
||||
CourseParticipant: Kursteilnehmer
|
||||
Administrator: Administrator
|
||||
CsvFormat: CSV-Format
|
||||
ExerciseSheet: Übungsblatt
|
||||
DefinitionCourseEvents: Kurstermine
|
||||
DefinitionCourseNews: Kurs-Aktuelles
|
||||
Invitations: Einladungen
|
||||
SheetSubmission: Abgabe
|
||||
CommCourse: Kursmitteilung
|
||||
CommTutorial: Tutorium-Mitteilung
|
||||
Clone: Klonen
|
||||
Deficit: Defizit
|
||||
|
||||
MetricNoSamples: Keine Messwerte
|
||||
MetricName: Name
|
||||
MetricValue: Wert
|
||||
2189
messages/uniworx/en-eu.msg
Normal file
2189
messages/uniworx/en-eu.msg
Normal file
File diff suppressed because it is too large
Load Diff
@ -21,12 +21,15 @@ Allocation -- attributes with prefix staff- affect lecturers only, but are invis
|
||||
registerByCourse UTCTime Maybe -- course registration dates are ignored until this day has passed or always prohibited
|
||||
overrideDeregister UTCTime Maybe -- course deregistration enforced to be this date, i.e. students may disenrol from course after or never
|
||||
-- overrideVisible not needed, since courses are always visible
|
||||
fingerprint AllocationFingerprint Maybe
|
||||
matchingLog FileId Maybe
|
||||
TermSchoolAllocationShort term school shorthand -- shorthand must be unique within school and semester
|
||||
TermSchoolAllocationName term school name -- name must be unique within school and semester
|
||||
deriving Show Eq Ord Generic
|
||||
|
||||
AllocationMatching
|
||||
allocation AllocationId
|
||||
fingerprint AllocationFingerprint
|
||||
log FileId
|
||||
|
||||
AllocationCourse
|
||||
allocation AllocationId
|
||||
course CourseId
|
||||
|
||||
@ -64,5 +64,5 @@ ExamCorrector
|
||||
UniqueExamCorrector exam user
|
||||
ExamPartCorrector
|
||||
part ExamPartId
|
||||
corrector ExamCorrector
|
||||
corrector ExamCorrectorId
|
||||
UniqueExamPartCorrector part corrector
|
||||
@ -6,3 +6,9 @@ File
|
||||
content ByteString Maybe -- Nothing iff this is a directory
|
||||
modified UTCTime
|
||||
deriving Show Eq Generic
|
||||
|
||||
SessionFile
|
||||
user UserId
|
||||
reference SessionFileReference
|
||||
file FileId
|
||||
touched UTCTime
|
||||
@ -16,3 +16,10 @@ CronLastExec
|
||||
time UTCTime -- When was the job executed
|
||||
instance InstanceId -- Which uni2work-instance did the work
|
||||
UniqueCronLastExec job
|
||||
|
||||
|
||||
SentNotification
|
||||
content Value
|
||||
user UserId
|
||||
time UTCTime
|
||||
instance InstanceId
|
||||
@ -6,8 +6,8 @@ Sheet -- exercise sheet for a given course
|
||||
grouping SheetGroup -- May participants submit in groups of certain sizes?
|
||||
markingText Html Maybe -- Instructons for correctors, included in marking templates
|
||||
visibleFrom UTCTime Maybe -- Invisible to enrolled participants before
|
||||
activeFrom UTCTime -- Download of questions and submission is permitted afterwards
|
||||
activeTo UTCTime -- Submission is only permitted before
|
||||
activeFrom UTCTime Maybe -- Download of questions and submission is permitted afterwards
|
||||
activeTo UTCTime Maybe -- Submission is only permitted before
|
||||
hintFrom UTCTime Maybe -- Additional files are made available
|
||||
solutionFrom UTCTime Maybe -- Solution is made available
|
||||
submissionMode SubmissionMode -- Submission upload by students and/or through tutors?
|
||||
|
||||
@ -3,13 +3,14 @@ Tutorial json
|
||||
course CourseId
|
||||
type (CI Text) -- "Tutorium", "Zentralübung", ...
|
||||
capacity Int Maybe -- limit for enrolment in this tutorial
|
||||
room Text
|
||||
room Text Maybe
|
||||
time Occurrences
|
||||
regGroup (CI Text) Maybe -- each participant may register for one tutorial per regGroup
|
||||
registerFrom UTCTime Maybe
|
||||
registerTo UTCTime Maybe
|
||||
deregisterUntil UTCTime Maybe
|
||||
lastChanged UTCTime default=now()
|
||||
tutorControlled Bool default=false
|
||||
UniqueTutorial course name
|
||||
deriving Generic
|
||||
Tutor
|
||||
|
||||
@ -28,10 +28,12 @@ User json -- Each Uni2work user has a corresponding row in this table; create
|
||||
dateFormat DateTimeFormat "default='%d.%m.%Y'" -- preferred Date-only display format for user; user-defined
|
||||
timeFormat DateTimeFormat "default='%R'" -- preferred Time-only display format for user; user-defined
|
||||
downloadFiles Bool default=false -- Should files be opened in browser or downloaded? (users often oblivious that their browser has a setting for this)
|
||||
mailLanguages MailLanguages "default='[]'::jsonb" -- Preferred language for eMail; i18n not yet implemented; user-defined
|
||||
languages Languages Maybe -- Preferred language; user-defined
|
||||
notificationSettings NotificationSettings -- Bit-array for which events email notifications are requested by user; user-defined
|
||||
warningDays NominalDiffTime default=1209600 -- timedistance to pending deadlines for homepage infos
|
||||
csvOptions CsvOptions "default='{}'::jsonb"
|
||||
sex Sex Maybe
|
||||
showSex Bool default=false
|
||||
UniqueAuthentication ident -- Column 'ident' can be used as a row-key in this table
|
||||
UniqueEmail email -- Column 'email' can be used as a row-key in this table
|
||||
deriving Show Eq Ord Generic -- Haskell-specific settings for runtime-value representing a row in memory
|
||||
@ -53,11 +55,13 @@ StudyFeatures -- multiple entries possible for students pursuing several degree
|
||||
user UserId
|
||||
degree StudyDegreeId -- Abschluss, i.e. Master, Bachelor, etc.
|
||||
field StudyTermsId -- Fach, i.e. Informatics, Philosophy, etc.
|
||||
superField StudyTermsId Maybe
|
||||
type StudyFieldType -- Major or minor, i.e. Haupt-/Nebenfach
|
||||
semester Int
|
||||
updated UTCTime default=now() -- last update from LDAP
|
||||
valid Bool default=true -- marked as active in LDAP (students may switch, but LDAP never forgets)
|
||||
UniqueStudyFeatures user degree field type semester
|
||||
deriving Eq Show
|
||||
-- UniqueUserSubject ubuser degree field -- There exists a counterexample
|
||||
StudyDegree -- Studienabschluss
|
||||
key Int -- LMU-internal key
|
||||
@ -65,19 +69,43 @@ StudyDegree -- Studienabschluss
|
||||
name Text Maybe -- description given by LDAP
|
||||
Primary key -- column key is used as actual DB row key
|
||||
-- newtype Key StudyDegree = StudyDegreeKey' { unStudyDegreeKey :: Int }
|
||||
deriving Show
|
||||
deriving Eq Show
|
||||
StudyTerms -- Studiengang
|
||||
key Int -- LMU-internal key
|
||||
key Int -- standardised key
|
||||
shorthand Text Maybe -- admin determined shorthand
|
||||
name Text Maybe -- description given by LDAP
|
||||
defaultDegree StudyDegreeId Maybe
|
||||
defaultType StudyFieldType Maybe
|
||||
Primary key -- column key is used as actual DB row key
|
||||
-- newtype Key StudyTerms = StudyTermsKey' { unStudyTermsKey :: Int }
|
||||
deriving Show
|
||||
StudyTermCandidate -- No one at LMU is willing and able to tell us the meaning of the keys for StudyDegrees and StudyTerms.
|
||||
deriving Eq Ord Show
|
||||
StudySubTerms
|
||||
child StudyTermsId
|
||||
parent StudyTermsId
|
||||
UniqueStudySubTerms child parent
|
||||
StudyTermNameCandidate -- No one at LMU is willing and able to tell us the meaning of the keys for StudyDegrees and StudyTerms.
|
||||
-- Each LDAP login provides an unordered set of keys and an unordered set of plain text description with an unknown 1-1 correspondence.
|
||||
-- This table helps us to infer which key belongs to which plain text by recording possible combinations at login.
|
||||
-- If a login provides n keys and n plan texts, then n^2 rows with the same incidence are created, storing all combinations
|
||||
incidence TermCandidateIncidence -- random id, generated once per login to associate matching pairs
|
||||
key Int -- a possible key for the studyTermName
|
||||
key Int -- a possible key for the studyTermName or studySubTermName
|
||||
name Text -- studyTermName as plain text from LDAP
|
||||
deriving Show Eq Ord
|
||||
StudySubTermParentCandidate
|
||||
incidence TermCandidateIncidence
|
||||
key Int
|
||||
parent Int
|
||||
deriving Show Eq Ord
|
||||
StudyTermStandaloneCandidate
|
||||
incidence TermCandidateIncidence
|
||||
key Int
|
||||
deriving Show Eq Ord
|
||||
|
||||
UserGroupMember
|
||||
group UserGroupName
|
||||
user UserId
|
||||
primary Checkmark nullable
|
||||
|
||||
UniquePrimaryUserGroupMember group primary !force
|
||||
UniqueUserGroupMember group user
|
||||
|
||||
|
||||
6331
package-lock.json
generated
6331
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "uni2work",
|
||||
"version": "7.13.0",
|
||||
"version": "10.1.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"start": "run-p frontend:build:watch yesod:start",
|
||||
"start": "npm-run-all frontend:build --parallel \"frontend:build:watch\" \"yesod:start\"",
|
||||
"test": "run-s frontend:test yesod:test",
|
||||
"lint": "run-s frontend:lint yesod:lint",
|
||||
"build": "run-s frontend:build yesod:build",
|
||||
@ -20,8 +20,8 @@
|
||||
"frontend:lint": "eslint frontend/src",
|
||||
"frontend:test": "karma start --conf karma.conf.js",
|
||||
"frontend:test:watch": "karma start --conf karma.conf.js --single-run false",
|
||||
"frontend:build": "webpack",
|
||||
"frontend:build:watch": "webpack --watch",
|
||||
"frontend:build": "webpack --progress",
|
||||
"frontend:build:watch": "webpack --watch --progress",
|
||||
"prerelease": "./is-clean.sh && npm run test",
|
||||
"release": "standard-version -a",
|
||||
"postrelease": "git push --follow-tags origin master"
|
||||
@ -42,36 +42,40 @@
|
||||
"scripts": {
|
||||
"postbump": "./sync-versions.hs && git add -- package.yaml"
|
||||
},
|
||||
"commitUrlFormat": "https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/{{hash}}",
|
||||
"compareUrlFormat": "https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/{{previousTag}}...{{currentTag}}",
|
||||
"issueUrlFormat": "https://gitlab.cip.ifi.lmu.de/jost/UniWorX/issues/{{id}}",
|
||||
"userUrlFormat": "https://gitlab.cip.ifi.lmu.de/{{user}}"
|
||||
"commitUrlFormat": "https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/{{hash}}",
|
||||
"compareUrlFormat": "https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/{{previousTag}}...{{currentTag}}",
|
||||
"issueUrlFormat": "https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/{{id}}",
|
||||
"userUrlFormat": "https://gitlab2.rz.ifi.lmu.de/{{user}}"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.6.3",
|
||||
"@babel/core": "^7.6.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.6.0",
|
||||
"@babel/plugin-transform-runtime": "^7.6.2",
|
||||
"@babel/preset-env": "^7.6.3",
|
||||
"@babel/cli": "^7.7.5",
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
"@commitlint/config-conventional": "^8.2.0",
|
||||
"autoprefixer": "^9.6.4",
|
||||
"@fortawesome/fontawesome-pro": "^5.12.0",
|
||||
"autoprefixer": "^9.7.3",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.5",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^5.1.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"husky": "^2.7.0",
|
||||
"jasmine-core": "^3.5.0",
|
||||
"karma": "^4.3.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"karma": "^4.4.1",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-jasmine": "^2.0.1",
|
||||
@ -80,23 +84,33 @@
|
||||
"karma-webpack": "^3.0.5",
|
||||
"lint-staged": "^8.2.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"node-sass": "^4.12.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"null-loader": "^2.0.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass": "^1.23.7",
|
||||
"sass-loader": "^7.3.1",
|
||||
"semver": "^6.3.0",
|
||||
"standard-version": "^6.0.1",
|
||||
"style-loader": "^0.23.1",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"webpack": "^4.41.0",
|
||||
"webpack-cli": "^3.3.9"
|
||||
"terser-webpack-plugin": "^2.2.3",
|
||||
"typeface-roboto": "0.0.75",
|
||||
"typeface-source-sans-pro": "0.0.75",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-manifest-plugin": "^2.2.0",
|
||||
"webpack-plugin-hash-output": "^3.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"core-js": "^3.2.1",
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"@juggle/resize-observer": "^2.5.0",
|
||||
"core-js": "^3.4.8",
|
||||
"moment": "^2.24.0",
|
||||
"npm": "^6.12.0",
|
||||
"tail.datetime": "git+https://github.com/uni2work/tail.DateTime.git#master",
|
||||
"npm": "^6.13.4",
|
||||
"tail.datetime": "git+ssh://git@gitlab2.rz.ifi.lmu.de/uni2work/tail.DateTime.git#master",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
72
package.yaml
72
package.yaml
@ -1,41 +1,37 @@
|
||||
name: uniworx
|
||||
version: 7.13.0
|
||||
version: 10.1.0
|
||||
|
||||
dependencies:
|
||||
- base >=4.9.1.0 && <5
|
||||
- yesod >=1.6 && <1.7
|
||||
- yesod-core >=1.6 && <1.7
|
||||
- yesod-auth >=1.6 && <1.7
|
||||
- yesod-static >=1.6 && <1.7
|
||||
- yesod-form >=1.6 && <1.7
|
||||
- classy-prelude >=1.5 && <1.6
|
||||
- classy-prelude-conduit >=1.5 && <1.6
|
||||
- classy-prelude-yesod >=1.5 && <1.6
|
||||
- bytestring >=0.10 && <0.11
|
||||
- text >=0.11 && <2.0
|
||||
- persistent >=2.9 && <2.10
|
||||
- persistent-postgresql >=2.9 && <2.10
|
||||
- persistent-template >=2.5 && <2.9
|
||||
- persistent-qq >=2.9 && <2.10
|
||||
- base
|
||||
- yesod
|
||||
- yesod-core
|
||||
- yesod-auth
|
||||
- yesod-static
|
||||
- yesod-form
|
||||
- classy-prelude
|
||||
- classy-prelude-yesod
|
||||
- bytestring
|
||||
- text
|
||||
- persistent
|
||||
- persistent-postgresql
|
||||
- persistent-template
|
||||
- persistent-qq
|
||||
- template-haskell
|
||||
- shakespeare >=2.0 && <2.1
|
||||
- hjsmin >=0.1 && <0.3
|
||||
- monad-control >=0.3 && <1.1
|
||||
- wai-extra >=3.0 && <3.1
|
||||
- yaml >=0.11 && <0.12
|
||||
- http-conduit >=2.3 && <2.4
|
||||
- directory >=1.1 && <1.4
|
||||
- warp >=3.0 && <3.3
|
||||
- shakespeare
|
||||
- monad-control
|
||||
- wai-extra
|
||||
- yaml
|
||||
- http-conduit
|
||||
- directory
|
||||
- warp
|
||||
- data-default
|
||||
- aeson >=1.4 && <1.5
|
||||
- conduit >=1.0 && <2.0
|
||||
- conduit-combinators
|
||||
- monad-logger >=0.3 && <0.4
|
||||
- fast-logger >=2.2 && <2.5
|
||||
- wai-logger >=2.2 && <2.4
|
||||
- aeson
|
||||
- conduit
|
||||
- monad-logger
|
||||
- fast-logger
|
||||
- wai-logger
|
||||
- foreign-store
|
||||
- file-embed
|
||||
- safe
|
||||
- unordered-containers
|
||||
- containers
|
||||
- vector
|
||||
@ -67,8 +63,7 @@ dependencies:
|
||||
- binary
|
||||
- cereal
|
||||
- mtl
|
||||
- sandi
|
||||
- esqueleto
|
||||
- esqueleto >=3.1.0
|
||||
- mime-types
|
||||
- generic-deriving
|
||||
- blaze-html
|
||||
@ -78,8 +73,6 @@ dependencies:
|
||||
- uuid
|
||||
- exceptions
|
||||
- stm
|
||||
- stm-chans
|
||||
- stm-conduit
|
||||
- lens
|
||||
- MonadRandom
|
||||
- email-validate
|
||||
@ -140,6 +133,10 @@ dependencies:
|
||||
- retry
|
||||
- generic-lens
|
||||
- array
|
||||
- cookie
|
||||
- prometheus-client
|
||||
- prometheus-metrics-ghc
|
||||
- wai-middleware-prometheus
|
||||
|
||||
other-extensions:
|
||||
- GeneralizedNewtypeDeriving
|
||||
@ -233,10 +230,7 @@ executables:
|
||||
uniworx:
|
||||
main: main.hs
|
||||
source-dirs: app
|
||||
ghc-options:
|
||||
- -threaded
|
||||
- -rtsopts
|
||||
- -with-rtsopts=-N
|
||||
ghc-options: -threaded -rtsopts "-with-rtsopts=-N -T"
|
||||
dependencies:
|
||||
- uniworx
|
||||
when:
|
||||
|
||||
743
records.json
Normal file
743
records.json
Normal file
@ -0,0 +1,743 @@
|
||||
{
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/app.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/alerts/alerts.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/async-form/async-form.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/asidenav/asidenav.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/async-table/async-table-filter.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/async-table/async-table.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/show-hide/show-hide.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/form/form.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/inputs/inputs.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/inputs/radio.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/mass-input/mass-input.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/hide-columns/hide-columns.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/navbar/navbar.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/course-teaser/course-teaser.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/tooltips/tooltips.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/modal/modal.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/inputs/file-input.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/sass-loader/dist/cjs.js??ref--6-3!frontend/src/utils/inputs/checkbox.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--5-1!node_modules/postcss-loader/src/index.js??ref--5-2!frontend/src/utils/form/datepicker.css": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"modules": {
|
||||
"byIdentifier": {
|
||||
"multi frontend/src/polyfill.js frontend/src/main.js": 0
|
||||
},
|
||||
"usedIds": {
|
||||
"0": 0
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
},
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/app.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/alerts/alerts.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/asidenav/asidenav.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/show-hide/show-hide.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/async-form/async-form.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/async-table/async-table-filter.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/async-table/async-table.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/form/form.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/inputs.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/radio.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/mass-input/mass-input.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/hide-columns/hide-columns.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/navbar/navbar.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/tooltips/tooltips.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/course-teaser/course-teaser.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/modal/modal.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/file-input.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/checkbox.scss": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--5-1!node_modules/postcss-loader/src/index.js??ref--5-2!node_modules/resolve-url-loader/index.js??ref--5-3!frontend/src/utils/form/datepicker.css": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/app.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/async-form/async-form.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/alerts/alerts.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/asidenav/asidenav.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/form/form.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/hide-columns/hide-columns.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/navbar/navbar.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/course-teaser/course-teaser.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/modal/modal.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/tooltips/tooltips.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/mass-input/mass-input.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/radio.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/inputs.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/show-hide/show-hide.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/async-table/async-table.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/async-table/async-table-filter.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/file-input.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/inputs/checkbox.sass": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
15
routes
15
routes
@ -40,7 +40,8 @@
|
||||
/auth AuthR Auth getAuth !free
|
||||
|
||||
/favicon.ico FaviconR GET !free
|
||||
/robots.txt RobotsR GET !free
|
||||
/robots.txt RobotsR GET !free
|
||||
/metrics MetricsR GET
|
||||
|
||||
/ HomeR GET !free
|
||||
/users UsersR GET POST -- no tags, i.e. admins only
|
||||
@ -61,9 +62,9 @@
|
||||
/instance InstanceR GET !free
|
||||
/info InfoR GET !free
|
||||
/info/lecturer InfoLecturerR GET !lecturer
|
||||
/info/data DataProtR GET !free
|
||||
/info/legal LegalR GET !free
|
||||
/info/allocation InfoAllocationR GET !free
|
||||
/impressum ImpressumR GET !free
|
||||
/info/glossary GlossaryR GET !free
|
||||
/version VersionR GET !free
|
||||
|
||||
/help HelpR GET POST !free
|
||||
@ -73,6 +74,7 @@
|
||||
/user/authpreds AuthPredsR GET POST !free
|
||||
/user/set-display-email SetDisplayEmailR GET POST !free
|
||||
/user/csv-options CsvOptionsR GET POST !free
|
||||
/user/lang LangR POST !free
|
||||
|
||||
/exam-office ExamOfficeR !exam-office:
|
||||
/ EOExamsR GET
|
||||
@ -113,7 +115,7 @@
|
||||
/users CUsersR GET POST
|
||||
!/users/new CAddUserR GET POST !lecturerANDallocation-time
|
||||
!/users/invite CInviteR GET POST
|
||||
/users/#CryptoUUIDUser CUserR GET POST !lecturerANDparticipant
|
||||
/users/#CryptoUUIDUser CUserR GET POST !lecturerANDparticipant !lecturerANDapplicant
|
||||
/correctors CHiWisR GET
|
||||
/communication CCommR GET POST
|
||||
/notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access!
|
||||
@ -141,7 +143,6 @@
|
||||
/invite SInviteR GET POST !ownerANDtimeANDuser-submissions
|
||||
!/#SubmissionFileType SubArchiveR GET !owner !corrector
|
||||
!/#SubmissionFileType/*FilePath SubDownloadR GET !owner !corrector
|
||||
/correctors SCorrR GET POST
|
||||
/iscorrector SIsCorrR GET !corrector -- Route is used to check for corrector access to this sheet
|
||||
/pseudonym SPseudonymR GET POST !course-registeredANDcorrector-submissions
|
||||
/corrector-invite/ SCorrInviteR GET POST
|
||||
@ -158,12 +159,12 @@
|
||||
/tuts CTutorialListR GET !tutor -- THIS route is used to check for overall course tutor access!
|
||||
/tuts/new CTutorialNewR GET POST
|
||||
/tuts/#TutorialName TutorialR:
|
||||
/edit TEditR GET POST
|
||||
/edit TEditR GET POST !tutorANDtutor-control
|
||||
/delete TDeleteR GET POST
|
||||
/participants TUsersR GET POST !tutor
|
||||
/register TRegisterR POST !timeANDcapacityANDcourse-registeredANDregister-group !timeANDtutorial-registered
|
||||
/communication TCommR GET POST !tutor
|
||||
/tutor-invite TInviteR GET POST
|
||||
/tutor-invite TInviteR GET POST !tutorANDtutor-control
|
||||
/exams CExamListR GET !free
|
||||
/exams/new CExamNewR GET POST
|
||||
/exams/#ExamName ExamR:
|
||||
|
||||
@ -19,10 +19,12 @@ let
|
||||
'';
|
||||
|
||||
override = oldAttrs: {
|
||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install ]);
|
||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
||||
shellHook = ''
|
||||
export PROMPT_INFO="${oldAttrs.name}"
|
||||
|
||||
export CHROME_BIN=$(which google-chrome-stable)
|
||||
|
||||
if [[ -z "$PGHOST" ]]; then
|
||||
set -xe
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@ import Database.Persist.Postgresql (createPostgresqlPool, pgConnStr,
|
||||
import Import hiding (cancel)
|
||||
import Language.Haskell.TH.Syntax (qLocation)
|
||||
import Network.Wai (Middleware)
|
||||
import qualified Network.Wai as Wai
|
||||
import qualified Network.HTTP.Types as HTTP
|
||||
import Network.Wai.Handler.Warp (Settings, defaultSettings,
|
||||
defaultShouldDisplayException,
|
||||
runSettings, runSettingsSocket, setHost,
|
||||
@ -48,6 +50,7 @@ import System.Directory
|
||||
import Jobs
|
||||
|
||||
import qualified Data.Text.Encoding as Text
|
||||
|
||||
import Yesod.Auth.Util.PasswordStore
|
||||
|
||||
import qualified Data.ByteString.Lazy as LBS
|
||||
@ -81,12 +84,20 @@ import Network.Socket (socketPort, Socket, PortNumber)
|
||||
import qualified Network.Socket as Socket (close)
|
||||
|
||||
import Control.Concurrent.STM.Delay
|
||||
import Control.Monad.STM (retry)
|
||||
import Control.Monad.Trans.Cont (runContT, callCC)
|
||||
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import Data.Semigroup (Max(..), Min(..))
|
||||
import Data.Semigroup (Min(..))
|
||||
|
||||
import qualified Prometheus.Metric.GHC as Prometheus
|
||||
import qualified Prometheus
|
||||
|
||||
import Data.Time.Clock.POSIX
|
||||
|
||||
import Handler.Utils.Routes (classifyHandler)
|
||||
|
||||
import Data.List (cycle)
|
||||
|
||||
-- Import all relevant handler modules here.
|
||||
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
||||
@ -111,6 +122,7 @@ import Handler.Health
|
||||
import Handler.Exam
|
||||
import Handler.Allocation
|
||||
import Handler.ExamOffice
|
||||
import Handler.Metrics
|
||||
|
||||
|
||||
-- This line actually creates our YesodDispatch instance. It is the second half
|
||||
@ -124,6 +136,8 @@ mkYesodDispatch "UniWorX" resourcesUniWorX
|
||||
-- migrations handled by Yesod.
|
||||
makeFoundation :: (MonadResource m, MonadUnliftIO m) => AppSettings -> m UniWorX
|
||||
makeFoundation appSettings'@AppSettings{..} = do
|
||||
void $ Prometheus.register Prometheus.ghcMetrics
|
||||
|
||||
-- Some basic initializations: HTTP connection manager, logger, and static
|
||||
-- subsite.
|
||||
appHttpManager <- newManager
|
||||
@ -290,7 +304,39 @@ makeApplication foundation = liftIO $ do
|
||||
logWare <- makeLogWare foundation
|
||||
-- Create the WAI application and apply middlewares
|
||||
appPlain <- toWaiAppPlain foundation
|
||||
return $ logWare $ defaultMiddlewaresNoLogging appPlain
|
||||
return . prometheusMiddleware . logWare $ defaultMiddlewaresNoLogging appPlain
|
||||
where
|
||||
prometheusMiddleware :: Middleware
|
||||
prometheusMiddleware app req respond' = do
|
||||
start <- getPOSIXTime
|
||||
app req $ \res -> do
|
||||
end <- getPOSIXTime
|
||||
let method = decodeUtf8 $ Wai.requestMethod req
|
||||
status = tshow . HTTP.statusCode $ Wai.responseStatus res
|
||||
route :: Maybe (Route UniWorX)
|
||||
route = parseRoute ( Wai.pathInfo req
|
||||
, over (mapped . _2) (fromMaybe "") . HTTP.queryToQueryText $ Wai.queryString req
|
||||
)
|
||||
handler' = pack . classifyHandler <$> route
|
||||
|
||||
labels :: Prometheus.Label3
|
||||
labels = (fromMaybe "n/a" handler', method, status)
|
||||
Prometheus.withLabel requestLatency labels . flip Prometheus.observe . realToFrac $ end - start
|
||||
|
||||
respond' res
|
||||
|
||||
{-# NOINLINE requestLatency #-}
|
||||
requestLatency :: Prometheus.Vector Prometheus.Label3 Prometheus.Histogram
|
||||
requestLatency = Prometheus.unsafeRegister
|
||||
$ Prometheus.vector ("handler", "method", "status")
|
||||
$ Prometheus.histogram info buckets
|
||||
where info = Prometheus.Info "http_request_duration_seconds"
|
||||
"HTTP request latency"
|
||||
buckets = map fromRational . takeWhile (<= 500) . go 50e-6 $ cycle [2, 2, 2.5]
|
||||
where
|
||||
go n [] = [n]
|
||||
go n (f:fs) = n : go (f * n) fs
|
||||
|
||||
|
||||
makeLogWare :: MonadIO m => UniWorX -> m Middleware
|
||||
makeLogWare app = do
|
||||
@ -320,13 +366,22 @@ makeLogWare app = do
|
||||
logWare <- either mkLogWare return lookupRes
|
||||
logWare wai req fin
|
||||
|
||||
data ReadySince = MkReadySince
|
||||
|
||||
-- | Warp settings for the given foundation value.
|
||||
warpSettings :: UniWorX -> Settings
|
||||
warpSettings foundation = defaultSettings
|
||||
& setBeforeMainLoop (runAppLoggingT foundation $ do
|
||||
let notifyReady = do
|
||||
$logInfoS "setup" "Ready"
|
||||
void $ liftIO Systemd.notifyReady
|
||||
void . liftIO $ do
|
||||
void . Prometheus.register . readyMetric =<< getCurrentTime
|
||||
Systemd.notifyReady
|
||||
readyMetric ts = Prometheus.Metric $ return (MkReadySince, collectReadySince)
|
||||
where
|
||||
collectReadySince = return [Prometheus.SampleGroup info Prometheus.GaugeType [Prometheus.Sample "ready_time" [] sample]]
|
||||
info = Prometheus.Info "ready_time" "POSIXTime this Uni2work-instance became ready"
|
||||
sample = encodeUtf8 . tshow . (realToFrac :: POSIXTime -> Nano) $ utcTimeToPOSIXSeconds ts
|
||||
if
|
||||
| foundation ^. _appHealthCheckDelayNotify
|
||||
-> void . forkIO $ do
|
||||
@ -425,39 +480,46 @@ appMain = runResourceT $ do
|
||||
case watchdogMicroSec of
|
||||
Just wInterval
|
||||
| maybe True (== myProcessID) watchdogProcess
|
||||
-> let notifyWatchdog :: IO ()
|
||||
-> let notifyWatchdog :: forall a. IO a
|
||||
notifyWatchdog = runAppLoggingT foundation $ go Nothing
|
||||
where
|
||||
go pStatus = do
|
||||
d <- liftIO . newDelay . floor $ wInterval % 2
|
||||
go :: Maybe (Set (UTCTime, HealthReport)) -> LoggingT IO a
|
||||
go pResults = do
|
||||
let delay = floor $ wInterval % 2
|
||||
d <- liftIO $ newDelay delay
|
||||
|
||||
status <- atomically $ asum
|
||||
[ Nothing <$ waitDelay d
|
||||
, Just <$> do
|
||||
$logDebugS "Notify" $ "Waiting up to " <> tshow delay <> "µs..."
|
||||
mResults <- atomically $ asum
|
||||
[ pResults <$ waitDelay d
|
||||
, do
|
||||
results <- readTVar $ foundation ^. _appHealthReport
|
||||
case fromNullable results of
|
||||
Nothing -> retry
|
||||
Just rs -> do
|
||||
let status = ofoldMap1 (Max *** Min . healthReportStatus) rs
|
||||
guard $ pStatus /= Just status
|
||||
return status
|
||||
guardOn (pResults /= Just results) $ Just results
|
||||
]
|
||||
|
||||
case status of
|
||||
Just (_, Min status') -> do
|
||||
$logInfoS "NotifyStatus" $ toPathPiece status'
|
||||
liftIO . void . Systemd.notifyStatus . unpack $ toPathPiece status'
|
||||
Nothing -> return ()
|
||||
$logDebugS "Notify" "Checking for status/watchdog..."
|
||||
(*> go mResults) . void . runMaybeT $ do
|
||||
results <- hoistMaybe mResults
|
||||
|
||||
case status of
|
||||
Just (_, Min HealthSuccess) -> do
|
||||
$logInfoS "NotifyWatchdog" "Notify"
|
||||
liftIO $ void Systemd.notifyWatchdog
|
||||
_other -> return ()
|
||||
Min status <- hoistMaybe $ ofoldMap1 (Min . healthReportStatus . view _2) <$> fromNullable results
|
||||
$logInfoS "NotifyStatus" $ toPathPiece status
|
||||
liftIO . void . Systemd.notifyStatus . unpack $ toPathPiece status
|
||||
|
||||
go status
|
||||
in void $ allocateLinkedAsync notifyWatchdog
|
||||
_other -> return ()
|
||||
now <- liftIO getCurrentTime
|
||||
iforM_ (foundation ^. _appHealthCheckInterval) . curry $ \case
|
||||
(_, Nothing) -> return ()
|
||||
(hc, Just interval) -> do
|
||||
lastSuccess <- hoistMaybe $ results
|
||||
& Set.filter (\(_, rep) -> classifyHealthReport rep == hc)
|
||||
& Set.filter (\(_, rep) -> healthReportStatus rep >= HealthSuccess)
|
||||
& Set.mapMonotonic (view _1)
|
||||
& Set.lookupMax
|
||||
guard $ lastSuccess > addUTCTime (negate interval) now
|
||||
$logInfoS "NotifyWatchdog" "Notify"
|
||||
liftIO $ void Systemd.notifyWatchdog
|
||||
in do
|
||||
$logDebugS "Notify" "Spawning notify thread..."
|
||||
void $ allocateLinkedAsync notifyWatchdog
|
||||
_other -> $logWarnS "Notify" "Not sending notifications of status/poking watchdog"
|
||||
|
||||
let runWarp socket = runSettingsSocket (warpSettings foundation) socket app
|
||||
case sockets of
|
||||
|
||||
@ -132,6 +132,12 @@ data Transaction
|
||||
{ transactionOffice :: UserId
|
||||
, transactionField :: StudyTermsId
|
||||
}
|
||||
| TransactionTutorialEdit
|
||||
{ transactionTutorial :: TutorialId
|
||||
}
|
||||
| TransactionTutorialDelete
|
||||
{ transactionTutorial :: TutorialId
|
||||
}
|
||||
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import qualified Data.CaseInsensitive as CI
|
||||
|
||||
|
||||
data DummyMessage = MsgDummyIdent
|
||||
| MsgDummyIdentPlaceholder
|
||||
| MsgDummyNoFormData
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
|
||||
@ -24,7 +25,9 @@ dummyForm :: ( RenderMessage (HandlerSite m) FormMessage
|
||||
, Button (HandlerSite m) ButtonSubmit
|
||||
, MonadHandler m
|
||||
) => AForm m (CI Text)
|
||||
dummyForm = areq (ciField & addDatalist userList) (fslI MsgDummyIdent & noAutocomplete) Nothing
|
||||
dummyForm = wFormToAForm $ do
|
||||
mr <- getMessageRender
|
||||
aFormToWForm $ areq (ciField & addDatalist userList) (fslpI MsgDummyIdent (mr MsgDummyIdentPlaceholder) & noAutocomplete & addName PostLoginDummy) Nothing
|
||||
where
|
||||
userList = fmap mkOptionList . runDB $ withReaderT projectBackend (map toOption <$> selectList [] [Asc UserIdent] :: ReaderT SqlBackend _ [Option UserIdent])
|
||||
toOption (Entity _ User{..}) = Option userDisplayName userIdent (CI.original userIdent)
|
||||
|
||||
@ -7,7 +7,7 @@ module Auth.LDAP
|
||||
, ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName
|
||||
, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname
|
||||
, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName
|
||||
, ldapUserSchoolAssociation
|
||||
, ldapUserSchoolAssociation, ldapUserSubTermsSemester, ldapSex
|
||||
) where
|
||||
|
||||
import Import.NoFoundation
|
||||
@ -36,6 +36,7 @@ data CampusLogin = CampusLogin
|
||||
data CampusMessage = MsgCampusIdentPlaceholder
|
||||
| MsgCampusIdent
|
||||
| MsgCampusPassword
|
||||
| MsgCampusPasswordPlaceholder
|
||||
| MsgCampusSubmit
|
||||
| MsgCampusInvalidCredentials
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
@ -47,10 +48,12 @@ findUser LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM (not
|
||||
userFilters =
|
||||
[ ldapUserPrincipalName Ldap.:= Text.encodeUtf8 ident
|
||||
, ldapUserPrincipalName Ldap.:= Text.encodeUtf8 [st|#{ident}@campus.lmu.de|]
|
||||
, ldapUserEmail Ldap.:= Text.encodeUtf8 ident
|
||||
, ldapUserEmail Ldap.:= Text.encodeUtf8 [st|#{ident}@lmu.de|]
|
||||
, ldapUserEmail Ldap.:= Text.encodeUtf8 [st|#{ident}@campus.lmu.de|]
|
||||
, ldapUserDisplayName Ldap.:= Text.encodeUtf8 ident
|
||||
] ++
|
||||
[ ldapUserEmail' Ldap.:= Text.encodeUtf8 ident'
|
||||
| ident' <- [ident, [st|#{ident}@lmu.de|], [st|#{ident}@campus.lmu.de|]]
|
||||
, ldapUserEmail' <- toList ldapUserEmail
|
||||
] ++
|
||||
[ ldapUserDisplayName Ldap.:= Text.encodeUtf8 ident
|
||||
]
|
||||
userSearchSettings = mconcat
|
||||
[ Ldap.scope ldapScope
|
||||
@ -59,17 +62,23 @@ findUser LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM (not
|
||||
, Ldap.derefAliases Ldap.DerefAlways
|
||||
]
|
||||
|
||||
ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName, ldapUserSchoolAssociation :: Ldap.Attr
|
||||
ldapUserPrincipalName, ldapUserDisplayName, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName, ldapUserSchoolAssociation, ldapSex, ldapUserSubTermsSemester :: Ldap.Attr
|
||||
ldapUserPrincipalName = Ldap.Attr "userPrincipalName"
|
||||
ldapUserEmail = Ldap.Attr "mail"
|
||||
ldapUserDisplayName = Ldap.Attr "displayName"
|
||||
ldapUserMatriculation = Ldap.Attr "LMU-Stud-Matrikelnummer"
|
||||
ldapUserFirstName = Ldap.Attr "givenName"
|
||||
ldapUserSurname = Ldap.Attr "sn"
|
||||
ldapUserTitle = Ldap.Attr "title"
|
||||
ldapUserStudyFeatures = Ldap.Attr "dfnEduPersonFeaturesOfStudy"
|
||||
ldapUserFieldName = Ldap.Attr "dfnEduPersonFieldOfStudyString"
|
||||
ldapUserFieldName = Ldap.Attr "LMU-Stg-Fach"
|
||||
ldapUserSchoolAssociation = Ldap.Attr "LMU-IFI-eduPersonOrgUnitDNString"
|
||||
ldapSex = Ldap.Attr "schacGender"
|
||||
ldapUserSubTermsSemester = Ldap.Attr "LMU-Stg-FachundFS"
|
||||
|
||||
ldapUserEmail :: NonEmpty Ldap.Attr
|
||||
ldapUserEmail = Ldap.Attr "mail" :|
|
||||
[ Ldap.Attr "name"
|
||||
]
|
||||
|
||||
|
||||
data CampusUserException = CampusUserLdapError LdapPoolError
|
||||
@ -122,7 +131,7 @@ campusForm = do
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
|
||||
ident <- wreq ciField (fslpI MsgCampusIdent (mr MsgCampusIdentPlaceholder) & addAttr "autofocus" "") Nothing
|
||||
password <- wreq passwordField (fslI MsgCampusPassword) Nothing
|
||||
password <- wreq passwordField (fslpI MsgCampusPassword (mr MsgCampusPasswordPlaceholder)) Nothing
|
||||
|
||||
return $ CampusLogin
|
||||
<$> ident
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user