Commit 00276ffc authored by Administrator's avatar Administrator

Update 4 files via Son of Anton

parent ffae678c
{
"name": "son-of-anton-frontend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.469.0",
"react": "^18.3.1",
......@@ -15,7 +6,9 @@
"react-markdown": "^9.0.1",
"react-router-dom": "^7.1.1",
"react-syntax-highlighter": "^15.6.1",
"remark-gfm": "^4.0.0"
"remark-gfm": "^4.0.0",
"mermaid": "^11.4.0",
"html-to-image": "^1.11.11"
},
"devDependencies": {
"@types/react": "^18.3.18",
......
import React, { useState } from "react";
import { Copy, Check, Palette } from "lucide-react";
export default function ColorPalette({ colors, title }) {
const [copiedColor, setCopiedColor] = useState(null);
function copyColor(color) {
navigator.clipboard.writeText(color);
setCopiedColor(color);
setTimeout(() => setCopiedColor(null), 1500);
}
function getContrastColor(hex) {
const c = hex.replace("#", "");
const r = parseInt(c.substr(0, 2), 16);
const g = parseInt(c.substr(2, 2), 16);
const b = parseInt(c.substr(4, 2), 16);
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? "#000000" : "#FFFFFF";
}
if (!colors?.length) return null;
return (
<div className="rounded-xl border border-anton-border overflow-hidden bg-anton-card">
<div className="flex items-center gap-2 px-3 py-2 bg-anton-surface border-b border-anton-border">
<Palette size={14} className="text-pink-400" />
<span className="text-xs font-medium text-white">{title || "Color Palette"}</span>
</div>
<div className="p-3 flex flex-wrap gap-2">
{colors.map((color, i) => {
const hex = typeof color === "string" ? color : color.hex;
const name = typeof color === "string" ? null : color.name;
const contrast = getContrastColor(hex);
const isCopied = copiedColor === hex;
return (
<button key={i} onClick={() => copyColor(hex)}
className="group relative rounded-lg overflow-hidden transition-transform hover:scale-105 active:scale-95"
style={{ width: "80px", height: "80px", backgroundColor: hex }}>
<div className="absolute inset-0 flex flex-col items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-black/30">
{isCopied ? <Check size={16} style={{ color: contrast }} /> : <Copy size={16} style={{ color: contrast }} />}
</div>
<div className="absolute bottom-0 left-0 right-0 px-1 py-0.5 text-center" style={{ color: contrast }}>
{name && <div className="text-[8px] font-medium truncate">{name}</div>}
<div className="text-[9px] font-mono opacity-80">{hex}</div>
</div>
</button>
);
})}
</div>
</div>
);
}
\ No newline at end of file
import React, { useState, useRef, useEffect } from "react";
import { Eye, EyeOff, Smartphone, Monitor, Tablet, Maximize2, Minimize2, Code2, Copy, Check, ExternalLink } from "lucide-react";
const VIEWPORT_SIZES = {
mobile: { width: 375, height: 667, label: "Mobile", icon: Smartphone },
tablet: { width: 768, height: 1024, label: "Tablet", icon: Tablet },
desktop: { width: "100%", height: 600, label: "Desktop", icon: Monitor },
};
export default function LivePreview({ html, css, js, title }) {
const [viewport, setViewport] = useState("desktop");
const [expanded, setExpanded] = useState(false);
const [showCode, setShowCode] = useState(false);
const [copied, setCopied] = useState(false);
const iframeRef = useRef(null);
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"><\/script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', system-ui, sans-serif; }
${css || ""}
</style>
</head>
<body>
${html || ""}
${js ? `<script>${js}<\/script>` : ""}
</body>
</html>`;
useEffect(() => {
if (iframeRef.current) {
const doc = iframeRef.current.contentDocument;
if (doc) {
doc.open();
doc.write(fullHtml);
doc.close();
}
}
}, [fullHtml, viewport]);
const vp = VIEWPORT_SIZES[viewport];
const iframeWidth = vp.width === "100%" ? "100%" : `${vp.width}px`;
function handleCopy() {
navigator.clipboard.writeText(fullHtml);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
function openInNewTab() {
const blob = new Blob([fullHtml], { type: "text/html" });
const url = URL.createObjectURL(blob);
window.open(url, "_blank");
setTimeout(() => URL.revokeObjectURL(url), 5000);
}
return (
<div className={`rounded-xl border border-anton-border overflow-hidden bg-anton-card ${expanded ? "fixed inset-4 z-50" : ""}`}>
{/* Toolbar */}
<div className="flex items-center justify-between px-3 py-2 bg-anton-surface border-b border-anton-border">
<div className="flex items-center gap-2">
<Eye size={14} className="text-green-400" />
<span className="text-xs font-medium text-white">{title || "Live Preview"}</span>
</div>
<div className="flex items-center gap-1">
{/* Viewport Switcher */}
{Object.entries(VIEWPORT_SIZES).map(([key, val]) => {
const Icon = val.icon;
return (
<button key={key} onClick={() => setViewport(key)}
className={`p-1.5 rounded transition ${viewport === key ? "bg-anton-accent/20 text-anton-accent" : "text-anton-muted hover:text-white"}`}
title={val.label}>
<Icon size={14} />
</button>
);
})}
<div className="w-px h-4 bg-anton-border mx-1" />
<button onClick={() => setShowCode(!showCode)}
className={`p-1.5 rounded transition ${showCode ? "bg-blue-500/20 text-blue-400" : "text-anton-muted hover:text-white"}`}
title="Toggle code">
<Code2 size={14} />
</button>
<button onClick={handleCopy} className="p-1.5 rounded text-anton-muted hover:text-white transition" title="Copy HTML">
{copied ? <Check size={14} className="text-green-400" /> : <Copy size={14} />}
</button>
<button onClick={openInNewTab} className="p-1.5 rounded text-anton-muted hover:text-white transition" title="Open in new tab">
<ExternalLink size={14} />
</button>
<button onClick={() => setExpanded(!expanded)}
className="p-1.5 rounded text-anton-muted hover:text-white transition"
title={expanded ? "Minimize" : "Fullscreen"}>
{expanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
</button>
</div>
</div>
{/* Preview Area */}
<div className="flex justify-center bg-[#1a1a2e] p-4 overflow-auto" style={{ minHeight: expanded ? "calc(100vh - 120px)" : "400px" }}>
<div style={{ width: iframeWidth, maxWidth: "100%" }} className="bg-white rounded-lg shadow-2xl overflow-hidden transition-all duration-300">
<iframe
ref={iframeRef}
title="preview"
sandbox="allow-scripts allow-same-origin"
className="w-full border-0"
style={{ height: expanded ? "calc(100vh - 160px)" : `${typeof vp.height === "number" ? vp.height : 500}px` }}
/>
</div>
</div>
{/* Code Panel */}
{showCode && (
<div className="border-t border-anton-border bg-[#0d1117] p-3 max-h-60 overflow-auto">
<pre className="text-xs text-gray-300 font-mono whitespace-pre-wrap">{fullHtml}</pre>
</div>
)}
</div>
);
}
\ No newline at end of file
import React, { useEffect, useRef, useState } from "react";
import { GitBranch, Maximize2, Minimize2, Copy, Check } from "lucide-react";
let mermaidInitialized = false;
export default function MermaidDiagram({ code, title }) {
const containerRef = useRef(null);
const [svg, setSvg] = useState("");
const [error, setError] = useState(null);
const [expanded, setExpanded] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => {
let cancelled = false;
async function render() {
try {
const mermaid = (await import("mermaid")).default;
if (!mermaidInitialized) {
mermaid.initialize({
startOnLoad: false,
theme: "dark",
themeVariables: {
primaryColor: "#e53e3e",
primaryTextColor: "#fff",
primaryBorderColor: "#e53e3e",
lineColor: "#6b6b8a",
secondaryColor: "#1a1a2e",
tertiaryColor: "#16162a",
background: "#0f0f1a",
mainBkg: "#1a1a2e",
nodeBorder: "#e53e3e",
clusterBkg: "#16162a",
titleColor: "#fff",
edgeLabelBackground: "#1a1a2e",
},
fontFamily: "Inter, system-ui, sans-serif",
fontSize: 14,
});
mermaidInitialized = true;
}
const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const { svg: renderedSvg } = await mermaid.render(id, code.trim());
if (!cancelled) {
setSvg(renderedSvg);
setError(null);
}
} catch (e) {
if (!cancelled) {
setError(e.message || "Failed to render diagram");
setSvg("");
}
}
}
if (code?.trim()) render();
return () => { cancelled = true; };
}, [code]);
function handleCopy() {
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
if (error) {
return (
<div className="rounded-xl border border-red-500/30 bg-red-500/5 p-4">
<div className="flex items-center gap-2 text-red-400 text-xs mb-2">
<GitBranch size={14} />
<span>Diagram Error</span>
</div>
<pre className="text-xs text-red-300/70 font-mono">{error}</pre>
<pre className="text-xs text-anton-muted font-mono mt-2 bg-anton-bg p-2 rounded">{code}</pre>
</div>
);
}
if (!svg) {
return (
<div className="rounded-xl border border-anton-border bg-anton-card p-8 flex items-center justify-center">
<div className="flex items-center gap-2 text-anton-muted text-sm">
<div className="w-4 h-4 border-2 border-anton-accent border-t-transparent rounded-full animate-spin" />
Rendering diagram...
</div>
</div>
);
}
return (
<div className={`rounded-xl border border-anton-border overflow-hidden bg-anton-card ${expanded ? "fixed inset-4 z-50" : ""}`}>
<div className="flex items-center justify-between px-3 py-2 bg-anton-surface border-b border-anton-border">
<div className="flex items-center gap-2">
<GitBranch size={14} className="text-purple-400" />
<span className="text-xs font-medium text-white">{title || "Diagram"}</span>
</div>
<div className="flex items-center gap-1">
<button onClick={handleCopy} className="p-1.5 rounded text-anton-muted hover:text-white transition">
{copied ? <Check size={14} className="text-green-400" /> : <Copy size={14} />}
</button>
<button onClick={() => setExpanded(!expanded)} className="p-1.5 rounded text-anton-muted hover:text-white transition">
{expanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
</button>
</div>
</div>
<div
ref={containerRef}
className="p-6 flex justify-center overflow-auto bg-[#0f0f1a]"
style={{ minHeight: expanded ? "calc(100vh - 100px)" : "200px" }}
dangerouslySetInnerHTML={{ __html: svg }}
/>
</div>
);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment