30f442208a
* Add more playwright axe tests to settings dialogs * Add utility to jest setupTests to detect React hydration errors * Iterate jest utility * Fix axe issue heading-order * Fix div-in-p issues * Fix setupTests.ts * Fix heading order * Make types happier * Fix hydration issues of thead containing text nodes * Update tests * Fix form-in-form React hydration issues * Fix li-in-li React hydration issues * Fix checked in form without onChange React hydration issue * Fix styling bleeding from _common.pcss * Update snapshots * Fix more remaining issues * Remove _common.pcss h2 rule altogether * Fix test * Update snapshots * Iterate * Iterate * Update snapshots * Simplify diff * Test * Update screenshots * Update screenshot
242 lines
9.2 KiB
TypeScript
242 lines
9.2 KiB
TypeScript
/*
|
|
Copyright 2024 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 JSX, lazy } from "react";
|
|
import { type EmptyObject } from "matrix-js-sdk/src/matrix";
|
|
|
|
import { _t } from "../../../languageHandler";
|
|
import SdkConfig from "../../../SdkConfig";
|
|
import Modal from "../../../Modal";
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
|
import AccessibleButton from "../elements/AccessibleButton";
|
|
import { formatBytes, formatCountLong } from "../../../utils/FormattingUtils";
|
|
import EventIndexPeg from "../../../indexing/EventIndexPeg";
|
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
|
import SeshatResetDialog from "../dialogs/SeshatResetDialog";
|
|
import InlineSpinner from "../elements/InlineSpinner";
|
|
import ExternalLink from "../elements/ExternalLink";
|
|
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
|
|
|
|
interface IState {
|
|
enabling: boolean;
|
|
eventIndexSize: number;
|
|
roomCount: number;
|
|
eventIndexingEnabled: boolean;
|
|
}
|
|
|
|
export default class EventIndexPanel extends React.Component<EmptyObject, IState> {
|
|
public constructor(props: EmptyObject) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
enabling: false,
|
|
eventIndexSize: 0,
|
|
roomCount: 0,
|
|
eventIndexingEnabled: SettingsStore.getValueAt(SettingLevel.DEVICE, "enableEventIndexing"),
|
|
};
|
|
}
|
|
|
|
public updateCurrentRoom = async (): Promise<void> => {
|
|
const eventIndex = EventIndexPeg.get();
|
|
const stats = await eventIndex?.getStats().catch(() => {});
|
|
// This call may fail if sporadically, not a huge issue as we will try later again and probably succeed.
|
|
if (!stats) return;
|
|
|
|
this.setState({
|
|
eventIndexSize: stats.size,
|
|
roomCount: stats.roomCount,
|
|
});
|
|
};
|
|
|
|
public componentWillUnmount(): void {
|
|
const eventIndex = EventIndexPeg.get();
|
|
|
|
if (eventIndex !== null) {
|
|
eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom);
|
|
}
|
|
}
|
|
|
|
public componentDidMount(): void {
|
|
this.updateState();
|
|
}
|
|
|
|
public async updateState(): Promise<void> {
|
|
const eventIndex = EventIndexPeg.get();
|
|
const eventIndexingEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, "enableEventIndexing");
|
|
const enabling = false;
|
|
|
|
let eventIndexSize = 0;
|
|
let roomCount = 0;
|
|
|
|
if (eventIndex !== null) {
|
|
eventIndex.on("changedCheckpoint", this.updateCurrentRoom);
|
|
|
|
const stats = await eventIndex.getStats().catch(() => {});
|
|
// This call may fail if sporadically, not a huge issue as we
|
|
// will try later again in the updateCurrentRoom call and
|
|
// probably succeed.
|
|
if (stats) {
|
|
eventIndexSize = stats.size;
|
|
roomCount = stats.roomCount;
|
|
}
|
|
}
|
|
|
|
this.setState({
|
|
enabling,
|
|
eventIndexSize,
|
|
roomCount,
|
|
eventIndexingEnabled,
|
|
});
|
|
}
|
|
|
|
private onManage = async (): Promise<void> => {
|
|
Modal.createDialog(
|
|
lazy(() => import("../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog")),
|
|
{
|
|
onFinished: () => {},
|
|
},
|
|
undefined,
|
|
/* priority = */ false,
|
|
/* static = */ true,
|
|
);
|
|
};
|
|
|
|
private onEnable = async (): Promise<void> => {
|
|
this.setState({
|
|
enabling: true,
|
|
});
|
|
|
|
await EventIndexPeg.initEventIndex();
|
|
await EventIndexPeg.get()?.addInitialCheckpoints();
|
|
EventIndexPeg.get()?.startCrawler();
|
|
await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, true);
|
|
await this.updateState();
|
|
};
|
|
|
|
private confirmEventStoreReset = (): void => {
|
|
const { finished, close } = Modal.createDialog(SeshatResetDialog);
|
|
finished.then(async ([success]) => {
|
|
if (success) {
|
|
await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, false);
|
|
await EventIndexPeg.deleteEventIndex();
|
|
await this.onEnable();
|
|
close();
|
|
}
|
|
});
|
|
};
|
|
|
|
public render(): React.ReactNode {
|
|
let eventIndexingSettings: JSX.Element | undefined;
|
|
const { brand, desktop_builds: desktopBuilds } = SdkConfig.get();
|
|
|
|
if (EventIndexPeg.get() !== null) {
|
|
eventIndexingSettings = (
|
|
<>
|
|
<SettingsSubsectionText>
|
|
{_t("settings|security|message_search_enabled", {
|
|
size: formatBytes(this.state.eventIndexSize, 0),
|
|
// This drives the singular / plural string
|
|
// selection for "room" / "rooms" only.
|
|
count: this.state.roomCount,
|
|
rooms: formatCountLong(this.state.roomCount),
|
|
})}
|
|
</SettingsSubsectionText>
|
|
<AccessibleButton kind="primary" onClick={this.onManage}>
|
|
{_t("action|manage")}
|
|
</AccessibleButton>
|
|
</>
|
|
);
|
|
} else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
|
|
eventIndexingSettings = (
|
|
<>
|
|
<SettingsSubsectionText>{_t("settings|security|message_search_disabled")}</SettingsSubsectionText>
|
|
<div>
|
|
<AccessibleButton kind="primary" disabled={this.state.enabling} onClick={this.onEnable}>
|
|
{_t("action|enable")}
|
|
</AccessibleButton>
|
|
{this.state.enabling ? <InlineSpinner /> : <div />}
|
|
</div>
|
|
</>
|
|
);
|
|
} else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) {
|
|
const nativeLink =
|
|
"https://github.com/element-hq/element-web/blob/develop/apps/desktop/" +
|
|
"docs/native-node-modules.md#" +
|
|
"adding-seshat-for-search-in-e2e-encrypted-rooms";
|
|
|
|
eventIndexingSettings = (
|
|
<SettingsSubsectionText>
|
|
{_t(
|
|
"settings|security|message_search_unsupported",
|
|
{
|
|
brand,
|
|
},
|
|
{
|
|
nativeLink: (sub) => (
|
|
<ExternalLink href={nativeLink} target="_blank" rel="noreferrer noopener">
|
|
{sub}
|
|
</ExternalLink>
|
|
),
|
|
},
|
|
)}
|
|
</SettingsSubsectionText>
|
|
);
|
|
} else if (!EventIndexPeg.platformHasSupport()) {
|
|
eventIndexingSettings = (
|
|
<SettingsSubsectionText>
|
|
{_t(
|
|
"settings|security|message_search_unsupported_web",
|
|
{
|
|
brand,
|
|
},
|
|
{
|
|
desktopLink: (sub) => (
|
|
<ExternalLink href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">
|
|
{sub}
|
|
</ExternalLink>
|
|
),
|
|
},
|
|
)}
|
|
</SettingsSubsectionText>
|
|
);
|
|
} else {
|
|
eventIndexingSettings = (
|
|
<>
|
|
<SettingsSubsectionText>
|
|
{this.state.enabling ? <InlineSpinner /> : _t("settings|security|message_search_failed")}
|
|
</SettingsSubsectionText>
|
|
{EventIndexPeg.error ? (
|
|
<SettingsSubsectionText>
|
|
<details>
|
|
<summary>{_t("common|advanced")}</summary>
|
|
<code>
|
|
{EventIndexPeg.error instanceof Error
|
|
? EventIndexPeg.error.message
|
|
: _t("error|unknown")}
|
|
</code>
|
|
<p>
|
|
<AccessibleButton
|
|
element="button"
|
|
key="delete"
|
|
kind="danger"
|
|
onClick={this.confirmEventStoreReset}
|
|
>
|
|
{_t("action|reset")}
|
|
</AccessibleButton>
|
|
</p>
|
|
</details>
|
|
</SettingsSubsectionText>
|
|
) : undefined}
|
|
</>
|
|
);
|
|
}
|
|
|
|
return eventIndexingSettings;
|
|
}
|
|
}
|