From 1dfb2e0a1a3e5a1c31478199729622c9fe23bb75 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Fri, 16 Feb 2024 10:59:05 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B07243=20-=20Add=20toast=20notifications?= =?UTF-8?q?=20to=20iTop=20(#614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * N°7243 - Add toast notifications to iTop * Apply suggestions from code review Co-authored-by: Molkobain * Apply suggestions from code review Co-authored-by: Molkobain * Apply suggestions from code review Co-authored-by: Molkobain * Apply suggestions from code review Co-authored-by: Molkobain * Apply suggestions from code review Co-authored-by: Molkobain * Apply suggestions from code review Co-authored-by: Molkobain * Update js/pages/backoffice/toolbox.js * Update js/utils.js * N°7243 - Move some rules to a dedicated partial and use spacing variables --------- Co-authored-by: Molkobain --- css/backoffice/components/_all.scss | 3 +- css/backoffice/components/_toast.scss | 62 +++ css/backoffice/vendors/_all.scss | 3 +- css/backoffice/vendors/_toastify.scss | 76 +++ .../en.dictionary.itop.preferences.php | 3 + js/pages/backoffice/toolbox.js | 21 + js/utils.js | 59 +++ node_modules/.package-lock.json | 9 +- node_modules/toastify-js/.gitattributes | 17 + node_modules/toastify-js/.prettierrc | 9 + node_modules/toastify-js/.travis.yml | 12 + node_modules/toastify-js/CHANGELOG.md | 133 +++++ node_modules/toastify-js/LICENSE | 21 + node_modules/toastify-js/README.md | 439 +++++++++++++++++ node_modules/toastify-js/example/pattern.png | Bin 0 -> 21967 bytes node_modules/toastify-js/example/script.css | 95 ++++ node_modules/toastify-js/example/script.js | 72 +++ node_modules/toastify-js/index.html | 59 +++ node_modules/toastify-js/package.json | 22 + node_modules/toastify-js/src/toastify-es.js | 466 ++++++++++++++++++ node_modules/toastify-js/src/toastify.css | 85 ++++ node_modules/toastify-js/src/toastify.js | 445 +++++++++++++++++ package-lock.json | 15 +- package.json | 3 +- pages/preferences.php | 26 + sources/Application/WebPage/iTopWebPage.php | 3 + 26 files changed, 2151 insertions(+), 7 deletions(-) create mode 100644 css/backoffice/components/_toast.scss create mode 100644 css/backoffice/vendors/_toastify.scss create mode 100644 node_modules/toastify-js/.gitattributes create mode 100644 node_modules/toastify-js/.prettierrc create mode 100644 node_modules/toastify-js/.travis.yml create mode 100644 node_modules/toastify-js/CHANGELOG.md create mode 100644 node_modules/toastify-js/LICENSE create mode 100644 node_modules/toastify-js/README.md create mode 100644 node_modules/toastify-js/example/pattern.png create mode 100644 node_modules/toastify-js/example/script.css create mode 100644 node_modules/toastify-js/example/script.js create mode 100644 node_modules/toastify-js/index.html create mode 100644 node_modules/toastify-js/package.json create mode 100644 node_modules/toastify-js/src/toastify-es.js create mode 100644 node_modules/toastify-js/src/toastify.css create mode 100644 node_modules/toastify-js/src/toastify.js diff --git a/css/backoffice/components/_all.scss b/css/backoffice/components/_all.scss index efb91bdb1..5237baeba 100644 --- a/css/backoffice/components/_all.scss +++ b/css/backoffice/components/_all.scss @@ -32,4 +32,5 @@ @import "search-form"; @import "field-badge"; @import "file-select"; -@import "medallion-icon"; \ No newline at end of file +@import "medallion-icon"; +@import "toast"; \ No newline at end of file diff --git a/css/backoffice/components/_toast.scss b/css/backoffice/components/_toast.scss new file mode 100644 index 000000000..4f9ddf66a --- /dev/null +++ b/css/backoffice/components/_toast.scss @@ -0,0 +1,62 @@ +/* + * @copyright Copyright (C) 2010-2024 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +/* SCSS variables */ +$ibo-toast--padding-y: $ibo-spacing-400 !default; +$ibo-toast--padding-right: $ibo-spacing-300 !default; +$ibo-toast--padding-left: $ibo-spacing-500 !default; +$ibo-toast--border-radius: $ibo-border-radius-300 !default; +$ibo-toast--box-shadow: $ibo-elevation-200 !default; +$ibo-toast--max-width: calc(50% - 20px) !default; + +@keyframes decreaseHighlight { + 0% { + height: 100%; + } + 8%{ + border-radius: 0 0 0 3px; + } + 100% { + height: 0; + } +} + +.ibo-toast { + display: inline-flex; + position: fixed; + align-items: center; + + max-width: $ibo-toast--max-width ; + padding: $ibo-toast--padding-y $ibo-toast--padding-right $ibo-toast--padding-y $ibo-toast--padding-left; + border-radius: $ibo-toast--border-radius; + + box-shadow: $ibo-toast--box-shadow; + transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1); + z-index: 2147483647; + &::before { + @include ibo-vertical-highlight; + top: initial; + bottom: 0; + border-radius: $ibo-toast--border-radius 0 0 $ibo-toast--border-radius; + } + &.ibo-is-auto-closeable::before{ + animation: decreaseHighlight 5s linear forwards; + } + &:hover::before { + animation: none; /* Pause animation on hover */ + } + &.ibo-is-error{ + @extend .ibo-alert.ibo-is-danger; + } + &.ibo-is-warning{ + @extend .ibo-alert.ibo-is-warning; + } + &.ibo-is-success{ + @extend .ibo-alert.ibo-is-success; + } + &.ibo-is-information{ + @extend .ibo-alert.ibo-is-information; + } +} \ No newline at end of file diff --git a/css/backoffice/vendors/_all.scss b/css/backoffice/vendors/_all.scss index 81603f943..58e188c19 100644 --- a/css/backoffice/vendors/_all.scss +++ b/css/backoffice/vendors/_all.scss @@ -17,4 +17,5 @@ @import "jquery-treeview"; @import "jquery-blockui"; @import "magnific-popup"; -@import "selectize"; \ No newline at end of file +@import "selectize"; +@import "toastify"; \ No newline at end of file diff --git a/css/backoffice/vendors/_toastify.scss b/css/backoffice/vendors/_toastify.scss new file mode 100644 index 000000000..b3678af4d --- /dev/null +++ b/css/backoffice/vendors/_toastify.scss @@ -0,0 +1,76 @@ +/* + * @copyright Copyright (C) 2010-2024 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +/* SCSS variables */ + +$ibo-vendors-toastify--right--right: $ibo-spacing-500 !default; +$ibo-vendors-toastify--left--left: $ibo-spacing-500 !default; +$ibo-vendors-toastify--top--top: -150px !default; +$ibo-vendors-toastify--bottom--bottom: -150px !default; + +$ibo-vendors-toastify--close--background: transparent !default; +$ibo-vendors-toastify--close--padding: 0 !default; +$ibo-vendors-toastify--close--margin-left: $ibo-spacing-300 !default; + +.toastify.on { + opacity: 1; +} + +.toast-close { + background: $ibo-vendors-toastify--close--background; + border: 0; + color: inherit; + cursor: pointer; + font-family: inherit; + padding: $ibo-vendors-toastify--close--padding; + margin-left: $ibo-vendors-toastify--close--margin-left; +} + +.toastify-right { + right: $ibo-vendors-toastify--right--right; +} + +.toastify-left { + left: $ibo-vendors-toastify--left--left; +} + +.toastify-top { + top: $ibo-vendors-toastify--top--top; +} + +.toastify-bottom { + bottom: $ibo-vendors-toastify--bottom--bottom; +} + +.toastify-rounded { + border-radius: 25px; +} + +.toastify-avatar { + width: 1.5em; + height: 1.5em; + margin: -7px 5px; + border-radius: 2px; +} + +.toastify-center { + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + max-width: fit-content; + max-width: -moz-fit-content; +} + +@media only screen and (max-width: 360px) { + .toastify-right, .toastify-left { + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + max-width: fit-content; + } +} + diff --git a/dictionaries/ui/pages/preferences/en.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/en.dictionary.itop.preferences.php index 3a552b8ba..6c24e4672 100644 --- a/dictionaries/ui/pages/preferences/en.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/preferences/en.dictionary.itop.preferences.php @@ -46,6 +46,9 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Preferences:Tabs:Scrollable:Label' => 'Navigation', 'UI:Preferences:Tabs:Scrollable:Classic' => 'Classic', 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Scrollable', + 'UI:Preferences:General:Toasts' => 'Toast notifications position', + 'UI:Preferences:General:Toasts:Bottom' => 'Bottom', + 'UI:Preferences:General:Toasts:Top' => 'Top', 'UI:Preferences:ChooseAPlaceholder' => 'User placeholder image', 'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one', )); diff --git a/js/pages/backoffice/toolbox.js b/js/pages/backoffice/toolbox.js index 41e8b4936..480793d62 100644 --- a/js/pages/backoffice/toolbox.js +++ b/js/pages/backoffice/toolbox.js @@ -458,6 +458,27 @@ CombodoModal.OpenInformativeModal = function(sMessage, sSeverity, oOptions) { // Open modal CombodoModal.OpenModal(oOptions); } +/** + * @override + * @inheritDoc + */ +CombodoToast.OpenToast = function(sMessage, sSeverity, aOptions) { + aOptions = $.extend({ + text: sMessage, + className: "ibo-toast ibo-is-" + sSeverity, + duration: 6000, + close: true, + gravity: GetUserPreference('toasts_vertical_position', 'bottom'), + position: "right", + stopOnFocus: true, + }, aOptions); + + if(aOptions.duration !== -1){ + aOptions.className += ' ibo-is-auto-closeable'; + } + + Toastify(aOptions).showToast(); +}; // Processing on each pages of the backoffice $(document).ready(function(){ diff --git a/js/utils.js b/js/utils.js index 5de41a519..9e8feeed2 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1574,4 +1574,63 @@ let CombodoModal = { OpenErrorModal: function(sMessage, oOptions) { CombodoModal.OpenInformativeModal(sMessage, CombodoModal.INFORMATIVE_MODAL_SEVERITY_ERROR, oOptions); }, +}; + +/** + * Abstract wrapper to manage toasts in iTop. + * Implementations for the various GUIs may vary but APIs are the same. + * + * @since 3.2.0 + */ +let CombodoToast = { + /** + * Open a standard toast and put the content into it. + * + * @param sMessage {String} Message to be displayed in the toast + * @param sSeverity {String} Severity of the information. Default values are success, information, warning, error. + * @param aOptions {Object} {@see CombodoModal.OpenModal + */ + OpenToast: function(sMessage, sSeverity, aOptions = {}) { + // Meant for overloading + CombodoJSConsole.Debug('CombodoToast.OpenToast not implemented'); + }, + /** + * Open a standard toast for success messages. + * + * @param sMessage {String} Success message to be displayed in the toast + * @param aOptions {Object} {@see CombodoModal.OpenModal + */ + OpenSuccessToast: function(sMessage, aOptions = {}) { + CombodoToast.OpenToast(sMessage, 'success', aOptions); + }, + + /** + * Open a standard toast for information messages. + * + * @param sMessage {String} Information message to be displayed in the toast + * @param aOptions {Object} {@see CombodoModal.OpenModal + */ + OpenInformationToast: function(sMessage, aOptions = {}) { + CombodoToast.OpenToast(sMessage, 'information', aOptions); + }, + + /** + * Open a standard toast for warning messages. + * + * @param sMessage {String} Warning message to be displayed in the toast + * @param aOptions {Object} {@see CombodoModal.OpenModal + */ + OpenWarningToast: function(sMessage, aOptions = {}) { + CombodoToast.OpenToast(sMessage, 'warning', aOptions); + }, + + /** + * Open a standard toast for error messages. + * + * @param sMessage {String} Error message to be displayed in the toast + * @param aOptions {Object} {@see CombodoModal.OpenModal + */ + OpenErrorToast: function(sMessage, aOptions = {}) { + CombodoToast.OpenToast(sMessage, 'error', aOptions); + } }; \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 513ea2659..d319d2387 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,6 +1,6 @@ { - "name": "dev-trunk", - "lockfileVersion": 2, + "name": "iTop", + "lockfileVersion": 3, "requires": true, "packages": { "node_modules/@fontsource/raleway": { @@ -88,6 +88,11 @@ "dependencies": { "@popperjs/core": "^2.4.4" } + }, + "node_modules/toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" } } } diff --git a/node_modules/toastify-js/.gitattributes b/node_modules/toastify-js/.gitattributes new file mode 100644 index 000000000..bdb0cabc8 --- /dev/null +++ b/node_modules/toastify-js/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/node_modules/toastify-js/.prettierrc b/node_modules/toastify-js/.prettierrc new file mode 100644 index 000000000..b909d0a75 --- /dev/null +++ b/node_modules/toastify-js/.prettierrc @@ -0,0 +1,9 @@ +{ + "useTabs": false, + "printWidth": 80, + "tabWidth": 2, + "singleQuote": false, + "trailingComma": "es5", + "parser": "babylon", + "noSemi": false +} diff --git a/node_modules/toastify-js/.travis.yml b/node_modules/toastify-js/.travis.yml new file mode 100644 index 000000000..5b7005dc4 --- /dev/null +++ b/node_modules/toastify-js/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - "8" +script: +- echo "skipping tests" +deploy: + skip_cleanup: true + provider: npm + email: $NPM_USERNAME + api_key: $NPM_TOKEN + on: + branch: master \ No newline at end of file diff --git a/node_modules/toastify-js/CHANGELOG.md b/node_modules/toastify-js/CHANGELOG.md new file mode 100644 index 000000000..fcdc1f943 --- /dev/null +++ b/node_modules/toastify-js/CHANGELOG.md @@ -0,0 +1,133 @@ +# Changelog + +All the changes made to toastify-js library. + +## [1.12.0] - 2022-07-21 + +* Accessibility fix: Support aria-live for the toast +* Accessibility fix: Add aria-label for close icon + +## [1.11.2] - 2021-10-06 + +* Bugfix: Style Options: "backgroundColor" not working! (#81) +* Bugfix: "ShadowRoot is undefined" in older browsers (#83) + +## [1.11.1] - 2021-07-15 + +* Bugfix: IE11 support broke since style option #77 + +## [1.11.0] - 2021-04-25 + +* New property `oldestFirst` allows to set the order of adding new toasts to page (#70 and #71) + +## [1.10.0] - 2021-03-25 + +* `selector` now supports a DOM Node, along with ID string ([#65](https://github.com/apvarun/toastify-js/pull/65)) +* New property - `escapeMarkup` - Toggle the default behavior of escaping HTML markup +* New property - `style` - Use the HTML DOM Style properties to add any style directly to toast +* Adds `toastify-es.js`, to be used from node_modules until there are no compatibility issues + +### Deprecations: + +* `backgroundColor` is deprecated. Use `style.background` instead + +## [1.9.3] - 2020-10-10 + +* Offset IE11 compatibility #64 + +## [1.9.2] - 2020-09-24 + +* Bugfix: Max width problem for firefox browser #61 + +## [1.9.1] - 2020-08-13 + +* Bugfix: Avatar positioning based on toast position + +## [1.9.0] - 2020-07-22 + +* Add support for providing toast `offset` +* Updated docs: offset + +## [1.8.0] - 2020-05-29 + +* Add option to provide a node instead of text +* Updated docs: permanent toast duration + +## [1.7.0] - 2020-03-01 + +* To be able to set `stopOnFocus` for toasts without close icon +* Bugfix: `duration` can be infinite by setting as `0` +* Bugfix: Prevent errors when parent node is removed from DOM while using frameworks +* Bugfix: IE 9/10 compatibility fix + +## [1.6.2] - 2020-01-03 + +* Bugfix: Closing the toast when custom close icon from icon fonts are used + +## [1.6.1] - 2019-06-29 + +* Bugfix: Disabling `stopOnFocus` + +## [1.6.0] - 2019-06-29 + +* **Deprecation Warning**: Migrating from `positionLeft` property to `position` +* Property `position` to support `center` as a value along with `left` and `right` - Useful for centering toast messages in the page + +## [1.5.0] - 2019-05-30 + +* Added persistant toast option with ability to programatically close it + +## [1.4.0] - 2019-05-12 + +* **Breaking Change**: Manually import CSS while using as module in your modern JavaScript applications +* Ability to pause the toast dismiss timer on hover (Using `stopOnFocus` property) + +## [1.3.2] - 2018-12-6 + +* Added z-index attribute + +## [1.2.1] - 2018-05-31 + +* Added support for Classes. Now custom classes can be added to the toast while creating it + +## [1.2.0] - 2018-03-05 + +* Fix issue when `destination` and `close` options is used at the same time + +## [1.1.0] - 2018-02-18 + +* Browser support extended to IE10+ without any polyfills + +## [1.0.0] - 2018-02-17 + +* Support for modules + +## [0.0.6] - 2017-09-09 + +* Support for changing background [Options] +* Optimized toast positioning logic +* Added changelog for library update tracking + +## [0.0.5] - 2017-09-06 + +* Support for toast messages on mobile screens +* Tweaked close icon + +## [0.0.4] - 2017-09-05 + +* Support for positioning of toasts on the page + +## [0.0.3] - 2017-09-05 + +* Close buton for toasts [Options] + +## [0.0.2] - 2017-09-04 + +* Option to add on-click link for toasts +* Updated comments for code readability + +## [0.0.1] - 2017-09-02 + +* Initial Release +* Added Preview page +* Optimized function structure diff --git a/node_modules/toastify-js/LICENSE b/node_modules/toastify-js/LICENSE new file mode 100644 index 000000000..506b07264 --- /dev/null +++ b/node_modules/toastify-js/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 apvarun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/toastify-js/README.md b/node_modules/toastify-js/README.md new file mode 100644 index 000000000..19363956f --- /dev/null +++ b/node_modules/toastify-js/README.md @@ -0,0 +1,439 @@ + +# Toastify + +![Built with JavaScript](https://img.shields.io/badge/Built%20with-JavaScript-red?style=for-the-badge&logo=javascript) + +[![toastify-js](https://img.shields.io/badge/toastify--js-1.12.0-brightgreen.svg)](https://www.npmjs.com/package/toastify-js) +![MIT License](https://img.shields.io/npm/l/toastify-js) + +Toastify is a lightweight, vanilla JS toast notification library. + +## Demo + +[Click here](https://apvarun.github.io/toastify-js/) + +## Features + +* Multiple stacked notifications +* Customizable +* No blocking of execution thread + +### Customization options + +* Notification Text +* Duration +* Toast background color +* Close icon display +* Display position +* Offset position + +## Installation + +#### Toastify now supports installation via NPM + +* Run the below command to add toastify-js to your exisitng or new project. + +``` +npm install --save toastify-js +``` + +or + +``` +yarn add toastify-js -S +``` + +* Import toastify-js into your module to start using it. + +``` +import Toastify from 'toastify-js' +``` + +You can use the default CSS from Toastify as below and later override it or choose to write your own CSS. + +``` +import "toastify-js/src/toastify.css" +``` + +#### Adding ToastifyJs to HTML page using the traditional method + +To start using **Toastify**, add the following CSS on to your page. + +```html + +``` + +And the script at the bottom of the page + +```html + +``` + +> Files are delivered via the CDN service provided by [jsdeliver](https://www.jsdelivr.com/) + +## Documentation + +```javascript +Toastify({ + text: "This is a toast", + duration: 3000, + destination: "https://github.com/apvarun/toastify-js", + newWindow: true, + close: true, + gravity: "top", // `top` or `bottom` + position: "left", // `left`, `center` or `right` + stopOnFocus: true, // Prevents dismissing of toast on hover + style: { + background: "linear-gradient(to right, #00b09b, #96c93d)", + }, + onClick: function(){} // Callback after click +}).showToast(); +``` + +> Toast messages will be centered on devices with screen width less than 360px. + +* See the [changelog](https://github.com/apvarun/toastify-js/blob/master/CHANGELOG.md) + +### Add own custom classes + +If you want to use custom classes on the toast for customizing (like info or warning for example), you can do that as follows: + +```javascript +Toastify({ + text: "This is a toast", + className: "info", + style: { + background: "linear-gradient(to right, #00b09b, #96c93d)", + } +}).showToast(); +``` + +Multiple classes also can be assigned as a string, with spaces between class names. + +### Add some offset + +If you want to add offset to the toast, you can do that as follows: + +```javascript +Toastify({ + text: "This is a toast with offset", + offset: { + x: 50, // horizontal axis - can be a number or a string indicating unity. eg: '2em' + y: 10 // vertical axis - can be a number or a string indicating unity. eg: '2em' + }, +}).showToast(); +``` + +Toast will be pushed 50px from right in x axis and 10px from top in y axis. + +**Note:** + +If `position` is equals to `left`, it will be pushed from left. +If `gravity` is equals to `bottom`, it will be pushed from bottom. + +## API + +| Option Key | type | Usage | Defaults | +|-----------------|----------------------|----------------------------------------------------------------------------|-------------| +| text | string | Message to be displayed in the toast | "Hi there!" | +| node | ELEMENT_NODE | Provide a node to be mounted inside the toast. `node` takes higher precedence over `text` | | +| duration | number | Duration for which the toast should be displayed.
-1 for permanent toast | 3000 | +| selector | string \| ELEMENT_NODE | ShadowRoot | CSS Selector or Element Node on which the toast should be added | body | +| destination | URL string | URL to which the browser should be navigated on click of the toast | | +| newWindow | boolean | Decides whether the `destination` should be opened in a new window or not | false | +| close | boolean | To show the close icon or not | false | +| gravity | "top" or "bottom" | To show the toast from top or bottom | "top" | +| position | "left" or "right" | To show the toast on left or right | "right" | +| backgroundColor | CSS background value | To be deprecated, use `style.background` option instead. Sets the background color of the toast | | +| avatar | URL string | Image/icon to be shown before text | | +| className | string | Ability to provide custom class name for further customization | | +| stopOnFocus | boolean | To stop timer when hovered over the toast (Only if duration is set) | true | +| callback | Function | Invoked when the toast is dismissed | | +| onClick | Function | Invoked when the toast is clicked | | +| offset | Object | Ability to add some offset to axis | | +| escapeMarkup | boolean | Toggle the default behavior of escaping HTML markup | true | +| style | object | Use the HTML DOM Style properties to add any style directly to toast | | +| ariaLive | string | Announce the toast to screen readers, see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions for options | "polite" | +| oldestFirst | boolean | Set the order in which toasts are stacked in page | true | + +> Deprecated properties: `backgroundColor` - use `style.background` option instead + +## Browsers support + +| ![][ie]
IE / Edge | ![][firefox]
Firefox | ![][chrome]
Chrome | ![][safari]
Safari | ![][opera]
Opera | +| ---------------------- | ------------------------- | ----------------------- | ----------------------- | --------------------- | +| IE10, IE11, Edge | last 10 versions | last 10 versions | last 10 versions | last 10 versions | + +## Contributors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + AStoker +
+ AStoker +
+
+ + caiomoura1994 +
+ caiomoura1994 +
+
+ + rndevfx +
+ rndevfx +
+
+ + 1ess +
+ 1ess +
+
+ + d4rn0k +
+ d4rn0k +
+
+ + danielkaiser80 +
+ danielkaiser80 +
+
+ + skjnldsv +
+ skjnldsv +
+
+ + chasedeanda +
+ chasedeanda +
+
+ + chrisgraham +
+ chrisgraham +
+
+ + Wachiwi +
+ Wachiwi +
+
+ + FeixuRuins +
+ FeixuRuins +
+
+ + gavinhungry +
+ gavinhungry +
+
+ + haydster7 +
+ haydster7 +
+
+ + joaquinwojcik +
+ joaquinwojcik +
+
+ + juliushaertl +
+ juliushaertl +
+
+ + mort3za +
+ mort3za +
+
+ + Sandip124 +
+ Sandip124 +
+
+ + Tadaz +
+ Tadaz +
+
+ + t12ung +
+ t12ung +
+
+ + victorfeijo +
+ victorfeijo +
+
+ + fiatjaf +
+ fiatjaf +
+
+ + prousseau-korem +
+ prousseau-korem +
+
+ + + + +## License + +MIT © [Varun A P](https://github.com/apvarun) + +Buy Me A Coffee + +[ie]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/edge.png +[firefox]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/firefox.png +[chrome]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome.png +[safari]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari.png +[opera]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/opera.png + diff --git a/node_modules/toastify-js/example/pattern.png b/node_modules/toastify-js/example/pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..310e8868d9d510f0aad29b0ff9081d42d13bf8b0 GIT binary patch literal 21967 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 + + + + + + + Toastify JS - Pure JavaScript Toast Notificaton Library + + + + + + + + +
+
+

Toastify JS

+
+

Better notification messages

+
+ Try + Docs + Tweet +
+
+

Usage

+ +

Toastify({

+

text: "This is a toast",

+

duration: 3000

+

}).showToast();

+
+
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/node_modules/toastify-js/package.json b/node_modules/toastify-js/package.json new file mode 100644 index 000000000..52dcf261b --- /dev/null +++ b/node_modules/toastify-js/package.json @@ -0,0 +1,22 @@ +{ + "name": "toastify-js", + "version": "1.12.0", + "description": "Toastify is a lightweight, vanilla JS toast notification library.", + "main": "./src/toastify.js", + "repository": { + "type": "git", + "url": "git+https://github.com/apvarun/toastify-js.git" + }, + "keywords": [ + "toastify", + "javascript", + "notifications", + "toast" + ], + "author": "Varun A P", + "license": "MIT", + "bugs": { + "url": "https://github.com/apvarun/toastify-js/issues" + }, + "homepage": "https://github.com/apvarun/toastify-js#readme" +} diff --git a/node_modules/toastify-js/src/toastify-es.js b/node_modules/toastify-js/src/toastify-es.js new file mode 100644 index 000000000..784cf59be --- /dev/null +++ b/node_modules/toastify-js/src/toastify-es.js @@ -0,0 +1,466 @@ +/*! + * Toastify js 1.12.0 + * https://github.com/apvarun/toastify-js + * @license MIT licensed + * + * Copyright (C) 2018 Varun A P + */ + +/** + * Options used for Toastify + * @typedef {Object} ToastifyConfigurationObject + * @property {string} text - Message to be displayed in the toast + * @property {Element} node - Provide a node to be mounted inside the toast. node takes higher precedence over text + * @property {number} duration - Duration for which the toast should be displayed. -1 for permanent toast + * @property {string|Element} selector - CSS ID Selector on which the toast should be added + * @property {url} destination - URL to which the browser should be navigated on click of the toast + * @property {boolean} newWindow - Decides whether the destination should be opened in a new window or not + * @property {boolean} close - To show the close icon or not + * @property {string} gravity - To show the toast from top or bottom + * @property {string} position - To show the toast on left or right + * @property {string} backgroundColor - Deprecated: Sets the background color of the toast + * @property {url} avatar - Image/icon to be shown before text + * @property {string} className - Ability to provide custom class name for further customization + * @property {boolean} stopOnFocus - To stop timer when hovered over the toast (Only if duration is set) + * @property {Function} callback - Invoked when the toast is dismissed + * @property {Function} onClick - Invoked when the toast is clicked + * @property {Object} offset - Ability to add some offset to axis + * @property {boolean} escapeMarkup - Toggle the default behavior of escaping HTML markup + * @property {string} ariaLive - Use the HTML DOM style property to add styles to toast + * @property {Object} style - Use the HTML DOM style property to add styles to toast + */ + + +class Toastify { + + defaults = { + oldestFirst: true, + text: "Toastify is awesome!", + node: undefined, + duration: 3000, + selector: undefined, + callback: function() {}, + destination: undefined, + newWindow: false, + close: false, + gravity: "toastify-top", + positionLeft: false, + position: "", + backgroundColor: "", + avatar: "", + className: "", + stopOnFocus: true, + onClick: function() {}, + offset: { x: 0, y: 0 }, + escapeMarkup: true, + ariaLive: "polite", + style: { background: "" }, + }; + + constructor(options) { + /** + * The version of Toastify + * @type {string} + * @public + */ + this.version = "1.12.0"; + + /** + * The configuration object to configure Toastify + * @type {ToastifyConfigurationObject} + * @public + */ + this.options = {}; + + /** + * The element that is the Toast + * @type {Element} + * @public + */ + this.toastElement = null; + + /** + * The root element that contains all the toasts + * @type {Element} + * @private + */ + this._rootElement = document.body; + + this._init(options); + } + + /** + * Display the toast + * @public + */ + showToast() { + // Creating the DOM object for the toast + this.toastElement = this._buildToast(); + + // Getting the root element to with the toast needs to be added + if (typeof this.options.selector === "string") { + this._rootElement = document.getElementById(this.options.selector); + } else if (this.options.selector instanceof HTMLElement || this.options.selector instanceof ShadowRoot) { + this._rootElement = this.options.selector; + } else { + this._rootElement = document.body; + } + + // Validating if root element is present in DOM + if (!this._rootElement) { + throw "Root element is not defined"; + } + + // Adding the DOM element + this._rootElement.insertBefore(this.toastElement, this._rootElement.firstChild); + + // Repositioning the toasts in case multiple toasts are present + this._reposition(); + + if (this.options.duration > 0) { + this.toastElement.timeOutValue = window.setTimeout( + () => { + // Remove the toast from DOM + this._removeElement(this.toastElement); + }, + this.options.duration + ); // Binding `this` for function invocation + } + + // Supporting function chaining + return this; + } + + /** + * Hide the toast + * @public + */ + hideToast() { + if (this.toastElement.timeOutValue) { + clearTimeout(this.toastElement.timeOutValue); + } + this._removeElement(this.toastElement); + } + + /** + * Init the Toastify class + * @param {ToastifyConfigurationObject} options - The configuration object to configure Toastify + * @param {string} [options.text=Hi there!] - Message to be displayed in the toast + * @param {Element} [options.node] - Provide a node to be mounted inside the toast. node takes higher precedence over text + * @param {number} [options.duration=3000] - Duration for which the toast should be displayed. -1 for permanent toast + * @param {string} [options.selector] - CSS Selector on which the toast should be added + * @param {url} [options.destination] - URL to which the browser should be navigated on click of the toast + * @param {boolean} [options.newWindow=false] - Decides whether the destination should be opened in a new window or not + * @param {boolean} [options.close=false] - To show the close icon or not + * @param {string} [options.gravity=toastify-top] - To show the toast from top or bottom + * @param {string} [options.position=right] - To show the toast on left or right + * @param {string} [options.backgroundColor] - Sets the background color of the toast (To be deprecated) + * @param {url} [options.avatar] - Image/icon to be shown before text + * @param {string} [options.className] - Ability to provide custom class name for further customization + * @param {boolean} [options.stopOnFocus] - To stop timer when hovered over the toast (Only if duration is set) + * @param {Function} [options.callback] - Invoked when the toast is dismissed + * @param {Function} [options.onClick] - Invoked when the toast is clicked + * @param {Object} [options.offset] - Ability to add some offset to axis + * @param {boolean} [options.escapeMarkup=true] - Toggle the default behavior of escaping HTML markup + * @param {string} [options.ariaLive] - Announce the toast to screen readers + * @param {Object} [options.style] - Use the HTML DOM style property to add styles to toast + * @private + */ + _init(options) { + + // Setting defaults + this.options = Object.assign(this.defaults, options); + + if (this.options.backgroundColor) { + // This is being deprecated in favor of using the style HTML DOM property + console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.'); + } + + this.toastElement = null; + + this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : "toastify-top"; // toast position - top or bottom + this.options.stopOnFocus = options.stopOnFocus === undefined ? true : options.stopOnFocus; // stop timeout on focus + if(options.backgroundColor) { + this.options.style.background = options.backgroundColor; + } + } + + /** + * Build the Toastify element + * @returns {Element} + * @private + */ + _buildToast() { + // Validating if the options are defined + if (!this.options) { + throw "Toastify is not initialized"; + } + + // Creating the DOM object + let divElement = document.createElement("div"); + divElement.className = `toastify on ${this.options.className}`; + + // Positioning toast to left or right or center (default right) + divElement.className += ` toastify-${this.options.position}`; + + // Assigning gravity of element + divElement.className += ` ${this.options.gravity}`; + + // Loop through our style object and apply styles to divElement + for (const property in this.options.style) { + divElement.style[property] = this.options.style[property]; + } + + // Announce the toast to screen readers + if (this.options.ariaLive) { + divElement.setAttribute('aria-live', this.options.ariaLive) + } + + // Adding the toast message/node + if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) { + // If we have a valid node, we insert it + divElement.appendChild(this.options.node) + } else { + if (this.options.escapeMarkup) { + divElement.innerText = this.options.text; + } else { + divElement.innerHTML = this.options.text; + } + + if (this.options.avatar !== "") { + let avatarElement = document.createElement("img"); + avatarElement.src = this.options.avatar; + + avatarElement.className = "toastify-avatar"; + + if (this.options.position == "left") { + // Adding close icon on the left of content + divElement.appendChild(avatarElement); + } else { + // Adding close icon on the right of content + divElement.insertAdjacentElement("afterbegin", avatarElement); + } + } + } + + // Adding a close icon to the toast + if (this.options.close === true) { + // Create a span for close element + let closeElement = document.createElement("button"); + closeElement.type = "button"; + closeElement.setAttribute("aria-label", "Close"); + closeElement.className = "toast-close"; + closeElement.innerHTML = "✖"; + + // Triggering the removal of toast from DOM on close click + closeElement.addEventListener( + "click", + (event) => { + event.stopPropagation(); + this._removeElement(this.toastElement); + window.clearTimeout(this.toastElement.timeOutValue); + } + ); + + //Calculating screen width + const width = window.innerWidth > 0 ? window.innerWidth : screen.width; + + // Adding the close icon to the toast element + // Display on the right if screen width is less than or equal to 360px + if ((this.options.position == "left") && width > 360) { + // Adding close icon on the left of content + divElement.insertAdjacentElement("afterbegin", closeElement); + } else { + // Adding close icon on the right of content + divElement.appendChild(closeElement); + } + } + + // Clear timeout while toast is focused + if (this.options.stopOnFocus && this.options.duration > 0) { + // stop countdown + divElement.addEventListener( + "mouseover", + (event) => { + window.clearTimeout(divElement.timeOutValue); + } + ) + // add back the timeout + divElement.addEventListener( + "mouseleave", + () => { + divElement.timeOutValue = window.setTimeout( + () => { + // Remove the toast from DOM + this._removeElement(divElement); + }, + this.options.duration + ) + } + ) + } + + // Adding an on-click destination path + if (typeof this.options.destination !== "undefined") { + divElement.addEventListener( + "click", + (event) => { + event.stopPropagation(); + if (this.options.newWindow === true) { + window.open(this.options.destination, "_blank"); + } else { + window.location = this.options.destination; + } + } + ); + } + + if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") { + divElement.addEventListener( + "click", + (event) => { + event.stopPropagation(); + this.options.onClick(); + } + ); + } + + // Adding offset + if (typeof this.options.offset === "object") { + + const x = this._getAxisOffsetAValue("x", this.options); + const y = this._getAxisOffsetAValue("y", this.options); + + const xOffset = this.options.position == "left" ? x : `-${x}`; + const yOffset = this.options.gravity == "toastify-top" ? y : `-${y}`; + + divElement.style.transform = `translate(${xOffset},${yOffset})`; + + } + + // Returning the generated element + return divElement; + } + + /** + * Remove the toast from the DOM + * @param {Element} toastElement + */ + _removeElement(toastElement) { + // Hiding the element + toastElement.className = toastElement.className.replace(" on", ""); + + // Removing the element from DOM after transition end + window.setTimeout( + () => { + // remove options node if any + if (this.options.node && this.options.node.parentNode) { + this.options.node.parentNode.removeChild(this.options.node); + } + + // Remove the element from the DOM, only when the parent node was not removed before. + if (toastElement.parentNode) { + toastElement.parentNode.removeChild(toastElement); + } + + // Calling the callback function + this.options.callback.call(toastElement); + + // Repositioning the toasts again + this._reposition(); + }, + 400 + ); // Binding `this` for function invocation + } + + /** + * Position the toast on the DOM + * @private + */ + _reposition() { + + // Top margins with gravity + let topLeftOffsetSize = { + top: 15, + bottom: 15, + }; + let topRightOffsetSize = { + top: 15, + bottom: 15, + }; + let offsetSize = { + top: 15, + bottom: 15, + }; + + // Get all toast messages that have been added to the container (selector) + let allToasts = this._rootElement.querySelectorAll(".toastify"); + + let classUsed; + + // Modifying the position of each toast element + for (let i = 0; i < allToasts.length; i++) { + // Getting the applied gravity + if (allToasts[i].classList.contains("toastify-top") === true) { + classUsed = "toastify-top"; + } else { + classUsed = "toastify-bottom"; + } + + let height = allToasts[i].offsetHeight; + classUsed = classUsed.substr(9, classUsed.length - 1) + // Spacing between toasts + let offset = 15; + + let width = window.innerWidth > 0 ? window.innerWidth : screen.width; + + // Show toast in center if screen with less than or equal to 360px + if (width <= 360) { + // Setting the position + allToasts[i].style[classUsed] = `${offsetSize[classUsed]}px`; + + offsetSize[classUsed] += height + offset; + } else { + if (allToasts[i].classList.contains("toastify-left") === true) { + // Setting the position + allToasts[i].style[classUsed] = `${topLeftOffsetSize[classUsed]}px`; + + topLeftOffsetSize[classUsed] += height + offset; + } else { + // Setting the position + allToasts[i].style[classUsed] = `${topRightOffsetSize[classUsed]}px`; + + topRightOffsetSize[classUsed] += height + offset; + } + } + } + } + + /** + * Helper function to get offset + * @param {string} axis - 'x' or 'y' + * @param {ToastifyConfigurationObject} options - The options object containing the offset object + */ + _getAxisOffsetAValue(axis, options) { + + if (options.offset[axis]) { + if (isNaN(options.offset[axis])) { + return options.offset[axis]; + } else { + return `${options.offset[axis]}px`; + } + } + + return '0px'; + + } + + } + + + // Returning the Toastify function to be assigned to the window object/module + function StartToastifyInstance(options) { + return new Toastify(options); + } + + export default StartToastifyInstance; diff --git a/node_modules/toastify-js/src/toastify.css b/node_modules/toastify-js/src/toastify.css new file mode 100644 index 000000000..ccd65c835 --- /dev/null +++ b/node_modules/toastify-js/src/toastify.css @@ -0,0 +1,85 @@ +/*! + * Toastify js 1.12.0 + * https://github.com/apvarun/toastify-js + * @license MIT licensed + * + * Copyright (C) 2018 Varun A P + */ + +.toastify { + padding: 12px 20px; + color: #ffffff; + display: inline-block; + box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3); + background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5); + background: linear-gradient(135deg, #73a5ff, #5477f5); + position: fixed; + opacity: 0; + transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1); + border-radius: 2px; + cursor: pointer; + text-decoration: none; + max-width: calc(50% - 20px); + z-index: 2147483647; +} + +.toastify.on { + opacity: 1; +} + +.toast-close { + background: transparent; + border: 0; + color: white; + cursor: pointer; + font-family: inherit; + font-size: 1em; + opacity: 0.4; + padding: 0 5px; +} + +.toastify-right { + right: 15px; +} + +.toastify-left { + left: 15px; +} + +.toastify-top { + top: -150px; +} + +.toastify-bottom { + bottom: -150px; +} + +.toastify-rounded { + border-radius: 25px; +} + +.toastify-avatar { + width: 1.5em; + height: 1.5em; + margin: -7px 5px; + border-radius: 2px; +} + +.toastify-center { + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + max-width: fit-content; + max-width: -moz-fit-content; +} + +@media only screen and (max-width: 360px) { + .toastify-right, .toastify-left { + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + max-width: fit-content; + } +} diff --git a/node_modules/toastify-js/src/toastify.js b/node_modules/toastify-js/src/toastify.js new file mode 100644 index 000000000..5d9659cd3 --- /dev/null +++ b/node_modules/toastify-js/src/toastify.js @@ -0,0 +1,445 @@ +/*! + * Toastify js 1.12.0 + * https://github.com/apvarun/toastify-js + * @license MIT licensed + * + * Copyright (C) 2018 Varun A P + */ +(function(root, factory) { + if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.Toastify = factory(); + } +})(this, function(global) { + // Object initialization + var Toastify = function(options) { + // Returning a new init object + return new Toastify.lib.init(options); + }, + // Library version + version = "1.12.0"; + + // Set the default global options + Toastify.defaults = { + oldestFirst: true, + text: "Toastify is awesome!", + node: undefined, + duration: 3000, + selector: undefined, + callback: function () { + }, + destination: undefined, + newWindow: false, + close: false, + gravity: "toastify-top", + positionLeft: false, + position: '', + backgroundColor: '', + avatar: "", + className: "", + stopOnFocus: true, + onClick: function () { + }, + offset: {x: 0, y: 0}, + escapeMarkup: true, + ariaLive: 'polite', + style: {background: ''} + }; + + // Defining the prototype of the object + Toastify.lib = Toastify.prototype = { + toastify: version, + + constructor: Toastify, + + // Initializing the object with required parameters + init: function(options) { + // Verifying and validating the input object + if (!options) { + options = {}; + } + + // Creating the options object + this.options = {}; + + this.toastElement = null; + + // Validating the options + this.options.text = options.text || Toastify.defaults.text; // Display message + this.options.node = options.node || Toastify.defaults.node; // Display content as node + this.options.duration = options.duration === 0 ? 0 : options.duration || Toastify.defaults.duration; // Display duration + this.options.selector = options.selector || Toastify.defaults.selector; // Parent selector + this.options.callback = options.callback || Toastify.defaults.callback; // Callback after display + this.options.destination = options.destination || Toastify.defaults.destination; // On-click destination + this.options.newWindow = options.newWindow || Toastify.defaults.newWindow; // Open destination in new window + this.options.close = options.close || Toastify.defaults.close; // Show toast close icon + this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : Toastify.defaults.gravity; // toast position - top or bottom + this.options.positionLeft = options.positionLeft || Toastify.defaults.positionLeft; // toast position - left or right + this.options.position = options.position || Toastify.defaults.position; // toast position - left or right + this.options.backgroundColor = options.backgroundColor || Toastify.defaults.backgroundColor; // toast background color + this.options.avatar = options.avatar || Toastify.defaults.avatar; // img element src - url or a path + this.options.className = options.className || Toastify.defaults.className; // additional class names for the toast + this.options.stopOnFocus = options.stopOnFocus === undefined ? Toastify.defaults.stopOnFocus : options.stopOnFocus; // stop timeout on focus + this.options.onClick = options.onClick || Toastify.defaults.onClick; // Callback after click + this.options.offset = options.offset || Toastify.defaults.offset; // toast offset + this.options.escapeMarkup = options.escapeMarkup !== undefined ? options.escapeMarkup : Toastify.defaults.escapeMarkup; + this.options.ariaLive = options.ariaLive || Toastify.defaults.ariaLive; + this.options.style = options.style || Toastify.defaults.style; + if(options.backgroundColor) { + this.options.style.background = options.backgroundColor; + } + + // Returning the current object for chaining functions + return this; + }, + + // Building the DOM element + buildToast: function() { + // Validating if the options are defined + if (!this.options) { + throw "Toastify is not initialized"; + } + + // Creating the DOM object + var divElement = document.createElement("div"); + divElement.className = "toastify on " + this.options.className; + + // Positioning toast to left or right or center + if (!!this.options.position) { + divElement.className += " toastify-" + this.options.position; + } else { + // To be depreciated in further versions + if (this.options.positionLeft === true) { + divElement.className += " toastify-left"; + console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.') + } else { + // Default position + divElement.className += " toastify-right"; + } + } + + // Assigning gravity of element + divElement.className += " " + this.options.gravity; + + if (this.options.backgroundColor) { + // This is being deprecated in favor of using the style HTML DOM property + console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.'); + } + + // Loop through our style object and apply styles to divElement + for (var property in this.options.style) { + divElement.style[property] = this.options.style[property]; + } + + // Announce the toast to screen readers + if (this.options.ariaLive) { + divElement.setAttribute('aria-live', this.options.ariaLive) + } + + // Adding the toast message/node + if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) { + // If we have a valid node, we insert it + divElement.appendChild(this.options.node) + } else { + if (this.options.escapeMarkup) { + divElement.innerText = this.options.text; + } else { + divElement.innerHTML = this.options.text; + } + + if (this.options.avatar !== "") { + var avatarElement = document.createElement("img"); + avatarElement.src = this.options.avatar; + + avatarElement.className = "toastify-avatar"; + + if (this.options.position == "left" || this.options.positionLeft === true) { + // Adding close icon on the left of content + divElement.appendChild(avatarElement); + } else { + // Adding close icon on the right of content + divElement.insertAdjacentElement("afterbegin", avatarElement); + } + } + } + + // Adding a close icon to the toast + if (this.options.close === true) { + // Create a span for close element + var closeElement = document.createElement("button"); + closeElement.type = "button"; + closeElement.setAttribute("aria-label", "Close"); + closeElement.className = "toast-close"; + closeElement.innerHTML = "✖"; + + // Triggering the removal of toast from DOM on close click + closeElement.addEventListener( + "click", + function(event) { + event.stopPropagation(); + this.removeElement(this.toastElement); + window.clearTimeout(this.toastElement.timeOutValue); + }.bind(this) + ); + + //Calculating screen width + var width = window.innerWidth > 0 ? window.innerWidth : screen.width; + + // Adding the close icon to the toast element + // Display on the right if screen width is less than or equal to 360px + if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) { + // Adding close icon on the left of content + divElement.insertAdjacentElement("afterbegin", closeElement); + } else { + // Adding close icon on the right of content + divElement.appendChild(closeElement); + } + } + + // Clear timeout while toast is focused + if (this.options.stopOnFocus && this.options.duration > 0) { + var self = this; + // stop countdown + divElement.addEventListener( + "mouseover", + function(event) { + window.clearTimeout(divElement.timeOutValue); + } + ) + // add back the timeout + divElement.addEventListener( + "mouseleave", + function() { + divElement.timeOutValue = window.setTimeout( + function() { + // Remove the toast from DOM + self.removeElement(divElement); + }, + self.options.duration + ) + } + ) + } + + // Adding an on-click destination path + if (typeof this.options.destination !== "undefined") { + divElement.addEventListener( + "click", + function(event) { + event.stopPropagation(); + if (this.options.newWindow === true) { + window.open(this.options.destination, "_blank"); + } else { + window.location = this.options.destination; + } + }.bind(this) + ); + } + + if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") { + divElement.addEventListener( + "click", + function(event) { + event.stopPropagation(); + this.options.onClick(); + }.bind(this) + ); + } + + // Adding offset + if(typeof this.options.offset === "object") { + + var x = getAxisOffsetAValue("x", this.options); + var y = getAxisOffsetAValue("y", this.options); + + var xOffset = this.options.position == "left" ? x : "-" + x; + var yOffset = this.options.gravity == "toastify-top" ? y : "-" + y; + + divElement.style.transform = "translate(" + xOffset + "," + yOffset + ")"; + + } + + // Returning the generated element + return divElement; + }, + + // Displaying the toast + showToast: function() { + // Creating the DOM object for the toast + this.toastElement = this.buildToast(); + + // Getting the root element to with the toast needs to be added + var rootElement; + if (typeof this.options.selector === "string") { + rootElement = document.getElementById(this.options.selector); + } else if (this.options.selector instanceof HTMLElement || (typeof ShadowRoot !== 'undefined' && this.options.selector instanceof ShadowRoot)) { + rootElement = this.options.selector; + } else { + rootElement = document.body; + } + + // Validating if root element is present in DOM + if (!rootElement) { + throw "Root element is not defined"; + } + + // Adding the DOM element + var elementToInsert = Toastify.defaults.oldestFirst ? rootElement.firstChild : rootElement.lastChild; + rootElement.insertBefore(this.toastElement, elementToInsert); + + // Repositioning the toasts in case multiple toasts are present + Toastify.reposition(); + + if (this.options.duration > 0) { + this.toastElement.timeOutValue = window.setTimeout( + function() { + // Remove the toast from DOM + this.removeElement(this.toastElement); + }.bind(this), + this.options.duration + ); // Binding `this` for function invocation + } + + // Supporting function chaining + return this; + }, + + hideToast: function() { + if (this.toastElement.timeOutValue) { + clearTimeout(this.toastElement.timeOutValue); + } + this.removeElement(this.toastElement); + }, + + // Removing the element from the DOM + removeElement: function(toastElement) { + // Hiding the element + // toastElement.classList.remove("on"); + toastElement.className = toastElement.className.replace(" on", ""); + + // Removing the element from DOM after transition end + window.setTimeout( + function() { + // remove options node if any + if (this.options.node && this.options.node.parentNode) { + this.options.node.parentNode.removeChild(this.options.node); + } + + // Remove the element from the DOM, only when the parent node was not removed before. + if (toastElement.parentNode) { + toastElement.parentNode.removeChild(toastElement); + } + + // Calling the callback function + this.options.callback.call(toastElement); + + // Repositioning the toasts again + Toastify.reposition(); + }.bind(this), + 400 + ); // Binding `this` for function invocation + }, + }; + + // Positioning the toasts on the DOM + Toastify.reposition = function() { + + // Top margins with gravity + var topLeftOffsetSize = { + top: 15, + bottom: 15, + }; + var topRightOffsetSize = { + top: 15, + bottom: 15, + }; + var offsetSize = { + top: 15, + bottom: 15, + }; + + // Get all toast messages on the DOM + var allToasts = document.getElementsByClassName("toastify"); + + var classUsed; + + // Modifying the position of each toast element + for (var i = 0; i < allToasts.length; i++) { + // Getting the applied gravity + if (containsClass(allToasts[i], "toastify-top") === true) { + classUsed = "toastify-top"; + } else { + classUsed = "toastify-bottom"; + } + + var height = allToasts[i].offsetHeight; + classUsed = classUsed.substr(9, classUsed.length-1) + // Spacing between toasts + var offset = 15; + + var width = window.innerWidth > 0 ? window.innerWidth : screen.width; + + // Show toast in center if screen with less than or equal to 360px + if (width <= 360) { + // Setting the position + allToasts[i].style[classUsed] = offsetSize[classUsed] + "px"; + + offsetSize[classUsed] += height + offset; + } else { + if (containsClass(allToasts[i], "toastify-left") === true) { + // Setting the position + allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px"; + + topLeftOffsetSize[classUsed] += height + offset; + } else { + // Setting the position + allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px"; + + topRightOffsetSize[classUsed] += height + offset; + } + } + } + + // Supporting function chaining + return this; + }; + + // Helper function to get offset. + function getAxisOffsetAValue(axis, options) { + + if(options.offset[axis]) { + if(isNaN(options.offset[axis])) { + return options.offset[axis]; + } + else { + return options.offset[axis] + 'px'; + } + } + + return '0px'; + + } + + function containsClass(elem, yourClass) { + if (!elem || typeof yourClass !== "string") { + return false; + } else if ( + elem.className && + elem.className + .trim() + .split(/\s+/gi) + .indexOf(yourClass) > -1 + ) { + return true; + } else { + return false; + } + } + + // Setting up the prototype for the init object + Toastify.lib.init.prototype = Toastify.lib; + + // Returning the Toastify function to be assigned to the window object/module + return Toastify; +}); diff --git a/package-lock.json b/package-lock.json index 64a3eef24..3890d3a15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "dev-trunk", + "name": "iTop", "lockfileVersion": 2, "requires": true, "packages": { @@ -15,7 +15,8 @@ "datatables.net-select": "^1.2.0", "scrollmagic": "^2.0.8", "selectize-plugin-a11y": "^1.1.0", - "tippy.js": "^6.2.5" + "tippy.js": "^6.2.5", + "toastify-js": "^1.12.0" } }, "node_modules/@fontsource/raleway": { @@ -103,6 +104,11 @@ "dependencies": { "@popperjs/core": "^2.4.4" } + }, + "node_modules/toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" } }, "dependencies": { @@ -181,6 +187,11 @@ "requires": { "@popperjs/core": "^2.4.4" } + }, + "toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" } } } diff --git a/package.json b/package.json index 3c1107b07..417b4e217 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "datatables.net-select": "^1.2.0", "scrollmagic": "^2.0.8", "selectize-plugin-a11y": "^1.1.0", - "tippy.js": "^6.2.5" + "tippy.js": "^6.2.5", + "toastify-js": "^1.12.0" } } diff --git a/pages/preferences.php b/pages/preferences.php index 4dce26961..5a24a2bb5 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -115,6 +115,7 @@ function DisplayPreferences($oP) $oSecondColumn->AddSubBlock($oMiscOptionsFieldset); $oMiscOptionsFieldset->AddSubBlock(GetObsoleteDataFieldBlock()); $oMiscOptionsFieldset->AddSubBlock(GetSummaryCardsFieldBlock()); + $oMiscOptionsFieldset->AddSubBlock(GetToastsPositionFieldBlock()); $oP->add_script( <<AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("bottom", Dict::S('UI:Preferences:General:Toasts:Bottom'), $sPosition === "bottom")); + $oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("top", Dict::S('UI:Preferences:General:Toasts:Top'), $sPosition === "top")); + + return $oSelect; +} + ///////////////////////////////////////////////////////////////////////////// // // Main program @@ -805,6 +825,12 @@ try { $bShowSummaryCards = (bool)utils::ReadParam('show_summary_cards', 0); appUserPreferences::SetPref('show_summary_cards', $bShowSummaryCards); + // - Toast notifications + $sToastsVerticalPosition = utils::ReadParam('toasts_vertical_position', "bottom"); + if(utils::IsNotNullOrEmptyString($sToastsVerticalPosition) && in_array($sToastsVerticalPosition, ["bottom", "top"], true)) { + appUserPreferences::SetPref('toasts_vertical_position', $sToastsVerticalPosition); + } + // Redirect to force a reload/display of the page in case language has been changed $oAppContext = new ApplicationContext(); $sURL = utils::GetAbsoluteUrlAppRoot().'pages/preferences.php?'.$oAppContext->GetForLink(); diff --git a/sources/Application/WebPage/iTopWebPage.php b/sources/Application/WebPage/iTopWebPage.php index 3be89d4f6..6a3a10f82 100644 --- a/sources/Application/WebPage/iTopWebPage.php +++ b/sources/Application/WebPage/iTopWebPage.php @@ -198,6 +198,9 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage // Tooltips $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.min.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.min.js'); + + // Toasts + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/toastify-js/src/toastify.js'); // Keyboard shortcuts $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/mousetrap/mousetrap.min.js');