/* Copyright 2024,2025 New Vector Ltd. Copyright 2020, 2021 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ import React, { type ChangeEventHandler } from "react"; import { type Capability, isTimelineCapability, type Widget, WidgetEventCapability, type WidgetKind, } from "matrix-widget-api"; import { lexicographicCompare } from "matrix-js-sdk/src/utils"; import { Form, SettingsToggleInput } from "@vector-im/compound-web"; import BaseDialog from "./BaseDialog"; import { _t } from "../../../languageHandler"; import { objectShallowClone } from "../../../utils/objects"; import StyledCheckbox from "../elements/StyledCheckbox"; import DialogButtons from "../elements/DialogButtons"; import { CapabilityText } from "../../../widgets/CapabilityText"; interface IProps { requestedCapabilities: Set; widget: Widget; widgetKind: WidgetKind; // TODO: Refactor into the Widget class onFinished(result?: { approved: Capability[]; remember: boolean }): void; } type BooleanStates = Partial<{ [capability in Capability]: boolean; }>; interface IState { booleanStates: BooleanStates; rememberSelection: boolean; } export default class WidgetCapabilitiesPromptDialog extends React.PureComponent { private eventPermissionsMap = new Map(); public constructor(props: IProps) { super(props); const parsedEvents = WidgetEventCapability.findEventCapabilities(this.props.requestedCapabilities); parsedEvents.forEach((e) => this.eventPermissionsMap.set(e.raw, e)); const states: BooleanStates = {}; this.props.requestedCapabilities.forEach((c) => (states[c] = true)); this.state = { booleanStates: states, rememberSelection: true, }; } private onToggle = (capability: Capability): void => { const newStates = objectShallowClone(this.state.booleanStates); newStates[capability] = !newStates[capability]; this.setState({ booleanStates: newStates }); }; private onRememberSelectionChange: ChangeEventHandler = (evt): void => { this.setState({ rememberSelection: evt.target.checked }); }; private onSubmit = async (): Promise => { this.closeAndTryRemember( Object.entries(this.state.booleanStates) .filter(([_, isSelected]) => isSelected) .map(([cap]) => cap), ); }; private onReject = async (): Promise => { this.closeAndTryRemember([]); // nothing was approved }; private closeAndTryRemember(approved: Capability[]): void { this.props.onFinished({ approved, remember: this.state.rememberSelection }); } public render(): React.ReactNode { // We specifically order the timeline capabilities down to the bottom. The capability text // generation cares strongly about this. const orderedCapabilities = Object.entries(this.state.booleanStates).sort(([capA], [capB]) => { const isTimelineA = isTimelineCapability(capA); const isTimelineB = isTimelineCapability(capB); // Sort timeline capabilities to the bottom, non-timeline to the top if (isTimelineA !== isTimelineB) { return isTimelineA ? 1 : -1; } // Both are the same type (both timeline or both non-timeline), sort lexicographically return lexicographicCompare(capA, capB); }); const checkboxRows = orderedCapabilities.map(([cap, isChecked], i) => { const text = CapabilityText.for(cap, this.props.widgetKind); return (
this.onToggle(cap)} description={text.byline}> {text.primary}
); }); return (
{_t("widget|capabilities_dialog|content_starting_text")}
{checkboxRows} } />
); } }