= {
cameras: {
icon: 'video',
name: localize('editor.cameras'),
description: localize('editor.cameras_secondary'),
renderCustomContent: (hass, input) => html`
`,
},
dimensions: {
icon: 'aspect-ratio',
name: localize('editor.dimensions'),
description: localize('editor.dimensions_secondary'),
},
folders: {
icon: 'folder-multiple',
name: localize('editor.folders'),
description: localize('editor.folders_secondary'),
renderCustomContent: (hass, input) => html`
`,
},
image: {
icon: 'image',
name: localize('editor.image'),
description: localize('editor.image_secondary'),
},
live: {
icon: 'cctv',
name: localize('editor.live'),
description: localize('editor.live_secondary'),
},
media_gallery: {
icon: 'play-box-multiple',
name: localize('editor.media_gallery'),
description: localize('editor.media_gallery_secondary'),
},
media_viewer: {
icon: 'filmstrip',
name: localize('editor.media_viewer'),
description: localize('editor.media_viewer_secondary'),
},
menu: {
icon: 'menu',
name: localize('editor.menu'),
description: localize('editor.menu_secondary'),
},
performance: {
icon: 'speedometer',
name: localize('editor.performance'),
description: localize('editor.performance_secondary'),
},
profiles: {
icon: 'folder-wrench-outline',
name: localize('editor.profiles'),
description: localize('editor.profiles_secondary'),
},
remote_control: {
icon: 'remote',
name: localize('editor.remote_control'),
description: localize('editor.remote_control_secondary'),
},
status_bar: {
icon: 'sign-text',
name: localize('editor.status_bar'),
description: localize('editor.status_bar_secondary'),
},
timeline: {
icon: 'chart-gantt',
name: localize('editor.timeline'),
description: localize('editor.timeline_secondary'),
},
view: {
icon: 'eye',
name: localize('editor.view'),
description: localize('editor.view_secondary'),
// The keyboard shortcuts are a panel of their own within the view section:
// a shortcut is assigned by pressing a key rather than by filling in a
// field, so no selector can express one.
renderCustomContent: (hass, input) => html`
`,
},
};
// The two parts of the simple editor with more behind them than a handful of
// controls: a list of cameras to add to and drill into, and a menu whose
// buttons are a longer list than everything else in the editor put together.
// The rest of its settings are shown as they are, below.
const SIMPLE_SECTIONS: EditorSection[] = [
{
icon: 'video',
name: localize('editor.cameras'),
description: localize('editor.cameras_secondary'),
docPath: [CONF_CAMERAS],
renderCustomContent: (hass, input) => html`
`,
},
{
icon: 'menu',
name: localize('editor.menu'),
description: localize('editor.menu_secondary'),
request: { kind: 'simple-menu' },
docPath: ['menu'],
},
];
/**
* Used to side-load lazily-loaded selectors the editor will use.
* See {@link AdvancedCameraCardEditor._renderSelectorSideload}.
*/
const SELECTOR_SIDELOAD_SCHEMA: HAFormSchema[] = [
{ name: 'text', selector: { text: {} } },
{ name: 'select', selector: { select: { options: [] } } },
{ name: 'number', selector: { number: {} } },
{ name: 'boolean', selector: { boolean: {} } },
{ name: 'icon', selector: { icon: {} } },
{ name: 'entity', selector: { entity: {} } },
{ name: 'object', selector: { object: {} } },
{ name: 'sideload', type: 'expandable', title: '', schema: [] },
{ name: 'sideload-grid', type: 'grid', schema: [] },
];
const SELECTOR_SIDELOAD_DATA = {};
const MODE_REQUEST: FormRequest = { kind: 'editor-mode' };
const SIMPLE_TOP_LEVEL_REQUEST: FormRequest = { kind: 'simple-top-level' };
@customElement('advanced-camera-card-editor')
export class AdvancedCameraCardEditor extends LitElement implements LovelaceCardEditor {
@property({ attribute: false })
public hass?: HomeAssistant;
private _controller = new EditorController(this);
// The forms the editor renders itself rather than within a full editor
// section: the switch between the editors, which belongs to neither of them,
// and the simple editor's remaining settings, which are shown as they are.
private _modeFormsController = this._createFormsController();
private _simpleFormsController = this._createFormsController();
private _createFormsController(): FormsController {
return new FormsController(
(changes: ConfigChange[]) =>
this._controller.applyIntent({ type: 'changes', changes }),
(path) => renderDocumentation(path),
);
}
public setConfig(config: RawAdvancedCameraCardConfig): void {
this._controller.setConfig(config);
}
protected willUpdate(changedProps: PropertyValues): void {
this._controller.initialize();
if (changedProps.has('hass') && this.hass) {
this._controller.setHASS(this.hass);
}
}
protected render(): TemplateResult | void {
const hass = this.hass;
if (!hass || !this._controller.getConfig()) {
return html``;
}
// Built once and given to every section: the sections compare what they are
// given against what they last had, and an identical object makes that
// comparison a reference check.
const input = this._controller.getFormsInput();
return html`
${this._renderSelectorSideload()}
) =>
this._controller.applyIntent(ev.detail)}
>
${[this._renderNotices(), this._renderModeSwitch(hass, input)]}
${this._controller.getEditorMode() === 'simple'
? this._renderSimple(hass, input)
: this._renderFull(hass, input)}
${this._renderActionButtons()}
`;
}
private _renderModeSwitch(hass: HomeAssistant, input: FormsInput): TemplateResult {
this._modeFormsController.setInput(MODE_REQUEST, input);
return html`
${renderForms(hass, this._modeFormsController.getContexts())}
`;
}
private _renderSimple(hass: HomeAssistant, input: FormsInput): TemplateResult {
this._simpleFormsController.setInput(SIMPLE_TOP_LEVEL_REQUEST, input);
return html`
${SIMPLE_SECTIONS.map((section) => this._renderSection(section, hass, input))}
${renderForms(hass, this._simpleFormsController.getContexts())}
`;
}
private _renderFull(hass: HomeAssistant, input: FormsInput): TemplateResult {
return html`
${Object.entries(SECTIONS).map(([name, section]) =>
this._renderSection(
{
...section,
request: { kind: 'full-section', name },
docPath: [name],
},
hass,
input,
),
)}
`;
}
private _renderSection(
section: EditorSection,
hass: HomeAssistant,
input: FormsInput,
): TemplateResult {
const renderCustomContent = section.renderCustomContent;
return html`
renderCustomContent(hass, input)
: undefined}
>
`;
}
// Card-state notices rendered as banners above the sections (matching how
// native Home Assistant editors surface such notes).
private _renderNotices(): TemplateResult {
return html`${this._controller
.getNotices()
.map(
(notice) =>
html`${notice.message}`,
)}`;
}
private _renderActionButtons(): TemplateResult {
return html`
${this._controller.isConfigUpgradeable()
? html` this._controller.upgrade()}
>
${localize('editor.upgrade')}
`
: ''}
{
fireAdvancedCameraCardEvent(this, 'editor:diagnostics');
}}
>
${localize('editor.toggle_diagnostics')}
`;
}
// `ha-form` and `ha-selector` lazily `import()` their per-type sub-elements
// on first use. A section rendered before those imports resolve is not laid
// out in time for its `ha-expansion-panel` to measure the correct height, so
// a hidden form covering every element type the editor uses is rendered up
// front to resolve the imports before the user opens anything.
private _renderSelectorSideload(): TemplateResult {
return html`
`;
}
static get styles(): CSSResultGroup {
return unsafeCSS(editorStyle);
}
}
declare global {
interface HTMLElementTagNameMap {
'advanced-camera-card-editor': AdvancedCameraCardEditor;
}
}