├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── jsconfig.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public └── thumbnail.png ├── src ├── app │ ├── favicon.ico │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ ├── globals.css │ ├── layout.js │ └── page.js ├── components │ └── ui │ │ └── switch.jsx └── lib │ └── utils.js └── tailwind.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Code Visualizer: 2 | 3 | **Transforming Code into Engaging Visual Timelines** 4 | 5 | Code Visualizer is a web application that generates colorful visualizations from code inputs, highlighting different segments for improved readability and engagement. 6 | 7 | Thumbnail 8 | 9 | Find Code Visualizer at the following URL: 10 | 11 | [codevi.netlify.com](https://codevi.netlify.app/)
-------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-timeline", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@nextui-org/button": "^2.0.38", 13 | "@nextui-org/system": "^2.2.6", 14 | "@nextui-org/theme": "^2.2.11", 15 | "@radix-ui/react-icons": "^1.3.0", 16 | "@radix-ui/react-switch": "^1.1.1", 17 | "@radix-ui/react-toggle": "^1.1.0", 18 | "class-variance-authority": "^0.7.0", 19 | "clsx": "^2.1.1", 20 | "framer-motion": "^11.11.2", 21 | "html2canvas": "^1.4.1", 22 | "lucide-react": "^0.451.0", 23 | "next": "14.2.14", 24 | "react": "^18", 25 | "react-ace": "^12.0.0", 26 | "react-dom": "^18", 27 | "tailwind-merge": "^2.5.3", 28 | "tailwindcss-animate": "^1.0.7" 29 | }, 30 | "devDependencies": { 31 | "eslint": "^8", 32 | "eslint-config-next": "14.2.14", 33 | "postcss": "^8", 34 | "tailwindcss": "^3.4.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/6c662a0bb7415201569594d7499391ea2faf655f/public/thumbnail.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/6c662a0bb7415201569594d7499391ea2faf655f/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/6c662a0bb7415201569594d7499391ea2faf655f/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/6c662a0bb7415201569594d7499391ea2faf655f/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } 8 | 9 | @layer utilities { 10 | .text-balance { 11 | text-wrap: balance; 12 | } 13 | } 14 | 15 | @layer base { 16 | :root { 17 | --background: 0 0% 100%; 18 | --foreground: 0 0% 3.9%; 19 | --card: 0 0% 100%; 20 | --card-foreground: 0 0% 3.9%; 21 | --popover: 0 0% 100%; 22 | --popover-foreground: 0 0% 3.9%; 23 | --primary: 0 0% 9%; 24 | --primary-foreground: 0 0% 98%; 25 | --secondary: 0 0% 96.1%; 26 | --secondary-foreground: 0 0% 9%; 27 | --muted: 0 0% 96.1%; 28 | --muted-foreground: 0 0% 45.1%; 29 | --accent: 0 0% 96.1%; 30 | --accent-foreground: 0 0% 9%; 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 0 0% 98%; 33 | --border: 0 0% 89.8%; 34 | --input: 0 0% 89.8%; 35 | --ring: 0 0% 3.9%; 36 | --chart-1: 12 76% 61%; 37 | --chart-2: 173 58% 39%; 38 | --chart-3: 197 37% 24%; 39 | --chart-4: 43 74% 66%; 40 | --chart-5: 27 87% 67%; 41 | --radius: 0.5rem; 42 | } 43 | .dark { 44 | --background: 0 0% 3.9%; 45 | --foreground: 0 0% 98%; 46 | --card: 0 0% 3.9%; 47 | --card-foreground: 0 0% 98%; 48 | --popover: 0 0% 3.9%; 49 | --popover-foreground: 0 0% 98%; 50 | --primary: 0 0% 98%; 51 | --primary-foreground: 0 0% 9%; 52 | --secondary: 0 0% 14.9%; 53 | --secondary-foreground: 0 0% 98%; 54 | --muted: 0 0% 14.9%; 55 | --muted-foreground: 0 0% 63.9%; 56 | --accent: 0 0% 14.9%; 57 | --accent-foreground: 0 0% 98%; 58 | --destructive: 0 62.8% 30.6%; 59 | --destructive-foreground: 0 0% 98%; 60 | --border: 0 0% 14.9%; 61 | --input: 0 0% 14.9%; 62 | --ring: 0 0% 83.1%; 63 | --chart-1: 220 70% 50%; 64 | --chart-2: 160 60% 45%; 65 | --chart-3: 30 80% 55%; 66 | --chart-4: 280 65% 60%; 67 | --chart-5: 340 75% 55%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app/layout.js: -------------------------------------------------------------------------------- 1 | import localFont from "next/font/local"; 2 | import "./globals.css"; 3 | 4 | const geistSans = localFont({ 5 | src: "./fonts/GeistVF.woff", 6 | variable: "--font-geist-sans", 7 | weight: "100 900", 8 | }); 9 | const geistMono = localFont({ 10 | src: "./fonts/GeistMonoVF.woff", 11 | variable: "--font-geist-mono", 12 | weight: "100 900", 13 | }); 14 | 15 | export const metadata = { 16 | title: "Code Timeline", 17 | description: "Visualize your code execution in a timeline", 18 | }; 19 | 20 | export default function RootLayout({ children }) { 21 | return ( 22 | 23 | 26 | {children} 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/page.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState, useRef } from "react"; 4 | import { Download ,Github} from "lucide-react"; 5 | import html2canvas from "html2canvas"; 6 | import AceEditor from "react-ace"; 7 | 8 | import "ace-builds/src-noconflict/mode-dart"; 9 | import "ace-builds/src-noconflict/theme-dracula"; 10 | import "ace-builds/src-noconflict/theme-github"; 11 | import "ace-builds/src-noconflict/ext-language_tools"; 12 | 13 | const CodeTimeline = () => { 14 | const [codeInput, setCodeInput] = useState(""); 15 | const [timelineData, setTimelineData] = useState([]); 16 | const [darkMode, setDarkMode] = useState(true); 17 | const timelineRef = useRef(null); 18 | const [elementTypes, setElementTypes] = useState({ 19 | keyword: "#FF6B6B", // Soft Red 20 | class: "#4ECDC4", // Teal 21 | function: "#45B7D1", // Sky Blue 22 | variable: "#96CEB4", // Sage Green 23 | operator: darkMode ? "#FFD93D" : "#FFD700", // Soft yellow 24 | string: "#FF8C42", // Soft Orange 25 | number: "#6A0572", // Deep Purple 26 | boolean: "#FF4081", // Pink 27 | comment: "#78909C", // Blue Grey 28 | import: "#26A69A", // Green Teal 29 | decorator: "#BA68C8", // Light Purple 30 | punctuation: "#B0BEC5", // Light Blue Grey 31 | bracket: "#00BCD4", // Cyan 32 | property: "#8BC34A", // Light Green 33 | space: "transparent", 34 | default: darkMode ? "#E0E0E0" : "#424242", // Light Grey / Dark Grey 35 | }); 36 | 37 | const keywords = [ 38 | "class", 39 | "function", 40 | "const", 41 | "let", 42 | "var", 43 | "if", 44 | "else", 45 | "for", 46 | "while", 47 | "return", 48 | "import", 49 | "from", 50 | "async", 51 | "await", 52 | "try", 53 | "catch", 54 | "throw", 55 | "new", 56 | "this", 57 | "super", 58 | ]; 59 | 60 | const tokenizeLine = (line) => { 61 | const tokens = line.split(/(\s+|[{}()[\],;.])/); 62 | 63 | return tokens 64 | .map((token) => { 65 | if (!token) return null; 66 | 67 | let color = elementTypes.default; 68 | let width = token.length * 8; 69 | 70 | if (token.trim() === "") { 71 | return { 72 | text: token, 73 | color: elementTypes.space, 74 | width: token.length * 8, 75 | }; 76 | } 77 | 78 | if (keywords.includes(token)) { 79 | color = elementTypes.keyword; 80 | } else if (token.match(/^[A-Z][a-zA-Z0-9]*$/)) { 81 | color = elementTypes.class; 82 | } else if (token.match(/^[a-z][a-zA-Z0-9]*(?=\()/)) { 83 | color = elementTypes.function; 84 | } else if (token.match(/^[a-z][a-zA-Z0-9]*$/)) { 85 | color = elementTypes.variable; 86 | } else if (token.match(/[+\-*/%=<>!&|^~]/)) { 87 | color = elementTypes.operator; 88 | } else if (token.match(/^(['"]).*\1$/)) { 89 | color = elementTypes.string; 90 | } else if (token.match(/^\d+$/)) { 91 | color = elementTypes.number; 92 | } else if (token === "true" || token === "false") { 93 | color = elementTypes.boolean; 94 | } else if (token.startsWith("//")) { 95 | color = elementTypes.comment; 96 | } else if (token === "import" || token === "from") { 97 | color = elementTypes.import; 98 | } else if (token.startsWith("@")) { 99 | color = elementTypes.decorator; 100 | } else if (token.match(/[{}()[\]]/)) { 101 | color = elementTypes.bracket; 102 | } else if (token.match(/[.,;]/)) { 103 | color = elementTypes.punctuation; 104 | } 105 | 106 | return { text: token, color, width }; 107 | }) 108 | .filter(Boolean); 109 | }; 110 | 111 | const generateTimelineFromCode = (code) => { 112 | const lines = code.split("\n"); 113 | return lines 114 | .map((line, index) => ({ 115 | id: index + 1, 116 | segments: tokenizeLine(line), 117 | })) 118 | .filter((line) => line.segments.length > 0); 119 | }; 120 | 121 | const handleInputChange = (newCode) => { 122 | setCodeInput(newCode); 123 | setTimelineData(generateTimelineFromCode(newCode)); 124 | }; 125 | 126 | const toggleDarkMode = () => { 127 | setDarkMode(!darkMode); 128 | localStorage.setItem("darkMode", !darkMode); 129 | }; 130 | 131 | const exportImage = async () => { 132 | if (timelineRef.current) { 133 | const clone = timelineRef.current.cloneNode(true); 134 | 135 | document.body.appendChild(clone); 136 | 137 | const { scrollWidth, scrollHeight } = clone; 138 | 139 | const canvas = await html2canvas(clone, { 140 | width: 922, 141 | height: scrollHeight, 142 | backgroundColor: darkMode ? "#2D2D2D" : "#FFFFFF", 143 | scale: window.devicePixelRatio, 144 | }); 145 | 146 | document.body.removeChild(clone); 147 | 148 | const image = canvas 149 | .toDataURL("image/png") 150 | .replace("image/png", "image/octet-stream"); 151 | const link = document.createElement("a"); 152 | link.download = "code-timeline.png"; 153 | link.href = image; 154 | link.click(); 155 | } 156 | }; 157 | 158 | // useEffect(() => { 159 | // const darkModeSetting = localStorage.getItem("darkMode"); 160 | // const isDarkMode = darkModeSetting === "true" || darkModeSetting === null; 161 | // setDarkMode(isDarkMode); 162 | 163 | // setElementTypes((prev) => ({ 164 | // ...prev, 165 | // operator: !isDarkMode ? "#FFD93D" : "#FFD700", 166 | // default: !isDarkMode ? "#E0E0E0" : "#424242", 167 | // })); 168 | // setTimelineData(generateTimelineFromCode(codeInput)); 169 | // }, [codeInput, darkMode]); 170 | 171 | return ( 172 |
173 |
174 |

179 | Code Timeline Visualizer 180 |

181 |
182 | {/*
183 | 184 | {darkMode ? ( 185 | 186 | ) : ( 187 | 188 | )} 189 | 190 |
*/} 191 | 192 | 202 | 203 | 204 | 205 | 215 |
216 |
217 | 218 |

223 | See your code come to life! 224 |

225 | 226 |
227 |
228 | 243 |
244 | 245 |
246 |
254 |
255 | {timelineData.map((row) => ( 256 |
257 | 262 | {row.id} 263 | 264 |
265 | {row.segments.map((segment, segIndex) => ( 266 |
275 | ))} 276 |
277 |
278 | ))} 279 |
280 |
281 | 282 |
289 |
290 | {Object.entries(elementTypes).map( 291 | ([key, color]) => 292 | key !== "space" && 293 | key !== "default" && ( 294 |
295 |
299 | 302 | {key.charAt(0).toUpperCase() + key.slice(1)} 303 | 304 |
305 | ) 306 | )} 307 |
308 |
309 |
310 |
311 |
312 | ); 313 | }; 314 | 315 | export default CodeTimeline; 316 | -------------------------------------------------------------------------------- /src/components/ui/switch.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Switch = React.forwardRef(({ className, ...props }, ref) => ( 9 | 17 | 22 | 23 | )); 24 | Switch.displayName = SwitchPrimitives.Root.displayName; 25 | 26 | export { Switch }; 27 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const {nextui} = require('@nextui-org/theme'); 2 | /** @type {import('tailwindcss').Config} */ 3 | module.exports = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 9 | "./node_modules/@nextui-org/theme/dist/components/(button|ripple|spinner).js" 10 | ], 11 | theme: { 12 | extend: { 13 | colors: { 14 | background: 'hsl(var(--background))', 15 | foreground: 'hsl(var(--foreground))', 16 | card: { 17 | DEFAULT: 'hsl(var(--card))', 18 | foreground: 'hsl(var(--card-foreground))' 19 | }, 20 | popover: { 21 | DEFAULT: 'hsl(var(--popover))', 22 | foreground: 'hsl(var(--popover-foreground))' 23 | }, 24 | primary: { 25 | DEFAULT: 'hsl(var(--primary))', 26 | foreground: 'hsl(var(--primary-foreground))' 27 | }, 28 | secondary: { 29 | DEFAULT: 'hsl(var(--secondary))', 30 | foreground: 'hsl(var(--secondary-foreground))' 31 | }, 32 | muted: { 33 | DEFAULT: 'hsl(var(--muted))', 34 | foreground: 'hsl(var(--muted-foreground))' 35 | }, 36 | accent: { 37 | DEFAULT: 'hsl(var(--accent))', 38 | foreground: 'hsl(var(--accent-foreground))' 39 | }, 40 | destructive: { 41 | DEFAULT: 'hsl(var(--destructive))', 42 | foreground: 'hsl(var(--destructive-foreground))' 43 | }, 44 | border: 'hsl(var(--border))', 45 | input: 'hsl(var(--input))', 46 | ring: 'hsl(var(--ring))', 47 | chart: { 48 | '1': 'hsl(var(--chart-1))', 49 | '2': 'hsl(var(--chart-2))', 50 | '3': 'hsl(var(--chart-3))', 51 | '4': 'hsl(var(--chart-4))', 52 | '5': 'hsl(var(--chart-5))' 53 | } 54 | }, 55 | borderRadius: { 56 | lg: 'var(--radius)', 57 | md: 'calc(var(--radius) - 2px)', 58 | sm: 'calc(var(--radius) - 4px)' 59 | } 60 | } 61 | }, 62 | plugins: [nextui(), require("tailwindcss-animate")], 63 | }; 64 | --------------------------------------------------------------------------------