├── .eslintrc.json
├── public
└── thumbnail.png
├── src
├── app
│ ├── favicon.ico
│ ├── fonts
│ │ ├── GeistVF.woff
│ │ └── GeistMonoVF.woff
│ ├── layout.js
│ ├── globals.css
│ └── page.js
├── lib
│ └── utils.js
└── components
│ └── ui
│ └── switch.jsx
├── jsconfig.json
├── next.config.mjs
├── postcss.config.mjs
├── README.md
├── components.json
├── .gitignore
├── package.json
└── tailwind.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/main/public/thumbnail.png
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/main/src/app/favicon.ico
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/fonts/GeistVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/main/src/app/fonts/GeistVF.woff
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/src/app/fonts/GeistMonoVF.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/X-SLAYER/code_timeline_preview/main/src/app/fonts/GeistMonoVF.woff
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
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 |
--------------------------------------------------------------------------------