From 97a55b8ab115f1ac68df0597e6787201079bc053 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:04:29 +0000 Subject: [PATCH 1/4] Initial plan From f221a36c8851e71d3f8f51622ff840be710a74ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:13:23 +0000 Subject: [PATCH 2/4] Refactor DocumentationView and FeatureIdeaCloud into modular components Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/components/DocumentationView.tsx | 193 +---------- .../DocumentationView/DocComponents.tsx | 182 +++++++++++ src/components/FeatureIdeaCloud.tsx | 299 +----------------- src/components/FeatureIdeaCloud/constants.ts | 100 ++++++ src/components/FeatureIdeaCloud/nodes.tsx | 145 +++++++++ src/components/FeatureIdeaCloud/types.ts | 21 ++ 6 files changed, 464 insertions(+), 476 deletions(-) create mode 100644 src/components/DocumentationView/DocComponents.tsx create mode 100644 src/components/FeatureIdeaCloud/nodes.tsx create mode 100644 src/components/FeatureIdeaCloud/types.ts diff --git a/src/components/DocumentationView.tsx b/src/components/DocumentationView.tsx index 41e92eb..4df23d4 100644 --- a/src/components/DocumentationView.tsx +++ b/src/components/DocumentationView.tsx @@ -8,10 +8,7 @@ import { Input } from '@/components/ui/input' import { BookOpen, MapPin, - FileCode, - CheckCircle, Clock, - Sparkle, Code, Database, Tree, @@ -28,6 +25,17 @@ import { MagnifyingGlass, GitBranch } from '@phosphor-icons/react' +import { + FeatureItem, + AIFeatureCard, + RoadmapItem, + AgentFileItem, + IntegrationPoint, + CICDPlatformItem, + PipelineStageCard, + SassComponentItem, + AnimationItem +} from './DocumentationView/DocComponents' export function DocumentationView() { const [activeTab, setActiveTab] = useState('readme') @@ -1894,182 +1902,3 @@ docker pull ghcr.io//:latest`} ) } - -function CICDPlatformItem({ name, file, description, features }: { - name: string - file: string - description: string - features: string[] -}) { - return ( -
-
-
- -

{name}

-
- {file} -

{description}

-
-
-

Key Features:

-
    - {features.map((feature, idx) => ( -
  • - - {feature} -
  • - ))} -
-
-
- ) -} - -function PipelineStageCard({ stage, description, duration }: { - stage: string - description: string - duration: string -}) { - return ( - - -
-
-

{stage}

-

{description}

-
- - {duration} - -
-
-
- ) -} - -function SassComponentItem({ name, classes, description }: { name: string; classes: string[]; description: string }) { - return ( -
-

{name}

-

{description}

-
- {classes.map((cls, idx) => ( - {cls} - ))} -
-
- ) -} - -function AnimationItem({ name, description }: { name: string; description: string }) { - return ( -
- {name} -

{description}

-
- ) -} - -function FeatureItem({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) { - return ( -
-
{icon}
-
-

{title}

-

{description}

-
-
- ) -} - -function AIFeatureCard({ title, description }: { title: string; description: string }) { - return ( - - -
- -
-

{title}

-

{description}

-
-
-
-
- ) -} - -function RoadmapItem({ status, title, description, version }: { - status: 'completed' | 'planned' - title: string - description: string - version: string -}) { - return ( - - -
-
-
-

{title}

- - {version} - -
-

{description}

-
-
-
-
- ) -} - -function AgentFileItem({ filename, path, description, features }: { - filename: string - path: string - description: string - features: string[] -}) { - return ( -
-
-
- - {filename} -
-

{path}

-

{description}

-
-
-

Key Features:

-
    - {features.map((feature, idx) => ( -
  • - - {feature} -
  • - ))} -
-
-
- ) -} - -function IntegrationPoint({ component, capabilities }: { component: string; capabilities: string[] }) { - return ( -
-

- - {component} -

-
    - {capabilities.map((capability, idx) => ( -
  • - - {capability} -
  • - ))} -
-
- ) -} diff --git a/src/components/DocumentationView/DocComponents.tsx b/src/components/DocumentationView/DocComponents.tsx new file mode 100644 index 0000000..b3a395b --- /dev/null +++ b/src/components/DocumentationView/DocComponents.tsx @@ -0,0 +1,182 @@ +import { Card, CardContent } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { FileCode, CheckCircle, Sparkle } from '@phosphor-icons/react' + +export function FeatureItem({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) { + return ( +
+
{icon}
+
+

{title}

+

{description}

+
+
+ ) +} + +export function AIFeatureCard({ title, description }: { title: string; description: string }) { + return ( + + +
+ +
+

{title}

+

{description}

+
+
+
+
+ ) +} + +export function RoadmapItem({ status, title, description, version }: { + status: 'completed' | 'planned' + title: string + description: string + version: string +}) { + return ( + + +
+
+
+

{title}

+ + {version} + +
+

{description}

+
+
+
+
+ ) +} + +export function AgentFileItem({ filename, path, description, features }: { + filename: string + path: string + description: string + features: string[] +}) { + return ( +
+
+
+ + {filename} +
+

{path}

+

{description}

+
+
+

Key Features:

+
    + {features.map((feature, idx) => ( +
  • + + {feature} +
  • + ))} +
+
+
+ ) +} + +export function IntegrationPoint({ component, capabilities }: { component: string; capabilities: string[] }) { + return ( +
+

+ + {component} +

+
    + {capabilities.map((capability, idx) => ( +
  • + + {capability} +
  • + ))} +
+
+ ) +} + +export function CICDPlatformItem({ name, file, description, features }: { + name: string + file: string + description: string + features: string[] +}) { + return ( +
+
+
+ +

{name}

+
+ {file} +

{description}

+
+
+

Key Features:

+
    + {features.map((feature, idx) => ( +
  • + + {feature} +
  • + ))} +
+
+
+ ) +} + +export function PipelineStageCard({ stage, description, duration }: { + stage: string + description: string + duration: string +}) { + return ( + + +
+
+

{stage}

+

{description}

+
+ + {duration} + +
+
+
+ ) +} + +export function SassComponentItem({ name, classes, description }: { name: string; classes: string[]; description: string }) { + return ( +
+

{name}

+

{description}

+
+ {classes.map((cls, idx) => ( + {cls} + ))} +
+
+ ) +} + +export function AnimationItem({ name, description }: { name: string; description: string }) { + return ( +
+ {name} +

{description}

+
+ ) +} diff --git a/src/components/FeatureIdeaCloud.tsx b/src/components/FeatureIdeaCloud.tsx index 10408e5..dc07481 100644 --- a/src/components/FeatureIdeaCloud.tsx +++ b/src/components/FeatureIdeaCloud.tsx @@ -1,6 +1,4 @@ -/// - -import { useState, useEffect, useCallback, useRef, ReactElement } from 'react' +import { useState, useEffect, useCallback, useRef } from 'react' import { useKV } from '@/hooks/use-kv' import ReactFlow, { Node, @@ -14,307 +12,20 @@ import ReactFlow, { MarkerType, ConnectionMode, Panel, - NodeProps, - Handle, - Position, reconnectEdge, } from 'reactflow' import 'reactflow/dist/style.css' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' -import { Card } from '@/components/ui/card' -import { Badge } from '@/components/ui/badge' import { Textarea } from '@/components/ui/textarea' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { ScrollArea } from '@/components/ui/scroll-area' -import { Plus, Trash, Sparkle, DotsThree, Package } from '@phosphor-icons/react' +import { Plus, Trash, Sparkle, Package } from '@phosphor-icons/react' import { toast } from 'sonner' - -interface FeatureIdea { - id: string - title: string - description: string - category: string - priority: 'low' | 'medium' | 'high' - status: 'idea' | 'planned' | 'in-progress' | 'completed' - createdAt: number - parentGroup?: string -} - -interface IdeaGroup { - id: string - label: string - color: string - createdAt: number -} - -interface IdeaEdgeData { - label?: string -} - -const SEED_IDEAS: FeatureIdea[] = [ - { - id: 'idea-1', - title: 'AI Code Assistant', - description: 'Integrate an AI assistant that can suggest code improvements and answer questions', - category: 'AI/ML', - priority: 'high', - status: 'completed', - createdAt: Date.now() - 10000000, - }, - { - id: 'idea-2', - title: 'Real-time Collaboration', - description: 'Allow multiple developers to work on the same project simultaneously', - category: 'Collaboration', - priority: 'high', - status: 'idea', - createdAt: Date.now() - 9000000, - }, - { - id: 'idea-3', - title: 'Component Marketplace', - description: 'A marketplace where users can share and download pre-built components', - category: 'Community', - priority: 'medium', - status: 'idea', - createdAt: Date.now() - 8000000, - }, - { - id: 'idea-4', - title: 'Visual Git Integration', - description: 'Git operations through a visual interface with branch visualization', - category: 'DevOps', - priority: 'high', - status: 'planned', - createdAt: Date.now() - 7000000, - }, - { - id: 'idea-5', - title: 'API Mock Server', - description: 'Built-in mock server for testing API integrations', - category: 'Testing', - priority: 'medium', - status: 'idea', - createdAt: Date.now() - 6000000, - }, - { - id: 'idea-6', - title: 'Performance Profiler', - description: 'Analyze and optimize application performance with visual metrics', - category: 'Performance', - priority: 'medium', - status: 'idea', - createdAt: Date.now() - 5000000, - }, - { - id: 'idea-7', - title: 'Theme Presets', - description: 'Pre-designed theme templates for quick project setup', - category: 'Design', - priority: 'low', - status: 'completed', - createdAt: Date.now() - 4000000, - }, - { - id: 'idea-8', - title: 'Database Schema Migrations', - description: 'Visual tool for creating and managing database migrations', - category: 'Database', - priority: 'high', - status: 'in-progress', - createdAt: Date.now() - 3000000, - }, - { - id: 'idea-9', - title: 'Mobile App Preview', - description: 'Live preview on actual mobile devices or simulators', - category: 'Mobile', - priority: 'medium', - status: 'planned', - createdAt: Date.now() - 2000000, - }, - { - id: 'idea-10', - title: 'Accessibility Checker', - description: 'Automated accessibility testing and suggestions', - category: 'Accessibility', - priority: 'high', - status: 'idea', - createdAt: Date.now() - 1000000, - }, -] - -const CATEGORIES = ['AI/ML', 'Collaboration', 'Community', 'DevOps', 'Testing', 'Performance', 'Design', 'Database', 'Mobile', 'Accessibility', 'Productivity', 'Security', 'Analytics', 'Other'] -const PRIORITIES = ['low', 'medium', 'high'] as const -const STATUSES = ['idea', 'planned', 'in-progress', 'completed'] as const - -const CONNECTION_STYLE = { - stroke: '#a78bfa', - strokeWidth: 2.5 -} - -const STATUS_COLORS = { - idea: 'bg-muted text-muted-foreground', - planned: 'bg-accent text-accent-foreground', - 'in-progress': 'bg-primary text-primary-foreground', - completed: 'bg-green-600 text-white', -} - -const PRIORITY_COLORS = { - low: 'border-blue-400/60 bg-blue-50/80 dark:bg-blue-950/40', - medium: 'border-amber-400/60 bg-amber-50/80 dark:bg-amber-950/40', - high: 'border-red-400/60 bg-red-50/80 dark:bg-red-950/40', -} - -const GROUP_COLORS = [ - { name: 'Blue', value: '#3b82f6', bg: 'rgba(59, 130, 246, 0.08)', border: 'rgba(59, 130, 246, 0.3)' }, - { name: 'Purple', value: '#a855f7', bg: 'rgba(168, 85, 247, 0.08)', border: 'rgba(168, 85, 247, 0.3)' }, - { name: 'Green', value: '#10b981', bg: 'rgba(16, 185, 129, 0.08)', border: 'rgba(16, 185, 129, 0.3)' }, - { name: 'Red', value: '#ef4444', bg: 'rgba(239, 68, 68, 0.08)', border: 'rgba(239, 68, 68, 0.3)' }, - { name: 'Orange', value: '#f97316', bg: 'rgba(249, 115, 22, 0.08)', border: 'rgba(249, 115, 22, 0.3)' }, - { name: 'Pink', value: '#ec4899', bg: 'rgba(236, 72, 153, 0.08)', border: 'rgba(236, 72, 153, 0.3)' }, - { name: 'Cyan', value: '#06b6d4', bg: 'rgba(6, 182, 212, 0.08)', border: 'rgba(6, 182, 212, 0.3)' }, - { name: 'Amber', value: '#f59e0b', bg: 'rgba(245, 158, 11, 0.08)', border: 'rgba(245, 158, 11, 0.3)' }, -] - -function GroupNode({ data, selected }: NodeProps) { - const colorScheme = GROUP_COLORS.find(c => c.value === data.color) || GROUP_COLORS[0] - - return ( -
-
- {data.label} -
- -
- ) -} - -function IdeaNode({ data, selected, id }: NodeProps & { id: string }) { - const [connectionCounts, setConnectionCounts] = useState>({ - left: 0, - right: 0, - top: 0, - bottom: 0, - }) - - useEffect(() => { - const updateConnectionCounts = (event: CustomEvent) => { - const { nodeId, counts } = event.detail - if (nodeId === id) { - setConnectionCounts(counts) - } - } - - window.addEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener) - return () => { - window.removeEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener) - } - }, [id]) - - const generateHandles = (position: Position, type: 'source' | 'target', side: string) => { - const count = connectionCounts[side] || 0 - const totalHandles = Math.max(2, count + 1) - const handles: ReactElement[] = [] - - for (let i = 0; i < totalHandles; i++) { - const handleId = `${side}-${i}` - const isVertical = position === Position.Top || position === Position.Bottom - const positionStyle = isVertical - ? { left: `${((i + 1) / (totalHandles + 1)) * 100}%` } - : { top: `${((i + 1) / (totalHandles + 1)) * 100}%` } - - handles.push( - - ) - } - - return handles - } - - return ( -
- {generateHandles(Position.Left, 'target', 'left')} - {generateHandles(Position.Right, 'source', 'right')} - {generateHandles(Position.Top, 'target', 'top')} - {generateHandles(Position.Bottom, 'source', 'bottom')} - - -
-
-

{data.title}

- -
-

- {data.description} -

-
- - {data.category} - - - {data.status} - -
-
-
-
- ) -} - -const nodeTypes = { - ideaNode: IdeaNode, - groupNode: GroupNode, -} +import { FeatureIdea, IdeaGroup, IdeaEdgeData } from './FeatureIdeaCloud/types' +import { SEED_IDEAS, CATEGORIES, PRIORITIES, STATUSES, CONNECTION_STYLE, GROUP_COLORS } from './FeatureIdeaCloud/constants' +import { nodeTypes } from './FeatureIdeaCloud/nodes' export function FeatureIdeaCloud() { const [ideas, setIdeas] = useKV('feature-ideas', SEED_IDEAS) diff --git a/src/components/FeatureIdeaCloud/constants.ts b/src/components/FeatureIdeaCloud/constants.ts index f6be8a7..ac25c07 100644 --- a/src/components/FeatureIdeaCloud/constants.ts +++ b/src/components/FeatureIdeaCloud/constants.ts @@ -1,3 +1,103 @@ +import { FeatureIdea } from './types' + +export const SEED_IDEAS: FeatureIdea[] = [ + { + id: 'idea-1', + title: 'AI Code Assistant', + description: 'Integrate an AI assistant that can suggest code improvements and answer questions', + category: 'AI/ML', + priority: 'high', + status: 'completed', + createdAt: Date.now() - 10000000, + }, + { + id: 'idea-2', + title: 'Real-time Collaboration', + description: 'Allow multiple developers to work on the same project simultaneously', + category: 'Collaboration', + priority: 'high', + status: 'idea', + createdAt: Date.now() - 9000000, + }, + { + id: 'idea-3', + title: 'Component Marketplace', + description: 'A marketplace where users can share and download pre-built components', + category: 'Community', + priority: 'medium', + status: 'idea', + createdAt: Date.now() - 8000000, + }, + { + id: 'idea-4', + title: 'Visual Git Integration', + description: 'Git operations through a visual interface with branch visualization', + category: 'DevOps', + priority: 'high', + status: 'planned', + createdAt: Date.now() - 7000000, + }, + { + id: 'idea-5', + title: 'API Mock Server', + description: 'Built-in mock server for testing API integrations', + category: 'Testing', + priority: 'medium', + status: 'idea', + createdAt: Date.now() - 6000000, + }, + { + id: 'idea-6', + title: 'Performance Profiler', + description: 'Analyze and optimize application performance with visual metrics', + category: 'Performance', + priority: 'medium', + status: 'idea', + createdAt: Date.now() - 5000000, + }, + { + id: 'idea-7', + title: 'Theme Presets', + description: 'Pre-designed theme templates for quick project setup', + category: 'Design', + priority: 'low', + status: 'completed', + createdAt: Date.now() - 4000000, + }, + { + id: 'idea-8', + title: 'Database Schema Migrations', + description: 'Visual tool for creating and managing database migrations', + category: 'Database', + priority: 'high', + status: 'in-progress', + createdAt: Date.now() - 3000000, + }, + { + id: 'idea-9', + title: 'Mobile App Preview', + description: 'Live preview on actual mobile devices or simulators', + category: 'Mobile', + priority: 'medium', + status: 'planned', + createdAt: Date.now() - 2000000, + }, + { + id: 'idea-10', + title: 'Accessibility Checker', + description: 'Automated accessibility testing and suggestions', + category: 'Accessibility', + priority: 'high', + status: 'idea', + createdAt: Date.now() - 1000000, + }, +] + +export const CONNECTION_STYLE = { + stroke: '#a78bfa', + strokeWidth: 2.5 +} + export const CATEGORIES = [ 'AI/ML', 'Collaboration', diff --git a/src/components/FeatureIdeaCloud/nodes.tsx b/src/components/FeatureIdeaCloud/nodes.tsx new file mode 100644 index 0000000..b1a7b88 --- /dev/null +++ b/src/components/FeatureIdeaCloud/nodes.tsx @@ -0,0 +1,145 @@ +import { useState, useEffect, ReactElement } from 'react' +import { NodeProps, Handle, Position } from 'reactflow' +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { DotsThree } from '@phosphor-icons/react' +import { FeatureIdea, IdeaGroup } from './types' +import { PRIORITY_COLORS, STATUS_COLORS, GROUP_COLORS } from './constants' + +export function GroupNode({ data, selected }: NodeProps) { + const colorScheme = GROUP_COLORS.find(c => c.value === data.color) || GROUP_COLORS[0] + + return ( +
+
+ {data.label} +
+ +
+ ) +} + +export function IdeaNode({ data, selected, id }: NodeProps & { id: string }) { + const [connectionCounts, setConnectionCounts] = useState>({ + left: 0, + right: 0, + top: 0, + bottom: 0, + }) + + useEffect(() => { + const updateConnectionCounts = (event: CustomEvent) => { + const { nodeId, counts } = event.detail + if (nodeId === id) { + setConnectionCounts(counts) + } + } + + window.addEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener) + return () => { + window.removeEventListener('updateConnectionCounts' as any, updateConnectionCounts as EventListener) + } + }, [id]) + + const generateHandles = (position: Position, type: 'source' | 'target', side: string) => { + const count = connectionCounts[side] || 0 + const totalHandles = Math.max(2, count + 1) + const handles: ReactElement[] = [] + + for (let i = 0; i < totalHandles; i++) { + const handleId = `${side}-${i}` + const isVertical = position === Position.Top || position === Position.Bottom + const positionStyle = isVertical + ? { left: `${((i + 1) / (totalHandles + 1)) * 100}%` } + : { top: `${((i + 1) / (totalHandles + 1)) * 100}%` } + + handles.push( + + ) + } + + return handles + } + + return ( +
+ {generateHandles(Position.Left, 'target', 'left')} + {generateHandles(Position.Right, 'source', 'right')} + {generateHandles(Position.Top, 'target', 'top')} + {generateHandles(Position.Bottom, 'source', 'bottom')} + + +
+
+

{data.title}

+ +
+

+ {data.description} +

+
+ + {data.category} + + + {data.status} + +
+
+
+
+ ) +} + +export const nodeTypes = { + ideaNode: IdeaNode, + groupNode: GroupNode, +} diff --git a/src/components/FeatureIdeaCloud/types.ts b/src/components/FeatureIdeaCloud/types.ts new file mode 100644 index 0000000..84efd30 --- /dev/null +++ b/src/components/FeatureIdeaCloud/types.ts @@ -0,0 +1,21 @@ +export interface FeatureIdea { + id: string + title: string + description: string + category: string + priority: 'low' | 'medium' | 'high' + status: 'idea' | 'planned' | 'in-progress' | 'completed' + createdAt: number + parentGroup?: string +} + +export interface IdeaGroup { + id: string + label: string + color: string + createdAt: number +} + +export interface IdeaEdgeData { + label?: string +} From 1414a08aa7c06335a8c3e208b47972f26e7df318 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:16:45 +0000 Subject: [PATCH 3/4] Refactor FaviconDesigner into modular components Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/components/FaviconDesigner.tsx | 341 +----------------- src/components/FaviconDesigner/canvasUtils.ts | 247 +++++++++++++ src/components/FaviconDesigner/constants.ts | 48 +++ src/components/FaviconDesigner/types.ts | 34 ++ 4 files changed, 339 insertions(+), 331 deletions(-) create mode 100644 src/components/FaviconDesigner/canvasUtils.ts create mode 100644 src/components/FaviconDesigner/constants.ts create mode 100644 src/components/FaviconDesigner/types.ts diff --git a/src/components/FaviconDesigner.tsx b/src/components/FaviconDesigner.tsx index bd8ce74..efc762f 100644 --- a/src/components/FaviconDesigner.tsx +++ b/src/components/FaviconDesigner.tsx @@ -4,7 +4,6 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Slider } from '@/components/ui/slider' import { ScrollArea } from '@/components/ui/scroll-area' @@ -14,96 +13,17 @@ import { Plus, Trash, Download, - CircleNotch, - Square, - Triangle, - Star, - Heart, - Polygon, - TextT, - Image as ImageIcon, - ArrowCounterClockwise, Copy, - FloppyDisk, PencilSimple, Eraser, Gradient, Sparkle, Drop, - MagicWand } from '@phosphor-icons/react' import { toast } from 'sonner' - -type BrushEffect = 'solid' | 'gradient' | 'spray' | 'glow' -type CanvasFilter = 'none' | 'blur' | 'brightness' | 'contrast' | 'grayscale' | 'sepia' | 'invert' | 'saturate' | 'hue-rotate' | 'pixelate' - -interface FaviconElement { - id: string - type: 'circle' | 'square' | 'triangle' | 'star' | 'heart' | 'polygon' | 'text' | 'emoji' | 'freehand' - x: number - y: number - width: number - height: number - color: string - rotation: number - text?: string - fontSize?: number - fontWeight?: string - emoji?: string - paths?: Array<{ x: number; y: number }> - strokeWidth?: number - brushEffect?: BrushEffect - gradientColor?: string - glowIntensity?: number -} - -interface FaviconDesign { - id: string - name: string - size: number - backgroundColor: string - elements: FaviconElement[] - createdAt: number - updatedAt: number - filter?: CanvasFilter - filterIntensity?: number -} - -const PRESET_SIZES = [16, 32, 48, 64, 128, 256, 512] -const ELEMENT_TYPES = [ - { value: 'circle', label: 'Circle', icon: CircleNotch }, - { value: 'square', label: 'Square', icon: Square }, - { value: 'triangle', label: 'Triangle', icon: Triangle }, - { value: 'star', label: 'Star', icon: Star }, - { value: 'heart', label: 'Heart', icon: Heart }, - { value: 'polygon', label: 'Polygon', icon: Polygon }, - { value: 'text', label: 'Text', icon: TextT }, - { value: 'emoji', label: 'Emoji', icon: ImageIcon }, -] - -const DEFAULT_DESIGN: FaviconDesign = { - id: 'default', - name: 'My Favicon', - size: 128, - backgroundColor: '#7c3aed', - elements: [ - { - id: '1', - type: 'text', - x: 64, - y: 64, - width: 100, - height: 100, - color: '#ffffff', - rotation: 0, - text: 'CF', - fontSize: 48, - fontWeight: 'bold', - }, - ], - createdAt: Date.now(), - updatedAt: Date.now(), -} +import { BrushEffect, CanvasFilter, FaviconElement, FaviconDesign } from './FaviconDesigner/types' +import { PRESET_SIZES, ELEMENT_TYPES, DEFAULT_DESIGN } from './FaviconDesigner/constants' +import { drawCanvas } from './FaviconDesigner/canvasUtils' export function FaviconDesigner() { const [designs, setDesigns] = useKV('favicon-designs', [DEFAULT_DESIGN]) @@ -125,255 +45,11 @@ export function FaviconDesigner() { const selectedElement = activeDesign.elements.find((e) => e.id === selectedElementId) useEffect(() => { - drawCanvas() - }, [activeDesign]) - - const drawCanvas = () => { const canvas = canvasRef.current - if (!canvas) return - - const ctx = canvas.getContext('2d') - if (!ctx) return - - const size = activeDesign.size - canvas.width = size - canvas.height = size - - ctx.fillStyle = activeDesign.backgroundColor - ctx.fillRect(0, 0, size, size) - - activeDesign.elements.forEach((element) => { - ctx.save() - - if (element.type === 'freehand' && element.paths && element.paths.length > 0) { - const effect = element.brushEffect || 'solid' - const strokeWidth = element.strokeWidth || 3 - - if (effect === 'glow') { - ctx.shadowColor = element.color - ctx.shadowBlur = element.glowIntensity || 10 - } - - if (effect === 'gradient' && element.gradientColor) { - const bounds = getPathBounds(element.paths) - const gradient = ctx.createLinearGradient( - bounds.minX, - bounds.minY, - bounds.maxX, - bounds.maxY - ) - gradient.addColorStop(0, element.color) - gradient.addColorStop(1, element.gradientColor) - ctx.strokeStyle = gradient - } else { - ctx.strokeStyle = element.color - } - - ctx.lineWidth = strokeWidth - ctx.lineCap = 'round' - ctx.lineJoin = 'round' - - if (effect === 'spray') { - element.paths.forEach((point, i) => { - if (i % 2 === 0) { - for (let j = 0; j < 3; j++) { - const offsetX = (Math.random() - 0.5) * strokeWidth * 2 - const offsetY = (Math.random() - 0.5) * strokeWidth * 2 - ctx.fillStyle = element.color - ctx.beginPath() - ctx.arc(point.x + offsetX, point.y + offsetY, strokeWidth / 3, 0, Math.PI * 2) - ctx.fill() - } - } - }) - } else { - ctx.beginPath() - ctx.moveTo(element.paths[0].x, element.paths[0].y) - for (let i = 1; i < element.paths.length; i++) { - ctx.lineTo(element.paths[i].x, element.paths[i].y) - } - ctx.stroke() - } - - ctx.shadowBlur = 0 - } else { - ctx.translate(element.x, element.y) - ctx.rotate((element.rotation * Math.PI) / 180) - ctx.fillStyle = element.color - - switch (element.type) { - case 'circle': - ctx.beginPath() - ctx.arc(0, 0, element.width / 2, 0, Math.PI * 2) - ctx.fill() - break - case 'square': - ctx.fillRect(-element.width / 2, -element.height / 2, element.width, element.height) - break - case 'triangle': - ctx.beginPath() - ctx.moveTo(0, -element.height / 2) - ctx.lineTo(element.width / 2, element.height / 2) - ctx.lineTo(-element.width / 2, element.height / 2) - ctx.closePath() - ctx.fill() - break - case 'star': - drawStar(ctx, 0, 0, 5, element.width / 2, element.width / 4) - break - case 'heart': - drawHeart(ctx, 0, 0, element.width) - break - case 'polygon': - drawPolygon(ctx, 0, 0, 6, element.width / 2) - break - case 'text': - ctx.fillStyle = element.color - ctx.font = `${element.fontWeight || 'bold'} ${element.fontSize || 32}px sans-serif` - ctx.textAlign = 'center' - ctx.textBaseline = 'middle' - ctx.fillText(element.text || '', 0, 0) - break - case 'emoji': - ctx.font = `${element.fontSize || 32}px sans-serif` - ctx.textAlign = 'center' - ctx.textBaseline = 'middle' - ctx.fillText(element.emoji || '😀', 0, 0) - break - } - } - - ctx.restore() - }) - - if (activeDesign.filter && activeDesign.filter !== 'none') { - applyCanvasFilter(ctx, activeDesign.filter, activeDesign.filterIntensity || 50) + if (canvas) { + drawCanvas(canvas, activeDesign) } - } - - const getPathBounds = (paths: Array<{ x: number; y: number }>) => { - const xs = paths.map(p => p.x) - const ys = paths.map(p => p.y) - return { - minX: Math.min(...xs), - maxX: Math.max(...xs), - minY: Math.min(...ys), - maxY: Math.max(...ys), - } - } - - const applyCanvasFilter = (ctx: CanvasRenderingContext2D, filter: CanvasFilter, intensity: number) => { - const canvas = ctx.canvas - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) - const data = imageData.data - - switch (filter) { - case 'blur': - ctx.filter = `blur(${intensity / 10}px)` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'brightness': - ctx.filter = `brightness(${intensity / 50})` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'contrast': - ctx.filter = `contrast(${intensity / 50})` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'grayscale': - ctx.filter = `grayscale(${intensity / 100})` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'sepia': - ctx.filter = `sepia(${intensity / 100})` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'invert': - ctx.filter = `invert(${intensity / 100})` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'saturate': - ctx.filter = `saturate(${intensity / 50})` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'hue-rotate': - ctx.filter = `hue-rotate(${intensity * 3.6}deg)` - ctx.drawImage(canvas, 0, 0) - ctx.filter = 'none' - break - case 'pixelate': { - const pixelSize = Math.max(1, Math.floor(intensity / 10)) - const tempCanvas = document.createElement('canvas') - tempCanvas.width = canvas.width / pixelSize - tempCanvas.height = canvas.height / pixelSize - const tempCtx = tempCanvas.getContext('2d') - if (tempCtx) { - tempCtx.imageSmoothingEnabled = false - tempCtx.drawImage(canvas, 0, 0, tempCanvas.width, tempCanvas.height) - ctx.imageSmoothingEnabled = false - ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height) - ctx.imageSmoothingEnabled = true - } - break - } - } - } - - const drawStar = (ctx: CanvasRenderingContext2D, cx: number, cy: number, spikes: number, outerRadius: number, innerRadius: number) => { - let rot = (Math.PI / 2) * 3 - let x = cx - let y = cy - const step = Math.PI / spikes - - ctx.beginPath() - ctx.moveTo(cx, cy - outerRadius) - for (let i = 0; i < spikes; i++) { - x = cx + Math.cos(rot) * outerRadius - y = cy + Math.sin(rot) * outerRadius - ctx.lineTo(x, y) - rot += step - - x = cx + Math.cos(rot) * innerRadius - y = cy + Math.sin(rot) * innerRadius - ctx.lineTo(x, y) - rot += step - } - ctx.lineTo(cx, cy - outerRadius) - ctx.closePath() - ctx.fill() - } - - const drawHeart = (ctx: CanvasRenderingContext2D, x: number, y: number, size: number) => { - const topCurveHeight = size * 0.3 - ctx.beginPath() - ctx.moveTo(x, y + topCurveHeight) - ctx.bezierCurveTo(x, y, x - size / 2, y - topCurveHeight, x - size / 2, y + topCurveHeight) - ctx.bezierCurveTo(x - size / 2, y + (size + topCurveHeight) / 2, x, y + (size + topCurveHeight) / 1.2, x, y + size) - ctx.bezierCurveTo(x, y + (size + topCurveHeight) / 1.2, x + size / 2, y + (size + topCurveHeight) / 2, x + size / 2, y + topCurveHeight) - ctx.bezierCurveTo(x + size / 2, y - topCurveHeight, x, y, x, y + topCurveHeight) - ctx.closePath() - ctx.fill() - } - - const drawPolygon = (ctx: CanvasRenderingContext2D, x: number, y: number, sides: number, radius: number) => { - ctx.beginPath() - for (let i = 0; i < sides; i++) { - const angle = (i * 2 * Math.PI) / sides - Math.PI / 2 - const px = x + radius * Math.cos(angle) - const py = y + radius * Math.sin(angle) - if (i === 0) ctx.moveTo(px, py) - else ctx.lineTo(px, py) - } - ctx.closePath() - ctx.fill() - } + }, [activeDesign]) const handleAddElement = (type: FaviconElement['type']) => { const newElement: FaviconElement = { @@ -720,7 +396,10 @@ export function FaviconDesigner() { } setCurrentPath([]) - drawCanvas() + const canvas = canvasRef.current + if (canvas) { + drawCanvas(canvas, activeDesign) + } } const handleCanvasMouseLeave = () => { diff --git a/src/components/FaviconDesigner/canvasUtils.ts b/src/components/FaviconDesigner/canvasUtils.ts new file mode 100644 index 0000000..d9d95a7 --- /dev/null +++ b/src/components/FaviconDesigner/canvasUtils.ts @@ -0,0 +1,247 @@ +import { FaviconElement, FaviconDesign, CanvasFilter } from './types' + +export function getPathBounds(paths: Array<{ x: number; y: number }>) { + const xs = paths.map(p => p.x) + const ys = paths.map(p => p.y) + return { + minX: Math.min(...xs), + maxX: Math.max(...xs), + minY: Math.min(...ys), + maxY: Math.max(...ys), + } +} + +export function drawStar(ctx: CanvasRenderingContext2D, cx: number, cy: number, spikes: number, outerRadius: number, innerRadius: number) { + let rot = (Math.PI / 2) * 3 + let x = cx + let y = cy + const step = Math.PI / spikes + + ctx.beginPath() + ctx.moveTo(cx, cy - outerRadius) + for (let i = 0; i < spikes; i++) { + x = cx + Math.cos(rot) * outerRadius + y = cy + Math.sin(rot) * outerRadius + ctx.lineTo(x, y) + rot += step + + x = cx + Math.cos(rot) * innerRadius + y = cy + Math.sin(rot) * innerRadius + ctx.lineTo(x, y) + rot += step + } + ctx.lineTo(cx, cy - outerRadius) + ctx.closePath() + ctx.fill() +} + +export function drawHeart(ctx: CanvasRenderingContext2D, x: number, y: number, size: number) { + const topCurveHeight = size * 0.3 + ctx.beginPath() + ctx.moveTo(x, y + topCurveHeight) + ctx.bezierCurveTo(x, y, x - size / 2, y - topCurveHeight, x - size / 2, y + topCurveHeight) + ctx.bezierCurveTo(x - size / 2, y + (size + topCurveHeight) / 2, x, y + (size + topCurveHeight) / 1.2, x, y + size) + ctx.bezierCurveTo(x, y + (size + topCurveHeight) / 1.2, x + size / 2, y + (size + topCurveHeight) / 2, x + size / 2, y + topCurveHeight) + ctx.bezierCurveTo(x + size / 2, y - topCurveHeight, x, y, x, y + topCurveHeight) + ctx.closePath() + ctx.fill() +} + +export function drawPolygon(ctx: CanvasRenderingContext2D, x: number, y: number, sides: number, radius: number) { + ctx.beginPath() + for (let i = 0; i < sides; i++) { + const angle = (i * 2 * Math.PI) / sides - Math.PI / 2 + const px = x + radius * Math.cos(angle) + const py = y + radius * Math.sin(angle) + if (i === 0) ctx.moveTo(px, py) + else ctx.lineTo(px, py) + } + ctx.closePath() + ctx.fill() +} + +export function applyCanvasFilter(ctx: CanvasRenderingContext2D, filter: CanvasFilter, intensity: number) { + const canvas = ctx.canvas + + switch (filter) { + case 'blur': + ctx.filter = `blur(${intensity / 10}px)` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'brightness': + ctx.filter = `brightness(${intensity / 50})` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'contrast': + ctx.filter = `contrast(${intensity / 50})` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'grayscale': + ctx.filter = `grayscale(${intensity / 100})` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'sepia': + ctx.filter = `sepia(${intensity / 100})` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'invert': + ctx.filter = `invert(${intensity / 100})` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'saturate': + ctx.filter = `saturate(${intensity / 50})` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'hue-rotate': + ctx.filter = `hue-rotate(${intensity * 3.6}deg)` + ctx.drawImage(canvas, 0, 0) + ctx.filter = 'none' + break + case 'pixelate': { + const pixelSize = Math.max(1, Math.floor(intensity / 10)) + const tempCanvas = document.createElement('canvas') + tempCanvas.width = canvas.width / pixelSize + tempCanvas.height = canvas.height / pixelSize + const tempCtx = tempCanvas.getContext('2d') + if (tempCtx) { + tempCtx.imageSmoothingEnabled = false + tempCtx.drawImage(canvas, 0, 0, tempCanvas.width, tempCanvas.height) + ctx.imageSmoothingEnabled = false + ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height) + ctx.imageSmoothingEnabled = true + } + break + } + } +} + +export function drawElement(ctx: CanvasRenderingContext2D, element: FaviconElement) { + ctx.save() + + if (element.type === 'freehand' && element.paths && element.paths.length > 0) { + const effect = element.brushEffect || 'solid' + const strokeWidth = element.strokeWidth || 3 + + if (effect === 'glow') { + ctx.shadowColor = element.color + ctx.shadowBlur = element.glowIntensity || 10 + } + + if (effect === 'gradient' && element.gradientColor) { + const bounds = getPathBounds(element.paths) + const gradient = ctx.createLinearGradient( + bounds.minX, + bounds.minY, + bounds.maxX, + bounds.maxY + ) + gradient.addColorStop(0, element.color) + gradient.addColorStop(1, element.gradientColor) + ctx.strokeStyle = gradient + } else { + ctx.strokeStyle = element.color + } + + ctx.lineWidth = strokeWidth + ctx.lineCap = 'round' + ctx.lineJoin = 'round' + + if (effect === 'spray') { + element.paths.forEach((point, i) => { + if (i % 2 === 0) { + for (let j = 0; j < 3; j++) { + const offsetX = (Math.random() - 0.5) * strokeWidth * 2 + const offsetY = (Math.random() - 0.5) * strokeWidth * 2 + ctx.fillStyle = element.color + ctx.beginPath() + ctx.arc(point.x + offsetX, point.y + offsetY, strokeWidth / 3, 0, Math.PI * 2) + ctx.fill() + } + } + }) + } else { + ctx.beginPath() + ctx.moveTo(element.paths[0].x, element.paths[0].y) + for (let i = 1; i < element.paths.length; i++) { + ctx.lineTo(element.paths[i].x, element.paths[i].y) + } + ctx.stroke() + } + + ctx.shadowBlur = 0 + } else { + ctx.translate(element.x, element.y) + ctx.rotate((element.rotation * Math.PI) / 180) + ctx.fillStyle = element.color + + switch (element.type) { + case 'circle': + ctx.beginPath() + ctx.arc(0, 0, element.width / 2, 0, Math.PI * 2) + ctx.fill() + break + case 'square': + ctx.fillRect(-element.width / 2, -element.height / 2, element.width, element.height) + break + case 'triangle': + ctx.beginPath() + ctx.moveTo(0, -element.height / 2) + ctx.lineTo(element.width / 2, element.height / 2) + ctx.lineTo(-element.width / 2, element.height / 2) + ctx.closePath() + ctx.fill() + break + case 'star': + drawStar(ctx, 0, 0, 5, element.width / 2, element.width / 4) + break + case 'heart': + drawHeart(ctx, 0, 0, element.width) + break + case 'polygon': + drawPolygon(ctx, 0, 0, 6, element.width / 2) + break + case 'text': + ctx.fillStyle = element.color + ctx.font = `${element.fontWeight || 'bold'} ${element.fontSize || 32}px sans-serif` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillText(element.text || '', 0, 0) + break + case 'emoji': + ctx.font = `${element.fontSize || 32}px sans-serif` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillText(element.emoji || '😀', 0, 0) + break + } + } + + ctx.restore() +} + +export function drawCanvas(canvas: HTMLCanvasElement, design: FaviconDesign) { + const ctx = canvas.getContext('2d') + if (!ctx) return + + const size = design.size + canvas.width = size + canvas.height = size + + ctx.fillStyle = design.backgroundColor + ctx.fillRect(0, 0, size, size) + + design.elements.forEach((element) => { + drawElement(ctx, element) + }) + + if (design.filter && design.filter !== 'none') { + applyCanvasFilter(ctx, design.filter, design.filterIntensity || 50) + } +} diff --git a/src/components/FaviconDesigner/constants.ts b/src/components/FaviconDesigner/constants.ts new file mode 100644 index 0000000..70c6c78 --- /dev/null +++ b/src/components/FaviconDesigner/constants.ts @@ -0,0 +1,48 @@ +import { + CircleNotch, + Square, + Triangle, + Star, + Heart, + Polygon, + TextT, + Image as ImageIcon, +} from '@phosphor-icons/react' +import { FaviconDesign } from './types' + +export const PRESET_SIZES = [16, 32, 48, 64, 128, 256, 512] + +export const ELEMENT_TYPES = [ + { value: 'circle', label: 'Circle', icon: CircleNotch }, + { value: 'square', label: 'Square', icon: Square }, + { value: 'triangle', label: 'Triangle', icon: Triangle }, + { value: 'star', label: 'Star', icon: Star }, + { value: 'heart', label: 'Heart', icon: Heart }, + { value: 'polygon', label: 'Polygon', icon: Polygon }, + { value: 'text', label: 'Text', icon: TextT }, + { value: 'emoji', label: 'Emoji', icon: ImageIcon }, +] + +export const DEFAULT_DESIGN: FaviconDesign = { + id: 'default', + name: 'My Favicon', + size: 128, + backgroundColor: '#7c3aed', + elements: [ + { + id: '1', + type: 'text', + x: 64, + y: 64, + width: 100, + height: 100, + color: '#ffffff', + rotation: 0, + text: 'CF', + fontSize: 48, + fontWeight: 'bold', + }, + ], + createdAt: Date.now(), + updatedAt: Date.now(), +} diff --git a/src/components/FaviconDesigner/types.ts b/src/components/FaviconDesigner/types.ts new file mode 100644 index 0000000..0a06892 --- /dev/null +++ b/src/components/FaviconDesigner/types.ts @@ -0,0 +1,34 @@ +export type BrushEffect = 'solid' | 'gradient' | 'spray' | 'glow' +export type CanvasFilter = 'none' | 'blur' | 'brightness' | 'contrast' | 'grayscale' | 'sepia' | 'invert' | 'saturate' | 'hue-rotate' | 'pixelate' + +export interface FaviconElement { + id: string + type: 'circle' | 'square' | 'triangle' | 'star' | 'heart' | 'polygon' | 'text' | 'emoji' | 'freehand' + x: number + y: number + width: number + height: number + color: string + rotation: number + text?: string + fontSize?: number + fontWeight?: string + emoji?: string + paths?: Array<{ x: number; y: number }> + strokeWidth?: number + brushEffect?: BrushEffect + gradientColor?: string + glowIntensity?: number +} + +export interface FaviconDesign { + id: string + name: string + size: number + backgroundColor: string + elements: FaviconElement[] + createdAt: number + updatedAt: number + filter?: CanvasFilter + filterIntensity?: number +} From 33787398e254c5ad25e2916d65ed329b65fd45cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:20:28 +0000 Subject: [PATCH 4/4] Fix icon consistency in CICDPlatformItem component Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/components/DocumentationView/DocComponents.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DocumentationView/DocComponents.tsx b/src/components/DocumentationView/DocComponents.tsx index b3a395b..b6225ce 100644 --- a/src/components/DocumentationView/DocComponents.tsx +++ b/src/components/DocumentationView/DocComponents.tsx @@ -1,6 +1,6 @@ import { Card, CardContent } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' -import { FileCode, CheckCircle, Sparkle } from '@phosphor-icons/react' +import { FileCode, CheckCircle, Sparkle, GitBranch } from '@phosphor-icons/react' export function FeatureItem({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) { return ( @@ -115,7 +115,7 @@ export function CICDPlatformItem({ name, file, description, features }: {
- +

{name}

{file}