Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/local-echo-dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Local echo (developer docs)

The React SDK provides some local echo functionality to allow for components to do something
quickly and fall back when it fails. This is all available in the `local-echo` directory within
`stores`.

Echo is handled in EchoChambers, with `GenericEchoChamber` being the base implementation for all
chambers. The `EchoChamber` class is provided as semantic access to a `GenericEchoChamber`
implementation, such as the `RoomEchoChamber` (which handles echoable details of a room).

Anything that can be locally echoed will be provided by the `GenericEchoChamber` implementation.
The echo chamber will also need to deal with external changes, and has full control over whether
or not something has successfully been echoed.

An `EchoContext` is provided to echo chambers (usually with a matching type: `RoomEchoContext`
gets provided to a `RoomEchoChamber` for example) with details about their intended area of
effect, as well as manage `EchoTransaction`s. An `EchoTransaction` is simply a unit of work that
needs to be locally echoed.

The `EchoStore` manages echo chamber instances, builds contexts, and is generally less semantically
accessible than the `EchoChamber` class. For separation of concerns, and to try and keep things
tidy, this is an intentional design decision.

**Note**: The local echo stack uses a "whenable" pattern, which is similar to thenables and
`EventEmitter`. Whenables are ways of actioning a changing condition without having to deal
with listeners being torn down. Once the reference count of the Whenable causes garbage collection,
the Whenable's listeners will also be torn down. This is accelerated by the `IDestroyable` interface
usage.

## Audit functionality

The UI supports a "Server isn't responding" dialog which includes a partial audit log-like
structure to it. This is partially the reason for added complexity of `EchoTransaction`s
and `EchoContext`s - this information feeds the UI states which then provide direct retry
mechanisms.

The `EchoStore` is responsible for ensuring that the appropriate non-urgent toast (lower left)
is set up, where the dialog then drives through the contexts and transactions.

3 changes: 3 additions & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
@import "./structures/_MainSplit.scss";
@import "./structures/_MatrixChat.scss";
@import "./structures/_MyGroups.scss";
@import "./structures/_NonUrgentToastContainer.scss";
@import "./structures/_NotificationPanel.scss";
@import "./structures/_RightPanel.scss";
@import "./structures/_RoomDirectory.scss";
Expand Down Expand Up @@ -75,6 +76,7 @@
@import "./views/dialogs/_RoomSettingsDialogBridges.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss";
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
@import "./views/dialogs/_ServerOfflineDialog.scss";
@import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss";
@import "./views/dialogs/_SetPasswordDialog.scss";
Expand Down Expand Up @@ -215,6 +217,7 @@
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
@import "./views/terms/_InlineTermsAgreement.scss";
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
@import "./views/verification/_VerificationShowSas.scss";
@import "./views/voip/_CallContainer.scss";
@import "./views/voip/_CallView.scss";
Expand Down
35 changes: 35 additions & 0 deletions res/css/structures/_NonUrgentToastContainer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
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_NonUrgentToastContainer {
position: absolute;
bottom: 30px;
left: 28px;
z-index: 101; // same level as other toasts

.mx_NonUrgentToastContainer_toast {
padding: 10px 12px;
border-radius: 8px;
width: 320px;
font-size: $font-13px;
margin-top: 8px;

// We don't use variables on the colours because we want it to be the same
// in all themes.
background-color: #17191c;
color: #fff;
}
}
72 changes: 72 additions & 0 deletions res/css/views/dialogs/_ServerOfflineDialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
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_ServerOfflineDialog {
.mx_ServerOfflineDialog_content {
padding-right: 85px;
color: $primary-fg-color;

hr {
border-color: $primary-fg-color;
opacity: 0.1;
border-bottom: none;
}

ul {
padding: 16px;

li:nth-child(n + 2) {
margin-top: 16px;
}
}

.mx_ServerOfflineDialog_content_context {
.mx_ServerOfflineDialog_content_context_timestamp {
display: inline-block;
width: 115px;
color: $muted-fg-color;
line-height: 24px; // same as avatar
vertical-align: top;
}

.mx_ServerOfflineDialog_content_context_timeline {
display: inline-block;
width: calc(100% - 155px); // 115px timestamp width + 40px right margin

.mx_ServerOfflineDialog_content_context_timeline_header {
span {
margin-left: 8px;
vertical-align: middle;
}
}

.mx_ServerOfflineDialog_content_context_txn {
position: relative;
margin-top: 8px;

.mx_ServerOfflineDialog_content_context_txn_desc {
width: calc(100% - 100px); // 100px is an arbitrary margin for the button
}

.mx_AccessibleButton {
float: right;
padding: 0;
}
}
}
}
}
}
37 changes: 37 additions & 0 deletions res/css/views/toasts/_NonUrgentEchoFailureToast.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
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_NonUrgentEchoFailureToast {
.mx_NonUrgentEchoFailureToast_icon {
display: inline-block;
width: $font-18px;
height: $font-18px;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background-color: #fff; // we know that non-urgent toasts are always styled the same
mask-image: url('$(res)/img/element-icons/cloud-off.svg');
margin-right: 8px;
}

span { // includes the i18n block
vertical-align: middle;
}

.mx_AccessibleButton {
padding: 0;
}
}
3 changes: 3 additions & 0 deletions res/img/element-icons/cloud-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/@types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { JSXElementConstructor } from "react";

// Based on https://stackoverflow.com/a/53229857/3532235
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never};
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };

export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
2 changes: 2 additions & 0 deletions src/components/structures/LoggedInView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import LeftPanel from "./LeftPanel";
import CallContainer from '../views/voip/CallContainer';
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
import RoomListStore from "../../stores/room-list/RoomListStore";
import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";

// We need to fetch each pinned message individually (if we don't already have it)
Expand Down Expand Up @@ -688,6 +689,7 @@ class LoggedInView extends React.Component<IProps, IState> {
</DragDropContext>
</div>
<CallContainer />
<NonUrgentToastContainer />
</MatrixClientContext.Provider>
);
}
Expand Down
63 changes: 63 additions & 0 deletions src/components/structures/NonUrgentToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
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 * as React from "react";
import { ComponentClass } from "../../@types/common";
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
import { UPDATE_EVENT } from "../../stores/AsyncStore";

interface IProps {
}

interface IState {
toasts: ComponentClass[],
}

export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
public constructor(props, context) {
super(props, context);

this.state = {
toasts: NonUrgentToastStore.instance.components,
};

NonUrgentToastStore.instance.on(UPDATE_EVENT, this.onUpdateToasts);
}

public componentWillUnmount() {
NonUrgentToastStore.instance.off(UPDATE_EVENT, this.onUpdateToasts);
}

private onUpdateToasts = () => {
this.setState({toasts: NonUrgentToastStore.instance.components});
};

public render() {
const toasts = this.state.toasts.map((t, i) => {
return (
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
{React.createElement(t, {})}
</div>
);
});

return (
<div className="mx_NonUrgentToastContainer" role="alert">
{toasts}
</div>
);
}
}
Loading