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

@ -1,5 +1,5 @@
import Navbar from "@/components/editor/navbar"
import { Room } from "@/components/editor/room"
import { Room } from "@/components/editor/live/room"
import { Sandbox, User, UsersToSandboxes } from "@/lib/types"
import { currentUser } from "@clerk/nextjs"
import dynamic from "next/dynamic"
@ -49,12 +49,12 @@ export default async function CodePage({ params }: { params: { id: string } }) {
return (
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
{/* <Room> */}
<Room id={sandboxId}>
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
<div className="w-screen flex grow">
<CodeEditor userData={userData} sandboxId={sandboxId} />
</div>
{/* </Room> */}
</Room>
</div>
)
}

View File

@ -23,7 +23,6 @@ export async function POST(request: NextRequest) {
// userInfo is made available in Liveblocks presence hooks, e.g. useOthers
const session = liveblocks.prepareSession(user.id, {
userInfo: {
id: user.id,
name: user.name,
email: user.email,
},

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,6 +504,8 @@ export default function CodeEditor({
</div>
</>
) : clerk.loaded ? (
<>
{provider ? <Cursors yProvider={provider} /> : null}
<Editor
height="100%"
language={editorLanguage}
@ -521,6 +533,7 @@ export default function CodeEditor({
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,
}}

View File

@ -69,8 +69,11 @@ type Storage = {
// provided by your own custom auth back end (if used). Useful for data that
// will not change during a session, like a user's name or avatar.
type UserMeta = {
// id?: string, // Accessible through `user.id`
// info?: Json, // Accessible through `user.info`
id: string
info: {
name: string
email: string
}
}
// Optionally, the type of custom events broadcast and listened to in this
@ -88,6 +91,12 @@ export type ThreadMetadata = {
// time: number;
}
export type UserAwareness = {
user?: UserMeta["info"]
}
export type AwarenessList = [number, UserAwareness][]
// Room-level hooks, use inside `RoomProvider`
export const {
suspense: {
@ -131,8 +140,8 @@ export const {
useUpdateRoomNotificationSettings,
// These hooks can be exported from either context
// useUser,
// useRoomInfo
useUser,
useRoomInfo,
},
} = createRoomContext<Presence, Storage, UserMeta, RoomEvent, ThreadMetadata>(
client
@ -146,10 +155,6 @@ export const {
useMarkAllInboxNotificationsAsRead,
useInboxNotifications,
useUnreadInboxNotificationsCount,
// These hooks can be exported from either context
useUser,
useRoomInfo,
},
} = createLiveblocksContext<UserMeta, ThreadMetadata>(client)