Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
Son Of Anton
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Son Of Anton
Commits
00276ffc
Commit
00276ffc
authored
Apr 04, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 4 files via Son of Anton
parent
ffae678c
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
305 additions
and
10 deletions
+305
-10
package.json
frontend/package.json
+3
-10
ColorPalette.jsx
frontend/src/components/ColorPalette.jsx
+54
-0
LivePreview.jsx
frontend/src/components/LivePreview.jsx
+131
-0
MermaidDiagram.jsx
frontend/src/components/MermaidDiagram.jsx
+117
-0
No files found.
frontend/package.json
View file @
00276ffc
{
{
"name"
:
"son-of-anton-frontend"
,
"version"
:
"1.0.0"
,
"private"
:
true
,
"type"
:
"module"
,
"scripts"
:
{
"dev"
:
"vite"
,
"build"
:
"vite build"
,
"preview"
:
"vite preview"
},
"dependencies"
:
{
"dependencies"
:
{
"lucide-react"
:
"^0.469.0"
,
"lucide-react"
:
"^0.469.0"
,
"react"
:
"^18.3.1"
,
"react"
:
"^18.3.1"
,
...
@@ -15,7 +6,9 @@
...
@@ -15,7 +6,9 @@
"react-markdown"
:
"^9.0.1"
,
"react-markdown"
:
"^9.0.1"
,
"react-router-dom"
:
"^7.1.1"
,
"react-router-dom"
:
"^7.1.1"
,
"react-syntax-highlighter"
:
"^15.6.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"
:
{
"devDependencies"
:
{
"@types/react"
:
"^18.3.18"
,
"@types/react"
:
"^18.3.18"
,
...
...
frontend/src/components/ColorPalette.jsx
0 → 100644
View file @
00276ffc
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
frontend/src/components/LivePreview.jsx
0 → 100644
View file @
00276ffc
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
frontend/src/components/MermaidDiagram.jsx
0 → 100644
View file @
00276ffc
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment