Merge branch 'tabellenspalten-ausblenden'
This commit is contained in:
commit
c6ca71f898
@ -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 {
|
||||
|
||||
@ -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,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];
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
|
||||
71
frontend/src/utils/hide-columns/hide-columns.scss
Normal file
71
frontend/src/utils/hide-columns/hide-columns.scss
Normal 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;
|
||||
}
|
||||
@ -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)) || {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
82
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
544
records.json
544
records.json
@ -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": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
10
src/Foundation/Routes/Definitions.hs
Normal file
10
src/Foundation/Routes/Definitions.hs
Normal 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")
|
||||
@ -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{..}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -4,4 +4,4 @@ $#
|
||||
$# participantTable : widget table
|
||||
|
||||
^{participantTable}
|
||||
_{MsgCourseMembersCountOf (fromIntegral numParticipants) (courseCapacity course)}.
|
||||
_{MsgCourseMembersCountOf (fromIntegral numParticipants) (courseCapacity course)}.
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
$newline never
|
||||
<div table-utils>
|
||||
<div .scrolltable .scrolltable--bordered>
|
||||
<table *{dbsAttrs'}>
|
||||
$maybe wHeaders' <- wHeaders
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user