-
-
Notifications
You must be signed in to change notification settings - Fork 91
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
PR1284 - Fixing for sections in large layout #1284
base: main
Are you sure you want to change the base?
PR1284 - Fixing for sections in large layout #1284
Conversation
I will check that soon, thank you for the fixes! |
Hi again! I wanted to let you know that I'm finally working on the new code structure for the cards, and I should be almost done! 🤞 This new structure will improve maintainability by a lot, because there is now a big part of the element creations, classes and of the CSS that is shared between all the cards! No need to modify the styles one by one anymore in all cards, it was about time! And don't worry, I will merge your PR first then I will adapt it to this new structure. I've also added more containers to allow more flexibility/customizations, this will make the upcoming layouts a lot easier to make. And there should be no breaking change for all current custom styles 🙌 |
How are you planning to do the main structure? Like I suggested or did you think of something better? |
This will look like that, here is for example the full import { createBaseStructure } from "../../components/card-structure.js";
import { addActions } from "../../tools/tap-actions.js";
import { getButtonType } from "./helpers.js";
import styles from "./styles.css";
export function createStructure(context, appendTo = context.container) {
const cardType = 'button';
const buttonType = getButtonType(context);
const isSlider = buttonType === 'slider';
const actions = {};
actions['switch'] = {
button: {
tap_action: { action: "toggle" },
double_tap_action: { action: "toggle" },
hold_action: { action: "more-info" }
}
};
actions['state'] = {
button: {
tap_action: { action: "more-info" },
double_tap_action: { action: "more-info" },
hold_action: { action: "more-info" }
}
};
actions['name'] = {
button: {
tap_action: { action: "none" },
double_tap_action: { action: "none" },
hold_action: { action: "none" }
},
icon: {
tap_action: { action: "none" },
double_tap_action: { action: "none" },
hold_action: { action: "none" }
}
};
const elements = createBaseStructure(context, {
type: cardType,
appendTo: appendTo,
styles: styles,
withSlider: isSlider,
withFeedback: !isSlider,
iconActions: actions[buttonType]?.icon,
buttonActions: actions[buttonType]?.button,
});
// Add backward compatibility
elements.background.classList.add('bubble-button-background');
elements.mainContainer.classList.add('bubble-button-card-container');
elements.cardWrapper.classList.add('bubble-button-card');
if (appendTo !== context.container) {
context.buttonType = buttonType;
} else {
context.cardType = cardType;
}
} The import { createElement, toggleEntity, throttle, forwardHaptic, isEntityType } from "../tools/utils.js";
import { addActions, addFeedback } from "../tools/tap-actions.js";
import { createSliderStructure } from "../components/slider.js";
import styles from "./styles.css";
const processedStylesCache = {};
export function createBaseStructure(context, config = {}) {
context.elements = context.elements || {};
const defaults = {
type: 'base',
appendTo: context.content,
baseCardStyles: styles,
styles: '',
withFeedback: true,
withImage: true,
withCustomStyle: true,
withState: true,
withBackground: true,
iconActions: false,
buttonActions: false
};
const options = { ...defaults, ...config };
context.elements.mainContainer = createElement('div', `bubble-${options.type}-container bubble-container`);
context.elements.cardWrapper = createElement('div', `bubble-${options.type} bubble-wrapper`);
context.elements.contentContainer = createElement('div', 'bubble-content-container');
context.elements.buttonsContainer = createElement('div', 'bubble-buttons-container');
context.elements.iconContainer = createElement('div', 'bubble-icon-container icon-container');
context.elements.icon = createElement('ha-icon', 'bubble-icon icon');
context.elements.image = createElement('div', 'bubble-entity-picture entity-picture');
context.elements.nameContainer = createElement('div', 'bubble-name-container name-container');
context.elements.name = createElement('div', 'bubble-name name');
context.elements.state = createElement('div', 'bubble-state state');
if(options.withBackground) {
context.elements.background = createElement('div', 'bubble-background');
context.elements.cardWrapper.prepend(context.elements.background);
}
context.elements.iconContainer.append(
context.elements.icon,
options.withImage ? context.elements.image : null
);
context.elements.nameContainer.append(
context.elements.name,
options.withState ? context.elements.state : null
);
context.elements.contentContainer.append(
context.elements.iconContainer,
context.elements.nameContainer
);
context.elements.cardWrapper.append(
context.elements.contentContainer,
context.elements.buttonsContainer
);
if (options.withFeedback) {
context.elements.feedbackContainer = createElement('div', 'bubble-feedback-container feedback-container');
context.elements.feedback = createElement('div', 'bubble-feedback-element feedback-element');
context.elements.feedback.style.display = 'none';
context.elements.feedbackContainer.append(context.elements.feedback);
context.elements.cardWrapper.append(context.elements.feedbackContainer);
addFeedback(context.elements.background, context.elements.feedback);
}
context.elements.mainContainer.appendChild(context.elements.cardWrapper);
if (options.withSlider) {
createSliderStructure(context);
}
if (options.styles) {
if (!processedStylesCache[options.type]) {
// Replace 'card-type' in CSS variables with the real card type
processedStylesCache[options.type] = options.baseCardStyles.replace(/card-type/g, options.type);
}
context.elements.style = createElement('style');
context.elements.style.innerText = processedStylesCache[options.type] + options.styles;
context.elements.mainContainer.appendChild(context.elements.style);
}
if (options.withCustomStyle) {
context.elements.customStyle = createElement('style');
context.elements.mainContainer.appendChild(context.elements.customStyle);
}
if (options.iconActions === true) {
addActions(context.elements.iconContainer, context.config);
} else if (options.iconActions !== false) {
addActions(context.elements.iconContainer, context.config, context.config.entity, options.iconActions);
}
if (options.buttonActions === true) {
addActions(context.elements.background, context.config.button_action, context.config.entity);
} else if (options.buttonActions !== undefined) {
addActions(context.elements.background, context.config.button_action, context.config.entity, options.buttonActions);
}
if (options.appendTo === context.content) {
context.content.appendChild(context.elements.mainContainer);
} else {
options.appendTo.appendChild(context.elements.mainContainer);
}
return context.elements;
} And the shared styles (this is removing a lot of duplicate CSS in all cards!): /* 'card-type' in CSS variables is replaced with the real card type
in card-structure.js for easier maintenance */
* {
-webkit-tap-highlight-color: transparent !important;
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
}
*::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}
ha-card {
margin-top: 0;
background: none;
opacity: 1;
}
.bubble-container {
position: relative;
width: 100%;
height: 50px;
background-color: var(--bubble-card-type-main-background-color, var(--bubble-main-background-color, var(--background-color-2, var(--secondary-background-color))));
border-radius: var(--bubble-card-type-border-radius, var(--bubble-border-radius, calc(var(--row-height,56px)/2)));
box-shadow: var(--bubble-card-type-box-shadow, var(--bubble-box-shadow, none));
overflow: scroll;
touch-action: pan-y;
border: var(--bubble-card-type-border, var(--bubble-border, none));
box-sizing: border-box;
}
.bubble-wrapper {
display: flex;
position: absolute;
justify-content: space-between;
align-items: center;
height: 100%;
width: 100%;
transition: all 1.5s;
border-radius: var(--bubble-card-type-border-radius, var(--bubble-border-radius, calc(var(--row-height,56px)/2)));
background-color: rgba(0,0,0,0);
overflow: visible;
}
.bubble-content-container {
display: contents;
flex-grow: 1;
overflow: hidden;
}
.bubble-buttons-container {
display: contents;
}
.bubble-sub-button-container {
right: 8px !important;
}
.bubble-background {
display: flex;
position: absolute;
height: 100%;
width: 100%;
transition: background-color 1.5s;
border-radius: var(--bubble-card-type-border-radius, var(--bubble-border-radius, calc(var(--row-height,56px)/2)));
-webkit-mask-image: radial-gradient(circle, rgba(0, 0, 0, 1) 98%, rgba(0, 0, 0, 0) 100%);
mask-image: radial-gradient(circle, rgba(0, 0, 0, 1) 98%, rgba(0, 0, 0, 0) 100%);
}
.bubble-icon-container {
display: flex;
flex-wrap: wrap;
align-content: center;
justify-content: center;
min-width: 38px;
min-height: 38px;
margin: 6px;
border-radius: var(--bubble-card-type-icon-border-radius, var(--bubble-icon-border-radius, var(--bubble-border-radius, 50%)));
background-color: var(--bubble-card-type-icon-background-color, var(--bubble-icon-background-color, var(--bubble-secondary-background-color, var(--card-background-color, var(--ha-card-background)))));
overflow: hidden;
position: relative;
cursor: pointer;
}
.is-off .bubble-icon {
opacity: 0.6;
}
.is-on .bubble-icon {
filter: brightness(1.1);
opacity: 1;
}
.bubble-entity-picture {
background-size: cover;
background-position: center;
height: 100%;
width: 100%;
position: absolute;
}
.bubble-name,
.bubble-state {
display: flex;
position: relative;
white-space: nowrap;
}
.bubble-name-container {
display: flex;
line-height: 18px;
flex-direction: column;
justify-content: center;
flex-grow: 1;
margin: 0 16px 0 4px;
pointer-events: none;
position: relative;
overflow: hidden;
}
.bubble-name {
font-size: 13px;
font-weight: 600;
}
.bubble-state {
font-size: 12px;
font-weight: normal;
opacity: 0.7;
}
.bubble-range-fill {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
left: -100%;
transition: all .3s;
z-index: 0;
}
.is-dragging .bubble-range-fill {
transition: none;
}
.is-light .bubble-range-fill {
opacity: 0.5;
}
.is-unavailable .bubble-wrapper,
.is-unavailable .bubble-range-slider {
cursor: not-allowed;
}
.is-unavailable {
opacity: 0.5;
}
.bubble-feedback-container,
.bubble-feedback-element {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
border-radius: var(--bubble-card-type-border-radius, var(--bubble-border-radius, calc(var(--row-height,56px)/2)));
}
.bubble-feedback-element {
opacity: 0;
background-color: rgb(0,0,0);
border-radius: 0;
}
@keyframes tap-feedback {
0% {transform: translateX(-100%); opacity: 0;}
64% {transform: translateX(0); opacity: 0.1;}
100% {transform: translateX(100%); opacity: 0;}
}
.large .bubble-container {
height: calc( var(--row-height,56px) * var(--row-size,1) + var(--row-gap,8px) * ( var(--row-size,1) - 1 ));
border-radius: var(--bubble-card-type-border-radius, var(--bubble-border-radius, calc(var(--row-height,56px)/2)));
}
.large .bubble-icon-container {
--mdc-icon-size: 24px;
min-width: 42px !important;
min-height: 42px !important;
margin-left: 8px;
}
.large .bubble-sub-button-container:has(> :last-child:nth-child(2)) :nth-child(2) {
grid-row: 1 / calc(var(--row-size,1) + 1);
}
.rows-2 .bubble-sub-button-container {
flex-direction: column;
gap: 4px !important;
display: grid !important;
grid-template-columns: repeat(1, 1fr);
grid-template-rows: repeat(calc(2*var(--row-size,1)), minmax(auto, max-content));
grid-auto-flow: column;
width: auto;
}
.rows-2 .bubble-sub-button {
height: 20px !important;
}
.large.rows-2 .bubble-sub-button-container:has(> :last-child:nth-child(2)) :nth-child(2) {
grid-row: 1 / calc(2*var(--row-size,1) + 1);
} |
In the resulting structure is not much changed, except the extra content-container and cardwrapper? Or am I missing things? The content-container makes it harder to do stuff like the room card (#927), there is a way around it but just so you know. |
Indeed the structure has not changed a lot, the card that will change the most is the cover card. For the media player, I've already refactored it, and indeed I'm only using this new function for the base structure, all buttons and card specific things are handled mostly like before. And for your room card, I'm actually using it in my test dashboard to see if anything breaks, and it's still looking the same with the new structure 👌 Edit: I'm a bit tired so I'm not sure to exactly understand what you mean for the media player, so here is the let volumeLevel = 0;
export function createStructure(context) {
const cardType = 'media-player';
const elements = createBaseStructure(context, {
type: cardType,
withFeedback: false,
styles: styles
});
elements.mediaInfoContainer = createElement('div', 'bubble-media-info-container');
elements.playPauseButton = createElement('ha-icon', 'bubble-play-pause-button');
elements.previousButton = createElement('ha-icon', 'bubble-previous-button');
elements.previousButton.setAttribute("icon", "mdi:skip-previous");
elements.nextButton = createElement('ha-icon', 'bubble-next-button');
elements.nextButton.setAttribute("icon", "mdi:skip-next");
elements.volumeButton = createElement('ha-icon', 'bubble-volume-button');
elements.volumeButton.setAttribute("icon", "mdi:volume-high");
elements.powerButton = createElement('ha-icon', 'bubble-power-button');
elements.powerButton.setAttribute("icon", "mdi:power-standby");
elements.muteButton = createElement('ha-icon', 'bubble-mute-button is-hidden');
elements.muteButton.setAttribute("icon", "mdi:volume-off");
elements.title = createElement('div', 'bubble-title');
elements.artist = createElement('div', 'bubble-artist');
// Add backward compatibility
elements.background.classList.add('bubble-cover-background');
elements.iconContainer.appendChild(elements.muteButton);
elements.mediaInfoContainer.append(elements.title, elements.artist);
elements.contentContainer.append(elements.mediaInfoContainer);
elements.buttonsContainer.append(
elements.powerButton,
elements.previousButton,
elements.nextButton,
elements.volumeButton,
elements.playPauseButton
);
addActions(elements.icon, context.config, context.config.entity);
addActions(elements.image, context.config, context.config.entity);
elements.volumeSliderContainer = createElement('div', 'bubble-volume-slider is-hidden');
createSliderStructure(context, {
targetElement: elements.volumeSliderContainer,
sliderLiveUpdate: false,
withValueDisplay: true,
});
elements.cardWrapper.appendChild(elements.volumeSliderContainer);
elements.volumeButton.addEventListener('click', () => {
elements.volumeSliderContainer.classList.toggle('is-hidden');
elements.muteButton.classList.toggle('is-hidden');
elements.icon.classList.toggle('is-hidden');
elements.image.classList.toggle('is-hidden');
changeVolumeIcon(context);
});
elements.powerButton.addEventListener('click', () => {
const isOn = isStateOn(context);
context._hass.callService('media_player', isOn ? 'turn_off' : 'turn_on', {
entity_id: context.config.entity
});
});
elements.muteButton.addEventListener('click', () => {
const isVolumeMuted = getAttribute(context, "is_volume_muted") === true;
context._hass.callService('media_player', 'volume_mute', {
entity_id: context.config.entity,
is_volume_muted: !isVolumeMuted
});
elements.muteButton.clicked = true;
});
elements.previousButton.addEventListener('click', () => {
context._hass.callService('media_player', 'media_previous_track', {
entity_id: context.config.entity
});
});
elements.nextButton.addEventListener('click', () => {
context._hass.callService('media_player', 'media_next_track', {
entity_id: context.config.entity
});
});
elements.playPauseButton.addEventListener('click', () => {
context._hass.callService('media_player', 'media_play_pause', {
entity_id: context.config.entity
});
elements.playPauseButton.clicked = true;
});
elements.mainContainer.addEventListener('click', () => forwardHaptic("selection"));
context.cardType = cardType;
} |
Proposed change
Fixes for large-layouts in sections
Type of change
Example configuration
Pop-ups > yaml
Separator > yaml
Example printscreens/gif
Pop-ups > After
Separator > After
Additional information
Additional documentation needed.
Needed to clearify that only large-layout change to sections size.
Checklist
If user exposed functionality or configuration variables are added/changed: