Skip to content

Commit

Permalink
Merge pull request #191 from FamousWolf/31-visual-editor
Browse files Browse the repository at this point in the history
[FEATURE] Add visual editor
  • Loading branch information
FamousWolf authored Nov 20, 2024
2 parents 4992104 + cc516ea commit f2a9564
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,46 @@ export class WeekPlannerCard extends LitElement {
_showLegend;
_actions;

/**
* Get config element
*
* @returns {HTMLElement}
*/
static getConfigElement() {
// Create and return an editor element
return document.createElement("week-planner-card-editor");
}

/**
* Get stub config
*
* @returns {}
*/
static getStubConfig() {
return {
calendars: [],
days: 7,
startingDay: 'today',
startingDayOffset: 0,
hideWeekend: false,
noCardBackground: false,
compact: false,
weather: {
showCondition: true,
showTemperature: false,
showLowTemperature: false,
useTwiceDaily: false,
},
locale: 'en',
showLocation: false,
hidePastEvents: false,
hideDaysWithoutEvents: false,
hideTodayWithoutEvents: false,
combineSimilarEvents: false,
showLegend: false
};
}

/**
* Get properties
*
Expand Down Expand Up @@ -177,6 +217,10 @@ export class WeekPlannerCard extends LitElement {
Object.assign(configuration, weatherConfiguration);
}

if (!configuration.hasOwnProperty('entity') || configuration.entity === null) {
return null;
}

return configuration;
}

Expand Down Expand Up @@ -523,6 +567,10 @@ export class WeekPlannerCard extends LitElement {

let calendarNumber = 0;
this._calendars.forEach(calendar => {
if (!calendar.entity || !this.hass.states[calendar.entity]) {
return;
}

if (!calendar.name) {
calendar = {
...calendar,
Expand Down
289 changes: 289 additions & 0 deletions src/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
import { html, LitElement } from "lit";
import styles from './editor.styles';

export class WeekPlannerCardEditor extends LitElement {
static styles = styles;

static get properties() {
return {
hass: {},
_config: {},
};
}

setConfig(config) {
this._config = config;
}

render() {
if (!this.hass || !this._config) {
return html``;
}

return html`
<div style="display: flex; flex-direction: column">
${this.addTextField('title', 'Title')}
${this.addExpansionPanel(
'Calendars',
html`
${this.getConfigValue('calendars').map((calendar, index) => {
return html`
${this.addExpansionPanel(
`Calendar: ${calendar.name ?? calendar.entity}`,
html`
${this.addTextField('calendars.' + index + '.entity', 'Entity')}
${this.addTextField('calendars.' + index + '.name', 'Name')}
${this.addTextField('calendars.' + index + '.color', 'Color')}
${this.addTextField('calendars.' + index + '.filter', 'Filter events (regex)')}
${this.addBooleanField('calendars.' + index + '.hideInLegend', 'Hide in legend')}
${this.addButton('Remove calendar', 'mdi:trash-can', () => {
const config = Object.assign({}, this._config);
if (config.calendars.length === 1) {
config.calendars = [];
} else {
delete config.calendars[index];
}
this._config = config;
this.dispatchConfigChangedEvent();
})}
`
)}
`
})}
${this.addButton('Add calendar', 'mdi:plus', () => {
const index = this.getConfigValue('calendars').length;
this.setConfigValue('calendars.' + index, {});
})}
`
)}
${this.addExpansionPanel(
'Days',
html`
${this.addTextField('days', 'Days')}
${this.addSelectField('startingDay', 'Starting day', [
{
value: 'today',
label: 'Today',
}, {
value: 'tomorrow',
label: 'Tomorrow',
}, {
value: 'yesterday',
label: 'Yesterday',
}, {
value: 'sunday',
label: 'Sunday',
}, {
value: 'monday',
label: 'Monday',
}, {
value: 'tuesday',
label: 'Tuesday',
}, {
value: 'wednesday',
label: 'Wednesday',
}, {
value: 'thursday',
label: 'Thursday',
}, {
value: 'friday',
label: 'Friday',
}, {
value: 'saturday',
label: 'Saturday',
}
], true)}
${this.addTextField('startingDayOffset', 'Starting day offset', 'number')}
${this.addBooleanField('hideWeekend', 'Hide weekend')}
${this.addBooleanField('hideDaysWithoutEvents', 'Hide days without events except for today')}
${this.addBooleanField('hideTodayWithoutEvents', 'Also hide today without events')}
`
)}
${this.addExpansionPanel(
'Events',
html`
${this.addBooleanField('hidePastEvents', 'Hide past events')}
${this.addTextField('filter', 'Filter events (regex)')}
${this.addBooleanField('combineSimilarEvents', 'Combine similar events')}
${this.addBooleanField('showLocation', 'Show location in overview')}
${this.addTextField('locationLink', 'Override location link base URL')}
`
)}
${this.addExpansionPanel(
'Date/time formats',
html`
<p>These formats use <a href="https://moment.github.io/luxon/#/formatting?id=table-of-tokens" target="_blank">Luxon format tokens</a></p>
${this.addTextField('locale', 'Locale')}
${this.addTextField('dateFormat', 'Date format')}
${this.addTextField('timeFormat', 'Time format')}
${this.addTextField('dayFormat', 'Override day number')}
`
)}
${this.addExpansionPanel(
'Weather',
html`
${this.addTextField('weather.entity', 'Weather entity')}
${this.addBooleanField('weather.showCondition', 'Show condition icon')}
${this.addBooleanField('weather.showTemperature', 'Show temperature')}
${this.addBooleanField('weather.showLowTemperature', 'Show low temperature')}
${this.addBooleanField('weather.useTwiceDaily', 'Use twice daily if entity does not support daily')}
`
)}
${this.addExpansionPanel(
'Appearance',
html`
${this.addBooleanField('showLegend', 'Show legend')}
${this.addBooleanField('noCardBackground', 'No card background')}
${this.addTextField('eventBackground', 'Override events background color')}
${this.addBooleanField('compact', 'Compact mode')}
`
)}
${this.addExpansionPanel(
'Texts',
html`
${this.addTextField('texts.fullDay', 'Entire day')}
${this.addTextField('texts.noEvents', 'No events')}
${this.addTextField('texts.today', 'Today')}
${this.addTextField('texts.tomorrow', 'Tomorrow')}
${this.addTextField('texts.yesterday', 'Yesterday')}
${this.addTextField('texts.sunday', 'Sunday')}
${this.addTextField('texts.monday', 'Monday')}
${this.addTextField('texts.tuesday', 'Tuesday')}
${this.addTextField('texts.wednesday', 'Wednesday')}
${this.addTextField('texts.thursday', 'Thursday')}
${this.addTextField('texts.friday', 'Friday')}
${this.addTextField('texts.saturday', 'Saturday')}
`
)}
${this.addExpansionPanel(
'Miscellaneous',
html`
${this.addTextField('updateInterval', 'Override update interval', 'number')}
`
)}
</div>
`;
}

addTextField(name, label, type) {
return html`
<ha-textfield
name="${name}"
label="${label ?? name}"
type="${type ?? 'text'}"
value="${this.getConfigValue(name)}"
@keyup="${this._valueChanged}"
@change="${this._valueChanged}"
/>
`;
}

addSelectField(name, label, options, clearable) {
return html`
<ha-select
name="${name}"
label="${label ?? name}"
value="${this.getConfigValue(name)}"
.clearable="${clearable}"
@change="${this._valueChanged}"
@closed="${(event) => { event.stopPropagation(); } /* Prevent a bug where the editor dialog also closes. See https://github.com/material-components/material-web/issues/1150 */}"
>
${options.map((option) => {
return html`
<mwc-list-item
value="${option.value}"
>${option.label ?? option.value}</mwc-list-item>
`;
})}
</ha-select>
`;
}

addBooleanField(name, label) {
return html`
<ha-formfield
label="${label ?? name}"
>
<ha-switch
name="${name}"
.checked="${this.getConfigValue(name)}"
value="true"
@change="${this._valueChanged}"
/>
</ha-formfield>
`;
}

addExpansionPanel(header, content, expanded) {
return html`
<ha-expansion-panel
header="${header}"
.expanded="${expanded ?? false}"
outlined="true"
>
<div style="display: flex; flex-direction: column">
${content}
</div>
</ha-expansion-panel>
`;
}

addButton(text, icon, clickFunction) {
return html`
<ha-button
@click="${clickFunction}"
>
<ha-icon icon="${icon}"></ha-icon>
${text}
</ha-button>
`;
}

_valueChanged(event) {
const target = event.target;
let value = target.value;

if (target.tagName === 'HA-SWITCH') {
value = target.checked;
}

this.setConfigValue(target.attributes.name.value, value);
}

getConfigValue(key) {
if (!this._config) {
return '';
}

return key.split('.').reduce((o, i) => o[i] ?? '', this._config) ?? '';
}

setConfigValue(key, value) {
const config = Object.assign({}, this._config);
const keyParts = key.split('.');
const lastKeyPart = keyParts.pop();
const lastObject = keyParts.reduce((objectPart, keyPart) => {
if (!objectPart[keyPart]) {
objectPart[keyPart] = {};
}
return objectPart[keyPart];
}, config);
if (value === '') {
delete lastObject[lastKeyPart];
} else {
lastObject[lastKeyPart] = value;
}
this._config = config;

this.dispatchConfigChangedEvent();
}

dispatchConfigChangedEvent() {
const configChangedEvent = new CustomEvent("config-changed", {
detail: { config: this._config },
bubbles: true,
composed: true,
});
this.dispatchEvent(configChangedEvent);
}
}
11 changes: 11 additions & 0 deletions src/editor.styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { css } from 'lit';

export default css`
ha-textfield,
ha-select,
ha-formfield,
ha-expansion-panel,
ha-button {
margin: 8px 0;
}
`;
6 changes: 6 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { WeekPlannerCard } from './card';
import { WeekPlannerCardEditor } from "./editor";
import { version } from '../package.json';

customElements.define(
Expand All @@ -13,6 +14,11 @@ window.customCards.push({
description: 'Card to display events for a number of days from one or several calendars.'
});

customElements.define(
'week-planner-card-editor',
WeekPlannerCardEditor
);

console.info(
`%c WEEK-PLANNER-CARD %c v${version} `,
'color: white; background: black; font-weight: 700;',
Expand Down

0 comments on commit f2a9564

Please sign in to comment.