mostly done liveblocks integration

This commit is contained in:
Ishaan Dey
2024-05-03 14:27:45 -07:00
parent 0f1654e3dd
commit a18bcf9c14
7 changed files with 147 additions and 49 deletions

View File

@ -11,7 +11,12 @@ import * as Y from "yjs"
import LiveblocksProvider from "@liveblocks/yjs"
import { MonacoBinding } from "y-monaco"
import { Awareness } from "y-protocols/awareness"
import { useRoom } from "@/liveblocks.config"
import {
TypedLiveblocksProvider,
useRoom,
AwarenessList,
useSelf,
} from "@/liveblocks.config"
import {
ResizableHandle,
@ -36,6 +41,8 @@ import GenerateInput from "./generate"
import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
import { User } from "@/lib/types"
import { processFileType, validateName } from "@/lib/utils"
import { Cursors } from "./live/cursors"
import { Avatars } from "./live/avatars"
export default function CodeEditor({
userData,
@ -63,6 +70,7 @@ export default function CodeEditor({
instance: monaco.editor.IEditorDecorationsCollection | undefined
}>({ options: [], instance: undefined })
const [terminals, setTerminals] = useState<string[]>([])
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
const clerk = useClerk()
const room = useRoom()
@ -268,6 +276,7 @@ export default function CodeEditor({
const yDoc = new Y.Doc()
const yText = yDoc.getText("monaco")
const yProvider: any = new LiveblocksProvider(room, yDoc)
setProvider(yProvider)
const binding = new MonacoBinding(
yText,
@ -481,10 +490,11 @@ export default function CodeEditor({
{tab.name}
</Tab>
))}
<Avatars />
</div>
<div
ref={editorContainerRef}
className="grow w-full overflow-hidden rounded-md"
className="grow w-full overflow-hidden rounded-md relative"
>
{activeId === null ? (
<>
@ -494,33 +504,36 @@ export default function CodeEditor({
</div>
</>
) : clerk.loaded ? (
<Editor
height="100%"
language={editorLanguage}
beforeMount={handleEditorWillMount}
onMount={handleEditorMount}
onChange={(value) => {
setTabs((prev) =>
prev.map((tab) =>
tab.id === activeId ? { ...tab, saved: false } : tab
<>
{provider ? <Cursors yProvider={provider} /> : null}
<Editor
height="100%"
language={editorLanguage}
beforeMount={handleEditorWillMount}
onMount={handleEditorMount}
onChange={(value) => {
setTabs((prev) =>
prev.map((tab) =>
tab.id === activeId ? { ...tab, saved: false } : tab
)
)
)
}}
options={{
minimap: {
enabled: false,
},
padding: {
bottom: 4,
top: 4,
},
scrollBeyondLastLine: false,
fixedOverflowWidgets: true,
fontFamily: "var(--font-geist-mono)",
}}
theme="vs-dark"
value={activeFile ?? ""}
/>
}}
options={{
minimap: {
enabled: false,
},
padding: {
bottom: 4,
top: 4,
},
scrollBeyondLastLine: false,
fixedOverflowWidgets: true,
fontFamily: "var(--font-geist-mono)",
}}
theme="vs-dark"
value={activeFile ?? ""}
/>
</>
) : null}
</div>
</ResizablePanel>

View File

@ -0,0 +1,21 @@
import { useOthers, useSelf } from "@/liveblocks.config"
import Avatar from "@/components/ui/avatar"
export function Avatars() {
const users = useOthers()
const currentUser = useSelf()
return (
<div className="flex">
{users.map(({ connectionId, info }) => {
return <Avatar key={connectionId} name={info.name} />
})}
{currentUser && (
<div className="relative ml-8 first:ml-0">
<Avatar name={currentUser.info.name} />
</div>
)}
</div>
)
}

View File

@ -0,0 +1,57 @@
import { useEffect, useMemo, useState } from "react"
import {
AwarenessList,
TypedLiveblocksProvider,
UserAwareness,
useSelf,
} from "@/liveblocks.config"
export function Cursors({ yProvider }: { yProvider: TypedLiveblocksProvider }) {
// Get user info from Liveblocks authentication endpoint
const userInfo = useSelf((me) => me.info)
const [awarenessUsers, setAwarenessUsers] = useState<AwarenessList>([])
useEffect(() => {
// Add user info to Yjs awareness
const localUser: UserAwareness["user"] = userInfo
yProvider.awareness.setLocalStateField("user", localUser)
// On changes, update `awarenessUsers`
function setUsers() {
setAwarenessUsers(
Array.from(yProvider.awareness.getStates()) as AwarenessList
)
}
yProvider.awareness.on("change", setUsers)
setUsers()
return () => {
yProvider.awareness.off("change", setUsers)
}
}, [yProvider])
// Insert awareness info into cursors with styles
const styleSheet = useMemo(() => {
let cursorStyles = ""
for (const [clientId, client] of awarenessUsers) {
if (client?.user) {
cursorStyles += `
.yRemoteSelection-${clientId},
.yRemoteSelectionHead-${clientId} {
--user-color: ${"#FF0000"};
}
.yRemoteSelectionHead-${clientId}::after {
content: "${client.user.name}";
}
`
}
}
return { __html: cursorStyles }
}, [awarenessUsers])
return <style dangerouslySetInnerHTML={styleSheet} />
}

View File

@ -1,15 +1,18 @@
"use client"
import { RoomProvider } from "@/liveblocks.config"
import { useSearchParams } from "next/navigation"
import { ClientSideSuspense } from "@liveblocks/react"
export function Room({ children }: { children: React.ReactNode }) {
// const roomId = useExampleRoomId("liveblocks:examples:nextjs-yjs-monaco");
export function Room({
id,
children,
}: {
id: string
children: React.ReactNode
}) {
return (
<RoomProvider
id={"roomId"}
id={id}
initialPresence={{
cursor: null,
}}