Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Commit

Permalink
feat!: tldraw-editor renamed to tldraw-singleplayer & new tldraw-mult…
Browse files Browse the repository at this point in the history
…iplayer component
  • Loading branch information
Quentin-Guillemin committed Jan 26, 2024
1 parent d305afa commit 72c4d3e
Show file tree
Hide file tree
Showing 11 changed files with 817 additions and 63 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@
"dependencies": {
"@gip-recia/tldraw-v1": "^1.29.2-recia-1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"y-presence": "^0.2.3",
"y-websocket": "^1.5.3",
"yjs": "^13.6.11"
},
"devDependencies": {
"@commitlint/cli": "^18.4.4",
Expand Down
66 changes: 57 additions & 9 deletions src/AppDev.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,65 @@
import './assets/scss/app.scss';
import Editor from './components/Editor.tsx';
import MultiplayerEditor from './components/MultiplayerEditor.tsx';
import SingleplayerEditor from './components/SingleplayerEditor.tsx';
import { useState } from 'react';

function App() {
const { VITE_PERSISTANCE_API_URL, VITE_ASSETS_API_URL, VITE_USER_INFO_API_URI } = import.meta.env;
const {
VITE_PERSISTANCE_API_URL,
VITE_ASSETS_API_URL,
VITE_WEBSOCKET_API_URL,
VITE_USER_INFO_API_URI,
VITE_ROOM_ID,
} = import.meta.env;

const [mode, setMode] = useState<string>('singleplayer');

return (
<div className="tldraw__editor">
<Editor
persistanceApiUrl={VITE_PERSISTANCE_API_URL}
assetsApiUrl={VITE_ASSETS_API_URL}
userInfoApiUrl={VITE_USER_INFO_API_URI}
/>
</div>
<>
<div>
<div>
Mode
<label>
<input
type="radio"
value="singleplayer"
checked={mode === 'singleplayer'}
onChange={() => setMode('singleplayer')}
/>
Singleplayer
</label>
<label>
<input
type="radio"
value="multiplayer"
checked={mode === 'multiplayer'}
onChange={() => setMode('multiplayer')}
/>
Multiplayer
</label>
</div>
</div>
<main>
<div className="app-container">
<div className="tldraw__editor">
{mode == 'singleplayer' && (
<SingleplayerEditor
persistanceApiUrl={VITE_PERSISTANCE_API_URL}
assetsApiUrl={VITE_ASSETS_API_URL}
userInfoApiUrl={VITE_USER_INFO_API_URI}
/>
)}
{mode == 'multiplayer' && (
<MultiplayerEditor
websocketApiUrl={VITE_WEBSOCKET_API_URL}
roomId={VITE_ROOM_ID}
userInfoApiUrl={VITE_USER_INFO_API_URI}
/>
)}
</div>
</div>
</main>
</>
);
}

Expand Down
69 changes: 40 additions & 29 deletions src/assets/scss/app.scss
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
html,
body {
body,
#root {
padding: 0;
margin: 0;
font-family:
-apple-system,
BlinkMacSystemFont,
Segoe UI,
Roboto,
Oxygen,
Ubuntu,
Cantarell,
Fira Sans,
Droid Sans,
Helvetica Neue,
sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
width: 100vw;
overflow: hidden;
}

* {
box-sizing: border-box;
}
main {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
overflow-y: auto;

.app-container {
border: 4px solid black;
height: 900px;
width: 1600px;

.tldraw__editor {
display: block;
position: relative;
width: 100%;
height: 100%;

.tldraw__editor {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
div.bottom-container {
position: absolute;
z-index: 2;
bottom: 0;
margin: 16px;
}

div.saving {
position: absolute;
z-index: 2;
bottom: 0;
margin: 16px;
@media only screen and (max-width: 600px) {
div.bottom-container {
margin-bottom: 74px;
}
}
}
}
}

* {
box-sizing: border-box;
}
12 changes: 10 additions & 2 deletions src/assets/scss/main.scss
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
tldraw-editor {
tldraw-editor,
tldraw-singleplayer,
tldraw-multiplayer {
display: block;
position: relative;
width: 100%;
height: 100%;

div.saving {
div.bottom-container {
position: absolute;
z-index: 2;
bottom: 0;
margin: 16px;
}

@media only screen and (max-width: 600px) {
div.bottom-container {
margin-bottom: 74px;
}
}
}

* {
Expand Down
19 changes: 15 additions & 4 deletions src/ce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import EditorSFC from './components/Editor.tsx';
import MultiplayerEditorSFC from './components/MultiplayerEditor.tsx';
import SingleplayerEditorSFC from './components/SingleplayerEditor.tsx';
import r2wc from '@r2wc/react-to-web-component';

const Editor = r2wc(EditorSFC, {
const SingleplayerEditor = r2wc(SingleplayerEditorSFC, {
props: {
persistanceApiUrl: 'string',
assetsApiUrl: 'string',
Expand All @@ -10,8 +11,18 @@ const Editor = r2wc(EditorSFC, {
},
});

const MultiplayerEditor = r2wc(MultiplayerEditorSFC, {
props: {
websocketApiUrl: 'string',
roomId: 'string',
userInfoApiUrl: 'string',
darkMode: 'boolean',
},
});

const register = (): void => {
customElements.define('tldraw-editor', Editor);
customElements.define('tldraw-singleplayer', SingleplayerEditor);
customElements.define('tldraw-multiplayer', MultiplayerEditor);
};

export { Editor, register };
export { SingleplayerEditor, MultiplayerEditor, register };
58 changes: 58 additions & 0 deletions src/components/Cursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { TLUser } from '@tldraw/core';
import { memo } from 'react';

export type CursorComponent<T = any> = (props: Pick<TLUser<T>, 'id' | 'color' | 'metadata'>) => any;

export const Cursor: CursorComponent = memo(({ color }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35" fill="none" fillRule="evenodd">
<g fill="rgba(0,0,0,.2)" transform="translate(1,1)">
<path d="m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z" />
<path d="m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z" />
</g>
<g fill="white">
<path d="m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z" />
<path d="m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z" />
</g>
<g fill={color}>
<path d="m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z" />
<path d="m13 10.814v11.188l2.969-2.866.428-.139h4.768z" />
</g>
</svg>
);
});

export const CustomCursor: CursorComponent = ({ color, metadata }) => {
return (
<div
style={{
display: 'flex',
width: 'fit-content',
alignItems: 'center',
gap: 8,
}}
>
<div
style={{
width: 12,
height: 12,
aspectRatio: 1,
background: color,
borderRadius: '100%',
}}
/>
{metadata?.name != undefined && (
<div
style={{
// whiteSpace: "nowrap",
background: 'white',
padding: '4px 8px',
borderRadius: 4,
}}
>
{metadata.name}
</div>
)}
</div>
);
};
70 changes: 70 additions & 0 deletions src/components/MultiplayerEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useMultiplayer } from '../hooks/useMultiplayer.ts';
import { setUserInfoApiUrl } from '../utils/soffitUtils.ts';
import { initProvider, newDoc } from '../utils/yjsUtils.ts';
import { CustomCursor } from './Cursor.tsx';
import { faUsers } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tldraw } from '@gip-recia/tldraw-v1';
import { useUsers } from 'y-presence';
import { WebsocketProvider } from 'y-websocket';

type MultiplayerEditorProps = {
websocketApiUrl: string;
roomId: string;
userInfoApiUrl: string;
darkMode?: boolean;
};

const components = {
Cursor: CustomCursor,
};

export default function MultiplayerEditor({
websocketApiUrl,
roomId,
userInfoApiUrl,
darkMode,
}: Readonly<MultiplayerEditorProps>) {
setUserInfoApiUrl(userInfoApiUrl);

const doc = newDoc();
const provider = initProvider(websocketApiUrl, roomId, doc);
const { ...events } = useMultiplayer(doc, provider, roomId);

const isOk: boolean = userInfoApiUrl.length > 0 && websocketApiUrl.length > 0 && roomId.length > 0;

return (
isOk && (
<>
<div className="bottom-container">
<UserCounter provider={provider} />
</div>
<Tldraw
autofocus
components={components}
showPages={false}
showMultiplayerMenu={false}
darkMode={darkMode}
hideNewReleaseLink
hideSocialLinks
hideSponsorLink
{...events}
/>
</>
)
);
}

type UserCounterProps = {
provider: WebsocketProvider;
};

function UserCounter({ provider }: Readonly<UserCounterProps>) {
const users = useUsers(provider.awareness);

return (
<>
<FontAwesomeIcon icon={faUsers} /> {users.size}
</>
);
}
Loading

0 comments on commit 72c4d3e

Please sign in to comment.