Merge branch 'main' into claude/cleanup-next-gitignore-mkitk4rcv33vsp0t-M7IDl
This commit is contained in:
commit
ba81ae7849
26 changed files with 1275 additions and 229 deletions
|
|
@ -4,11 +4,10 @@ import { CodeEditor } from '@/components/CodeEditor';
|
||||||
import { AIChat } from '@/components/AIChat';
|
import { AIChat } from '@/components/AIChat';
|
||||||
import { Toolbar } from '@/components/Toolbar';
|
import { Toolbar } from '@/components/Toolbar';
|
||||||
import { TemplatesDrawer } from '@/components/TemplatesDrawer';
|
import { TemplatesDrawer } from '@/components/TemplatesDrawer';
|
||||||
import { WelcomeDialog } from '@/components/WelcomeDialog';
|
|
||||||
import { FileTree, FileNode } from '@/components/FileTree';
|
import { FileTree, FileNode } from '@/components/FileTree';
|
||||||
import { FileTabs } from '@/components/FileTabs';
|
import { FileTabs } from '@/components/FileTabs';
|
||||||
import { PreviewModal } from '@/components/PreviewModal';
|
import { PreviewModal } from '@/components/PreviewModal';
|
||||||
import { NewProjectModal, ProjectConfig } from '@/components/NewProjectModal';
|
// Removed named imports for WelcomeDialog and NewProjectModal. Use lazy-loaded versions from src/App.tsx.
|
||||||
import { ConsolePanel } from '@/components/ConsolePanel';
|
import { ConsolePanel } from '@/components/ConsolePanel';
|
||||||
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
|
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
|
||||||
import { useKV } from '@github/spark/hooks';
|
import { useKV } from '@github/spark/hooks';
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,9 @@ const buttonVariants = cva(
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
|
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
||||||
|
size?: "default" | "sm" | "lg" | "icon";
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
|
|
||||||
|
|
@ -53,4 +53,64 @@ const DialogContent = React.forwardRef<
|
||||||
))
|
))
|
||||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent }
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot="dialog-title"
|
||||||
|
className={cn("text-lg leading-none font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot="dialog-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogClose,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,18 @@ const DropdownMenuSeparator = React.forwardRef<
|
||||||
))
|
))
|
||||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const DropdownMenuLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
|
@ -70,4 +82,5 @@ export {
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuLabel,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
github-spark-0.44.15.tgz
Normal file
BIN
github-spark-0.44.15.tgz
Normal file
Binary file not shown.
|
|
@ -1,7 +1,13 @@
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
swcMinify: true,
|
swcMinify: true,
|
||||||
|
webpack: (config) => {
|
||||||
|
config.resolve.alias['@'] = path.resolve(__dirname, 'src');
|
||||||
|
return config;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig
|
||||||
|
|
|
||||||
1119
package-lock.json
generated
1119
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -14,6 +14,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
|
"@phosphor-icons/react": "^2.1.10",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
|
|
@ -32,15 +33,19 @@
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@radix-ui/react-toast": "^1.2.4",
|
"@radix-ui/react-toast": "^1.2.4",
|
||||||
"@radix-ui/react-tooltip": "^1.1.6",
|
"@radix-ui/react-tooltip": "^1.1.6",
|
||||||
|
"@sentry/browser": "^10.34.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^11.15.0",
|
"framer-motion": "^11.15.0",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.52.2",
|
||||||
"next": "14.2.15",
|
"next": "14.2.15",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"posthog-js": "^1.328.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-error-boundary": "^6.1.0",
|
"react-error-boundary": "^6.1.0",
|
||||||
|
"react-resizable-panels": "^4.4.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
|
|
@ -53,6 +58,8 @@
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
|
"@types/node": "22.19.7",
|
||||||
|
"@types/react": "18.3.27",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { initSentry, captureError } from './lib/sentry';
|
||||||
import { LoadingSpinner } from './components/ui/loading-spinner';
|
import { LoadingSpinner } from './components/ui/loading-spinner';
|
||||||
|
|
||||||
import { PlatformId } from './lib/platforms';
|
import { PlatformId } from './lib/platforms';
|
||||||
|
import { ProjectConfig } from './components/NewProjectModal';
|
||||||
|
|
||||||
// Lazy load heavy/modal components for code splitting and better initial load
|
// Lazy load heavy/modal components for code splitting and better initial load
|
||||||
const TemplatesDrawer = lazy(() => import('./components/TemplatesDrawer').then(m => ({ default: m.TemplatesDrawer })));
|
const TemplatesDrawer = lazy(() => import('./components/TemplatesDrawer').then(m => ({ default: m.TemplatesDrawer })));
|
||||||
|
|
@ -513,7 +514,7 @@ end)`,
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<ResizablePanelGroup direction="horizontal">
|
<ResizablePanelGroup orientation="horizontal">
|
||||||
<ResizablePanel defaultSize={15} minSize={10} maxSize={25}>
|
<ResizablePanel defaultSize={15} minSize={10} maxSize={25}>
|
||||||
<FileTree
|
<FileTree
|
||||||
files={files || []}
|
files={files || []}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import { useKV } from '@github/spark/hooks';
|
import { usePersistentState } from '@/lib/usePersistentState';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { LoadingSpinner } from './ui/loading-spinner';
|
import { LoadingSpinner } from './ui/loading-spinner';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
@ -19,7 +19,7 @@ export function CodeEditor({ onCodeChange, platform = 'roblox' }: CodeEditorProp
|
||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
const editorLanguage = languageMap[platform];
|
const editorLanguage = languageMap[platform];
|
||||||
const [code, setCode] = useKV('aethex-current-code', `-- Welcome to AeThex Studio!
|
const [code, setCode] = usePersistentState('aethex-current-code', `-- Welcome to AeThex Studio!
|
||||||
-- Write your Roblox Lua code here
|
-- Write your Roblox Lua code here
|
||||||
|
|
||||||
local Players = game:GetService("Players")
|
local Players = game:GetService("Players")
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from './ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from './ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from './ui/label';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from './ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from './ui/badge';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from './ui/checkbox';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from './ui/switch';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||||
import { GameController, Globe, DeviceMobile, FileCode, Info, Check } from '@phosphor-icons/react';
|
import { GameController, Globe, DeviceMobile, FileCode, Info, Check } from '@phosphor-icons/react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ export function NewProjectModal({ open, onClose, onCreateProject }: NewProjectMo
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="platform-roblox"
|
id="platform-roblox"
|
||||||
checked={platforms.roblox}
|
checked={platforms.roblox}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked: boolean) =>
|
||||||
setPlatforms((p) => ({ ...p, roblox: checked === true }))
|
setPlatforms((p) => ({ ...p, roblox: checked === true }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -218,7 +218,7 @@ export function NewProjectModal({ open, onClose, onCreateProject }: NewProjectMo
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="platform-web"
|
id="platform-web"
|
||||||
checked={platforms.web}
|
checked={platforms.web}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked: boolean) =>
|
||||||
setPlatforms((p) => ({ ...p, web: checked === true }))
|
setPlatforms((p) => ({ ...p, web: checked === true }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -231,7 +231,7 @@ export function NewProjectModal({ open, onClose, onCreateProject }: NewProjectMo
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="platform-mobile"
|
id="platform-mobile"
|
||||||
checked={platforms.mobile}
|
checked={platforms.mobile}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked: boolean) =>
|
||||||
setPlatforms((p) => ({ ...p, mobile: checked === true }))
|
setPlatforms((p) => ({ ...p, mobile: checked === true }))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { Button } from '@/components/ui/button';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -11,10 +10,11 @@ import {
|
||||||
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List, ArrowsLeftRight } from '@phosphor-icons/react';
|
import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List, ArrowsLeftRight } from '@phosphor-icons/react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useState, useEffect, useCallback, memo } from 'react';
|
import { useState, useEffect, useCallback, memo } from 'react';
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './ui/dialog';
|
||||||
import { ThemeSwitcher } from './ThemeSwitcher';
|
import { ThemeSwitcher } from './ThemeSwitcher';
|
||||||
import { PlatformSelector } from './PlatformSelector';
|
import { PlatformSelector } from './PlatformSelector';
|
||||||
import { PlatformId } from '@/lib/platforms';
|
import { PlatformId } from '../lib/platforms';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
|
||||||
interface ToolbarProps {
|
interface ToolbarProps {
|
||||||
code: string;
|
code: string;
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ import {
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Sparkle, Code, FileCode } from '@phosphor-icons/react';
|
import { Sparkle, Code, FileCode } from '@phosphor-icons/react';
|
||||||
import { useKV } from '@github/spark/hooks';
|
import { usePersistentState } from '@/lib/usePersistentState';
|
||||||
|
|
||||||
export function WelcomeDialog() {
|
export function WelcomeDialog() {
|
||||||
const [hasSeenWelcome, setHasSeenWelcome] = useKV('aethex-welcome-seen', 'false');
|
const [hasSeenWelcome, setHasSeenWelcome] = usePersistentState('aethex-welcome-seen', 'false');
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,7 @@ function DropdownMenuSubContent({
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
DropdownMenu,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
|
@ -254,6 +255,14 @@ export {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
}
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import { cn } from "@/lib/utils"
|
||||||
function ResizablePanelGroup({
|
function ResizablePanelGroup({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
|
}: ComponentProps<typeof ResizablePrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<ResizablePrimitive.PanelGroup
|
<ResizablePrimitive.Group
|
||||||
data-slot="resizable-panel-group"
|
data-slot="resizable-panel-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||||
|
|
@ -30,11 +30,11 @@ function ResizableHandle({
|
||||||
withHandle,
|
withHandle,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
}: ComponentProps<typeof ResizablePrimitive.Separator> & {
|
||||||
withHandle?: boolean
|
withHandle?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ResizablePrimitive.PanelResizeHandle
|
<ResizablePrimitive.Separator
|
||||||
data-slot="resizable-handle"
|
data-slot="resizable-handle"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||||
|
|
@ -47,7 +47,7 @@ function ResizableHandle({
|
||||||
<GripVerticalIcon className="size-2.5" />
|
<GripVerticalIcon className="size-2.5" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ResizablePrimitive.PanelResizeHandle>
|
</ResizablePrimitive.Separator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,51 +3,196 @@ export const chapters = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'Introduction to Roblox Scripting',
|
title: 'Introduction to Roblox Scripting',
|
||||||
content: `# Chapter 1: Introduction to Roblox Scripting\n\nWelcome to Roblox development!\n\n**Learning Objectives:**\n- Understand what Roblox Studio is\n- Learn why Lua is used\n- Write your first script\n\n**What is Roblox Studio?**\nRoblox Studio is the official IDE for creating Roblox games. It lets you build worlds, write scripts, and publish games.\n\n**Why Lua?**\nLua is a lightweight scripting language used for game logic in Roblox.\n\n**Your First Script:**\nPaste this in a Script object:\n\n\n```lua\nprint('Hello, Roblox!')\n```\n\nTry running your game to see the output!`
|
content: `# Chapter 1: Introduction to Roblox Scripting
|
||||||
|
|
||||||
|
Welcome to Roblox development!
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Understand what Roblox Studio is
|
||||||
|
- Learn why Lua is used
|
||||||
|
- Write your first script
|
||||||
|
|
||||||
|
**What is Roblox Studio?**
|
||||||
|
Roblox Studio is the official IDE for creating Roblox games. It lets you build worlds, write scripts, and publish games.
|
||||||
|
|
||||||
|
**Why Lua?**
|
||||||
|
Lua is a lightweight scripting language used for game logic in Roblox.
|
||||||
|
|
||||||
|
**Your First Script:**
|
||||||
|
Paste this in a Script object:
|
||||||
|
|
||||||
|
\`\`\`lua
|
||||||
|
print('Hello, Roblox!')
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Try running your game to see the output!`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'Variables and Data Types',
|
title: 'Variables and Data Types',
|
||||||
content: `# Chapter 2: Variables and Data Types\n\n**Learning Objectives:**\n- Use variables to store data\n- Understand numbers, strings, tables\n\n**Variables:**\n```lua\nlocal score = 10\nlocal playerName = "Alex"\n```\n\n**Tables:**\n```lua\nlocal inventory = {"Sword", "Shield"}\nprint(inventory[1]) -- Sword\n```\n`
|
content: `# Chapter 2: Variables and Data Types
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Use variables to store data
|
||||||
|
- Understand numbers, strings, tables
|
||||||
|
|
||||||
|
**Variables:**
|
||||||
|
\`\`\`lua
|
||||||
|
local score = 10
|
||||||
|
local playerName = "Alex"
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Tables:**
|
||||||
|
\`\`\`lua
|
||||||
|
local inventory = {"Sword", "Shield"}
|
||||||
|
print(inventory[1]) -- Sword
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Functions and Events',
|
title: 'Functions and Events',
|
||||||
content: `# Chapter 3: Functions and Events\n\n**Learning Objectives:**\n- Write reusable functions\n- Respond to game events\n\n**Functions:**\n```lua\nfunction greet(name)\n print("Hello, " .. name)\nend\ngreet("Alex")\n```\n\n**Events:**\n```lua\nlocal Players = game:GetService("Players")\nPlayers.PlayerAdded:Connect(function(player)\n print(player.Name .. " joined!")\nend)\n```\n`
|
content: `# Chapter 3: Functions and Events
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Write reusable functions
|
||||||
|
- Respond to game events
|
||||||
|
|
||||||
|
**Functions:**
|
||||||
|
\`\`\`lua
|
||||||
|
function greet(name)
|
||||||
|
print("Hello, " .. name)
|
||||||
|
end
|
||||||
|
greet("Alex")
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Events:**
|
||||||
|
\`\`\`lua
|
||||||
|
local Players = game:GetService("Players")
|
||||||
|
Players.PlayerAdded:Connect(function(player)
|
||||||
|
print(player.Name .. " joined!")
|
||||||
|
end)
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
title: 'Game Objects and Hierarchy',
|
title: 'Game Objects and Hierarchy',
|
||||||
content: `# Chapter 4: Game Objects and Hierarchy\n\n**Learning Objectives:**\n- Explore Roblox object hierarchy\n- Access and modify game objects\n\n**Example:**\n```lua\nlocal workspace = game.Workspace\nlocal part = workspace.Part\npart.BrickColor = BrickColor.new("Bright red")\n```\n`
|
content: `# Chapter 4: Game Objects and Hierarchy
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Explore Roblox object hierarchy
|
||||||
|
- Access and modify game objects
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
\`\`\`lua
|
||||||
|
local workspace = game.Workspace
|
||||||
|
local part = workspace.Part
|
||||||
|
part.BrickColor = BrickColor.new("Bright red")
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
title: 'Player Interaction',
|
title: 'Player Interaction',
|
||||||
content: `# Chapter 5: Player Interaction\n\n**Learning Objectives:**\n- Detect player actions\n- Respond to input\n\n**Example:**\n```lua\nlocal Players = game:GetService("Players")\nPlayers.PlayerAdded:Connect(function(player)\n player.Chatted:Connect(function(message)\n print(player.Name .. " said: " .. message)\n end)\nend)\n```\n`
|
content: `# Chapter 5: Player Interaction
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Detect player actions
|
||||||
|
- Respond to input
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
\`\`\`lua
|
||||||
|
local Players = game:GetService("Players")
|
||||||
|
Players.PlayerAdded:Connect(function(player)
|
||||||
|
player.Chatted:Connect(function(message)
|
||||||
|
print(player.Name .. " said: " .. message)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
title: 'Building Game Logic',
|
title: 'Building Game Logic',
|
||||||
content: `# Chapter 6: Building Game Logic\n\n**Learning Objectives:**\n- Create rules and mechanics\n- Use conditions and loops\n\n**Example:**\n```lua\nlocal score = 0\nwhile score < 10 do\n score = score + 1\n print("Score:", score)\nend\n```\n`
|
content: `# Chapter 6: Building Game Logic
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Create rules and mechanics
|
||||||
|
- Use conditions and loops
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
\`\`\`lua
|
||||||
|
local score = 0
|
||||||
|
while score < 10 do
|
||||||
|
score = score + 1
|
||||||
|
print("Score:", score)
|
||||||
|
end
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 7,
|
||||||
title: 'Saving Data',
|
title: 'Saving Data',
|
||||||
content: `# Chapter 7: Saving Data\n\n**Learning Objectives:**\n- Store player progress\n- Use DataStore service\n\n**Example:**\n```lua\nlocal DataStoreService = game:GetService("DataStoreService")\nlocal scoreStore = DataStoreService:GetDataStore("Scores")\n\n-- Save score\nscoreStore:SetAsync("player_123", 100)\n-- Load score\nlocal score = scoreStore:GetAsync("player_123")\nprint(score)\n```\n`
|
content: `# Chapter 7: Saving Data
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Store player progress
|
||||||
|
- Use DataStore service
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
\`\`\`lua
|
||||||
|
local DataStoreService = game:GetService("DataStoreService")
|
||||||
|
local scoreStore = DataStoreService:GetDataStore("Scores")
|
||||||
|
|
||||||
|
-- Save score
|
||||||
|
scoreStore:SetAsync("player_123", 100)
|
||||||
|
-- Load score
|
||||||
|
local score = scoreStore:GetAsync("player_123")
|
||||||
|
print(score)
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 8,
|
||||||
title: 'Deploying Your Game',
|
title: 'Deploying Your Game',
|
||||||
content: `# Chapter 8: Deploying Your Game\n\n**Learning Objectives:**\n- Publish your game to Roblox\n- Update and manage releases\n\n**Steps:**\n1. Click "File > Publish to Roblox As..."\n2. Choose a name and description\n3. Set permissions and publish\n`
|
content: `# Chapter 8: Deploying Your Game
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Publish your game to Roblox
|
||||||
|
- Update and manage releases
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Click "File > Publish to Roblox As..."
|
||||||
|
2. Choose a name and description
|
||||||
|
3. Set permissions and publish
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 9,
|
||||||
title: 'Debugging and Testing',
|
title: 'Debugging and Testing',
|
||||||
content: `# Chapter 9: Debugging and Testing\n\n**Learning Objectives:**\n- Find and fix bugs\n- Use print statements and breakpoints\n\n**Example:**\n```lua\nprint("Debug: Player joined")\n-- Use breakpoints in Studio to pause and inspect\n```\n`
|
content: `# Chapter 9: Debugging and Testing
|
||||||
|
|
||||||
|
**Learning Objectives:**
|
||||||
|
- Find and fix bugs
|
||||||
|
- Use print statements and breakpoints
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
\`\`\`lua
|
||||||
|
print("Debug: Player joined")
|
||||||
|
-- Use breakpoints in Studio to pause and inspect
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 10,
|
id: 10,
|
||||||
title: 'Next Steps and Resources',
|
title: 'Next Steps and Resources',
|
||||||
content: `# Chapter 10: Next Steps and Resources\n\n**Continue Learning:**\n- Join the [Roblox Developer Forum](https://devforum.roblox.com/)\n- Explore [Roblox Education](https://education.roblox.com/)\n- Try building your own game!\n`
|
content: `# Chapter 10: Next Steps and Resources
|
||||||
|
|
||||||
|
**Continue Learning:**
|
||||||
|
- Join the [Roblox Developer Forum](https://devforum.roblox.com/)
|
||||||
|
- Explore [Roblox Education](https://education.roblox.com/)
|
||||||
|
- Try building your own game!
|
||||||
|
`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,11 @@ export interface ScriptTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTemplatesForPlatform(platform: PlatformId): ScriptTemplate[] {
|
export function getTemplatesForPlatform(platform: PlatformId): ScriptTemplate[] {
|
||||||
return templates.filter(t => t.platform === platform);
|
return templates.filter(t => t.platform === platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { getTemplatesForPlatform };
|
||||||
|
|
||||||
const robloxTemplates: ScriptTemplate[] = [
|
const robloxTemplates: ScriptTemplate[] = [
|
||||||
{
|
{
|
||||||
id: 'hello-world',
|
id: 'hello-world',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { PlatformId, getPlatform } from './platforms';
|
import { PlatformId, getPlatform } from './platforms';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { captureEvent, captureError } from './analytics';
|
import { trackEvent, trackError } from './analytics';
|
||||||
|
|
||||||
export interface TranslationRequest {
|
export interface TranslationRequest {
|
||||||
sourceCode: string;
|
sourceCode: string;
|
||||||
|
|
@ -199,7 +199,7 @@ async function translateWithClaudeAPI(
|
||||||
const result = parseClaudeResponse(content, request.targetPlatform);
|
const result = parseClaudeResponse(content, request.targetPlatform);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureError(error as Error, {
|
trackError(error as Error, {
|
||||||
context: 'translation_api',
|
context: 'translation_api',
|
||||||
sourcePlatform: request.sourcePlatform,
|
sourcePlatform: request.sourcePlatform,
|
||||||
targetPlatform: request.targetPlatform,
|
targetPlatform: request.targetPlatform,
|
||||||
|
|
@ -292,7 +292,7 @@ export async function translateCode(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log translation attempt
|
// Log translation attempt
|
||||||
captureEvent('translation_started', {
|
trackEvent('translation_started', {
|
||||||
sourcePlatform: request.sourcePlatform,
|
sourcePlatform: request.sourcePlatform,
|
||||||
targetPlatform: request.targetPlatform,
|
targetPlatform: request.targetPlatform,
|
||||||
codeLength: request.sourceCode.length,
|
codeLength: request.sourceCode.length,
|
||||||
|
|
@ -303,7 +303,7 @@ export async function translateCode(
|
||||||
|
|
||||||
// Log result
|
// Log result
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
captureEvent('translation_success', {
|
trackEvent('translation_success', {
|
||||||
sourcePlatform: request.sourcePlatform,
|
sourcePlatform: request.sourcePlatform,
|
||||||
targetPlatform: request.targetPlatform,
|
targetPlatform: request.targetPlatform,
|
||||||
});
|
});
|
||||||
|
|
@ -311,7 +311,7 @@ export async function translateCode(
|
||||||
`Translated ${request.sourcePlatform} → ${request.targetPlatform}`
|
`Translated ${request.sourcePlatform} → ${request.targetPlatform}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
captureEvent('translation_failed', {
|
trackEvent('translation_failed', {
|
||||||
sourcePlatform: request.sourcePlatform,
|
sourcePlatform: request.sourcePlatform,
|
||||||
targetPlatform: request.targetPlatform,
|
targetPlatform: request.targetPlatform,
|
||||||
error: result.error,
|
error: result.error,
|
||||||
|
|
@ -321,7 +321,7 @@ export async function translateCode(
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureError(error as Error, { context: 'translate_code' });
|
trackError(error as Error, { context: 'translate_code' });
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Unexpected error: ${(error as Error).message}`,
|
error: `Unexpected error: ${(error as Error).message}`,
|
||||||
|
|
|
||||||
27
src/lib/usePersistentState.ts
Normal file
27
src/lib/usePersistentState.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* usePersistentState is a React hook that syncs state with localStorage.
|
||||||
|
* @param key The key to store the value under in localStorage.
|
||||||
|
* @param initialValue The initial value to use if nothing is stored.
|
||||||
|
*/
|
||||||
|
export function usePersistentState<T>(key: string, initialValue: T): [T, (value: T) => void] {
|
||||||
|
const [state, setState] = useState<T>(() => {
|
||||||
|
if (typeof window === 'undefined') return initialValue;
|
||||||
|
try {
|
||||||
|
const item = window.localStorage.getItem(key);
|
||||||
|
return item ? (JSON.parse(item) as T) : initialValue;
|
||||||
|
} catch {
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(state));
|
||||||
|
} catch {}
|
||||||
|
}, [key, state]);
|
||||||
|
|
||||||
|
return [state, setState];
|
||||||
|
}
|
||||||
5
src/types/lucide-react__icons__grip-vertical.d.ts
vendored
Normal file
5
src/types/lucide-react__icons__grip-vertical.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
declare module 'lucide-react/dist/esm/icons/grip-vertical' {
|
||||||
|
import { FC, SVGProps } from 'react';
|
||||||
|
const GripVerticalIcon: FC<SVGProps<SVGSVGElement>>;
|
||||||
|
export default GripVerticalIcon;
|
||||||
|
}
|
||||||
1
src/types/phosphor-icons__react.d.ts
vendored
Normal file
1
src/types/phosphor-icons__react.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
declare module '@phosphor-icons/react';
|
||||||
1
src/types/sonner.d.ts
vendored
Normal file
1
src/types/sonner.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
declare module 'sonner';
|
||||||
3
src/types/window-spark.d.ts
vendored
Normal file
3
src/types/window-spark.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface Window {
|
||||||
|
spark?: any;
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
|
|
||||||
1
types/phosphor-icons__react.d.ts
vendored
Normal file
1
types/phosphor-icons__react.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
declare module '@phosphor-icons/react';
|
||||||
1
types/sonner.d.ts
vendored
Normal file
1
types/sonner.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
declare module 'sonner';
|
||||||
Loading…
Reference in a new issue