feat(course-participants): show exercise sheets (first cornice)
This commit is contained in:
parent
e10cfe9c58
commit
26cc8e4b53
@ -425,6 +425,7 @@ input[type="button"].btn-info:not(.btn-link):hover,
|
||||
padding-bottom: 10px
|
||||
font-weight: bold
|
||||
text-align: left
|
||||
vertical-align: middle
|
||||
|
||||
a
|
||||
color: white
|
||||
|
||||
@ -115,6 +115,7 @@ export class UtilRegistry {
|
||||
} catch(err) {
|
||||
if (DEBUG_MODE > 0) {
|
||||
console.error('Error while trying to initialize a utility!', { util , element, err });
|
||||
console.error(err.stack);
|
||||
}
|
||||
utilInstance = null;
|
||||
}
|
||||
|
||||
@ -8,13 +8,10 @@ const CHECK_ALL_INITIALIZED_CLASS = 'check-all--initialized';
|
||||
selector: 'table:not([uw-no-check-all])',
|
||||
})
|
||||
export class CheckAll {
|
||||
|
||||
_element;
|
||||
_app;
|
||||
|
||||
_columns = [];
|
||||
_checkboxColumn = [];
|
||||
_checkAllCheckbox = null;
|
||||
_checkAllColumns = [];
|
||||
|
||||
constructor(element, app) {
|
||||
if (!element) {
|
||||
@ -22,80 +19,81 @@ export class CheckAll {
|
||||
}
|
||||
|
||||
this._element = element;
|
||||
this._app = app;
|
||||
|
||||
if (this._element.classList.contains(CHECK_ALL_INITIALIZED_CLASS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._gatherColumns();
|
||||
this._setupCheckAllCheckbox();
|
||||
this._findCheckboxColumns().forEach(columnId => this._checkAllColumns.push(new CheckAllColumn(this._element, app, this._columns[columnId])));
|
||||
|
||||
// mark initialized
|
||||
this._element.classList.add(CHECK_ALL_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._checkAllCheckbox.destroy();
|
||||
}
|
||||
|
||||
_getCheckboxId() {
|
||||
return 'check-all-checkbox-' + Math.floor(Math.random() * 100000);
|
||||
}
|
||||
|
||||
_gatherColumns() {
|
||||
const rows = Array.from(this._element.querySelectorAll('tr'));
|
||||
const rows = Array.from(this._element.rows);
|
||||
const cols = [];
|
||||
rows.forEach((tr) => {
|
||||
const cells = Array.from(tr.querySelectorAll('td'));
|
||||
cells.forEach((cell, cellIndex) => {
|
||||
if (!cols[cellIndex]) {
|
||||
cols[cellIndex] = [];
|
||||
const cells = Array.from(tr.cells);
|
||||
cells.forEach(cell => {
|
||||
let i = 0;
|
||||
for (const sibling of cells.slice(0, cell.cellIndex))
|
||||
i += Math.max(1, sibling.colSpan) || 1;
|
||||
|
||||
for (let j = i; j < i + cell.colSpan; j++) {
|
||||
if (!cols[j]) {
|
||||
cols[j] = [];
|
||||
}
|
||||
cols[j].push(cell);
|
||||
}
|
||||
cols[cellIndex].push(cell);
|
||||
});
|
||||
});
|
||||
this._columns = cols;
|
||||
}
|
||||
|
||||
_findCheckboxColumn(columns) {
|
||||
let checkboxColumnId = null;
|
||||
columns.forEach((col, i) => {
|
||||
_findCheckboxColumns() {
|
||||
let checkboxColumnIds = [];
|
||||
this._columns.forEach((col, i) => {
|
||||
if (this._isCheckboxColumn(col)) {
|
||||
checkboxColumnId = i;
|
||||
checkboxColumnIds.push(i);
|
||||
}
|
||||
});
|
||||
return checkboxColumnId;
|
||||
return checkboxColumnIds;
|
||||
}
|
||||
|
||||
_isCheckboxColumn(col) {
|
||||
let onlyCheckboxes = true;
|
||||
col.forEach((cell) => {
|
||||
if (onlyCheckboxes && !cell.querySelector(CHECKBOX_SELECTOR)) {
|
||||
onlyCheckboxes = false;
|
||||
}
|
||||
});
|
||||
return onlyCheckboxes;
|
||||
return col.every(cell => cell.tagName == 'TH' || cell.querySelector(CHECKBOX_SELECTOR))
|
||||
&& col.some(cell => cell.querySelector(CHECKBOX_SELECTOR));
|
||||
}
|
||||
}
|
||||
|
||||
_setupCheckAllCheckbox() {
|
||||
const checkboxColumnId = this._findCheckboxColumn(this._columns);
|
||||
if (checkboxColumnId === null) {
|
||||
return;
|
||||
}
|
||||
class CheckAllColumn {
|
||||
_app;
|
||||
_table;
|
||||
_column;
|
||||
|
||||
_checkAllCheckbox;
|
||||
_checkboxId = 'check-all-checkbox-' + Math.floor(Math.random() * 100000);
|
||||
|
||||
constructor(table, app, column) {
|
||||
this._column = column;
|
||||
this._table = table;
|
||||
this._app = app;
|
||||
|
||||
const th = this._column.filter(element => element.tagName == 'TH')[0];
|
||||
if (!th)
|
||||
return false;
|
||||
|
||||
this._checkboxColumn = this._columns[checkboxColumnId];
|
||||
const firstRow = this._element.querySelector('tr');
|
||||
const th = Array.from(firstRow.querySelectorAll('th, td'))[checkboxColumnId];
|
||||
this._checkAllCheckbox = document.createElement('input');
|
||||
this._checkAllCheckbox.setAttribute('type', 'checkbox');
|
||||
this._checkAllCheckbox.setAttribute('id', this._getCheckboxId());
|
||||
this._checkAllCheckbox.setAttribute('id', this._checkboxId);
|
||||
th.insertBefore(this._checkAllCheckbox, th.firstChild);
|
||||
|
||||
// set up new checkbox
|
||||
this._app.utilRegistry.initAll(th);
|
||||
|
||||
this._checkAllCheckbox.addEventListener('input', () => this._onCheckAllCheckboxInput());
|
||||
this._checkAllCheckbox.addEventListener('input', this._onCheckAllCheckboxInput.bind(this));
|
||||
this._setupCheckboxListeners();
|
||||
}
|
||||
|
||||
@ -104,23 +102,25 @@ export class CheckAll {
|
||||
}
|
||||
|
||||
_setupCheckboxListeners() {
|
||||
this._checkboxColumn.map((cell) => {
|
||||
return cell.querySelector(CHECKBOX_SELECTOR);
|
||||
})
|
||||
.forEach((checkbox) => {
|
||||
checkbox.addEventListener('input', () => this._updateCheckAllCheckboxState());
|
||||
});
|
||||
this._column
|
||||
.flatMap(cell => cell.tagName == 'TH' ? new Array() : Array.from(cell.querySelectorAll(CHECKBOX_SELECTOR)))
|
||||
.forEach(checkbox =>
|
||||
checkbox.addEventListener('input', this._updateCheckAllCheckboxState.bind(this))
|
||||
);
|
||||
}
|
||||
|
||||
_updateCheckAllCheckboxState() {
|
||||
const allChecked = this._checkboxColumn.every((cell) => {
|
||||
return cell.querySelector(CHECKBOX_SELECTOR).checked;
|
||||
});
|
||||
const allChecked = this._column.every(cell =>
|
||||
cell.tagName == 'TH' || cell.querySelector(CHECKBOX_SELECTOR).checked
|
||||
);
|
||||
this._checkAllCheckbox.checked = allChecked;
|
||||
}
|
||||
|
||||
_toggleAll(checked) {
|
||||
this._checkboxColumn.forEach((cell) => {
|
||||
this._column.forEach(cell => {
|
||||
if (cell.tagName == 'TH')
|
||||
return;
|
||||
|
||||
cell.querySelector(CHECKBOX_SELECTOR).checked = checked;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
|
||||
import './hide-columns.sass';
|
||||
import * as memoize from 'lodash.memoize';
|
||||
|
||||
const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns';
|
||||
const TABLE_HEADER_IDENT = 'uw-hide-column-header';
|
||||
const HIDE_COLUMNS_HIDER_LABEL = 'uw-hide-columns--hider-label';
|
||||
const HIDE_COLUMNS_NO_HIDE = 'uw-hide-columns--no-hide';
|
||||
const HIDE_COLUMNS_DEFAULT_HIDDEN = 'uw-hide-column-default-hidden';
|
||||
|
||||
const TABLE_UTILS_ATTR = 'table-utils';
|
||||
const TABLE_UTILS_CONTAINER_SELECTOR = `[${TABLE_UTILS_ATTR}]`;
|
||||
@ -18,6 +20,8 @@ const TABLE_PILL_CLASS = 'table-pill';
|
||||
const CELL_HIDDEN_CLASS = 'hide-columns--hidden-cell';
|
||||
const CELL_ORIGINAL_COLSPAN = 'uw-hide-column-original-colspan';
|
||||
|
||||
const HIDE_COLUMNS_INITIALIZED = 'uw-hide-columns--initialized';
|
||||
|
||||
@Utility({
|
||||
selector: `[${HIDE_COLUMNS_CONTAINER_IDENT}] table`,
|
||||
})
|
||||
@ -44,14 +48,15 @@ export class HideColumns {
|
||||
constructor(element) {
|
||||
this._autoHide = this._storageManager.load('autoHide', {}) || false;
|
||||
|
||||
if (!element) {
|
||||
if (!element)
|
||||
throw new Error('Hide Columns utility cannot be setup without an element!');
|
||||
}
|
||||
|
||||
// do not provide hide-column ability in tables inside modals, async forms with response or tail.datetime instances
|
||||
if (element.closest('[uw-modal], .async-form__response, .tail-datetime-calendar')) {
|
||||
if (element.closest('[uw-modal], .async-form__response, .tail-datetime-calendar'))
|
||||
return false;
|
||||
|
||||
if (element.classList.contains(HIDE_COLUMNS_INITIALIZED))
|
||||
return false;
|
||||
}
|
||||
|
||||
this._element = element;
|
||||
|
||||
@ -74,10 +79,12 @@ export class HideColumns {
|
||||
|
||||
this._mutationObserver = new MutationObserver(this._tableMutated.bind(this));
|
||||
this._mutationObserver.observe(this._element, { childList: true, subtree: true });
|
||||
|
||||
this._element.classList.add(HIDE_COLUMNS_INITIALIZED);
|
||||
}
|
||||
|
||||
setupHideButton(th) {
|
||||
const preHidden = this.isHiddenColumn(th);
|
||||
const preHidden = this.isHiddenTH(th);
|
||||
|
||||
const hider = document.createElement('span');
|
||||
|
||||
@ -104,20 +111,20 @@ export class HideColumns {
|
||||
hider.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.switchColumnDisplay(th, hider);
|
||||
this.switchColumnDisplay(th);
|
||||
// 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);
|
||||
const currentlyHidden = this.hiderStatus(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);
|
||||
const currentlyHidden = this.hiderStatus(th);
|
||||
this.updateHiderIcon(hider, currentlyHidden);
|
||||
});
|
||||
|
||||
@ -126,64 +133,76 @@ export class HideColumns {
|
||||
// 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);
|
||||
}
|
||||
this.switchColumnDisplay(th, preHidden);
|
||||
}
|
||||
|
||||
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);
|
||||
switchColumnDisplay(th, hidden) {
|
||||
hidden = typeof(hidden) === 'undefined' ? !this.isHiddenTH(th) : !!hidden;
|
||||
|
||||
for (let 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);
|
||||
}
|
||||
this.cellColumns(th).forEach(columnIndex => this.updateColumnDisplay(columnIndex, hidden));
|
||||
}
|
||||
|
||||
updateColumnDisplay(columnIndex, hidden) {
|
||||
this._element.getElementsByTagName('tr').forEach(row => {
|
||||
// console.debug('updateColumnDisplay', { columnIndex, hidden });
|
||||
this._element.rows.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;
|
||||
const visibleColumns = this.cellColumns(cell).reduce((count, cColumnIndex) => (cColumnIndex === columnIndex ? hidden : this.isHiddenColumn(cColumnIndex)) ? count : count + 1, 0);
|
||||
|
||||
if (hidden) {
|
||||
if (colspan > 1) {
|
||||
// if (cell.tagName === 'TH') {
|
||||
// console.debug({cell, originalColspan, colspan, visibleColumns, isHidden: cell.classList.contains(CELL_HIDDEN_CLASS)});
|
||||
// }
|
||||
|
||||
|
||||
if (visibleColumns <= 0) {
|
||||
cell.classList.add(CELL_HIDDEN_CLASS);
|
||||
} else {
|
||||
cell.classList.remove(CELL_HIDDEN_CLASS);
|
||||
|
||||
if (colspan !== visibleColumns) {
|
||||
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++;
|
||||
cell.colSpan = visibleColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const touchedColumns = new Array();
|
||||
|
||||
this.columnTHs(columnIndex)
|
||||
.forEach(th => {
|
||||
touchedColumns.push(...this.cellColumns(th));
|
||||
|
||||
if (!this._element.classList.contains(HIDE_COLUMNS_INITIALIZED))
|
||||
return;
|
||||
|
||||
const thHidden = this.cellColumns(th).every(cColumnIndex => cColumnIndex === columnIndex ? hidden : this.isHiddenColumn(cColumnIndex));
|
||||
// persist new hidden setting for column
|
||||
if (thHidden == this.isDefaultHiddenTH(th)) {
|
||||
this._storageManager.remove(this.getStorageKey(th));
|
||||
} else {
|
||||
this._storageManager.save(this.getStorageKey(th), thHidden);
|
||||
}
|
||||
});
|
||||
|
||||
touchedColumns.flatMap(cColumnIndex => this.columnTHs(cColumnIndex))
|
||||
.forEach(th => {
|
||||
const hider = this.headerToHider.get(th);
|
||||
if (!hider)
|
||||
return;
|
||||
|
||||
this.updateHider(hider, this.hiderStatus(th));
|
||||
});
|
||||
}
|
||||
|
||||
updateHider(hider, hidden) {
|
||||
// console.debug({hider, hidden, columnIndex: this.colIndex(this.hiderToHeader.get(hider)), colSpan: this.colSpan(this.hiderToHeader.get(hider))});
|
||||
|
||||
if (hidden) {
|
||||
hider.classList.remove(TABLE_HIDER_CLASS);
|
||||
hider.classList.add(TABLE_PILL_CLASS);
|
||||
@ -223,12 +242,21 @@ export class HideColumns {
|
||||
}
|
||||
|
||||
_tableMutated(mutationList) {
|
||||
// console.log('_tableMutated', mutationList, observer);
|
||||
|
||||
if (!Array.from(mutationList).some(mutation => mutation.type === 'childList'))
|
||||
return;
|
||||
|
||||
[...this._element.querySelectorAll('th')].filter(th => !th.hasAttribute(HIDE_COLUMNS_NO_HIDE)).forEach(th => this.updateColumnDisplay(this.colIndex(th), this.isHiddenColumn(th)));
|
||||
if (Array.from(mutationList).every(mRecord => mRecord.type === 'childList' && [...mRecord.addedNodes, ...mRecord.removedNodes].every(isTableHider)))
|
||||
return;
|
||||
|
||||
// console.debug('_tableMutated', { mutationList });
|
||||
|
||||
this._colIndexMemoized = undefined;
|
||||
this._getColMemoized = undefined;
|
||||
|
||||
Array.from(this._element.rows)
|
||||
.flatMap(row => Array.from(row.cells))
|
||||
.filter(th => th.tagName === 'TH' && !th.hasAttribute(HIDE_COLUMNS_NO_HIDE))
|
||||
.forEach(th => this.updateColumnDisplay(this.colIndex(th), this.isHiddenTH(th)));
|
||||
}
|
||||
|
||||
getStorageKey(th) {
|
||||
@ -257,9 +285,9 @@ export class HideColumns {
|
||||
}
|
||||
|
||||
isEmptyColumn(columnIndex) {
|
||||
for (let row of this._element.getElementsByTagName('tr')) {
|
||||
for (let row of this._element.rows) {
|
||||
const cell = this.getCol(row, columnIndex);
|
||||
if (cell.matches('th'))
|
||||
if (!cell || cell.tagName == 'TH')
|
||||
continue;
|
||||
if (cell.querySelector('.table__td-content')) {
|
||||
for (let child of cell.children) {
|
||||
@ -273,10 +301,47 @@ export class HideColumns {
|
||||
}
|
||||
}
|
||||
|
||||
isHiddenColumn(th) {
|
||||
const hidden = this._storageManager.load(this.getStorageKey(th)),
|
||||
emptyColumn = this.isEmptyColumn(this.colIndex(th));
|
||||
return hidden === true || hidden === undefined && emptyColumn && this._autoHide;
|
||||
columnTHs(columnIndex) {
|
||||
return Array.from(this._element.rows)
|
||||
.map(row => this.getCol(row, columnIndex))
|
||||
.filter(cell => cell && cell.tagName === 'TH');
|
||||
}
|
||||
|
||||
cellColumns(cell) {
|
||||
const columnIndex = this.colIndex(cell);
|
||||
return Array.from(new Array(this.colSpan(cell)), (_x, i) => columnIndex + i);
|
||||
}
|
||||
|
||||
isHiddenTH(th) {
|
||||
return this.cellColumns(th).every(columnIndex => this.isHiddenColumn(columnIndex));
|
||||
}
|
||||
|
||||
hiderStatus(th) {
|
||||
const columnsHidden = this.isHiddenTH(th);
|
||||
const shadowed = this.cellColumns(th).every(columnIndex => this.isHiddenColumn(columnIndex, this.columnTHs(columnIndex).filter(oTH => oTH !== th)));
|
||||
const isFirst = this.cellColumns(th).some(columnIndex => this.columnTHs(columnIndex)[0] === th);
|
||||
|
||||
// console.debug("hiderStatus", { th, columnsHidden, shadowed, isFirst });
|
||||
|
||||
return columnsHidden && (!shadowed || isFirst);
|
||||
}
|
||||
|
||||
isDefaultHiddenTH(th) {
|
||||
return this.cellColumns(th).every(columnIndex => this.isDefaultHiddenColumn(columnIndex, Array.of(th)));
|
||||
}
|
||||
|
||||
isHiddenColumn(columnIndex, ths) {
|
||||
ths = ths === undefined ? this.columnTHs(columnIndex) : ths;
|
||||
|
||||
const hidden = ths.map(th => this._storageManager.load(this.getStorageKey(th)));
|
||||
|
||||
return hidden.every(h => h === undefined) ? this.isDefaultHiddenColumn(columnIndex, ths) : hidden.some(h => h);
|
||||
}
|
||||
|
||||
isDefaultHiddenColumn(columnIndex, ths) {
|
||||
ths = ths === undefined ? this.columnTHs(columnIndex) : ths;
|
||||
|
||||
return this.isEmptyColumn(columnIndex) && this._autoHide || ths.some(th => th.hasAttribute(HIDE_COLUMNS_DEFAULT_HIDDEN));
|
||||
}
|
||||
|
||||
colSpan(cell) {
|
||||
@ -289,7 +354,7 @@ export class HideColumns {
|
||||
return originalColspan ? Math.max(colspan, originalColspan) : colspan;
|
||||
}
|
||||
|
||||
colIndex(cell) {
|
||||
colIndexDirect(cell) {
|
||||
if (!cell)
|
||||
return 0;
|
||||
|
||||
@ -298,23 +363,62 @@ export class HideColumns {
|
||||
if (!rowParent)
|
||||
return 0;
|
||||
|
||||
let i = 0;
|
||||
for (const sibling of Array.from(rowParent.cells).slice(0, cell.cellIndex)) {
|
||||
i += this.colSpan(sibling);
|
||||
let rowBefore = 0;
|
||||
for (const cell of Array.from(rowParent.cells).slice(0, cell.cellIndex)) {
|
||||
if (!cell)
|
||||
continue;
|
||||
|
||||
rowBefore += this.colSpan(cell);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
for (const pRow of Array.from(this._element.rows).slice(0, rowParent.rowIndex + 1)) {
|
||||
if (!pRow)
|
||||
continue;
|
||||
|
||||
let space = 0;
|
||||
for (const cell of pRow === rowParent ? Array.from(pRow.cells).slice(0, cell.cellIndex) : pRow.cells) {
|
||||
if (!cell)
|
||||
continue;
|
||||
|
||||
const rowSpan = cell.rowSpan || 1;
|
||||
if (rowParent.rowIndex - pRow.rowIndex < rowSpan)
|
||||
i += this.colSpan(cell);
|
||||
else if (pRow !== rowParent)
|
||||
space += this.colSpan(cell);
|
||||
|
||||
if (space > rowBefore)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// console.debug({ rowParent, cell, rowBefore, i });
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
getCol(row, columnIndex) {
|
||||
let c = 0;
|
||||
_colIndexMemoized;
|
||||
|
||||
for (const cell of row.cells) {
|
||||
c += cell ? this.colSpan(cell) : 1;
|
||||
colIndex(cell) {
|
||||
if (!this._colIndexMemoized)
|
||||
this._colIndexMemoized = memoize(this.colIndexDirect.bind(this));
|
||||
|
||||
if (columnIndex < c)
|
||||
return this._colIndexMemoized(cell);
|
||||
}
|
||||
|
||||
getColDirect(row, columnIndex) {
|
||||
for (const cell of row.cells)
|
||||
if (cell && this.colIndex(cell) <= columnIndex && this.colIndex(cell) + this.colSpan(cell) > columnIndex)
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
_getColMemoized;
|
||||
|
||||
getCol(row, columnIndex) {
|
||||
if (!this._getColMemoized)
|
||||
this._getColMemoized = memoize(this.getColDirect.bind(this), (row, columnIndex) => Array.of(row.rowIndex, columnIndex).toString());
|
||||
|
||||
return this._getColMemoized(row, columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,3 +430,8 @@ function isEmptyElement(element) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTableHider(element) {
|
||||
return element.classList.contains(TABLE_HIDER_CLASS)
|
||||
|| element.classList.contains(TABLE_HIDER_VISIBLE_CLASS)
|
||||
|| element.classList.contains(TABLE_PILL_CLASS);
|
||||
}
|
||||
|
||||
@ -2599,6 +2599,7 @@ CourseParticipantActive: Teilnehmer
|
||||
CourseParticipantInactive: Abgemeldet
|
||||
CourseParticipantNoShow: Nicht erschienen
|
||||
CourseUserState: Zustand
|
||||
CourseUserSheets: Übungsblätter
|
||||
|
||||
TestDownload: Download-Test
|
||||
TestDownloadMaxSize: Maximale Dateigröße
|
||||
|
||||
@ -2599,6 +2599,7 @@ CourseParticipantActive: Participant
|
||||
CourseParticipantInactive: Deregistered
|
||||
CourseParticipantNoShow: No show
|
||||
CourseUserState: State
|
||||
CourseUserSheets: Exercise sheets
|
||||
|
||||
TestDownload: Download test
|
||||
TestDownloadMaxSize: Maximum filesize
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-redundant-constraints #-}
|
||||
|
||||
module Handler.Course.Users
|
||||
( queryUser
|
||||
@ -98,6 +98,7 @@ type UserTableData = DBRow ( Entity User
|
||||
, ([Entity Tutorial], Map (CI Text) (Maybe (Entity Tutorial)))
|
||||
, [Entity Exam]
|
||||
, Maybe (Entity SubmissionGroup)
|
||||
, Map SheetName (SheetType, Maybe Points)
|
||||
)
|
||||
|
||||
instance HasEntity UserTableData User where
|
||||
@ -130,13 +131,15 @@ _userExams = _dbrOutput . _6
|
||||
_userSubmissionGroup :: Traversal' UserTableData (Entity SubmissionGroup)
|
||||
_userSubmissionGroup = _dbrOutput . _7 . _Just
|
||||
|
||||
_userSheets :: Lens' UserTableData (Map SheetName (SheetType, Maybe Points))
|
||||
_userSheets = _dbrOutput . _8
|
||||
|
||||
|
||||
colUserComment :: IsDBTable m c => TermId -> SchoolId -> CourseShorthand -> Colonnade Sortable UserTableData (DBCell m c)
|
||||
colUserComment tid ssh csh =
|
||||
sortable (Just "note") (i18nCell MsgCourseUserNote)
|
||||
$ \DBRow{ dbrOutput=(Entity uid _, _, mbNoteKey, _, _, _, _) } ->
|
||||
maybeEmpty mbNoteKey $ const $
|
||||
anchorCellM (courseLink <$> encrypt uid) (hasComment True)
|
||||
sortable (Just "note") (i18nCell MsgCourseUserNote) $ views (_dbrOutput . $(multifocusG 2) (_1 . _entityKey) _3) $ \(uid, mbNoteKey) ->
|
||||
maybeEmpty mbNoteKey $ const $
|
||||
anchorCellM (courseLink <$> encrypt uid) (hasComment True)
|
||||
where
|
||||
courseLink = CourseR tid ssh csh . CUserR
|
||||
|
||||
@ -183,6 +186,20 @@ colUserSubmissionGroup :: IsDBTable m c => Colonnade Sortable UserTableData (DBC
|
||||
colUserSubmissionGroup = sortable (Just "submission-group") (i18nCell MsgSubmissionGroup) $
|
||||
foldMap (cell . toWidget) . preview (_userSubmissionGroup . _entityVal . _submissionGroupName)
|
||||
|
||||
colUserSheets :: forall m c. IsDBTable m c => [SheetName] -> Cornice Sortable ('Cap 'Base) UserTableData (DBCell m c)
|
||||
colUserSheets shns = cap (Sortable Nothing caption) $ foldMap userSheetCol shns
|
||||
where
|
||||
caption = i18nCell MsgCourseUserSheets
|
||||
& cellAttrs <>~ [ ("uw-hide-column-header", "sheets")
|
||||
, ("uw-hide-column-default-hidden", "")
|
||||
]
|
||||
|
||||
userSheetCol :: SheetName -> Colonnade Sortable UserTableData (DBCell m c)
|
||||
userSheetCol shn = sortable (Just . SortingKey $ "sheet-" <> shn) (i18nCell shn) . views (_userSheets . at shn) $ \case
|
||||
Just (preview _grading -> Just Points{..}, Just points) -> i18nCell $ MsgAchievedOf points maxPoints
|
||||
Just (preview _grading -> Just grading', Just points) -> i18nCell . bool MsgNotPassed MsgPassed . fromMaybe False $ gradingPassed grading' points
|
||||
_other -> mempty
|
||||
|
||||
|
||||
data UserTableCsvStudyFeature = UserTableCsvStudyFeature
|
||||
{ csvUserField :: Text
|
||||
@ -310,14 +327,15 @@ data CourseUserActionData = CourseUserSendMailData
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
|
||||
makeCourseUserTable :: forall h act act'.
|
||||
makeCourseUserTable :: forall h p cols act act'.
|
||||
( Functor h, ToSortable h
|
||||
, Ord act, PathPiece act, RenderMessage UniWorX act
|
||||
, AsCornice h p UserTableData (DBCell (MForm Handler) (FormResult (First act', DBFormResult UserId Bool UserTableData))) cols
|
||||
)
|
||||
=> CourseId
|
||||
-> Map act (AForm Handler act')
|
||||
-> (UserTableExpr -> E.SqlExpr (E.Value Bool))
|
||||
-> Colonnade h UserTableData (DBCell (MForm Handler) (FormResult (First act', DBFormResult UserId Bool UserTableData)))
|
||||
-> cols
|
||||
-> PSValidator (MForm Handler) (FormResult (First act', DBFormResult UserId Bool UserTableData))
|
||||
-> Maybe (Csv.Name -> Bool)
|
||||
-> DB (FormResult (act', Set UserId), Widget)
|
||||
@ -336,12 +354,23 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
|
||||
dbtProj = traverse $ \(user, participant, E.Value userNoteId, (feature,degree,terms), subGroup) -> do
|
||||
tuts'' <- selectList [ TutorialParticipantUser ==. entityKey user, TutorialParticipantTutorial <-. map entityKey tutorials ] []
|
||||
exams' <- selectList [ ExamRegistrationUser ==. entityKey user ] []
|
||||
subs' <- E.select . E.from $ \(sheet `E.InnerJoin` (submission `E.InnerJoin` submissionUser)) -> do
|
||||
E.on $ submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId
|
||||
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
|
||||
E.&&. submissionUser E.^. SubmissionUserUser E.==. E.val (entityKey user)
|
||||
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
|
||||
return ( sheet E.^. SheetName
|
||||
, ( sheet E.^. SheetType
|
||||
, submission
|
||||
)
|
||||
)
|
||||
let
|
||||
regGroups = setOf (folded . _entityVal . _tutorialRegGroup . _Just) tutorials
|
||||
tuts' = filter (\(Entity tutId _) -> any ((== tutId) . tutorialParticipantTutorial . entityVal) tuts'') tutorials
|
||||
tuts = foldr (\tut@(Entity _ Tutorial{..}) -> maybe (over _1 $ cons tut) (over _2 . flip (Map.insertWith (<|>)) (Just tut)) tutorialRegGroup) ([], Map.fromSet (const Nothing) regGroups) tuts'
|
||||
exs = filter (\(Entity eId _) -> any ((== eId) . examRegistrationExam . entityVal) exams') exams
|
||||
return (user, participant, userNoteId, (entityVal <$> feature, entityVal <$> degree, entityVal <$> terms), tuts, exs, subGroup)
|
||||
subs = Map.fromList $ mapMaybe (over (mapped . _2 . _2) (submissionRatingPoints . entityVal) . assertM' (views (_2 . _2 . _entityVal) submissionRatingDone) . over _1 E.unValue . over (_2 . _1) E.unValue) subs'
|
||||
return (user, participant, userNoteId, (entityVal <$> feature, entityVal <$> degree, entityVal <$> terms), tuts, exs, subGroup, subs)
|
||||
dbtColonnade = colChoices
|
||||
dbtSorting = mconcat
|
||||
[ single $ sortUserNameLink queryUser -- slower sorting through clicking name column header
|
||||
@ -541,22 +570,24 @@ postCUsersR tid ssh csh = do
|
||||
E.&&. courseParticipant E.^. CourseParticipantCourse E.==. submissionGroup E.^. SubmissionGroupCourse
|
||||
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
|
||||
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val cid
|
||||
sheetList <- selectList [SheetCourse ==. cid] [Desc SheetActiveTo, Desc SheetActiveFrom]
|
||||
let exams = nubOn entityKey $ examOccurrencesPerExam ^.. folded . _1
|
||||
let colChoices = mconcat $ catMaybes
|
||||
[ pure $ dbSelect (applying _2) id (return . view (hasEntity . _entityKey))
|
||||
, pure $ colUserNameLink (CourseR tid ssh csh . CUserR)
|
||||
, guardOn showSex $ colUserSex'
|
||||
, pure $ colUserEmail
|
||||
, pure $ colUserMatriclenr
|
||||
, pure $ colUserDegreeShort
|
||||
, pure $ colUserField
|
||||
, pure $ colUserSemester
|
||||
, guardOn hasSubmissionGroups colUserSubmissionGroup
|
||||
, guardOn hasTutorials $ colUserTutorials tid ssh csh
|
||||
, guardOn hasExams $ colUserExams tid ssh csh
|
||||
, pure $ sortable (Just "registration") (i18nCell MsgRegisteredSince) (maybe mempty dateCell . preview (_Just . _userTableRegistration) . assertM' (has $ _userTableParticipant . _entityVal . _courseParticipantState . _CourseParticipantActive))
|
||||
, pure $ sortable (Just "state") (i18nCell MsgCourseUserState) (i18nCell . view (_userTableParticipant . _entityVal . _courseParticipantState))
|
||||
, pure $ colUserComment tid ssh csh
|
||||
[ pure . cap' $ dbSelect (applying _2) id (return . view (hasEntity . _entityKey))
|
||||
, pure . cap' $ colUserNameLink (CourseR tid ssh csh . CUserR)
|
||||
, guardOn showSex . cap' $ colUserSex'
|
||||
, pure . cap' $ colUserEmail
|
||||
, pure . cap' $ colUserMatriclenr
|
||||
, pure . cap' $ colUserDegreeShort
|
||||
, pure . cap' $ colUserField
|
||||
, pure . cap' $ colUserSemester
|
||||
, guardOn hasSubmissionGroups $ cap' colUserSubmissionGroup
|
||||
, guardOn hasTutorials . cap' $ colUserTutorials tid ssh csh
|
||||
, guardOn hasExams . cap' $ colUserExams tid ssh csh
|
||||
, pure . cap' $ sortable (Just "registration") (i18nCell MsgRegisteredSince) (maybe mempty dateCell . preview (_Just . _userTableRegistration) . assertM' (has $ _userTableParticipant . _entityVal . _courseParticipantState . _CourseParticipantActive))
|
||||
, pure . cap' $ sortable (Just "state") (i18nCell MsgCourseUserState) (i18nCell . view (_userTableParticipant . _entityVal . _courseParticipantState))
|
||||
, guardOn (not $ null sheetList) . colUserSheets $ map (sheetName . entityVal) sheetList
|
||||
, pure . cap' $ colUserComment tid ssh csh
|
||||
]
|
||||
psValidator = def & defaultSortingByName
|
||||
& defaultFilter (singletonMap "active" [toPathPiece True])
|
||||
|
||||
@ -42,6 +42,7 @@ module Handler.Utils.Table.Pagination
|
||||
, formCell, DBFormResult(..), getDBFormResult
|
||||
, dbSelect
|
||||
, (&)
|
||||
, cap'
|
||||
, module Control.Monad.Trans.Maybe
|
||||
, module Colonnade
|
||||
, DBSTemplateMode(..)
|
||||
@ -59,6 +60,8 @@ import Utils.Lens
|
||||
|
||||
import Import hiding (pi)
|
||||
|
||||
import qualified Data.Foldable as Foldable
|
||||
|
||||
import qualified Yesod.Form.Functions as Yesod
|
||||
|
||||
import qualified Database.Esqueleto as E
|
||||
@ -68,12 +71,11 @@ import qualified Database.Esqueleto.Internal.Sql as E (SqlSelect,unsafeSqlValue)
|
||||
import qualified Network.Wai as Wai
|
||||
|
||||
import Control.Monad.RWS (RWST(..), execRWS)
|
||||
import Control.Monad.State (evalStateT)
|
||||
import Control.Monad.State (evalStateT, execStateT)
|
||||
import Control.Monad.Trans.Maybe
|
||||
import Control.Monad.State.Class (modify)
|
||||
import qualified Control.Monad.State.Class as State
|
||||
|
||||
import Data.Foldable (Foldable(foldMap))
|
||||
import Control.Monad.Trans.Writer.Lazy (censor)
|
||||
|
||||
import Data.Map ((!))
|
||||
import qualified Data.Map as Map
|
||||
@ -90,7 +92,7 @@ import Colonnade.Encode hiding (row)
|
||||
|
||||
import Text.Hamlet (hamletFile)
|
||||
|
||||
import Data.List (elemIndex)
|
||||
import Data.List (elemIndex, inits)
|
||||
|
||||
import Data.Maybe (fromJust)
|
||||
|
||||
@ -108,7 +110,8 @@ import qualified Data.ByteString.Lazy as LBS
|
||||
|
||||
import Data.Semigroup as Sem (Semigroup(..))
|
||||
|
||||
import qualified Data.Conduit.List as C
|
||||
import qualified Data.Conduit.List as C (sourceList)
|
||||
import qualified Data.Conduit.Combinators as C
|
||||
|
||||
import Handler.Utils.DateTime (formatTimeRangeW)
|
||||
import qualified Control.Monad.Catch as Catch
|
||||
@ -1136,7 +1139,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
| otherwise
|
||||
= id
|
||||
in C.sourceList <=< lift . doHandle . runConduit $ dbtCsvComputeActions x .| C.foldMap pure
|
||||
innerAct .| C.fold accActionMap Map.empty
|
||||
innerAct .| C.foldl accActionMap Map.empty
|
||||
actionMap <- flip evalStateT Map.empty . runConduit $ sourceDiff .| transPipe lift dbtCsvComputeActions'
|
||||
|
||||
when (Map.null actionMap) $ do
|
||||
@ -1266,12 +1269,47 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
DBSTDefault{..} -> dbstmNumber rowCount
|
||||
_other -> False
|
||||
|
||||
genHeaders :: _ -> [WriterT x m Widget]
|
||||
genHeaders SortableP{..} = flip (headersMonoidal Nothing) (annotate $ dbtColonnade ^. _Cornice) $ pure
|
||||
( \Sized{ sizedSize, sizedContent = toSortable -> Sortable{..} } -> pure $ do
|
||||
genHeaders :: forall h. Cornice h _ _ (DBCell m x) -> SortableP h -> WriterT x m Widget
|
||||
genHeaders cornice SortableP{..} = execWriterT . go mempty $ annotate cornice
|
||||
where
|
||||
go :: forall (p' :: Pillar) r'.
|
||||
[(Int, Int, Int)]
|
||||
-> AnnotatedCornice (Maybe Int) h p' r' (DBCell m x)
|
||||
-> WriterT Widget (WriterT x m) ()
|
||||
go rowspanAcc (AnnotatedCorniceBase _ (Colonnade (toList -> v))) = censor wrap . forM_ (zip (inits v) v) $ \(before, OneColonnade Sized{..} _) -> do
|
||||
let (_, cellSize') = compCellSize rowspanAcc (map oneColonnadeHead before) Sized{..}
|
||||
whenIsJust cellSize' $ \cellSize -> tellM $ fromContent Sized { sizedSize = cellSize, sizedContent }
|
||||
go rowspanAcc (AnnotatedCorniceCap _ v@(toList -> oneCornices)) = do
|
||||
rowspanAcc' <- (execStateT ?? rowspanAcc) . hoist (censor wrap) . forM_ (zip (inits oneCornices) oneCornices) $ \(before, OneCornice h (size -> sz')) -> do
|
||||
let sz = Sized sz' h
|
||||
let (beforeSize, cellSize') = compCellSize rowspanAcc (concatMap (map oneColonnadeHead . toList . getColonnade . uncapAnnotated . oneCorniceBody) before) sz
|
||||
whenIsJust cellSize' $ \cellSize -> do
|
||||
let Sized{..} = sz
|
||||
lift . tellM $ fromContent Sized { sizedSize = cellSize, sizedContent }
|
||||
if | [n] <- mapMaybe (\(key, val) -> guardOnM (is _Rowspan key) $ readMay val) (toSortable sizedContent ^. _sortableContent . cellAttrs)
|
||||
-> State.modify $ (:) (n, beforeSize, cellSize)
|
||||
| otherwise -> return ()
|
||||
let rowspanAcc'' = rowspanAcc'
|
||||
& traverse . _1 %~ pred
|
||||
whenIsJust (flattenAnnotated v) $ go rowspanAcc''
|
||||
|
||||
compCellSize :: forall h' c. [(Int, Int, Int)] -> [Sized (Maybe Int) h' c] -> Sized (Maybe Int) h' c -> (Int, Maybe Int)
|
||||
compCellSize rowspanAcc before Sized{..} = (beforeSize,) . assertM' (> 0) $ fromMaybe 1 sizedSize - shadowed
|
||||
where Sum beforeSize = foldMap (\(Sized sz _) -> Sum $ fromMaybe 1 sz) before
|
||||
Sum shadowed = flip foldMap rowspanAcc $ \(rowsRem, firstCol, sz) -> fromMaybe mempty $ do
|
||||
guard $ rowsRem > 0
|
||||
guard $ firstCol <= beforeSize
|
||||
guard $ beforeSize < firstCol + sz
|
||||
return . Sum $ sz - (beforeSize - firstCol)
|
||||
|
||||
wrap :: Widget -> Widget
|
||||
wrap row = case dbsTemplate of
|
||||
DBSTCourse{} -> row
|
||||
DBSTDefault{} -> $(widgetFile "table/header")
|
||||
fromContent :: Sized Int h (DBCell m x) -> WriterT x m Widget
|
||||
fromContent Sized{ sizedSize = cellSize, sizedContent = toSortable -> Sortable{..} } = do
|
||||
widget <- sortableContent ^. cellContents
|
||||
let
|
||||
cellSize = fromMaybe 1 sizedSize
|
||||
directions = [dir | SortingSetting k dir <- psSorting, Just k == sortableKey ]
|
||||
isSortable = isJust sortableKey
|
||||
isSorted dir = fromMaybe False $ (==) <$> (SortingSetting <$> sortableKey <*> pure dir) <*> listToMaybe psSorting
|
||||
@ -1280,12 +1318,8 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
|
||||
case dbsTemplate of
|
||||
DBSTCourse{} -> return $(widgetFile "table/course/header")
|
||||
DBSTDefault{} -> return $(widgetFile "table/cell/header")
|
||||
, case dbsTemplate of
|
||||
DBSTCourse{} -> id
|
||||
DBSTDefault{} -> pure . (>>= \row -> return $(widgetFile "table/header")) . foldMapM id
|
||||
)
|
||||
in do
|
||||
wHeaders <- maybe (return Nothing) (fmap Just . foldMapM id . genHeaders) pSortable
|
||||
wHeaders <- maybe (return Nothing) (fmap Just . genHeaders (dbtColonnade ^. _Cornice)) pSortable
|
||||
case dbsTemplate of
|
||||
DBSTCourse c l r s a -> do
|
||||
wRows <- forM rows $ \row' -> let
|
||||
@ -1611,3 +1645,27 @@ dbSelect resLens selLens genIndex = Colonnade.singleton (headednessPure $ mempty
|
||||
genForm _ mkUnique = do
|
||||
(selResult, selWidget) <- mreq checkBoxField (fsUniq mkUnique "select") (Just False)
|
||||
return (set selLens <$> selResult, [whamlet|^{fvWidget selWidget}|])
|
||||
|
||||
|
||||
cap' :: ( AsCornice Sortable p r' (DBCell m x) colonnade
|
||||
, IsDBTable m x
|
||||
)
|
||||
=> colonnade
|
||||
-> Cornice Sortable ('Cap p) r' (DBCell m x)
|
||||
cap' (view _Cornice -> cornice) = case cornice of
|
||||
CorniceBase Colonnade{..}
|
||||
| [OneColonnade{..}] <- toList getColonnade
|
||||
-> recap (oneColonnadeHead & _sortableContent . cellAttrs %~ incRowspan) cornice
|
||||
CorniceCap cornices
|
||||
-> CorniceCap $ fmap (\OneCornice{..} -> OneCornice { oneCorniceHead = oneCorniceHead & _sortableContent . cellAttrs %~ incRowspan, oneCorniceBody = cap' oneCorniceBody }) cornices
|
||||
other
|
||||
-> recap (fromSortable . Sortable Nothing $ cell mempty) other
|
||||
where
|
||||
incRowspan :: [(Text, Text)] -> [(Text, Text)]
|
||||
incRowspan attrs
|
||||
| [n] <- mapMaybe (\(key, val) -> guardOnM (is _Rowspan key) $ readMay val) attrs
|
||||
= (_Rowspan # (), tshow (succ n :: Natural)) : filter (hasn't $ _1 . _Rowspan) attrs
|
||||
| otherwise = (_Rowspan # (), "2") : filter (hasn't $ _1 . _Rowspan) attrs
|
||||
|
||||
_Rowspan :: Prism' Text ()
|
||||
_Rowspan = prism' (\() -> "rowspan") $ flip guardOn () . ((==) `on` CI.mk) "rowspan"
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
|
||||
module Handler.Utils.Table.Pagination.Types
|
||||
( FilterKey(FilterKey), SortingKey(SortingKey)
|
||||
, Sortable(..)
|
||||
, Sortable(..), _sortableKey, _sortableContent
|
||||
, sortable
|
||||
, ToSortable(..), FromSortable(..)
|
||||
, SortableP(..)
|
||||
, IsSortable, _Sortable
|
||||
, DBTableInvalid(..)
|
||||
, AsCornice(..)
|
||||
) where
|
||||
@ -30,6 +31,8 @@ data Sortable a = Sortable
|
||||
, sortableContent :: a
|
||||
}
|
||||
|
||||
makeLenses_ ''Sortable
|
||||
|
||||
sortable :: Maybe SortingKey -> c -> (a -> c) -> Colonnade Sortable a c
|
||||
sortable k h = singleton (Sortable k h)
|
||||
|
||||
@ -69,6 +72,12 @@ instance FromSortable Headless where
|
||||
fromSortable _ = Headless
|
||||
|
||||
|
||||
type IsSortable h = (ToSortable h, FromSortable h)
|
||||
|
||||
_Sortable :: IsSortable h => Prism' (h a) (Sortable a)
|
||||
_Sortable = prism' fromSortable $ \x -> ($ x) . toSortable <$> pSortable
|
||||
|
||||
|
||||
data DBTableInvalid = DBTIRowsMissing Int
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ import Control.Arrow as Utils ((>>>))
|
||||
import Control.Monad.Trans.Except (ExceptT(..), throwE, runExceptT)
|
||||
import Control.Monad.Except (MonadError(..))
|
||||
import Control.Monad.Trans.Maybe as Utils (MaybeT(..))
|
||||
import Control.Monad.Trans.Writer.Lazy (execWriterT, tell)
|
||||
import Control.Monad.Trans.Writer.Lazy (WriterT, execWriterT, tell)
|
||||
import Control.Monad.Catch
|
||||
import Control.Monad.Morph (hoist)
|
||||
import Control.Monad.Fail
|
||||
@ -802,6 +802,9 @@ diffTimeout timeoutLength timeoutRes act = fromMaybe timeoutRes <$> timeout time
|
||||
= let (MkFixed micro :: Micro) = realToFrac timeoutLength
|
||||
in fromInteger micro
|
||||
|
||||
tellM :: (Monad m, Monoid x) => m x -> WriterT x m ()
|
||||
tellM = tell <=< lift
|
||||
|
||||
-------------
|
||||
-- Conduit --
|
||||
-------------
|
||||
|
||||
@ -31,7 +31,7 @@ extra-deps:
|
||||
- git: git@gitlab2.rz.ifi.lmu.de:uni2work/xss-sanitize.git
|
||||
commit: 074ed7c8810aca81f60f2c535f9e7bad67e9d95a
|
||||
- git: git@gitlab2.rz.ifi.lmu.de:uni2work/colonnade.git
|
||||
commit: 65164334e9704afc24603a9f3197b4581c996ad8
|
||||
commit: f8170266ab25b533576e96715bedffc5aa4f19fa
|
||||
subdirs:
|
||||
- colonnade
|
||||
|
||||
|
||||
@ -144,12 +144,12 @@ packages:
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/colonnade.git
|
||||
pantry-tree:
|
||||
size: 481
|
||||
sha256: c7137405813404f4e0d2334d67876ab7150e7dc7f8b9f23ad452c5ee76ce4737
|
||||
commit: 65164334e9704afc24603a9f3197b4581c996ad8
|
||||
sha256: 392393652cc0f354d351482557b9385c8e6122e706359b030373656565f2e045
|
||||
commit: f8170266ab25b533576e96715bedffc5aa4f19fa
|
||||
original:
|
||||
subdir: colonnade
|
||||
git: git@gitlab2.rz.ifi.lmu.de:uni2work/colonnade.git
|
||||
commit: 65164334e9704afc24603a9f3197b4581c996ad8
|
||||
commit: f8170266ab25b533576e96715bedffc5aa4f19fa
|
||||
- completed:
|
||||
hackage: hsass-0.8.0@sha256:82d55fb2a10342accbc4fe80d263163f40a138d8636e275aa31ffa81b14abf01,2792
|
||||
pantry-tree:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
$newline never
|
||||
<th .table__th *{attrs} :isSortable:.sortable :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc uw-hide-column-header=#{maybe "" toPathPiece sortableKey} :cellSize /= 1:colspan=#{cellSize}>
|
||||
<th .table__th *{attrs} :isSortable:.sortable :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc :is _Just sortableKey:uw-hide-column-header=#{maybe "" toPathPiece sortableKey} :cellSize /= 1:colspan=#{cellSize}>
|
||||
$maybe flag <- sortableKey
|
||||
$case directions
|
||||
$of [SortAsc]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user