Merge branch 'master' into 624-case-insensitive-suche-nach-ident-email-in-usersr

This commit is contained in:
Sarah Vaupel 2021-11-24 22:15:48 +01:00
commit 9a5e53c648
12 changed files with 241 additions and 48 deletions

View File

@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [25.22.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.3...v25.22.4) (2021-10-26)
### Bug Fixes
* **routes:** make access to workflows free ([29c54db](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/29c54db06f01659a3a6419009964a85cd11d5441))
## [25.22.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.2...v25.22.3) (2021-10-21)
### Bug Fixes
* **navigation:** always link workflows nav to instances ([adf9709](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/adf9709567d9a320f2c17d3c5dde940c2f9d8862))
## [25.22.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.1...v25.22.2) (2021-10-13)
## [25.22.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.22.0...v25.22.1) (2021-10-02)

View File

@ -0,0 +1,22 @@
export class FrontendTooltips {
static addToolTip(element, text) {
let tooltipWrap = document.createElement('span');
tooltipWrap.className = 'tooltip';
let tooltipContent = document.createElement('span');
tooltipContent.className = 'tooltip__content';
tooltipContent.appendChild(document.createTextNode(text));
tooltipWrap.append(tooltipContent);
let tooltipHandle = document.createElement('span');
tooltipHandle.className = 'tooltip__handle';
let icon = document.createElement('i');
icon.classList.add('fas');
icon.classList.add('fa-question-circle');
tooltipHandle.append(icon);
tooltipWrap.append(tooltipHandle);
element.append(tooltipWrap);
}
}

17
frontend/src/messages.js Normal file
View File

@ -0,0 +1,17 @@
export class Translations {
static translations = {
'checkrangeTooltip' : {
'de' : 'Shift-Klick, um mehrere Zellen zu markieren.',
'en' : 'Shift-click to mark multiple cells.',
},
};
static getTranslation(key, language) {
let json = Translations.translations[key];
if(language === 'en') {
return json.en;
} else {
return json.de;
}
}
};

View File

@ -21,6 +21,8 @@ export class CheckAll {
_tableIndices;
_lastCheckedCell = null;
constructor(element, app) {
if (!element) {
throw new Error('Check All utility cannot be setup without an element!');
@ -41,7 +43,9 @@ export class CheckAll {
if (DEBUG_MODE > 0)
console.log(this._columns);
this._findCheckboxColumns().forEach(columnId => this._checkAllColumns.push(new CheckAllColumn(this._element, app, this._columns[columnId], this._eventManager)));
let checkboxColumns = this._findCheckboxColumns();
checkboxColumns.forEach(columnId => this._checkAllColumns.push(new CheckAllColumn(this._element, app, this._columns[columnId])));
// mark initialized
this._element.classList.add(CHECK_ALL_INITIALIZED_CLASS);
@ -116,6 +120,7 @@ class CheckAllColumn {
this._checkAllCheckbox = document.createElement('input');
this._checkAllCheckbox.setAttribute('type', 'checkbox');
this._checkAllCheckbox.setAttribute('id', this._checkboxId);
th.insertBefore(this._checkAllCheckbox, th.firstChild);
// set up new checkbox

View File

@ -0,0 +1,125 @@
import { Utility } from '../../core/utility';
import { TableIndices } from '../../lib/table/table';
import { FrontendTooltips } from '../../lib/tooltips/frontend-tooltips';
import { Translations } from '../../messages';
const CHECKRANGE_INITIALIZED_CLASS = 'checkrange--initialized';
const CHECKBOX_SELECTOR = '[type="checkbox"]';
@Utility({
selector: 'table:not([uw-no-check-all])',
})
export class CheckRange {
_lastCheckedCell = null;
_element;
_tableIndices
_columns = new Array();
constructor(element) {
if(!element) {
throw new Error('Check Range Utility cannot be setup without an element');
}
this._element = element;
if (this._element.classList.contains(CHECKRANGE_INITIALIZED_CLASS))
return false;
this._tableIndices = new TableIndices(this._element);
this._gatherColumns();
let checkboxColumns = this._findCheckboxColumns();
checkboxColumns.forEach(columnId => this._setUpShiftClickOnColumn(columnId));
this._element.classList.add(CHECKRANGE_INITIALIZED_CLASS);
}
_setUpShiftClickOnColumn(columnId) {
if (!this._columns || columnId < 0 || columnId >= this._columns.length) return;
let column = this._columns[columnId];
let language = document.documentElement.lang;
let toolTipMessage = Translations.getTranslation('checkrangeTooltip', language);
FrontendTooltips.addToolTip(column[0], toolTipMessage);
column.forEach(el => el.addEventListener('click', (ev) => {
if(ev.shiftKey && this.lastCheckedCell !== null) {
let lastClickedIndex = this._tableIndices.rowIndex(this._lastCheckedCell);
let currentCellIndex = this._tableIndices.rowIndex(el);
let cell = this._columns[columnId][currentCellIndex];
if(currentCellIndex > lastClickedIndex)
this._handleCellsInBetween(cell, lastClickedIndex, currentCellIndex, columnId);
else
this._handleCellsInBetween(cell, currentCellIndex, lastClickedIndex, columnId);
} else {
this._lastCheckedCell = el;
}
}));
}
_handleCellsInBetween(cell, firstRowIndex, lastRowIndex, columnId) {
if(this._isChecked(cell)) {
this._uncheckMultipleCells(firstRowIndex, lastRowIndex, columnId);
} else {
this._checkMultipleCells(firstRowIndex, lastRowIndex, columnId);
}
}
_checkMultipleCells(firstRowIndex, lastRowIndex, columnId) {
for(let i=firstRowIndex; i<=lastRowIndex; i++) {
let cell = this._columns[columnId][i];
if (cell.tagName !== 'TH') {
cell.querySelector(CHECKBOX_SELECTOR).checked = true;
}
}
}
_uncheckMultipleCells(firstRowIndex, lastRowIndex, columnId) {
for(let i=firstRowIndex; i<=lastRowIndex; i++) {
let cell = this._columns[columnId][i];
if (cell.tagName !== 'TH') {
cell.querySelector(CHECKBOX_SELECTOR).checked = false;
}
}
}
_isChecked(cell) {
return cell.querySelector(CHECKBOX_SELECTOR).checked;
}
_gatherColumns() {
for (const rowIndex of Array(this._tableIndices.maxRow + 1).keys()) {
for (const colIndex of Array(this._tableIndices.maxCol + 1).keys()) {
const cell = this._tableIndices.getCell(rowIndex, colIndex);
if (!cell)
continue;
if (!this._columns[colIndex])
this._columns[colIndex] = new Array();
this._columns[colIndex][rowIndex] = cell;
}
}
}
_findCheckboxColumns() {
let checkboxColumnIds = new Array();
this._columns.forEach((col, i) => {
if (this._isCheckboxColumn(col)) {
checkboxColumnIds.push(i);
}
});
return checkboxColumnIds;
}
_isCheckboxColumn(col) {
return col.every(cell => cell.tagName == 'TH' || cell.querySelector(CHECKBOX_SELECTOR))
&& col.some(cell => cell.querySelector(CHECKBOX_SELECTOR));
}
}

View File

@ -0,0 +1,5 @@
# Checkrange Utility
Is set on the table header of a specific row. Remembers the last checked checkbox. When the users shift-clicks another checkbox in the same row, all checkboxes in between are also checked.
# Attribute: table:not([uw-no-check-all]
(will be setup on all tables which use the util check-all)

View File

@ -2,6 +2,7 @@ import { Checkbox } from './checkbox';
import { FileInput } from './file-input';
import { FileMaxSize } from './file-max-size';
import { Password } from './password';
import { CheckRange } from './checkrange';
import './inputs.sass';
import './radio-group.sass';
@ -11,4 +12,5 @@ export const InputUtils = [
FileInput,
FileMaxSize,
Password,
CheckRange,
];

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "uni2work",
"version": "25.22.2",
"version": "25.22.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "uni2work",
"version": "25.22.2",
"version": "25.22.4",
"description": "",
"keywords": [],
"author": "",

View File

@ -1,5 +1,5 @@
name: uniworx
version: 25.22.2
version: 25.22.4
dependencies:
- base
- yesod

4
routes
View File

@ -78,7 +78,7 @@
/global-workflows/instances/#WorkflowInstanceName GlobalWorkflowInstanceR:
/edit GWIEditR GET POST
/delete GWIDeleteR GET POST
/workflows GWIWorkflowsR GET !¬empty
/workflows GWIWorkflowsR GET !free
/initiate GWIInitiateR GET POST !workflow
/update GWIUpdateR POST
/global-workflows GlobalWorkflowWorkflowListR GET !free
@ -145,7 +145,7 @@
/workflows/instances/#WorkflowInstanceName SchoolWorkflowInstanceR:
/edit SWIEditR GET POST
/delete SWIDeleteR GET POST
/workflows SWIWorkflowsR GET !¬empty
/workflows SWIWorkflowsR GET !free
/initiate SWIInitiateR GET POST !workflow
/update SWIUpdateR POST
/workflows SchoolWorkflowWorkflowListR GET !free

View File

@ -573,8 +573,8 @@ navLinkAccess NavLink{..} = case navAccess' of
defaultLinks :: ( MonadHandler m
, HandlerSite m ~ UniWorX
, MonadThrow m
, WithRunDB SqlReadBackend (HandlerFor UniWorX) m
-- , MonadThrow m
-- , WithRunDB SqlReadBackend (HandlerFor UniWorX) m
, BearerAuthSite UniWorX
) => m [Nav]
defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header.
@ -761,12 +761,14 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
, do
guardVolatile clusterVolatileWorkflowsEnabled
authCtx <- getAuthContext
(haveInstances, haveWorkflows) <- lift . memcachedBy (Just . Right $ 2 * diffMinute) (NavCacheHaveTopWorkflowsInstances authCtx) . useRunDB $ (,)
<$> haveTopWorkflowInstances
<*> haveTopWorkflowWorkflows
-- authCtx <- getAuthContext
-- (haveInstances, haveWorkflows) <- lift . memcachedBy (Just . Right $ 2 * diffMinute) (NavCacheHaveTopWorkflowsInstances authCtx) . useRunDB $ (,)
-- <$> haveTopWorkflowInstances
-- <*> haveTopWorkflowWorkflows
if | haveInstances -> return NavHeader
mUserId <- maybeAuthId
-- if | haveInstances -> return NavHeader
if | isJust mUserId -> return NavHeader
{ navHeaderRole = NavHeaderPrimary
, navIcon = IconMenuWorkflows
, navLink = NavLink
@ -778,18 +780,18 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
, navForceActive = False
}
}
| haveWorkflows -> return NavHeader
{ navHeaderRole = NavHeaderPrimary
, navIcon = IconMenuWorkflows
, navLink = NavLink
{ navLabel = MsgMenuTopWorkflowWorkflowListHeader
, navRoute = TopWorkflowWorkflowListR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
}
-- | haveWorkflows -> return NavHeader
-- { navHeaderRole = NavHeaderPrimary
-- , navIcon = IconMenuWorkflows
-- , navLink = NavLink
-- { navLabel = MsgMenuTopWorkflowWorkflowListHeader
-- , navRoute = TopWorkflowWorkflowListR
-- , navAccess' = NavAccessTrue
-- , navType = NavTypeLink { navModal = False }
-- , navQuick' = mempty
-- , navForceActive = False
-- }
-- }
| otherwise -> mzero
, return NavHeaderContainer
{ navHeaderRole = NavHeaderPrimary
@ -2730,34 +2732,35 @@ haveWorkflowWorkflows rScope = hoist liftHandler . withReaderT (projectBackend @
lift $ anyM roles evalRole
haveTopWorkflowInstances, haveTopWorkflowWorkflows
-- haveTopWorkflowInstances,
haveTopWorkflowWorkflows
:: ( MonadHandler m, HandlerSite m ~ UniWorX
, BackendCompatible SqlReadBackend backend
, BearerAuthSite UniWorX
)
=> ReaderT backend m Bool
haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do
roles <- memcachedBy @(Set ((RouteWorkflowScope, WorkflowInstanceName), WorkflowRole UserId)) (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do
let
getInstances = E.selectSource . E.from $ \workflowInstance -> do
E.where_ . isTopWorkflowScopeSql $ workflowInstance E.^. WorkflowInstanceScope
return workflowInstance
instanceRoles (Entity _ WorkflowInstance{..}) = do
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope
wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph
return . Set.mapMonotonic ((rScope, workflowInstanceName), ) . fold $ do
WGN{..} <- wiGraph ^.. _wgNodes . folded
WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded
return wgeActors
runConduit $ transPipe lift getInstances .| C.foldMapM instanceRoles
let
evalRole :: _ -> ReaderT SqlReadBackend (HandlerFor UniWorX) Bool
evalRole ((rScope, win), role) = do
let route = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR)
is _Authorized <$> hasWorkflowRole Nothing role route False
lift $ anyM roles evalRole
-- haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do
-- roles <- memcachedBy @(Set ((RouteWorkflowScope, WorkflowInstanceName), WorkflowRole UserId)) (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do
-- let
-- getInstances = E.selectSource . E.from $ \workflowInstance -> do
-- E.where_ . isTopWorkflowScopeSql $ workflowInstance E.^. WorkflowInstanceScope
-- return workflowInstance
-- instanceRoles (Entity _ WorkflowInstance{..}) = do
-- rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope
-- wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph
-- return . Set.mapMonotonic ((rScope, workflowInstanceName), ) . fold $ do
-- WGN{..} <- wiGraph ^.. _wgNodes . folded
-- WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded
-- return wgeActors
-- runConduit $ transPipe lift getInstances .| C.foldMapM instanceRoles
--
-- let
-- evalRole :: _ -> ReaderT SqlReadBackend (HandlerFor UniWorX) Bool
-- evalRole ((rScope, win), role) = do
-- let route = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR)
-- is _Authorized <$> hasWorkflowRole Nothing role route False
--
-- lift $ anyM roles evalRole
haveTopWorkflowWorkflows = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do
roles <- memcachedBy (Just $ Right diffDay) NavCacheHaveTopWorkflowWorkflowsRoles $ do
let