Code Monkey home page Code Monkey logo

matrix-react-sdk's Introduction

npm Tests Playwright Static Analysis Localazy Quality Gate Status Coverage Vulnerabilities Bugs

matrix-react-sdk

This is a react-based SDK for inserting a Matrix chat/voip client into a web page.

This package provides the React components needed to build a Matrix web client using React. It is not useable in isolation, and instead must be used from a 'skin'. A skin provides:

  • Customised implementations of presentation components.
  • Custom CSS
  • The containing application
  • Zero or more 'modules' containing non-UI functionality

As of Aug 2018, the only skin that exists is vector-im/element-web; it and matrix-org/matrix-react-sdk should effectively be considered as a single project (for instance, matrix-react-sdk bugs are currently filed against vector-im/element-web rather than this project).

Developer Guide

Platform Targets:

All code lands on the develop branch - master is only used for stable releases. Please file PRs against develop!!

We use the same contribution guide as Element. Check it out here: https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md

Our code style is also the same as Element's: https://github.com/vector-im/element-web/blob/develop/code_style.md

Code should be committed as follows:

React components in matrix-react-sdk come in two different flavours: 'structures' and 'views'. Structures are stateful components which handle the more complicated business logic of the app, delegating their actual presentation rendering to stateless 'view' components. For instance, the RoomView component that orchestrates the act of visualising the contents of a given Matrix chat room tracks lots of state for its child components which it passes into them for visual rendering via props.

Good separation between the components is maintained by adopting various best practices that anyone working with the SDK needs to be aware of and uphold:

  • Components are named with upper camel case (e.g. views/rooms/EventTile.js)

  • They are organised in a typically two-level hierarchy - first whether the component is a view or a structure, and then a broad functional grouping (e.g. 'rooms' here)

  • The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css). CSS for matrix-react-sdk currently resides in https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css.

  • Per-view CSS is optional - it could choose to inherit all its styling from the context of the rest of the app, although this is unusual for any but

  • Theme specific CSS & resources: https://github.com/matrix-org/matrix-react-sdk/tree/master/res/themes structural components (lacking presentation logic) and the simplest view components.

  • The view MUST only refer to the CSS rules defined in its own CSS file. 'Stealing' styling information from other components (including parents) is not cool, as it breaks the independence of the components.

  • CSS classes are named with an app-specific name-spacing prefix to try to avoid CSS collisions. The base skin shipped by Matrix.org with the matrix-react-sdk uses the naming prefix "mx*". A company called Yoyodyne Inc might use a prefix like "yy*" for its app-specific classes.

  • CSS classes use upper camel case when they describe React components - e.g. .mx_MessageTile is the selector for the CSS applied to a MessageTile view.

  • CSS classes for DOM elements within a view which aren't components are named by appending a lower camel case identifier to the view's class name - e.g. .mx_MessageTile_randomDiv is how you'd name the class of an arbitrary div within the MessageTile view.

  • We deliberately use vanilla CSS 3.0 to avoid adding any more magic dependencies into the mix than we already have. App developers are welcome to use whatever floats their boat however. In future we'll start using css-next to pull in features like CSS variable support.

  • The CSS for a component can override the rules for child components. For instance, .mxRoomList .mx_RoomTile {} would be the selector to override styles of RoomTiles when viewed in the context of a RoomList view. Overrides _must be scoped to the View's CSS class - i.e. don't just define .mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override only to the context of RoomList views. N.B. overrides should be relatively rare as in general CSS inheritance should be enough.

  • Components should render only within the bounding box of their outermost DOM element. Page-absolute positioning and negative CSS margins and similar are generally not cool and stop the component from being reused easily in different places.

Originally matrix-react-sdk followed the Atomic design pattern as per http://patternlab.io to try to encourage a modular architecture. However, we found that the grouping of components into atoms/molecules/organisms made them harder to find relative to a functional split, and didn't emphasise the distinction between 'structural' and 'view' components, so we backed away from it.

Github Issues

All issues should be filed under https://github.com/vector-im/element-web/issues for now.

Development

Ensure you have the latest LTS version of Node.js installed.

Using yarn instead of npm is recommended. Please see the Yarn 1 install guide if you do not have it already. This project has not yet been migrated to Yarn 2, so please ensure yarn --version shows a version from the 1.x series.

matrix-react-sdk depends on matrix-js-sdk. To make use of changes in the latter and to ensure tests run against the develop branch of matrix-js-sdk, you should set up matrix-js-sdk:

git clone https://github.com/matrix-org/matrix-js-sdk
cd matrix-js-sdk
git checkout develop
yarn link
yarn install

Then check out matrix-react-sdk and pull in dependencies:

git clone https://github.com/matrix-org/matrix-react-sdk
cd matrix-react-sdk
git checkout develop
yarn link matrix-js-sdk
yarn install

See the help for yarn link for more details about this.

Running tests

Ensure you've followed the above development instructions and then:

yarn test

Running lint

To check your code complies with the project style, ensure you've followed the above development instructions and then:

yarn lint

Dependency problems

If you see errors (particularly "Cannot find module") running the lint or test commands, and yarn install doesn't fix them, it may be because yarn is not fetching git dependencies eagerly enough.

Try running this:

yarn cache clean && yarn install --force

Now the yarn commands should work as normal.

End-to-End tests

We use Playwright and Element Web for end-to-end tests. See docs/playwright.md for more information.

matrix-react-sdk's People

Contributors

aaronraimist avatar andybalaam avatar ara4n avatar bwindels avatar dbkr avatar florianduros avatar foldleft avatar germain-gg avatar jaiwanth-v avatar jorikschellekens avatar jryans avatar kegsay avatar kerryarchibald avatar luixxiul avatar lukebarnard1 avatar poljar avatar renovate[bot] avatar richvdh avatar riotrobot avatar robintown avatar rxl881 avatar s8321414 avatar simonbrandner avatar szimszon avatar t3chguy avatar turt2live avatar ujdhesa avatar waclaw66 avatar weeman1337 avatar wmwragg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

matrix-react-sdk's Issues

Background Colours for Messages

Background colours for messages would be a useful addition to the matrix web interface because some bridged systems support this functionality, namely IRC. This can be problematic when an IRC message is sent across which has white text and a black background. The text could become difficult to read when against the potentially light background of a matrix client.

A suggestion is to modify the HTML sanitation of <font> tags so that

    <font color="white" data-matrix-bg-color="black">Hopefully visible text</font>

is transformed into

    <font color="white" style="background-color:black;">Hopefully visible text</font>

whilst checking for CSS injection of the provided bg-color.

Given that CSS injection needs to be checked for anyway, perhaps a better solution would be to accept the style attribute and only allow color and background-color attributes to be allowed. http-sanitize sadly doesn't support this functionality, but it could be written separately.

Sanitization within matrix-react-sdk:
https://github.com/matrix-org/matrix-react-sdk/blob/24223ae2b69debb33fa22fcda5aeba6fa93c93eb/src/HtmlUtils.js

(See matrix-org/matrix-appservice-irc#62)

How can we use this SDK into local server?

I want to use chat application in one of my project and I found out this SDK. I setup SDK and follow the steps which is given in Readme.md file. After fire the commnad yarn start in terminal this command was executed babel src -w -s -d lib --verbose --extensions ".ts,.js" the src file converts into lib file and "then the watcher is ready" prints. So how can I see the login page or any view page I can see?

Please guide me over here.

Enable desktop notifications - TypeError

Hi guys, got a bug for you in /src/Notifier.js that actually comes from the electron Riot-web client: the desktop notifications initially work until it's checked OFF. Then it can't be re-enabled and throws the error:

Notifier.js:132 Uncaught TypeError: Cannot read property 'done' of undefined

Afterwards if you check this option, it will uncheck again whenever one of the radio buttons is selected and the page gets updated.

Cannot find type definition file for 'testing-library__jest-dom'.

Description

I have a fork of matrix-react-sdk that I use as a subtree in my project.
When trying to build my fork, it fails when running the command :
yarn build:types

output :

error TS2688: Cannot find type definition file for 'testing-library__jest-dom'.
  The file is in the program because:
    Entry point for implicit type library 'testing-library__jest-dom'


Found 1 error.

I didn't touch any configuration files or added any dependencies in the project.

Starting a new call ends in a race where either: the new call wont be sticky, or: both calls will be disconnected.

Currently this is solved with the workaround that the user does not even get the option to start a new call if there is a running one.

But in the future it would be nice if starting a new call would first disconnect the old call and then connect the new one.

All the requirements are in place:

  • The new call needs to wait until it can become sticky -> since there is always only one widget that can be sticky, we would kill the running widget by making the new one sticky. This is solved by a stickyPromise that needs to resolve before we can connect the new call.

It seems the issue is, that if a call is hung up the roomview will dispatch an action with view_call = false this action will kill the newly started call...
The logic is correct: only dispatch with view_call = false if there is no active call. But due to a potential race there seems to be no active call even though there should be. Maybe checking CallStore.instance.getCall could reveal if there is another call and one should not dispatch view_call = false

Avatar issue on 1:1 call

In a 1:1 private conversation with another person, when receiving a call, the avatar icon is stretch :

As it is:
2024-05-03_10-21

As it should be:
2024-05-03_10-20

The issue is due to the flex box of the parent element > mx_IncomingLegacyCallToast

Unable to run matrix-element web react app on the local development environment

I am having an issue to run the element react font-end app on my local environment when I try to run this command is the matrix-react-sdk with yarn like yarn start It will start building the app instead of opening on localhost, I don't want to run the entire app I just want to run only front-end app, React-app only, can you tell me how can I archive this?

request: support text input with clipboard events disabled.

some certification bodies requires disabling clipboardEvents. I will update this later on with any doc i can share.

This can impact govt and financial institution adoption of element-web (and other clients using this SDK) as it breaks pasting to the div-as-input component here.

Not to mention usability/18n as several niche browsers/extensions/ime input do not work well with that pattern.

you can simulate cases where that feature is not available on firefox, about:config, dom.event.clipboardevents.enabled=false. copy and cut (with preventDefault at the end of the methods) works fine. Only paste is impacted currently.

Documentation

Hi all. Is there any normal documentation for this project, API description?

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Pending Approval

These branches will be created by Renovate only once you click their checkbox below.

  • Update dependency @testing-library/jest-dom to v6.4.5
  • Update all non-major dependencies (eslint-plugin-jest, mcr.microsoft.com/playwright, posthog-js)
  • Update dependency @matrix-org/matrix-wysiwyg to v2.37.1
  • Update dependency @sentry/browser to v7.113.0
  • Update stylelint (stylelint, stylelint-scss)
  • ๐Ÿ” Create all pending approval PRs at once ๐Ÿ”

Pending Status Checks

These updates await pending status checks. To force their creation now, click the checkbox below.

  • Update definitelyTyped (@types/lodash, @types/node)
  • Update dependency axe-core to v4.9.1
  • Update dependency @playwright/test to v1.44.0

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

dockerfile
playwright/Dockerfile
  • mcr.microsoft.com/playwright v1.43.1-jammy
github-actions
.github/workflows/backport.yml
  • tibdex/backport v2@9565281eda0731b1d20c4025c43339fb0a23812e
.github/workflows/end-to-end-tests-netlify.yaml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/download-artifact v4
  • actions/upload-artifact v4
  • matrix-org/netlify-pr-preview v3
.github/workflows/end-to-end-tests.yaml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/upload-artifact v4
  • actions/checkout v4
  • actions/download-artifact v4
  • actions/setup-node v4
  • actions/cache v4
  • coactions/setup-xvfb 6b00cf1889f4e1d5a48635647013c0508128ee1a
  • actions/upload-artifact v4
.github/workflows/localazy_download.yaml
.github/workflows/localazy_upload.yaml
.github/workflows/netlify.yaml
  • bobheadxi/deployments v1@648679e8e4915b27893bd7dbc35cb504dc915bc8
  • actions/download-artifact v4
  • matrix-org/netlify-pr-preview v3
.github/workflows/notify-element-web.yml
  • peter-evans/repository-dispatch v3@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0
.github/workflows/pull_request.yaml
.github/workflows/release-drafter.yml
.github/workflows/release-gitflow.yml
.github/workflows/release.yml
.github/workflows/sonarqube.yml
.github/workflows/static_analysis.yaml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/setup-node v4
.github/workflows/tests.yml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/cache v4
  • SimenB/github-actions-cpu-cores v2@97ba232459a8e02ff6121db9362b09661c875ab8
  • actions/upload-artifact v4
  • Sibz/github-status-action v1@071b5370da85afbb16637d6eed8524a06bc2053e
  • actions/checkout v4
  • actions/setup-node v4
nodenv
.node-version
  • node 20
npm
package.json
  • @babel/runtime ^7.12.5
  • @matrix-org/analytics-events ^0.20.0
  • @matrix-org/emojibase-bindings ^1.1.2
  • @matrix-org/matrix-wysiwyg 2.17.0
  • @matrix-org/olm 3.2.15
  • @matrix-org/react-sdk-module-api ^2.4.0
  • @matrix-org/spec ^1.7.0
  • @sentry/browser ^7.0.0
  • @testing-library/react-hooks ^8.0.1
  • @vector-im/compound-design-tokens ^1.2.0
  • @vector-im/compound-web ^4.1.2
  • @zxcvbn-ts/core ^3.0.4
  • @zxcvbn-ts/language-common ^3.0.4
  • @zxcvbn-ts/language-en ^3.0.2
  • await-lock ^2.1.0
  • blurhash ^2.0.3
  • classnames ^2.2.6
  • commonmark ^0.31.0
  • counterpart ^0.18.6
  • diff-dom ^5.0.0
  • diff-match-patch ^1.0.5
  • emojibase-regex 15.3.0
  • escape-html ^1.0.3
  • file-saver ^2.0.5
  • filesize 10.1.1
  • gfm.css ^1.1.2
  • glob-to-regexp ^0.4.1
  • graphemer ^1.4.0
  • highlight.js ^11.3.1
  • html-entities ^2.0.0
  • is-ip ^3.1.0
  • js-xxhash ^3.0.1
  • jszip ^3.7.0
  • katex ^0.16.0
  • linkify-element 4.1.3
  • linkify-react 4.1.3
  • linkify-string 4.1.3
  • linkifyjs 4.1.3
  • lodash ^4.17.20
  • maplibre-gl ^2.0.0
  • matrix-encrypt-attachment ^1.0.3
  • matrix-events-sdk 0.0.1
  • matrix-widget-api ^1.5.0
  • memoize-one ^6.0.0
  • minimist ^1.2.5
  • oidc-client-ts ^3.0.1
  • opus-recorder ^8.0.3
  • pako ^2.0.3
  • png-chunks-extract ^1.0.0
  • posthog-js 1.130.1
  • proposal-temporal ^0.9.0
  • qrcode 1.5.3
  • re-resizable ^6.9.0
  • react 17.0.2
  • react-beautiful-dnd ^13.1.0
  • react-blurhash ^0.3.0
  • react-dom 17.0.2
  • react-focus-lock ^2.5.1
  • react-transition-group ^4.4.1
  • rfc4648 ^1.4.0
  • sanitize-filename ^1.6.3
  • sanitize-html 2.13.0
  • tar-js ^0.3.0
  • ua-parser-js ^1.0.2
  • uuid ^9.0.0
  • what-input ^5.2.10
  • @action-validator/cli ^0.6.0
  • @action-validator/core ^0.6.0
  • @axe-core/playwright ^4.8.1
  • @babel/cli ^7.12.10
  • @babel/core ^7.12.10
  • @babel/eslint-parser ^7.12.10
  • @babel/eslint-plugin ^7.12.10
  • @babel/parser ^7.12.11
  • @babel/plugin-proposal-class-properties ^7.12.1
  • @babel/plugin-proposal-export-default-from ^7.12.1
  • @babel/plugin-proposal-numeric-separator ^7.12.7
  • @babel/plugin-proposal-object-rest-spread ^7.12.1
  • @babel/plugin-transform-runtime ^7.12.10
  • @babel/preset-env ^7.12.11
  • @babel/preset-react ^7.12.10
  • @babel/preset-typescript ^7.12.7
  • @babel/register ^7.12.10
  • @casualbot/jest-sonar-reporter 2.2.7
  • @peculiar/webcrypto ^1.4.3
  • @playwright/test ^1.40.1
  • @testing-library/dom ^9.0.0
  • @testing-library/jest-dom ^6.0.0
  • @testing-library/react ^12.1.5
  • @testing-library/user-event ^14.4.3
  • @types/commonmark ^0.27.4
  • @types/content-type ^1.1.5
  • @types/counterpart ^0.18.1
  • @types/diff-match-patch ^1.0.32
  • @types/escape-html ^1.0.1
  • @types/express ^4.17.21
  • @types/file-saver ^2.0.3
  • @types/fs-extra ^11.0.0
  • @types/glob-to-regexp ^0.4.1
  • @types/jest 29.5.12
  • @types/katex ^0.16.0
  • @types/lodash ^4.14.168
  • @types/modernizr ^3.5.3
  • @types/node 18
  • @types/node-fetch ^2.6.2
  • @types/pako ^2.0.0
  • @types/qrcode ^1.3.5
  • @types/react 17.0.80
  • @types/react-beautiful-dnd ^13.0.0
  • @types/react-dom 17.0.25
  • @types/react-transition-group ^4.4.0
  • @types/sanitize-html 2.11.0
  • @types/sdp-transform ^2.4.6
  • @types/tar-js ^0.3.2
  • @types/ua-parser-js ^0.7.36
  • @types/uuid ^9.0.2
  • @typescript-eslint/eslint-plugin ^7.0.0
  • @typescript-eslint/parser ^7.0.0
  • axe-core 4.9.0
  • babel-jest ^29.0.0
  • blob-polyfill ^7.0.0
  • eslint 8.57.0
  • eslint-config-google ^0.14.0
  • eslint-config-prettier ^9.0.0
  • eslint-plugin-deprecate 0.8.4
  • eslint-plugin-import ^2.25.4
  • eslint-plugin-jest ^28.0.0
  • eslint-plugin-jsx-a11y ^6.5.1
  • eslint-plugin-matrix-org 1.2.1
  • eslint-plugin-react ^7.28.0
  • eslint-plugin-react-hooks ^4.3.0
  • eslint-plugin-unicorn ^52.0.0
  • express ^4.18.2
  • fake-indexeddb ^5.0.2
  • fetch-mock-jest ^1.5.1
  • fs-extra ^11.0.0
  • jest ^29.6.2
  • jest-canvas-mock ^2.5.2
  • jest-environment-jsdom ^29.6.2
  • jest-mock ^29.6.2
  • jest-raw-loader ^1.0.1
  • jsqr ^1.4.0
  • mailhog ^4.16.0
  • matrix-web-i18n ^3.2.1
  • mocha-junit-reporter ^2.2.0
  • node-fetch 2
  • postcss-scss ^4.0.4
  • prettier 3.2.5
  • raw-loader ^4.0.2
  • rimraf ^5.0.0
  • stylelint ^16.1.0
  • stylelint-config-standard ^36.0.0
  • stylelint-scss ^6.0.0
  • ts-node ^10.9.1
  • typescript 5.4.5
  • postcss ^8.4.19
  • webpack ^4.0.0 || ^5.0.0
  • @types/react-dom 17.0.25
  • @types/react 17.0.80
  • oidc-client-ts 3.0.1
  • jwt-decode 4.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

Comment says "if we're currently in a 1:1..., start a new chat"

Should I simply change this to "if we're NOT currently in a 1:1..., start a new chat"

or send in the following

diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index ddd0d1f..cc0351d 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -365,15 +365,9 @@ module.exports = React.createClass({
             var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
             currentMembers = currentRoom.getJoinedMembers();
         }
-        // if we're currently in a 1:1 with this user, start a new chat
-        if (currentMembers && currentMembers.length === 2 &&
-            userIds.indexOf(currentMembers[0].userId) !== -1 &&
-            userIds.indexOf(currentMembers[1].userId) !== -1)
-        {
+        if (no_existing_private_one_on_one()) {
             existingRoomId = null;
-        }
-        // otherwise reuse the first private 1:1 we find
-        else {
+        } else { // reuse existing private one on one
             existingRoomId = null;

             for (var i = 0; i < rooms.length; i++) {
@@ -391,6 +385,11 @@ module.exports = React.createClass({
                 }
             }
         }
+        function no_existing_private_one_on_one() {
+            return currentMembers && currentMembers.length === 2 &&
+                userIds.indexOf(currentMembers[0].userId) !== -1 &&
+                userIds.indexOf(currentMembers[1].userId) !== -1;
+        }


Add support for LaTeX formulas

It would be nice to support the rendering of LaTeX formulas, both text and displayed formulas.

I hope this is the right place to put this feature request.

Detailed explanation of using matrix-react-sdk

Hello, I've tried to setup matrix-react-sdk in order to use matrix chat and its UI components and etc in my app. But I can't find any valid examples and all the time just get that matrix-react-sdk is not resolved by webpack, can you please help me or just give the link to the valid documentation of how to start it from scratch or something?

Architecture and design

Spoke with @dbkr today about the split of React SDK and skins on top. This issue is a huge summary of the discussion and some proposals moving forwards.

Problems with the existing architecture

There's a number of issues currently which I feel aren't so great, including:

  • The view/controller approach was intended to allow people to replace the entire HTML(view) but keep the underlying controller logic. In practice, there's tighter coupling here than you'd think and it's not always clear what should go where (e.g. I click a hide/show button which changes state, this is logic so you'd think controller. but it's purely aesthetic information from the view).
  • Augmenting controllers doesn't really work. One of the main problems we both identified was Vector's RoomView controller which is a c/p of the React SDK one with some conference specific guff added. This trips people up (both of us included) when instinctively modifying the React SDK's controller and expecting it to work. It doesn't because the Vector one is entirely clobbering the React SDK one, not augmenting it.
  • Coupling between controllers and views makes it very easy to introduce subtle breakages. A good example of this is the CreateRoom controller in the React SDK which calls a function which is only defined in the Vector skin (which means it'll crash on anything not vector). A similar subtlety is when views call functions seemingly not defined anywhere (because they're defined in their controller).
  • The API surface exposed by React SDK is not well-formed. You can't really make an API reference for it. We have some classes which do some things but there's no real place to get a reference of what you can do aside from examples.

Proposed solution

Rather than splitting along the View/Controller, I propose splitting along component boundaries. In practice, this means:

  • Combining the views/controller for a class together.
  • Exposing a set of reusable components that the layers on top (Vector) can use.

The biggest concerns with this was "how do you prevent the layers on top from re-implementing all the logic that the SDK does?". My proposal is to make the components extremely thin, stateless UI code and that's it. Currently, the controllers do a lot of "business logic" like making API calls, dispatching actions to the rest of the app, etc. This is good but shouldn't really reside in the controller for a specific component. Take for example resending messages. Previously, you could hit a message and hit Resend which would do the API call and dispatch actions to the rest of the app. When I came to implement "Resend all" in a different component I couldn't easily reuse the existing code to resend a single message because it was tightly coupled with the component it resided in (setting things like this.state and this.props which my new component didn't want).

The proposed architecture would resolve these issues by effectively having "stateless UI components" and "wire components" which glue up other components and make calls through to the React SDK. Every component would accept props which govern their inputs and callbacks that they'd invoke. They would look something like this (using an example of leaving PR comments):

Syntax:
ComponentName(Type propInput, Type propInput)


PullRequestPage(String repo, Integer pr)
     |
     |_____________ PullRequestSummary(PullRequest pr)
     |_____________ CommentListView(Comment[] comments, Boolean showCommentBox, Callback onSubmitText)
                            |
                            |________CommentView(Comment comment)
                            |________CommentView(Comment comment)
                            |________CommentView(Comment comment)
                            |________ ...
                            |________CommentBox(Callback onSubmitText)

An implementation of CommentView would be extremely thin, basically just the render() method:

module.exports = React.createClass({displayName: 'CommentView',
    propTypes: {
        comment: React.PropTypes.any.isRequired
    },
    render: function() {
        return (
            <div className="CommentView">
                <img className="CommentView_avatar" src={
                    comment.getUser().getAvatarUrl()
                } />
                <div className="CommentView_header">
                    {comment.getUser().getName()} commented {
                        comment.getTimeAgo()
                    } ago <a href={comment.getLink()} target="_blank">(Source)</a>
                </div>
                <div className="CommentView_body">
                    {comment.getBody()}
                </div>
            </div>
        );
    }

If you wanted to add any UI-only state (show/hide, etc) it would reside in this file. If you wanted to do anything like say delete the comment, you would add a new prop onDeleteClick which will be called when they hit the delete button. This allows massive code reuse. For example, in Github's case you hit two different endpoints depending on whether you're interacting with Line Comments or PR comments. This UI component need not care, it just knows how to display the comment and a delete button. If one of the APIs didn't expose a deletion endpoint, you could easily add a prop to hide the delete button (or do inference e.g. "if there is a delete callback, I'll show a delete button").

By using props everywhere, we encourage "bubbling" of callbacks up from children to parents (See how onSubmitText is bubbled up from the CommentBox to the CommentListView to the PullRequestPage). This is a bit boilerplatey but is situationally useful (e.g. to disable the parent UI if you're sending a message from the child). There is no hard and fast rule on whether you should bubble or not other than asking if the component you're writing might be reused in another part which may have different logic (think the GH commenting example). Both uses are valid.

Dispatcher

The dispatcher would remain similar to as it is today but with a much stricter split between React SDK triggered actions and other stuff (e.g. Vector-specific triggered actions). Layers on top of the SDK would be almost exclusively consumers of SDK actions from the dispatcher, and would be able to produce/consume actions in their own layer with a sensible prefix e.g. vector_. Layers on top may in rare cases want to emit SDK-level actions for more exotic behaviour but it should be rare. Doing this means if you're following through some code you can have greater intuition of the control flow between the different layers based off a helpful action name.

Business logic

This relates to the "resend" example I gave earlier. The proposal here is to have a set of "pure logic classes" which:

  • Make JS SDK API calls.
  • Emit actions on the dispatcher.
  • Manage any internal state (e.g. call state).

We already have started to organically grow these with CasLogic, CallHandler, SlashCommands, TextForEvent, WhoIsTyping and so on. I propose we formalise this concept (e.g. into a logic directory) split on concept boundaries (Calls, Messages, Auth, /commands, etc). These can either be "statically" linked into the React class (like we currently do by require()ing them) or by DIing them as additional props. This brings me onto...

Testing

Testing React components and UIs in general is Hard. We basically have two options. Test visually by comparing to "known working" snapshots and test by using shallow rendering (basically it dumps render()'s HTML output to a string which you can then test for equality. I dislike both of these. A change to the HTML <div> ordering shouldn't break the tests IMO. To this end, I do not propose testing React components directly, but instead the business logic classes. This is one of the reasons why the proposed architecture has extremely thin UI components with not a lot that can go wrong in them.

Example control flow

  • [MessageComposer] User hits a button to send a message. Invoke a prop callback with the raw input string.
  • [BottomRoomBar] Maybe augment the raw input string (e.g. imagine if a "Markdown" check box was enabled) then invoke a prop callback.
  • [RoomView] Call through to a logic class e.g. MessageController.
  • [MessageController] Do the API call and get a Promise. Maybe immediately emit a "sending" action on the dispatcher so other UI components not on the critical path can grey out their buttons. Attach a resolve/reject listener to emit a "sent" or "failed to send" action. Return the Promise.
  • [RoomView] Return the promise.
  • [BottomRoomBar] setState to display a spinner. Attach a resolve/reject listener on the promise to hide the spinner depending on the outcome. Return the promise.
  • [MessageComposer] Grey out the UI. Attach a resolve/reject listener to un-grey the UI after it succeeds/fails.

This gives components a lot of flexibility. If components are on the critical path of parent-child relationships, more often than not they want to do stuff so rather than forcing them to listen for the "dispatcher" actions (which emulate a request/response cycle which is by far better represented as you know, a function call) they can just hook in before the bubble back the response. Furthermore, in this control flow everything is explicit. Your boundaries (the props) are well-defined.

Outstanding problems

  • Augmenting components (the RoomView example) remains a thorny issue. This modified the render() method and added in extra props to the React SDK RoomView. With RoomView as it is, there is no good solution to this. You can't even abuse mixins because you can only have One True render() method which would need to know the "base" React SDK HTML and then the "extra" Vector HTML on top. To this end, a rather unsatisfying (though probably correct) solution would be to keep as much shared code as possible and then clobber the rest. In practice, this would mean breaking RoomView up into smaller stateless UI components and then switching to the Vector-specific code higher up the chain when you're just gluing together components. This results in less code duplication and prevents you from opening the "wrong" file (because you're loading a different component entirely rather than the same component with a different controller backend.

Next steps

If @ara4n and @dbkr are in agreement, the "actionable" steps to take on this would be:

  • Go through on a component-by-component basis and combine the view/controller. Suck out anything which is not exclusively UI-related into a logic class. Aggressively define and inject props when you need some data to work with (rather than yanking it from MatrixClientPeg) and think about what callbacks you want to provide (e.g. RoomList may want to just have onRoomClick(Room), MessageComposer may want onTabClick(inputText), onSubmit(text), etc). Make Vector either use the pure-UI class that is left if the HTML is right, or write the pure-UI class and use that instead. This can be done incrementally for each component without requiring one huge branch/PR/merge.
  • If Vector emits any actions specific to it (e.g. conf call stuff?) then add the vector_ prefix.
  • Start adding tests for the logic classes we've pulled out of the components.

What the React SDK API reference would look like

  • A list of Components each with:
    • A name.
    • A list of defined properties with types (from propTypes) documenting the inputs/callbacks.
    • A list of actions that it will be listening for.
    • A list of actions that it emits (with args).
  • A list of "logic" classes with JSDoc. Any component is free to directly call these.
  • An "action" reference. This is a complete list of actions that the SDK emits each with:
    • A name.
    • Names of keys / values with types on the payload.

Most components will be stateless UI components but some components may be "wire" components which could form the basis of Matrix React Console (though it would need CSS).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.