import { html, nothing, type TemplateResult } from 'lit'; import type { SummaryPanelConfig, SummaryPanelSource } from './types'; import { type SummaryDraftProblem, type SummaryPanelLocalPreferences, moveSummaryItem, newSummaryBlock, newSummaryValue, normalizeSummaryDraft, sameSummaryPanel, } from './summary-panel'; import type { SummaryPanelDialogState } from './summary-panel-runtime-loaded'; import type { SummaryPanelHost } from './summary-panel-host'; import { searchSummaryEntityIndex, type SummaryEntityIndex, } from './summary-panel-picker'; import { summaryIcon } from './summary-panel-icons'; export type SummaryPanelEditorContext = { host: SummaryPanelHost; dialog: SummaryPanelDialogState; local: SummaryPanelLocalPreferences; storageUnavailable: boolean; problems: SummaryDraftProblem[]; entityIndex: SummaryEntityIndex; setDialog(dialog: SummaryPanelDialogState | null): void; saveLocal(patch: Partial): void; mutate(mutate: (draft: SummaryPanelConfig) => void): void; deleteBlock(index: number): void; dragStart(event: DragEvent, token: string): void; drop(event: DragEvent, target: string): void; sourceToken(source: SummaryPanelSource): string; openSource(blockId: string, valueId: string): void; closeSource(returnFocus?: boolean): void; setSource(blockId: string, valueId: string, token: string): void; save(): void; reload(): void; close(): void; t(key: string): string; }; export type SummaryPanelEditorRenderer = ( context: SummaryPanelEditorContext, ) => TemplateResult | typeof nothing; /** Lazy settings surface for #437; imported only after the gear is pressed. */ export const renderSummaryPanelEditor: SummaryPanelEditorRenderer = (context) => { const { host, dialog, problems, t } = context; const firstError = problems.find((problem) => problem.kind === 'error'); const changed = dialog.localShow !== dialog.baseLocalShow || !dialog.localOnly && !sameSummaryPanel(normalizeSummaryDraft(dialog.draft), dialog.base); const canSave = changed && !dialog.busy && !firstError; const fieldProblem = (path: string) => problems.find((problem) => problem.path === path); const problemText = (problem: SummaryDraftProblem | undefined) => problem ? t(`summary.problem.${problem.code}`) : ''; const close = () => context.close(); const showRow = html``; return html`
context.closeSource()}> ${dialog.localOnly ? html`

${t( dialog.localOnlyHint, )}

` : nothing}

${t('summary.general_settings')}

${!dialog.localOnly ? html`
context.mutate((draft) => { draft.title = (event.target as HTMLInputElement).value; })} /> ${fieldProblem('title') ? html`
${problemText(fieldProblem('title'))}
` : nothing}
` : nothing} ${showRow} ${!dialog.localOnly ? html`` : nothing}
${context.storageUnavailable ? html`
${t('summary.storage_unavailable')}
` : nothing}
${!dialog.localOnly ? html`

${t('summary.blocks')}

${t('summary.blocks_hint')}

${t('summary.block_count') .replace('{count}', String(dialog.draft.blocks.length)).replace('{limit}', '10')}
${dialog.draft.blocks.map((block, bi) => { const bp = `blocks.${bi}`; const scopeProblem = fieldProblem(`${bp}.scope`); return html`
event.preventDefault()} @drop=${(event: DragEvent) => context.drop(event, `block:${bi}`)}>
context.dragStart(event, `block:${bi}`)}>${summaryIcon('grip')}
context.mutate((draft) => { draft.blocks[bi].title = (event.target as HTMLInputElement).value; })} /> ${fieldProblem(`${bp}.title`) ? html`
${problemText(fieldProblem(`${bp}.title`))}
` : nothing}
${block.scope.type === 'space' ? html`` : nothing}
${scopeProblem ? html`
${problemText(scopeProblem)}
` : nothing}
${block.values.map((value, vi) => { const vp = `${bp}.values.${vi}`; const sourceProblem = fieldProblem(`${vp}.source`); const sourceToken = context.sourceToken(value.source); const active = dialog.activeSource?.blockId === block.id && dialog.activeSource.valueId === value.id; const sourceLabel = value.source.type === 'system' ? t(`summary.system.${value.source.key}`) : value.source.entity_id ? context.entityIndex.labels.get(value.source.entity_id) || value.source.entity_id : t('summary.select_source'); const sourceDetail = value.source.type === 'system' ? t('summary.system_source') : value.source.entity_id; const search = active ? searchSummaryEntityIndex(context.entityIndex, dialog.entityFilter) : null; const broken = value.source.type === 'entity' && !!value.source.entity_id && !context.entityIndex.labels.has(value.source.entity_id); const owner = `${block.id}\n${value.id}`; return html`
{ event.preventDefault(); event.stopPropagation(); }} @drop=${(event: DragEvent) => context.drop(event, `value:${bi}:${vi}`)}> context.dragStart(event, `value:${bi}:${vi}`)}>${summaryIcon('grip')}
context.mutate((draft) => { draft.blocks[bi].values[vi].label = (event.target as HTMLInputElement).value; })} /> ${fieldProblem(`${vp}.label`) ? html`
${problemText(fieldProblem(`${vp}.label`))}
` : nothing}
${active && search ? html`
event.stopPropagation()} @keydown=${(event: KeyboardEvent) => { if (event.key !== 'Escape') return; event.preventDefault(); event.stopPropagation(); context.closeSource(true); }}> context.setDialog({ ...dialog, entityFilter: (event.target as HTMLInputElement).value, })} />
${t('summary.system_group')}
${(['device_count', 'total_area', 'datetime'] as const).map((key) => html` `)} ${broken ? html`
${t('summary.current_source')}
` : nothing}
${t('summary.entities_group')}
${search.entries.map((entry) => html``)} ${!search.total ? html`
${t('summary.no_search_results')}
` : nothing} ${search.truncated ? html`
${t('summary.refine_search')}
` : nothing}
` : nothing} ${sourceProblem ? html`
${problemText(sourceProblem)}
` : nothing}
`; })}
`; })}
${dialog.error ? html`` : nothing} ${dialog.conflict ? html`` : nothing} ` : nothing}
`; };