mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
Generated by Spark: Create Lua code snippet library with insertable templates for common patterns
This commit is contained in:
182
LUA_SNIPPETS_GUIDE.md
Normal file
182
LUA_SNIPPETS_GUIDE.md
Normal 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
31
PRD.md
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
285
src/components/LuaSnippetLibrary.tsx
Normal file
285
src/components/LuaSnippetLibrary.tsx
Normal 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
998
src/lib/lua-snippets.ts
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user