Adding updates to fartmachine
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
# Create GlitchComponent
|
||||
|
||||
Create a new GlitchComponent project that conforms to the standardized plugin architecture for Glitch University.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `$ARGUMENTS` - Component name (e.g., "matrix-rain", "particle-system")
|
||||
|
||||
## Instructions
|
||||
|
||||
When creating a new GlitchComponent:
|
||||
|
||||
1. **If no directory specified**, create in a sibling directory: `../glitch-components/$ARGUMENTS/`
|
||||
|
||||
2. **Create the full project structure**:
|
||||
```
|
||||
$ARGUMENTS/
|
||||
├── src/
|
||||
│ ├── index.tsx # Main export with metadata
|
||||
│ ├── Component.tsx # Component implementation
|
||||
│ ├── styles.module.css # Scoped CSS styles
|
||||
│ ├── types.ts # TypeScript interfaces
|
||||
│ └── dev.tsx # Leva dev harness
|
||||
├── vite.config.ts
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── index.html
|
||||
```
|
||||
|
||||
3. After creation, explain how to:
|
||||
- Run `npm install && npm run dev` for local development
|
||||
- Build with `npm run build`
|
||||
- Deploy output to host app
|
||||
|
||||
---
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
1. **Build as Library**: Use Vite Library Mode to output a self-contained bundle
|
||||
2. **CSS Isolation**: Use CSS Modules to prevent style bleeding
|
||||
3. **Inversion of Control**: Accept props for all configurable parameters
|
||||
4. **Local Dev Support**: Use Leva controls during development, props in production
|
||||
|
||||
---
|
||||
|
||||
## Required TypeScript Interfaces
|
||||
|
||||
```typescript
|
||||
// src/types.ts
|
||||
|
||||
export interface GlitchComponentConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface GlitchComponentProps {
|
||||
config: GlitchComponentConfig;
|
||||
onComplete: (result: GlitchComponentResult) => void;
|
||||
onProgress?: (percent: number) => void;
|
||||
theme?: GlitchTheme;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface GlitchComponentResult {
|
||||
success: boolean;
|
||||
score?: number;
|
||||
data?: unknown;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface GlitchTheme {
|
||||
primary: string; // #6366f1
|
||||
accent: string; // #22d3ee
|
||||
bg: string; // #0a0a0f
|
||||
bgSecondary: string; // #12121a
|
||||
text: string; // #e8e8ec
|
||||
textMuted: string; // #9999a8
|
||||
border: string; // #2a2a3a
|
||||
}
|
||||
|
||||
export interface GlitchComponentMetadata {
|
||||
name: string;
|
||||
displayName: string;
|
||||
version: string;
|
||||
paramSchema: ParamSchema;
|
||||
defaultParams: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ParamSchema {
|
||||
[key: string]: {
|
||||
type: 'number' | 'string' | 'boolean' | 'color' | 'select' | 'range';
|
||||
label?: string;
|
||||
description?: string;
|
||||
default: unknown;
|
||||
options?: { value: string | number; label: string }[];
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Main Export Structure
|
||||
|
||||
```typescript
|
||||
// src/index.tsx
|
||||
import Component from './Component';
|
||||
import type { GlitchComponentMetadata } from './types';
|
||||
|
||||
export default Component;
|
||||
|
||||
export const metadata: GlitchComponentMetadata = {
|
||||
name: 'my-component',
|
||||
displayName: 'My Component',
|
||||
version: '1.0.0',
|
||||
paramSchema: {
|
||||
speed: { type: 'range', label: 'Speed', default: 1.0, min: 0.1, max: 5.0, step: 0.1 },
|
||||
primaryColor: { type: 'color', label: 'Color', default: '#6366f1' }
|
||||
},
|
||||
defaultParams: { speed: 1.0, primaryColor: '#6366f1' }
|
||||
};
|
||||
|
||||
export type { GlitchComponentProps, GlitchComponentConfig, GlitchComponentResult } from './types';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Template
|
||||
|
||||
```typescript
|
||||
// src/Component.tsx
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import type { GlitchComponentProps } from './types';
|
||||
|
||||
interface ComponentParams {
|
||||
speed: number;
|
||||
primaryColor: string;
|
||||
}
|
||||
|
||||
export default function MyComponent({ config, onComplete, onProgress, theme, className }: GlitchComponentProps) {
|
||||
const params = config.params as ComponentParams;
|
||||
const { speed = 1.0, primaryColor } = params;
|
||||
|
||||
const handleComplete = useCallback((success: boolean, score?: number) => {
|
||||
onComplete({ success, score, data: { completedAt: new Date().toISOString() } });
|
||||
}, [onComplete]);
|
||||
|
||||
const cssVars = {
|
||||
'--gc-primary': primaryColor || theme?.primary || '#6366f1',
|
||||
'--gc-accent': theme?.accent || '#22d3ee',
|
||||
'--gc-bg': theme?.bg || '#0a0a0f',
|
||||
'--gc-text': theme?.text || '#e8e8ec',
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} ${className || ''}`} style={cssVars}>
|
||||
<h2 className={styles.title}>My GlitchComponent</h2>
|
||||
<button className={styles.button} onClick={() => handleComplete(true, 100)}>
|
||||
Complete
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS Module Template
|
||||
|
||||
```css
|
||||
/* src/styles.module.css */
|
||||
.container {
|
||||
background: var(--gc-bg, #0a0a0f);
|
||||
color: var(--gc-text, #e8e8ec);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-sizing: border-box;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container *, .container *::before, .container *::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--gc-primary, #6366f1);
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: var(--gc-primary, #6366f1);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.button:hover { opacity: 0.9; }
|
||||
.button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vite Configuration
|
||||
|
||||
```typescript
|
||||
// vite.config.ts
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [react(), cssInjectedByJsPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.tsx'),
|
||||
name: 'MyGlitchComponent',
|
||||
fileName: 'my-glitch-component',
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
||||
output: {
|
||||
globals: { react: 'React', 'react-dom': 'ReactDOM', 'react/jsx-runtime': 'jsxRuntime' },
|
||||
assetFileNames: 'assets/[name][extname]'
|
||||
}
|
||||
},
|
||||
sourcemap: true,
|
||||
minify: mode === 'production'
|
||||
},
|
||||
server: { port: 3001, open: true }
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-glitch-component",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./dist/my-glitch-component.js",
|
||||
"module": "./dist/my-glitch-component.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"leva": "^0.9.35",
|
||||
"typescript": "^5.3.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-css-injected-by-js": "^3.3.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dev Harness with Leva
|
||||
|
||||
```typescript
|
||||
// src/dev.tsx
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { useControls, Leva } from 'leva';
|
||||
import Component from './Component';
|
||||
import { metadata } from './index';
|
||||
|
||||
function DevHarness() {
|
||||
const levaSchema = Object.entries(metadata.paramSchema).reduce((acc, [key, schema]) => {
|
||||
if (schema.type === 'range' || schema.type === 'number') {
|
||||
acc[key] = { value: schema.default, min: schema.min, max: schema.max, step: schema.step };
|
||||
} else if (schema.type === 'select') {
|
||||
acc[key] = { value: schema.default, options: schema.options?.reduce((o, opt) => ({ ...o, [opt.label]: opt.value }), {}) };
|
||||
} else {
|
||||
acc[key] = schema.default;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>);
|
||||
|
||||
const params = useControls(levaSchema);
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: '#0a0a0f', padding: '2rem' }}>
|
||||
<Leva collapsed={false} />
|
||||
<Component
|
||||
config={{ id: 'dev', name: metadata.name, version: metadata.version, params }}
|
||||
theme={{ primary: '#6366f1', accent: '#22d3ee', bg: '#0a0a0f', bgSecondary: '#12121a', text: '#e8e8ec', textMuted: '#9999a8', border: '#2a2a3a' }}
|
||||
onComplete={(r) => { console.log('Complete:', r); alert(`Done! Score: ${r.score}`); }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><DevHarness /></React.StrictMode>);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Three.js / R3F Components
|
||||
|
||||
For 3D components, add to externals in vite.config.ts:
|
||||
|
||||
```typescript
|
||||
external: ['react', 'react-dom', 'react/jsx-runtime', 'three', '@react-three/fiber', '@react-three/drei']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
0. `nvm use 20` # to select node version 20
|
||||
1. `npm run build`
|
||||
2. Copy `dist/*.js` to host app's `src/components/glitch/`
|
||||
3. Add database entry:
|
||||
```sql
|
||||
INSERT INTO glitch_components (name, display_name, version, file_path, param_schema, default_params)
|
||||
VALUES ('my-component', 'My Component', '1.0.0', 'my-component.js', '{"speed":{"type":"range","default":1.0}}', '{"speed":1.0}');
|
||||
```
|
||||
4. Link to tech via `glitch_component_id`
|
||||
Reference in New Issue
Block a user