diff --git a/src/Components/Cards/Base.tsx b/src/Components/Cards/Base.tsx
index e61a55047..078abd417 100644
--- a/src/Components/Cards/Base.tsx
+++ b/src/Components/Cards/Base.tsx
@@ -1,21 +1,27 @@
// @flow
-import React from 'react';
+import React, { useCallback, useEffect } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import PropTypes from 'prop-types';
import { makeStyles, Theme, useTheme } from '@material-ui/core/styles';
import ButtonBase from '@material-ui/core/ButtonBase';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
+import Dialog from '@material-ui/core/Dialog';
import Grid from '@material-ui/core/Grid';
import IconButton from '@material-ui/core/IconButton';
import Typography from '@material-ui/core/Typography';
+import useMediaQuery from '@material-ui/core/useMediaQuery';
import ArrowDownwardsIcon from '@material-ui/icons/ArrowDownward';
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
+import CloseIcon from '@material-ui/icons/Close';
import DeleteIcon from '@material-ui/icons/Delete';
import EditIcon from '@material-ui/icons/Edit';
import { CardProps } from '../Configuration/Config';
-import { HomeAssistantChangeProps } from '../HomeAssistant/HomeAssistant';
+import {
+ HomeAssistantChangeProps,
+ entitySizes
+} from '../HomeAssistant/HomeAssistant';
import ConfirmDialog from '../Utils/ConfirmDialog';
import EditCard from '../Configuration/EditCard/EditCard';
import Entity from '../HomeAssistant/Cards/Entity';
@@ -27,6 +33,11 @@ const useStyles = makeStyles((theme: Theme) => ({
root: {
overflow: 'visible'
},
+ buttonExpand: {
+ position: 'absolute',
+ top: theme.spacing(1.2),
+ right: theme.spacing(0.8)
+ },
card: {
flex: 1
},
@@ -61,7 +72,7 @@ const useStyles = makeStyles((theme: Theme) => ({
margin: 4
},
title: {
- minHeight: 20,
+ minHeight: theme.spacing(3),
fontWeight: 400,
lineHeight: 1.2
},
@@ -75,15 +86,116 @@ export interface BaseProps
HomeAssistantChangeProps {
card: CardProps;
editing: number;
+ expandable: boolean;
+ handleCloseExpand?: () => void;
handleDelete?: () => void;
handleMoveDown?: () => void;
handleMoveUp?: () => void;
handleUpdate: (data: CardProps) => void;
}
+let holdTimeout: NodeJS.Timeout;
function Base(props: BaseProps) {
const [deleteConfirm, setDeleteConfirm] = React.useState(false);
const [editCard, setEditCard] = React.useState(false);
+ const [expandable, setExpandable] = React.useState(false);
+ const [expandCard, setExpandCard] = React.useState(false);
+ const [height, setHeight] = React.useState();
+ const [width, setWidth] = React.useState();
+ const [toggleable, setToggleable] = React.useState();
+
+ const classes = useStyles();
+ const theme = useTheme();
+ const fullScreen = useMediaQuery(theme.breakpoints.down('xs'));
+
+ const handleSetHeight = useCallback(
+ (cardSize: number, entitySizeKey?: string) => {
+ let h =
+ !props.expandable && entitySizeKey
+ ? entitySizes[entitySizeKey].height * cardSize
+ : props.editing === 2 || !props.card.height
+ ? 'initial'
+ : isNaN(props.card.height)
+ ? props.card.height
+ : props.card.height * cardSize || cardSize;
+ if (h) setHeight(h);
+ },
+ [props.card.height, props.editing, props.expandable]
+ );
+
+ const handleSetWidth = useCallback(
+ (cardSize: number, entitySizeKey?: string) => {
+ let w =
+ !props.expandable && entitySizeKey
+ ? entitySizes[entitySizeKey].width * cardSize
+ : props.editing === 2 || !props.card.width
+ ? -1
+ : props.card.width * cardSize || cardSize;
+ if (w !== cardSize) {
+ // Adjust for margins
+ w = props.card.width
+ ? w + (props.card.width - 1) * theme.spacing(1)
+ : w;
+ }
+ if (w) setWidth(w);
+ },
+ [props.card.width, props.editing, props.expandable, theme]
+ );
+
+ const handleSetToggleable = useCallback(() => {
+ setToggleable(
+ props.editing === 1
+ ? false
+ : !props.card.disabled && props.card.toggleable
+ );
+ }, [props.card.disabled, props.card.toggleable, props.editing]);
+
+ const handleSetExpandable = useCallback(
+ (entitySizeKey: string) => {
+ if (entitySizeKey)
+ if (props.card.height && props.card.width)
+ setExpandable(
+ props.card.height < entitySizes[entitySizeKey].height ||
+ props.card.width < entitySizes[entitySizeKey].width
+ );
+ else if (props.card.width)
+ setExpandable(props.card.width < entitySizes[entitySizeKey].width);
+ },
+ [props.card.height, props.card.width]
+ );
+
+ useEffect(() => {
+ const cardSize = theme.breakpoints.down('sm') ? 140 : 120;
+
+ const entitySizeKey =
+ props.card.entity &&
+ Object.keys(entitySizes).find(
+ (domain: string) => domain === props.card.entity!.split('.')[0]
+ );
+ handleSetHeight(cardSize, entitySizeKey);
+ handleSetWidth(cardSize, entitySizeKey);
+
+ handleSetToggleable();
+
+ if (
+ props.expandable &&
+ props.card.type === 'entity' &&
+ props.card.entity &&
+ entitySizeKey
+ )
+ handleSetExpandable(entitySizeKey);
+ }, [
+ props.card.entity,
+ props.card.type,
+ props.expandable,
+ handleSetExpandable,
+ handleSetHeight,
+ handleSetToggleable,
+ handleSetWidth,
+ height,
+ theme.breakpoints,
+ width
+ ]);
function handleDeleteConfirm() {
setDeleteConfirm(true);
@@ -125,27 +237,26 @@ function Base(props: BaseProps) {
setEditCard(false);
}
- const classes = useStyles();
- const theme = useTheme();
+ function handleExpand() {
+ setExpandCard(true);
+ }
- const cardSize = theme.breakpoints.down('sm') ? 140 : 120;
+ function handleCloseExpand() {
+ setExpandCard(false);
+ }
- let height =
- props.editing === 2 || !props.card.height
- ? 'initial'
- : isNaN(props.card.height)
- ? props.card.height
- : props.card.height! * cardSize || cardSize;
- if (props.card.title === 'test') console.log('height:', height);
- let width =
- props.editing === 2 ? -1 : props.card.width! * cardSize || cardSize;
- if (width !== cardSize) {
- // Adjust for margins
- width = props.card.width! * theme.spacing(1) + width;
+ function handleHold() {
+ if (expandable) {
+ handleHoldCancel();
+ holdTimeout = setTimeout(() => {
+ handleExpand();
+ }, 1000);
+ }
}
- const toggleable =
- props.editing === 1 ? false : !props.card.disabled && props.card.toggleable;
+ function handleHoldCancel() {
+ if (holdTimeout) clearTimeout(holdTimeout);
+ }
return (
@@ -157,7 +268,10 @@ function Base(props: BaseProps) {
cursor: !toggleable ? 'unset' : 'pointer',
userSelect: !toggleable ? 'text' : 'none'
}}
- onClick={toggleable ? handleHassToggle : undefined}>
+ onClick={toggleable ? handleHassToggle : undefined}
+ onMouseDown={handleHold}
+ onMouseUp={handleHoldCancel}
+ onMouseLeave={handleHoldCancel}>
- {props.card.title && (
-
- {props.card.title}
-
- )}
+
+
+ {props.card.title && (
+
+ {props.card.title}
+
+ )}
+
+
+ {!props.expandable && (
+
+
+
+ )}
+
+
{props.card.type === 'entity' && (
+ {expandCard && (
+
+ )}
);
}
diff --git a/src/Components/Configuration/Config.tsx b/src/Components/Configuration/Config.tsx
index e1e66344e..f48d757f9 100644
--- a/src/Components/Configuration/Config.tsx
+++ b/src/Components/Configuration/Config.tsx
@@ -61,7 +61,7 @@ export type CardProps = {
key: string;
group: string;
type: string;
- width: number;
+ width?: number;
height?: number;
square?: boolean;
padding?: number | string;
diff --git a/src/Components/Configuration/EditCard/EditCard.tsx b/src/Components/Configuration/EditCard/EditCard.tsx
index 6869bd6f9..cc1749fb6 100644
--- a/src/Components/Configuration/EditCard/EditCard.tsx
+++ b/src/Components/Configuration/EditCard/EditCard.tsx
@@ -148,7 +148,7 @@ function EditCard(props: EditCardProps) {
container
alignContent="center"
justify="space-around">
-
+
diff --git a/src/Components/HomeAssistant/Cards/AlarmPanel.tsx b/src/Components/HomeAssistant/Cards/AlarmPanel.tsx
index 861349e16..2fe316eb7 100644
--- a/src/Components/HomeAssistant/Cards/AlarmPanel.tsx
+++ b/src/Components/HomeAssistant/Cards/AlarmPanel.tsx
@@ -104,74 +104,76 @@ function AlarmPanel(props: AlarmPanelProps) {
{properCase(entity.state)}
- {props.card.width > 1 && (
-
- {!armed && (
-
-
-
- )}
- {!armed && (
-
-
-
- )}
- {armed && (
-
-
-
- )}
-
- )}
- {attributes.code_arm_required && props.card.width > 1 && (
-
-
-
- )}
+ {!props.card.width ||
+ (props.card.width > 1 && (
+
+ {!armed && (
+
+
+
+ )}
+ {!armed && (
+
+
+
+ )}
+ {armed && (
+
+
+
+ )}
+
+ ))}
+ {attributes.code_arm_required &&
+ (!props.card.width || props.card.width > 1) && (
+
+
+
+ )}
{attributes.code_arm_required &&
- props.card.width > 1 &&
- props.card.height! > 1 && (
+ (!props.card.width || props.card.width > 1) &&
+ (!props.card.height || props.card.height > 1) && (
- {props.card.width > 1 && (
+ {(!props.card.width || props.card.width > 1) && (
{attributes.temperature ? (
)}
- {props.card.width > 1 && props.card.height! > 1 && (
-
- {attributes.hvac_modes.map((mode: string, key: number) => {
- let icon: string | undefined =
- mode === 'off'
- ? 'mdi-power'
- : mode === 'heat'
- ? 'mdi-fire'
- : mode === 'cool'
- ? 'mdi-snowflake'
- : mode === 'heat_cool'
- ? 'mdi-autorenew'
- : mode === 'auto'
- ? 'mdi-calendar-repeat'
- : mode === 'dry'
- ? 'mdi-water-percent'
- : mode === 'fan_only'
- ? 'mdi-fan'
- : undefined;
- if (icon)
+ {(!props.card.width || props.card.width > 1) &&
+ (!props.card.height || props.card.height > 1) && (
+
+ {attributes.hvac_modes.map((mode: string, key: number) => {
+ let icon: string | undefined =
+ mode === 'off'
+ ? 'mdi-power'
+ : mode === 'heat'
+ ? 'mdi-fire'
+ : mode === 'cool'
+ ? 'mdi-snowflake'
+ : mode === 'heat_cool'
+ ? 'mdi-autorenew'
+ : mode === 'auto'
+ ? 'mdi-calendar-repeat'
+ : mode === 'dry'
+ ? 'mdi-water-percent'
+ : mode === 'fan_only'
+ ? 'mdi-fan'
+ : undefined;
+ if (icon)
+ return (
+
+ handleHvacChange(mode)}>
+
+
+
+ );
return (
- handleHvacChange(mode)}>
+ {mode}
+
+
+ );
+ })}
+ {attributes.away_mode && (
+
+
+ handleAwayToggle()}>
- );
- return (
-
-
- );
- })}
- {attributes.away_mode && (
-
-
- handleAwayToggle()}>
-
-
-
-
- )}
-
- )}
+ )}
+
+ )}
);
}
diff --git a/src/Components/HomeAssistant/Cards/Cover.tsx b/src/Components/HomeAssistant/Cards/Cover.tsx
index 17e675217..6cd9fc699 100644
--- a/src/Components/HomeAssistant/Cards/Cover.tsx
+++ b/src/Components/HomeAssistant/Cards/Cover.tsx
@@ -137,7 +137,7 @@ function Cover(props: CoverProps) {
{attributes.current_tilt_position !== undefined &&
- props.card.width > 1 ? (
+ (!props.card.width || props.card.width > 1) ? (
+ justify="center"
+ spacing={1}>
)}
- {controls
- .splice(0, props.card.height! > 1 ? props.card.height! : 0)
- .map(control => control)}
+ {!props.card.height
+ ? controls
+ : controls
+ .splice(0, props.card.height > 1 ? props.card.height : 0)
+ .map(control => control)}
);
}
diff --git a/src/Components/HomeAssistant/Cards/Media.tsx b/src/Components/HomeAssistant/Cards/Media.tsx
index 9e85c7a46..226f67df9 100644
--- a/src/Components/HomeAssistant/Cards/Media.tsx
+++ b/src/Components/HomeAssistant/Cards/Media.tsx
@@ -130,7 +130,7 @@ function Media(props: MediaProps) {
return (
- {props.card.height! > 1 && (
+ {(!props.card.height || props.card.height > 1) && (
{attributes.media_title && (
@@ -142,7 +142,7 @@ function Media(props: MediaProps) {
)}
)}
- {props.card.height! > 1 && (
+ {(!props.card.height || props.card.height > 1) && (
- {props.card.width > 1 && state !== 'off' && (
+ {(!props.card.width || props.card.width > 1) && state !== 'off' && (
handleChange('next')}>
- {props.card.width > 1 && state !== 'off' && (
+ {(!props.card.width || props.card.width > 1) && state !== 'off' && (
1 ? 4 : 12}
+ xs={!props.card.width || props.card.width > 1 ? 4 : 12}
container
direction="column"
alignContent="center"
@@ -211,7 +211,9 @@ function Weather(props: WeatherProps) {
- {props.card.width > 1 || props.card.height! > 1 ? (
+ {!props.card.width ||
+ props.card.width > 1 ||
+ (!props.card.height || props.card.height > 1) ? (
{Object.keys(attributes)
.filter(i => typeof attributes[i] == 'number')
@@ -232,52 +234,53 @@ function Weather(props: WeatherProps) {
) : null}
- {props.card.width > 1 && props.card.height! > 1 && (
-
- {attributes.forecast.map((w: object | any, key: number) => {
- const datetime = moment(w.datetime);
- const icon = weatherMap[w.condition];
- return (
-
-
- {datetime.format('ddd')}
-
- {datetime.format('h a')}
-
+ {(!props.card.width || props.card.width > 1) &&
+ (!props.card.height || props.card.height > 1) && (
+
+ {attributes.forecast.map((w: object | any, key: number) => {
+ const datetime = moment(w.datetime);
+ const icon = weatherMap[w.condition];
+ return (
+
+
+ {datetime.format('ddd')}
+
+ {datetime.format('h a')}
+
-
-
-
+
+
+
-
- {w.temperature}
- {getUnit('temperature')}
-
-
- {w.precipitation}
-
-
- );
- })}
-
- )}
+
+ {w.temperature}
+ {getUnit('temperature')}
+
+
+ {w.precipitation}
+
+
+ );
+ })}
+
+ )}
);
}
diff --git a/src/Components/HomeAssistant/HomeAssistant.tsx b/src/Components/HomeAssistant/HomeAssistant.tsx
index 8d6ade9ec..7e50640d1 100644
--- a/src/Components/HomeAssistant/HomeAssistant.tsx
+++ b/src/Components/HomeAssistant/HomeAssistant.tsx
@@ -41,6 +41,32 @@ export interface HomeAssistantChangeProps extends HomeAssistantEntityProps {
) => void;
}
+export const entitySizes: {
+ [key: string]: { height: number; width: number };
+} = {
+ air_quality: { height: 1, width: 1 },
+ alarm_control_panel: { height: 2, width: 2 },
+ binary_sensor: { height: 1, width: 1 },
+ camera: { height: 1, width: 1 },
+ climate: { height: 2, width: 2 },
+ cover: { height: 1, width: 2 },
+ device_tracker: { height: 1, width: 1 },
+ fan: { height: 1, width: 2 },
+ geo_location: { height: 1, width: 1 },
+ input_boolean: { height: 1, width: 1 },
+ input_select: { height: 1, width: 1 },
+ light: { height: 3, width: 2 },
+ lock: { height: 1, width: 1 },
+ media_player: { height: 2, width: 2 },
+ remote: { height: 1, width: 1 },
+ scene: { height: 1, width: 1 },
+ script: { height: 1, width: 1 },
+ sensor: { height: 1, width: 1 },
+ sun: { height: 1, width: 1 },
+ switch: { height: 1, width: 1 },
+ weather: { height: 2, width: 3 }
+};
+
let connection: Connection, auth: Auth;
export function loadTokens() {
diff --git a/src/Components/Overview/Overview.tsx b/src/Components/Overview/Overview.tsx
index e903528d9..dbb8f01fa 100644
--- a/src/Components/Overview/Overview.tsx
+++ b/src/Components/Overview/Overview.tsx
@@ -234,7 +234,7 @@ function Overview(props: OverviewProps) {
justify="flex-start"
alignContent="flex-start"
style={{
- width: groupWidth * group.width + theme.spacing(6.5)
+ width: group.width * (groupWidth + theme.spacing(2))
}}>