// SPDX-FileCopyrightText: 2024 Mehdi Benadel // SPDX-FileCopyrightText: 2024 John Livingston // // SPDX-License-Identifier: AGPL-3.0-only import type { ChannelConfiguration } from 'shared/lib/types' import { TemplateResult, html, nothing } from 'lit' import { customElement, property, state } from 'lit/decorators.js' import { ptTr } from '../../lib/directives/translation' import { Task } from '@lit/task' import { ChannelDetailsService } from '../services/channel-details' import { provide } from '@lit/context' import { channelConfigurationContext, channelDetailsServiceContext } from '../contexts/channel' import { LivechatElement } from '../../lib/elements/livechat' import { ValidationError, ValidationErrorType } from '../../lib/models/validation' import { classMap } from 'lit/directives/class-map.js' @customElement('livechat-channel-configuration') export class ChannelConfigurationElement extends LivechatElement { @property({ attribute: false }) public channelId?: number @provide({ context: channelConfigurationContext }) @state() public _channelConfiguration?: ChannelConfiguration @provide({ context: channelDetailsServiceContext }) private _channelDetailsService?: ChannelDetailsService @state() public _validationError?: ValidationError @state() private _actionDisabled: boolean = false private _asyncTaskRender: Task constructor () { super() this._asyncTaskRender = this._initTask() } protected _initTask (): Task { return new Task(this, { task: async () => { this._channelDetailsService = new ChannelDetailsService(this.ptOptions) this._channelConfiguration = await this._channelDetailsService.fetchConfiguration(this.channelId ?? 0) this._actionDisabled = false // in case of reset }, args: () => [] }) } private async _reset (event?: Event): Promise { event?.preventDefault() this._actionDisabled = true this._asyncTaskRender = this._initTask() this.requestUpdate() } private readonly _saveConfig = async (event?: Event): Promise => { event?.preventDefault() if (this._channelDetailsService && this._channelConfiguration) { this._actionDisabled = true this._channelDetailsService.saveOptions(this._channelConfiguration.channel.id, this._channelConfiguration.configuration) .then(() => { this._validationError = undefined this.ptTranslate(LOC_SUCCESSFULLY_SAVED).then((msg) => { this.ptNotifier.info(msg) }, () => {}) this.requestUpdate('_validationError') }) .catch(async (error: Error) => { this._validationError = undefined if (error instanceof ValidationError) { this._validationError = error } console.warn(`A validation error occurred in saving configuration. ${error.name}: ${error.message}`) this.ptNotifier.error( error.message ? error.message : await this.ptTranslate(LOC_ERROR) ) this.requestUpdate('_validationError') }) .finally(() => { this._actionDisabled = false }) } } private readonly _getInputValidationClass = (propertyName: string): { [key: string]: boolean } => { const validationErrorTypes: ValidationErrorType[] | undefined = this._validationError?.properties[`${propertyName}`] return validationErrorTypes ? (validationErrorTypes.length ? { 'is-invalid': true } : { 'is-valid': true }) : {} } private readonly _renderFeedback = (feedbackId: string, propertyName: string): TemplateResult | typeof nothing => { const errorMessages: TemplateResult[] = [] const validationErrorTypes: ValidationErrorType[] | undefined = this._validationError?.properties[`${propertyName}`] ?? undefined if (validationErrorTypes && validationErrorTypes.length !== 0) { if (validationErrorTypes.includes(ValidationErrorType.WrongType)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_WRONG_TYPE)}`) } if (validationErrorTypes.includes(ValidationErrorType.WrongFormat)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_WRONG_FORMAT)}`) } if (validationErrorTypes.includes(ValidationErrorType.NotInRange)) { errorMessages.push(html`${ptTr(LOC_INVALID_VALUE_NOT_IN_RANGE)}`) } return html`
${errorMessages}
` } else { return nothing } } protected override render = (): unknown => { const tableHeaderList = { forbiddenWords: { entries: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_DESC2) }, regexp: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REGEXP_DESC) }, applyToModerators: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_APPLYTOMODERATORS_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_APPLYTOMODERATORS_DESC) }, label: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_LABEL_DESC) }, reason: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REASON_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_REASON_DESC) }, comments: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_FORBIDDEN_WORDS_COMMENTS_DESC) } }, quotes: { messages: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_QUOTE_LABEL2), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_QUOTE_DESC2) }, delay: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_QUOTE_DELAY_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_QUOTE_DELAY_DESC) } }, commands: { command: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_COMMAND_CMD_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_COMMAND_CMD_DESC) }, message: { colName: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_COMMAND_MESSAGE_LABEL), description: ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_COMMAND_MESSAGE_DESC) } } } const tableSchema = { forbiddenWords: { entries: { inputType: 'tags', default: [], separators: ['\n', '\t', ';'] }, regexp: { inputType: 'checkbox', default: false }, applyToModerators: { inputType: 'checkbox', default: false }, label: { inputType: 'text', default: '' }, reason: { inputType: 'text', default: '', datalist: [] }, comments: { inputType: 'textarea', default: '' } }, quotes: { messages: { inputType: 'tags', default: [], separators: ['\n', '\t', ';'] }, delay: { inputType: 'number', default: 10 } }, commands: { command: { inputType: 'text', default: '' }, message: { inputType: 'text', default: '' } } } return this._asyncTaskRender.render({ pending: () => html``, error: () => html``, complete: () => html`

${this._channelConfiguration?.channel.displayName} ${this._channelConfiguration?.channel.name}

${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_DESC)}

${this._renderFeedback('peertube-livechat-slowmode-duration-feedback', 'slowMode.duration')}
${!this._channelConfiguration?.configuration.bot.enabled ? '' : html`
{ if (event?.target && this._channelConfiguration) { this._channelConfiguration.configuration.bot.nickname = (event.target as HTMLInputElement).value } this.requestUpdate('_channelConfiguration') } } value="${this._channelConfiguration?.configuration.bot.nickname ?? ''}" /> ${this._renderFeedback('peertube-livechat-bot-nickname-feedback', 'bot.nickname')}
{ if (this._channelConfiguration) { this._channelConfiguration.configuration.bot.forbiddenWords = e.detail this.requestUpdate('_channelConfiguration') } } } .formName=${'forbidden-words'} > { if (this._channelConfiguration) { this._channelConfiguration.configuration.bot.quotes = e.detail this.requestUpdate('_channelConfiguration') } } } .formName=${'quote'} > { if (this._channelConfiguration) { this._channelConfiguration.configuration.bot.commands = e.detail this.requestUpdate('_channelConfiguration') } } } .formName=${'command'} > `}
` }) } }