diff --git a/package.json b/package.json index a015728256b..da0d33a59e8 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", - "matrix-widget-api": "^0.1.0-beta.8", + "matrix-widget-api": "^0.1.0-beta.9", "minimist": "^1.2.5", "pako": "^1.0.11", "parse5": "^5.1.1", diff --git a/res/css/_common.scss b/res/css/_common.scss index 0317e89d203..7ab88d6f02b 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -60,6 +60,10 @@ pre, code { color: $accent-color; } +.text-muted { + color: $muted-fg-color; +} + b { // On Firefox, the default weight for `` is `bolder` which results in no bold // effect since we only have specific weights of our fonts available. @@ -364,6 +368,11 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Dialog_buttons { margin-top: 20px; text-align: right; + + .mx_Dialog_buttons_additive { + // The consumer is responsible for positioning their elements. + float: left; + } } /* XXX: Our button style are a mess: buttons that happen to appear in dialogs get special styles applied diff --git a/res/css/_components.scss b/res/css/_components.scss index eae67a84a2e..445ed70ff41 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -91,6 +91,7 @@ @import "./views/dialogs/_TermsDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; +@import "./views/dialogs/_WidgetCapabilitiesPromptDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; @import "./views/dialogs/security/_AccessSecretStorageDialog.scss"; @import "./views/dialogs/security/_CreateCrossSigningDialog.scss"; diff --git a/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.scss b/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.scss new file mode 100644 index 00000000000..176919b84c9 --- /dev/null +++ b/res/css/views/dialogs/_WidgetCapabilitiesPromptDialog.scss @@ -0,0 +1,75 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +.mx_WidgetCapabilitiesPromptDialog { + .text-muted { + font-size: $font-12px; + } + + .mx_Dialog_content { + margin-bottom: 16px; + } + + .mx_WidgetCapabilitiesPromptDialog_cap { + margin-top: 20px; + font-size: $font-15px; + line-height: $font-15px; + + .mx_WidgetCapabilitiesPromptDialog_byline { + color: $muted-fg-color; + margin-left: 26px; + font-size: $font-12px; + line-height: $font-12px; + } + } + + .mx_Dialog_buttons { + margin-top: 40px; // double normal + } + + .mx_SettingsFlag { + line-height: calc($font-14px + 7px + 7px); // 7px top & bottom padding + color: $muted-fg-color; + font-size: $font-12px; + + .mx_ToggleSwitch { + display: inline-block; + vertical-align: middle; + margin-right: 8px; + + // downsize the switch + ball + width: $font-32px; + height: $font-15px; + + + &.mx_ToggleSwitch_on > .mx_ToggleSwitch_ball { + left: calc(100% - $font-15px); + } + + .mx_ToggleSwitch_ball { + width: $font-15px; + height: $font-15px; + border-radius: $font-15px; + } + } + + .mx_SettingsFlag_label { + display: inline-block; + vertical-align: middle; + } + } +} diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx index c8a736e8a66..e7223745557 100644 --- a/src/components/views/dialogs/ModalWidgetDialog.tsx +++ b/src/components/views/dialogs/ModalWidgetDialog.tsx @@ -31,6 +31,7 @@ import { ModalButtonKind, Widget, WidgetApiFromWidgetAction, + WidgetKind, } from "matrix-widget-api"; import {StopGapWidgetDriver} from "../../../stores/widgets/StopGapWidgetDriver"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; @@ -72,7 +73,7 @@ export default class ModalWidgetDialog extends React.PureComponent; + widget: Widget; + widgetKind: WidgetKind; // TODO: Refactor into the Widget class +} + +interface IBooleanStates { + // @ts-ignore - TS wants a string key, but we know better + [capability: Capability]: boolean; +} + +interface IState { + booleanStates: IBooleanStates; + rememberSelection: boolean; +} + +export default class WidgetCapabilitiesPromptDialog extends React.PureComponent { + private eventPermissionsMap = new Map(); + + constructor(props: IProps) { + super(props); + + const parsedEvents = WidgetEventCapability.findEventCapabilities(this.props.requestedCapabilities); + parsedEvents.forEach(e => this.eventPermissionsMap.set(e.raw, e)); + + const states: IBooleanStates = {}; + this.props.requestedCapabilities.forEach(c => states[c] = true); + + this.state = { + booleanStates: states, + rememberSelection: true, + }; + } + + private onToggle = (capability: Capability) => { + const newStates = objectShallowClone(this.state.booleanStates); + newStates[capability] = !newStates[capability]; + this.setState({booleanStates: newStates}); + }; + + private onRememberSelectionChange = (newVal: boolean) => { + this.setState({rememberSelection: newVal}); + }; + + private onSubmit = async (ev) => { + this.closeAndTryRemember(Object.entries(this.state.booleanStates) + .filter(([_, isSelected]) => isSelected) + .map(([cap]) => cap)); + }; + + private onReject = async (ev) => { + this.closeAndTryRemember([]); // nothing was approved + }; + + private closeAndTryRemember(approved: Capability[]) { + if (this.state.rememberSelection) { + setRememberedCapabilitiesForWidget(this.props.widget, approved); + } + this.props.onFinished({approved}); + } + + public render() { + const checkboxRows = Object.entries(this.state.booleanStates).map(([cap, isChecked], i) => { + const text = CapabilityText.for(cap, this.props.widgetKind); + const byline = text.byline + ? {text.byline} + : null; + + return ( +
+ this.onToggle(cap)} + >{text.primary} + {byline} +
+ ); + }); + + return ( + +
+
+
{_t("This widget would like to:")}
+ {checkboxRows} + } + /> +
+
+
+ ); + } +} diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js index 001292b6b74..3417485eb8e 100644 --- a/src/components/views/elements/DialogButtons.js +++ b/src/components/views/elements/DialogButtons.js @@ -54,6 +54,9 @@ export default class DialogButtons extends React.Component { // disables only the primary button primaryDisabled: PropTypes.bool, + + // something to stick next to the buttons, optionally + additive: PropTypes.element, }; static defaultProps = { @@ -85,8 +88,14 @@ export default class DialogButtons extends React.Component { ; } + let additive = null; + if (this.props.additive) { + additive =
{this.props.additive}
; + } + return (
+ { additive } { cancelButton } { this.props.children }