shared user restrictions + cleanup console logs
This commit is contained in:
parent
9d288f580d
commit
12e8051673
@ -94,7 +94,6 @@ export default {
|
|||||||
const initStorageRes = await env.STORAGE.fetch(initStorageRequest);
|
const initStorageRes = await env.STORAGE.fetch(initStorageRequest);
|
||||||
|
|
||||||
const initStorage = await initStorageRes.text();
|
const initStorage = await initStorageRes.text();
|
||||||
console.log("initStorage: ", initStorage);
|
|
||||||
|
|
||||||
return new Response(sb.id, { status: 200 });
|
return new Response(sb.id, { status: 200 });
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,6 +39,7 @@ const io = new Server(httpServer, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let inactivityTimeout: NodeJS.Timeout | null = null;
|
let inactivityTimeout: NodeJS.Timeout | null = null;
|
||||||
|
let isOwnerConnected = false;
|
||||||
|
|
||||||
const terminals: {
|
const terminals: {
|
||||||
[id: string]: { terminal: IPty; onData: IDisposable; onExit: IDisposable }
|
[id: string]: { terminal: IPty; onData: IDisposable; onExit: IDisposable }
|
||||||
@ -58,7 +59,7 @@ io.use(async (socket, next) => {
|
|||||||
const parseQuery = handshakeSchema.safeParse(q)
|
const parseQuery = handshakeSchema.safeParse(q)
|
||||||
|
|
||||||
if (!parseQuery.success) {
|
if (!parseQuery.success) {
|
||||||
console.log("Invalid request.")
|
("Invalid request.")
|
||||||
next(new Error("Invalid request."))
|
next(new Error("Invalid request."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -68,7 +69,6 @@ io.use(async (socket, next) => {
|
|||||||
const dbUserJSON = (await dbUser.json()) as User
|
const dbUserJSON = (await dbUser.json()) as User
|
||||||
|
|
||||||
if (!dbUserJSON) {
|
if (!dbUserJSON) {
|
||||||
console.log("DB error.")
|
|
||||||
next(new Error("DB error."))
|
next(new Error("DB error."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -79,7 +79,6 @@ io.use(async (socket, next) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!sandbox && !sharedSandboxes) {
|
if (!sandbox && !sharedSandboxes) {
|
||||||
console.log("Invalid credentials.")
|
|
||||||
next(new Error("Invalid credentials."))
|
next(new Error("Invalid credentials."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -103,6 +102,15 @@ io.on("connection", async (socket) => {
|
|||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.isOwner) {
|
||||||
|
isOwnerConnected = true
|
||||||
|
} else {
|
||||||
|
if (!isOwnerConnected) {
|
||||||
|
socket.emit("disableAccess", "The sandbox owner is not connected.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const sandboxFiles = await getSandboxFiles(data.sandboxId)
|
const sandboxFiles = await getSandboxFiles(data.sandboxId)
|
||||||
sandboxFiles.fileData.forEach((file) => {
|
sandboxFiles.fileData.forEach((file) => {
|
||||||
const filePath = path.join(dirName, file.id)
|
const filePath = path.join(dirName, file.id)
|
||||||
@ -224,12 +232,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
socket.on("createTerminal", (id: string, callback) => {
|
socket.on("createTerminal", (id: string, callback) => {
|
||||||
console.log("creating terminal", id)
|
console.log("creating terminal", id)
|
||||||
if (terminals[id]) {
|
if (terminals[id] || Object.keys(terminals).length >= 4) {
|
||||||
console.log("Terminal already exists.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (Object.keys(terminals).length >= 4) {
|
|
||||||
console.log("Too many terminals.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,9 +243,7 @@ io.on("connection", async (socket) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const onData = pty.onData((data) => {
|
const onData = pty.onData((data) => {
|
||||||
console.log("ondata")
|
|
||||||
socket.emit("terminalResponse", {
|
socket.emit("terminalResponse", {
|
||||||
// data: Buffer.from(data, "utf-8").toString("base64"),
|
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
@ -263,7 +264,6 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
socket.on("terminalData", (id: string, data: string) => {
|
socket.on("terminalData", (id: string, data: string) => {
|
||||||
if (!terminals[id]) {
|
if (!terminals[id]) {
|
||||||
console.log("terminals", terminals)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +276,6 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
socket.on("closeTerminal", (id: string, callback) => {
|
socket.on("closeTerminal", (id: string, callback) => {
|
||||||
if (!terminals[id]) {
|
if (!terminals[id]) {
|
||||||
console.log("tried to close, but term does not exist. terminals", terminals)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,11 +331,7 @@ io.on("connection", async (socket) => {
|
|||||||
delete terminals[t[0]]
|
delete terminals[t[0]]
|
||||||
})
|
})
|
||||||
|
|
||||||
// console.log("The owner disconnected.")
|
socket.broadcast.emit("disableAccess", "The sandbox owner has disconnected.")
|
||||||
socket.broadcast.emit("ownerDisconnected")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// console.log("A shared user disconnected.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sockets = await io.fetchSockets()
|
const sockets = await io.fetchSockets()
|
||||||
|
@ -16,7 +16,6 @@ export const getSandboxFiles = async (id: string) => {
|
|||||||
|
|
||||||
const paths = sandboxData.objects.map((obj) => obj.key)
|
const paths = sandboxData.objects.map((obj) => obj.key)
|
||||||
const processedFiles = await processFiles(paths, id)
|
const processedFiles = await processFiles(paths, id)
|
||||||
// console.log("processedFiles.fileData:", processedFiles.fileData)
|
|
||||||
return processedFiles
|
return processedFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ const processFiles = async (paths: string[], id: string) => {
|
|||||||
paths.forEach((path) => {
|
paths.forEach((path) => {
|
||||||
const allParts = path.split("/")
|
const allParts = path.split("/")
|
||||||
if (allParts[1] !== id) {
|
if (allParts[1] !== id) {
|
||||||
console.log("invalid path!!!!")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import { User } from "@/lib/types"
|
import { User } from "@/lib/types";
|
||||||
import { currentUser } from "@clerk/nextjs"
|
import { currentUser } from "@clerk/nextjs";
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function AppAuthLayout({
|
export default async function AppAuthLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const user = await currentUser()
|
const user = await currentUser();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect("/")
|
redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUser = await fetch(
|
const dbUser = await fetch(
|
||||||
`https://database.ishaan1013.workers.dev/api/user?id=${user.id}`
|
`https://database.ishaan1013.workers.dev/api/user?id=${user.id}`
|
||||||
)
|
);
|
||||||
const dbUserJSON = (await dbUser.json()) as User
|
const dbUserJSON = (await dbUser.json()) as User;
|
||||||
|
|
||||||
if (!dbUserJSON.id) {
|
if (!dbUserJSON.id) {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
@ -32,12 +32,8 @@ export default async function AppAuthLayout({
|
|||||||
email: user.emailAddresses[0].emailAddress,
|
email: user.emailAddresses[0].emailAddress,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
console.log(res)
|
|
||||||
} else {
|
|
||||||
// user already exists in db
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Input } from "../../ui/input"
|
import { Input } from "../../ui/input";
|
||||||
import { Search } from "lucide-react"
|
import { Search } from "lucide-react";
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function DashboardNavbarSearch() {
|
export default function DashboardNavbarSearch() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("");
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delayDebounceFn = setTimeout(() => {
|
const delayDebounceFn = setTimeout(() => {
|
||||||
console.log("SEARCH", search)
|
|
||||||
if (search) {
|
if (search) {
|
||||||
router.push(`/dashboard?q=${search}`)
|
router.push(`/dashboard?q=${search}`);
|
||||||
} else {
|
} else {
|
||||||
router.push(`/dashboard`)
|
router.push(`/dashboard`);
|
||||||
}
|
}
|
||||||
}, 300)
|
}, 300);
|
||||||
|
|
||||||
return () => clearTimeout(delayDebounceFn)
|
return () => clearTimeout(delayDebounceFn);
|
||||||
}, [search])
|
}, [search]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-9 w-44 flex items-center justify-start">
|
<div className="relative h-9 w-44 flex items-center justify-start">
|
||||||
@ -32,5 +31,5 @@ export default function DashboardNavbarSearch() {
|
|||||||
className="pl-8"
|
className="pl-8"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,6 @@ export default function CodeEditor({
|
|||||||
const clerk = useClerk();
|
const clerk = useClerk();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const activeTerminal = terminals.find((t) => t.id === activeTerminalId);
|
const activeTerminal = terminals.find((t) => t.id === activeTerminalId);
|
||||||
console.log("activeTerminal", activeTerminal ? activeTerminal.id : "none");
|
|
||||||
|
|
||||||
// const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
// const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||||
const [editorRef, setEditorRef] =
|
const [editorRef, setEditorRef] =
|
||||||
@ -270,9 +269,6 @@ export default function CodeEditor({
|
|||||||
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// const activeTab = tabs.find((t) => t.id === activeFileId)
|
|
||||||
// console.log("saving:", activeTab?.name, editorRef?.getValue())
|
|
||||||
|
|
||||||
setTabs((prev) =>
|
setTabs((prev) =>
|
||||||
prev.map((tab) =>
|
prev.map((tab) =>
|
||||||
tab.id === activeFileId ? { ...tab, saved: true } : tab
|
tab.id === activeFileId ? { ...tab, saved: true } : tab
|
||||||
@ -367,7 +363,11 @@ export default function CodeEditor({
|
|||||||
createTerminal();
|
createTerminal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDisconnect = () => {};
|
const onDisconnect = () => {
|
||||||
|
console.log("disconnected");
|
||||||
|
|
||||||
|
closeAllTerminals();
|
||||||
|
};
|
||||||
|
|
||||||
const onLoadedEvent = (files: (TFolder | TFile)[]) => {
|
const onLoadedEvent = (files: (TFolder | TFile)[]) => {
|
||||||
setFiles(files);
|
setFiles(files);
|
||||||
@ -385,11 +385,19 @@ export default function CodeEditor({
|
|||||||
if (term && term.terminal) term.terminal.write(res);
|
if (term && term.terminal) term.terminal.write(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDisableAccess = (message: string) => {
|
||||||
|
setDisableAccess({
|
||||||
|
isDisabled: true,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
socket.on("connect", onConnect);
|
socket.on("connect", onConnect);
|
||||||
socket.on("disconnect", onDisconnect);
|
socket.on("disconnect", onDisconnect);
|
||||||
socket.on("loaded", onLoadedEvent);
|
socket.on("loaded", onLoadedEvent);
|
||||||
socket.on("rateLimit", onRateLimit);
|
socket.on("rateLimit", onRateLimit);
|
||||||
socket.on("terminalResponse", onTerminalResponse);
|
socket.on("terminalResponse", onTerminalResponse);
|
||||||
|
socket.on("disableAccess", onDisableAccess);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("connect", onConnect);
|
socket.off("connect", onConnect);
|
||||||
@ -397,6 +405,7 @@ export default function CodeEditor({
|
|||||||
socket.off("loaded", onLoadedEvent);
|
socket.off("loaded", onLoadedEvent);
|
||||||
socket.off("rateLimit", onRateLimit);
|
socket.off("rateLimit", onRateLimit);
|
||||||
socket.off("terminalResponse", onTerminalResponse);
|
socket.off("terminalResponse", onTerminalResponse);
|
||||||
|
socket.off("disableAccess", onDisableAccess);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -492,6 +501,13 @@ export default function CodeEditor({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeAllTerminals = () => {
|
||||||
|
terminals.forEach((term) => {
|
||||||
|
socket.emit("closeTerminal", term.id, () => {}); // no need to wait for response here
|
||||||
|
setTerminals((prev) => prev.filter((t) => t.id !== term.id));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleRename = (
|
const handleRename = (
|
||||||
id: string,
|
id: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
|
@ -9,7 +9,15 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
import { ChevronRight, FileStack, Globe, TextCursor } from "lucide-react";
|
import {
|
||||||
|
ChevronRight,
|
||||||
|
FileStack,
|
||||||
|
Globe,
|
||||||
|
Loader2,
|
||||||
|
TextCursor,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function DisableAccessModal({
|
export default function DisableAccessModal({
|
||||||
open,
|
open,
|
||||||
@ -20,13 +28,30 @@ export default function DisableAccessModal({
|
|||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
message: string;
|
message: string;
|
||||||
}) {
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
router.push("/dashboard");
|
||||||
|
}, 5000);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Live Collaboration Disabled</DialogTitle>
|
<DialogTitle>Live Collaboration Disabled</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="text-sm text-muted-foreground">{message}</div>
|
<div className="text-sm text-muted-foreground space-y-2">
|
||||||
|
<div>{message}</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||||
|
Redirecting you to dashboard...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -7,10 +7,10 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog";
|
||||||
import { z } from "zod"
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -20,41 +20,41 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form"
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select";
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react";
|
||||||
import { useState } from "react"
|
import { useState } from "react";
|
||||||
import { Sandbox } from "@/lib/types"
|
import { Sandbox } from "@/lib/types";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { deleteSandbox, updateSandbox } from "@/lib/actions"
|
import { deleteSandbox, updateSandbox } from "@/lib/actions";
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1).max(16),
|
name: z.string().min(1).max(16),
|
||||||
visibility: z.enum(["public", "private"]),
|
visibility: z.enum(["public", "private"]),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default function EditSandboxModal({
|
export default function EditSandboxModal({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
data,
|
data,
|
||||||
}: {
|
}: {
|
||||||
open: boolean
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void;
|
||||||
data: Sandbox
|
data: Sandbox;
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingDelete, setLoadingDelete] = useState(false)
|
const [loadingDelete, setLoadingDelete] = useState(false);
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@ -62,24 +62,22 @@ export default function EditSandboxModal({
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
visibility: data.visibility,
|
visibility: data.visibility,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
console.log(values)
|
setLoading(true);
|
||||||
|
await updateSandbox({ id: data.id, ...values });
|
||||||
|
|
||||||
setLoading(true)
|
toast.success("Sandbox updated successfully");
|
||||||
await updateSandbox({ id: data.id, ...values })
|
|
||||||
|
|
||||||
toast.success("Sandbox updated successfully")
|
setLoading(false);
|
||||||
|
|
||||||
setLoading(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDelete() {
|
async function onDelete() {
|
||||||
setLoadingDelete(true)
|
setLoadingDelete(true);
|
||||||
await deleteSandbox(data.id)
|
await deleteSandbox(data.id);
|
||||||
|
|
||||||
router.push("/dashboard")
|
router.push("/dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -155,5 +153,5 @@ export default function EditSandboxModal({
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image"
|
import Image from "next/image";
|
||||||
import { getIconForFile } from "vscode-icons-js"
|
import { getIconForFile } from "vscode-icons-js";
|
||||||
import { TFile, TTab } from "@/lib/types"
|
import { TFile, TTab } from "@/lib/types";
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from "@/components/ui/context-menu"
|
} from "@/components/ui/context-menu";
|
||||||
import { Loader2, Pencil, Trash2 } from "lucide-react"
|
import { Loader2, Pencil, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
export default function SidebarFile({
|
export default function SidebarFile({
|
||||||
data,
|
data,
|
||||||
@ -18,29 +18,26 @@ export default function SidebarFile({
|
|||||||
handleRename,
|
handleRename,
|
||||||
handleDeleteFile,
|
handleDeleteFile,
|
||||||
}: {
|
}: {
|
||||||
data: TFile
|
data: TFile;
|
||||||
selectFile: (file: TTab) => void
|
selectFile: (file: TTab) => void;
|
||||||
handleRename: (
|
handleRename: (
|
||||||
id: string,
|
id: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
oldName: string,
|
oldName: string,
|
||||||
type: "file" | "folder"
|
type: "file" | "folder"
|
||||||
) => boolean
|
) => boolean;
|
||||||
handleDeleteFile: (file: TFile) => void
|
handleDeleteFile: (file: TFile) => void;
|
||||||
}) {
|
}) {
|
||||||
const [imgSrc, setImgSrc] = useState(`/icons/${getIconForFile(data.name)}`)
|
const [imgSrc, setImgSrc] = useState(`/icons/${getIconForFile(data.name)}`);
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [pendingDelete, setPendingDelete] = useState(false)
|
const [pendingDelete, setPendingDelete] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
setTimeout(() => inputRef.current?.focus(), 0)
|
setTimeout(() => inputRef.current?.focus(), 0);
|
||||||
}
|
}
|
||||||
if (!inputRef.current) {
|
}, [editing, inputRef.current]);
|
||||||
console.log("no input ref")
|
|
||||||
}
|
|
||||||
}, [editing, inputRef.current])
|
|
||||||
|
|
||||||
const renameFile = () => {
|
const renameFile = () => {
|
||||||
const renamed = handleRename(
|
const renamed = handleRename(
|
||||||
@ -48,19 +45,19 @@ export default function SidebarFile({
|
|||||||
inputRef.current?.value ?? data.name,
|
inputRef.current?.value ?? data.name,
|
||||||
data.name,
|
data.name,
|
||||||
"file"
|
"file"
|
||||||
)
|
);
|
||||||
if (!renamed && inputRef.current) {
|
if (!renamed && inputRef.current) {
|
||||||
inputRef.current.value = data.name
|
inputRef.current.value = data.name;
|
||||||
}
|
}
|
||||||
setEditing(false)
|
setEditing(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger
|
<ContextMenuTrigger
|
||||||
disabled={pendingDelete}
|
disabled={pendingDelete}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!editing && !pendingDelete) selectFile({ ...data, saved: true })
|
if (!editing && !pendingDelete) selectFile({ ...data, saved: true });
|
||||||
}}
|
}}
|
||||||
// onDoubleClick={() => {
|
// onDoubleClick={() => {
|
||||||
// setEditing(true)
|
// setEditing(true)
|
||||||
@ -83,8 +80,8 @@ export default function SidebarFile({
|
|||||||
) : (
|
) : (
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
renameFile()
|
renameFile();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@ -102,8 +99,8 @@ export default function SidebarFile({
|
|||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("rename")
|
console.log("rename");
|
||||||
setEditing(true)
|
setEditing(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pencil className="w-4 h-4 mr-2" />
|
<Pencil className="w-4 h-4 mr-2" />
|
||||||
@ -112,9 +109,9 @@ export default function SidebarFile({
|
|||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
disabled={pendingDelete}
|
disabled={pendingDelete}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("delete")
|
console.log("delete");
|
||||||
setPendingDelete(true)
|
setPendingDelete(true);
|
||||||
handleDeleteFile(data)
|
handleDeleteFile(data);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
@ -122,5 +119,5 @@ export default function SidebarFile({
|
|||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image"
|
import Image from "next/image";
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js"
|
import { getIconForFolder, getIconForOpenFolder } from "vscode-icons-js";
|
||||||
import { TFile, TFolder, TTab } from "@/lib/types"
|
import { TFile, TFolder, TTab } from "@/lib/types";
|
||||||
import SidebarFile from "./file"
|
import SidebarFile from "./file";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from "@/components/ui/context-menu"
|
} from "@/components/ui/context-menu";
|
||||||
import { Pencil, Trash2 } from "lucide-react"
|
import { Pencil, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
export default function SidebarFolder({
|
export default function SidebarFolder({
|
||||||
data,
|
data,
|
||||||
@ -20,30 +20,30 @@ export default function SidebarFolder({
|
|||||||
handleDeleteFile,
|
handleDeleteFile,
|
||||||
handleDeleteFolder,
|
handleDeleteFolder,
|
||||||
}: {
|
}: {
|
||||||
data: TFolder
|
data: TFolder;
|
||||||
selectFile: (file: TTab) => void
|
selectFile: (file: TTab) => void;
|
||||||
handleRename: (
|
handleRename: (
|
||||||
id: string,
|
id: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
oldName: string,
|
oldName: string,
|
||||||
type: "file" | "folder"
|
type: "file" | "folder"
|
||||||
) => boolean
|
) => boolean;
|
||||||
handleDeleteFile: (file: TFile) => void
|
handleDeleteFile: (file: TFile) => void;
|
||||||
handleDeleteFolder: (folder: TFolder) => void
|
handleDeleteFolder: (folder: TFolder) => void;
|
||||||
}) {
|
}) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const folder = isOpen
|
const folder = isOpen
|
||||||
? getIconForOpenFolder(data.name)
|
? getIconForOpenFolder(data.name)
|
||||||
: getIconForFolder(data.name)
|
: getIconForFolder(data.name);
|
||||||
|
|
||||||
const [editing, setEditing] = useState(false)
|
const [editing, setEditing] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus();
|
||||||
}
|
}
|
||||||
}, [editing])
|
}, [editing]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
@ -60,9 +60,8 @@ export default function SidebarFolder({
|
|||||||
/>
|
/>
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
console.log("file renamed")
|
setEditing(false);
|
||||||
setEditing(false)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@ -73,8 +72,7 @@ export default function SidebarFolder({
|
|||||||
disabled={!editing}
|
disabled={!editing}
|
||||||
defaultValue={data.name}
|
defaultValue={data.name}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
console.log("file renamed")
|
setEditing(false);
|
||||||
setEditing(false)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
@ -82,8 +80,7 @@ export default function SidebarFolder({
|
|||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("rename")
|
setEditing(true);
|
||||||
setEditing(true)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pencil className="w-4 h-4 mr-2" />
|
<Pencil className="w-4 h-4 mr-2" />
|
||||||
@ -92,7 +89,7 @@ export default function SidebarFolder({
|
|||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
// disabled={pendingDelete}
|
// disabled={pendingDelete}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("delete")
|
console.log("delete");
|
||||||
// setPendingDelete(true)
|
// setPendingDelete(true)
|
||||||
// handleDeleteFile(data)
|
// handleDeleteFile(data)
|
||||||
}}
|
}}
|
||||||
@ -129,5 +126,5 @@ export default function SidebarFolder({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { FilePlus, FolderPlus, Loader2, Search, Sparkles } from "lucide-react"
|
import { FilePlus, FolderPlus, Loader2, Search, Sparkles } from "lucide-react";
|
||||||
import SidebarFile from "./file"
|
import SidebarFile from "./file";
|
||||||
import SidebarFolder from "./folder"
|
import SidebarFolder from "./folder";
|
||||||
import { TFile, TFolder, TTab } from "@/lib/types"
|
import { TFile, TFolder, TTab } from "@/lib/types";
|
||||||
import { useState } from "react"
|
import { useState } from "react";
|
||||||
import New from "./new"
|
import New from "./new";
|
||||||
import { Socket } from "socket.io-client"
|
import { Socket } from "socket.io-client";
|
||||||
import Button from "@/components/ui/customButton"
|
import Button from "@/components/ui/customButton";
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
export default function Sidebar({
|
export default function Sidebar({
|
||||||
files,
|
files,
|
||||||
@ -21,22 +21,24 @@ export default function Sidebar({
|
|||||||
ai,
|
ai,
|
||||||
setAi,
|
setAi,
|
||||||
}: {
|
}: {
|
||||||
files: (TFile | TFolder)[]
|
files: (TFile | TFolder)[];
|
||||||
selectFile: (tab: TTab) => void
|
selectFile: (tab: TTab) => void;
|
||||||
handleRename: (
|
handleRename: (
|
||||||
id: string,
|
id: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
oldName: string,
|
oldName: string,
|
||||||
type: "file" | "folder"
|
type: "file" | "folder"
|
||||||
) => boolean
|
) => boolean;
|
||||||
handleDeleteFile: (file: TFile) => void
|
handleDeleteFile: (file: TFile) => void;
|
||||||
handleDeleteFolder: (folder: TFolder) => void
|
handleDeleteFolder: (folder: TFolder) => void;
|
||||||
socket: Socket
|
socket: Socket;
|
||||||
addNew: (name: string, type: "file" | "folder") => void
|
addNew: (name: string, type: "file" | "folder") => void;
|
||||||
ai: boolean
|
ai: boolean;
|
||||||
setAi: React.Dispatch<React.SetStateAction<boolean>>
|
setAi: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}) {
|
}) {
|
||||||
const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>(null)
|
const [creatingNew, setCreatingNew] = useState<"file" | "folder" | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-56 select-none flex flex-col text-sm items-start justify-between p-2">
|
<div className="h-full w-56 select-none flex flex-col text-sm items-start justify-between p-2">
|
||||||
@ -94,8 +96,7 @@ export default function Sidebar({
|
|||||||
socket={socket}
|
socket={socket}
|
||||||
type={creatingNew}
|
type={creatingNew}
|
||||||
stopEditing={() => {
|
stopEditing={() => {
|
||||||
console.log("stopped editing")
|
setCreatingNew(null);
|
||||||
setCreatingNew(null)
|
|
||||||
}}
|
}}
|
||||||
addNew={addNew}
|
addNew={addNew}
|
||||||
/>
|
/>
|
||||||
@ -119,5 +120,5 @@ export default function Sidebar({
|
|||||||
<Switch checked={ai} onCheckedChange={setAi} />
|
<Switch checked={ai} onCheckedChange={setAi} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@ export default function EditorTerminal({
|
|||||||
setTerm(term);
|
setTerm(term);
|
||||||
}
|
}
|
||||||
const disposable = term.onData((data) => {
|
const disposable = term.onData((data) => {
|
||||||
console.log("sending data", data);
|
|
||||||
socket.emit("terminalData", id, data);
|
socket.emit("terminalData", id, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user