From bd334c73d560da2ae5b8a68759d771eaa9bfeabe Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 21:54:43 +0100 Subject: [PATCH 01/10] expose scope of js util as part of return object --- static/js/utils/alerts.js | 1 + static/js/utils/asidenav.js | 1 + static/js/utils/asyncForm.js | 1 + static/js/utils/asyncTable.js | 1 + static/js/utils/asyncTableFilter.js | 1 + static/js/utils/checkAll.js | 1 + static/js/utils/form.js | 4 ++++ static/js/utils/inputs.js | 5 +++++ static/js/utils/modal.js | 1 + static/js/utils/showHide.js | 1 + static/js/utils/tabber.js | 4 ---- 11 files changed, 17 insertions(+), 4 deletions(-) diff --git a/static/js/utils/alerts.js b/static/js/utils/alerts.js index 03f36b7aa..b854495a0 100644 --- a/static/js/utils/alerts.js +++ b/static/js/utils/alerts.js @@ -89,6 +89,7 @@ alertElements.forEach(initAlert); return { + scope: alertsEl, destroy: function() {}, }; }; diff --git a/static/js/utils/asidenav.js b/static/js/utils/asidenav.js index cbe43fb4f..bb95f6455 100644 --- a/static/js/utils/asidenav.js +++ b/static/js/utils/asidenav.js @@ -57,6 +57,7 @@ initAsidenavSubmenus(); return { + scope: asideEl, destroy: function() {}, }; }; diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index 0ce2ed986..89ce162a2 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -58,6 +58,7 @@ setup(); return { + scope: formElement, destroy: function() {}, }; }; diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 0eeb7528d..ea6458633 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -225,6 +225,7 @@ init(); return { + scope: wrapper, destroy: destroyUtils, }; }; diff --git a/static/js/utils/asyncTableFilter.js b/static/js/utils/asyncTableFilter.js index 478eba9d1..98d9cda75 100644 --- a/static/js/utils/asyncTableFilter.js +++ b/static/js/utils/asyncTableFilter.js @@ -159,6 +159,7 @@ setup(); return { + scope: formElement, destroy: function() {}, }; } diff --git a/static/js/utils/checkAll.js b/static/js/utils/checkAll.js index 8cbeb28b6..b37a89454 100644 --- a/static/js/utils/checkAll.js +++ b/static/js/utils/checkAll.js @@ -124,6 +124,7 @@ init(); return { + scope: wrapper, destroy: destroy, }; }; diff --git a/static/js/utils/form.js b/static/js/utils/form.js index 0c919ee8f..8dc8642a2 100644 --- a/static/js/utils/form.js +++ b/static/js/utils/form.js @@ -54,6 +54,7 @@ } return { + scope: form, destroy: destroyUtils, }; }; @@ -97,6 +98,7 @@ } return { + scope: form, destroy: function() {}, }; }; @@ -138,6 +140,7 @@ } return { + scope: form, destroy: function() {}, }; }; @@ -149,6 +152,7 @@ } return { + scope: form, destroy: function() {}, }; }; diff --git a/static/js/utils/inputs.js b/static/js/utils/inputs.js index 4d8f79946..85229e678 100644 --- a/static/js/utils/inputs.js +++ b/static/js/utils/inputs.js @@ -44,6 +44,7 @@ } return { + scope: wrapper, destroy: destroyUtils, }; }; @@ -135,6 +136,7 @@ }); return { + scope: input, destroy: function() {}, }; } @@ -169,6 +171,7 @@ setup(); return { + scope: input, destroy: function() {}, }; } @@ -195,6 +198,7 @@ } return { + scope: input, destroy: function() {}, }; } @@ -218,6 +222,7 @@ } return { + scope: input, destroy: function() {}, }; } diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 4273c5d4d..8bf15bd1a 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -146,6 +146,7 @@ } return { + scope: modalElement, destroy: destroyUtils, }; }; diff --git a/static/js/utils/showHide.js b/static/js/utils/showHide.js index 9f7a4a4df..0441cde4b 100644 --- a/static/js/utils/showHide.js +++ b/static/js/utils/showHide.js @@ -70,6 +70,7 @@ }); return { + scope: wrapper, destroy: function() {}, }; }; diff --git a/static/js/utils/tabber.js b/static/js/utils/tabber.js index e0dd952fe..38fb43578 100644 --- a/static/js/utils/tabber.js +++ b/static/js/utils/tabber.js @@ -86,9 +86,5 @@ $(t).tabgroup(); }); } - - return { - destroy: function() {}, - }; }); })($); From e0c7edc1ca23edf68abbd51c34699584910ebe59 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 22:23:08 +0100 Subject: [PATCH 02/10] pass i18n object to each js util instance --- static/js/utils/setup.js | 5 +++++ templates/default-layout.julius | 16 +++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 6ed7c4a35..5a32a6166 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -22,6 +22,11 @@ options = options || {}; + // i18n + if (window.I18N) { + options.i18n = window.I18N; + } + var listener = function(event) { if (event.detail.targetUtil !== utilName) { diff --git a/templates/default-layout.julius b/templates/default-layout.julius index 52f28f0d7..d83daccd0 100644 --- a/templates/default-layout.julius +++ b/templates/default-layout.julius @@ -35,14 +35,16 @@ function setupDatepicker(wrapper) { }); } -document.addEventListener('DOMContentLoaded', function() { - var I18N = { - filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated - selectFile: 'Datei auswählen', - selectFiles: 'Datei(en) auswählen', - }; +// this global I18N object will be picked up automatically by the setup util +window.I18N = { + filesSelected: 'Dateien ausgewählt', // TODO: interpolate these to be translated + selectFile: 'Datei auswählen', + selectFiles: 'Datei(en) auswählen', + asyncFormFailure: 'Da ist etwas schief gelaufen, das tut uns Leid.
Falls das erneut passiert schicke uns gerne eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben.

Vielen Dank für deine Hilfe', +}; +document.addEventListener('DOMContentLoaded', function() { window.utils.setup('flatpickr', document.body, { setupFunction: setupDatepicker }); window.utils.setup('showHide', document.body); - window.utils.setup('inputs', document.body, { i18n: I18N }); + window.utils.setup('inputs', document.body); }); From 010718dfab9a70e6c555500f13be8f806de1c092 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 3 Mar 2019 23:18:26 +0100 Subject: [PATCH 03/10] store active js util instances to be able to easier destroy them --- static/js/utils/setup.js | 58 +++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 5a32a6166..2b34a8161 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -4,6 +4,7 @@ window.utils = window.utils || {}; var registeredSetupListeners = {}; + var activeInstances = {}; /** * setup function to initiate a util (utilName) on a scope (sope) with options (options). @@ -13,20 +14,33 @@ */ window.utils.setup = function(utilName, scope, options) { - - var utilInstance; - 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 }); + } + } + + function setup() { var listener = function(event) { if (event.detail.targetUtil !== utilName) { @@ -42,15 +56,25 @@ } 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] && !options.singleton) { + } + + if (!registeredSetupListeners[utilName] || Array.isArray(registeredSetupListeners[utilName])) { + registeredSetupListeners[utilName] = []; + } registeredSetupListeners[utilName].push(listener); - } else { - registeredSetupListeners[utilName] = [ listener ]; - } document.addEventListener('setup', listener); @@ -59,16 +83,32 @@ bubbles: true, cancelable: true, })); + } + + setup(); return utilInstance; }; - window.utils.teardown = function(utilName) { + + window.utils.teardown = function(utilName, destroy) { if (registeredSetupListeners[utilName]) { - registeredSetupListeners[utilName].forEach(function(listener) { + 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) { + console.log({ instance }); + instance.destroy(); + }); + delete activeInstances[utilName]; + } } })(); From fb26d70c40216f2d4662fa9ab27bb97cd883606b Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Mar 2019 19:22:28 +0100 Subject: [PATCH 04/10] show failure message on failed async form requests --- static/js/utils/asyncForm.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index 89ce162a2..d57199e72 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -6,9 +6,12 @@ var ASYNC_FORM_RESPONSE_CLASS = 'async-form-response'; var ASYNC_FORM_LOADING_CLASS = 'async-form-loading'; var ASYNC_FORM_MIN_DELAY = 600; + var DEFAULT_FAILURE_MESSAGE = 'The response we received from the server did not match what we expected. Please let us know this happened via the help widget in the top navigation.'; window.utils.asyncForm = function(formElement, options) { + options = options || {}; + var lastRequestTimestamp = 0; function setup() { @@ -47,11 +50,21 @@ window.utils.httpClient.post(url, headers, body) .then(function(response) { - return response.json(); + if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header + return response.json(); + } else { + throw new TypeError('Response from "' + url + '" has unexpected Content-Type. Expected: "application/json". Received: "' + (response.headers.get("content-type") || '(undefined)') + '"'); + } }).then(function(response) { - processResponse(response[0]) + processResponse(response[0]); }).catch(function(error) { - console.error('could not fetch or process response from ' + url, { error }); + var failureMessage = DEFAULT_FAILURE_MESSAGE; + if (options.i18n && options.i18n.asyncFormFailure) { + failureMessage = options.i18n.asyncFormFailure; + } + processResponse({ content: failureMessage }); + + formElement.classList.remove(ASYNC_FORM_LOADING_CLASS); }); } From d730c369b5cc51ffddf6e8fc2e3dce8c042c1f9c Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Mar 2019 19:28:30 +0100 Subject: [PATCH 05/10] fix js indentation inconsistencies --- static/js/utils/setup.js | 48 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index 2b34a8161..dc2f2c2e3 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -41,21 +41,20 @@ } 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'); + var listener = function(event) { + if (event.detail.targetUtil !== utilName) { + return false; } - utilInstance = util(scope, options); + 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) { @@ -64,11 +63,11 @@ } else { activeInstances[utilName] = [ utilInstance ]; } - } - }; + } + }; if (registeredSetupListeners[utilName] && Array.isArray(registeredSetupListeners[utilName])) { - window.utils.teardown(utilName); + window.utils.teardown(utilName); } if (!registeredSetupListeners[utilName] || Array.isArray(registeredSetupListeners[utilName])) { @@ -76,13 +75,13 @@ } registeredSetupListeners[utilName].push(listener); - document.addEventListener('setup', listener); + document.addEventListener('setup', listener); - document.dispatchEvent(new CustomEvent('setup', { - detail: { targetUtil: utilName, module: 'none' }, - bubbles: true, - cancelable: true, - })); + document.dispatchEvent(new CustomEvent('setup', { + detail: { targetUtil: utilName, module: 'none' }, + bubbles: true, + cancelable: true, + })); } setup(); @@ -90,14 +89,13 @@ 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); - }); + document.removeEventListener('setup', listener); + }); delete registeredSetupListeners[utilName]; } From b2cb12d02912b25a839e4c554a0f43d30c75d058 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Mar 2019 19:30:45 +0100 Subject: [PATCH 06/10] remove obsolete js console.log --- static/js/utils/setup.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/js/utils/setup.js b/static/js/utils/setup.js index dc2f2c2e3..e9afb216b 100644 --- a/static/js/utils/setup.js +++ b/static/js/utils/setup.js @@ -103,7 +103,6 @@ activeInstances[utilName] .filter(function(instance) { return !!instance }) .forEach(function(instance) { - console.log({ instance }); instance.destroy(); }); delete activeInstances[utilName]; From 6939b73802d56d50a156d89580395a196170a594 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 10 Mar 2019 14:40:44 +0100 Subject: [PATCH 07/10] old-style json-answers in admin email test --- src/Handler/Admin.hs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index 501cc97b9..946310640 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -20,6 +20,8 @@ import Database.Persist.Sql (fromSqlKey) -- import qualified Data.UUID.Cryptographic as UUID +import Control.Monad.Trans.Writer (mapWriterT) + -- BEGIN - Buttons needed only here data ButtonCreate = CreateMath | CreateInf -- Dummy for Example deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable) @@ -84,15 +86,12 @@ postAdminTestR = do _other -> addMessage Warning "KEIN Knopf erkannt" ((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm "email" $ renderAForm FormStandard emailTestForm - case emailResult of - (FormSuccess (email, ls)) -> do - jId <- runDB $ do - jId <- queueJob $ JobSendTestEmail email ls - addMessage Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|] - return jId - writeJobCtl $ JobCtlPerform jId - FormMissing -> return () - (FormFailure errs) -> forM_ errs $ addMessage Error . toHtml + formResultModal emailResult AdminTestR $ \(email, ls) -> do + jId <- mapWriterT runDB $ do + jId <- queueJob $ JobSendTestEmail email ls + tell . pure $ Message Success [shamlet|Email-test gestartet (Job ##{tshow (fromSqlKey jId)})|] + return jId + writeJobCtl $ JobCtlPerform jId let emailWidget' = [whamlet|
From 824a8e24e189fbf9ad8ad2e0b51df95272b42731 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Mar 2019 15:22:29 +0100 Subject: [PATCH 08/10] beautify async form response in modals --- static/css/utils/asyncForm.scss | 21 +++++++++++++++++++++ static/css/utils/modal.scss | 11 ++++++----- static/js/utils/asyncForm.js | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/static/css/utils/asyncForm.scss b/static/css/utils/asyncForm.scss index 4241d8f31..00c721822 100644 --- a/static/css/utils/asyncForm.scss +++ b/static/css/utils/asyncForm.scss @@ -1,5 +1,26 @@ .async-form-response { margin: 20px 0; + position: relative; + width: 100%; + font-size: 18px; + text-align: center; +} + +.async-form-response--success { + padding-top: 60px; +} + +.async-form-response--success::before { + content: ''; + position: absolute; + top: 0px; + left: 50%; + display: block; + width: 17px; + height: 28px; + border: solid #000; + border-width: 0 5px 5px 0; + transform: translateX(-50%) rotate(45deg); } .async-form-loading { diff --git a/static/css/utils/modal.scss b/static/css/utils/modal.scss index 5cac989a3..2f5d0e168 100644 --- a/static/css/utils/modal.scss +++ b/static/css/utils/modal.scss @@ -3,7 +3,7 @@ left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.8, 0.8); - display: block; + display: flex; background-color: rgba(255, 255, 255, 1); min-width: 60vw; min-height: 100px; @@ -26,10 +26,6 @@ z-index: 200; transform: translate(-50%, -50%) scale(1, 1); } - - .modal__content { - margin: 20px 0; - } } @media (max-width: 1024px) { @@ -96,3 +92,8 @@ color: white; } } + +.modal__content { + margin: 20px 0; + width: 100%; +} diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index d57199e72..e417b0ceb 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -19,19 +19,27 @@ } function processResponse(response) { - var responseElement = document.createElement('div'); - responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS); - responseElement.innerHTML = response.content; + var responseElement = makeResponseElement(response.content, response.class); var parentElement = formElement.parentElement; // make sure there is a delay between click and response var delay = Math.max(0, ASYNC_FORM_MIN_DELAY + lastRequestTimestamp - Date.now()); + setTimeout(function() { parentElement.insertBefore(responseElement, formElement); formElement.remove(); }, delay); } + function makeResponseElement(content, type) { + var responseElement = document.createElement('div'); + type = type || 'info'; + responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS); + responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + type); + responseElement.innerHTML = content; + return responseElement; + } + function submitHandler(event) { event.preventDefault(); @@ -53,7 +61,7 @@ if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header return response.json(); } else { - throw new TypeError('Response from "' + url + '" has unexpected Content-Type. Expected: "application/json". Received: "' + (response.headers.get("content-type") || '(undefined)') + '"'); + throw new TypeError('Unexpected Content-Type. Expected Content-Type: "application/json". Requested URL:' + url + '"'); } }).then(function(response) { processResponse(response[0]); From 101822fd21802e09cd1987d3d923ac6d0b6fe9eb Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 10 Mar 2019 15:47:33 +0100 Subject: [PATCH 09/10] =?UTF-8?q?`MessageClass`=20=E2=86=92=20`MessageStat?= =?UTF-8?q?us`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/system-messages | 2 +- src/Foundation.hs | 2 +- src/Handler/Utils/Form.hs | 2 +- src/Model.hs | 2 +- src/Settings.hs | 2 +- src/Utils/Message.hs | 50 +++++++++++++++++++-------------------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/models/system-messages b/models/system-messages index 0547718ae..0ceec9223 100644 --- a/models/system-messages +++ b/models/system-messages @@ -2,7 +2,7 @@ SystemMessage from UTCTime Maybe to UTCTime Maybe authenticatedOnly Bool - severity MessageClass + severity MessageStatus defaultLanguage Lang content Html summary Html Maybe diff --git a/src/Foundation.hs b/src/Foundation.hs index 047e3f670..70ad9da14 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -220,7 +220,7 @@ instance RenderMessage UniWorX MsgLanguage where instance RenderMessage UniWorX (UnsupportedAuthPredicate (Route UniWorX)) where renderMessage f ls (UnsupportedAuthPredicate tag route) = renderMessage f ls $ MsgUnsupportedAuthPredicate tag (show route) -embedRenderMessage ''UniWorX ''MessageClass ("Message" <>) +embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>) embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel embedRenderMessage ''UniWorX ''StudyFieldType id embedRenderMessage ''UniWorX ''SheetFileType id diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index af7379f8c..7bf0de1d1 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -654,5 +654,5 @@ formResultModal res finalDest handler = maybeT_ $ do if | isModal -> sendResponse $ toJSON messages | otherwise -> do - forM_ messages $ \Message{..} -> addMessage messageClass messageContent + forM_ messages $ \Message{..} -> addMessage messageStatus messageContent redirect finalDest diff --git a/src/Model.hs b/src/Model.hs index 54acc1b28..4a0e3f1c9 100644 --- a/src/Model.hs +++ b/src/Model.hs @@ -19,7 +19,7 @@ import Data.Aeson (Value) import Data.CaseInsensitive (CI) import Data.CaseInsensitive.Instances () -import Utils.Message (MessageClass) +import Utils.Message (MessageStatus) import Settings.Cluster (ClusterSettingsKey) import Data.Binary (Binary) diff --git a/src/Settings.hs b/src/Settings.hs index 81f98eb45..f717ee378 100644 --- a/src/Settings.hs +++ b/src/Settings.hs @@ -39,7 +39,7 @@ import qualified Data.Text.Encoding as Text import qualified Ldap.Client as Ldap -import Utils hiding (MessageClass(..)) +import Utils hiding (MessageStatus(..)) import Control.Lens import Data.Maybe (fromJust) diff --git a/src/Utils/Message.hs b/src/Utils/Message.hs index 7cf7f653f..69ce9e45e 100644 --- a/src/Utils/Message.hs +++ b/src/Utils/Message.hs @@ -1,6 +1,6 @@ module Utils.Message - ( MessageClass(..) - , UnknownMessageClass(..) + ( MessageStatus(..) + , UnknownMessageStatus(..) , addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget , Message(..) , messageI, messageIHamlet, messageFile, messageWidget @@ -25,64 +25,64 @@ import Text.Blaze.Html.Renderer.Text (renderHtml) import Text.HTML.SanitizeXSS (sanitizeBalance) -data MessageClass = Error | Warning | Info | Success +data MessageStatus = Error | Warning | Info | Success deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift) -instance Universe MessageClass -instance Finite MessageClass +instance Universe MessageStatus +instance Finite MessageStatus deriveJSON defaultOptions { constructorTagModifier = camelToPathPiece - } ''MessageClass + } ''MessageStatus -nullaryPathPiece ''MessageClass camelToPathPiece -derivePersistField "MessageClass" +nullaryPathPiece ''MessageStatus camelToPathPiece +derivePersistField "MessageStatus" -newtype UnknownMessageClass = UnknownMessageClass Text +newtype UnknownMessageStatus = UnknownMessageStatus Text deriving (Eq, Ord, Read, Show, Generic, Typeable) -instance Exception UnknownMessageClass +instance Exception UnknownMessageStatus data Message = Message - { messageClass :: MessageClass + { messageStatus :: MessageStatus , messageContent :: Html } instance Eq Message where - a == b = ((==) `on` messageClass) a b && ((==) `on` renderHtml . messageContent) a b + a == b = ((==) `on` messageStatus) a b && ((==) `on` renderHtml . messageContent) a b instance Ord Message where - a `compare` b = (compare `on` messageClass) a b `mappend` (compare `on` renderHtml . messageContent) a b + a `compare` b = (compare `on` messageStatus) a b `mappend` (compare `on` renderHtml . messageContent) a b instance ToJSON Message where toJSON Message{..} = object - [ "class" .= messageClass + [ "status" .= messageStatus , "content" .= renderHtml messageContent ] instance FromJSON Message where parseJSON = withObject "Message" $ \o -> do - messageClass <- o .: "class" + messageStatus <- o .: "status" messageContent <- preEscapedText . sanitizeBalance <$> o .: "content" return Message{..} -addMessage :: MonadHandler m => MessageClass -> Html -> m () +addMessage :: MonadHandler m => MessageStatus -> Html -> m () addMessage mc = ClassyPrelude.Yesod.addMessage (toPathPiece mc) -addMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m () +addMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageStatus -> msg -> m () addMessageI mc = ClassyPrelude.Yesod.addMessageI (toPathPiece mc) -messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m Message -messageI messageClass msg = do +messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageStatus -> msg -> m Message +messageI messageStatus msg = do messageContent <- toHtml . ($ msg) <$> getMessageRender return Message{..} addMessageIHamlet :: ( MonadHandler m , RenderMessage (HandlerSite m) msg , HandlerSite m ~ site - ) => MessageClass -> HtmlUrlI18n msg (Route site) -> m () + ) => MessageStatus -> HtmlUrlI18n msg (Route site) -> m () addMessageIHamlet mc iHamlet = do mr <- getMessageRender ClassyPrelude.Yesod.addMessage (toPathPiece mc) =<< withUrlRenderer (iHamlet $ toHtml . mr) @@ -90,22 +90,22 @@ addMessageIHamlet mc iHamlet = do messageIHamlet :: ( MonadHandler m , RenderMessage (HandlerSite m) msg , HandlerSite m ~ site - ) => MessageClass -> HtmlUrlI18n msg (Route site) -> m Message + ) => MessageStatus -> HtmlUrlI18n msg (Route site) -> m Message messageIHamlet mc iHamlet = do mr <- getMessageRender Message mc <$> withUrlRenderer (iHamlet $ toHtml . mr) -addMessageFile :: MessageClass -> FilePath -> ExpQ +addMessageFile :: MessageStatus -> FilePath -> ExpQ addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|] -messageFile :: MessageClass -> FilePath -> ExpQ +messageFile :: MessageStatus -> FilePath -> ExpQ messageFile mc tPath = [e|messageIHamlet mc $(ihamletFile tPath)|] addMessageWidget :: forall m site. ( MonadHandler m , HandlerSite m ~ site , Yesod site - ) => MessageClass -> WidgetT site IO () -> m () + ) => MessageStatus -> WidgetT site IO () -> m () -- ^ _Note_: `addMessageWidget` ignores `pageTitle` and `pageHead` addMessageWidget mc wgt = do PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt @@ -115,7 +115,7 @@ messageWidget :: forall m site. ( MonadHandler m , HandlerSite m ~ site , Yesod site - ) => MessageClass -> WidgetT site IO () -> m Message + ) => MessageStatus -> WidgetT site IO () -> m Message messageWidget mc wgt = do PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site)) From 24df9cb93e0e58cf28d1f65e8925cdb1ca853d6a Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Mar 2019 16:15:34 +0100 Subject: [PATCH 10/10] integrate api changes in FE --- static/css/utils/asyncForm.scss | 60 +++++++++++++++++++++++++++++---- static/js/utils/asyncForm.js | 8 ++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/static/css/utils/asyncForm.scss b/static/css/utils/asyncForm.scss index 00c721822..a0f9956dd 100644 --- a/static/css/utils/asyncForm.scss +++ b/static/css/utils/asyncForm.scss @@ -4,25 +4,73 @@ width: 100%; font-size: 18px; text-align: center; -} - -.async-form-response--success { padding-top: 60px; } -.async-form-response--success::before { - content: ''; +.async-form-response::before, +.async-form-response::after { position: absolute; top: 0px; left: 50%; display: block; +} + +.async-form-response--success::before { + content: ''; width: 17px; height: 28px; - border: solid #000; + border: solid #069e04; border-width: 0 5px 5px 0; transform: translateX(-50%) rotate(45deg); } +.async-form-response--info::before { + content: ''; + width: 5px; + height: 30px; + top: 10px; + background-color: #777; + transform: translateX(-50%); +} +.async-form-response--info::after { + content: ''; + width: 5px; + height: 5px; + background-color: #777; + transform: translateX(-50%); +} + +.async-form-response--warning::before { + content: ''; + width: 5px; + height: 30px; + background-color: rgb(255, 187, 0); + transform: translateX(-50%); +} +.async-form-response--warning::after { + content: ''; + width: 5px; + height: 5px; + top: 35px; + background-color: rgb(255, 187, 0); + transform: translateX(-50%); +} + +.async-form-response--error::before { + content: ''; + width: 5px; + height: 40px; + background-color: #940d0d; + transform: translateX(-50%) rotate(-45deg); +} +.async-form-response--error::after { + content: ''; + width: 5px; + height: 40px; + background-color: #940d0d; + transform: translateX(-50%) rotate(45deg); +} + .async-form-loading { opacity: 0.1; transition: opacity 800ms ease-out; diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index e417b0ceb..aa57ed2a0 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -19,7 +19,7 @@ } function processResponse(response) { - var responseElement = makeResponseElement(response.content, response.class); + var responseElement = makeResponseElement(response.content, response.status); var parentElement = formElement.parentElement; // make sure there is a delay between click and response @@ -31,11 +31,11 @@ }, delay); } - function makeResponseElement(content, type) { + function makeResponseElement(content, status) { var responseElement = document.createElement('div'); - type = type || 'info'; + status = status || 'info'; responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS); - responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + type); + responseElement.classList.add(ASYNC_FORM_RESPONSE_CLASS + '--' + status); responseElement.innerHTML = content; return responseElement; }