Skip to content

IRSA-6706, IRSA-6571: Add Image on background of Results tab #1724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions buildScript/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ export default function makeWebpackConfig(config) {
use: ['@svgr/webpack'],
},
{
test: /\.(png|jpg|gif)$/,
type: 'asset/inline'
test: /\.(png|jpg|gif|webp)$/,
// webpack will automatically choose 'asset/inline' (data URI) for <8KB and 'asset/resource' (file path) otherwise
type: 'asset', // see https://webpack.js.org/guides/asset-modules/#general-asset-type
}
];

Expand Down
39 changes: 21 additions & 18 deletions src/firefly/js/templates/fireflyviewer/LandingPage.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Button, Divider, Sheet, Snackbar, Stack, Typography} from '@mui/joy';
import {Box, Button, Divider, Sheet, Snackbar, Stack, Typography} from '@mui/joy';
import React, {useContext, useState} from 'react';
import {elementType, shape, object, string, arrayOf, element, oneOf} from 'prop-types';
import {elementType, shape, object, string, arrayOf, oneOf, node} from 'prop-types';
import QueryStats from '@mui/icons-material/QueryStats';
import TipsAndUpdates from '@mui/icons-material/TipsAndUpdates';

Expand All @@ -24,12 +24,11 @@ export function LandingPage({slotProps={}, sx, ...props}) {
const defSlotProps = {
tabsMenuHint: {appTitle, id: APP_HINT_IDS.TABS_MENU, hintText: 'Choose a tab to search for or upload data.', sx: { left: '16rem' }},
bgMonitorHint: {appTitle, id: APP_HINT_IDS.BG_MONITOR, hintText: 'Load job results from background monitor', tipPlacement: 'end', sx: { right: 8 }},
topSection: { appTitle },
topSection: { title: `Welcome to ${appTitle}` },
bottomSection: {
icon: <QueryStats sx={{ width: '6rem', height: '6rem' }} />,
text: 'Getting Started',
subtext: undefined,
// subtext: undefined,
summaryText: 'Visualizations of the results will appear in this tab',
actionItems: [
{ text: 'Search for data', subtext: 'using the tabs above' },
Expand Down Expand Up @@ -57,11 +56,13 @@ export function LandingPage({slotProps={}, sx, ...props}) {
dispatchShowDropDown({view:fileDropEventAction, initArgs: {searchParams: {dropEvent: newEv}}});
},
}}>
<Stack justifyContent='space-between' width={1}>
<Stack spacing={2} width={1} px={4} py={3} {...slotProps?.contentSection}>
<Slot component={DefaultAppBranding} {...defSlotProps.topSection} slotProps={slotProps?.topSection}/>
<Slot component={EmptyResults} {...defSlotProps.bottomSection} slotProps={slotProps?.bottomSection}/>
</Stack>
<Stack justifyContent='space-between' width={1} spacing={1}>
<Box {...slotProps?.bgContainer}>
<Stack spacing={2} width={1} px={4} py={3} {...slotProps?.contentSection}>
<Slot component={DefaultAppBranding} {...defSlotProps.topSection} slotProps={slotProps?.topSection}/>
<Slot component={EmptyResults} {...defSlotProps.bottomSection} slotProps={slotProps?.bottomSection}/>
</Stack>
</Box>
{footer ? footer : undefined}
</Stack>
</FileDropZone>
Expand All @@ -70,17 +71,17 @@ export function LandingPage({slotProps={}, sx, ...props}) {
}


function DefaultAppBranding({appTitle, appDescription}) {
function DefaultAppBranding({title, desc}) {
return (
<Stack spacing={.25} alignItems='center'>
<Typography fontSize='xl3' color='neutral'>{`Welcome to ${appTitle}`}</Typography>
{appDescription && <Typography level={'body-md'}>{appDescription}</Typography>}
<Typography fontSize='xl3' color='neutral'>{title}</Typography>
{desc && <Typography level='body-md' textAlign='center'>{desc}</Typography>}
</Stack>
);
}


function EmptyResults({icon, text, subtext, summaryText, actionItems}) {
function EmptyResults({icon, text, subtext, summaryText, actionItems, slotProps}) {
const renderActionItem = ({text, subtext}) => (
<Stack spacing={.5} alignItems='center'>
<Typography level={'title-lg'} color={'primary'}>{text}</Typography>
Expand All @@ -89,7 +90,7 @@ function EmptyResults({icon, text, subtext, summaryText, actionItems}) {
);

return (
<Sheet variant='soft' sx={{pt: 8, pb: 4, px: 2}}>
<Sheet variant='soft' sx={{pt: 8, pb: 4, px: 2}} {...slotProps?.root}>
<Stack spacing={10} alignItems='center'>
<Stack spacing={Boolean(subtext) ? 6 : 3}>
<Stack spacing={2} alignItems='center'>
Expand Down Expand Up @@ -194,24 +195,26 @@ LandingPage.propTypes = {
...EmptyResults.propTypes, // defaults to EmptyResults.propTypes
}),
contentSection: object,
bgContainer: object,
})
};


DefaultAppBranding.propTypes = {
appTitle: string,
appDescription: string,
title: string,
desc: string,
};


EmptyResults.propTypes = {
icon: element,
icon: node,
text: string,
subtext: string,
actionItems: arrayOf(shape({
text: string,
subtext: string
}))
})),
slotProps: object,
};


Expand Down
98 changes: 77 additions & 21 deletions src/firefly/js/templates/hydra/HydraViewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@


import React, {useEffect} from 'react';
import PropTypes, {element, node, object, shape, string} from 'prop-types';
import {Typography} from '@mui/joy';
import {cloneDeep} from 'lodash/lang.js';
import PropTypes, {node, object, shape, string} from 'prop-types';
import {Divider, Typography} from '@mui/joy';
import {cloneDeep, defaultsDeep} from 'lodash';

import {
dispatchSetMenu,
Expand All @@ -32,6 +32,7 @@ import {LandingPage} from 'firefly/templates/fireflyviewer/LandingPage.jsx';
import {Stacker} from 'firefly/ui/Stacker.jsx';
import {setIf as setIfUndefined} from 'firefly/util/WebUtil.js';
import {handleInitialAppNavigation} from 'firefly/templates/common/FireflyLayout';
import {hexColorWithAlpha} from 'firefly/util/Color';


/*
Expand Down Expand Up @@ -152,39 +153,94 @@ function closeExpanded() {
dispatchSetLayoutMode(LO_MODE.expanded, LO_VIEW.none);
}

export function HydraLanding({icon, title, desc, slotProps={}, ...props} ) {
const HydraAppBranding = ({title, desc}) => (
<Stacker direction='column' alignItems='start'>
<Typography level='h4'>{title || 'Welcome message here'}</Typography>
<Typography level='body-md'>{desc || 'Additional description of this application'}</Typography>
</Stacker>
);

const defaultCommonProps = ({title, desc}) => ({
bgMonitorHint: { sx: { right: 50 } },
topSection: { title, desc }
});


const defaultPropsWithoutBgImage = ({icon}) => ({
contentSection: { sx: { maxWidth: '80rem', mx: 'auto' } },
topSection: { component: HydraAppBranding }, // use custom topSection instead of DefaultAppBranding
bottomSection: { icon }
});

const defaultPropsWithBgImage = ({bgImage}) => ({
// visually combine topSection & bottomSection into contentSection that can contrast with the bgContainer
bgContainer: {
sx: {
display: 'flex', flexGrow: 1,
backgroundImage: `url(${bgImage})`, backgroundSize: 'cover', backgroundPosition: 'center'
}
},
contentSection: {
sx: (theme) => {
// background image will remain same in both the theme modes, and we need text to contrast with the image,
// so we create a translucent dark overlay and put text over it as if it's dark theme
const darkPalette = theme.colorSchemes.dark.palette;
return {
maxWidth: '56rem', m: 'auto',
backgroundColor: hexColorWithAlpha(darkPalette.background.surface.split(', ')?.[1]?.slice(0, -1) ?? '#000', 0.6),
backdropFilter: 'blur(1px)',
'.MuiTypography-body-md, .MuiTypography-body-lg': { color: darkPalette.text.secondary },
'.MuiTypography-h2': { color: darkPalette.text.primary },
'.MuiTypography-colorPrimary': { color: `rgb(${darkPalette.primary.mainChannel})` },
'.MuiDivider-root': { backgroundColor: darkPalette.neutral.solidBg }
};
},
divider: <Divider sx={{ width: '4rem', alignSelf: 'center' }} />
},
bottomSection: {
icon: false, // to prevent default being applied (on undefined)
slotProps: {
root: { sx: { py: 4, pb: 0, backgroundColor: 'transparent' } }
}
}
});

const Greetings = () => (
<Stacker startDecorator={icon} direction='column' alignItems='start'>
<Typography level='h4'>{title || 'Welcome message here'}</Typography>
<Typography level='body-md'>{desc || 'Additional description of this application'}</Typography>
</Stacker>
);

const mSlotProps = cloneDeep(slotProps || {});
setIfUndefined(mSlotProps,'bgMonitorHint.sx.right', 50);
setIfUndefined(mSlotProps,'topSection.component', Greetings); // use custom topSection
setIfUndefined(mSlotProps,'contentSection.sx', {maxWidth: '80em', mx: 'auto'}); // limit page's width
export function HydraLanding({icon, title, desc, bgImage, slotProps={}, ...props} ) {
const mSlotProps = cloneDeep(slotProps);
defaultsDeep(mSlotProps, defaultCommonProps({title, desc}));

bgImage
? defaultsDeep(mSlotProps, defaultPropsWithBgImage({bgImage}))
: defaultsDeep(mSlotProps, defaultPropsWithoutBgImage({icon}));

return <LandingPage slotProps={mSlotProps} {...props}/>;
}

HydraLanding.propTypes = {
icon: element,
icon: node,
title: string,
desc: node,
bgImage: string,
...LandingPage.propTypes,
};


export function applyLayoutFix({slotProps, props}) {
const mSlotProps = cloneDeep(slotProps || {});
defaultsDeep(mSlotProps, {
banner: {
slotProps: {
// Adjust banner for appIcon
icon: {
style: { marginTop: -40 }, // Won't take precedence if defined in sx
sx: { color: 'primary.softActiveColor' } // Same as active tab's font color
},
tabs: { pl: '120px' }
}
},
landing: { title: props?.appTitle }
});

// adjust banner for appIcon
setIfUndefined(mSlotProps,'banner.slotProps.icon.style.marginTop', -40); //won't take precedence if defined in sx
setIfUndefined(mSlotProps,'banner.slotProps.icon.sx', {color: 'primary.softActiveColor'}); //same as active tab's font color
setIfUndefined(mSlotProps,'banner.slotProps.tabs.pl', '120px');

setIfUndefined(mSlotProps,'landing.title', props?.appTitle);
return mSlotProps;
}
2 changes: 2 additions & 0 deletions src/firefly/js/util/Color.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const toRGB= (color) => chroma.valid(color) ? chroma(color).rgb() : [0,0,
*/
export const getRGBA= (color) => chroma.valid(color) ? chroma(color).rgba() : [0,0,0,1];

export const hexColorWithAlpha = (color, alpha) => chroma.valid(color) ? chroma(color).alpha(alpha).hex() : color;


export const brighter = (colorStr,level=1) =>
chroma.valid(colorStr) ? chroma(colorStr).brighten(level).toString() : undefined;
Expand Down