diff --git a/res/css/_common.scss b/res/css/_common.scss index b92a618504..e062e0bd73 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -428,6 +428,11 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { color: $accent-fg-color; } +.mx_Dialog button.warning, .mx_Dialog input[type="submit"].warning { + border: solid 1px $warning-color; + color: $warning-color; +} + .mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled, .mx_Dialog_buttons button:disabled, .mx_Dialog_buttons input[type="submit"]:disabled { background-color: $light-fg-color; border: solid 1px $light-fg-color; diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js new file mode 100644 index 0000000000..120b086ef6 --- /dev/null +++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js @@ -0,0 +1,73 @@ +/* +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. +*/ + +import React from 'react'; +import * as sdk from '../../../../index'; +import PropTypes from 'prop-types'; +import dis from "../../../../dispatcher"; +import { _t } from '../../../../languageHandler'; + +import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; +import EventIndexPeg from "../../../../indexing/EventIndexPeg"; + +/* + * Allows the user to disable the Event Index. + */ +export default class DisableEventIndexDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + disabling: false, + }; + } + + _onDisable = async () => { + this.setState({ + disabling: true, + }); + + await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false); + await EventIndexPeg.deleteEventIndex(); + this.props.onFinished(); + dis.dispatch({ action: 'view_user_settings' }); + } + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const Spinner = sdk.getComponent('elements.Spinner'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + + return ( + + {_t("If disabled, messages from encrypted rooms won't appear in search results.")} + {this.state.disabling ? :
} + + + ); + } +} diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js new file mode 100644 index 0000000000..b7ea87b1b2 --- /dev/null +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.js @@ -0,0 +1,154 @@ +/* +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. +*/ + +import React from 'react'; +import * as sdk from '../../../../index'; +import PropTypes from 'prop-types'; +import { _t } from '../../../../languageHandler'; + +import Modal from '../../../../Modal'; +import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils"; +import EventIndexPeg from "../../../../indexing/EventIndexPeg"; + +/* + * Allows the user to introspect the event index state and disable it. + */ +export default class ManageEventIndexDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + eventIndexSize: 0, + eventCount: 0, + roomCount: 0, + currentRoom: null, + }; + } + + async updateCurrentRoom(room) { + const eventIndex = EventIndexPeg.get(); + const stats = await eventIndex.getStats(); + let currentRoom = null; + + if (room) currentRoom = room.name; + + this.setState({ + eventIndexSize: stats.size, + roomCount: stats.roomCount, + eventCount: stats.eventCount, + currentRoom: currentRoom, + }); + } + + componentWillUnmount(): void { + const eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom.bind(this)); + } + } + + async componentWillMount(): void { + let eventIndexSize = 0; + let roomCount = 0; + let eventCount = 0; + let currentRoom = null; + + const eventIndex = EventIndexPeg.get(); + + if (eventIndex !== null) { + eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this)); + + const stats = await eventIndex.getStats(); + eventIndexSize = stats.size; + roomCount = stats.roomCount; + eventCount = stats.eventCount; + + const room = eventIndex.currentRoom(); + if (room) currentRoom = room.name; + } + + this.setState({ + eventIndexSize, + eventCount, + roomCount, + currentRoom, + }); + } + + _onDisable = async () => { + Modal.createTrackedDialogAsync("Disable message search", "Disable message search", + import("./DisableEventIndexDialog"), + null, null, /* priority = */ false, /* static = */ true, + ); + } + + _onDone = () => { + this.props.onFinished(true); + } + + render() { + let crawlerState; + + if (this.state.currentRoom === null) { + crawlerState = _t("Not currently downloading messages for any room."); + } else { + crawlerState = ( + _t("Downloading mesages for %(currentRoom)s.", { currentRoom: this.state.currentRoom }) + ); + } + + const eventIndexingSettings = ( +
+ { + _t( "Riot is securely caching encrypted messages locally for them " + + "to appear in search results:", + ) + } +
+ {_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}
+ {_t("Indexed messages:")} {formatCountLong(this.state.eventCount)}
+ {_t("Number of rooms:")} {formatCountLong(this.state.roomCount)}
+ {crawlerState}
+
+
+ ); + + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + + return ( + + {eventIndexingSettings} + + + ); + } +} diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js index 7b37fdb4eb..ee15bfc3f2 100644 --- a/src/components/views/elements/DialogButtons.js +++ b/src/components/views/elements/DialogButtons.js @@ -43,6 +43,10 @@ export default createReactClass({ // should there be a cancel button? default: true hasCancel: PropTypes.bool, + // The class of the cancel button, only used if a cancel button is + // enabled + cancelButtonClass: PropTypes.node, + // onClick handler for the cancel button. onCancel: PropTypes.func, @@ -72,12 +76,14 @@ export default createReactClass({ primaryButtonClassName += " " + this.props.primaryButtonClass; } let cancelButton; + if (this.props.cancelButton || this.props.hasCancel) { cancelButton =