diff --git a/checker/src/checker.py b/checker/src/checker.py index 2d7ab38..5eb6080 100644 --- a/checker/src/checker.py +++ b/checker/src/checker.py @@ -1,6 +1,5 @@ import string import random -import secrets import re from logging import LoggerAdapter diff --git a/service/frontend/src/actions/navigate.ts b/service/frontend/src/actions/navigate.ts index fc6bf69..ee94388 100644 --- a/service/frontend/src/actions/navigate.ts +++ b/service/frontend/src/actions/navigate.ts @@ -1,7 +1,7 @@ -'use server' +"use server"; -import { redirect } from 'next/navigation' +import { redirect } from "next/navigation"; export async function navigate(path: string) { - redirect(path) + redirect(path); } diff --git a/service/frontend/src/app/devenv/[slug]/page.tsx b/service/frontend/src/app/devenv/[slug]/page.tsx index 48ee4df..9bd8ea3 100644 --- a/service/frontend/src/app/devenv/[slug]/page.tsx +++ b/service/frontend/src/app/devenv/[slug]/page.tsx @@ -2,51 +2,71 @@ import dynamic from "next/dynamic"; import FileTree from "@/components/file-tree"; -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable"; import { useEffect, useState } from "react"; import ExecTerm from "@/components/exec-term"; import { useDevenvGeneration } from "@/hooks/use-devenv-generation"; import { useDevenvGenerationMutation } from "@/hooks/use-devenv-generation-mutation"; -const Editor = dynamic(() => import('@/components/editor'), { - ssr: false -}) +const Editor = dynamic(() => import("@/components/editor"), { + ssr: false, +}); export default function Page({ params }: { params: { slug: string } }) { const [currentFile, setCurrentFile] = useState(); - const devenvGenerationMutation = useDevenvGenerationMutation({ uuid: params.slug }) - const generation = useDevenvGeneration({ uuid: params.slug }) + const devenvGenerationMutation = useDevenvGenerationMutation({ + uuid: params.slug, + }); + const generation = useDevenvGeneration({ uuid: params.slug }); useEffect(() => { return () => { - devenvGenerationMutation.mutate(null) - } - }, []) + devenvGenerationMutation.mutate(null); + }; + }, []); return (
- +
- + - {Boolean(generation) && <> - - - - - } + {Boolean(generation) && ( + <> + + + + + + )} -
); diff --git a/service/frontend/src/app/layout.tsx b/service/frontend/src/app/layout.tsx index f5783eb..f36b937 100644 --- a/service/frontend/src/app/layout.tsx +++ b/service/frontend/src/app/layout.tsx @@ -2,12 +2,12 @@ import "./globals.css"; import "@xterm/xterm/css/xterm.css"; import type { Metadata } from "next"; -import { ThemeProvider } from "@/components/theme-provider" +import { ThemeProvider } from "@/components/theme-provider"; import Navbar from "@/components/navigation-bar"; -import localFont from 'next/font/local'; +import localFont from "next/font/local"; import ReactQueryProvider from "@/components/react-query-provider"; -const dejaVuFont = localFont({ src: './DejaVuSansMNerdFontMono-Regular.ttf' }) +const dejaVuFont = localFont({ src: "./DejaVuSansMNerdFontMono-Regular.ttf" }); export const metadata: Metadata = { title: "replme", diff --git a/service/frontend/src/app/login/page.tsx b/service/frontend/src/app/login/page.tsx index 2ed4d5e..93140f5 100644 --- a/service/frontend/src/app/login/page.tsx +++ b/service/frontend/src/app/login/page.tsx @@ -4,14 +4,20 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { Button } from "@/components/ui/button"; import { z } from "zod"; -import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { useLoginMutation } from "@/hooks/use-login-mutation"; const LoginFormSchema = z.object({ username: z.string().min(1, { message: "Username can't be empty" }), - password: z.string().min(1, { message: "Password can't be empty" }) -}) + password: z.string().min(1, { message: "Password can't be empty" }), +}); type LoginForm = z.infer; @@ -21,20 +27,20 @@ export default function Page() { defaultValues: { username: "", password: "", - } - }) + }, + }); const loginMutation = useLoginMutation({ onError: () => { form.setError("password", { - message: "Password is wrong" - }) - } - }) + message: "Password is wrong", + }); + }, + }); const onSubmit = (credentials: LoginForm) => { loginMutation.mutate(credentials); - } + }; return (
diff --git a/service/frontend/src/app/page.tsx b/service/frontend/src/app/page.tsx index b4945f5..0cd10e1 100644 --- a/service/frontend/src/app/page.tsx +++ b/service/frontend/src/app/page.tsx @@ -4,28 +4,30 @@ import CreateDevenvButton from "@/components/create-devenv-button"; import CreateReplButton from "@/components/create-repl-button"; import { Button } from "@/components/ui/button"; import { useUserQuery } from "@/hooks/use-user-query"; -import { RocketIcon } from "@radix-ui/react-icons" +import { RocketIcon } from "@radix-ui/react-icons"; export default function Page() { - const userQuery = useUserQuery() - const isAuthenticatedMode = !userQuery.isStale && userQuery.isSuccess + const userQuery = useUserQuery(); + const isAuthenticatedMode = !userQuery.isStale && userQuery.isSuccess; return (
Want a clean /home?
Use replme!
- Hack together your ideas in development environments, - use a throwaway shell - all in one place. + Hack together your ideas in development environments, use a throwaway + shell - all in one place.
- {isAuthenticatedMode ? - : + {isAuthenticatedMode ? ( + + ) : ( - } + + )}
diff --git a/service/frontend/src/app/register/page.tsx b/service/frontend/src/app/register/page.tsx index 77a9318..34fc300 100644 --- a/service/frontend/src/app/register/page.tsx +++ b/service/frontend/src/app/register/page.tsx @@ -4,38 +4,50 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { Button } from "@/components/ui/button"; import { z } from "zod"; -import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { useRegisterMutation } from "@/hooks/use-register-mutation"; const RegisterFormSchema = z.object({ - username: z.string().min(4, { message: "Minimum length 4" }).max(64, { message: "Maximum length 4" }).regex(/^[a-zA-Z0-9]*$/, { message: "Only alphanumeric" }), - password: z.string().min(4, { message: "Minimum length 4" }).max(64, { message: "Maximum length 4" }) -}) + username: z + .string() + .min(4, { message: "Minimum length 4" }) + .max(64, { message: "Maximum length 4" }) + .regex(/^[a-zA-Z0-9]*$/, { message: "Only alphanumeric" }), + password: z + .string() + .min(4, { message: "Minimum length 4" }) + .max(64, { message: "Maximum length 4" }), +}); type RegisterForm = z.infer; export default function Page() { - const form = useForm({ resolver: zodResolver(RegisterFormSchema), defaultValues: { username: "", password: "", - } - }) + }, + }); const mutation = useRegisterMutation({ onError: () => { form.setError("username", { - message: "That did not work, user exists?" - }) - } - }) + message: "That did not work, user exists?", + }); + }, + }); const onSubmit = (credentials: RegisterForm) => { mutation.mutate(credentials); - } + }; return (
diff --git a/service/frontend/src/app/repl/[slug]/page.tsx b/service/frontend/src/app/repl/[slug]/page.tsx index d90ecba..1a71dc0 100644 --- a/service/frontend/src/app/repl/[slug]/page.tsx +++ b/service/frontend/src/app/repl/[slug]/page.tsx @@ -1,15 +1,20 @@ "use client"; -import dynamic from 'next/dynamic' +import dynamic from "next/dynamic"; -const Terminal = dynamic(() => import('@/components/terminal'), { - ssr: false -}) +const Terminal = dynamic(() => import("@/components/terminal"), { + ssr: false, +}); export default function Page({ params }: { params: { slug: string } }) { return ( -
- +
+
- ) + ); } diff --git a/service/frontend/src/components/create-devenv-button.tsx b/service/frontend/src/components/create-devenv-button.tsx index 6aaf8cf..2d65be1 100644 --- a/service/frontend/src/components/create-devenv-button.tsx +++ b/service/frontend/src/components/create-devenv-button.tsx @@ -10,26 +10,36 @@ const CreateDevenvButton = React.forwardRef( (props, ref) => { const createDevenvMutation = useCreateDevenvMutation(); - const handleCreateDevenv = (event: React.MouseEvent) => { + const handleCreateDevenv = ( + event: React.MouseEvent, + ) => { const name = randomString(10); createDevenvMutation.mutate({ name, buildCmd: "gcc -o main main.c", - runCmd: "./main" + runCmd: "./main", }); - if (props.onClick) props.onClick(event) - } + if (props.onClick) props.onClick(event); + }; return ( - - ) - } -) + ); + }, +); CreateDevenvButton.displayName = "CreateDevenvButton"; export default CreateDevenvButton; - diff --git a/service/frontend/src/components/create-devenv-file-menu.tsx b/service/frontend/src/components/create-devenv-file-menu.tsx index 2622720..4773b65 100644 --- a/service/frontend/src/components/create-devenv-file-menu.tsx +++ b/service/frontend/src/components/create-devenv-file-menu.tsx @@ -1,6 +1,6 @@ "use client"; -import { zodResolver } from "@hookform/resolvers/zod" +import { zodResolver } from "@hookform/resolvers/zod"; import { Drawer, DrawerClose, @@ -9,15 +9,14 @@ import { DrawerHeader, DrawerTitle, DrawerTrigger, -} from "@/components/ui/drawer" +} from "@/components/ui/drawer"; import { Button } from "./ui/button"; import { PlusIcon } from "@radix-ui/react-icons"; -import { Form, FormControl, FormField, FormItem } from "./ui/form" -import { z } from "zod" -import { useForm } from "react-hook-form" +import { Form, FormControl, FormField, FormItem } from "./ui/form"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; import { useDevenvCreateFileMutation } from "@/hooks/use-devenv-create-file-mutation"; -import { Input } from "./ui/input" - +import { Input } from "./ui/input"; const CreateFileFormSchema = z.object({ name: z.string().min(1).max(30), @@ -26,25 +25,24 @@ const CreateFileFormSchema = z.object({ type CreateFileForm = z.infer; export type CreateDevenvFileMenuProps = { - uuid: string, -} + uuid: string; +}; const CreateDevenvFileMenu: React.FC = (props) => { - const createFileMutation = useDevenvCreateFileMutation({ uuid: props.uuid, - }) + }); const form = useForm({ resolver: zodResolver(CreateFileFormSchema), defaultValues: { name: "", - } - }) + }, + }); const onCreateFileSubmit = (file: CreateFileForm) => { - createFileMutation.mutate(file) - } + createFileMutation.mutate(file); + }; return ( @@ -61,7 +59,10 @@ const CreateDevenvFileMenu: React.FC = (props) => {
- + = (props) => {
- ) -} + ); +}; export default CreateDevenvFileMenu; diff --git a/service/frontend/src/components/create-repl-button.tsx b/service/frontend/src/components/create-repl-button.tsx index 4606ef4..74cfe22 100644 --- a/service/frontend/src/components/create-repl-button.tsx +++ b/service/frontend/src/components/create-repl-button.tsx @@ -9,22 +9,32 @@ import { useCreateReplMutation } from "@/hooks/use-create-repl-mutation"; const CreateReplButton = React.forwardRef( (props, ref) => { - const createReplMutation = useCreateReplMutation() - const handleCreateRepl = (event: React.MouseEvent) => { + const createReplMutation = useCreateReplMutation(); + const handleCreateRepl = ( + event: React.MouseEvent, + ) => { const username = randomString(60); const password = randomString(60); createReplMutation.mutate({ username, password }); - if (props.onClick) props.onClick(event) - } + if (props.onClick) props.onClick(event); + }; return ( - - ) - } -) + ); + }, +); CreateReplButton.displayName = "CreateReplButton"; export default CreateReplButton; - diff --git a/service/frontend/src/components/devenv-menu.tsx b/service/frontend/src/components/devenv-menu.tsx index eb633b0..fae9dd9 100644 --- a/service/frontend/src/components/devenv-menu.tsx +++ b/service/frontend/src/components/devenv-menu.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "@/components/ui/button" +import { Button } from "@/components/ui/button"; import { Drawer, DrawerClose, @@ -9,7 +9,7 @@ import { DrawerHeader, DrawerTitle, DrawerTrigger, -} from "@/components/ui/drawer" +} from "@/components/ui/drawer"; import { CodeIcon } from "@radix-ui/react-icons"; import { navigate } from "@/actions/navigate"; import CreateDevenvButton from "./create-devenv-button"; @@ -24,7 +24,11 @@ const DevenvMenu = () => { @@ -37,7 +41,10 @@ const DevenvMenu = () => {
{devenvsQuery.data?.data?.map((devenv) => ( - @@ -49,7 +56,7 @@ const DevenvMenu = () => {
- ) -} + ); +}; export default DevenvMenu; diff --git a/service/frontend/src/components/devenv-settings-menu.tsx b/service/frontend/src/components/devenv-settings-menu.tsx index 492fff2..417f952 100644 --- a/service/frontend/src/components/devenv-settings-menu.tsx +++ b/service/frontend/src/components/devenv-settings-menu.tsx @@ -2,57 +2,71 @@ import { zodResolver } from "@hookform/resolvers/zod"; import React, { useEffect } from "react"; -import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger } from "./ui/drawer"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "./ui/drawer"; import { Button } from "./ui/button"; import { GearIcon } from "@radix-ui/react-icons"; import { z } from "zod"; import { useDevenvQuery } from "@/hooks/use-devenv-query"; import { usePatchDevenvMutation } from "@/hooks/use-patch-devenv-mutation"; import { useForm } from "react-hook-form"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import { Input } from "./ui/input"; export type DevenvSettingsMenuProps = { - uuid: string -} + uuid: string; +}; const DevenvFormSchema = z.object({ name: z.string().min(1, { message: "Name command can't be empty" }), buildCmd: z.string().min(1, { message: "Build command can't be empty" }), - runCmd: z.string().min(1, { message: "Run command can't be empty" }) -}) + runCmd: z.string().min(1, { message: "Run command can't be empty" }), +}); type DevenvForm = z.infer; const DevenvSettingsMenu: React.FC = (props) => { const devenvQuery = useDevenvQuery({ - uuid: props.uuid - }) + uuid: props.uuid, + }); const form = useForm({ resolver: zodResolver(DevenvFormSchema), defaultValues: { buildCmd: "", runCmd: "", - } - }) + }, + }); const patchDevenvMutation = usePatchDevenvMutation({ uuid: props.uuid, - }) + }); useEffect(() => { if (devenvQuery.isSuccess) { - form.setValue("name", devenvQuery.data.name) - form.setValue("buildCmd", devenvQuery.data.buildCmd) - form.setValue("runCmd", devenvQuery.data.runCmd) + form.setValue("name", devenvQuery.data.name); + form.setValue("buildCmd", devenvQuery.data.buildCmd); + form.setValue("runCmd", devenvQuery.data.runCmd); } - }, [devenvQuery.isSuccess, devenvQuery.data]) - + }, [devenvQuery.isSuccess, devenvQuery.data]); const onSubmit = (devenvPatch: DevenvForm) => { patchDevenvMutation.mutate(devenvPatch); - } + }; return ( @@ -65,11 +79,16 @@ const DevenvSettingsMenu: React.FC = (props) => {
Devenv Settings - Set the build command and run command + + Set the build command and run command +
- + = (props) => { Name - + @@ -93,10 +109,7 @@ const DevenvSettingsMenu: React.FC = (props) => { Build command - + @@ -120,7 +133,9 @@ const DevenvSettingsMenu: React.FC = (props) => { className="w-full" disabled={!form.formState.isDirty} type="submit" - >Save + > + Save + @@ -128,7 +143,7 @@ const DevenvSettingsMenu: React.FC = (props) => {
- ) -} + ); +}; export default DevenvSettingsMenu; diff --git a/service/frontend/src/components/editor.tsx b/service/frontend/src/components/editor.tsx index 755bf65..03f0b7e 100644 --- a/service/frontend/src/components/editor.tsx +++ b/service/frontend/src/components/editor.tsx @@ -1,25 +1,25 @@ "use client"; -import { useDevenvFileContentQuery } from '@/hooks/use-devenv-file-content-query'; -import { useDevenvFileContentMutation } from '@/hooks/use-devenv-file-content-mutation'; -import MonacoEditor, { Monaco, OnChange } from '@monaco-editor/react'; -import { useTheme } from 'next-themes'; -import { useEffect, useState } from 'react'; +import { useDevenvFileContentQuery } from "@/hooks/use-devenv-file-content-query"; +import { useDevenvFileContentMutation } from "@/hooks/use-devenv-file-content-mutation"; +import MonacoEditor, { Monaco, OnChange } from "@monaco-editor/react"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; import { useDebounce } from "@uidotdev/usehooks"; -import { useDevenvStateMutation } from '@/hooks/use-devenv-state-mutation'; +import { useDevenvStateMutation } from "@/hooks/use-devenv-state-mutation"; type EditorProps = { - className?: string, - devenvUuid: string, - filename?: string -} + className?: string; + devenvUuid: string; + filename?: string; +}; type DebouncedEditorProps = { - className?: string, - devenvUuid: string, - filename: string, - initialContent: string, -} + className?: string; + devenvUuid: string; + filename: string; + initialContent: string; +}; const DebouncedEditor: React.FC = (props) => { const { className, devenvUuid, filename, initialContent } = props; @@ -28,75 +28,74 @@ const DebouncedEditor: React.FC = (props) => { const editorTheme = resolvedTheme === "light" ? "light" : "dark"; const [content, setContent] = useState(initialContent); - const debouncedContent = useDebounce(content, 1000) + const debouncedContent = useDebounce(content, 1000); const fileContentMutation = useDevenvFileContentMutation({ uuid: devenvUuid, - filename - }) + filename, + }); const devenvStateMutation = useDevenvStateMutation({ uuid: devenvUuid, - }) + }); useEffect(() => { - fileContentMutation.mutate(debouncedContent) - }, [debouncedContent, fileContentMutation.mutate]) + fileContentMutation.mutate(debouncedContent); + }, [debouncedContent, fileContentMutation.mutate]); const handleEditorChange: OnChange = (value, _) => { if (value) { - devenvStateMutation.mutate("dirty") - setContent(value) + devenvStateMutation.mutate("dirty"); + setContent(value); } - } + }; useEffect(() => { return () => { - devenvStateMutation.mutate("ok") - } + devenvStateMutation.mutate("ok"); + }; }, []); const handleEditorWillMount = (monaco: Monaco) => { monaco.editor.defineTheme("dark", { - "base": "vs-dark", - "inherit": true, - "rules": [], - "colors": { - "editor.background": "#020817" - } - }) - } + base: "vs-dark", + inherit: true, + rules: [], + colors: { + "editor.background": "#020817", + }, + }); + }; return ( - ) -} + ); +}; const Editor: React.FC = (props) => { const { className, devenvUuid, filename } = props; const fileContentQuery = useDevenvFileContentQuery({ uuid: devenvUuid, - filename - }) + filename, + }); - if (!filename) - return <> + if (!filename) return <>; if (fileContentQuery.isStale || fileContentQuery.isLoading) { - return <> + return <>; } if (!fileContentQuery.isSuccess) { - return <>Loading file failed + return <>Loading file failed; } return ( @@ -106,7 +105,7 @@ const Editor: React.FC = (props) => { filename={filename} initialContent={fileContentQuery.data} /> - ) -} + ); +}; export default Editor; diff --git a/service/frontend/src/components/exec-term.tsx b/service/frontend/src/components/exec-term.tsx index e0a7812..c82a94f 100644 --- a/service/frontend/src/components/exec-term.tsx +++ b/service/frontend/src/components/exec-term.tsx @@ -1,25 +1,25 @@ "use client"; -import dynamic from 'next/dynamic' +import dynamic from "next/dynamic"; -const Terminal = dynamic(() => import('@/components/terminal'), { - ssr: false -}) +const Terminal = dynamic(() => import("@/components/terminal"), { + ssr: false, +}); type ExecTermProps = { - className?: string - id: string - path: string -} + className?: string; + id: string; + path: string; +}; const ExecTerm: React.FC = (props) => { - - const { className, id, path } = props - - return
- -
-} - + const { className, id, path } = props; + + return ( +
+ +
+ ); +}; export default ExecTerm; diff --git a/service/frontend/src/components/file-tree.tsx b/service/frontend/src/components/file-tree.tsx index 59766bb..b7150d4 100644 --- a/service/frontend/src/components/file-tree.tsx +++ b/service/frontend/src/components/file-tree.tsx @@ -1,15 +1,15 @@ -import { Button } from "./ui/button" -import { Skeleton } from "./ui/skeleton" -import { Cross2Icon } from "@radix-ui/react-icons" -import { useDevenvFilesQuery } from "@/hooks/use-devenv-files-query" -import { useDevenvDeleteFileMutation } from "@/hooks/use-devenv-delete-file-mutation" +import { Button } from "./ui/button"; +import { Skeleton } from "./ui/skeleton"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { useDevenvFilesQuery } from "@/hooks/use-devenv-files-query"; +import { useDevenvDeleteFileMutation } from "@/hooks/use-devenv-delete-file-mutation"; type FileTreeProps = { - className?: string - devenvUuid: string - selectedFile?: string - setSelectedFile: (file: string | undefined) => void -} + className?: string; + devenvUuid: string; + selectedFile?: string; + setSelectedFile: (file: string | undefined) => void; +}; const FileTree: React.FC = (props) => { const { className, devenvUuid, selectedFile, setSelectedFile } = props; @@ -17,17 +17,16 @@ const FileTree: React.FC = (props) => { const filesQuery = useDevenvFilesQuery({ uuid: devenvUuid, callback: (files) => { - if (!selectedFile && files.length > 0) - setSelectedFile(files[0]) - } - }) + if (!selectedFile && files.length > 0) setSelectedFile(files[0]); + }, + }); const deleteFileMutation = useDevenvDeleteFileMutation({ uuid: devenvUuid, onSuccess: (filename, files) => { if (selectedFile === filename) - setSelectedFile(files.length > 0 ? files[0] : undefined) - } + setSelectedFile(files.length > 0 ? files[0] : undefined); + }, }); return ( @@ -38,26 +37,40 @@ const FileTree: React.FC = (props) => { )} - {filesQuery.isSuccess && -
+ {filesQuery.isSuccess && ( +
{filesQuery.data.map((filename) => ( -
setSelectedFile(filename)}> +
setSelectedFile(filename)} + >
{filename}
-
))}
- } - {filesQuery.isError && -
Uh oh, something went wrong
- } + )} + {filesQuery.isError &&
Uh oh, something went wrong
}
- ) -} + ); +}; export default FileTree; diff --git a/service/frontend/src/components/login-button.tsx b/service/frontend/src/components/login-button.tsx index 71d2f17..561de72 100644 --- a/service/frontend/src/components/login-button.tsx +++ b/service/frontend/src/components/login-button.tsx @@ -6,6 +6,5 @@ export function LoginButton() { - ) + ); } - diff --git a/service/frontend/src/components/logout-button.tsx b/service/frontend/src/components/logout-button.tsx index cdf1ea3..dda7fcb 100644 --- a/service/frontend/src/components/logout-button.tsx +++ b/service/frontend/src/components/logout-button.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { ExitIcon } from "@radix-ui/react-icons"; import { Button } from "./ui/button"; @@ -8,9 +8,12 @@ export function LogoutButton() { const logoutMutation = useLogoutMutation(); return ( - - ) + ); } - diff --git a/service/frontend/src/components/mode-toggle.tsx b/service/frontend/src/components/mode-toggle.tsx index 534cfb0..010b087 100644 --- a/service/frontend/src/components/mode-toggle.tsx +++ b/service/frontend/src/components/mode-toggle.tsx @@ -1,19 +1,19 @@ -"use client" +"use client"; -import * as React from "react" -import { MoonIcon, SunIcon } from "@radix-ui/react-icons" -import { useTheme } from "next-themes" +import * as React from "react"; +import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; +import { useTheme } from "next-themes"; -import { Button } from "@/components/ui/button" +import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" +} from "@/components/ui/dropdown-menu"; export function ModeToggle() { - const { setTheme } = useTheme() + const { setTheme } = useTheme(); return ( @@ -36,6 +36,5 @@ export function ModeToggle() { - ) + ); } - diff --git a/service/frontend/src/components/navigation-bar.tsx b/service/frontend/src/components/navigation-bar.tsx index ed09233..60f847e 100644 --- a/service/frontend/src/components/navigation-bar.tsx +++ b/service/frontend/src/components/navigation-bar.tsx @@ -10,7 +10,12 @@ import { useUserQuery } from "@/hooks/use-user-query"; import { usePathname } from "next/navigation"; import { Button } from "./ui/button"; import { useDevenvGenerationMutation } from "@/hooks/use-devenv-generation-mutation"; -import { CheckIcon, CircleIcon, PlayIcon, ReloadIcon } from '@radix-ui/react-icons' +import { + CheckIcon, + CircleIcon, + PlayIcon, + ReloadIcon, +} from "@radix-ui/react-icons"; import DevenvSettingsMenu from "./devenv-settings-menu"; import CreateDevenvFileMenu from "./create-devenv-file-menu"; import { useMutationState } from "@tanstack/react-query"; @@ -18,54 +23,59 @@ import { useDevenvState } from "@/hooks/use-devenv-state"; const Navbar = () => { const pathname = usePathname(); - const userQuery = useUserQuery() - const isAuthenticatedMode = !userQuery.isStale && userQuery.isSuccess + const userQuery = useUserQuery(); + const isAuthenticatedMode = !userQuery.isStale && userQuery.isSuccess; - const match = pathname.match("(?<=/devenv/)[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}") - const devenvUuid = match !== null && match?.length >= 1 ? match[0] : undefined; + const match = pathname.match( + "(?<=/devenv/)[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}", + ); + const devenvUuid = + match !== null && match?.length >= 1 ? match[0] : undefined; const devenvGenerationMutation = useDevenvGenerationMutation({ - uuid: devenvUuid - }) + uuid: devenvUuid, + }); const devenvFileContentMutationsStatus = useMutationState({ - filters: { mutationKey: ['devenv', devenvUuid, 'files'] }, - select: (mutation) => mutation.state.status - }) + filters: { mutationKey: ["devenv", devenvUuid, "files"] }, + select: (mutation) => mutation.state.status, + }); const devenvState = useDevenvState({ - uuid: devenvUuid ?? "" - }) + uuid: devenvUuid ?? "", + }); return ( ); -} +}; export default Navbar; - diff --git a/service/frontend/src/components/react-query-provider.tsx b/service/frontend/src/components/react-query-provider.tsx index 36b8055..1f0dc77 100644 --- a/service/frontend/src/components/react-query-provider.tsx +++ b/service/frontend/src/components/react-query-provider.tsx @@ -1,25 +1,27 @@ "use client"; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React, { useState } from 'react'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React, { useState } from "react"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; const ReactQueryDevtoolsProduction = React.lazy(() => - import('@tanstack/react-query-devtools/build/modern/production.js').then( + import("@tanstack/react-query-devtools/build/modern/production.js").then( (d) => ({ default: d.ReactQueryDevtools, }), ), -) +); -const ReactQueryProvider: React.FC = ({ children }) => { +const ReactQueryProvider: React.FC = ({ + children, +}) => { const [queryClient] = useState(() => new QueryClient()); - const [showDevtools, setShowDevtools] = React.useState(false) + const [showDevtools, setShowDevtools] = React.useState(false); React.useEffect(() => { // @ts-expect-error - window.toggleDevtools = () => setShowDevtools((old) => !old) - }, []) + window.toggleDevtools = () => setShowDevtools((old) => !old); + }, []); return ( @@ -32,6 +34,6 @@ const ReactQueryProvider: React.FC = ({ children }) => )} ); -} +}; export default ReactQueryProvider; diff --git a/service/frontend/src/components/repl-menu.tsx b/service/frontend/src/components/repl-menu.tsx index f53e9cd..091f339 100644 --- a/service/frontend/src/components/repl-menu.tsx +++ b/service/frontend/src/components/repl-menu.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "@/components/ui/button" +import { Button } from "@/components/ui/button"; import { Drawer, DrawerClose, @@ -9,14 +9,14 @@ import { DrawerHeader, DrawerTitle, DrawerTrigger, -} from "@/components/ui/drawer" +} from "@/components/ui/drawer"; import CreateReplButton from "./create-repl-button"; import { navigate } from "@/actions/navigate"; import { PiTerminalWindowLight } from "react-icons/pi"; import { useReplSessionsQuery } from "@/hooks/use-repl-sessions-query"; const ReplMenu = () => { - const replSessionsQuery = useReplSessionsQuery() + const replSessionsQuery = useReplSessionsQuery(); const numSessions = replSessionsQuery.data?.data?.length ?? 0; return ( @@ -24,7 +24,11 @@ const ReplMenu = () => { @@ -37,7 +41,10 @@ const ReplMenu = () => {
{replSessionsQuery.data?.data?.map((id) => ( - @@ -49,7 +56,7 @@ const ReplMenu = () => {
- ) -} + ); +}; export default ReplMenu; diff --git a/service/frontend/src/components/terminal.tsx b/service/frontend/src/components/terminal.tsx index e92ee89..73f2ff7 100644 --- a/service/frontend/src/components/terminal.tsx +++ b/service/frontend/src/components/terminal.tsx @@ -10,60 +10,60 @@ import { sleep } from "@/lib/utils"; import { useTheme } from "next-themes"; const LIGHT_THEME = { - background: '#ffffff', - foreground: '#333333', - cursor: '#333333', - cursorAccent: '#ffffff', - selectionBackground: '#add6ff', - black: '#000000', - blue: '#0451a5', - brightBlack: '#666666', - brightBlue: '#0451a5', - brightCyan: '#0598bc', - brightGreen: '#14ce14', - brightMagenta: '#bc05bc', - brightRed: '#cd3131', - brightWhite: '#a5a5a5', - brightYellow: '#b5ba00', - cyan: '#0598bc', - green: '#00bc00', - magenta: '#bc05bc', - red: '#cd3131', - white: '#555555', - yellow: '#949800' -} + background: "#ffffff", + foreground: "#333333", + cursor: "#333333", + cursorAccent: "#ffffff", + selectionBackground: "#add6ff", + black: "#000000", + blue: "#0451a5", + brightBlack: "#666666", + brightBlue: "#0451a5", + brightCyan: "#0598bc", + brightGreen: "#14ce14", + brightMagenta: "#bc05bc", + brightRed: "#cd3131", + brightWhite: "#a5a5a5", + brightYellow: "#b5ba00", + cyan: "#0598bc", + green: "#00bc00", + magenta: "#bc05bc", + red: "#cd3131", + white: "#555555", + yellow: "#949800", +}; const DARK_THEME = { - foreground: '#f8f8f8', - background: '#020817', - selectionBackground: '#5da5d533', - selectionInactiveBackground: '#555555aa', - black: '#1e1e1d', - brightBlack: '#262625', - red: '#ce5c5c', - brightRed: '#ff7272', - green: '#5bcc5b', - brightGreen: '#72ff72', - yellow: '#cccc5b', - brightYellow: '#ffff72', - blue: '#5d5dd3', - brightBlue: '#7279ff', - magenta: '#bc5ed1', - brightMagenta: '#e572ff', - cyan: '#5da5d5', - brightCyan: '#72f0ff', - white: '#f8f8f8', - brightWhite: '#ffffff' + foreground: "#f8f8f8", + background: "#020817", + selectionBackground: "#5da5d533", + selectionInactiveBackground: "#555555aa", + black: "#1e1e1d", + brightBlack: "#262625", + red: "#ce5c5c", + brightRed: "#ff7272", + green: "#5bcc5b", + brightGreen: "#72ff72", + yellow: "#cccc5b", + brightYellow: "#ffff72", + blue: "#5d5dd3", + brightBlue: "#7279ff", + magenta: "#bc5ed1", + brightMagenta: "#e572ff", + cyan: "#5da5d5", + brightCyan: "#72f0ff", + white: "#f8f8f8", + brightWhite: "#ffffff", }; const RETRY = 5; const DELAY = 1000; type TerminalProps = { - id?: string, - className?: string, - path: string, - catchClose?: boolean, + id?: string; + className?: string; + path: string; + catchClose?: boolean; }; const Terminal: React.FC = (props) => { @@ -76,23 +76,23 @@ const Terminal: React.FC = (props) => { const websocketRef = useRef(null); useEffect(() => { - if (!xtermRef.current) - return - - xtermRef.current.options.theme = { ...(resolvedTheme === "light" ? LIGHT_THEME : DARK_THEME) } + if (!xtermRef.current) return; - }, [xtermRef, resolvedTheme]) + xtermRef.current.options.theme = { + ...(resolvedTheme === "light" ? LIGHT_THEME : DARK_THEME), + }; + }, [xtermRef, resolvedTheme]); useEffect(() => { - if (!terminalRef.current) - return; + if (!terminalRef.current) return; - const protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://'; - let socketURL = protocol + (process.env.NEXT_PUBLIC_WS || location.host) + path; + const protocol = location.protocol === "https:" ? "wss://" : "ws://"; + let socketURL = + protocol + (process.env.NEXT_PUBLIC_WS || location.host) + path; const terminal = new XTerm({ allowProposedApi: true, fontFamily: '"DejaVuSansM Nerd Font", courier-new, courier, monospace', - theme: resolvedTheme === "light" ? LIGHT_THEME : DARK_THEME + theme: resolvedTheme === "light" ? LIGHT_THEME : DARK_THEME, }); xtermRef.current = terminal; @@ -109,7 +109,7 @@ const Terminal: React.FC = (props) => { fit.fit(); }); - terminal.open(terminalRef.current) + terminal.open(terminalRef.current); terminal.focus(); resizeObserver.observe(terminalRef.current); @@ -120,15 +120,15 @@ const Terminal: React.FC = (props) => { websocketRef.current = socket; socket.onopen = async () => { terminal.loadAddon(new AttachAddon(socket)); - fit.fit() + fit.fit(); }; if (catchClose) - window.onbeforeunload = function(e: any) { + window.onbeforeunload = function (e: any) { if (e) { - e.returnValue = 'Leave site?'; + e.returnValue = "Leave site?"; } // safari - return 'Leave site?'; + return "Leave site?"; }; break; } catch (error) { @@ -144,14 +144,11 @@ const Terminal: React.FC = (props) => { return () => { terminal.dispose(); websocketRef.current?.close(); - if (catchClose) - window.onbeforeunload = null; - } - }, [terminalRef, websocketRef]) + if (catchClose) window.onbeforeunload = null; + }; + }, [terminalRef, websocketRef]); - return ( -
- ) -} + return
; +}; export default Terminal; diff --git a/service/frontend/src/components/theme-provider.tsx b/service/frontend/src/components/theme-provider.tsx index 8c90fbc..b0ff266 100644 --- a/service/frontend/src/components/theme-provider.tsx +++ b/service/frontend/src/components/theme-provider.tsx @@ -1,9 +1,9 @@ -"use client" +"use client"; -import * as React from "react" -import { ThemeProvider as NextThemesProvider } from "next-themes" -import { type ThemeProviderProps } from "next-themes/dist/types" +import * as React from "react"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { type ThemeProviderProps } from "next-themes/dist/types"; export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children} + return {children}; } diff --git a/service/frontend/src/hooks/use-create-devenv-mutation.ts b/service/frontend/src/hooks/use-create-devenv-mutation.ts index c6ef23f..9004bee 100644 --- a/service/frontend/src/hooks/use-create-devenv-mutation.ts +++ b/service/frontend/src/hooks/use-create-devenv-mutation.ts @@ -9,16 +9,17 @@ export function useCreateDevenvMutation() { const client = useQueryClient(); return useMutation({ - mutationFn: (credentials: CreateDevenvRequest) => axios.post( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/devenv', - credentials, - { - withCredentials: true - } - ), + mutationFn: (credentials: CreateDevenvRequest) => + axios.post( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv", + credentials, + { + withCredentials: true, + }, + ), onSuccess: (response) => { - client.invalidateQueries({ queryKey: ['devenvs'] }) - navigate("/devenv/" + response.data.devenvUuid) + client.invalidateQueries({ queryKey: ["devenvs"] }); + navigate("/devenv/" + response.data.devenvUuid); }, - }) + }); } diff --git a/service/frontend/src/hooks/use-create-repl-mutation.ts b/service/frontend/src/hooks/use-create-repl-mutation.ts index dc11aec..df4e062 100644 --- a/service/frontend/src/hooks/use-create-repl-mutation.ts +++ b/service/frontend/src/hooks/use-create-repl-mutation.ts @@ -5,25 +5,25 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; export type CreateReplMutationPayload = { - username: string, - password: string, -} + username: string; + password: string; +}; export function useCreateReplMutation() { const client = useQueryClient(); return useMutation({ - mutationFn: (credentials: CreateReplMutationPayload) => axios.post<{ id: string }>( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/repl', - credentials, - { - withCredentials: true - } - ), + mutationFn: (credentials: CreateReplMutationPayload) => + axios.post<{ id: string }>( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/repl", + credentials, + { + withCredentials: true, + }, + ), onSuccess: (response) => { - client.invalidateQueries({ queryKey: ['repl-sessions'] }) - navigate("/repl/" + response.data.id) + client.invalidateQueries({ queryKey: ["repl-sessions"] }); + navigate("/repl/" + response.data.id); }, - }) + }); } - diff --git a/service/frontend/src/hooks/use-devenv-create-file-mutation.ts b/service/frontend/src/hooks/use-devenv-create-file-mutation.ts index ec4d2db..34eb730 100644 --- a/service/frontend/src/hooks/use-devenv-create-file-mutation.ts +++ b/service/frontend/src/hooks/use-devenv-create-file-mutation.ts @@ -1,40 +1,44 @@ "use client"; -import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useMutation, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; export type CreateFilePayload = { - name: string -} + name: string; +}; export type DevenvCreateFileMutationOptions = { - uuid: string, - onSuccess?: (file: CreateFilePayload) => void -} + uuid: string; + onSuccess?: (file: CreateFilePayload) => void; +}; -export function useDevenvCreateFileMutation(options: DevenvCreateFileMutationOptions) { +export function useDevenvCreateFileMutation( + options: DevenvCreateFileMutationOptions, +) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (file: CreateFilePayload) => axios.post( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/devenv/' + options.uuid + "/files", - file, - { - withCredentials: true - } - ), + mutationFn: (file: CreateFilePayload) => + axios.post( + (process.env.NEXT_PUBLIC_API ?? "") + + "/api/devenv/" + + options.uuid + + "/files", + file, + { + withCredentials: true, + }, + ), onSuccess: (_, file) => { queryClient.setQueryData( - ['devenv', options.uuid, 'files'], + ["devenv", options.uuid, "files"], (oldData) => { - let _data: string[] = oldData ?? [] - if (_data.includes(file.name)) - return _data - if (options.onSuccess) - options.onSuccess(file) - return [..._data, file.name].sort() - } - ) + let _data: string[] = oldData ?? []; + if (_data.includes(file.name)) return _data; + if (options.onSuccess) options.onSuccess(file); + return [..._data, file.name].sort(); + }, + ); }, - }) + }); } diff --git a/service/frontend/src/hooks/use-devenv-delete-file-mutation.ts b/service/frontend/src/hooks/use-devenv-delete-file-mutation.ts index 42eaaef..32da721 100644 --- a/service/frontend/src/hooks/use-devenv-delete-file-mutation.ts +++ b/service/frontend/src/hooks/use-devenv-delete-file-mutation.ts @@ -1,36 +1,42 @@ "use client"; -import { useMutation, useQueryClient } from "@tanstack/react-query" -import axios from "axios" +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import axios from "axios"; -export type DeleteFilePayload = string +export type DeleteFilePayload = string; export type DevenvDeleteFileMutationOptions = { - uuid: string, - onSuccess?: (filename: DeleteFilePayload, files: string[]) => void -} + uuid: string; + onSuccess?: (filename: DeleteFilePayload, files: string[]) => void; +}; -export function useDevenvDeleteFileMutation(options: DevenvDeleteFileMutationOptions) { +export function useDevenvDeleteFileMutation( + options: DevenvDeleteFileMutationOptions, +) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (filename: DeleteFilePayload) => axios.delete( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/devenv/' + options.uuid + "/files/" + encodeURI(filename), - { - withCredentials: true - } - ), + mutationFn: (filename: DeleteFilePayload) => + axios.delete( + (process.env.NEXT_PUBLIC_API ?? "") + + "/api/devenv/" + + options.uuid + + "/files/" + + encodeURI(filename), + { + withCredentials: true, + }, + ), onSuccess: (_, filename) => { queryClient.setQueryData( - ['devenv', options.uuid, 'files'], + ["devenv", options.uuid, "files"], (oldData) => { - let _data: string[] = oldData ?? [] - _data = _data.filter((name) => name !== filename) - if (options.onSuccess) - options.onSuccess(filename, _data) - return _data - } - ) - } - }) + let _data: string[] = oldData ?? []; + _data = _data.filter((name) => name !== filename); + if (options.onSuccess) options.onSuccess(filename, _data); + return _data; + }, + ); + }, + }); } diff --git a/service/frontend/src/hooks/use-devenv-file-content-mutation.ts b/service/frontend/src/hooks/use-devenv-file-content-mutation.ts index 3b7cb5b..54e29ed 100644 --- a/service/frontend/src/hooks/use-devenv-file-content-mutation.ts +++ b/service/frontend/src/hooks/use-devenv-file-content-mutation.ts @@ -1,38 +1,45 @@ "use client"; -import { useMutation, useQueryClient } from "@tanstack/react-query" -import axios from "axios" +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import axios from "axios"; import { useDevenvStateMutation } from "./use-devenv-state-mutation"; export type DevenvFileContentMutationOptions = { uuid: string; - filename?: string -} + filename?: string; +}; -export function useDevenvFileContentMutation(options: DevenvFileContentMutationOptions) { +export function useDevenvFileContentMutation( + options: DevenvFileContentMutationOptions, +) { const queryClient = useQueryClient(); const devenvStateMutation = useDevenvStateMutation({ - uuid: options.uuid - }) + uuid: options.uuid, + }); return useMutation({ - mutationKey: ['devenv', options.uuid, 'files', options.filename, 'content'], - mutationFn: (value: string) => axios.post( - (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv/" + options.uuid + "/files/" + options.filename, - value, - { - withCredentials: true - } - ), + mutationKey: ["devenv", options.uuid, "files", options.filename, "content"], + mutationFn: (value: string) => + axios.post( + (process.env.NEXT_PUBLIC_API ?? "") + + "/api/devenv/" + + options.uuid + + "/files/" + + options.filename, + value, + { + withCredentials: true, + }, + ), onSuccess: (_, value) => { - devenvStateMutation.mutate("ok") + devenvStateMutation.mutate("ok"); queryClient.setQueryData( - ['devenv', options.uuid, 'files', options.filename, 'content'], + ["devenv", options.uuid, "files", options.filename, "content"], () => { - return value - } - ) + return value; + }, + ); }, - }) + }); } diff --git a/service/frontend/src/hooks/use-devenv-file-content-query.ts b/service/frontend/src/hooks/use-devenv-file-content-query.ts index 4a09f9b..c78fc59 100644 --- a/service/frontend/src/hooks/use-devenv-file-content-query.ts +++ b/service/frontend/src/hooks/use-devenv-file-content-query.ts @@ -1,25 +1,34 @@ "use client"; -import { useQuery } from "@tanstack/react-query" +import { useQuery } from "@tanstack/react-query"; import axios from "axios"; export type DevenvFileContentQueryOptions = { uuid: string; filename?: string; -} +}; -export function useDevenvFileContentQuery(options: DevenvFileContentQueryOptions) { +export function useDevenvFileContentQuery( + options: DevenvFileContentQueryOptions, +) { return useQuery({ - queryKey: ['devenv', options.uuid, 'files', options.filename, 'content'], - queryFn: () => axios.get( - (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv/" + options.uuid + "/files/" + options.filename, - { - withCredentials: true - } - ).then((data) => { - return data.data - }), + queryKey: ["devenv", options.uuid, "files", options.filename, "content"], + queryFn: () => + axios + .get( + (process.env.NEXT_PUBLIC_API ?? "") + + "/api/devenv/" + + options.uuid + + "/files/" + + options.filename, + { + withCredentials: true, + }, + ) + .then((data) => { + return data.data; + }), staleTime: Infinity, - enabled: Boolean(options.filename) - }) + enabled: Boolean(options.filename), + }); } diff --git a/service/frontend/src/hooks/use-devenv-files-query.ts b/service/frontend/src/hooks/use-devenv-files-query.ts index ef39e1b..0aebf5f 100644 --- a/service/frontend/src/hooks/use-devenv-files-query.ts +++ b/service/frontend/src/hooks/use-devenv-files-query.ts @@ -4,24 +4,28 @@ import { useQuery } from "@tanstack/react-query"; import axios from "axios"; export type DevenvFilesQueryOptions = { - uuid: string, - callback?: (files: string[]) => void -} + uuid: string; + callback?: (files: string[]) => void; +}; export function useDevenvFilesQuery(options: DevenvFilesQueryOptions) { return useQuery({ - queryKey: ['devenv', options.uuid, 'files'], - queryFn: () => axios.get( - (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv/" + options.uuid + "/files", - { - withCredentials: true - } - ).then((data) => { - const files = data.data.sort(); - if (options.callback) - options.callback(files) - return files - }), - }) + queryKey: ["devenv", options.uuid, "files"], + queryFn: () => + axios + .get( + (process.env.NEXT_PUBLIC_API ?? "") + + "/api/devenv/" + + options.uuid + + "/files", + { + withCredentials: true, + }, + ) + .then((data) => { + const files = data.data.sort(); + if (options.callback) options.callback(files); + return files; + }), + }); } - diff --git a/service/frontend/src/hooks/use-devenv-generation-mutation.ts b/service/frontend/src/hooks/use-devenv-generation-mutation.ts index 1a23fbc..e776e8f 100644 --- a/service/frontend/src/hooks/use-devenv-generation-mutation.ts +++ b/service/frontend/src/hooks/use-devenv-generation-mutation.ts @@ -1,26 +1,32 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; export type DevenvGenerationMutationOptions = { - uuid?: string -} + uuid?: string; +}; -export function useDevenvGenerationMutation(options: DevenvGenerationMutationOptions) { +export function useDevenvGenerationMutation( + options: DevenvGenerationMutationOptions, +) { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (gen?: number | null) => { if (gen === null) { - queryClient.setQueryData(['devenv', options.uuid, 'generation'], null) + queryClient.setQueryData(["devenv", options.uuid, "generation"], null); } else if (gen !== undefined) { - queryClient.setQueryData(['devenv', options.uuid, 'generation'], gen) + queryClient.setQueryData(["devenv", options.uuid, "generation"], gen); } else { - let generation = queryClient.getQueryData(['devenv', options.uuid, 'generation']) + let generation = queryClient.getQueryData([ + "devenv", + options.uuid, + "generation", + ]); generation = generation ? generation + 1 : 1; - queryClient.setQueryData(['devenv', options.uuid, 'generation'], generation) + queryClient.setQueryData( + ["devenv", options.uuid, "generation"], + generation, + ); } - } + }, }); } - - - diff --git a/service/frontend/src/hooks/use-devenv-generation.ts b/service/frontend/src/hooks/use-devenv-generation.ts index f422a08..7e12954 100644 --- a/service/frontend/src/hooks/use-devenv-generation.ts +++ b/service/frontend/src/hooks/use-devenv-generation.ts @@ -4,20 +4,20 @@ import { useQuery } from "@tanstack/react-query"; import { useRef } from "react"; export type DevenvGenerationOptions = { - uuid: string -} + uuid: string; +}; export function useDevenvGeneration(options: DevenvGenerationOptions) { const generationRef = useRef(0); const query = useQuery({ - queryKey: ['devenv', options.uuid, 'generation'], + queryKey: ["devenv", options.uuid, "generation"], staleTime: Infinity, - retry: false - }) + retry: false, + }); if (query.isSuccess && query.data !== undefined) { generationRef.current = query.data; - return generationRef.current + return generationRef.current; } else { - return generationRef.current + return generationRef.current; } } diff --git a/service/frontend/src/hooks/use-devenv-query.ts b/service/frontend/src/hooks/use-devenv-query.ts index c0779de..d703e39 100644 --- a/service/frontend/src/hooks/use-devenv-query.ts +++ b/service/frontend/src/hooks/use-devenv-query.ts @@ -1,25 +1,27 @@ "use client"; import { Devenv } from "@/lib/types"; -import { useQuery } from "@tanstack/react-query" +import { useQuery } from "@tanstack/react-query"; import axios from "axios"; export type DevenvQueryOptions = { - uuid: string -} + uuid: string; +}; export function useDevenvQuery(options: DevenvQueryOptions) { return useQuery({ - queryKey: ['devenv', options.uuid], - queryFn: () => axios.get( - (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv/" + options.uuid, - { - withCredentials: true - } - ).then((data) => { - return data.data - }), + queryKey: ["devenv", options.uuid], + queryFn: () => + axios + .get( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv/" + options.uuid, + { + withCredentials: true, + }, + ) + .then((data) => { + return data.data; + }), staleTime: Infinity, - }) + }); } - diff --git a/service/frontend/src/hooks/use-devenv-state-mutation.ts b/service/frontend/src/hooks/use-devenv-state-mutation.ts index 7303c9d..04189f7 100644 --- a/service/frontend/src/hooks/use-devenv-state-mutation.ts +++ b/service/frontend/src/hooks/use-devenv-state-mutation.ts @@ -3,18 +3,15 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; export type DevenvStateMutationOptions = { - uuid?: string -} + uuid?: string; +}; export function useDevenvStateMutation(options: DevenvStateMutationOptions) { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (state: "ok" | "dirty") => { - queryClient.setQueryData(['devenv', options.uuid, 'state'], state) - } + queryClient.setQueryData(["devenv", options.uuid, "state"], state); + }, }); } - - - diff --git a/service/frontend/src/hooks/use-devenv-state.ts b/service/frontend/src/hooks/use-devenv-state.ts index 0a867b0..8f84de4 100644 --- a/service/frontend/src/hooks/use-devenv-state.ts +++ b/service/frontend/src/hooks/use-devenv-state.ts @@ -4,20 +4,20 @@ import { useQuery } from "@tanstack/react-query"; import { useRef } from "react"; export type DevenvStateOptions = { - uuid: string -} + uuid: string; +}; export function useDevenvState(options: DevenvStateOptions) { const generationRef = useRef("ok"); const query = useQuery<"ok" | "dirty" | undefined>({ - queryKey: ['devenv', options.uuid, 'state'], + queryKey: ["devenv", options.uuid, "state"], staleTime: Infinity, - retry: false - }) + retry: false, + }); if (query.isSuccess && query.data !== undefined) { generationRef.current = query.data; - return generationRef.current + return generationRef.current; } else { - return generationRef.current + return generationRef.current; } } diff --git a/service/frontend/src/hooks/use-devenvs-query.ts b/service/frontend/src/hooks/use-devenvs-query.ts index f98aab8..2f3a340 100644 --- a/service/frontend/src/hooks/use-devenvs-query.ts +++ b/service/frontend/src/hooks/use-devenvs-query.ts @@ -5,11 +5,11 @@ import { useQuery } from "@tanstack/react-query"; import axios from "axios"; export function useDevenvsQuery() { - return useQuery( - { - queryKey: ["devenvs"], - queryFn: () => axios.get((process.env.NEXT_PUBLIC_API ?? "") + '/api/devenv', { withCredentials: true }), - } - ) + return useQuery({ + queryKey: ["devenvs"], + queryFn: () => + axios.get((process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv", { + withCredentials: true, + }), + }); } - diff --git a/service/frontend/src/hooks/use-login-mutation.ts b/service/frontend/src/hooks/use-login-mutation.ts index 7617cc8..beede4a 100644 --- a/service/frontend/src/hooks/use-login-mutation.ts +++ b/service/frontend/src/hooks/use-login-mutation.ts @@ -5,28 +5,29 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; export type LoginPayload = { - username: string, - password: string -} + username: string; + password: string; +}; export type LoginMutationOptions = { onError?: () => Promise | undefined | void; -} +}; export function useLoginMutation(options: LoginMutationOptions) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (credentials: LoginPayload) => axios.post( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/auth/login', - credentials, - { - withCredentials: true - } - ), + mutationFn: (credentials: LoginPayload) => + axios.post( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/auth/login", + credentials, + { + withCredentials: true, + }, + ), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['user'] }) - navigate("/") + queryClient.invalidateQueries({ queryKey: ["user"] }); + navigate("/"); }, - onError: options.onError - }) + onError: options.onError, + }); } diff --git a/service/frontend/src/hooks/use-logout-mutation.ts b/service/frontend/src/hooks/use-logout-mutation.ts index 621a9db..1eabe7b 100644 --- a/service/frontend/src/hooks/use-logout-mutation.ts +++ b/service/frontend/src/hooks/use-logout-mutation.ts @@ -8,18 +8,17 @@ export function useLogoutMutation() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: () => axios.post( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/auth/logout', - undefined, - { - withCredentials: true - } - ), + mutationFn: () => + axios.post( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/auth/logout", + undefined, + { + withCredentials: true, + }, + ), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['user'] }) - navigate("/") - } - }) + queryClient.invalidateQueries({ queryKey: ["user"] }); + navigate("/"); + }, + }); } - - diff --git a/service/frontend/src/hooks/use-patch-devenv-mutation.ts b/service/frontend/src/hooks/use-patch-devenv-mutation.ts index 4287e82..59e92f1 100644 --- a/service/frontend/src/hooks/use-patch-devenv-mutation.ts +++ b/service/frontend/src/hooks/use-patch-devenv-mutation.ts @@ -5,40 +5,34 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; export type PatchDevenvMutationOptions = { - uuid: string, - onSuccess?: (data: Devenv) => void -} + uuid: string; + onSuccess?: (data: Devenv) => void; +}; export function usePatchDevenvMutation(options: PatchDevenvMutationOptions) { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (patch: PatchDevenvRequest) => axios.patch( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/devenv/' + options.uuid, - patch, - { - withCredentials: true - } - ), + mutationFn: (patch: PatchDevenvRequest) => + axios.patch( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/devenv/" + options.uuid, + patch, + { + withCredentials: true, + }, + ), onSuccess: (_, data) => { - queryClient.invalidateQueries({ queryKey: ['devenvs'] }) - queryClient.setQueryData( - ['devenv', options.uuid], - (oldData) => { - if (!oldData) - return oldData; - if (data.name && data.name !== "") - oldData.name = data.name - if (data.buildCmd && data.buildCmd !== "") - oldData.buildCmd = data.buildCmd - if (data.runCmd && data.runCmd !== "") - oldData.runCmd = data.runCmd - if (options.onSuccess) - options.onSuccess(oldData) - return { ...oldData } - } - ) + queryClient.invalidateQueries({ queryKey: ["devenvs"] }); + queryClient.setQueryData(["devenv", options.uuid], (oldData) => { + if (!oldData) return oldData; + if (data.name && data.name !== "") oldData.name = data.name; + if (data.buildCmd && data.buildCmd !== "") + oldData.buildCmd = data.buildCmd; + if (data.runCmd && data.runCmd !== "") oldData.runCmd = data.runCmd; + if (options.onSuccess) options.onSuccess(oldData); + return { ...oldData }; + }); }, - }) + }); } diff --git a/service/frontend/src/hooks/use-register-mutation.ts b/service/frontend/src/hooks/use-register-mutation.ts index 7d89f45..774623e 100644 --- a/service/frontend/src/hooks/use-register-mutation.ts +++ b/service/frontend/src/hooks/use-register-mutation.ts @@ -1,31 +1,31 @@ -import { navigate } from "@/actions/navigate" -import { useMutation } from "@tanstack/react-query" -import axios from "axios" +import { navigate } from "@/actions/navigate"; +import { useMutation } from "@tanstack/react-query"; +import axios from "axios"; export type RegisterPayload = { - username: string, - password: string, -} + username: string; + password: string; +}; export type RegisterMutationOptions = { - onError?: () => void -} + onError?: () => void; +}; export function useRegisterMutation(options: RegisterMutationOptions) { return useMutation({ - mutationFn: (credentials: RegisterPayload) => axios.post( - (process.env.NEXT_PUBLIC_API ?? "") + '/api/auth/register', - credentials, - { - withCredentials: true - } - ), + mutationFn: (credentials: RegisterPayload) => + axios.post( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/auth/register", + credentials, + { + withCredentials: true, + }, + ), onSuccess: () => { - navigate("/login") + navigate("/login"); }, onError: () => { - if (options.onError) - options.onError() - } - }) + if (options.onError) options.onError(); + }, + }); } diff --git a/service/frontend/src/hooks/use-repl-sessions-query.ts b/service/frontend/src/hooks/use-repl-sessions-query.ts index 039b574..79f17ea 100644 --- a/service/frontend/src/hooks/use-repl-sessions-query.ts +++ b/service/frontend/src/hooks/use-repl-sessions-query.ts @@ -4,10 +4,12 @@ import { useQuery } from "@tanstack/react-query"; import axios from "axios"; export function useReplSessionsQuery() { - return useQuery( - { - queryKey: ["repl-sessions"], - queryFn: () => axios.get((process.env.NEXT_PUBLIC_API ?? "") + '/api/repl/sessions', { withCredentials: true }), - } - ) + return useQuery({ + queryKey: ["repl-sessions"], + queryFn: () => + axios.get( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/repl/sessions", + { withCredentials: true }, + ), + }); } diff --git a/service/frontend/src/hooks/use-user-query.ts b/service/frontend/src/hooks/use-user-query.ts index 50ebfd7..d4bdf50 100644 --- a/service/frontend/src/hooks/use-user-query.ts +++ b/service/frontend/src/hooks/use-user-query.ts @@ -6,14 +6,14 @@ import axios from "axios"; export function useUserQuery() { return useQuery({ - queryKey: ['user'], - queryFn: () => axios.get( - (process.env.NEXT_PUBLIC_API ?? "") + "/api/auth/user", - { - withCredentials: true - } - ), + queryKey: ["user"], + queryFn: () => + axios.get( + (process.env.NEXT_PUBLIC_API ?? "") + "/api/auth/user", + { + withCredentials: true, + }, + ), staleTime: Infinity, - }) + }); } - diff --git a/service/frontend/src/lib/attach-addon.ts b/service/frontend/src/lib/attach-addon.ts index 7540a93..c25fe40 100644 --- a/service/frontend/src/lib/attach-addon.ts +++ b/service/frontend/src/lib/attach-addon.ts @@ -1,5 +1,5 @@ -import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm'; -import type { AttachAddon as IAttachApi } from '@xterm/addon-attach'; +import type { Terminal, IDisposable, ITerminalAddon } from "@xterm/xterm"; +import type { AttachAddon as IAttachApi } from "@xterm/addon-attach"; interface IAttachOptions { bidirectional?: boolean; @@ -13,41 +13,49 @@ export class AttachAddon implements ITerminalAddon, IAttachApi { constructor(socket: WebSocket, options?: IAttachOptions) { this._socket = socket; // always set binary type to arraybuffer, we do not handle blobs - this._socket.binaryType = 'arraybuffer'; + this._socket.binaryType = "arraybuffer"; this._bidirectional = !(options && options.bidirectional === false); } public activate(terminal: Terminal): void { this._disposables.push( - addSocketListener(this._socket, 'message', ev => { + addSocketListener(this._socket, "message", (ev) => { const data: ArrayBuffer | string = ev.data; - terminal.write(typeof data === 'string' ? data : new Uint8Array(data)); - }) + terminal.write(typeof data === "string" ? data : new Uint8Array(data)); + }), ); const handleEscape = (event: KeyboardEvent) => { if (event.code === "Escape") { event.preventDefault(); - this._socket.send(JSON.stringify({ stdin: '\x1B' })); + this._socket.send(JSON.stringify({ stdin: "\x1B" })); terminal.focus(); } - } + }; - document.addEventListener('keydown', handleEscape); + document.addEventListener("keydown", handleEscape); this._disposables.push({ dispose() { - document.removeEventListener('keydown', handleEscape); + document.removeEventListener("keydown", handleEscape); }, - }) + }); if (this._bidirectional) { - this._disposables.push(terminal.onData(data => this._sendData(data))); - this._disposables.push(terminal.onBinary(data => this._sendBinary(data))); - this._disposables.push(terminal.onResize(data => this._sendResize(data))); + this._disposables.push(terminal.onData((data) => this._sendData(data))); + this._disposables.push( + terminal.onBinary((data) => this._sendBinary(data)), + ); + this._disposables.push( + terminal.onResize((data) => this._sendResize(data)), + ); } - this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose())); - this._disposables.push(addSocketListener(this._socket, 'error', () => this.dispose())); + this._disposables.push( + addSocketListener(this._socket, "close", () => this.dispose()), + ); + this._disposables.push( + addSocketListener(this._socket, "error", () => this.dispose()), + ); } public dispose(): void { @@ -72,7 +80,7 @@ export class AttachAddon implements ITerminalAddon, IAttachApi { return; } - data = this._toStdin(data) + data = this._toStdin(data); const buffer = new Uint8Array(data.length); for (let i = 0; i < data.length; ++i) { @@ -81,7 +89,7 @@ export class AttachAddon implements ITerminalAddon, IAttachApi { this._socket.send(buffer); } - private _sendResize(data: { cols: number, rows: number }): void { + private _sendResize(data: { cols: number; rows: number }): void { if (!this._checkOpenSocket()) { return; } @@ -94,19 +102,23 @@ export class AttachAddon implements ITerminalAddon, IAttachApi { case WebSocket.OPEN: return true; case WebSocket.CONNECTING: - throw new Error('Attach addon was loaded before socket was open'); + throw new Error("Attach addon was loaded before socket was open"); case WebSocket.CLOSING: - console.warn('Attach addon socket is closing'); + console.warn("Attach addon socket is closing"); return false; case WebSocket.CLOSED: - throw new Error('Attach addon socket is closed'); + throw new Error("Attach addon socket is closed"); default: - throw new Error('Unexpected socket state'); + throw new Error("Unexpected socket state"); } } } -function addSocketListener(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable { +function addSocketListener( + socket: WebSocket, + type: K, + handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any, +): IDisposable { socket.addEventListener(type, handler); return { dispose: () => { @@ -115,6 +127,6 @@ function addSocketListener(socket: WebSocket, return; } socket.removeEventListener(type, handler); - } + }, }; } diff --git a/service/frontend/src/lib/types.ts b/service/frontend/src/lib/types.ts index 0e152a0..e21eb59 100644 --- a/service/frontend/src/lib/types.ts +++ b/service/frontend/src/lib/types.ts @@ -1,34 +1,33 @@ export type Devenv = { - id: string, - public: boolean, - name: string, - buildCmd: string, - runCmd: string, - created: string, - updated: string -} + id: string; + public: boolean; + name: string; + buildCmd: string; + runCmd: string; + created: string; + updated: string; +}; export type GetUserResponse = { - id: string, - username: string, - created: string, - updated: string, - deleted: string, -} + id: string; + username: string; + created: string; + updated: string; + deleted: string; +}; export type CreateDevenvRequest = { - name: string, - buildCmd: string, - runCmd: string -} + name: string; + buildCmd: string; + runCmd: string; +}; export type PatchDevenvRequest = { - name?: string, - buildCmd?: string, - runCmd?: string -} + name?: string; + buildCmd?: string; + runCmd?: string; +}; export type CreateDevenvResponse = { - devenvUuid: string, -} - + devenvUuid: string; +}; diff --git a/service/frontend/src/lib/utils.ts b/service/frontend/src/lib/utils.ts index 933d112..1afaf13 100644 --- a/service/frontend/src/lib/utils.ts +++ b/service/frontend/src/lib/utils.ts @@ -1,13 +1,13 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } export function randomString(n: number): string { - const charSet = 'abcdefghijklmnopqrstuvwxyz012345'; - var str = ''; + const charSet = "abcdefghijklmnopqrstuvwxyz012345"; + var str = ""; for (let i = 0; i < n; i++) { const j = Math.floor(Math.random() * charSet.length); str += charSet[j]; @@ -20,5 +20,5 @@ export async function sleep(ms: number) { setTimeout(() => { resolve(null); }, ms); - }) + }); }