Skip to content

Commit ccd73b2

Browse files
committed
app(): tdc
1 parent d952456 commit ccd73b2

File tree

9 files changed

+209
-6
lines changed

9 files changed

+209
-6
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@testing-library/react": "^16.3.0",
1111
"@testing-library/user-event": "^13.5.0",
1212
"color-thief-react": "^2.1.0",
13+
"diff-match-patch": "^1.0.5",
1314
"framer-motion": "^12.23.24",
1415
"front-matter": "^4.0.2",
1516
"marked": "^16.4.1",

public/apps/apps.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@
175175
"title": "Color Contrast Checker",
176176
"description": "Check WCAG color contrast ratios for accessibility.",
177177
"icon": "Palette"
178+
},
179+
{
180+
"slug": "text-diff-checker",
181+
"to": "/apps/text-diff-checker",
182+
"title": "Text Diff Checker",
183+
"description": "Compare two texts and highlight the differences.",
184+
"icon": "TextAa"
178185
}
179186
]
180187
}

src/components/AnimatedRoutes.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import JsonFormatterPage from '../pages/apps/JsonFormatterPage';
3939
import ColorContrastCheckerPage from '../pages/apps/ColorContrastCheckerPage';
4040
import QrCodeGeneratorPage from '../pages/apps/QrCodeGeneratorPage';
4141
import JsonPimlConverterPage from '../pages/apps/JsonPimlConverterPage';
42+
import TextDiffCheckerPage from '../pages/apps/TextDiffCheckerPage';
4243
import JSONGeneratorPage from '../pages/apps/JSONGeneratorPage';
4344
import SettingsPage from '../pages/SettingsPage'; // Import SettingsPage
4445

@@ -391,6 +392,7 @@ function AnimatedRoutes() {
391392
<Route path="/apps::qr" element={<Navigate to="/apps/qr-code-generator" replace />} />
392393
<Route path="/apps::jpc" element={<Navigate to="/apps/json-piml-converter" replace />} />
393394
<Route path="/apps::jg" element={<Navigate to="/apps/json-generator" replace />} />
395+
<Route path="/apps::tdc" element={<Navigate to="/apps/text-diff-checker" replace />} />
394396
{/* End of hardcoded redirects */}
395397
<Route
396398
path="/apps/ip"
@@ -406,6 +408,20 @@ function AnimatedRoutes() {
406408
</motion.div>
407409
}
408410
/>
411+
<Route
412+
path="/apps/text-diff-checker"
413+
element={
414+
<motion.div
415+
initial="initial"
416+
animate="in"
417+
exit="out"
418+
variants={pageVariants}
419+
transition={pageTransition}
420+
>
421+
<TextDiffCheckerPage />
422+
</motion.div>
423+
}
424+
/>
409425
<Route
410426
path="/apps/json-generator"
411427
element={

src/components/Navbar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from 'react';
22
import { Link } from 'react-router-dom';
33
import Fez from './Fez';
4-
import { SidebarIcon, UserIcon, BookOpenIcon, MagnifyingGlassIcon, NotebookIcon } from '@phosphor-icons/react';
4+
import { SidebarIcon, UserIcon, MagnifyingGlassIcon } from '@phosphor-icons/react';
55

66
const Navbar = ({ toggleSidebar, isSidebarOpen, isSearchVisible, toggleSearch }) => {
77
const [isScrolled, setIsScrolled] = useState(false);

src/components/Sidebar.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import {
2828
RssIcon,
2929
SquaresFourIcon,
3030
GearSix, // Import GearSix icon
31-
NotebookIcon,
3231
} from '@phosphor-icons/react';
3332

3433
import Fez from './Fez';

src/pages/AppPage.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ function AppPage() {
2222

2323
const [groupedApps, setGroupedApps] = useState({});
2424
const [collapsedCategories, setCollapsedCategories] = useState({});
25-
const [isLoading, setIsLoading] = useState(true); // New loading state
25+
const [isLoading, setIsLoading] = useState(true);
26+
const [totalAppsCount, setTotalAppsCount] = useState(0); // New state for total app count
2627

2728
useEffect(() => {
28-
setIsLoading(true); // Set loading to true when fetch starts
29+
setIsLoading(true);
2930
fetch('/apps/apps.json')
3031
.then((response) => response.json())
3132
.then((data) => {
@@ -35,10 +36,17 @@ function AppPage() {
3536
return acc;
3637
}, {});
3738
setCollapsedCategories(initialCollapsedState);
39+
40+
// Calculate total number of apps
41+
let count = 0;
42+
for (const categoryKey in data) {
43+
count += data[categoryKey].apps.length;
44+
}
45+
setTotalAppsCount(count);
3846
})
3947
.catch((error) => console.error('Error fetching apps:', error))
4048
.finally(() => {
41-
setIsLoading(false); // Set loading to false when fetch completes
49+
setIsLoading(false);
4250
});
4351
}, []);
4452

@@ -67,6 +75,8 @@ function AppPage() {
6775
<span className="codex-color">fc</span>
6876
<span className="separator-color">::</span>
6977
<span className="apps-color">apps</span>
78+
<span className="separator-color">::</span>
79+
<span className="single-app-color">[{totalAppsCount}]</span>
7080
</h1>
7181
<hr className="border-gray-700" />
7282
{isLoading ? (
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import React, { useState, useCallback, useEffect } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeftIcon } from '@phosphor-icons/react';
4+
import colors from '../../config/colors';
5+
import useSeo from "../../hooks/useSeo";
6+
import { diff_match_patch } from 'diff-match-patch';
7+
8+
const dmp = new diff_match_patch();
9+
10+
function TextDiffCheckerPage() {
11+
useSeo({
12+
title: 'Text Diff Checker | Fezcodex',
13+
description: 'Compare two texts and highlight the differences (additions, deletions, changes).',
14+
keywords: ['Fezcodex', 'text diff', 'diff checker', 'text comparison', 'code diff'],
15+
ogTitle: 'Text Diff Checker | Fezcodex',
16+
ogDescription: 'Compare two texts and highlight the differences (additions, deletions, changes).',
17+
ogImage: 'https://fezcode.github.io/logo512.png',
18+
twitterCard: 'summary_large_image',
19+
twitterTitle: 'Text Diff Checker | Fezcodex',
20+
twitterDescription: 'Compare two texts and highlight the differences (additions, deletions, changes).',
21+
twitterImage: 'https://fezcode.github.io/logo512.png'
22+
});
23+
24+
const [textA, setTextA] = useState('');
25+
const [textB, setTextB] = useState('');
26+
const [diffOutput, setDiffOutput] = useState([]);
27+
28+
const computeDiff = useCallback(() => {
29+
if (!textA && !textB) {
30+
setDiffOutput([]);
31+
return;
32+
}
33+
34+
const diffs = dmp.diff_main(textA, textB);
35+
dmp.diff_cleanupSemantic(diffs); // Optional: improve human readability
36+
setDiffOutput(diffs);
37+
}, [textA, textB]);
38+
39+
useEffect(() => {
40+
// Automatically compute diff when texts change
41+
computeDiff();
42+
}, [textA, textB, computeDiff]);
43+
44+
const renderDiff = () => {
45+
return diffOutput.map((part, i) => {
46+
const [type, text] = part;
47+
let style = {};
48+
switch (type) {
49+
case DIFF_INSERT:
50+
style = { backgroundColor: '#aaffaa', color: '#000' }; // Green for additions
51+
break;
52+
case DIFF_DELETE:
53+
style = { backgroundColor: '#ffaaaa', color: '#000', textDecoration: 'line-through' }; // Red for deletions
54+
break;
55+
case DIFF_EQUAL:
56+
default:
57+
style = { color: colors['app-light'] }; // Default for unchanged
58+
break;
59+
}
60+
return <span key={i} style={style}>{text.split('\n').map((item, key) => {
61+
return (
62+
<React.Fragment key={key}>
63+
{item}
64+
{key < text.split('\n').length - 1 && <br />}
65+
</React.Fragment>
66+
);
67+
})}</span>;
68+
});
69+
};
70+
71+
const DIFF_DELETE = -1;
72+
const DIFF_INSERT = 1;
73+
const DIFF_EQUAL = 0;
74+
75+
const cardStyle = {
76+
backgroundColor: colors['app-alpha-10'],
77+
borderColor: colors['app-alpha-50'],
78+
color: colors.app,
79+
};
80+
81+
const textareaStyle = `w-full h-48 p-4 bg-gray-900/50 font-mono resize-y border rounded-md border-app-alpha-50 text-app-light focus:ring-0`;
82+
83+
return (
84+
<div className="py-16 sm:py-24">
85+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
86+
<Link
87+
to="/apps"
88+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
89+
>
90+
<ArrowLeftIcon size={24} /> Back to Apps
91+
</Link>
92+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
93+
<span className="codex-color">fc</span>
94+
<span className="separator-color">::</span>
95+
<span className="apps-color">apps</span>
96+
<span className="separator-color">::</span>
97+
<span className="single-app-color">tdc</span>
98+
</h1>
99+
<hr className="border-gray-700" />
100+
<div className="flex justify-center items-center mt-16">
101+
<div
102+
className="group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative overflow-hidden h-full w-full max-w-6xl"
103+
style={cardStyle}
104+
>
105+
<div
106+
className="absolute top-0 left-0 w-full h-full opacity-10"
107+
style={{
108+
backgroundImage:
109+
'radial-gradient(circle, white 1px, transparent 1px)',
110+
backgroundSize: '10px 10px',
111+
}}
112+
></div>
113+
<div className="relative z-10 p-1">
114+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app"> Text Diff Checker </h1>
115+
<hr className="border-gray-700 mb-4" />
116+
117+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
118+
<div>
119+
<label htmlFor="textA" className="block text-lg font-semibold mb-2 text-app">Original Text (A)</label>
120+
<textarea
121+
id="textA"
122+
value={textA}
123+
onChange={(e) => setTextA(e.target.value)}
124+
placeholder="Enter original text here..."
125+
className={textareaStyle}
126+
/>
127+
</div>
128+
<div>
129+
<label htmlFor="textB" className="block text-lg font-semibold mb-2 text-app">New Text (B)</label>
130+
<textarea
131+
id="textB"
132+
value={textB}
133+
onChange={(e) => setTextB(e.target.value)}
134+
placeholder="Enter new text here..."
135+
className={textareaStyle}
136+
/>
137+
</div>
138+
</div>
139+
140+
<div className="flex justify-center mb-6">
141+
<button
142+
onClick={computeDiff}
143+
className="px-6 py-2 rounded-md text-lg font-arvo font-normal transition-colors duration-300 ease-in-out border bg-tb text-app border-app-alpha-50 hover:bg-app/15"
144+
>
145+
Compare Texts
146+
</button>
147+
</div>
148+
149+
<div className="mt-4">
150+
<label className="block text-lg font-semibold mb-2 text-app">Differences</label>
151+
<div className="w-full p-4 bg-gray-800/50 font-mono resize-y border rounded-md border-app-alpha-50 text-app-light overflow-auto" style={{ minHeight: '100px' }}>
152+
{diffOutput.length > 0 ? renderDiff() : <p className="text-gray-500">No differences or empty input.</p>}
153+
</div>
154+
</div>
155+
</div>
156+
</div>
157+
</div>
158+
</div>
159+
</div>
160+
);
161+
}
162+
163+
export default TextDiffCheckerPage;

src/utils/projectParser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const useProjects = (fetchPinnedOnly = false) => {
2525
};
2626

2727
fetchProjects();
28-
}, []);
28+
}, [fetchPinnedOnly]);
2929

3030
return { projects, loading, error };
3131
};

0 commit comments

Comments
 (0)