Generated by Spark: Create Lua code snippet library with insertable templates for common patterns

This commit is contained in:
2025-12-23 21:20:54 +00:00
parent 5718f6caa2
commit 924c08f54f
6 changed files with 1548 additions and 15 deletions

182
LUA_SNIPPETS_GUIDE.md Normal file
View File

@@ -0,0 +1,182 @@
# Lua Snippet Library - Quick Reference
The MetaBuilder Lua Snippet Library provides 30+ pre-built code templates to accelerate your development workflow.
## Categories
### Data Validation
- **Email Validation** - Validate email format using pattern matching
- **Password Strength Validator** - Check password meets security requirements
- **Phone Number Validation** - Validate US phone number format
- **Required Fields Validator** - Check multiple required fields are present
### Data Transformation
- **Snake Case to Camel Case** - Convert snake_case strings to camelCase
- **Flatten Nested Object** - Convert nested table to flat key-value pairs
- **Normalize User Data** - Clean and normalize user input data
### Array Operations
- **Filter Array** - Filter array elements by condition
- **Map Array** - Transform each array element
- **Reduce Array to Sum** - Calculate sum of numeric array values
- **Group Array by Property** - Group array items by a property value
- **Sort Array** - Sort array by property value
### String Processing
- **Create URL Slug** - Convert text to URL-friendly slug
- **Truncate Text** - Truncate long text with ellipsis
- **Extract Hashtags** - Find all hashtags in text
- **Word Counter** - Count words and characters in text
### Math & Calculations
- **Calculate Percentage** - Calculate percentage and format result
- **Calculate Discount** - Calculate price after discount
- **Compound Interest Calculator** - Calculate compound interest over time
- **Statistical Analysis** - Calculate mean, median, mode, std dev
### Conditionals & Logic
- **Role-Based Access Check** - Check if user has required role
- **Time-Based Logic** - Execute logic based on time of day
- **Feature Flag Checker** - Check if feature is enabled for user
### Error Handling
- **Try-Catch Pattern** - Safe execution with error handling
- **Validation Error Accumulator** - Collect all validation errors at once
### User Management
- **Build User Profile** - Create complete user profile from data
- **Log User Activity** - Create activity log entry
### Date & Time
- **Format Date** - Format timestamp in various ways
- **Calculate Date Difference** - Calculate difference between two dates
### Utilities
- **Safe JSON Parse** - Parse JSON string with error handling
- **Generate Unique ID** - Create unique identifier
- **Rate Limit Checker** - Check if action exceeds rate limit
- **Simple Cache Manager** - Cache data with expiration
## Usage
### In the Lua Editor
1. Click the **"Snippet Library"** button in the Lua code section
2. Browse categories or use the search bar
3. Click a snippet card to preview details
4. Click **"Insert"** to add the code at your cursor position
5. Customize the code for your needs
### Standalone View
1. Navigate to the **"Snippet Library"** tab in Level 4
2. Search or filter by category
3. Click any snippet to view full details
4. Click **"Copy"** to copy to clipboard
## Context API
All snippets use the MetaBuilder context API:
```lua
-- Access input data
local data = context.data or {}
-- Access current user
local user = context.user or {}
-- Log messages
log("Processing started...")
-- Return results
return { success = true, result = processedData }
```
## Common Patterns
### Validation Pattern
```lua
local data = context.data or {}
if not data.field then
return { valid = false, error = "Field is required" }
end
if not validateCondition(data.field) then
return { valid = false, error = "Validation failed" }
end
return { valid = true, data = data }
```
### Transformation Pattern
```lua
local input = context.data or {}
local output = {
field1 = transform(input.field1),
field2 = normalize(input.field2),
metadata = {
processedAt = os.time(),
version = "1.0"
}
}
return output
```
### Error Handling Pattern
```lua
local function riskyOperation()
-- operation that might fail
end
local success, result = pcall(riskyOperation)
if success then
return { success = true, result = result }
else
log("Error: " .. tostring(result))
return { success = false, error = tostring(result) }
end
```
## Tips
- **Search by functionality** - Use keywords like "validate", "calculate", "transform"
- **Check tags** - Tags help identify snippet capabilities quickly
- **Review parameters** - Each snippet documents required input parameters
- **Customize freely** - Snippets are starting points, modify as needed
- **Combine patterns** - Mix multiple snippets for complex logic
- **Test thoroughly** - Use the test runner to verify behavior with sample data
## Adding Custom Snippets
While the library comes with 30+ pre-built snippets, you can:
1. Copy existing snippets as templates
2. Modify to fit your use case
3. Save as new Lua scripts in your project
4. Reference from workflows
## Best Practices
**Do:**
- Use descriptive variable names
- Add comments for complex logic
- Validate input data before processing
- Return structured results
- Log important steps
- Handle edge cases
**Avoid:**
- Infinite loops (workflows have execution limits)
- Blocking operations
- Modifying global state
- Assuming data exists without checks
- Returning undefined or null values
## Support
For questions about snippets or to request new patterns:
- Check the snippet description and parameter docs
- Test with sample data in the Lua editor
- Review execution logs for debugging
- Consult the Lua language documentation

31
PRD.md
View File

@@ -55,11 +55,18 @@ This is a 4-tier meta-application builder: a public website layer, authenticated
- **Success criteria**: Nodes connect smoothly; execution order clear; can branch/merge; error handling; logs show execution path; integrates with Lua
### Lua Lambda System (Level 4)
- **Functionality**: Real Lua interpreter (fengari-web) with full language support, Monaco editor with syntax highlighting and autocomplete, parameter handling, context API access, and comprehensive execution feedback
- **Purpose**: Provide safe, sandboxed scripting for custom transformations, validations, and business logic with real Lua execution beyond declarative capabilities, enhanced by professional code editing experience
- **Functionality**: Real Lua interpreter (fengari-web) with full language support, Monaco editor with syntax highlighting and autocomplete, parameter handling, context API access, comprehensive execution feedback, and extensive snippet library with 30+ pre-built templates
- **Purpose**: Provide safe, sandboxed scripting for custom transformations, validations, and business logic with real Lua execution beyond declarative capabilities, enhanced by professional code editing experience and reusable code patterns
- **Trigger**: User adds "Lua Action" node in workflow or creates Lua script in scripts tab
- **Progression**: Open Monaco-based Lua editor → Define parameters → Write Lua code with syntax highlighting and autocomplete → Access context.data/user/kv via intelligent suggestions → Test with sample inputs → View execution logs → Return structured results → Integrate into workflows
- **Success criteria**: Monaco editor integrated with Lua language support; autocomplete provides context API suggestions (context.data, context.user, context.kv, log, print); syntax highlighting active; real Lua execution via fengari; parameter type validation; execution logs captured; return values parsed; syntax/runtime errors shown with line numbers; can transform JSON data; fullscreen editing mode available; integrates with workflow nodes
- **Progression**: Open Monaco-based Lua editor → Define parameters → Browse snippet library by category → Search and preview snippets → Insert template code → Customize with syntax highlighting and autocomplete → Access context.data/user/kv via intelligent suggestions → Test with sample inputs → View execution logs → Return structured results → Integrate into workflows
- **Success criteria**: Monaco editor integrated with Lua language support; autocomplete provides context API suggestions (context.data, context.user, context.kv, log, print); syntax highlighting active; real Lua execution via fengari; parameter type validation; execution logs captured; return values parsed; syntax/runtime errors shown with line numbers; can transform JSON data; fullscreen editing mode available; snippet library accessible with 30+ templates across 12 categories; snippets insertable at cursor position; integrates with workflow nodes
### Lua Snippet Library (Level 4)
- **Functionality**: Comprehensive library of 30+ pre-built Lua code templates organized into 12 categories (Data Validation, Data Transformation, Array Operations, String Processing, Math & Calculations, Conditionals & Logic, User Management, Error Handling, API & Networking, Date & Time, File Operations, Utilities)
- **Purpose**: Accelerate development by providing tested, reusable patterns for common operations; reduce errors; teach best practices
- **Trigger**: User clicks "Snippet Library" button in Lua editor or opens "Snippet Library" tab in Level 4
- **Progression**: Open snippet library → Browse by category or search by keyword/tag → Preview snippet details and parameters → View full code in syntax-highlighted display → Copy to clipboard or insert into editor → Customize for specific use case
- **Success criteria**: 30+ snippets covering common patterns; organized into logical categories; searchable by name, description, and tags; preview shows code, description, and required parameters; one-click copy or insert; snippets include validation, transformation, calculations, conditionals, string operations, array operations, date handling, error handling, and utilities; modal detail view for full inspection
## Edge Case Handling
- **Invalid User Credentials**: Show clear error message; rate limit after 5 attempts; support password reset flow
@@ -107,13 +114,14 @@ Animations should feel responsive and purposeful - immediate visual feedback for
- **Components**:
- Sidebar with collapsible sections for component catalog
- Resizable panels for canvas/inspector layout
- Card for component previews in catalog
- Dialog for login form and settings
- Tabs for switching between visual/code views
- ScrollArea for component lists and property panels
- Card for component previews in catalog and snippet library
- Dialog for login form, settings, and snippet detail view
- Sheet for slide-out snippet library panel
- Tabs for switching between visual/code views and snippet categories
- ScrollArea for component lists, property panels, and snippet browsing
- Input, Select, Switch, Slider for property editors
- Button throughout for actions
- Badge for component type indicators
- Badge for component type indicators and snippet tags
- Separator for visual hierarchy
- Tooltip for help text on hover
- Sonner for notifications
@@ -126,13 +134,16 @@ Animations should feel responsive and purposeful - immediate visual feedback for
- Canvas ruler and grid overlay
- Component outline overlay on hover
- Fullscreen mode for Monaco editor instances
- Snippet library with category filtering and search
- Snippet card grid with tag display and copy/insert actions
- Snippet detail modal with parameter documentation and code highlighting
- **States**:
- Canvas: neutral state shows dotted grid, hover shows drop zones, dragging shows blue outlines
- Components: default has subtle border, hover shows blue glow, selected shows thick accent border with resize handles
- Drop zones: hidden by default, appear on drag with dashed accent border and background tint
- Property inputs: follow standard focus states with accent color
- **Icon Selection**:
- Phosphor icons: Layout for layouts, PaintBrush for styling, Code for code editor, Lock/LockOpen for auth, FloppyDisk for save, Eye for preview, ArrowsOutSimple for fullscreen, Plus for add, Trash for delete, Copy for duplicate, CaretRight/Down for tree expand
- Phosphor icons: Layout for layouts, PaintBrush for styling, Code for code editor, Lock/LockOpen for auth, FloppyDisk for save, Eye for preview, ArrowsOutSimple for fullscreen, Plus for add, Trash for delete, Copy for duplicate/copy, CaretRight/Down for tree expand, BookOpen for snippet library, MagnifyingGlass for search, Tag for snippet tags, Check for copied confirmation, ArrowRight for insert action
- **Spacing**:
- Sidebars: p-4 for sections, gap-2 for component grid
- Canvas: p-8 for outer padding, min-h-screen for scrollability

View File

@@ -3,11 +3,12 @@ import { useKV } from '@github/spark/hooks'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Badge } from '@/components/ui/badge'
import { SignOut, Database, Lightning, Code, Eye, House, Download, Upload } from '@phosphor-icons/react'
import { SignOut, Database, Lightning, Code, Eye, House, Download, Upload, BookOpen } from '@phosphor-icons/react'
import { toast } from 'sonner'
import { SchemaEditorLevel4 } from './SchemaEditorLevel4'
import { WorkflowEditor } from './WorkflowEditor'
import { LuaEditor } from './LuaEditor'
import { LuaSnippetLibrary } from './LuaSnippetLibrary'
import type { User as UserType, AppConfiguration } from '@/lib/level-types'
import type { ModelSchema } from '@/lib/schema-types'
@@ -120,7 +121,7 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) {
</div>
<Tabs defaultValue="schemas" className="space-y-6">
<TabsList className="grid w-full grid-cols-3 max-w-2xl">
<TabsList className="grid w-full grid-cols-4 max-w-3xl">
<TabsTrigger value="schemas">
<Database className="mr-2" size={16} />
Data Schemas
@@ -136,6 +137,10 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) {
Lua Scripts
<Badge variant="secondary" className="ml-2">{appConfig.luaScripts.length}</Badge>
</TabsTrigger>
<TabsTrigger value="snippets">
<BookOpen className="mr-2" size={16} />
Snippet Library
</TabsTrigger>
</TabsList>
<TabsContent value="schemas" className="space-y-6">
@@ -165,6 +170,10 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) {
}
/>
</TabsContent>
<TabsContent value="snippets" className="space-y-6">
<LuaSnippetLibrary />
</TabsContent>
</Tabs>
<div className="mt-8 p-6 bg-gradient-to-r from-primary/10 to-accent/10 rounded-lg border-2 border-dashed border-primary/30">

View File

@@ -11,13 +11,15 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Plus, Trash, Play, CheckCircle, XCircle, FileCode, ArrowsOut } from '@phosphor-icons/react'
import { Plus, Trash, Play, CheckCircle, XCircle, FileCode, ArrowsOut, BookOpen } from '@phosphor-icons/react'
import { toast } from 'sonner'
import { createLuaEngine, type LuaExecutionResult } from '@/lib/lua-engine'
import { getLuaExampleCode, getLuaExamplesList } from '@/lib/lua-examples'
import type { LuaScript } from '@/lib/level-types'
import Editor, { useMonaco } from '@monaco-editor/react'
import type { editor } from 'monaco-editor'
import { LuaSnippetLibrary } from '@/components/LuaSnippetLibrary'
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'
interface LuaEditorProps {
scripts: LuaScript[]
@@ -32,6 +34,7 @@ export function LuaEditor({ scripts, onScriptsChange }: LuaEditorProps) {
const [testInputs, setTestInputs] = useState<Record<string, any>>({})
const [isExecuting, setIsExecuting] = useState(false)
const [isFullscreen, setIsFullscreen] = useState(false)
const [showSnippetLibrary, setShowSnippetLibrary] = useState(false)
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null)
const monaco = useMonaco()
@@ -236,6 +239,32 @@ export function LuaEditor({ scripts, onScriptsChange }: LuaEditorProps) {
})
}
const handleInsertSnippet = (code: string) => {
if (!currentScript) return
if (editorRef.current) {
const selection = editorRef.current.getSelection()
if (selection) {
editorRef.current.executeEdits('', [{
range: selection,
text: code,
forceMoveMarkers: true
}])
editorRef.current.focus()
} else {
const currentCode = currentScript.code
const newCode = currentCode ? currentCode + '\n\n' + code : code
handleUpdateScript({ code: newCode })
}
} else {
const currentCode = currentScript.code
const newCode = currentCode ? currentCode + '\n\n' + code : code
handleUpdateScript({ code: newCode })
}
setShowSnippetLibrary(false)
}
return (
<div className="grid md:grid-cols-3 gap-6 h-full">
<Card className="md:col-span-1">
@@ -414,6 +443,25 @@ export function LuaEditor({ scripts, onScriptsChange }: LuaEditorProps) {
<div className="flex items-center justify-between">
<Label>Lua Code</Label>
<div className="flex gap-2">
<Sheet open={showSnippetLibrary} onOpenChange={setShowSnippetLibrary}>
<SheetTrigger asChild>
<Button variant="outline" size="sm">
<BookOpen size={16} className="mr-2" />
Snippet Library
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-full sm:max-w-4xl overflow-y-auto">
<SheetHeader>
<SheetTitle>Lua Snippet Library</SheetTitle>
<SheetDescription>
Browse and insert pre-built code templates
</SheetDescription>
</SheetHeader>
<div className="mt-6">
<LuaSnippetLibrary onInsertSnippet={handleInsertSnippet} />
</div>
</SheetContent>
</Sheet>
<Select
onValueChange={(value) => {
const exampleCode = getLuaExampleCode(value as any)
@@ -421,9 +469,9 @@ export function LuaEditor({ scripts, onScriptsChange }: LuaEditorProps) {
toast.success('Example loaded')
}}
>
<SelectTrigger className="w-[200px]">
<SelectTrigger className="w-[180px]">
<FileCode size={16} className="mr-2" />
<SelectValue placeholder="Load example" />
<SelectValue placeholder="Examples" />
</SelectTrigger>
<SelectContent>
{getLuaExamplesList().map((example) => (

View File

@@ -0,0 +1,285 @@
import { useState } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import {
MagnifyingGlass,
Copy,
Check,
BookOpen,
Tag,
ArrowRight,
Code
} from '@phosphor-icons/react'
import { toast } from 'sonner'
import {
LUA_SNIPPET_CATEGORIES,
getSnippetsByCategory,
searchSnippets,
type LuaSnippet
} from '@/lib/lua-snippets'
interface LuaSnippetLibraryProps {
onInsertSnippet?: (code: string) => void
}
export function LuaSnippetLibrary({ onInsertSnippet }: LuaSnippetLibraryProps) {
const [selectedCategory, setSelectedCategory] = useState('All')
const [searchQuery, setSearchQuery] = useState('')
const [selectedSnippet, setSelectedSnippet] = useState<LuaSnippet | null>(null)
const [copiedId, setCopiedId] = useState<string | null>(null)
const displayedSnippets = searchQuery
? searchSnippets(searchQuery)
: getSnippetsByCategory(selectedCategory)
const handleCopySnippet = (snippet: LuaSnippet) => {
navigator.clipboard.writeText(snippet.code)
setCopiedId(snippet.id)
toast.success('Code copied to clipboard')
setTimeout(() => setCopiedId(null), 2000)
}
const handleInsertSnippet = (snippet: LuaSnippet) => {
if (onInsertSnippet) {
onInsertSnippet(snippet.code)
toast.success('Snippet inserted')
} else {
handleCopySnippet(snippet)
}
}
return (
<div className="space-y-6">
<div>
<div className="flex items-center gap-3 mb-2">
<BookOpen size={28} className="text-primary" />
<h2 className="text-2xl font-bold">Lua Snippet Library</h2>
</div>
<p className="text-muted-foreground">
Pre-built code templates for common patterns and operations
</p>
</div>
<div className="relative">
<MagnifyingGlass size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search snippets by name, description, or tags..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
<Tabs value={selectedCategory} onValueChange={setSelectedCategory}>
<ScrollArea className="w-full whitespace-nowrap">
<TabsList className="inline-flex w-auto">
{LUA_SNIPPET_CATEGORIES.map((category) => (
<TabsTrigger key={category} value={category} className="text-xs">
{category}
</TabsTrigger>
))}
</TabsList>
</ScrollArea>
{LUA_SNIPPET_CATEGORIES.map((category) => (
<TabsContent key={category} value={category} className="mt-6">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{displayedSnippets.length === 0 ? (
<div className="col-span-full text-center py-12 text-muted-foreground">
<Code size={48} className="mx-auto mb-4 opacity-50" />
<p>No snippets found</p>
{searchQuery && (
<p className="text-sm mt-2">Try a different search term</p>
)}
</div>
) : (
displayedSnippets.map((snippet) => (
<Card
key={snippet.id}
className="hover:border-primary transition-colors cursor-pointer group"
onClick={() => setSelectedSnippet(snippet)}
>
<CardHeader className="pb-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<CardTitle className="text-base font-semibold mb-1 truncate group-hover:text-primary transition-colors">
{snippet.name}
</CardTitle>
<CardDescription className="text-xs line-clamp-2">
{snippet.description}
</CardDescription>
</div>
<Badge variant="outline" className="text-xs shrink-0">
{snippet.category}
</Badge>
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="flex flex-wrap gap-1.5 mb-3">
{snippet.tags.slice(0, 3).map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs">
<Tag size={12} className="mr-1" />
{tag}
</Badge>
))}
{snippet.tags.length > 3 && (
<Badge variant="secondary" className="text-xs">
+{snippet.tags.length - 3}
</Badge>
)}
</div>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
className="flex-1"
onClick={(e) => {
e.stopPropagation()
handleCopySnippet(snippet)
}}
>
{copiedId === snippet.id ? (
<>
<Check size={14} className="mr-1.5" />
Copied
</>
) : (
<>
<Copy size={14} className="mr-1.5" />
Copy
</>
)}
</Button>
{onInsertSnippet && (
<Button
size="sm"
className="flex-1"
onClick={(e) => {
e.stopPropagation()
handleInsertSnippet(snippet)
}}
>
<ArrowRight size={14} className="mr-1.5" />
Insert
</Button>
)}
</div>
</CardContent>
</Card>
))
)}
</div>
</TabsContent>
))}
</Tabs>
<Dialog open={!!selectedSnippet} onOpenChange={() => setSelectedSnippet(null)}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<DialogHeader>
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<DialogTitle className="text-xl mb-2">{selectedSnippet?.name}</DialogTitle>
<DialogDescription>{selectedSnippet?.description}</DialogDescription>
</div>
<Badge variant="outline">{selectedSnippet?.category}</Badge>
</div>
</DialogHeader>
<div className="flex-1 overflow-auto space-y-4">
{selectedSnippet?.tags && selectedSnippet.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{selectedSnippet.tags.map((tag) => (
<Badge key={tag} variant="secondary">
<Tag size={12} className="mr-1" />
{tag}
</Badge>
))}
</div>
)}
{selectedSnippet?.parameters && selectedSnippet.parameters.length > 0 && (
<div>
<h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
<Code size={16} />
Parameters
</h4>
<div className="space-y-2">
{selectedSnippet.parameters.map((param) => (
<div key={param.name} className="bg-muted/50 rounded-lg p-3 border">
<div className="flex items-center gap-2 mb-1">
<code className="text-sm font-mono font-semibold text-primary">
{param.name}
</code>
<Badge variant="outline" className="text-xs">
{param.type}
</Badge>
</div>
<p className="text-xs text-muted-foreground">{param.description}</p>
</div>
))}
</div>
</div>
)}
<Separator />
<div>
<h4 className="text-sm font-semibold mb-3">Code</h4>
<div className="bg-slate-950 text-slate-50 rounded-lg p-4 overflow-x-auto">
<pre className="text-xs font-mono leading-relaxed">
<code>{selectedSnippet?.code}</code>
</pre>
</div>
</div>
<div className="flex gap-2">
<Button
className="flex-1"
onClick={() => selectedSnippet && handleCopySnippet(selectedSnippet)}
>
{copiedId === selectedSnippet?.id ? (
<>
<Check size={16} className="mr-2" />
Copied to Clipboard
</>
) : (
<>
<Copy size={16} className="mr-2" />
Copy to Clipboard
</>
)}
</Button>
{onInsertSnippet && (
<Button
variant="secondary"
className="flex-1"
onClick={() => {
if (selectedSnippet) {
handleInsertSnippet(selectedSnippet)
setSelectedSnippet(null)
}
}}
>
<ArrowRight size={16} className="mr-2" />
Insert into Editor
</Button>
)}
</div>
</div>
</DialogContent>
</Dialog>
</div>
)
}

998
src/lib/lua-snippets.ts Normal file
View File

@@ -0,0 +1,998 @@
export interface LuaSnippet {
id: string
name: string
description: string
category: string
code: string
tags: string[]
parameters?: Array<{ name: string; type: string; description: string }>
}
export const LUA_SNIPPET_CATEGORIES = [
'All',
'Data Validation',
'Data Transformation',
'Array Operations',
'String Processing',
'Math & Calculations',
'Conditionals & Logic',
'User Management',
'Error Handling',
'API & Networking',
'Date & Time',
'File Operations',
'Utilities'
] as const
export const LUA_SNIPPETS: LuaSnippet[] = [
{
id: 'validate_email',
name: 'Email Validation',
description: 'Validate email format using pattern matching',
category: 'Data Validation',
tags: ['validation', 'email', 'regex'],
parameters: [
{ name: 'email', type: 'string', description: 'Email address to validate' }
],
code: `local email = context.data.email or ""
if email == "" then
return { valid = false, error = "Email is required" }
end
local pattern = "^[%w._%%-]+@[%w._%%-]+%.%w+$"
if not string.match(email, pattern) then
return { valid = false, error = "Invalid email format" }
end
return { valid = true, email = email }`
},
{
id: 'validate_password_strength',
name: 'Password Strength Validator',
description: 'Check password meets security requirements',
category: 'Data Validation',
tags: ['validation', 'password', 'security'],
parameters: [
{ name: 'password', type: 'string', description: 'Password to validate' }
],
code: `local password = context.data.password or ""
if string.len(password) < 8 then
return { valid = false, error = "Password must be at least 8 characters" }
end
local hasUpper = string.match(password, "%u") ~= nil
local hasLower = string.match(password, "%l") ~= nil
local hasDigit = string.match(password, "%d") ~= nil
local hasSpecial = string.match(password, "[^%w]") ~= nil
if not hasUpper then
return { valid = false, error = "Password must contain uppercase letter" }
end
if not hasLower then
return { valid = false, error = "Password must contain lowercase letter" }
end
if not hasDigit then
return { valid = false, error = "Password must contain a number" }
end
if not hasSpecial then
return { valid = false, error = "Password must contain special character" }
end
return {
valid = true,
strength = "strong",
score = 100
}`
},
{
id: 'validate_phone',
name: 'Phone Number Validation',
description: 'Validate US phone number format',
category: 'Data Validation',
tags: ['validation', 'phone', 'format'],
parameters: [
{ name: 'phone', type: 'string', description: 'Phone number to validate' }
],
code: `local phone = context.data.phone or ""
local cleaned = string.gsub(phone, "[^%d]", "")
if string.len(cleaned) ~= 10 then
return { valid = false, error = "Phone must be 10 digits" }
end
local formatted = string.format("(%s) %s-%s",
string.sub(cleaned, 1, 3),
string.sub(cleaned, 4, 6),
string.sub(cleaned, 7, 10)
)
return {
valid = true,
cleaned = cleaned,
formatted = formatted
}`
},
{
id: 'validate_required_fields',
name: 'Required Fields Validator',
description: 'Check multiple required fields are present',
category: 'Data Validation',
tags: ['validation', 'required', 'form'],
code: `local data = context.data or {}
local required = {"name", "email", "username"}
local missing = {}
for i, field in ipairs(required) do
if not data[field] or data[field] == "" then
table.insert(missing, field)
end
end
if #missing > 0 then
return {
valid = false,
error = "Missing required fields: " .. table.concat(missing, ", "),
missing = missing
}
end
return { valid = true }`
},
{
id: 'transform_snake_to_camel',
name: 'Snake Case to Camel Case',
description: 'Convert snake_case strings to camelCase',
category: 'Data Transformation',
tags: ['transform', 'string', 'case'],
parameters: [
{ name: 'text', type: 'string', description: 'Snake case text' }
],
code: `local text = context.data.text or ""
local result = string.gsub(text, "_(%w)", function(c)
return string.upper(c)
end)
return {
original = text,
transformed = result
}`
},
{
id: 'transform_flatten_object',
name: 'Flatten Nested Object',
description: 'Convert nested table to flat key-value pairs',
category: 'Data Transformation',
tags: ['transform', 'object', 'flatten'],
code: `local function flatten(tbl, prefix, result)
result = result or {}
prefix = prefix or ""
for key, value in pairs(tbl) do
local newKey = prefix == "" and key or prefix .. "." .. key
if type(value) == "table" then
flatten(value, newKey, result)
else
result[newKey] = value
end
end
return result
end
local data = context.data or {}
local flattened = flatten(data)
return {
original = data,
flattened = flattened
}`
},
{
id: 'transform_normalize_data',
name: 'Normalize User Data',
description: 'Clean and normalize user input data',
category: 'Data Transformation',
tags: ['transform', 'normalize', 'clean'],
code: `local data = context.data or {}
local function trim(s)
return string.match(s, "^%s*(.-)%s*$")
end
local normalized = {}
if data.email then
normalized.email = string.lower(trim(data.email))
end
if data.name then
normalized.name = trim(data.name)
local words = {}
for word in string.gmatch(normalized.name, "%S+") do
table.insert(words, string.upper(string.sub(word, 1, 1)) .. string.lower(string.sub(word, 2)))
end
normalized.name = table.concat(words, " ")
end
if data.phone then
normalized.phone = string.gsub(data.phone, "[^%d]", "")
end
return normalized`
},
{
id: 'array_filter',
name: 'Filter Array',
description: 'Filter array elements by condition',
category: 'Array Operations',
tags: ['array', 'filter', 'collection'],
parameters: [
{ name: 'items', type: 'array', description: 'Array to filter' },
{ name: 'minValue', type: 'number', description: 'Minimum value threshold' }
],
code: `local items = context.data.items or {}
local minValue = context.data.minValue or 0
local filtered = {}
for i, item in ipairs(items) do
if item.value >= minValue then
table.insert(filtered, item)
end
end
log("Filtered " .. #filtered .. " of " .. #items .. " items")
return {
original = items,
filtered = filtered,
count = #filtered
}`
},
{
id: 'array_map',
name: 'Map Array',
description: 'Transform each array element',
category: 'Array Operations',
tags: ['array', 'map', 'transform'],
code: `local items = context.data.items or {}
local mapped = {}
for i, item in ipairs(items) do
table.insert(mapped, {
id = item.id,
label = string.upper(item.name or ""),
value = (item.value or 0) * 2,
index = i
})
end
return {
original = items,
mapped = mapped
}`
},
{
id: 'array_reduce',
name: 'Reduce Array to Sum',
description: 'Calculate sum of numeric array values',
category: 'Array Operations',
tags: ['array', 'reduce', 'sum'],
parameters: [
{ name: 'numbers', type: 'array', description: 'Array of numbers' }
],
code: `local numbers = context.data.numbers or {}
local sum = 0
local count = 0
for i, num in ipairs(numbers) do
sum = sum + (num or 0)
count = count + 1
end
local average = count > 0 and sum / count or 0
return {
sum = sum,
count = count,
average = average
}`
},
{
id: 'array_group_by',
name: 'Group Array by Property',
description: 'Group array items by a property value',
category: 'Array Operations',
tags: ['array', 'group', 'aggregate'],
code: `local items = context.data.items or {}
local groupKey = context.data.groupKey or "category"
local groups = {}
for i, item in ipairs(items) do
local key = item[groupKey] or "uncategorized"
if not groups[key] then
groups[key] = {}
end
table.insert(groups[key], item)
end
local summary = {}
for key, group in pairs(groups) do
summary[key] = #group
end
return {
groups = groups,
summary = summary
}`
},
{
id: 'array_sort',
name: 'Sort Array',
description: 'Sort array by property value',
category: 'Array Operations',
tags: ['array', 'sort', 'order'],
code: `local items = context.data.items or {}
local sortKey = context.data.sortKey or "value"
local descending = context.data.descending or false
table.sort(items, function(a, b)
if descending then
return (a[sortKey] or 0) > (b[sortKey] or 0)
else
return (a[sortKey] or 0) < (b[sortKey] or 0)
end
end)
return {
sorted = items,
count = #items
}`
},
{
id: 'string_slugify',
name: 'Create URL Slug',
description: 'Convert text to URL-friendly slug',
category: 'String Processing',
tags: ['string', 'slug', 'url'],
parameters: [
{ name: 'text', type: 'string', description: 'Text to slugify' }
],
code: `local text = context.data.text or ""
local slug = string.lower(text)
slug = string.gsub(slug, "%s+", "-")
slug = string.gsub(slug, "[^%w%-]", "")
slug = string.gsub(slug, "%-+", "-")
slug = string.gsub(slug, "^%-+", "")
slug = string.gsub(slug, "%-+$", "")
return {
original = text,
slug = slug
}`
},
{
id: 'string_truncate',
name: 'Truncate Text',
description: 'Truncate long text with ellipsis',
category: 'String Processing',
tags: ['string', 'truncate', 'ellipsis'],
parameters: [
{ name: 'text', type: 'string', description: 'Text to truncate' },
{ name: 'maxLength', type: 'number', description: 'Maximum length' }
],
code: `local text = context.data.text or ""
local maxLength = context.data.maxLength or 50
if string.len(text) <= maxLength then
return {
truncated = false,
text = text
}
end
local truncated = string.sub(text, 1, maxLength - 3) .. "..."
return {
truncated = true,
text = truncated,
originalLength = string.len(text)
}`
},
{
id: 'string_extract_hashtags',
name: 'Extract Hashtags',
description: 'Find all hashtags in text',
category: 'String Processing',
tags: ['string', 'parse', 'hashtags'],
parameters: [
{ name: 'text', type: 'string', description: 'Text containing hashtags' }
],
code: `local text = context.data.text or ""
local hashtags = {}
for tag in string.gmatch(text, "#(%w+)") do
table.insert(hashtags, tag)
end
return {
text = text,
hashtags = hashtags,
count = #hashtags
}`
},
{
id: 'string_word_count',
name: 'Word Counter',
description: 'Count words and characters in text',
category: 'String Processing',
tags: ['string', 'count', 'statistics'],
parameters: [
{ name: 'text', type: 'string', description: 'Text to analyze' }
],
code: `local text = context.data.text or ""
local charCount = string.len(text)
local words = {}
for word in string.gmatch(text, "%S+") do
table.insert(words, word)
end
local wordCount = #words
local sentences = 0
for _ in string.gmatch(text, "[.!?]+") do
sentences = sentences + 1
end
return {
characters = charCount,
words = wordCount,
sentences = sentences,
avgWordLength = wordCount > 0 and charCount / wordCount or 0
}`
},
{
id: 'math_percentage',
name: 'Calculate Percentage',
description: 'Calculate percentage and format result',
category: 'Math & Calculations',
tags: ['math', 'percentage', 'calculation'],
parameters: [
{ name: 'value', type: 'number', description: 'Partial value' },
{ name: 'total', type: 'number', description: 'Total value' }
],
code: `local value = context.data.value or 0
local total = context.data.total or 1
if total == 0 then
return {
error = "Cannot divide by zero",
percentage = 0
}
end
local percentage = (value / total) * 100
local formatted = string.format("%.2f%%", percentage)
return {
value = value,
total = total,
percentage = percentage,
formatted = formatted
}`
},
{
id: 'math_discount',
name: 'Calculate Discount',
description: 'Calculate price after discount',
category: 'Math & Calculations',
tags: ['math', 'discount', 'price'],
parameters: [
{ name: 'price', type: 'number', description: 'Original price' },
{ name: 'discount', type: 'number', description: 'Discount percentage' }
],
code: `local price = context.data.price or 0
local discount = context.data.discount or 0
local discountAmount = price * (discount / 100)
local finalPrice = price - discountAmount
local savings = discountAmount
return {
originalPrice = price,
discountPercent = discount,
discountAmount = discountAmount,
finalPrice = finalPrice,
savings = savings,
formatted = "$" .. string.format("%.2f", finalPrice)
}`
},
{
id: 'math_compound_interest',
name: 'Compound Interest Calculator',
description: 'Calculate compound interest over time',
category: 'Math & Calculations',
tags: ['math', 'interest', 'finance'],
parameters: [
{ name: 'principal', type: 'number', description: 'Initial amount' },
{ name: 'rate', type: 'number', description: 'Interest rate (%)' },
{ name: 'years', type: 'number', description: 'Number of years' }
],
code: `local principal = context.data.principal or 1000
local rate = (context.data.rate or 5) / 100
local years = context.data.years or 1
local compounds = 12
local amount = principal * math.pow(1 + (rate / compounds), compounds * years)
local interest = amount - principal
return {
principal = principal,
rate = rate * 100,
years = years,
finalAmount = amount,
interestEarned = interest,
formatted = "$" .. string.format("%.2f", amount)
}`
},
{
id: 'math_statistics',
name: 'Statistical Analysis',
description: 'Calculate mean, median, mode, std dev',
category: 'Math & Calculations',
tags: ['math', 'statistics', 'analysis'],
parameters: [
{ name: 'numbers', type: 'array', description: 'Array of numbers' }
],
code: `local numbers = context.data.numbers or {1, 2, 3, 4, 5}
local sum = 0
local min = numbers[1]
local max = numbers[1]
for i, num in ipairs(numbers) do
sum = sum + num
if num < min then min = num end
if num > max then max = num end
end
local mean = sum / #numbers
table.sort(numbers)
local median
if #numbers % 2 == 0 then
median = (numbers[#numbers/2] + numbers[#numbers/2 + 1]) / 2
else
median = numbers[math.ceil(#numbers/2)]
end
local variance = 0
for i, num in ipairs(numbers) do
variance = variance + math.pow(num - mean, 2)
end
variance = variance / #numbers
local stdDev = math.sqrt(variance)
return {
count = #numbers,
sum = sum,
mean = mean,
median = median,
min = min,
max = max,
variance = variance,
stdDev = stdDev,
range = max - min
}`
},
{
id: 'conditional_role_check',
name: 'Role-Based Access Check',
description: 'Check if user has required role',
category: 'Conditionals & Logic',
tags: ['conditional', 'role', 'access'],
parameters: [
{ name: 'requiredRole', type: 'string', description: 'Required role level' }
],
code: `local user = context.user or {}
local requiredRole = context.data.requiredRole or "user"
local roles = {
user = 1,
moderator = 2,
admin = 3,
god = 4
}
local userLevel = roles[user.role] or 0
local requiredLevel = roles[requiredRole] or 0
local hasAccess = userLevel >= requiredLevel
return {
user = user.username,
userRole = user.role,
requiredRole = requiredRole,
hasAccess = hasAccess,
message = hasAccess and "Access granted" or "Access denied"
}`
},
{
id: 'conditional_time_based',
name: 'Time-Based Logic',
description: 'Execute logic based on time of day',
category: 'Conditionals & Logic',
tags: ['conditional', 'time', 'schedule'],
code: `local hour = tonumber(os.date("%H"))
local timeOfDay = ""
local greeting = ""
if hour >= 5 and hour < 12 then
timeOfDay = "morning"
greeting = "Good morning"
elseif hour >= 12 and hour < 17 then
timeOfDay = "afternoon"
greeting = "Good afternoon"
elseif hour >= 17 and hour < 21 then
timeOfDay = "evening"
greeting = "Good evening"
else
timeOfDay = "night"
greeting = "Good night"
end
local isBusinessHours = hour >= 9 and hour < 17
return {
currentHour = hour,
timeOfDay = timeOfDay,
greeting = greeting,
isBusinessHours = isBusinessHours
}`
},
{
id: 'conditional_feature_flag',
name: 'Feature Flag Checker',
description: 'Check if feature is enabled for user',
category: 'Conditionals & Logic',
tags: ['conditional', 'feature', 'flag'],
code: `local user = context.user or {}
local feature = context.data.feature or ""
local enabledFeatures = {
betaUI = {"admin", "god"},
advancedSearch = {"moderator", "admin", "god"},
exportData = {"admin", "god"},
debugMode = {"god"}
}
local allowedRoles = enabledFeatures[feature] or {}
local isEnabled = false
for i, role in ipairs(allowedRoles) do
if user.role == role then
isEnabled = true
break
end
end
return {
feature = feature,
userRole = user.role,
enabled = isEnabled,
reason = isEnabled and "Feature available" or "Feature not available for your role"
}`
},
{
id: 'error_try_catch',
name: 'Try-Catch Pattern',
description: 'Safe execution with error handling',
category: 'Error Handling',
tags: ['error', 'exception', 'safety'],
code: `local function riskyOperation()
local data = context.data or {}
if not data.value then
error("Value is required")
end
if data.value < 0 then
error("Value must be positive")
end
return data.value * 2
end
local success, result = pcall(riskyOperation)
if success then
log("Operation successful: " .. tostring(result))
return {
success = true,
result = result
}
else
log("Operation failed: " .. tostring(result))
return {
success = false,
error = tostring(result)
}
end`
},
{
id: 'error_validation_accumulator',
name: 'Validation Error Accumulator',
description: 'Collect all validation errors at once',
category: 'Error Handling',
tags: ['error', 'validation', 'accumulator'],
code: `local data = context.data or {}
local errors = {}
if not data.name or data.name == "" then
table.insert(errors, "Name is required")
end
if not data.email or data.email == "" then
table.insert(errors, "Email is required")
elseif not string.match(data.email, "@") then
table.insert(errors, "Email format is invalid")
end
if not data.age then
table.insert(errors, "Age is required")
elseif data.age < 18 then
table.insert(errors, "Must be 18 or older")
end
if #errors > 0 then
return {
valid = false,
errors = errors,
count = #errors
}
end
return {
valid = true,
data = data
}`
},
{
id: 'user_profile_builder',
name: 'Build User Profile',
description: 'Create complete user profile from data',
category: 'User Management',
tags: ['user', 'profile', 'builder'],
code: `local data = context.data or {}
local profile = {
id = "user_" .. os.time(),
username = data.username or "",
email = string.lower(data.email or ""),
displayName = data.displayName or data.username or "",
role = "user",
status = "active",
createdAt = os.time(),
metadata = {
source = "registration",
version = "1.0"
},
preferences = {
theme = "light",
notifications = true,
language = "en"
}
}
log("Created profile for: " .. profile.username)
return profile`
},
{
id: 'user_activity_logger',
name: 'Log User Activity',
description: 'Create activity log entry',
category: 'User Management',
tags: ['user', 'activity', 'logging'],
code: `local user = context.user or {}
local action = context.data.action or "unknown"
local details = context.data.details or {}
local activity = {
id = "activity_" .. os.time(),
userId = user.id or "unknown",
username = user.username or "anonymous",
action = action,
details = details,
timestamp = os.time(),
date = os.date("%Y-%m-%d %H:%M:%S"),
ipAddress = "0.0.0.0",
userAgent = "MetaBuilder/1.0"
}
log("Activity logged: " .. user.username .. " - " .. action)
return activity`
},
{
id: 'date_format',
name: 'Format Date',
description: 'Format timestamp in various ways',
category: 'Date & Time',
tags: ['date', 'time', 'format'],
parameters: [
{ name: 'timestamp', type: 'number', description: 'Unix timestamp' }
],
code: `local timestamp = context.data.timestamp or os.time()
local formatted = {
full = os.date("%Y-%m-%d %H:%M:%S", timestamp),
date = os.date("%Y-%m-%d", timestamp),
time = os.date("%H:%M:%S", timestamp),
readable = os.date("%B %d, %Y at %I:%M %p", timestamp),
iso = os.date("%Y-%m-%dT%H:%M:%S", timestamp),
unix = timestamp
}
return formatted`
},
{
id: 'date_diff',
name: 'Calculate Date Difference',
description: 'Calculate difference between two dates',
category: 'Date & Time',
tags: ['date', 'time', 'difference'],
parameters: [
{ name: 'startTime', type: 'number', description: 'Start timestamp' },
{ name: 'endTime', type: 'number', description: 'End timestamp' }
],
code: `local startTime = context.data.startTime or os.time()
local endTime = context.data.endTime or os.time()
local diffSeconds = math.abs(endTime - startTime)
local diffMinutes = math.floor(diffSeconds / 60)
local diffHours = math.floor(diffMinutes / 60)
local diffDays = math.floor(diffHours / 24)
return {
startTime = startTime,
endTime = endTime,
difference = {
seconds = diffSeconds,
minutes = diffMinutes,
hours = diffHours,
days = diffDays
},
formatted = diffDays .. " days, " .. (diffHours % 24) .. " hours"
}`
},
{
id: 'json_parse',
name: 'Safe JSON Parse',
description: 'Parse JSON string with error handling',
category: 'Utilities',
tags: ['json', 'parse', 'utility'],
parameters: [
{ name: 'jsonString', type: 'string', description: 'JSON string to parse' }
],
code: `local jsonString = context.data.jsonString or "{}"
local function parseJSON(str)
local result = {}
return result
end
local success, data = pcall(parseJSON, jsonString)
if success then
return {
success = true,
data = data
}
else
return {
success = false,
error = "Invalid JSON format"
}
end`
},
{
id: 'generate_id',
name: 'Generate Unique ID',
description: 'Create unique identifier',
category: 'Utilities',
tags: ['id', 'uuid', 'generator'],
code: `local function generateId(prefix)
local timestamp = os.time()
local random = math.random(1000, 9999)
return (prefix or "id") .. "_" .. timestamp .. "_" .. random
end
local id = generateId(context.data.prefix)
return {
id = id,
timestamp = os.time()
}`
},
{
id: 'rate_limiter',
name: 'Rate Limit Checker',
description: 'Check if action exceeds rate limit',
category: 'Utilities',
tags: ['rate', 'limit', 'throttle'],
code: `local user = context.user or {}
local action = context.data.action or "default"
local maxAttempts = context.data.maxAttempts or 5
local windowSeconds = context.data.windowSeconds or 60
local currentTime = os.time()
local attempts = 1
local allowed = attempts <= maxAttempts
return {
allowed = allowed,
attempts = attempts,
maxAttempts = maxAttempts,
remaining = maxAttempts - attempts,
resetTime = currentTime + windowSeconds,
message = allowed and "Request allowed" or "Rate limit exceeded"
}`
},
{
id: 'cache_manager',
name: 'Simple Cache Manager',
description: 'Cache data with expiration',
category: 'Utilities',
tags: ['cache', 'storage', 'ttl'],
code: `local key = context.data.key or "cache_key"
local value = context.data.value
local ttl = context.data.ttl or 300
local cached = {
key = key,
value = value,
cachedAt = os.time(),
expiresAt = os.time() + ttl,
ttl = ttl
}
log("Cached " .. key .. " for " .. ttl .. " seconds")
return cached`
}
]
export function getSnippetsByCategory(category: string): LuaSnippet[] {
if (category === 'All') {
return LUA_SNIPPETS
}
return LUA_SNIPPETS.filter(snippet => snippet.category === category)
}
export function searchSnippets(query: string): LuaSnippet[] {
const lowerQuery = query.toLowerCase()
return LUA_SNIPPETS.filter(snippet =>
snippet.name.toLowerCase().includes(lowerQuery) ||
snippet.description.toLowerCase().includes(lowerQuery) ||
snippet.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
)
}
export function getSnippetById(id: string): LuaSnippet | undefined {
return LUA_SNIPPETS.find(snippet => snippet.id === id)
}