Skip to content

Commit

Permalink
feat!: join, leave, share
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentin-Guillemin committed Mar 8, 2023
1 parent 955fcdf commit f9c18e2
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 57 deletions.
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,29 @@ yarn start:ws

### WebComponent

| Prop | Description | Type | Required | Default |
| :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----: | :------: | :-----: |
| idb-name | Name for indexeddb | string | true | - |
| api-url | API url for file managment | string | true | - |
| ws-url | WebSocket url | string | false | - |
| room-id | Identifier of multiplayer room | string | false | - |
| read-only | Disable edition on multiplayer | bool | false | false |
| language | Default interface language (check [tldraw translation](https://github.com/tldraw/tldraw/tree/main/packages/tldraw/src/translations) for availables translations) | string | false | en |
| Prop | Description | Type | Required | Default |
| :--------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----: | :------: | :-----: |
| idb-name | Name for indexeddb | string | true | - |
| api-url | API url for file managment | string | true | - |
| ws-url | WebSocket url | string | false | - |
| room-id | Identifier of multiplayer room | string | false | - |
| language | Default interface language (check [tldraw translation](https://github.com/tldraw/tldraw/tree/main/packages/tldraw/src/translations) for availables translations) | string | false | en |
| read-only | Disable edition on multiplayer | bool | false | false |
| cant-join | Disallow users to join a room | bool | false | false |
| cant-leave | Disallow users to leave a room | bool | false | false |
| cant-share | Disallow users to share a romm | bool | false | false |

```html
<tldraw-editor
idb-name=""
api-url=""
ws-url=""
room-id=""
read-only
language=""
read-only
cant-join
cant-share
cant-leave
/>
```

Expand Down
6 changes: 6 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@tldraw/tldraw": "^1.28.0",
"axios": "^1.3.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.58.3",
"uuid": "^9.0.0",
"y-indexeddb": "^9.0.9",
"y-presence": "^0.2.3",
"y-websocket": "^1.4.6",
Expand All @@ -27,6 +32,7 @@
"@types/node": "^18.14.6",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.1",
"@vitejs/plugin-react": "^3.1.0",
"prop-types": "^15.8.1",
"react-to-webcomponent": "^1.7.3",
Expand Down
33 changes: 21 additions & 12 deletions client/src/App.css → client/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,37 @@ a {
height: 100%;
}

$border-radius: 9px;
$margin: 1px;
$padding: 9px;
$height: 40px;
$left-share: 110px;
$left-leave: 50px;

.share-container,
.leave-container {
z-index: 2;
position: absolute;
left: 110px;
left: $left-share;
display: flex;
box-shadow: var(--shadows-panel);
border-bottom-right-radius: 9px;
border-bottom-left-radius: 9px;
border-bottom-right-radius: $border-radius;
border-bottom-left-radius: $border-radius;
background-color: var(--colors-panelContrast);
}

.leave-container {
left: 50px;
left: $left-leave;
}

.share-item,
.leave-item {
width: 60px;
height: 40px;
border-radius: 9px;
margin: 1px;
width: fit-content;
min-width: $height;
height: $height;
border-radius: $border-radius;
margin: $margin;
padding: $padding;
cursor: pointer;
display: flex;
align-items: center;
Expand All @@ -66,9 +75,9 @@ a {
.input-join {
border: none;
outline: none;
width: 100px;
height: 40px;
margin: 1px;
border-radius: 9px;
width: 280px;
height: $height;
margin: $margin;
border-radius: $border-radius;
text-align: center;
}
2 changes: 1 addition & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Editor from "./components/Editor";
import "./App.css";
import "./App.scss";

const { VITE_API_URL, VITE_IDB_NAME, VITE_LANGUAGE, VITE_WS_URL } = import.meta
.env;
Expand Down
128 changes: 97 additions & 31 deletions client/src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,35 @@ import { CustomCursor } from "./Cursor";
import { useAssets } from "../hooks/useAssets";
import { useMultiplayer } from "../hooks/useMultiplayer";
import { useSingleplayer } from "../hooks/useSingleplayer";
import { initPersistence } from "../utils/y-indexeddb";
import { doc as localDoc, initPersistence } from "../utils/y-indexeddb";
import { destroyProvider, initProvider } from "../utils/y-websocket";
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
import PropTypes from "prop-types";
import {
Multiplayer,
MultiplayerReadOnly,
Settings,
Singleplayer,
} from "../types/types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowRightFromBracket,
faArrowRightToBracket,
faShareNodes,
faUsers,
faXmark,
} from "@fortawesome/free-solid-svg-icons";

Editor.propTypes = {
idbName: PropTypes.string.isRequired,
apiUrl: PropTypes.string.isRequired,
wsUrl: PropTypes.string,
roomId: PropTypes.string,
readOnly: PropTypes.bool,
language: PropTypes.string,
readOnly: PropTypes.bool,
cantJoin: PropTypes.bool,
cantLeave: PropTypes.bool,
cantShare: PropTypes.bool,
};

const components = {
Expand All @@ -32,66 +44,120 @@ function Editor({
apiUrl,
wsUrl,
roomId,
readOnly,
language,
readOnly,
cantJoin,
cantLeave,
cantShare,
}: Settings) {
const [room, setRoom] = useState(roomId);
const [joinRoom, setJoinRoom] = useState(undefined);
const [wantJoinRoom, setWantJoinRoom] = useState(false);
const [useLocalDoc, setUseLocalDoc] = useState(false);

language = language || "en";
initPersistence(idbName);
let editor = <SingleplayerEditor apiUrl={apiUrl} language={language} />;
if (wsUrl && room) {
initProvider(wsUrl, room);
useLocalDoc
? initProvider(wsUrl, room, localDoc)
: initProvider(wsUrl, room);
editor = readOnly ? (
<MultiplayerReadOnlyEditor roomId={room} language={language} />
) : (
<MultiplayerEditor apiUrl={apiUrl} roomId={room} language={language} />
);
} else setTimeout(() => destroyProvider(), 500);

const resetStates = () => {
setRoom(undefined);
setJoinRoom(undefined);
setWantJoinRoom(false);
setUseLocalDoc(false);
};

return (
<div>
{wsUrl && !room && (
<div className="share-container">
<input
onChange={(e: FormEvent<HTMLInputElement>) =>
setJoinRoom(e.target.value)
}
className="input-join"
type="text"
placeholder="id of room"
></input>
{joinRoom && (
{!cantShare && !readOnly && (
<a
className="share-item"
onClick={() => setRoom(uuidv4())}
title="Generate a room"
>
<FontAwesomeIcon icon={faUsers} />
</a>
)}
{!cantShare && !readOnly && (
<a
className="share-item"
onClick={() => {
setUseLocalDoc(true);
setRoom(uuidv4());
}}
title="Share current document"
>
<FontAwesomeIcon icon={faShareNodes} />
</a>
)}
{!cantJoin && !wantJoinRoom && (
<a
className="share-item"
onClick={() => setWantJoinRoom(true)}
title="Join room"
>
<FontAwesomeIcon icon={faArrowRightToBracket} />
</a>
)}
{!cantJoin && wantJoinRoom && (
<input
onChange={(e: FormEvent<HTMLInputElement>) =>
setJoinRoom(e.target.value)
}
className="input-join"
type="text"
placeholder="id of room to join..."
maxLength={uuidv4().length}
autoFocus
></input>
)}
{!cantJoin && wantJoinRoom && (
<a
className="share-item"
onClick={resetStates}
title="Close joining room input"
>
<FontAwesomeIcon icon={faXmark} />
</a>
)}
{!cantJoin && joinRoom && uuidValidate(joinRoom) && (
<a
className="share-item"
onClick={() => setRoom(joinRoom)}
title="Join room"
>
Join
<FontAwesomeIcon icon={faArrowRightToBracket} />
</a>
)}
</div>
)}
{wsUrl && room && (
<div className="leave-container">
<a
className="leave-item"
onClick={() => navigator.clipboard.writeText(room)}
title="Copy room id to clipboard"
>
{room}
</a>
<a
className="leave-item"
onClick={() => {
setJoinRoom(undefined);
setRoom(undefined);
}}
title="Leave room"
>
Leave
</a>
{!cantShare && (
<a
className="leave-item"
onClick={() => navigator.clipboard.writeText(room)}
title="Copy room id to clipboard"
>
{room}
</a>
)}
{!cantLeave && (
<a className="leave-item" onClick={resetStates} title="Leave room">
<FontAwesomeIcon icon={faArrowRightFromBracket} />
</a>
)}
</div>
)}
{editor}
Expand Down
3 changes: 3 additions & 0 deletions client/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export type Settings = {
roomId?: string;
readOnly?: boolean;
language?: string;
cantJoin?: boolean;
cantLeave?: boolean;
cantShare?: boolean;
};

export type Singleplayer = {
Expand Down
4 changes: 2 additions & 2 deletions client/src/utils/y-websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const provider = (): WebsocketProvider => {
return _provider;
};

const initProvider = (wsUrl: string, roomId: string) => {
_doc = new Y.Doc();
const initProvider = (wsUrl: string, roomId: string, doc?: Y.Doc) => {
_doc = doc ? doc : new Y.Doc();
_provider = new WebsocketProvider(wsUrl, roomId, _doc, {
connect: true,
});
Expand Down
Loading

0 comments on commit f9c18e2

Please sign in to comment.