Merge pull request #11 from Code-Victor/feat/light-theme

Feat/light theme
This commit is contained in:
James Murdza 2024-10-24 12:11:00 -06:00 committed by GitHub
commit 3b93090d97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 186 additions and 34 deletions

View File

@ -224,10 +224,11 @@ io.on("connection", async (socket) => {
containers[data.sandboxId], containers[data.sandboxId],
sendLoadedEvent sendLoadedEvent
) )
await fileManagers[data.sandboxId].initialize()
terminalManagers[data.sandboxId] = new TerminalManager( terminalManagers[data.sandboxId] = new TerminalManager(
containers[data.sandboxId] containers[data.sandboxId]
) )
console.log(`terminal manager set up for ${data.sandboxId}`)
await fileManagers[data.sandboxId].initialize()
} }
const fileManager = fileManagers[data.sandboxId] const fileManager = fileManagers[data.sandboxId]
@ -415,6 +416,12 @@ io.on("connection", async (socket) => {
socket.on("createTerminal", async (id: string, callback) => { socket.on("createTerminal", async (id: string, callback) => {
try { try {
await lockManager.acquireLock(data.sandboxId, async () => { await lockManager.acquireLock(data.sandboxId, async () => {
let terminalManager = terminalManagers[data.sandboxId]
if (!terminalManager) {
terminalManager = terminalManagers[data.sandboxId] =
new TerminalManager(containers[data.sandboxId])
}
await terminalManager.createTerminal(id, (responseString: string) => { await terminalManager.createTerminal(id, (responseString: string) => {
socket.emit("terminalResponse", { id, data: responseString }) socket.emit("terminalResponse", { id, data: responseString })
const port = extractPortNumber(responseString) const port = extractPortNumber(responseString)

View File

@ -99,6 +99,29 @@
); /* violet 900 -> bg */ ); /* violet 900 -> bg */
} }
.light .gradient-button-bg {
background: radial-gradient(
circle at top,
#262626 0%,
#f5f5f5 50%
); /* Dark gray -> Light gray */
}
.light .gradient-button {
background: radial-gradient(
circle at bottom,
hsl(0, 10%, 25%) -10%,
#9d9d9d 50%
); /* Light gray -> Almost white */
}
.light .gradient-button-bg > div:hover {
background: radial-gradient(
circle at bottom,
hsl(0, 10%, 25%) -10%,
#9d9d9d 80%
); /* Light gray -> Almost white */
}
.inline-decoration::before { .inline-decoration::before {
content: "Generate"; content: "Generate";
color: #525252; color: #525252;

View File

@ -1,7 +1,7 @@
import { ThemeProvider } from "@/components/layout/themeProvider"
import { Toaster } from "@/components/ui/sonner" import { Toaster } from "@/components/ui/sonner"
import { ThemeProvider } from "@/components/ui/theme-provider"
import { PreviewProvider } from "@/context/PreviewContext" import { PreviewProvider } from "@/context/PreviewContext"
import { SocketProvider } from "@/context/SocketContext" import { SocketProvider } from '@/context/SocketContext'
import { ClerkProvider } from "@clerk/nextjs" import { ClerkProvider } from "@clerk/nextjs"
import { Analytics } from "@vercel/analytics/react" import { Analytics } from "@vercel/analytics/react"
import { GeistMono } from "geist/font/mono" import { GeistMono } from "geist/font/mono"
@ -25,8 +25,7 @@ export default function RootLayout({
<body> <body>
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="dark" defaultTheme="system"
forcedTheme="dark"
disableTransitionOnChange disableTransitionOnChange
> >
<SocketProvider> <SocketProvider>

View File

@ -1,4 +1,5 @@
import Logo from "@/assets/logo.svg" import Logo from "@/assets/logo.svg"
import { ThemeSwitcher } from "@/components/ui/theme-switcher"
import { User } from "@/lib/types" import { User } from "@/lib/types"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
@ -19,6 +20,7 @@ export default function DashboardNavbar({ userData }: { userData: User }) {
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<DashboardNavbarSearch /> <DashboardNavbarSearch />
<ThemeSwitcher />
<UserButton userData={userData} /> <UserButton userData={userData} />
</div> </div>
</div> </div>

View File

@ -288,7 +288,7 @@ function SearchInput({
<form {...{ onSubmit }} className="w-40 h-8 "> <form {...{ onSubmit }} className="w-40 h-8 ">
<label <label
htmlFor="template-search" htmlFor="template-search"
className="flex gap-2 rounded-sm transition-colors bg-[#2e2e2e] border border-[--s-color] [--s-color:hsl(var(--muted-foreground))] focus-within:[--s-color:#fff] h-full items-center px-2" className="flex gap-2 rounded-sm transition-colors bg-gray-100 dark:bg-[#2e2e2e] border border-[--s-color] [--s-color:hsl(var(--muted-foreground))] focus-within:[--s-color:hsl(var(--muted-foreground),50%)] h-full items-center px-2"
> >
<Search className="size-4 text-[--s-color] transition-colors" /> <Search className="size-4 text-[--s-color] transition-colors" />
<input <input
@ -298,7 +298,7 @@ function SearchInput({
placeholder="Search templates" placeholder="Search templates"
value={value} value={value}
onChange={(e) => onValueChange?.(e.target.value)} onChange={(e) => onValueChange?.(e.target.value)}
className="bg-transparent placeholder:text-muted-foreground text-white w-full focus:outline-none text-xs" className="bg-transparent placeholder:text-muted-foreground w-full focus:outline-none text-xs"
/> />
</label> </label>
</form> </form>

View File

@ -39,6 +39,7 @@ import {
Sparkles, Sparkles,
TerminalSquare, TerminalSquare,
} from "lucide-react" } from "lucide-react"
import { useTheme } from "next-themes"
import React from "react" import React from "react"
import { ImperativePanelHandle } from "react-resizable-panels" import { ImperativePanelHandle } from "react-resizable-panels"
import { Button } from "../ui/button" import { Button } from "../ui/button"
@ -61,7 +62,8 @@ export default function CodeEditor({
}) { }) {
//SocketContext functions and effects //SocketContext functions and effects
const { socket, setUserAndSandboxId } = useSocket() const { socket, setUserAndSandboxId } = useSocket()
// theme
const { theme } = useTheme()
useEffect(() => { useEffect(() => {
// Ensure userData.id and sandboxData.id are available before attempting to connect // Ensure userData.id and sandboxData.id are available before attempting to connect
if (userData.id && sandboxData.id) { if (userData.id && sandboxData.id) {
@ -105,6 +107,7 @@ export default function CodeEditor({
// Editor state // Editor state
const [editorLanguage, setEditorLanguage] = useState("plaintext") const [editorLanguage, setEditorLanguage] = useState("plaintext")
console.log("editor language: ",editorLanguage)
const [cursorLine, setCursorLine] = useState(0) const [cursorLine, setCursorLine] = useState(0)
const [editorRef, setEditorRef] = const [editorRef, setEditorRef] =
useState<monaco.editor.IStandaloneCodeEditor>() useState<monaco.editor.IStandaloneCodeEditor>()
@ -1118,7 +1121,7 @@ export default function CodeEditor({
fixedOverflowWidgets: true, fixedOverflowWidgets: true,
fontFamily: "var(--font-geist-mono)", fontFamily: "var(--font-geist-mono)",
}} }}
theme="vs-dark" theme={theme === "light" ? "vs" : "vs-dark"}
value={activeFileContent} value={activeFileContent}
/> />
</> </>
@ -1148,7 +1151,7 @@ export default function CodeEditor({
defaultSize={isPreviewCollapsed ? 4 : 20} defaultSize={isPreviewCollapsed ? 4 : 20}
minSize={25} minSize={25}
collapsedSize={isHorizontalLayout ? 20 : 4} collapsedSize={isHorizontalLayout ? 20 : 4}
className="p-2 flex flex-col" className="p-2 flex flex-col gap-2"
collapsible collapsible
onCollapse={() => setIsPreviewCollapsed(true)} onCollapse={() => setIsPreviewCollapsed(true)}
onExpand={() => setIsPreviewCollapsed(false)} onExpand={() => setIsPreviewCollapsed(false)}

View File

@ -2,6 +2,7 @@
import Logo from "@/assets/logo.svg" import Logo from "@/assets/logo.svg"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { ThemeSwitcher } from "@/components/ui/theme-switcher"
import UserButton from "@/components/ui/userButton" import UserButton from "@/components/ui/userButton"
import { Sandbox, User } from "@/lib/types" import { Sandbox, User } from "@/lib/types"
import { Pencil, Users } from "lucide-react" import { Pencil, Users } from "lucide-react"
@ -79,6 +80,7 @@ export default function Navbar({
</Button> </Button>
</> </>
) : null} ) : null}
<ThemeSwitcher />
<UserButton userData={userData} /> <UserButton userData={userData} />
</div> </div>
</div> </div>

View File

@ -6,9 +6,9 @@ import "./xterm.css"
import { debounce } from "@/lib/utils" import { debounce } from "@/lib/utils"
import { Loader2 } from "lucide-react" import { Loader2 } from "lucide-react"
import { useTheme } from "next-themes"
import { ElementRef, useEffect, useRef } from "react" import { ElementRef, useEffect, useRef } from "react"
import { Socket } from "socket.io-client" import { Socket } from "socket.io-client"
export default function EditorTerminal({ export default function EditorTerminal({
socket, socket,
id, id,
@ -22,18 +22,17 @@ export default function EditorTerminal({
setTerm: (term: Terminal) => void setTerm: (term: Terminal) => void
visible: boolean visible: boolean
}) { }) {
const terminalRef = useRef<ElementRef<"div">>(null) const { theme } = useTheme()
const terminalContainerRef = useRef<ElementRef<"div">>(null)
const fitAddonRef = useRef<FitAddon | null>(null) const fitAddonRef = useRef<FitAddon | null>(null)
useEffect(() => { useEffect(() => {
if (!terminalRef.current) return if (!terminalContainerRef.current) return
// console.log("new terminal", id, term ? "reusing" : "creating"); // console.log("new terminal", id, term ? "reusing" : "creating");
const terminal = new Terminal({ const terminal = new Terminal({
cursorBlink: true, cursorBlink: true,
theme: { theme: theme === "light" ? lightTheme : darkTheme,
background: "#262626",
},
fontFamily: "var(--font-geist-mono)", fontFamily: "var(--font-geist-mono)",
fontSize: 14, fontSize: 14,
lineHeight: 1.5, lineHeight: 1.5,
@ -47,14 +46,20 @@ export default function EditorTerminal({
return dispose return dispose
}, []) }, [])
useEffect(() => {
if (term) {
term.options.theme = theme === "light" ? lightTheme : darkTheme
}
}, [theme])
useEffect(() => { useEffect(() => {
if (!term) return if (!term) return
if (!terminalRef.current) return if (!terminalContainerRef.current) return
if (!fitAddonRef.current) { if (!fitAddonRef.current) {
const fitAddon = new FitAddon() const fitAddon = new FitAddon()
term.loadAddon(fitAddon) term.loadAddon(fitAddon)
term.open(terminalRef.current) term.open(terminalContainerRef.current)
fitAddon.fit() fitAddon.fit()
fitAddonRef.current = fitAddon fitAddonRef.current = fitAddon
} }
@ -69,7 +74,7 @@ export default function EditorTerminal({
}) })
const resizeObserver = new ResizeObserver( const resizeObserver = new ResizeObserver(
debounce((entries) => { debounce((entries) => {
if (!fitAddonRef.current || !terminalRef.current) return if (!fitAddonRef.current || !terminalContainerRef.current) return
const entry = entries[0] const entry = entries[0]
if (!entry) return if (!entry) return
@ -78,8 +83,8 @@ export default function EditorTerminal({
// Only call fit if the size has actually changed // Only call fit if the size has actually changed
if ( if (
width !== terminalRef.current.offsetWidth || width !== terminalContainerRef.current.offsetWidth ||
height !== terminalRef.current.offsetHeight height !== terminalContainerRef.current.offsetHeight
) { ) {
try { try {
fitAddonRef.current.fit() fitAddonRef.current.fit()
@ -91,13 +96,13 @@ export default function EditorTerminal({
) )
// start observing for resize // start observing for resize
resizeObserver.observe(terminalRef.current) resizeObserver.observe(terminalContainerRef.current)
return () => { return () => {
disposableOnData.dispose() disposableOnData.dispose()
disposableOnResize.dispose() disposableOnResize.dispose()
resizeObserver.disconnect() resizeObserver.disconnect()
} }
}, [term, terminalRef.current]) }, [term, terminalContainerRef.current])
useEffect(() => { useEffect(() => {
if (!term) return if (!term) return
@ -116,7 +121,7 @@ export default function EditorTerminal({
return ( return (
<> <>
<div <div
ref={terminalRef} ref={terminalContainerRef}
style={{ display: visible ? "block" : "none" }} style={{ display: visible ? "block" : "none" }}
className="w-full h-full text-left" className="w-full h-full text-left"
> >
@ -130,3 +135,56 @@ export default function EditorTerminal({
</> </>
) )
} }
const lightTheme = {
foreground: "#2e3436",
background: "#ffffff",
black: "#2e3436",
brightBlack: "#555753",
red: "#cc0000",
brightRed: "#ef2929",
green: "#4e9a06",
brightGreen: "#8ae234",
yellow: "#c4a000",
brightYellow: "#fce94f",
blue: "#3465a4",
brightBlue: "#729fcf",
magenta: "#75507b",
brightMagenta: "#ad7fa8",
cyan: "#06989a",
brightCyan: "#34e2e2",
white: "#d3d7cf",
brightWhite: "#eeeeec",
cursor: "#2e3436",
cursorAccent: "#ffffff",
selectionBackground: "#3465a4",
selectionForeground: "#ffffff",
selectionInactiveBackground: "#264973",
}
// Dark Theme
const darkTheme = {
foreground: "#f8f8f2",
background: "#0a0a0a",
black: "#21222c",
brightBlack: "#6272a4",
red: "#ff5555",
brightRed: "#ff6e6e",
green: "#50fa7b",
brightGreen: "#69ff94",
yellow: "#f1fa8c",
brightYellow: "#ffffa5",
blue: "#bd93f9",
brightBlue: "#d6acff",
magenta: "#ff79c6",
brightMagenta: "#ff92df",
cyan: "#8be9fd",
brightCyan: "#a4ffff",
white: "#f8f8f2",
brightWhite: "#ffffff",
cursor: "#f8f8f2",
cursorAccent: "#0a0a0a",
selectionBackground: "#264973",
selectionForeground: "#ffffff",
selectionInactiveBackground: "#1a3151",
}

View File

@ -1,9 +1,10 @@
import Logo from "@/assets/logo.svg" import Logo from "@/assets/logo.svg"
import XLogo from "@/assets/x.svg" import CustomButton from "@/components/ui/customButton"
import Button from "@/components/ui/customButton"
import { ChevronRight } from "lucide-react" import { ChevronRight } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
import { Button } from "../ui/button"
import { ThemeSwitcher } from "../ui/theme-switcher"
export default function Landing() { export default function Landing() {
return ( return (
@ -20,21 +21,37 @@ export default function Landing() {
/> />
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<a href="https://www.x.com/ishaandey_" target="_blank"> <Button variant="outline" size="icon" asChild>
<Image src={XLogo} alt="X Logo" width={18} height={18} /> <a href="https://www.x.com/ishaandey_" target="_blank">
</a> <svg
width="1200"
height="1227"
viewBox="0 0 1200 1227"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="size-[1.125rem] text-muted-foreground"
>
<path
d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"
fill="currentColor"
/>
</svg>
</a>
</Button>
<ThemeSwitcher />
</div> </div>
</div> </div>
<h1 className="text-2xl font-medium text-center mt-16"> <h1 className="text-2xl font-medium text-center mt-16">
A Collaborative + AI-Powered Code Environment A Collaborative + AI-Powered Code Environment
</h1> </h1>
<div className="text-muted-foreground mt-4 text-center "> <p className="text-muted-foreground mt-4 text-center ">
Sandbox is an open-source cloud-based code editing environment with Sandbox is an open-source cloud-based code editing environment with
custom AI code autocompletion and real-time collaboration. custom AI code autocompletion and real-time collaboration.
</div> </p>
<div className="mt-8 flex space-x-4"> <div className="mt-8 flex space-x-4">
<Link href="/sign-up"> <Link href="/sign-up">
<Button>Go To App</Button> <CustomButton>Go To App</CustomButton>
</Link> </Link>
<a <a
href="https://github.com/ishaan1013/sandbox" href="https://github.com/ishaan1013/sandbox"

View File

@ -24,7 +24,7 @@ const Button = ({
`gradient-button-bg p-[1px] inline-flex group rounded-md text-sm font-medium focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50` `gradient-button-bg p-[1px] inline-flex group rounded-md text-sm font-medium focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50`
)} )}
> >
<div className="rounded-[6px] w-full gradient-button flex items-center justify-center whitespace-nowrap px-4 py-2 h-9"> <div className="rounded-[6px] w-full gradient-button transition-colors flex items-center justify-center whitespace-nowrap px-4 py-2 h-9">
{children} {children}
</div> </div>
</button> </button>

View File

@ -28,7 +28,7 @@ export default function Tab({
variant={"secondary"} variant={"secondary"}
className={`font-normal select-none ${ className={`font-normal select-none ${
selected selected
? "bg-neutral-700 hover:bg-neutral-600 text-foreground" ? "bg-muted-foreground/50 hover:bg-muted-foreground/40 text-foreground"
: "text-muted-foreground" : "text-muted-foreground"
}`} }`}
> >

View File

@ -6,3 +6,4 @@ import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider> return <NextThemesProvider {...props}>{children}</NextThemesProvider>
} }

View File

@ -0,0 +1,39 @@
"use client"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ThemeSwitcher() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="text-muted-foreground">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@ -38,6 +38,7 @@
"csl": "xml", "csl": "xml",
"cson": "coffeescript", "cson": "coffeescript",
"csproj": "xml", "csproj": "xml",
"css":"css",
"ct": "xml", "ct": "xml",
"ctp": "php", "ctp": "php",
"cxx": "cpp", "cxx": "cpp",