From 13ce684d23d884e2c5a5bc9e0f3923cdca7b3d0a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 7 Mar 2025 14:58:51 -0600 Subject: [PATCH] Initial checkin of Banners under QBrandingMetaData - includes migration from (now deprecated) MetaDataFilterInterface to MetaDataActionCustomizerInterface (stored on the QInstance and used by MetaDataAction) - includes migration from (now deprecated) environmentBannerText and environmentBannerColor in QBrandingMetaData to now be implemented as a banner --- package.json | 2 +- pom.xml | 2 +- src/App.tsx | 20 ++++ .../MaterialDashboardBannerSlots.java | 36 +++++++ .../components/horseshoe/sidenav/SideNav.tsx | 32 ++++-- .../horseshoe/sidenav/SideNavRoot.tsx | 1 + src/qqq/components/misc/Banners.tsx | 97 +++++++++++++++++++ src/qqq/layouts/BaseLayout.tsx | 35 +++++-- src/qqq/styles/qqq-override-styles.css | 31 ++++-- 9 files changed, 236 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardBannerSlots.java create mode 100644 src/qqq/components/misc/Banners.tsx diff --git a/package.json b/package.json index 102e896..ccb1bf9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@auth0/auth0-react": "1.10.2", "@emotion/react": "11.7.1", "@emotion/styled": "11.6.0", - "@kingsrook/qqq-frontend-core": "1.0.114", + "@kingsrook/qqq-frontend-core": "1.0.117", "@mui/icons-material": "5.4.1", "@mui/material": "5.11.1", "@mui/styles": "5.11.1", diff --git a/pom.xml b/pom.xml index 33d28d1..94f876b 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ com.kingsrook.qqq qqq-backend-core - 0.21.0 + 0.25.0-integration-sprint-62-20250307-205536 org.slf4j diff --git a/src/App.tsx b/src/App.tsx index fd4c6b5..62ca7a2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,6 +29,7 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import Avatar from "@mui/material/Avatar"; +import Box from "@mui/material/Box"; import CssBaseline from "@mui/material/CssBaseline"; import Icon from "@mui/material/Icon"; import {ThemeProvider} from "@mui/material/styles"; @@ -38,6 +39,7 @@ import jwt_decode from "jwt-decode"; import QContext from "QContext"; import Sidenav from "qqq/components/horseshoe/sidenav/SideNav"; import theme from "qqq/components/legacy/Theme"; +import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners"; import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "qqq/context"; import AppHome from "qqq/pages/apps/Home"; import NoApps from "qqq/pages/apps/NoApps"; @@ -691,6 +693,23 @@ export default function App() } + /*************************************************************************** + ** + ***************************************************************************/ + function banner(): JSX.Element | null + { + const banner = getBanner(metaData?.branding, "QFMD_TOP_OF_SITE"); + + if (!banner) + { + return (null); + } + + return ( + {makeBannerContent(banner)} + ); + } + return ( appRoutes && ( @@ -718,6 +737,7 @@ export default function App() + {banner()} . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.model.metadata; + + +import com.kingsrook.qqq.backend.core.model.metadata.branding.BannerSlot; + + +/******************************************************************************* + ** + *******************************************************************************/ +public enum MaterialDashboardBannerSlots implements BannerSlot +{ + QFMD_TOP_OF_SITE, + QFMD_TOP_OF_BODY, + QFMD_SIDE_NAV_UNDER_LOGO +} diff --git a/src/qqq/components/horseshoe/sidenav/SideNav.tsx b/src/qqq/components/horseshoe/sidenav/SideNav.tsx index 8ac14e9..da14699 100644 --- a/src/qqq/components/horseshoe/sidenav/SideNav.tsx +++ b/src/qqq/components/horseshoe/sidenav/SideNav.tsx @@ -34,6 +34,7 @@ import SideNavList from "qqq/components/horseshoe/sidenav/SideNavList"; import SidenavRoot from "qqq/components/horseshoe/sidenav/SideNavRoot"; import sidenavLogoLabel from "qqq/components/horseshoe/sidenav/styles/SideNav"; import MDTypography from "qqq/components/legacy/MDTypography"; +import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners"; import {setMiniSidenav, setTransparentSidenav, setWhiteSidenav, useMaterialUIController,} from "qqq/context"; @@ -300,6 +301,30 @@ function Sidenav({color, icon, logo, appName, branding, routes, ...rest}: Props) } ); + /*************************************************************************** + ** + ***************************************************************************/ + function EnvironmentBanner({branding}: { branding: QBrandingMetaData }): JSX.Element | null + { + // deprecated! + if (branding && branding.environmentBannerText) + { + return + {branding.environmentBannerText} + ; + } + + const banner = getBanner(branding, "QFMD_SIDE_NAV_UNDER_LOGO"); + if (banner) + { + return + {makeBannerContent(banner)} + ; + } + + return (null); + } + return ( } - { - branding && branding.environmentBannerText && - - {branding.environmentBannerText} - - } + . + */ + +import {Banner} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Banner"; +import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData"; +import parse from "html-react-parser"; + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// On may render a banner using the functions in this file as: // +// // +// const banner = getBanner(branding, "QFMD_SIDE_NAV_UNDER_LOGO"); // +// return ( // +// {makeBannerContent(banner)} // +// ); // +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +/*************************************************************************** + ** + ***************************************************************************/ +export function getBanner(branding: QBrandingMetaData, slot: string): Banner | null +{ + if (branding?.banners?.has(slot)) + { + return (branding.banners.get(slot)); + } + + return (null); +} + + +/*************************************************************************** + ** + ***************************************************************************/ +export function getBannerStyles(banner: Banner) +{ + let bgColor = ""; + let color = ""; + + if (banner) + { + if (banner.backgroundColor) + { + bgColor = banner.backgroundColor; + } + + if (banner.textColor) + { + bgColor = banner.textColor; + } + } + + const rest = banner?.additionalStyles ?? {}; + + return ({ + backgroundColor: bgColor, + color: color, + ...rest + }); +} + + +/*************************************************************************** + ** + ***************************************************************************/ +export function getBannerClassName(banner: Banner) +{ + return `banner ${banner?.severity?.toLowerCase()}`; +} + + +/*************************************************************************** + ** + ***************************************************************************/ +export function makeBannerContent(banner: Banner): JSX.Element +{ + return <>{banner?.messageHTML ? parse(banner?.messageHTML) : banner?.messageText}; +} + diff --git a/src/qqq/layouts/BaseLayout.tsx b/src/qqq/layouts/BaseLayout.tsx index 94f6aea..d56791d 100644 --- a/src/qqq/layouts/BaseLayout.tsx +++ b/src/qqq/layouts/BaseLayout.tsx @@ -21,11 +21,12 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import Box from "@mui/material/Box"; -import {ReactNode, useEffect, useState} from "react"; import Footer from "qqq/components/horseshoe/Footer"; import NavBar from "qqq/components/horseshoe/NavBar"; +import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners"; import DashboardLayout from "qqq/layouts/DashboardLayout"; import Client from "qqq/utils/qqq/Client"; +import {ReactNode, useEffect, useState} from "react"; interface Props { @@ -80,12 +81,34 @@ function BaseLayout({stickyNavbar, children}: Props): JSX.Element return () => window.removeEventListener("resize", handleTabsOrientation); }, [tabsOrientation]); + + /*************************************************************************** + ** + ***************************************************************************/ + function banner(): JSX.Element | null + { + const banner = getBanner(metaData?.branding, "QFMD_TOP_OF_BODY"); + + if (!banner) + { + return (null); + } + + return ( + {makeBannerContent(banner)} + ); + } + + return ( - - - {children} -