🐛 App tile flex fix (#1255)

* 🎨 flex

* 🎨 Improved flex organization on app tile

* ✏️ disallowAppNameProgagation to Propagation

*  User customizable lineclamp and config migration
This commit is contained in:
Tagaishi
2023-08-11 20:47:14 +02:00
committed by GitHub
parent d5f74eb4bf
commit d6736d6539
9 changed files with 121 additions and 94 deletions

View File

@@ -72,6 +72,10 @@
"bottom":"Bottom", "bottom":"Bottom",
"left":"Left" "left":"Left"
} }
},
"lineClampAppName":{
"label":"App Name Line Clamp",
"description":"Defines on how many lines your title should fit at it's maximum. Set 0 for unlimited."
} }
}, },
"integration": { "integration": {

View File

@@ -205,7 +205,7 @@ export const EditAppModal = ({
<NetworkTab form={form} /> <NetworkTab form={form} />
<AppearanceTab <AppearanceTab
form={form} form={form}
disallowAppNameProgagation={() => setAllowAppNamePropagation(false)} disallowAppNamePropagation={() => setAllowAppNamePropagation(false)}
allowAppNamePropagation={allowAppNamePropagation} allowAppNamePropagation={allowAppNamePropagation}
/> />
<IntegrationTab form={form} /> <IntegrationTab form={form} />

View File

@@ -1,4 +1,4 @@
import { Flex, Select, Stack, Switch, Tabs } from '@mantine/core'; import { Flex, NumberInput, Select, Stack, Switch, Tabs } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form'; import { UseFormReturnType } from '@mantine/form';
import { useDebouncedValue } from '@mantine/hooks'; import { useDebouncedValue } from '@mantine/hooks';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
@@ -9,13 +9,13 @@ import { IconSelector } from '../../../../../IconSelector/IconSelector';
interface AppearanceTabProps { interface AppearanceTabProps {
form: UseFormReturnType<AppType, (values: AppType) => AppType>; form: UseFormReturnType<AppType, (values: AppType) => AppType>;
disallowAppNameProgagation: () => void; disallowAppNamePropagation: () => void;
allowAppNamePropagation: boolean; allowAppNamePropagation: boolean;
} }
export const AppearanceTab = ({ export const AppearanceTab = ({
form, form,
disallowAppNameProgagation, disallowAppNamePropagation,
allowAppNamePropagation, allowAppNamePropagation,
}: AppearanceTabProps) => { }: AppearanceTabProps) => {
const iconSelectorRef = useRef(); const iconSelectorRef = useRef();
@@ -46,7 +46,7 @@ export const AppearanceTab = ({
defaultValue={form.values.appearance.iconUrl} defaultValue={form.values.appearance.iconUrl}
onChange={(value) => { onChange={(value) => {
form.setFieldValue('appearance.iconUrl', value); form.setFieldValue('appearance.iconUrl', value);
disallowAppNameProgagation(); disallowAppNamePropagation();
}} }}
value={form.values.appearance.iconUrl} value={form.values.appearance.iconUrl}
ref={iconSelectorRef} ref={iconSelectorRef}
@@ -66,26 +66,41 @@ export const AppearanceTab = ({
}} }}
/> />
{form.values.appearance.appNameStatus === 'normal' && ( {form.values.appearance.appNameStatus === 'normal' && (
<Select <>
label={t('appearance.positionAppName.label')} <Select
description={t('appearance.positionAppName.description')} label={t('appearance.positionAppName.label')}
data={[ description={t('appearance.positionAppName.description')}
{ value: 'column', label: t('appearance.positionAppName.dropdown.top') as string }, data={[
{ {
value: 'row-reverse', value: 'column',
label: t('appearance.positionAppName.dropdown.right') as string, label: t('appearance.positionAppName.dropdown.top') as string },
}, {
{ value: 'row-reverse',
value: 'column-reverse', label: t('appearance.positionAppName.dropdown.right') as string,
label: t('appearance.positionAppName.dropdown.bottom') as string, },
}, {
{ value: 'row', label: t('appearance.positionAppName.dropdown.left') as string }, value: 'column-reverse',
]} label: t('appearance.positionAppName.dropdown.bottom') as string,
{...form.getInputProps('appearance.positionAppName')} },
onChange={(value) => { {
form.setFieldValue('appearance.positionAppName', value); value: 'row',
}} label: t('appearance.positionAppName.dropdown.left') as string },
/> ]}
{...form.getInputProps('appearance.positionAppName')}
onChange={(value) => {
form.setFieldValue('appearance.positionAppName', value);
}}
/>
<NumberInput
label={t('appearance.lineClampAppName.label')}
description={t('appearance.lineClampAppName.description')}
min={0}
{...form.getInputProps('appearance.lineClampAppName')}
onChange={(value) => {
form.setFieldValue('appearance.lineClampAppName', value);
}}
/>
</>
)} )}
</Stack> </Stack>
</Tabs.Panel> </Tabs.Panel>

View File

@@ -95,6 +95,7 @@ export const AvailableElementTypes = ({
iconUrl: '/imgs/logo/logo.png', iconUrl: '/imgs/logo/logo.png',
appNameStatus: 'normal', appNameStatus: 'normal',
positionAppName: 'column', positionAppName: 'column',
lineClampAppName: 1,
}, },
network: { network: {
enabledStatusChecker: true, enabledStatusChecker: true,

View File

@@ -30,7 +30,7 @@ export const AppPing = ({ app }: AppPingProps) => {
<motion.div <motion.div
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: replaceDotWithIcon ? 5 : 20, bottom: replaceDotWithIcon ? 0 : 20,
right: replaceDotWithIcon ? 8 : 20, right: replaceDotWithIcon ? 8 : 20,
zIndex: 2, zIndex: 2,
}} }}

View File

@@ -1,10 +1,9 @@
import { Box, Flex, Text, Tooltip, UnstyledButton } from '@mantine/core'; import { Affix, Box, Text, Tooltip, UnstyledButton } from '@mantine/core';
import { createStyles, useMantineTheme } from '@mantine/styles'; import { createStyles, useMantineTheme } from '@mantine/styles';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import { AppType } from '../../../../types/app'; import { AppType } from '../../../../types/app';
import { useCardStyles } from '../../../layout/useCardStyles';
import { useEditModeStore } from '../../Views/useEditModeStore'; import { useEditModeStore } from '../../Views/useEditModeStore';
import { HomarrCardWrapper } from '../HomarrCardWrapper'; import { HomarrCardWrapper } from '../HomarrCardWrapper';
import { BaseTileProps } from '../type'; import { BaseTileProps } from '../type';
@@ -17,89 +16,70 @@ interface AppTileProps extends BaseTileProps {
export const AppTile = ({ className, app }: AppTileProps) => { export const AppTile = ({ className, app }: AppTileProps) => {
const isEditMode = useEditModeStore((x) => x.enabled); const isEditMode = useEditModeStore((x) => x.enabled);
const { cx, classes } = useStyles(); const { cx, classes } = useStyles();
const { colorScheme } = useMantineTheme(); const { colorScheme } = useMantineTheme();
const tooltipContent = [ const tooltipContent = [
app.appearance.appNameStatus === "hover" ? app.name : undefined, app.appearance.appNameStatus === 'hover' ? app.name : undefined,
app.behaviour.tooltipDescription app.behaviour.tooltipDescription,
].filter( e => e ).join( ': ' ); ]
.filter((e) => e)
.join(': ');
const { const isRow = app.appearance.positionAppName.includes('row');
classes: { card: cardClass },
} = useCardStyles(false);
function Inner() { function Inner() {
return ( return (
<Tooltip.Floating <Tooltip.Floating
label={tooltipContent} label={tooltipContent}
position="right-start" position="right-start"
c={ colorScheme === 'light' ? "black" : "dark.0" } c={colorScheme === 'light' ? 'black' : 'dark.0'}
color={ colorScheme === 'light' ? "gray.2" : "dark.4" } color={colorScheme === 'light' ? 'gray.2' : 'dark.4'}
multiline multiline
disabled={tooltipContent === ''} disabled={!tooltipContent}
styles={{ tooltip: { '&': { maxWidth: 300, }, }, }} styles={{ tooltip: { maxWidth: 300 } }}
> >
<Flex <Box
m={0} className={`${classes.base} ${cx(classes.appContent, 'dashboard-tile-app')}`}
p={0}
justify="space-around"
align="center"
h="100%" h="100%"
w="100%" sx={{
className="dashboard-tile-app" flexFlow: app.appearance.positionAppName ?? 'column',
direction={app.appearance.positionAppName ?? 'column'} }}
> >
<Box w="100%" hidden={["hover", "hidden"].includes(app.appearance.appNameStatus)}> {app.appearance.appNameStatus === 'normal' && (
<Text <Text
w="100%" className={cx(classes.appName, 'dashboard-tile-app-title')}
size="md" fw={700}
ta="center" size="md"
weight={700} ta="center"
className={cx(classes.appName, 'dashboard-tile-app-title')}
lineClamp={1}
>
{app.name}
</Text>
</Box>
<Box
w="100%"
h="100%"
display="flex"
sx={{ sx={{
alignContent: 'center', flex: isRow ? '1' : undefined,
justifyContent: 'center',
flex: '1 1 auto',
flexWrap: 'wrap',
}} }}
lineClamp={app.appearance.lineClampAppName}
> >
<motion.img {app.name}
className={classes.image} </Text>
height="85%" )}
style={{ <motion.img
objectFit: 'contain', className={cx(classes.appImage, 'dashboard-tile-app-image')}
}} src={app.appearance.iconUrl}
src={app.appearance.iconUrl} alt={app.name}
alt={app.name} whileHover={{ scale: 1 }}
whileHover={{ initial={{ scale: 0.9 }}
scale: 1.2, style={{
transition: { duration: 0.2 }, width: isRow ? 0 : undefined,
}} }}
/> />
</Box> </Box>
</Flex>
</Tooltip.Floating> </Tooltip.Floating>
); );
} }
return ( return (
<HomarrCardWrapper className={className}> <HomarrCardWrapper className={className} p={10}>
<AppMenu app={app} /> <AppMenu app={app} />
{!app.url || isEditMode ? ( {!app.url || isEditMode ? (
<UnstyledButton <UnstyledButton
className={classes.button} className={`${classes.button} ${classes.base}`}
style={{ pointerEvents: isEditMode ? 'none' : 'auto' }} style={{ pointerEvents: isEditMode ? 'none' : 'auto' }}
> >
<Inner /> <Inner />
@@ -110,7 +90,7 @@ export const AppTile = ({ className, app }: AppTileProps) => {
component={Link} component={Link}
href={app.behaviour.externalUrl.length > 0 ? app.behaviour.externalUrl : app.url} href={app.behaviour.externalUrl.length > 0 ? app.behaviour.externalUrl : app.url}
target={app.behaviour.isOpeningNewTab ? '_blank' : '_self'} target={app.behaviour.isOpeningNewTab ? '_blank' : '_self'}
className={cx(classes.button)} className={`${classes.button} ${classes.base}`}
> >
<Inner /> <Inner />
</UnstyledButton> </UnstyledButton>
@@ -121,19 +101,27 @@ export const AppTile = ({ className, app }: AppTileProps) => {
}; };
const useStyles = createStyles((theme, _params, getRef) => ({ const useStyles = createStyles((theme, _params, getRef) => ({
image: { base: {
maxHeight: '90%', display: 'flex',
maxWidth: '90%', alignItems: 'center',
justifyContent: 'center',
},
appContent: {
gap: 0,
overflow: 'visible',
flexGrow: 5,
}, },
appName: { appName: {
wordBreak: 'break-word', wordBreak: 'break-word',
}, },
appImage: {
flex: '1',
objectFit: 'contain',
overflowY: 'auto',
},
button: { button: {
height: '100%', height: '100%',
width: '100%', width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 4, gap: 4,
}, },
})); }));

View File

@@ -130,7 +130,8 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
appearance: { appearance: {
iconUrl: '/imgs/logo/logo.png', iconUrl: '/imgs/logo/logo.png',
appNameStatus: 'normal', appNameStatus: 'normal',
positionAppName: 'column' positionAppName: 'column',
lineClampAppName: 1,
}, },
network: { network: {
enabledStatusChecker: true, enabledStatusChecker: true,

View File

@@ -10,6 +10,8 @@ export const getFrontendConfig = async (name: string): Promise<ConfigType> => {
let config = getConfig(name); let config = getConfig(name);
let shouldMigrateConfig = false; let shouldMigrateConfig = false;
config = migrateAppConfigs(config);
const anyWeatherWidgetWithStringLocation = config.widgets.some( const anyWeatherWidgetWithStringLocation = config.widgets.some(
(widget) => widget.type === 'weather' && typeof widget.properties.location === 'string' (widget) => widget.type === 'weather' && typeof widget.properties.location === 'string'
); );
@@ -129,3 +131,18 @@ const migratePiholeIntegrationField = (config: BackendConfigType) => {
}), }),
}; };
}; };
const migrateAppConfigs = (config: BackendConfigType) => {
return {
...config,
apps: config.apps.map((app) => ({
...app,
appearance: {
...app.appearance,
appNameStatus: app.appearance.appNameStatus?? 'normal',
positionAppName: app.appearance.positionAppName?? 'column',
lineClampAppName: app.appearance.lineClampAppName?? 1,
}
}))
}
}

View File

@@ -36,6 +36,7 @@ interface AppAppearanceType {
iconUrl: string; iconUrl: string;
appNameStatus: "normal"|"hover"|"hidden"; appNameStatus: "normal"|"hover"|"hidden";
positionAppName: Property.FlexDirection; positionAppName: Property.FlexDirection;
lineClampAppName: number;
} }
export type IntegrationType = export type IntegrationType =