diff --git a/client/common/configuration/templates/ChannelConfigurationElement.ts b/client/common/configuration/templates/ChannelConfigurationElement.ts new file mode 100644 index 00000000..9343ef6d --- /dev/null +++ b/client/common/configuration/templates/ChannelConfigurationElement.ts @@ -0,0 +1,42 @@ +import { html, LitElement } from 'lit' +import { repeat } from 'lit-html/directives/repeat.js' +import { customElement, property } from 'lit/decorators.js' + + +@customElement('channel-configuration') +export class ChannelConfigurationElement extends LitElement { + + @property() + public list: string[] = ["foo", "bar", "baz"] + + @property() + public newEl: string = 'change_me' + + createRenderRoot = () => { + return this + } + + render() { + return html` + + ` + } + + private _addToList(newEl: string) { + return () => { + this.list.push(newEl) + this.requestUpdate('list') + } + } + + private _removeFromList(index: number) { + return () => { + this.list.splice(index, 1) + this.requestUpdate('list') + } + } +} diff --git a/client/common/configuration/templates/DynamicTableFormElement.ts b/client/common/configuration/templates/DynamicTableFormElement.ts new file mode 100644 index 00000000..80c32cff --- /dev/null +++ b/client/common/configuration/templates/DynamicTableFormElement.ts @@ -0,0 +1,313 @@ +import { html, LitElement, TemplateResult } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { customElement, property, state } from 'lit/decorators.js' + +type DynamicTableAcceptedTypes = number | string | boolean | Date + +type DynamicTableAcceptedInputTypes = 'textarea' + | 'select' + | 'checkbox' + | 'range' + | 'color' + | 'date' + | 'datetime' + | 'datetime-local' + | 'email' + | 'file' + | 'image' + | 'month' + | 'number' + | 'password' + | 'tel' + | 'text' + | 'time' + | 'url' + | 'week' + + +interface CellDataSchema { + min?: number + max?: number + minlength?: number + maxlength?: number + size?: number + label?: string + options?: { [key: string]: string } + inputType?: DynamicTableAcceptedInputTypes + default?: DynamicTableAcceptedTypes +} + +@customElement('dynamic-table-form') +export class DynamicTableFormElement extends LitElement { + + @property({ attribute: false }) + public header: { [key : string]: TemplateResult<1> } = {} + + + @property({ attribute: false }) + public schema: { [key : string]: CellDataSchema } = {} + + + @property({ reflect: true }) + public rows: { _id: number; [key : string]: DynamicTableAcceptedTypes }[] = [] + + + @property({ attribute: false }) + public formName: string = '' + + + @state() + private _lastRowId = 1 + + createRenderRoot = () => { + return this + } + + private _getDefaultRow = () => { + return Object.fromEntries([...Object.entries(this.schema).map((entry) => [entry[0], entry[1].default ?? '']), ['_id', this._lastRowId++]]) + + } + + private _addRow = () => { + this.rows.push(this._getDefaultRow()) + + this.requestUpdate('rows') + + } + + + private _removeRow = (rowId: number) => { + this.rows = this.rows.filter((x) => x._id != rowId) + + this.requestUpdate('rows') + + } + + + render = () => { + const inputId = `peertube-livechat-${this.formName.replaceAll('_','-')}-table` + + + return html` +
+
+

Bot command #1

+

You can configure the bot to respond to commands. A command is a message starting with a "!", like for example "!help" that calls the "help" command. For more information about how to configure this feature, please refer to the documentation by clicking on the help button.

+ + + + + + +
+
+ + ${this._renderHeader()} + + ${repeat(this.rows, this._renderDataRow)} + + + + +
+
+
+ ${JSON.stringify(this.rows)} + ` + + } + + private _renderHeader = () => { + return html`#${Object.values(this.header).map(this._renderHeaderCell)}Remove Row` + + } + + private _renderHeaderCell = (headerCellData: TemplateResult<1> | any) => { + return html`${headerCellData}` + + } + + private _renderDataRow = (rowData: { _id: number; [key : string]: DynamicTableAcceptedTypes }) => { + if (!rowData._id) { + rowData._id = this._lastRowId++ + + } + + const inputId = `peertube-livechat-${this.formName.replaceAll('_','-')}-row-${rowData._id}` + + return html`${rowData._id}${repeat(Object.entries(rowData).filter(([k,v]) => k != '_id'), (data) => this.renderDataCell(data, rowData._id))}` + + } + + renderDataCell = (property: [string, DynamicTableAcceptedTypes], rowId: number) => { + const [propertyName, propertyValue] = property + const propertySchema = this.schema[propertyName] ?? {} + + let formElement + + const inputName = `${this.formName.replaceAll('-','_')}_${propertyName.toString().replaceAll('-','_')}_${rowId}` + const inputId = `peertube-livechat-${this.formName.replaceAll('_','-')}-${propertyName.toString().replaceAll('_','-')}-${rowId}` + + switch (propertyValue.constructor) { + case String: + switch (propertySchema.inputType) { + case undefined: + propertySchema.inputType = 'text' + + case 'text': + case 'color': + case 'date': + case 'datetime': + case 'datetime-local': + case 'email': + case 'file': + case 'image': + case 'month': + case 'number': + case 'password': + case 'range': + case 'tel': + case 'time': + case 'url': + case 'week': + formElement = html` this._updatePropertyFromValue(event, propertyName, rowId)} + .value=${propertyValue} + />` + break + + case 'textarea': + formElement = html`` + break + + case 'select': + formElement = html`` + break + + } + break + + case Date: + switch (propertySchema.inputType) { + case undefined: + propertySchema.inputType = 'datetime' + + case 'date': + case 'datetime': + case 'datetime-local': + case 'time': + formElement = html` this._updatePropertyFromValue(event, propertyName, rowId)} + .value=${propertyValue} + />` + break + + } + break + + case Number: + switch (propertySchema.inputType) { + case undefined: + propertySchema.inputType = 'number' + + case 'number': + case 'range': + formElement = html` this._updatePropertyFromValue(event, propertyName, rowId)} + .value=${propertyValue} + />` + break + + } + break + + case Boolean: + switch (propertySchema.inputType) { + case undefined: + propertySchema.inputType = 'checkbox' + + case 'checkbox': + formElement = html` this._updatePropertyFromValue(event, propertyName, rowId)} + value="" + ?checked=${propertyValue} + />` + break + + } + break + + } + + if (!formElement) { + console.warn(`value type '${propertyValue.constructor}' is incompatible with field type '${propertySchema.inputType}' for form entry '${propertyName.toString()}'.`) + + } + + console.log + + return html`${formElement}` + + } + + + _updatePropertyFromValue(event: InputEvent, propertyName: string, rowId : number) { + let target = event?.target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) + + if(target?.value) { + for(let row of this.rows) { + if(row._id === rowId) { + row[propertyName] = target?.value + + return + } + } + + console.warn(`Could not update property : Did not find a property named '${propertyName}' in row '${rowId}'`) + } + else { + console.warn(`Could not update property : Target or value was undefined`) + } + } +} diff --git a/client/common/configuration/templates/channel.mustache b/client/common/configuration/templates/channel.mustache index 6813b672..09e717eb 100644 --- a/client/common/configuration/templates/channel.mustache +++ b/client/common/configuration/templates/channel.mustache @@ -74,18 +74,18 @@ - + - - - - + + + + {{#forbiddenWordsArray}}{{! iterating on forbiddenWordsArray to display N fields }} - +
{{forbiddenWords}} {{forbiddenWordsDesc2}}{{forbiddenWords}} {{forbiddenWordsDesc2}} {{forbiddenWordsRegexp}} {{forbiddenWordsRegexpDesc}} {{forbiddenWordsApplyToModerators}} {{forbiddenWordsApplyToModeratorsDesc}}{{forbiddenWordsLabel}} {{forbiddenWordsLabelDesc}}{{forbiddenWordsReason}} {{forbiddenWordsReasonDesc}}{{forbiddenWordsComments}} {{forbiddenWordsCommentsDesc}}Remove Remove Row{{forbiddenWordsLabel}} {{forbiddenWordsLabelDesc}}{{forbiddenWordsReason}} {{forbiddenWordsReasonDesc}}{{forbiddenWordsComments}} {{forbiddenWordsCommentsDesc}}Remove Remove Row
{{! warning: don't add extra line break in textarea! }}