Merge branch 'tabellenspalten-ausblenden'

This commit is contained in:
Gregor Kleen 2019-12-16 15:24:43 +01:00
commit c6ca71f898
25 changed files with 901 additions and 712 deletions

View File

@ -4,9 +4,6 @@ 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.scss';
export class App {

View File

@ -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();
});
});

View 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
View File

@ -0,0 +1,4 @@
import 'whatwg-fetch';
import { ResizeObserver as Polyfill } from '@juggle/resize-observer';
window.ResizeObserver = window.ResizeObserver || Polyfill;

View File

@ -1,4 +1,5 @@
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';
@ -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];
}

View File

@ -0,0 +1,312 @@
import { Utility } from '../../core/utility';
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
import './hide-columns.scss';
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;
}

View File

@ -0,0 +1,71 @@
.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(.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;
}

View File

@ -1,4 +1,5 @@
import { Utility } from '../../core/utility';
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
import './show-hide.scss';
const SHOW_HIDE_LOCAL_STORAGE_KEY = 'SHOW_HIDE';
@ -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)) || {};
}
}

View File

@ -11,6 +11,7 @@ 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,
@ -27,4 +28,5 @@ export const Utils = [
Tooltip,
CourseTeaser,
...NavbarUtils,
HideColumns,
];

82
package-lock.json generated
View File

@ -178,6 +178,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@ -1907,6 +1913,14 @@
"@babel/helper-plugin-utils": "^7.0.0",
"resolve": "^1.8.1",
"semver": "^5.5.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"@babel/plugin-transform-shorthand-properties": {
@ -2035,6 +2049,12 @@
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@ -2541,6 +2561,11 @@
"integrity": "sha512-EKKR4p0higjsIPKjSSkGqtweUwo/GgR/zKL4rCwzF5Z/BZ/ebJZaS8ZjGE7YUNEN63SYk2WhpJVI+l9dwfU7RQ==",
"dev": true
},
"@juggle/resize-observer": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-2.5.0.tgz",
"integrity": "sha512-Nmkeaj5LalJeciRVEqi9Uxi61r0LvGc2yhUCykhXuft9fMyb/6VkZbwJ+UmUl8bk2k6qhwd1qJw6S2YJ0joXlA=="
},
"@marionebl/sander": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/@marionebl/sander/-/sander-0.6.1.tgz",
@ -6277,6 +6302,14 @@
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"crypto-browserify": {
@ -6989,6 +7022,12 @@
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@ -8608,6 +8647,12 @@
"strip-indent": "^2.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@ -10348,6 +10393,14 @@
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"mamacro": {
@ -10941,6 +10994,14 @@
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"normalize-path": {
@ -14352,6 +14413,12 @@
"os-tmpdir": "^1.0.0"
}
},
"outdent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/outdent/-/outdent-0.7.0.tgz",
"integrity": "sha512-Ue462G+UIFoyQmOzapGIKWS3d/9NHeD/018WGEDZIhN2/VaQpVXbofMcZX0socv1fw4/tmEn7Vd3McOdPZfKzQ==",
"dev": true
},
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@ -15468,9 +15535,9 @@
}
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"semver-compare": {
@ -17555,6 +17622,15 @@
"tapable": "^1.0.0"
}
},
"webpack-plugin-hash-output": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/webpack-plugin-hash-output/-/webpack-plugin-hash-output-3.2.1.tgz",
"integrity": "sha512-Iu4Sox3/bdiqd6TdYwZAExuH+XNbnJStPrwh6yhzOflwc/hZUP9MdiZDbFwTXrmm9ZwoXNUmvn7C0Qj4qRez2A==",
"dev": true,
"requires": {
"outdent": "^0.7.0"
}
},
"webpack-sources": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz",

View File

@ -6,7 +6,7 @@
"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",
@ -90,6 +90,7 @@
"null-loader": "^2.0.0",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.3.1",
"semver": "^6.3.0",
"standard-version": "^6.0.1",
"style-loader": "^0.23.1",
"terser-webpack-plugin": "^2.2.3",
@ -97,10 +98,12 @@
"typeface-source-sans-pro": "0.0.75",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-manifest-plugin": "^2.2.0"
"webpack-manifest-plugin": "^2.2.0",
"webpack-plugin-hash-output": "^3.2.1"
},
"dependencies": {
"@babel/runtime": "^7.7.6",
"@juggle/resize-observer": "^2.5.0",
"core-js": "^3.4.8",
"moment": "^2.24.0",
"npm": "^6.13.3",

View File

@ -1,544 +0,0 @@
{
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/typeface-roboto/index.css": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/typeface-source-sans-pro/index.css": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/@fortawesome/fontawesome-pro/css/all.css": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/asidenav/asidenav.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/inputs/inputs.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/inputs/radio.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/form/form.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/alerts/alerts.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/tooltips/tooltips.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/modal/modal.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/navbar/navbar.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!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!node_modules/postcss-loader/src/index.js!node_modules/sass-loader/dist/cjs.js!frontend/src/utils/inputs/checkbox.scss": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js!frontend/src/utils/form/datepicker.css": [
{
"modules": {
"byIdentifier": {},
"usedIds": {}
},
"chunks": {
"byName": {},
"bySource": {},
"usedIds": []
}
}
],
"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/typeface-source-sans-pro/index.css": [
{
"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/typeface-roboto/index.css": [
{
"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/@fortawesome/fontawesome-pro/css/all.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/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-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/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/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/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/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/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/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/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/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/navbar/navbar.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": []
}
}
],
"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--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": []
}
}
]
}

View File

@ -71,6 +71,7 @@ import Handler.Utils.SchoolLdap
import Handler.Utils.ExamOffice.Exam
import Handler.Utils.ExamOffice.Course
import Handler.Utils.Profile
import Handler.Utils.Routes
import Utils.Form
import Utils.Sheet
import Utils.SystemMessage
@ -96,86 +97,12 @@ import qualified Ldap.Client as Ldap
import UnliftIO.Pool
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
-- http://www.yesodweb.com/book/routing-and-handlers
--
-- Note that this is really half the story; in Application.hs, mkYesodDispatch
-- generates the rest of the code. Please see the following documentation
-- for an explanation for this split:
-- http://www.yesodweb.com/book/scaffolding-and-the-site-template#scaffolding-and-the-site-template_foundation_and_application_modules
--
-- This function also generates the following type synonyms:
-- type Handler x = HandlerT UniWorX IO x
-- type Widget = WidgetT UniWorX IO ()
mkYesodData "UniWorX" uniworxRoutes
deriving instance Generic CourseR
deriving instance Generic SheetR
deriving instance Generic SubmissionR
deriving instance Generic MaterialR
deriving instance Generic TutorialR
deriving instance Generic ExamR
deriving instance Generic CourseApplicationR
deriving instance Generic AllocationR
deriving instance Generic SchoolR
deriving instance Generic ExamOfficeR
deriving instance Generic CourseNewsR
deriving instance Generic CourseEventR
deriving instance Generic (Route UniWorX)
data RouteChildren
type instance Children RouteChildren a = ChildrenRouteChildren a
type family ChildrenRouteChildren a where
ChildrenRouteChildren (Route EmbeddedStatic) = '[]
ChildrenRouteChildren (Route Auth) = '[]
ChildrenRouteChildren UUID = '[]
ChildrenRouteChildren (Key a) = '[]
ChildrenRouteChildren (CI a) = '[]
ChildrenRouteChildren a = Children ChGeneric a
-- | Convenient Type Synonyms:
type DB = YesodDB UniWorX
type Form x = Html -> MForm (HandlerFor UniWorX) (FormResult x, Widget)
type MsgRenderer = MsgRendererS UniWorX -- see Utils
type MailM a = MailT (HandlerFor UniWorX) a
-- Pattern Synonyms for convenience
pattern CSheetR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetR -> Route UniWorX
pattern CSheetR tid ssh csh shn ptn
= CourseR tid ssh csh (SheetR shn ptn)
pattern CMaterialR :: TermId -> SchoolId -> CourseShorthand -> MaterialName -> MaterialR -> Route UniWorX
pattern CMaterialR tid ssh csh mnm ptn
= CourseR tid ssh csh (MaterialR mnm ptn)
pattern CTutorialR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> TutorialR -> Route UniWorX
pattern CTutorialR tid ssh csh tnm ptn
= CourseR tid ssh csh (TutorialR tnm ptn)
pattern CExamR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> ExamR -> Route UniWorX
pattern CExamR tid ssh csh tnm ptn
= CourseR tid ssh csh (ExamR tnm ptn)
pattern CSubmissionR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> SubmissionR -> Route UniWorX
pattern CSubmissionR tid ssh csh shn cid ptn
= CSheetR tid ssh csh shn (SubmissionR cid ptn)
pattern CApplicationR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> CourseApplicationR -> Route UniWorX
pattern CApplicationR tid ssh csh appId ptn
= CourseR tid ssh csh (CourseApplicationR appId ptn)
pattern CNewsR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseNews -> CourseNewsR -> Route UniWorX
pattern CNewsR tid ssh csh nId ptn
= CourseR tid ssh csh (CourseNewsR nId ptn)
pattern CEventR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseEvent -> CourseEventR -> Route UniWorX
pattern CEventR tid ssh csh nId ptn
= CourseR tid ssh csh (CourseEventR nId ptn)
-- Requires `rendeRoute`, thus cannot currently be moved to Foundation.I18n
instance RenderMessage UniWorX (UnsupportedAuthPredicate AuthTag (Route UniWorX)) where
renderMessage f ls (UnsupportedAuthPredicate tag route) = mr . MsgUnsupportedAuthPredicate (mr tag) $ Text.intercalate "/" pieces
@ -1486,6 +1413,7 @@ siteLayout' headingOverride widget = do
primaryLanguage <- unsafeHead . Text.splitOn "-" <$> selectLanguage appLanguages
mcurrentRoute <- getCurrentRoute
let currentHandler = classifyHandler <$> mcurrentRoute
-- Get the breadcrumbs, as defined in the YesodBreadcrumbs instance.
let

View File

@ -1,10 +1,84 @@
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Foundation.Routes
( uniworxRoutes
( module Foundation.Routes.Definitions
, module Foundation.Routes
) where
import ClassyPrelude.Yesod
import Yesod.Routes.TH.Types (ResourceTree)
import Import.NoFoundation
import Foundation.Type
import Foundation.Routes.Definitions
uniworxRoutes :: [ResourceTree String]
uniworxRoutes = $(parseRoutesFile "routes")
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
-- http://www.yesodweb.com/book/routing-and-handlers
--
-- Note that this is really half the story; in Application.hs, mkYesodDispatch
-- generates the rest of the code. Please see the following documentation
-- for an explanation for this split:
-- http://www.yesodweb.com/book/scaffolding-and-the-site-template#scaffolding-and-the-site-template_foundation_and_application_modules
--
-- This function also generates the following type synonyms:
-- type Handler x = HandlerT UniWorX IO x
-- type Widget = WidgetT UniWorX IO ()
mkYesodData "UniWorX" uniworxRoutes
deriving instance Generic CourseR
deriving instance Generic SheetR
deriving instance Generic SubmissionR
deriving instance Generic MaterialR
deriving instance Generic TutorialR
deriving instance Generic ExamR
deriving instance Generic CourseApplicationR
deriving instance Generic AllocationR
deriving instance Generic SchoolR
deriving instance Generic ExamOfficeR
deriving instance Generic CourseNewsR
deriving instance Generic CourseEventR
deriving instance Generic (Route UniWorX)
data RouteChildren
type instance Children RouteChildren a = ChildrenRouteChildren a
type family ChildrenRouteChildren a where
ChildrenRouteChildren (Route EmbeddedStatic) = '[]
ChildrenRouteChildren (Route Auth) = '[]
ChildrenRouteChildren UUID = '[]
ChildrenRouteChildren (Key a) = '[]
ChildrenRouteChildren (CI a) = '[]
ChildrenRouteChildren a = Children ChGeneric a
-- Pattern Synonyms for convenience
pattern CSheetR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetR -> Route UniWorX
pattern CSheetR tid ssh csh shn ptn
= CourseR tid ssh csh (SheetR shn ptn)
pattern CMaterialR :: TermId -> SchoolId -> CourseShorthand -> MaterialName -> MaterialR -> Route UniWorX
pattern CMaterialR tid ssh csh mnm ptn
= CourseR tid ssh csh (MaterialR mnm ptn)
pattern CTutorialR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> TutorialR -> Route UniWorX
pattern CTutorialR tid ssh csh tnm ptn
= CourseR tid ssh csh (TutorialR tnm ptn)
pattern CExamR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> ExamR -> Route UniWorX
pattern CExamR tid ssh csh tnm ptn
= CourseR tid ssh csh (ExamR tnm ptn)
pattern CSubmissionR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> CryptoFileNameSubmission -> SubmissionR -> Route UniWorX
pattern CSubmissionR tid ssh csh shn cid ptn
= CSheetR tid ssh csh shn (SubmissionR cid ptn)
pattern CApplicationR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> CourseApplicationR -> Route UniWorX
pattern CApplicationR tid ssh csh appId ptn
= CourseR tid ssh csh (CourseApplicationR appId ptn)
pattern CNewsR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseNews -> CourseNewsR -> Route UniWorX
pattern CNewsR tid ssh csh nId ptn
= CourseR tid ssh csh (CourseNewsR nId ptn)
pattern CEventR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseEvent -> CourseEventR -> Route UniWorX
pattern CEventR tid ssh csh nId ptn
= CourseR tid ssh csh (CourseEventR nId ptn)

View File

@ -0,0 +1,10 @@
module Foundation.Routes.Definitions
( uniworxRoutes
) where
import ClassyPrelude.Yesod
import Yesod.Routes.TH.Types (ResourceTree)
uniworxRoutes :: [ResourceTree String]
uniworxRoutes = $(parseRoutesFile "routes")

View File

@ -89,7 +89,7 @@ getAllocationListR = do
dbtSorting = mconcat
[ sortTerm $ queryAllocation . to (E.^. AllocationTerm)
, sortSchool $ queryAllocation . to (E.^. AllocationSchool)
, sortSchoolShort $ queryAllocation . to (E.^. AllocationSchool)
, sortAllocationName $ queryAllocation . to (E.^. AllocationName)
, singletonMap "available" . SortColumn $ view queryAvailable
, if
@ -124,7 +124,7 @@ getAllocationListR = do
psValidator :: PSValidator _ _
psValidator = def
& defaultSorting [SortDescBy "term", SortAscBy "school", SortAscBy "allocation"]
& defaultSorting [SortDescBy "term", SortAscBy "school-short", SortAscBy "allocation"]
table <- runDB $ dbTableWidget' psValidator DBTable{..}

View File

@ -2,7 +2,9 @@ module Handler.Utils.Routes
( classifyHandler
) where
import Import
import Import.NoFoundation
import Foundation.Routes
import Foundation.Type
import Utils.TH.Routes

View File

@ -138,12 +138,33 @@ packages:
original:
hackage: HaXml-1.25.5
- completed:
hackage: esqueleto-3.0.0@sha256:efd84fd11ceaf0ae4e1b0c6236122b1f213c2c6f2f4f58e30f03eddc2ec3f423,5248
hackage: persistent-2.10.4@sha256:16c4c0823dd5e16bac4d607895ab0f4febd0626c020e5755ed1a52bf04068148,4738
pantry-tree:
size: 1127
sha256: 74e43834d5cc468acc3cb6e8a81567ebfbb5350a3e07ae01dd7f30d6255274a1
size: 2094
sha256: b40d1783b539ddbbceaa827bf286d0b3bfcf76ca19e604c9d510b2a64008714e
original:
hackage: esqueleto-3.0.0
hackage: persistent-2.10.4
- completed:
hackage: persistent-postgresql-2.10.1@sha256:ea53a0f1f4223b4884b5e19511325367879560d2432a02a976aa4da57c5fb760,2871
pantry-tree:
size: 740
sha256: 3cdbc757b1cebb65542fb919369be238b3f120adc45f023084a8b64c214d9675
original:
hackage: persistent-postgresql-2.10.1
- completed:
hackage: persistent-template-2.7.3@sha256:ac3e5e8c48e968b927bbf4e97162c52e7e417d69b05efeb1c581d7c682e043d2,2703
pantry-tree:
size: 560
sha256: fdfb2a721eb9c9831d7381d36bc52de0808a008ed3d553b6490080f337249684
original:
hackage: persistent-template-2.7.3
- completed:
hackage: esqueleto-3.2.3@sha256:5e1e0a8600e2744127ef4bb5956fa84ae6bc1fc337c7b8726fabb7ca53e2d9b3,5466
pantry-tree:
size: 1461
sha256: f6215274a43addd339f8bc89f1ca0e8fdfb08180b13d779ae8f7e360acc4c473
original:
hackage: esqueleto-3.2.3
- completed:
hackage: HaskellNet-SSL-0.3.4.1@sha256:3ca14dd69460a380cf69aed40654fb10c4c03e344632b6a9986568c87feda157,1843
pantry-tree:
@ -242,6 +263,20 @@ packages:
sha256: 9ed161eadfda5b1eb36cfcf077146f7b66db1da69f1041fc720aea287ec021b0
original:
hackage: generic-lens-1.2.0.0
- completed:
hackage: prometheus-metrics-ghc-1.0.0@sha256:0f4ecbefa810bd847e66c498ab3387bf21e426525a7c9a94841973c582719ba3,1231
pantry-tree:
size: 293
sha256: 8a6d6ef3235ab980e867f64b712b5d38f1a84c3ac4920f5b4c3b3e63bcdf6ec9
original:
hackage: prometheus-metrics-ghc-1.0.0
- completed:
hackage: wai-middleware-prometheus-1.0.0@sha256:1625792914fb2139f005685be8ce519111451cfb854816e430fbf54af46238b4,1314
pantry-tree:
size: 307
sha256: 6d64803c639ed4c7204ea6fab0536b97d3ee16cdecb9b4a883cd8e56d3c61402
original:
hackage: wai-middleware-prometheus-1.0.0
snapshots:
- completed:
size: 498180

View File

@ -4,4 +4,4 @@ $#
$# participantTable : widget table
^{participantTable}
_{MsgCourseMembersCountOf (fromIntegral numParticipants) (courseCapacity course)}.
_{MsgCourseMembersCountOf (fromIntegral numParticipants) (courseCapacity course)}.

View File

@ -11,7 +11,7 @@ $if not isModal
<div .main>
<div .main__content uw-poc>
<div .main__content uw-poc :isJust currentHandler:uw-handler=#{fromMaybe "" currentHandler}>
$if not isModal
<!-- breadcrumbs -->

View File

@ -329,8 +329,12 @@ input[type="button"].btn-info:hover,
width: 100%;
}
.table:only-child {
margin: 0;
.table:first-child {
margin-top: 0;
}
.table:last-child {
margin-bottom: 0;
}
.table--striped {

View File

@ -1,5 +1,5 @@
$newline never
<th .table__th *{attrs} :isSortable:.sortable :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc>
<th .table__th *{attrs} :isSortable:.sortable :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc uw-hide-column-header=#{maybe "" toPathPiece sortableKey}>
$maybe flag <- sortableKey
$case directions
$of [SortAsc]

View File

@ -1,4 +1,5 @@
$newline never
<div table-utils>
<div .scrolltable .scrolltable--bordered>
<table *{dbsAttrs'}>
$maybe wHeaders' <- wHeaders

View File

@ -1,3 +1,3 @@
$newline never
<div ##{wIdent "table-wrapper"} :not (null rows && (dbsEmptyStyle == DBESNoHeading)):uw-async-table data-async-table-db-header=#{toPathPiece HeaderDBTableShortcircuit}>
<div ##{wIdent "table-wrapper"} :not (null rows && (dbsEmptyStyle == DBESNoHeading)):uw-async-table uw-hide-columns=#{dbtIdent} data-async-table-db-header=#{toPathPiece HeaderDBTableShortcircuit}>
^{table}

View File

@ -6,9 +6,10 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const yaml = require('js-yaml');
const HashOutput = require('webpack-plugin-hash-output');
var webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
const packageVersion = require('./package.json').version;
module.exports = {
module: {
@ -71,15 +72,21 @@ module.exports = {
},
entry: {
main: path.resolve(__dirname, 'frontend/src', 'main.js')
main: [ path.resolve(__dirname, 'frontend/src', 'polyfill.js'),
path.resolve(__dirname, 'frontend/src', 'main.js')
]
},
plugins: [
new HashOutput({
validateOutput: true,
validateOutputRegex: /static\/wp-[^\/]\//
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// all options are optional
filename: '[contenthash].css',
chunkFilename: '[contenthash].css',
filename: '[chunkhash].css',
chunkFilename: '[chunkhash].css',
ignoreOrder: false, // Enable to remove warnings about conflicting order
}),
new webpack.NamedChunksPlugin((chunk) => {
@ -101,12 +108,15 @@ module.exports = {
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new CopyPlugin([
{ from: 'assets/lmu/sigillum.svg', to: path.resolve(__dirname, 'static', 'img/lmu/sigillum.svg') },
])
]),
new webpack.DefinePlugin({
VERSION: JSON.stringify(packageVersion)
})
],
output: {
chunkFilename: '[contenthash].js',
filename: '[contenthash].js',
chunkFilename: '[chunkhash].js',
filename: '[chunkhash].js',
path: path.resolve(__dirname, 'static', `wp-${webpackVersion}`),
publicPath: `/static/res/wp-${webpackVersion}/`,
hashFunction: 'shake256',