import { html, css, LitElement, PropertyValues, nothing } from 'lit' import { repeat } from 'lit/directives/repeat.js' import { keyed } from 'lit/directives/keyed.js' import { property, state, customElement, query } from 'lit/decorators.js' import { InputData } from './definition-schema.js' import '@material/web/fab/fab.js' import '@material/web/icon/icon.js' import '@material/web/dialog/dialog.js' import '@material/web/button/text-button.js' import '@material/web/button/outlined-button.js' import '@material/web/button/filled-button.js' import '@material/web/textfield/outlined-text-field.js' import '@material/web/checkbox/checkbox.js' import '@material/web/select/outlined-select.js' import '@material/web/select/select-option.js' import type { MdDialog } from '@material/web/dialog/dialog.js' type Column = Exclude[number] type Theme = { theme_name: string theme_object: any } @customElement('widget-form-versionplaceholder') export class WidgetForm extends LitElement { @property({ type: Object }) inputData?: InputData @property({ type: Object }) theme?: Theme @property({ type: String }) route?: string @state() private themeBgColor?: string @state() private themeTitleColor?: string @state() dialogOpen: boolean = false @query('md-dialog') dialog!: MdDialog @state() private formKey = 0 version: string = 'versionplaceholder' update(changedProperties: Map) { if (changedProperties.has('theme')) { this.registerTheme(this.theme) } super.update(changedProperties) } protected firstUpdated(_changedProperties: PropertyValues): void { this.registerTheme(this.theme) } registerTheme(theme?: Theme) { const cssTextColor = getComputedStyle(this).getPropertyValue('--re-text-color').trim() const cssBgColor = getComputedStyle(this).getPropertyValue('--re-tile-background-color').trim() this.themeBgColor = cssBgColor || this.theme?.theme_object?.backgroundColor this.themeTitleColor = cssTextColor || this.theme?.theme_object?.title?.textStyle?.color } openFormDialog() { this.dialogOpen = true } handleFormSubmit(event: Event) { event.preventDefault() const submitter = (event as SubmitEvent).submitter as HTMLElement const action = submitter?.getAttribute('value') ?? 'submit' const form = event.target as HTMLFormElement const formData = new FormData(form) const submitData = this.inputData?.formFields?.map((field, i) => { const name = `column-${i}` let rawValue: any if (field.hiddenField) { rawValue = field.preFilledValue ?? field.defaultValue ?? '' } else if (field.type === 'checkbox') { rawValue = formData.has(name) ? 'on' : 'off' } else { const entry = formData.get(name) rawValue = entry === null || entry === '' ? (field.defaultValue ?? '') : entry } return { swarm_app_databackend_key: field.targetColumn?.swarm_app_databackend_key, table_name: field.targetColumn?.tablename, column_name: field.targetColumn?.column, value: this.formatValue(rawValue, field.type ?? 'textfield') } }) if (this.inputData?.deleteFlagColumn) submitData?.push({ swarm_app_databackend_key: this.inputData?.deleteFlagColumn?.swarm_app_databackend_key, table_name: this.inputData?.deleteFlagColumn?.tablename, column_name: this.inputData?.deleteFlagColumn?.column, value: action === 'delete' }) this.dispatchEvent( new CustomEvent('data-submit', { detail: submitData, bubbles: false, composed: false }) ) if (action === 'delete') { const deleteRoute = this.resolveRoute({ route: this.inputData?.deleteNavigationRoute, variables: this.inputData?.variables }) if (deleteRoute) { this.dispatchEvent( new CustomEvent('nav-submit', { detail: { path: String(deleteRoute) }, bubbles: true, composed: true }) ) } } this.resetForm() this.dialogOpen = false } formatValue(value: string, type: string): any { switch (type) { case 'numberfield': { if (value === '' || value === null || value === undefined) return null const n = parseFloat(value) return Number.isNaN(n) ? null : n } case 'checkbox': return value === 'on' || value === 'true' ? true : false default: return value } } renderTextField(field: Column, i: number) { return html` ` } renderNumberField(field: Column, i: number) { return html` ` } renderCheckbox(field: Column, i: number) { return html`
` } renderTextArea(field: Column, i: number) { return html` ` } renderDropdown(field: Column, i: number) { return html` ` } renderDateTimeField(field: Column, i: number) { return html` ` } resetForm() { this.formKey++ } resolveRoute(item?: any): string | undefined { let route: string = item?.route ?? '' if (!route) return undefined if (item?.variables) { for (const variable of item.variables) { if (variable.label) { route = route .split(`{{${variable.label}}}`) .join(encodeURIComponent(String(variable.value ?? ''))) } } } if (route.includes('*')) { const currentSegments = (this.route || '').split('/').filter(Boolean) const routeSegments = route.split('/').filter(Boolean) for (let i = 0; i < routeSegments.length; i++) { if (routeSegments[i] === '*') { routeSegments[i] = currentSegments[i] ?? '' } } route = (route.startsWith('/') ? '/' : '') + routeSegments.filter(Boolean).join('/') } return route } cancelEdit(event: Event) { this.resetForm() this.dialogOpen = false } static styles = css` :host { display: flex; flex-direction: column; font-family: sans-serif; box-sizing: border-box; position: relative; margin: auto; } .edit-fab { --md-fab-icon-color: white; --md-fab-container-color: #007bff; --md-fab-label-text-color: white; position: absolute; bottom: 24px; right: 24px; z-index: 10; } .paging:not([active]) { display: none !important; } .wrapper { display: flex; flex-direction: column; padding: 16px; box-sizing: border-box; overflow: auto; } .form-actions { display: flex; flex-direction: row; justify-content: flex-end; gap: 8px; margin-top: 16px; } h3 { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 16px 0px 0px 16px; box-sizing: border-box; } p { margin: 10px 0 16px 0; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-left: 16px; box-sizing: border-box; } /* The dialog classes */ .form { min-width: 80%; } .form [slot='header'] { display: flex; flex-direction: row-reverse; align-items: center; } .form .headline { flex: 1; } .form-content, .form-row { display: flex; gap: 8px; } .form-content { flex-direction: column; gap: 24px; } .form-row > * { flex: 1; } .checkbox-container { display: flex; align-items: center; gap: 12px; } .label { display: flex; flex-direction: column; } md-outlined-select { flex: 1; } md-dialog { overflow: visible; } .header { display: flex; align-items: center; --md-fab-icon-color: white; --md-fab-container-color: #007bff; --md-fab-label-text-color: white; } .delete-btn { --md-filled-button-container-color: #d32f2f; --md-filled-button-label-text-color: #fff; --md-filled-button-hover-label-text-color: #fff; --md-filled-button-focus-label-text-color: #fff; --md-filled-button-pressed-label-text-color: #fff; } ` render() { const fontColor = this.themeTitleColor const bgColor = this.themeBgColor const bgColorOpaque = bgColor?.startsWith('rgba') ? bgColor.replace(/rgba\(([^)]+),\s*[\d.]+\)/, 'rgb($1)') : bgColor?.startsWith('#') && bgColor.length === 9 ? bgColor.substring(0, 7) : bgColor return html`
${this.inputData?.formButton ? html` add ` : nothing}

${this.inputData?.title}

${this.inputData?.subTitle}

${!this.inputData?.formButton ? html`
${this.renderForm()}
${this.inputData?.deleteButton ? html`Delete` : nothing} Reset Submit
` : html` { event.preventDefault() }} @keydown=${(event: any) => { if (event.key === 'Escape') event.preventDefault() }} @closed=${() => (this.dialogOpen = false)} >
${this.inputData?.title ?? 'Data Entry'}
${this.renderForm()}
${this.inputData?.deleteButton ? html`Delete` : nothing} Reset Cancel Submit
`} ` } renderForm() { return keyed( this.formKey, html`
${repeat( this.inputData?.formFields ?? [], (field, i) => i, (field, i) => { if (field.hiddenField) return nothing switch (field.type) { case 'textfield': return this.renderTextField(field, i) case 'numberfield': return this.renderNumberField(field, i) case 'datetime': return this.renderDateTimeField(field, i) case 'textarea': return this.renderTextArea(field, i) case 'dropdown': return this.renderDropdown(field, i) case 'checkbox': return this.renderCheckbox(field, i) } } )}
` ) } }