diff --git a/package.json b/package.json index 1c16402..bda9c13 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "typeCheck": "tsc --noEmit" }, "dependencies": { + "@blocksuite/sync": "^0.16.0", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", "@fn-sphere/filter": "^0.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef9f2b1..3873045 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@blocksuite/sync': + specifier: ^0.16.0 + version: 0.16.0(yjs@13.6.18) '@emotion/react': specifier: ^11.13.0 version: 11.13.0(@types/react@18.3.3)(react@18.3.1) @@ -258,6 +261,14 @@ packages: resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==} engines: {node: '>=6.9.0'} + '@blocksuite/global@0.16.0': + resolution: {integrity: sha512-e42EJYB4ILIioAiNoh88w42iIvRMI8APLXEaiqbrfs8QYalFCdWtPvJogzfbOHCYRMXAPB+jm1J7NBEk7QGgSQ==} + + '@blocksuite/sync@0.16.0': + resolution: {integrity: sha512-lcEL8ZrpcQ6CVEUOiCtC2tWujcPaJDMXBfsUf9YqTahGR8T+SSFnwHEk1GbOXkWA3b/N4F/6Kq0abNGaSoTWng==} + peerDependencies: + yjs: ^13.6.15 + '@emotion/babel-plugin@11.12.0': resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} @@ -1620,6 +1631,12 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + idb-keyval@6.2.1: + resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} + + idb@8.0.0: + resolution: {integrity: sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1787,6 +1804,11 @@ packages: engines: {node: '>=16'} hasBin: true + lib0@0.2.97: + resolution: {integrity: sha512-Q4d1ekgvufi9FiHkkL46AhecfNjznSL9MRNoJRQ76gBHS9OqU2ArfQK0FvBpuxgWeJeNI0LVgAYMIpsGeX4gYg==} + engines: {node: '>=16'} + hasBin: true + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2676,6 +2698,19 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@blocksuite/global@0.16.0': + dependencies: + lib0: 0.2.97 + zod: 3.23.8 + + '@blocksuite/sync@0.16.0(yjs@13.6.18)': + dependencies: + '@blocksuite/global': 0.16.0 + idb: 8.0.0 + idb-keyval: 6.2.1 + y-protocols: 1.0.6(yjs@13.6.18) + yjs: 13.6.18 + '@emotion/babel-plugin@11.12.0': dependencies: '@babel/helper-module-imports': 7.24.7 @@ -4074,6 +4109,10 @@ snapshots: dependencies: react-is: 16.13.1 + idb-keyval@6.2.1: {} + + idb@8.0.0: {} + ieee754@1.2.1: optional: true @@ -4236,6 +4275,10 @@ snapshots: dependencies: isomorphic.js: 0.2.5 + lib0@0.2.97: + dependencies: + isomorphic.js: 0.2.5 + lilconfig@2.1.0: {} lilconfig@3.1.2: {} diff --git a/src/components/connect-button.tsx b/src/components/connect-button.tsx index d2e5344..f96315c 100644 --- a/src/components/connect-button.tsx +++ b/src/components/connect-button.tsx @@ -1,16 +1,15 @@ import { Cable, RotateCw, Unplug } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { WebsocketProvider } from "y-websocket"; -import * as Y from "yjs"; +import { ConnectProvider } from "../providers/types"; import { useYDoc } from "../state"; import { ConnectDialog } from "./connect-dialog"; import { Button } from "./ui/button"; import { Dialog } from "./ui/dialog"; export function ConnectButton() { - const [yDoc, setYDoc] = useYDoc(); + const [yDoc] = useYDoc(); const [open, setOpen] = useState(false); - const [provider, setProvider] = useState(); + const [provider, setProvider] = useState(); const [connectState, setConnectState] = useState< "connecting" | "connected" | "disconnected" >("disconnected"); @@ -18,12 +17,11 @@ export function ConnectButton() { const disconnect = useCallback(() => { if (connectState === "disconnected") return; provider?.disconnect(); - provider?.destroy(); setProvider(undefined); setConnectState("disconnected"); }, [connectState, provider]); - // This effect is for convenience, it is evil. + // This effect is for convenience, it is evil. We should add the connect logic to global state and handle it there. useEffect(() => { // Disconnect when the yDoc changes if (connectState === "disconnected") return; @@ -41,28 +39,19 @@ export function ConnectButton() { }, [yDoc, disconnect, provider, connectState]); const onConnect = useCallback( - ({ doc, url, room }: { doc: Y.Doc; url: string; room: string }) => { + (provider: ConnectProvider) => { if (connectState !== "disconnected") { throw new Error("Should not be able to connect when already connected"); } - provider?.disconnect(); - const wsProvider = new WebsocketProvider(url, room, doc); - wsProvider.on("sync", (isSynced: boolean) => { - if (isSynced) { - setConnectState("connected"); - } - }); - // wsProvider.on( - // "status", - // ({ status }: { status: "connected" | "disconnected" | string }) => {}, - // ); - wsProvider.connect(); + provider.connect(); setConnectState("connecting"); - setYDoc(doc); - setProvider(wsProvider); + provider.waitForSynced().then(() => { + setConnectState("connected"); + }); + setProvider(provider); setOpen(false); }, - [connectState, provider, setYDoc], + [connectState], ); const handleClick = () => { diff --git a/src/components/connect-dialog.tsx b/src/components/connect-dialog.tsx index cb0545f..59fceae 100644 --- a/src/components/connect-dialog.tsx +++ b/src/components/connect-dialog.tsx @@ -1,6 +1,9 @@ +import { BlocksuiteWebsocketProvider } from "@/providers/blocksuite/provider"; +import { WebSocketConnectProvider } from "@/providers/websocket"; import { RocketIcon } from "lucide-react"; import { useState } from "react"; import * as Y from "yjs"; +import { ConnectProvider } from "../providers/types"; import { useYDoc } from "../state"; import { Alert, AlertDescription, AlertTitle } from "./ui/alert"; import { Button } from "./ui/button"; @@ -24,50 +27,69 @@ import { } from "./ui/select"; import { Switch } from "./ui/switch"; +// Hardcoded in the playground of blocksuite +// See https://github.com/toeverything/blocksuite/blob/9203e1c39651e40d33b1d724ef0261bdcabf6ca8/packages/playground/apps/default/utils/collection.ts#L65 +const BLOCKSUITE_PLAYGROUND_DOC_GUID = "quickEdgeless"; +const BLOCKSUITE_NAME = "Blocksuite Playground"; + const officialDemos = [ { name: "ProseMirror", room: "prosemirror-demo-2024/06", - url: "https://demos.yjs.dev/prosemirror/prosemirror.html", + url: "wss://demos.yjs.dev/ws", + demoUrl: "https://demos.yjs.dev/prosemirror/prosemirror.html", }, { name: "ProseMirror with Version History", room: "prosemirror-versions-demo-2024/06", - url: "https://demos.yjs.dev/prosemirror-versions/prosemirror-versions.html", + url: "wss://demos.yjs.dev/ws", + demoUrl: + "https://demos.yjs.dev/prosemirror-versions/prosemirror-versions.html", }, { name: "Quill", room: "quill-demo-2024/06", - url: "https://demos.yjs.dev/quill/quill.html", + url: "wss://demos.yjs.dev/ws", + demoUrl: "https://demos.yjs.dev/quill/quill.html", }, { name: "Monaco", room: "monaco-demo-2024/06", - url: "https://demos.yjs.dev/monaco/monaco.html", + url: "wss://demos.yjs.dev/ws", + demoUrl: "https://demos.yjs.dev/monaco/monaco.html", }, { name: "CodeMirror", room: "codemirror-demo-2024/06", - url: "https://demos.yjs.dev/codemirror/codemirror.html", + url: "wss://demos.yjs.dev/ws", + demoUrl: "https://demos.yjs.dev/codemirror/codemirror.html", }, { name: "CodeMirror 6", room: "codemirror.next-demo-2024/06", - url: "https://demos.yjs.dev/codemirror.next/codemirror.next.html", + url: "wss://demos.yjs.dev/ws", + demoUrl: "https://demos.yjs.dev/codemirror.next/codemirror.next.html", + }, + { + name: BLOCKSUITE_NAME, + room: "", + url: "wss://blocksuite-playground.toeverything.workers.dev", + demoUrl: "https://try-blocksuite.vercel.app", + custom: true, }, -] as const; +]; export function ConnectDialog({ onConnect, }: { - onConnect: (data: { doc: Y.Doc; url: string; room: string }) => void; + onConnect: (provider: ConnectProvider) => void; }) { - const [yDoc] = useYDoc(); + const [yDoc, setYDoc] = useYDoc(); const [url, setUrl] = useState("wss://demos.yjs.dev/ws"); const [room, setRoom] = useState("quill-demo-2024/06"); - const [provider, setProvider] = useState("quill-demo-2024/06"); + const [provider, setProvider] = useState("Quill"); const [needCreateNewDoc, setNeedCreateNewDoc] = useState(true); - const officialDemo = officialDemos.find((demo) => demo.room === provider); + const officialDemo = officialDemos.find((demo) => demo.name === provider); return ( @@ -87,9 +109,9 @@ export function ConnectDialog({ value={provider} onValueChange={(value) => { setProvider(value); - const demo = officialDemos.find((demo) => demo.room === provider); + const demo = officialDemos.find((demo) => demo.name === value); if (demo) { - setUrl("wss://demos.yjs.dev/ws"); + setUrl(demo.url); setRoom(demo.room); return; } @@ -101,11 +123,16 @@ export function ConnectDialog({ Official Demos - {officialDemos.map((demo) => ( - - {demo.name} - - ))} + { + // Ad-hoc remove the blocksuite playground from the official demos + officialDemos + .filter((i) => i.name !== BLOCKSUITE_NAME) + .map((demo) => ( + + {demo.name} + + )) + } @@ -114,8 +141,8 @@ export function ConnectDialog({ y-webrtc (coming soon) - - Blocksuite Playground + + {BLOCKSUITE_NAME} LiveblocksProvider (coming soon) @@ -146,10 +173,10 @@ export function ConnectDialog({ setRoom(e.currentTarget.value)} - placeholder="room-name" + placeholder="Please enter a room name" /> @@ -173,7 +200,7 @@ export function ConnectDialog({ Click here to access the  @@ -186,12 +213,30 @@ export function ConnectDialog({