Skip to content

Commit

Permalink
update todo example
Browse files Browse the repository at this point in the history
  • Loading branch information
YousefED committed Jan 10, 2022
1 parent 0b7d82c commit 7a5f0ef
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 34,552 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Matrix CRDT

<a href="https://discord.gg/aDQxXezfNj"><img alt="Discord" src="https://img.shields.io/badge/Chat on discord%20-%237289DA.svg?&style=for-the-badge&logo=discord&logoColor=white"/></a>
<a href="https://discord.gg/aDQxXezfNj"><img alt="Discord" src="https://img.shields.io/badge/Chat on discord%20-%237289DA.svg?&style=for-the-badge&logo=discord&logoColor=white"/></a> <a href="https://matrix.to/#/#beyond-chat:matrix.org"><img alt="Matrix" src="https://img.shields.io/badge/Chat on matrix%20-%23000.svg?&style=for-the-badge&logo=matrix&logoColor=white"/></a>

[![npm version](https://badge.fury.io/js/matrix-crdt.svg)](https://badge.fury.io/js/matrix-crdt) [![Coverage Status](https://coveralls.io/repos/github/YousefED/Matrix-CRDT/badge.svg?branch=main)](https://coveralls.io/github/YousefED/Matrix-CRDT?branch=main)

Expand Down
3 changes: 2 additions & 1 deletion examples/todo-simple-react/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
A simple example using SyncedStore and Matrix-CRDT.
A simple TODO app example using SyncedStore and Matrix-CRDT.
TODOs are saved in a Matrix Room.

- [Open live demo](https://lvclo.csb.app/) (via CodeSandbox)
- [Edit code](https://codesandbox.io/s/matrix-crdt-todo-simple-example-lvclo?file=/src/App.tsx) (CodeSandbox)
3 changes: 3 additions & 0 deletions examples/todo-simple-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"yjs": "^13.5.16",
"y-protocols": "^1.0.5"
},
"devDependencies": {
"react-error-overlay": "^6.0.9"
},
"scripts": {
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start",
"package": "react-scripts build",
Expand Down
45 changes: 8 additions & 37 deletions examples/todo-simple-react/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,20 @@
import { Box, Checkbox, Heading, Text, TextInput } from "@primer/react";
import { getYjsValue } from "@syncedstore/core";
import { useSyncedStore } from "@syncedstore/react";
import { MatrixProvider } from "matrix-crdt";
import { MatrixClient } from "matrix-js-sdk";
import React, { useState } from "react";
import { LoginButton } from "./login/LoginButton";
import * as Y from "yjs";
import React from "react";
import MatrixStatusBar from "./MatrixStatusBar";
import { globalStore } from "./store";

export default function App() {
const state = useSyncedStore(globalStore);
const [isOpen, setIsOpen] = useState(false);
const [matrixProvider, setMatrixProvider] = useState<MatrixProvider>();

// Called when a MatrixClient is available (provided by LoginButton)
const setMatrixClient = React.useCallback(
(matrixClient: MatrixClient, roomAlias: string) => {
if (matrixProvider) {
matrixProvider.dispose();
}
const newMatrixProvider = new MatrixProvider(
getYjsValue(globalStore) as any,
matrixClient,
{ type: "alias", alias: roomAlias },
undefined,
{
translator: { updatesAsRegularMessages: true },
reader: { snapshotInterval: 10 },
writer: { flushInterval: 500 },
}
);
newMatrixProvider.initialize(); // TODO: show status
setMatrixProvider(newMatrixProvider);
},
[matrixProvider]
);

return (
<Box m={3} maxWidth={600} marginLeft={"auto"} marginRight={"auto"} p={3}>
<Box textAlign={"right"}>
{/* TODO: add options to go offline / webrtc, snapshots etc */}
<LoginButton
setMatrixClient={setMatrixClient}
isOpen={isOpen}
setIsOpen={setIsOpen}
/>
</Box>
{/* This is the top bar with Sign in button and Matrix status
It also takes care of hooking up the Y.Doc to Matrix.
*/}
<MatrixStatusBar doc={getYjsValue(state) as Y.Doc} />

<Heading sx={{ mb: 2 }}>Todo items:</Heading>

Expand Down
160 changes: 160 additions & 0 deletions examples/todo-simple-react/src/MatrixStatusBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { Box, ChoiceInputField, Label, Radio } from "@primer/react";
import { MatrixProvider } from "matrix-crdt";
import { MatrixClient } from "matrix-js-sdk";
import React, { useState } from "react";
import { LoginButton } from "./login/LoginButton";
import * as Y from "yjs";

/**
* The Top Bar of the app that contains the sign in button and status of the MatrixProvider (connection to the Matrix Room)
*/
export default function MatrixStatusBar({ doc }: { doc: Y.Doc }) {
const [isOpen, setIsOpen] = useState(false);
const [matrixProvider, setMatrixProvider] = useState<MatrixProvider>();
const [status, setStatus] = useState<
"loading" | "failed" | "ok" | "disconnected"
>();

const [matrixClient, setMatrixClient] = useState<MatrixClient>();
const [roomAlias, setRoomAlias] = useState<string>();

const connect = React.useCallback(
(matrixClient: MatrixClient, roomAlias: string) => {
if (!matrixClient || !roomAlias) {
throw new Error("can't connect without matrixClient or roomAlias");
}

// This is the main code that sets up the connection between
// yjs and Matrix. It creates a new MatrixProvider and
// registers it to the `doc`.
const newMatrixProvider = new MatrixProvider(
doc,
matrixClient,
{ type: "alias", alias: roomAlias },
undefined,
{
translator: { updatesAsRegularMessages: true },
reader: { snapshotInterval: 10 },
writer: { flushInterval: 500 },
}
);
setStatus("loading");
newMatrixProvider.initialize();
setMatrixProvider(newMatrixProvider);

// (optional): capture events from MatrixProvider to reflect the status in the UI
newMatrixProvider.onDocumentAvailable((e) => {
setStatus("ok");
});

newMatrixProvider.onCanWriteChanged((e) => {
if (!newMatrixProvider.canWrite) {
setStatus("failed");
} else {
setStatus("ok");
}
});

newMatrixProvider.onDocumentUnavailable((e) => {
setStatus("failed");
});
},
[doc]
);

const onLogin = React.useCallback(
(matrixClient: MatrixClient, roomAlias: string) => {
if (matrixProvider) {
matrixProvider.dispose();
setStatus("disconnected");
setMatrixProvider(undefined);
}

// (optional) stored on state for easy disconnect + connect toggle
setMatrixClient(matrixClient);
setRoomAlias(roomAlias);

// actually connect
connect(matrixClient, roomAlias);
},
[matrixProvider, connect]
);

const onConnectChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (!matrixClient || !roomAlias) {
throw new Error("matrixClient and roomAlias should be set");
}

if (matrixProvider) {
matrixProvider.dispose();
setStatus("disconnected");
setMatrixProvider(undefined);
}

if (e.target.value === "true") {
connect(matrixClient, roomAlias);
}
},
[connect, matrixClient, roomAlias, matrixProvider]
);

return (
<Box textAlign={"right"}>
{/* TODO: add options to go offline / webrtc, snapshots etc */}
{status === undefined && (
<LoginButton onLogin={onLogin} isOpen={isOpen} setIsOpen={setIsOpen} />
)}
{matrixClient && (
<fieldset style={{ margin: 0, padding: 0, border: 0 }}>
<ChoiceInputField>
<ChoiceInputField.Label>Online</ChoiceInputField.Label>
<Radio
name="online"
value="true"
defaultChecked={true}
onChange={onConnectChange}
/>
</ChoiceInputField>
<ChoiceInputField>
<ChoiceInputField.Label>
Offline (disable sync)
</ChoiceInputField.Label>
<Radio
name="online"
value="false"
defaultChecked={false}
onChange={onConnectChange}
/>
</ChoiceInputField>
</fieldset>
)}
{status === "loading" && (
<Label variant="small" outline>
Connecting with Matrix room…
</Label>
)}
{status === "disconnected" && (
<Label variant="small" outline>
Disconnected
</Label>
)}
{status === "ok" && (
<Label
variant="small"
outline
sx={{ borderColor: "success.emphasis", color: "success.fg" }}>
Connected with Matrix room
</Label>
)}
{status === "failed" && (
<Label
variant="small"
outline
sx={{ borderColor: "danger.emphasis", color: "danger.fg" }}>
Failed. Make sure the user has access to the Matrix room
</Label>
)}
</Box>
);
}
8 changes: 4 additions & 4 deletions examples/todo-simple-react/src/login/LoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { createMatrixClient, LoginData } from "./utils";
export const LoginButton = ({
isOpen,
setIsOpen,
setMatrixClient,
onLogin,
}: {
isOpen: boolean;
setIsOpen: (open: boolean) => void;
setMatrixClient: (client: MatrixClient, roomAlias: string) => void;
onLogin: (client: MatrixClient, roomAlias: string) => void;
}) => {
const [loginData, setLoginData] = React.useState<LoginData>();
const [status, setStatus] = React.useState<"ok" | "loading" | "failed">("ok");
Expand All @@ -27,13 +27,13 @@ export const LoginButton = ({
try {
const matrixClient = await createMatrixClient(loginData!);
setIsOpen(false);
setMatrixClient(matrixClient, loginData!.roomAlias);
onLogin(matrixClient, loginData!.roomAlias);
setStatus("ok");
} catch (e) {
setStatus("failed");
}
})();
}, [setIsOpen, loginData, setMatrixClient]);
}, [setIsOpen, loginData, onLogin]);

return (
<>
Expand Down
4 changes: 0 additions & 4 deletions examples/todo-simple-react/src/login/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,6 @@ export default function LoginForm({
</InputField.Caption>
</InputField>
</FormGroup>
{/* <ButtonPrimary
onClick={() => setMatrixCredentials(server, user, token, roomAlias)}>
Sign in
</ButtonPrimary> */}
</Box>
</div>
);
Expand Down
41 changes: 1 addition & 40 deletions examples/todo-simple-react/src/store.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,8 @@
import { getYjsValue, syncedStore } from "@syncedstore/core";
import { MatrixProvider } from "matrix-crdt";
import sdk from "matrix-js-sdk";
import { syncedStore } from "@syncedstore/core";

export type Todo = {
title: string;
completed: boolean;
};

export const globalStore = syncedStore({ todos: [] as Todo[] });
// new MatrixProvider(getYjsValue(globalStore) as any, client, {}
let matrixProvider: MatrixProvider | undefined;
export function setMatrixCredentials(
server: string,
userId: string,
token: string,
room: string
) {
if (matrixProvider) {
matrixProvider.dispose();
}
const matrixClient = sdk.createClient({
baseUrl: server,
accessToken: token,
userId,
});
// overwrites because we don't call .start();
(matrixClient as any).canSupportVoip = false;
(matrixClient as any).clientOpts = {
lazyLoadMembers: true,
};

matrixClient.loginWithToken(token);

matrixProvider = new MatrixProvider(
getYjsValue(globalStore) as any,
matrixClient,
{ type: "alias", alias: room },
undefined,
{
translator: { updatesAsRegularMessages: true },
reader: { snapshotInterval: 10 },
writer: { flushInterval: 500 },
}
);
matrixProvider.initialize();
}

0 comments on commit 7a5f0ef

Please sign in to comment.