Skip to content

Commit 39cd5c0

Browse files
committed
NEW(APP): JSON TO/FROM PIML.
THIS IS ACTUALLY CRAZY. USING PIML.JS NPM PACKAGE. ALSO TOASTS NOW HAVE TYPE.
1 parent 86d985a commit 39cd5c0

File tree

9 files changed

+206
-10
lines changed

9 files changed

+206
-10
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
@@ -13,6 +13,7 @@
1313
"framer-motion": "^12.23.24",
1414
"front-matter": "^4.0.2",
1515
"marked": "^16.4.1",
16+
"piml": "^1.0.4",
1617
"qrcode.react": "^4.2.0",
1718
"react": "^19.2.0",
1819
"react-dom": "^19.2.0",

public/apps/apps.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
[
2+
{
3+
"slug": "json-piml-converter",
4+
"to": "/apps/json-piml-converter",
5+
"title": "JSON - PIML Converter",
6+
"description": "Convert JSON to PIML and vice-versa.",
7+
"icon": "Code"
8+
},
29
{
310
"slug": "image-toolkit",
411
"to": "/apps/image-toolkit",
@@ -132,4 +139,4 @@
132139
"description": "Convert between px, em, rem, vw, vh, and % units.",
133140
"icon": "Ruler"
134141
}
135-
]
142+
]

src/components/AnimatedRoutes.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import LogDetailPage from '../pages/LogDetailPage';
1212
import NotFoundPage from '../pages/NotFoundPage';
1313
import SeriesPage from '../pages/SeriesPage';
1414
import DndPage from '../pages/DndPage';
15-
import DndNotFoundPage from '../pages/DndNotFoundPage'; // New import
16-
import DndEpisodePage from '../pages/DndEpisodePage'; // New import
17-
import DndLorePage from '../pages/DndLorePage'; // New import
18-
import DndBookPage from '../pages/DndBookPage'; // New import
15+
import DndNotFoundPage from '../pages/DndNotFoundPage';
16+
import DndEpisodePage from '../pages/DndEpisodePage';
17+
import DndLorePage from '../pages/DndLorePage';
18+
import DndBookPage from '../pages/DndBookPage';
1919
import AppPage from '../pages/AppPage';
2020
import IpPage from '../pages/apps/IpPage';
2121
import WordCounterPage from '../pages/apps/WordCounterPage';
@@ -37,6 +37,7 @@ import PasswordGeneratorPage from '../pages/apps/PasswordGeneratorPage';
3737
import JsonFormatterPage from '../pages/apps/JsonFormatterPage';
3838
import ColorContrastCheckerPage from '../pages/apps/ColorContrastCheckerPage';
3939
import QrCodeGeneratorPage from '../pages/apps/QrCodeGeneratorPage';
40+
import JsonPimlConverterPage from '../pages/apps/JsonPimlConverterPage';
4041
import SettingsPage from '../pages/SettingsPage'; // Import SettingsPage
4142

4243
import UsefulLinksPage from '../pages/UsefulLinksPage';
@@ -341,6 +342,7 @@ function AnimatedRoutes() {
341342
<Route path="/apps::jf" element={<Navigate to="/apps/json-formatter" replace />} />
342343
<Route path="/apps::ccc" element={<Navigate to="/apps/color-contrast-checker" replace />} />
343344
<Route path="/apps::qr" element={<Navigate to="/apps/qr-code-generator" replace />} />
345+
<Route path="/apps::jpc" element={<Navigate to="/apps/json-piml-converter" replace />} />
344346
{/* End of hardcoded redirects */}
345347
<Route
346348
path="/apps/ip"
@@ -623,6 +625,20 @@ function AnimatedRoutes() {
623625
</motion.div>
624626
}
625627
/>
628+
<Route
629+
path="/apps/json-piml-converter"
630+
element={
631+
<motion.div
632+
initial="initial"
633+
animate="in"
634+
exit="out"
635+
variants={pageVariants}
636+
transition={pageTransition}
637+
>
638+
<JsonPimlConverterPage />
639+
</motion.div>
640+
}
641+
/>
626642
{/* D&D specific 404 page */}
627643
<Route
628644
path="/dnd/*"

src/components/Search.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const Search = ({ isVisible }) => {
135135
case 'route': // Handle routes
136136
return result.slug;
137137
case 'app': // Handle apps
138-
return result.slug;
138+
return `${result.to}`;
139139
default:
140140
return '/';
141141
}

src/components/Toast.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
22
import { motion } from 'framer-motion';
33
import { XIcon } from '@phosphor-icons/react';
44

5-
const Toast = ({ id, title, message, duration = 3000, removeToast }) => {
5+
const Toast = ({ id, title, message, duration = 3000, type, removeToast }) => {
66
useEffect(() => {
77
const timer = setTimeout(() => {
88
removeToast(id);
@@ -13,14 +13,16 @@ const Toast = ({ id, title, message, duration = 3000, removeToast }) => {
1313
};
1414
}, [id, duration, removeToast]);
1515

16+
const defaultStyle = "text-gray-100 font-arvo py-4 px-10 rounded-lg shadow-lg border backdrop-blur-sm flex items-center justify-between w-96 mb-4 transition-colors"
17+
const successStyle = " bg-toast-background border-toast-border hover:bg-toast-background/90"
18+
const errorStyle = " bg-toast-error-background border-toast-error-border hover:bg-toast-error-background/90"
1619
return (
1720
<motion.div
1821
initial={{ x: '100%', opacity: 0 }}
1922
animate={{ x: 0, opacity: 1 }}
2023
exit={{ opacity: 0 }}
2124
transition={{ type: 'spring', stiffness: 120, damping: 20 }}
22-
className="text-gray-100 font-arvo py-4 px-10 rounded-lg shadow-lg border backdrop-blur-sm flex items-center justify-between w-96 mb-4
23-
transition-colors bg-toast-background border-toast-border hover:bg-toast-background/90"
25+
className={ `${defaultStyle} ${type === "error" ? `${errorStyle}` : `${successStyle}` }` }
2426
>
2527
<div className="flex flex-col text-sm group w-max flex-grow">
2628
<span className="text-base text-red-100">{title}</span>

src/config/colors.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,16 @@ module.exports = {
6464
'tools-alpha-10': 'rgba(224, 170, 255, 0.1)',
6565
'tools-alpha-50': 'rgba(224, 170, 255, 0.5)',
6666

67-
// Toast colors
67+
// Toast success colors
6868
'toast-background': 'rgba(72,59,87,0.8)',
6969
'toast-hover-background': 'rgba(102,84,122,0.8)',
7070
'toast-border': '#514b68',
7171

72+
// Toast error colors
73+
'toast-error-background': 'rgba(87,59,59,0.8)',
74+
'toast-error-hover-background': 'rgba(122,84,84,0.8)',
75+
'toast-error-border': '#684b4b',
76+
7277
// Code Theme Colors (from src/utils/customTheme.js)
7378
codeTheme: {
7479
'text-default': '#d1d5db',

src/context/ToastContext.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const ToastProvider = ({ children }) => {
3434
title={toast.title}
3535
message={toast.message}
3636
duration={toast.duration}
37+
type={toast.type}
3738
removeToast={removeToast}
3839
/>
3940
))}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import React, { useState } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import {ArrowLeftIcon, ClipboardIcon} from '@phosphor-icons/react';
4+
import colors from '../../config/colors';
5+
import usePageTitle from '../../utils/usePageTitle';
6+
import { useToast } from '../../hooks/useToast';
7+
import piml from 'piml'; // Import the piml library
8+
9+
function JsonPimlConverterPage() {
10+
usePageTitle('JSON - PIML Converter');
11+
const { addToast } = useToast();
12+
13+
const [jsonInput, setJsonInput] = useState('');
14+
const [pimlInput, setPimlInput] = useState('');
15+
const [output, setOutput] = useState('');
16+
const [errorMsg, setErrorMsg] = useState('');
17+
const [selectedFormat, setSelectedFormat] = useState('');
18+
19+
const handleJsonToPiml = () => {
20+
setSelectedFormat("PIML");
21+
try {
22+
const json = JSON.parse(jsonInput);
23+
const pimlOutput = piml.stringify(json); // Use piml.stringify
24+
setOutput(pimlOutput);
25+
setErrorMsg('');
26+
addToast({ title: 'Success', message: 'JSON converted to PIML!', duration: 10000, type: 'success' });
27+
} catch (error) {
28+
addToast({ title: `Error ${selectedFormat}`, message: `Invalid JSON input: ${error.message}` , duration: 10000, type: 'error' });
29+
console.log(error.message)
30+
setOutput('');
31+
setErrorMsg(error.toString());
32+
}
33+
};
34+
35+
const handlePimlToJson = () => {
36+
setSelectedFormat("JSON");
37+
try {
38+
const jsonOutput = piml.parse(pimlInput); // Use piml.parse
39+
setOutput(JSON.stringify(jsonOutput, null, 2));
40+
setErrorMsg('');
41+
addToast({ title: 'Success', message: 'PIML converted to JSON!', duration: 10000 });
42+
} catch (error) {
43+
addToast({ title: `Error ${selectedFormat}`, message: `Invalid PIML input: ${error.message}`, duration: 10000, type: 'error' });
44+
console.log(error.message)
45+
setOutput('');
46+
setErrorMsg(error.toString());
47+
}
48+
};
49+
50+
// Removed custom convertJsonToPiml, convertPimlToJson, and parsePimlValue functions
51+
52+
const copyToClipboard = () => {
53+
if (output) {
54+
navigator.clipboard.writeText(output);
55+
addToast({ title: 'Copied!', message: `${selectedFormat} object is copied to clipboard.`, duration: 2000 });
56+
} else {
57+
addToast({ title: 'Cannot Copy', message: `${selectedFormat} object is cannot be clipboard.`, duration: 2000, type: 'error' });
58+
}
59+
};
60+
61+
const cardStyle = {
62+
backgroundColor: colors['app-alpha-10'],
63+
borderColor: colors['app-alpha-50'],
64+
color: colors.app,
65+
};
66+
67+
const buttonStyle = "px-6 py-2 flex items-center gap-2 text-lg font-arvo font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out bg-tb text-app border-app-alpha-50 hover:bg-app/15";
68+
69+
return (
70+
<div className="py-16 sm:py-24">
71+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
72+
<Link
73+
to="/apps"
74+
className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4"
75+
>
76+
<ArrowLeftIcon size={24} /> Back to Apps
77+
</Link>
78+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
79+
<span className="codex-color">fc</span>
80+
<span className="separator-color">::</span>
81+
<span className="apps-color">apps</span>
82+
<span className="separator-color">::</span>
83+
<span className="single-app-color">jpc</span>
84+
</h1>
85+
<hr className="border-gray-700" />
86+
<div className="flex justify-center items-center mt-16">
87+
<div
88+
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"
89+
style={cardStyle}
90+
>
91+
<div
92+
className="absolute top-0 left-0 w-full h-full opacity-10"
93+
style={{
94+
backgroundImage:
95+
'radial-gradient(circle, white 1px, transparent 1px)',
96+
backgroundSize: '10px 10px',
97+
}}
98+
></div>
99+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app">JSON - PIML Converter</h1>
100+
<hr className="border-gray-700 mb-4" />
101+
<div className="relative z-10 p-1">
102+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
103+
<div>
104+
<h2 className="text-xl font-arvo font-normal mb-2 text-app">JSON Input</h2>
105+
<textarea
106+
className="w-full h-64 p-2 rounded-md bg-gray-800 text-white border border-gray-700"
107+
value={jsonInput}
108+
onChange={(e) => setJsonInput(e.target.value)}
109+
placeholder="Enter JSON here..."
110+
></textarea>
111+
<button onClick={handleJsonToPiml} className={`${buttonStyle} mt-6`}>Convert JSON to PIML</button>
112+
</div>
113+
<div>
114+
<h2 className="text-xl font-arvo font-normal mb-2 text-app">PIML Input</h2>
115+
<textarea
116+
className="w-full h-64 p-2 rounded-md bg-gray-800 text-white border border-gray-700"
117+
value={pimlInput}
118+
onChange={(e) => setPimlInput(e.target.value)}
119+
placeholder="Enter PIML here..."
120+
></textarea>
121+
<button onClick={handlePimlToJson} className={`${buttonStyle} mt-6`}>Convert PIML to JSON</button>
122+
</div>
123+
</div>
124+
<hr className="border-gray-700 mt-8 mb-8" />
125+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
126+
<div>
127+
<h2 className="text-xl font-arvo font-normal mb-2 text-app">Output</h2>
128+
<textarea
129+
className="w-full h-64 p-2 rounded-md bg-gray-800 text-white border border-gray-700"
130+
value={output}
131+
readOnly
132+
placeholder="Conversion output..."
133+
></textarea>
134+
<button onClick={copyToClipboard} className={`${buttonStyle} mt-6`}>
135+
<ClipboardIcon size={24}> </ClipboardIcon>
136+
Copy to Clipboard
137+
</button>
138+
</div>
139+
<div>
140+
<h2 className="text-xl font-arvo font-normal mb-2 text-app">Error Message</h2>
141+
<textarea
142+
className="w-full h-64 p-2 rounded-md bg-gray-800 text-white border border-gray-700"
143+
value={errorMsg}
144+
readOnly
145+
placeholder="No error messages..."
146+
></textarea>
147+
</div>
148+
</div>
149+
</div>
150+
</div>
151+
</div>
152+
</div>
153+
</div>
154+
);
155+
}
156+
157+
export default JsonPimlConverterPage;

0 commit comments

Comments
 (0)