From af6a21438e640bbfe22247c07ecc9e1771f75a17 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Wed, 3 Apr 2019 23:21:12 +0200 Subject: [PATCH 01/69] add new JS utility registry and proof-of-concept utility --- src/Foundation.hs | 27 ++++---- static/js/utils/poc.js | 33 +++++++++ static/js/utils/registry.js | 109 ++++++++++++++++++++++++++++++ static/js/utils/setup.js | 114 -------------------------------- templates/default-layout.hamlet | 2 +- 5 files changed, 157 insertions(+), 128 deletions(-) create mode 100644 static/js/utils/poc.js create mode 100644 static/js/utils/registry.js delete mode 100644 static/js/utils/setup.js diff --git a/src/Foundation.hs b/src/Foundation.hs index 0d8e5d909..a15dd5a9b 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1065,19 +1065,20 @@ siteLayout' headingOverride widget = do addScript $ StaticR js_polyfills_fetchPolyfill_js addScript $ StaticR js_polyfills_urlPolyfill_js -- JavaScript utils - addScript $ StaticR js_utils_alerts_js - addScript $ StaticR js_utils_asidenav_js - addScript $ StaticR js_utils_asyncForm_js - addScript $ StaticR js_utils_asyncTable_js - addScript $ StaticR js_utils_asyncTableFilter_js - addScript $ StaticR js_utils_checkAll_js - addScript $ StaticR js_utils_httpClient_js - addScript $ StaticR js_utils_form_js - addScript $ StaticR js_utils_inputs_js - addScript $ StaticR js_utils_modal_js - addScript $ StaticR js_utils_setup_js - addScript $ StaticR js_utils_showHide_js - addScript $ StaticR js_utils_tabber_js + -- addScript $ StaticR js_utils_alerts_js + -- addScript $ StaticR js_utils_asidenav_js + -- addScript $ StaticR js_utils_asyncForm_js + -- addScript $ StaticR js_utils_asyncTable_js + -- addScript $ StaticR js_utils_asyncTableFilter_js + -- addScript $ StaticR js_utils_checkAll_js + -- addScript $ StaticR js_utils_httpClient_js + -- addScript $ StaticR js_utils_form_js + -- addScript $ StaticR js_utils_inputs_js + -- addScript $ StaticR js_utils_modal_js + addScript $ StaticR js_utils_registry_js + addScript $ StaticR js_utils_poc_js + -- addScript $ StaticR js_utils_showHide_js + -- addScript $ StaticR js_utils_tabber_js addStylesheet $ StaticR css_utils_alerts_scss addStylesheet $ StaticR css_utils_asidenav_scss addStylesheet $ StaticR css_utils_asyncForm_scss diff --git a/static/js/utils/poc.js b/static/js/utils/poc.js new file mode 100644 index 000000000..5d87a0f82 --- /dev/null +++ b/static/js/utils/poc.js @@ -0,0 +1,33 @@ +(function() { + + var UTIL_NAME = 'poc'; + var UTIL_SELECTOR = '[uw-poc]'; + + var util = function(element) { + + function _init() { + var color = 'red'; + if (element.dataset.color) { + color = element.dataset.color; + } + element.style.outline = '1px solid ' + color; + } + + _init(); + + return { + name: UTIL_NAME, + element: element, + destroy: function() {}, + }; + }; + + if (UtilRegistry) { + UtilRegistry.register({ + name: UTIL_NAME, + selector: UTIL_SELECTOR, + setup: util, + }); + } + +})(); diff --git a/static/js/utils/registry.js b/static/js/utils/registry.js new file mode 100644 index 000000000..775b58fe4 --- /dev/null +++ b/static/js/utils/registry.js @@ -0,0 +1,109 @@ +(function() { + 'use strict'; + + var registeredUtils = []; + var activeUtilInstances = []; + + // Registry + // (revealing module pattern) + window.UtilRegistry = (function() { + + /** + * function registerUtil + * + * utils need to have at least these properties: + * name: string | utils name, e.g. 'example' + * selector: string | utils selector, e.g. '[uw-example]' + * setup: Function | utils setup function, see below + * + * setup function must return instance object with at least these properties: + * name: string | utils name + * element: HTMLElement | element the util is applied to + * destroy: Function | function to destroy the util and remove any listeners + * + * @param util Object Utility that should be added to the registry + */ + function registerUtil(util) { + console.log('registering util', util); + registeredUtils.push(util); + } + + function deregisterUtil(name, destroy) { + var utilIndex = _findUtilIndex(name); + + if (utilIndex >= 0) { + if (destroy === true) { + _destroyUtilInstances(name); + } + + registeredUtils.splice(utilIndex, 1); + } + } + + function setupAllUtils() { + console.log('setting up all registered utils'); + registeredUtils.forEach(function(util) { + setupUtil(util); + }); + } + + function setupUtil(util, scope) { + console.log('setting up util', { util }); + scope = scope || document; + + if (util && typeof util.setup === 'function') { + const elements = _findUtilElements(util, scope); + + elements.forEach(function(element) { + var utilInstance = util.setup(element); + if (utilInstance) { + activeUtilInstances.push(utilInstance); + } + }); + } + } + + function findUtil(name) { + return registeredUtils.find(function(util) { + return util.name === name; + }); + } + + function _findUtilElements(util, scope) { + return Array.from(scope.querySelectorAll(util.selector)); + } + + function _findUtilIndex(name) { + return registeredUtils.findIndex(function(util) { + return util.name === name; + }); + } + + function _destroyUtilInstances(name) { + console.log('TODO: destroy util instances', { name }); + // call destroy on each activeUtilInstance that matches the name + } + + // public API + return { + register: registerUtil, + deregister: deregisterUtil, + setupAll: setupAllUtils, + setup: setupUtil, + find: findUtil, + } + })(); + + document.addEventListener('DOMContentLoaded', function() { + window.UtilRegistry.setupAll(); + }); + + + // REMOVE ME. JUST HERE TO AVOID JS ERRORS + window.utils = { + setup: function(name) { + console.log('not really setting up', name); + }, + }; + +})(); diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js deleted file mode 100644 index bb3bb0e3d..000000000 --- a/static/js/utils/setup.js +++ /dev/null @@ -1,114 +0,0 @@ -(function() { - 'use strict'; - - window.utils = window.utils || {}; - - var registeredSetupListeners = {}; - var activeInstances = {}; - -/** - * setup function to initiate a util (utilName) on a scope (sope) with options (options). - * - * Utils need to be defined as property of `window.utils` and need to accept a scope and (optionally) options. - * Example: `window.utils.autoSubmit = function(scope, options) { ... };` - */ - - window.utils.setup = function(utilName, scope, options) { - if (!utilName || !scope) { - return; - } - - options = options || {}; - - var utilInstance; - - // i18n - if (window.I18N) { - options.i18n = window.I18N; - } - - if (activeInstances[utilName]) { - var instanceWithSameScope = activeInstances[utilName] - .filter(function(instance) { return !!instance; }) - .find(function(instance) { - return instance.scope === scope; - }); - var isAlreadySetup = !!instanceWithSameScope; - - if (isAlreadySetup) { - console.warn('Trying to setup a JS utility that\'s already been set up', { utility: utilName, scope, options }); - if (!options.force) { - return false; - } - } - } - - function setup() { - var listener = function(event) { - if (event.detail.targetUtil !== utilName) { - return false; - } - - if (options.setupFunction) { - utilInstance = options.setupFunction(scope, options); - } else { - var util = window.utils[utilName]; - if (!util) { - throw new Error('"' + utilName + '" is not a known js util'); - } - - utilInstance = util(scope, options); - } - - if (utilInstance) { - if (activeInstances[utilName] && Array.isArray(activeInstances[utilName])) { - activeInstances[utilName].push(utilInstance); - } else { - activeInstances[utilName] = [ utilInstance ]; - } - } - }; - - if (registeredSetupListeners[utilName] && Array.isArray(registeredSetupListeners[utilName])) { - window.utils.teardown(utilName); - } - - if (!registeredSetupListeners[utilName] || Array.isArray(registeredSetupListeners[utilName])) { - registeredSetupListeners[utilName] = []; - } - registeredSetupListeners[utilName].push(listener); - - document.addEventListener('setup', listener); - - document.dispatchEvent(new CustomEvent('setup', { - detail: { targetUtil: utilName, module: 'none' }, - bubbles: true, - cancelable: true, - })); - } - - setup(); - - return utilInstance; - }; - - window.utils.teardown = function(utilName, destroy) { - if (registeredSetupListeners[utilName]) { - registeredSetupListeners[utilName] - .filter(function(listener) { return !!listener }) - .forEach(function(listener) { - document.removeEventListener('setup', listener); - }); - delete registeredSetupListeners[utilName]; - } - - if (destroy === true && activeInstances[utilName]) { - activeInstances[utilName] - .filter(function(instance) { return !!instance }) - .forEach(function(instance) { - instance.destroy(); - }); - delete activeInstances[utilName]; - } - } -})(); diff --git a/templates/default-layout.hamlet b/templates/default-layout.hamlet index f43282fb0..7a679af27 100644 --- a/templates/default-layout.hamlet +++ b/templates/default-layout.hamlet @@ -6,7 +6,7 @@ $if not isModal
-
+
$if not isModal From 6d824d33928a39c66054cc814648787213ce2984 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Wed, 3 Apr 2019 23:23:53 +0200 Subject: [PATCH 02/69] WIP: refactor show hide JS utility to work with new registry --- src/Foundation.hs | 2 +- static/js/utils/showHide.js | 106 ++++++++++++++++++++++-------------- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index a15dd5a9b..a0d739bc7 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1077,7 +1077,7 @@ siteLayout' headingOverride widget = do -- addScript $ StaticR js_utils_modal_js addScript $ StaticR js_utils_registry_js addScript $ StaticR js_utils_poc_js - -- addScript $ StaticR js_utils_showHide_js + addScript $ StaticR js_utils_showHide_js -- addScript $ StaticR js_utils_tabber_js addStylesheet $ StaticR css_utils_alerts_scss addStylesheet $ StaticR css_utils_asidenav_scss diff --git a/static/js/utils/showHide.js b/static/js/utils/showHide.js index 0441cde4b..6689b83bd 100644 --- a/static/js/utils/showHide.js +++ b/static/js/utils/showHide.js @@ -1,77 +1,99 @@ (function() { 'use strict'; - window.utils = window.utils || {}; + var UTIL_NAME = 'showHide'; + var UTIL_SELECTOR = '[uw-show-hide]'; var JS_INITIALIZED_CLASS = 'js-show-hide-initialized'; var LOCAL_STORAGE_SHOW_HIDE = 'SHOW_HIDE'; - var SHOW_HIDE_TOGGLE_CLASS = 'js-show-hide__toggle'; var SHOW_HIDE_COLLAPSED_CLASS = 'js-show-hide--collapsed'; var SHOW_HIDE_TARGET_CLASS = 'js-show-hide__target'; /** - * div - * div.js-show-hide__toggle - * toggle here - * div - * content here + *
+ *
+ * [toggle here] + *
+ * [content here] */ - window.utils.showHide = function(wrapper, options) { + var util = function(element) { - options = options || {}; + function _init() { + if (!element) { + throw new Error('ShowHide utility cannot be setup without an element!'); + } - function addEventHandler(el) { - el.addEventListener('click', function elClickListener() { - var newState = el.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS); - updateLSState(el.dataset.shIndex || null, newState); + if (element.classList.contains(JS_INITIALIZED_CLASS)) { + return false; + } + + _addEventHandler(); + } + + function _addEventHandler() { + element.addEventListener('click', function clickListener() { + console.log('showhide clicked'); + var newState = element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS); + _updateLSState(element.dataset.shIndex || null, newState); }); } - function updateLSState(index, state) { + function _updateLSState(index, state) { if (!index) { return false; } - var lsData = getLocalStorageData(); + var lsData = _getLocalStorageData(); lsData[index] = state; window.localStorage.setItem(LOCAL_STORAGE_SHOW_HIDE, JSON.stringify(lsData)); } - function collapsedStateInLocalStorage(index) { - var lsState = getLocalStorageData(); - return lsState[index]; - } + // function _getCollapsedLSState(index) { + // var lsState = _getLocalStorageData(); + // return lsState[index]; + // } - function getLocalStorageData() { + function _getLocalStorageData() { return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SHOW_HIDE)) || {}; } - Array - .from(wrapper.querySelectorAll('.' + SHOW_HIDE_TOGGLE_CLASS)) - .forEach(function(el) { - if (el.classList.contains(JS_INITIALIZED_CLASS)) { - return false; - } + // var showHides = Array.from(scope.querySelectorAll(UTIL_SELECTOR)); + // showHides.forEach(function(el) { + // if (el.classList.contains(JS_INITIALIZED_CLASS)) { + // return false; + // } - var index = el.dataset.shIndex || null; - var isCollapsed = el.dataset.collapsed === 'true'; - var lsCollapsedState = collapsedStateInLocalStorage(index); - if (typeof lsCollapsedState !== 'undefined') { - isCollapsed = lsCollapsedState; - } - el.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS, isCollapsed); + // var index = el.dataset.shIndex || null; + // var isCollapsed = el.dataset.collapsed === 'true'; + // var lsCollapsedState = _getCollapsedLSState(index); + // if (typeof lsCollapsedState !== 'undefined') { + // isCollapsed = lsCollapsedState; + // } + // el.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS, isCollapsed); - Array.from(el.parentElement.children).forEach(function(el) { - if (!el.classList.contains('' + SHOW_HIDE_TOGGLE_CLASS)) { - el.classList.add(SHOW_HIDE_TARGET_CLASS); - } - }); - el.classList.add(JS_INITIALIZED_CLASS); - addEventHandler(el); - }); + // Array.from(el.parentElement.children).forEach(function(el) { + // if (!el.matches(UTIL_SELECTOR)) { + // el.classList.add(SHOW_HIDE_TARGET_CLASS); + // } + // }); + // el.classList.add(JS_INITIALIZED_CLASS); + // _addEventHandler(el); + // }); + + _init(); return { - scope: wrapper, + name: UTIL_NAME, + element: element, destroy: function() {}, }; }; + + if (UtilRegistry) { + UtilRegistry.register({ + name: UTIL_NAME, + selector: UTIL_SELECTOR, + setup: util + }); + } + })(); From ff59d0a41269e105865cf589fac364de34935bf7 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Wed, 3 Apr 2019 23:31:22 +0200 Subject: [PATCH 03/69] move JS UtilRegistry to top of imports to ensure its present in scope --- src/Foundation.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index a0d739bc7..365a64ccf 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1065,6 +1065,7 @@ siteLayout' headingOverride widget = do addScript $ StaticR js_polyfills_fetchPolyfill_js addScript $ StaticR js_polyfills_urlPolyfill_js -- JavaScript utils + addScript $ StaticR js_utils_registry_js -- addScript $ StaticR js_utils_alerts_js -- addScript $ StaticR js_utils_asidenav_js -- addScript $ StaticR js_utils_asyncForm_js @@ -1075,7 +1076,6 @@ siteLayout' headingOverride widget = do -- addScript $ StaticR js_utils_form_js -- addScript $ StaticR js_utils_inputs_js -- addScript $ StaticR js_utils_modal_js - addScript $ StaticR js_utils_registry_js addScript $ StaticR js_utils_poc_js addScript $ StaticR js_utils_showHide_js -- addScript $ StaticR js_utils_tabber_js From 6da0850add13840203ac7d4eec71a5ce90262099 Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Thu, 4 Apr 2019 18:01:46 +0200 Subject: [PATCH 04/69] Filter-UI course participants improved --- src/Database/Esqueleto/Utils.hs | 41 +++++++++++++++++++++++++-------- src/Handler/Course.hs | 19 ++++++++++++--- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/Database/Esqueleto/Utils.hs b/src/Database/Esqueleto/Utils.hs index f3aec73aa..6c89e6c96 100644 --- a/src/Database/Esqueleto/Utils.hs +++ b/src/Database/Esqueleto/Utils.hs @@ -5,8 +5,9 @@ module Database.Esqueleto.Utils , isInfixOf, hasInfix , any, all , SqlIn(..) - , mkExactFilter, mkContainsFilter - , anyFilter + , mkExactFilter, mkExactFilterWith + , mkContainsFilter + , anyFilter, allFilter ) where import ClassyPrelude.Yesod hiding (isInfixOf, any, all) @@ -74,13 +75,22 @@ _queryFeaturesDegree = $(sqlIJproj 3 2) -- Given a lens-like function, make filter for exact matches in a collection -- (Generalizing from Set to Foldable ok here, but gives ambigouus types elsewhere) mkExactFilter :: (PersistField a) - => (t -> E.SqlExpr (E.Value a)) -- ^ getter from query to searched element + => (t -> E.SqlExpr (E.Value a)) -- ^ getter from query to searched element -> t -- ^ query row -> Set.Set a -- ^ needle collection -> E.SqlExpr (E.Value Bool) -mkExactFilter lenslike row criterias +mkExactFilter = mkExactFilterWith id + +-- | like @mkExactFiler@ but allows for conversion; convenient in conjunction with @anyFilter@ and @allFilter@ +mkExactFilterWith :: (PersistField b) + => (a -> b) -- ^ type conversion + -> (t -> E.SqlExpr (E.Value b)) -- ^ getter from query to searched element + -> t -- ^ query row + -> Set.Set a -- ^ needle collection + -> E.SqlExpr (E.Value Bool) +mkExactFilterWith cast lenslike row criterias | Set.null criterias = true - | otherwise = lenslike row `E.in_` E.valList (Set.toList criterias) + | otherwise = lenslike row `E.in_` E.valList (cast <$> Set.toList criterias) -- | generic filter creation for dbTable -- Given a lens-like function, make filter searching for needles in String-like elements @@ -94,9 +104,22 @@ mkContainsFilter lenslike row criterias | Set.null criterias = true | otherwise = any (hasInfix $ lenslike row) criterias - -anyFilter :: (Foldable f) => f (t -> Set.Set Text-> E.SqlExpr (E.Value Bool)) - -> t -> Set.Set Text-> E.SqlExpr (E.Value Bool) +-- | Combine several filters, using logical or +anyFilter :: (Foldable f) + => f (t -> Set.Set Text-> E.SqlExpr (E.Value Bool)) + -> t + -> Set.Set Text + -> E.SqlExpr (E.Value Bool) anyFilter fltrs needle criterias = F.foldr aux false fltrs where - aux fltr acc = fltr needle criterias E.||. acc \ No newline at end of file + aux fltr acc = fltr needle criterias E.||. acc + +-- | Combine several filters, using logical and +allFilter :: (Foldable f) + => f (t -> Set.Set Text-> E.SqlExpr (E.Value Bool)) + -> t + -> Set.Set Text + -> E.SqlExpr (E.Value Bool) +allFilter fltrs needle criterias = F.foldr aux true fltrs + where + aux fltr acc = fltr needle criterias E.&&. acc \ No newline at end of file diff --git a/src/Handler/Course.hs b/src/Handler/Course.hs index 5d4ec2bf9..98016ca8e 100644 --- a/src/Handler/Course.hs +++ b/src/Handler/Course.hs @@ -862,15 +862,28 @@ makeCourseUserTable cid colChoices psValidator = do , fltrUserEmail queryUser , fltrUserMatriclenr queryUser , fltrUserNameEmail queryUser - -- , ("course-user-degree", error "TODO") -- TODO - -- , ("field" , FilterColumn $ queryFeaturesField error "TODO") -- TODO - , ("semesternr", FilterColumn $ E.mkExactFilter $ queryFeaturesStudy >>> (E.?. StudyFeaturesSemester)) + , ("field-name" , FilterColumn $ E.mkContainsFilter $ queryFeaturesField >>> (E.?. StudyTermsName)) + , ("field-short" , FilterColumn $ E.mkContainsFilter $ queryFeaturesField >>> (E.?. StudyTermsShorthand)) + , ("field-key" , FilterColumn $ E.mkExactFilter $ queryFeaturesField >>> (E.?. StudyTermsKey)) + , ("field" , FilterColumn $ E.anyFilter + [ E.mkContainsFilter $ queryFeaturesField >>> (E.?. StudyTermsName) + , E.mkContainsFilter $ queryFeaturesField >>> (E.?. StudyTermsShorthand) + , E.mkExactFilterWith readMay $ queryFeaturesField >>> (E.?. StudyTermsKey) + ] ) + , ("degree" , FilterColumn $ E.anyFilter + [ E.mkContainsFilter $ queryFeaturesDegree >>> (E.?. StudyDegreeName) + , E.mkContainsFilter $ queryFeaturesDegree >>> (E.?. StudyDegreeShorthand) + , E.mkExactFilterWith readMay $ queryFeaturesDegree >>> (E.?. StudyDegreeKey) + ] ) + , ("semesternr" , FilterColumn $ E.mkExactFilter $ queryFeaturesStudy >>> (E.?. StudyFeaturesSemester)) -- , ("course-registration", error "TODO") -- TODO -- , ("course-user-note", error "TODO") -- TODO ] dbtFilterUI mPrev = mconcat [ fltrUserNameEmailUI mPrev , fltrUserMatriclenrUI mPrev + , prismAForm (singletonFilter "degree") mPrev $ aopt (searchField False) (fslI MsgStudyFeatureDegree) + , prismAForm (singletonFilter "field") mPrev $ aopt (searchField False) (fslI MsgCourseStudyFeature) ] dbtParams = DBParamsForm { dbParamsFormMethod = POST From 40c6f1296860b20945472bb6afc0a926cf8bd0e6 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Fri, 5 Apr 2019 13:31:01 +0200 Subject: [PATCH 05/69] Only set submission rated if it was assigned to uploader Fixes #330 --- src/Handler/Utils/Submission.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Handler/Utils/Submission.hs b/src/Handler/Utils/Submission.hs index 124da1b83..67c8fab75 100644 --- a/src/Handler/Utils/Submission.hs +++ b/src/Handler/Utils/Submission.hs @@ -462,10 +462,10 @@ sinkSubmission userId mExists isUpdate = do case isUpdate of False -> lift . insert_ $ SubmissionEdit userId now submissionId True -> do - Submission{submissionRatingTime} <- lift $ getJust submissionId - when (isNothing submissionRatingTime) $ tellSt mempty { sinkSubmissionNotifyRating = Any True } - lift $ update submissionId [ SubmissionRatingBy =. Just userId, SubmissionRatingTime =. Just now ] - -- TODO: Should submissionRatingAssigned change here if userId changes? + Submission{submissionRatingTime, submissionRatingBy} <- lift $ getJust submissionId + when (submissionRatingBy == Just userId) $ do + when (isNothing submissionRatingTime) $ tellSt mempty { sinkSubmissionNotifyRating = Any True } + lift $ update submissionId [ SubmissionRatingTime =. Just now ] tellSt $ mempty{ sinkSubmissionTouched = Any True } finalize :: SubmissionSinkState -> YesodJobDB UniWorX () From eedd4714f994b27f16370b34447583aef8c6a778 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Fri, 5 Apr 2019 13:56:32 +0200 Subject: [PATCH 06/69] Fix `colRated` to use `submissionRatingDone`, as it should --- src/Handler/Corrections.hs | 1 + src/Handler/Sheet.hs | 3 ++- templates/widgets/rating/rating.hamlet | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Handler/Corrections.hs b/src/Handler/Corrections.hs index 9381e0829..42c21d62a 100644 --- a/src/Handler/Corrections.hs +++ b/src/Handler/Corrections.hs @@ -161,6 +161,7 @@ colRating = sortable (Just "rating") (i18nCell MsgRating) $ \DBRow{ dbrOutput=(E mkRoute = do cid <- encrypt subId return $ CSubmissionR tid ssh csh sheetName cid CorrectionR + mTuple mA mB = (,) <$> mA <*> mB -- Hamlet does not support enough haskell-syntax for this in mconcat [ anchorCellM mkRoute $(widgetFile "widgets/rating/rating") , writerCell $ do diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 7faa02e29..cc5bc7718 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -199,11 +199,12 @@ getSheetListR tid ssh csh = do let stats = sheetTypeSum sheetType in -- for statistics over all shown rows case mbSub of Nothing -> cellTell mempty $ stats Nothing - (Just (Entity sid Submission{..})) -> + (Just (Entity sid sub@Submission{..})) -> let mkCid = encrypt sid mkRoute = do cid' <- mkCid return $ CSubmissionR tid ssh csh sheetName cid' CorrectionR + mTuple mA mB = (,) <$> mA <*> mB -- Hamlet does not support enough haskell-syntax for this acell = anchorCellM mkRoute $(widgetFile "widgets/rating/rating") in cellTell acell $ stats submissionRatingPoints diff --git a/templates/widgets/rating/rating.hamlet b/templates/widgets/rating/rating.hamlet index 5c42595e2..a2fb74fe0 100644 --- a/templates/widgets/rating/rating.hamlet +++ b/templates/widgets/rating/rating.hamlet @@ -1,8 +1,10 @@ $# Display Rating, expects +$# sub :: Submission +$# submissionRatingDone :: Submission -> Bool $# submissionRatingPoints :: Maybe points -$maybe points <- submissionRatingPoints - $maybe grading <- preview _grading sheetType +$if submissionRatingDone sub + $maybe (grading, points) <- mTuple (preview _grading sheetType) submissionRatingPoints $case grading $of Points{..} _{MsgAchievedOf points maxPoints} From 819ec36073700913549d63117214998b641ff0a8 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Fri, 5 Apr 2019 20:48:31 +0200 Subject: [PATCH 07/69] autofocus on campus login --- src/Auth/LDAP.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auth/LDAP.hs b/src/Auth/LDAP.hs index 5233faaf3..2131bf527 100644 --- a/src/Auth/LDAP.hs +++ b/src/Auth/LDAP.hs @@ -66,7 +66,7 @@ campusForm :: ( RenderMessage site FormMessage , Button site ButtonSubmit ) => AForm (HandlerT site IO) CampusLogin campusForm = CampusLogin - <$> areq ciField (fslpI MsgCampusIdent "user.name@campus.lmu.de" & setTooltip MsgCampusIdentNote) Nothing + <$> areq ciField (fslpI MsgCampusIdent "user.name@campus.lmu.de" & setTooltip MsgCampusIdentNote & addAttr "autofocus" "") Nothing <*> areq passwordField (fslI MsgCampusPassword) Nothing campusLogin :: forall site. From 5e71e8c9e6fa8deb00efcc4781f9df7c1687ee7d Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 4 Apr 2019 22:57:53 +0200 Subject: [PATCH 08/69] rework show-hide js utility --- static/css/utils/asidenav.scss | 19 +-- static/css/utils/showHide.scss | 24 +-- static/js/utils/modal.js | 16 +- static/js/utils/registry.js | 9 +- static/js/utils/showHide.js | 145 ++++++++++--------- templates/adminTest.hamlet | 2 +- templates/table/layout-filter-default.hamlet | 2 +- templates/table/layout-filter-default.lucius | 4 + templates/widgets/asidenav/asidenav.hamlet | 6 +- 9 files changed, 125 insertions(+), 102 deletions(-) diff --git a/static/css/utils/asidenav.scss b/static/css/utils/asidenav.scss index 1ac580d58..101bf5506 100644 --- a/static/css/utils/asidenav.scss +++ b/static/css/utils/asidenav.scss @@ -55,10 +55,6 @@ .asidenav__box-title { font-size: 18px; padding-left: 10px; - - &.js-show-hide__toggle::before { - z-index: 1; - } } } } @@ -94,18 +90,9 @@ margin-top: 30px; background-color: transparent; transition: all .2s ease; - padding: 30px 13px 10px; + padding: 10px 13px; margin: 0; border-bottom: 1px solid var(--color-grey); - - &.js-show-hide__toggle { - - &::before { - left: auto; - right: 20px; - color: var(--color-font); - } - } } /* LOGO */ @@ -361,9 +348,5 @@ background-color: var(--color-lightwhite); } } - - .js-show-hide__toggle::before { - content: none; - } } } diff --git a/static/css/utils/showHide.scss b/static/css/utils/showHide.scss index ab82286b8..1f85fbf36 100644 --- a/static/css/utils/showHide.scss +++ b/static/css/utils/showHide.scss @@ -1,10 +1,9 @@ $show-hide-toggle-size: 6px; -.js-show-hide__toggle { +.show-hide__toggle { position: relative; cursor: pointer; - padding: 3px 7px; &:hover { background-color: var(--color-grey-lighter); @@ -12,32 +11,33 @@ $show-hide-toggle-size: 6px; } } -.js-show-hide__toggle::before { +.show-hide__toggle::before { content: ''; position: absolute; width: $show-hide-toggle-size; height: $show-hide-toggle-size; left: -15px; - top: 12px - $show-hide-toggle-size / 2; + top: 50%; color: var(--color-primary); border-right: 2px solid currentColor; border-top: 2px solid currentColor; transition: transform .2s ease; - transform-origin: ($show-hide-toggle-size / 2); - transform: translateY($show-hide-toggle-size) rotate(-45deg); + transform: translateY(-50%) rotate(-45deg); } -.js-show-hide__target { - transition: all .2s ease; +.show-hide__toggle--right::before { + left: auto; + right: 20px; + color: var(--color-font); } -.js-show-hide--collapsed { +.show-hide--collapsed { - .js-show-hide__toggle::before { - transform: translateY($show-hide-toggle-size / 3) rotate(135deg); + .show-hide__toggle::before { + transform: translateY(-50%) rotate(135deg); } - .js-show-hide__target { + :not(.show-hide__toggle) { display: block; height: 0; margin: 0; diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index a5971edf7..50307f3db 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -2,6 +2,20 @@ 'use strict'; window.utils = window.utils || {}; + // ######################## + // TODO: make use of selector + // or think of a different way to dynamically initialize widgets + // with selectors with specific ids like #modal-hident69 + // + // Idee: + // Alles wegschmeißen zu dynamischen IDs. Util init rein über Selector '[uw-...]' + // bedarf Änderung in Templates. + // Ausserdem müssen sich Utils bei Event 'util-setup-ready' registrieren als Util + // utils.setup wird dann überflüssig, bzw. wird zu einer Registry / Controller + // der die utils bei DomCOntentLoaded intialisiert. + // + // ######################## + var SELECTOR = '[uw-modal]'; var JS_INITIALIZED_CLASS = 'js-modal-initialized'; var MODAL_OPEN_CLASS = 'modal--open'; @@ -17,7 +31,7 @@ var OVERLAY_OPEN_CLASS = 'modal__overlay--open'; var CLOSER_CLASS = 'modal__closer'; - window.utils.modal = function(modalElement, options) { + window.utils.modal = function(scope, options) { if (!modalElement || modalElement.classList.contains(JS_INITIALIZED_CLASS)) { return; diff --git a/static/js/utils/registry.js b/static/js/utils/registry.js index 775b58fe4..9acff588d 100644 --- a/static/js/utils/registry.js +++ b/static/js/utils/registry.js @@ -55,7 +55,14 @@ const elements = _findUtilElements(util, scope); elements.forEach(function(element) { - var utilInstance = util.setup(element); + var utilInstance = null; + + try { + utilInstance = util.setup(element); + } catch(err) { + console.warn('Error while trying to initialize a utility!', { util , element, err }); + } + if (utilInstance) { activeUtilInstances.push(utilInstance); } diff --git a/static/js/utils/showHide.js b/static/js/utils/showHide.js index 6689b83bd..cd3a6e4ac 100644 --- a/static/js/utils/showHide.js +++ b/static/js/utils/showHide.js @@ -1,98 +1,113 @@ (function() { 'use strict'; - var UTIL_NAME = 'showHide'; - var UTIL_SELECTOR = '[uw-show-hide]'; + var SHOW_HIDE_UTIL_NAME = 'showHide'; + var SHOW_HIDE_UTIL_SELECTOR = '[uw-show-hide]'; - var JS_INITIALIZED_CLASS = 'js-show-hide-initialized'; - var LOCAL_STORAGE_SHOW_HIDE = 'SHOW_HIDE'; - var SHOW_HIDE_COLLAPSED_CLASS = 'js-show-hide--collapsed'; - var SHOW_HIDE_TARGET_CLASS = 'js-show-hide__target'; + var SHOW_HIDE_LOCAL_STORAGE_KEY = 'SHOW_HIDE'; + var SHOW_HIDE_INITIALIZED_CLASS = 'show-hide--initialized'; + var SHOW_HIDE_COLLAPSED_CLASS = 'show-hide--collapsed'; + var SHOW_HIDE_TOGGLE_CLASS = 'show-hide__toggle'; + var SHOW_HIDE_TOGGLE_RIGHT_CLASS = 'show-hide__toggle--right'; /** + * + * ShowHide Utility + * + * Attribute: uw-show-hide + * + * Params: (all optional) + * data-show-hide-id: string + * If this param is given the state of the utility will be persisted in the clients local storage. + * data-show-hide-collapsed: boolean property + * If this param is present the ShowHide utility will be collapsed. This value will be overruled by any value stored in the LocalStorage. + * data-show-hide-align: 'right' + * Where to put the arrow that marks the element as a ShowHide toggle. Left of toggle by default. + * + * Example usage: *
- *
- * [toggle here] - *
- * [content here] + *
Click me + *
This will be toggled + *
This will be toggled as well */ - var util = function(element) { + var showHideUtil = function(element) { - function _init() { + var showHideId; + + function init() { if (!element) { throw new Error('ShowHide utility cannot be setup without an element!'); } - if (element.classList.contains(JS_INITIALIZED_CLASS)) { - return false; + if (element.classList.contains(SHOW_HIDE_INITIALIZED_CLASS)) { + throw new Error('ShowHide utility already initialized!'); } - _addEventHandler(); + // register click listener + addClickListener(); + + // param showHideId + if (element.dataset.showHideId) { + showHideId = element.dataset.showHideId; + } + + // param showHideCollapsed + var collapsed = false; + if (element.dataset.showHideCollapsed !== undefined) { + collapsed = true; + } + if (showHideId) { + var localStorageCollapsed = getLocalStorage()[showHideId]; + if (typeof localStorageCollapsed !== 'undefined') { + collapsed = localStorageCollapsed; + } + } + element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS, collapsed); + + // param showHideAlign + var alignment = element.dataset.showHideAlign; + if (alignment === 'right') { + element.classList.add(SHOW_HIDE_TOGGLE_RIGHT_CLASS); + } + + // mark as initialized + element.classList.add(SHOW_HIDE_INITIALIZED_CLASS, SHOW_HIDE_TOGGLE_CLASS); + + return { + name: SHOW_HIDE_UTIL_NAME, + element: element, + destroy: function() {}, + }; } - function _addEventHandler() { + function addClickListener() { element.addEventListener('click', function clickListener() { - console.log('showhide clicked'); var newState = element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS); - _updateLSState(element.dataset.shIndex || null, newState); + + if (showHideId) { + setLocalStorage(showHideId, newState); + } }); } - function _updateLSState(index, state) { - if (!index) { - return false; - } - var lsData = _getLocalStorageData(); - lsData[index] = state; - window.localStorage.setItem(LOCAL_STORAGE_SHOW_HIDE, JSON.stringify(lsData)); + function setLocalStorage(id, state) { + var lsData = getLocalStorage(); + lsData[id] = state; + window.localStorage.setItem(SHOW_HIDE_LOCAL_STORAGE_KEY, JSON.stringify(lsData)); } - // function _getCollapsedLSState(index) { - // var lsState = _getLocalStorageData(); - // return lsState[index]; - // } - - function _getLocalStorageData() { - return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SHOW_HIDE)) || {}; + function getLocalStorage() { + return JSON.parse(window.localStorage.getItem(SHOW_HIDE_LOCAL_STORAGE_KEY)) || {}; } - // var showHides = Array.from(scope.querySelectorAll(UTIL_SELECTOR)); - // showHides.forEach(function(el) { - // if (el.classList.contains(JS_INITIALIZED_CLASS)) { - // return false; - // } - - // var index = el.dataset.shIndex || null; - // var isCollapsed = el.dataset.collapsed === 'true'; - // var lsCollapsedState = _getCollapsedLSState(index); - // if (typeof lsCollapsedState !== 'undefined') { - // isCollapsed = lsCollapsedState; - // } - // el.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS, isCollapsed); - - // Array.from(el.parentElement.children).forEach(function(el) { - // if (!el.matches(UTIL_SELECTOR)) { - // el.classList.add(SHOW_HIDE_TARGET_CLASS); - // } - // }); - // el.classList.add(JS_INITIALIZED_CLASS); - // _addEventHandler(el); - // }); - - _init(); - - return { - name: UTIL_NAME, - element: element, - destroy: function() {}, - }; + return init(); }; if (UtilRegistry) { UtilRegistry.register({ - name: UTIL_NAME, - selector: UTIL_SELECTOR, - setup: util + name: SHOW_HIDE_UTIL_NAME, + selector: SHOW_HIDE_UTIL_SELECTOR, + setup: showHideUtil }); } diff --git a/templates/adminTest.hamlet b/templates/adminTest.hamlet index 7e59d9599..b595fe813 100644 --- a/templates/adminTest.hamlet +++ b/templates/adminTest.hamlet @@ -6,7 +6,7 @@ Der Handler sollte jeweils aktuelle Beispiele für alle möglichen Funktionalitäten enthalten, so dass man immer weiß, wo man nachschlagen kann.
-

Teilweise funktionierende Abschnitte +

Teilweise funktionierende Abschnitte