From 53e431a676ea6df334a5f2b7796532adaf3b9d34 Mon Sep 17 00:00:00 2001 From: Leonardo Christino <leomilho@gmail.com> Date: Wed, 13 Sep 2023 11:20:29 +0200 Subject: [PATCH] feat(alert): extra time for alerts and allow cancel operation by user click --- .../authorization/dashboardAlerts.tsx | 66 +++++++++++++------ libs/shared/package.json | 2 + pnpm-lock.yaml | 34 ++++------ 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx b/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx index c7744b58e..f67aa55e4 100644 --- a/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx +++ b/libs/shared/lib/data-access/authorization/dashboardAlerts.tsx @@ -1,10 +1,11 @@ -import React, { ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode, useEffect, useState, useImperativeHandle, useRef } from 'react'; import Broker from '@graphpolaris/shared/lib/data-access/socket/broker'; import { useImmer } from 'use-immer'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import { useAppDispatch, useConfig } from '../store'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import { removeLastError, removeLastWarning } from '../store/configSlice'; +import { includes } from 'lodash-es'; type Message = { message: ReactNode; @@ -12,32 +13,52 @@ type Message = { }; export const DashboardAlerts = (props: { timer?: number }) => { - const [messages, setMessages] = useImmer<{ data: any; routingKey: string; message: Message; showing: boolean }[]>([]); - const timer = props.timer || 1200; + const [messages, setMessages] = useImmer<{ data: any; routingKey: string; message: Message; showing: boolean; key: string }[]>([]); + const timer = props.timer || 4000; const config = useConfig(); const dispatch = useAppDispatch(); + const ref = useRef<any>(null); - async function processMessage(message: Message | undefined, data: any, routingKey: string) { - // Queue the message to be shown for props.time time, then fade it out matching the animation of animate-closemenu fade out time - if (message) { + useImperativeHandle(ref, () => ({ + onAdd: (message: Message, data: any, routingKey: string) => { + let newMessage = { data, routingKey, message: message as Message, showing: true, key: `${Date.now()}-${Math.random()}` }; + setMessages((draft) => { + draft.unshift(newMessage); + return draft; + }); + return newMessage.key; + }, + onTimeout: (key: string) => { + setMessages((draft) => { + const idx = draft.findIndex((m) => m.key === key); + if (idx === -1) return draft; + draft.splice(idx, 1); + return draft; + }); + }, + onShowTimeout: (key: string) => { setMessages((draft) => { - draft.unshift({ data, routingKey, message: message as Message, showing: true }); + const idx = draft.findIndex((m) => m.key === key); + if (idx === -1) return draft; + draft[idx].showing = false; return draft; }); + setTimeout(() => { - setMessages((draft) => { - draft[draft.length - 1].showing = false; - // draft.pop(); - return draft; - }); + ref.current.onTimeout(key); + return true; + }, 300); // matches the animation of animate-closemenu + }, + })); - setTimeout(() => { - setMessages((draft) => { - draft.pop(); - return draft; - }); - return true; - }, 300); // matches the animation of animate-closemenu + async function processMessage(message: Message | undefined, data: any, routingKey: string) { + // Queue the message to be shown for props.time time, then fade it out matching the animation of animate-closemenu fade out time + if (message && ref?.current) { + const key = ref.current.onAdd(message, data, routingKey); + + setTimeout(() => { + ref.current.onShowTimeout(key); + return true; }, timer); } } @@ -125,13 +146,18 @@ export const DashboardAlerts = (props: { timer?: number }) => { <div key={i} className={ - `absolute bottom-5 right-5 w-fit min-w-[20rem] alert z-50 ` + + `absolute bottom-5 right-5 w-fit min-w-[20rem] cursor-pointer alert z-50 ` + (m.showing ? 'animate-openmenu ' : 'animate-closemenu ') + (m.message?.className || '') } style={{ transform: `translateY(-${i * 5}rem)`, }} + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + ref.current.onShowTimeout(m.key); + }} > <span className="flex flex-row content-center gap-3 text-white">{m.message.message}</span> {/* {!message && (m.data?.status ? m.data.status : m.routingKey)} */} diff --git a/libs/shared/package.json b/libs/shared/package.json index 66a91ab1e..5a7fd7345 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -50,6 +50,7 @@ "jspdf": "^2.5.1", "kepler.gl": "^2.5.5", "keycloak-js": "^21.1.1", + "lodash-es": "^4.17.21", "pixi-viewport": "^5.0.2", "pixi.js": "^7.1.4", "react-color": "^2.19.3", @@ -80,6 +81,7 @@ "@testing-library/react-hooks": "8.0.1", "@types/color": "^3.0.3", "@types/d3": "^7.4.0", + "@types/lodash-es": "^4.17.9", "@types/node": "18.13.0", "@types/react": "^18.0.27", "@types/react-color": "^3.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61424fc30..05e29cb6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,6 +313,9 @@ importers: keycloak-js: specifier: ^21.1.1 version: 21.1.1 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 pixi-viewport: specifier: ^5.0.2 version: 5.0.2 @@ -398,6 +401,9 @@ importers: '@types/d3': specifier: ^7.4.0 version: 7.4.0 + '@types/lodash-es': + specifier: ^4.17.9 + version: 4.17.9 '@types/node': specifier: 18.13.0 version: 18.13.0 @@ -662,7 +668,7 @@ importers: version: 8.7.0(eslint@7.32.0) eslint-config-turbo: specifier: latest - version: 1.10.12(eslint@7.32.0) + version: 1.10.13(eslint@7.32.0) eslint-plugin-react: specifier: 7.31.8 version: 7.31.8(eslint@7.32.0) @@ -8707,6 +8713,12 @@ packages: /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + /@types/lodash-es@4.17.9: + resolution: {integrity: sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==} + dependencies: + '@types/lodash': 4.14.191 + dev: true + /@types/lodash@4.14.191: resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} dev: true @@ -12241,15 +12253,6 @@ packages: dependencies: eslint: 7.32.0 - /eslint-config-turbo@1.10.12(eslint@7.32.0): - resolution: {integrity: sha512-z3jfh+D7UGYlzMWGh+Kqz++hf8LOE96q3o5R8X4HTjmxaBWlLAWG+0Ounr38h+JLR2TJno0hU9zfzoPNkR9BdA==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - eslint: 7.32.0 - eslint-plugin-turbo: 1.10.12(eslint@7.32.0) - dev: false - /eslint-config-turbo@1.10.13(eslint@7.32.0): resolution: {integrity: sha512-Ffa0SxkRCPMtfUX/HDanEqsWoLwZTQTAXO9W4IsOtycb2MzJDrVcLmoFW5sMwCrg7gjqbrC4ZJoD+1SPPzIVqg==} peerDependencies: @@ -12257,7 +12260,6 @@ packages: dependencies: eslint: 7.32.0 eslint-plugin-turbo: 1.10.13(eslint@7.32.0) - dev: true /eslint-import-resolver-node@0.3.7: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} @@ -12400,15 +12402,6 @@ packages: semver: 6.3.0 string.prototype.matchall: 4.0.8 - /eslint-plugin-turbo@1.10.12(eslint@7.32.0): - resolution: {integrity: sha512-uNbdj+ohZaYo4tFJ6dStRXu2FZigwulR1b3URPXe0Q8YaE7thuekKNP+54CHtZPH9Zey9dmDx5btAQl9mfzGOw==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - dotenv: 16.0.3 - eslint: 7.32.0 - dev: false - /eslint-plugin-turbo@1.10.13(eslint@7.32.0): resolution: {integrity: sha512-el4AAmn0zXmvHEyp1h0IQMfse10Vy8g5Vbg4IU3+vD9CSj5sDbX07iFVt8sCKg7og9Q5FAa9mXzlCf7t4vYgzg==} peerDependencies: @@ -12416,7 +12409,6 @@ packages: dependencies: dotenv: 16.0.3 eslint: 7.32.0 - dev: true /eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} -- GitLab