Initial import

This commit is contained in:
Flatlogic Bot 2026-02-24 14:59:28 +00:00
commit c126b6f264
21 changed files with 5486 additions and 0 deletions

139
base44-site-template-main/.gitignore vendored Normal file
View File

@ -0,0 +1,139 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.*
!.env.example
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Sveltekit cache directory
.svelte-kit/
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Firebase cache directory
.firebase/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Yair Morgenstern
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,43 @@
# Base44 Site Template
## What?
[base44](https://base44.com/) creates really beautiful sites, which is a lifesaver for someone like me who cant design. But the sites are all online and not under your control. This is how you can make them run locally!
## How?
Make this site run:
- `npm install`
- `npm start` to check
Make your site run:
- Copy the jsx files from base44 (you can see them in base44 under Workspace > Code) into the `src/` directory
- Fix imports between the files, if you didn't keep the original file structure
- Add any missing npm packages (in your jsx files, but not in template)
- Install any missing shadcn components — e.g. `npx shadcn@latest add button input dialog`
- Remove base44 “entity access” code (or comment out, to replace with in-memory key-value storage later)
That's it!
## Usage instructions
- `npm start` — This will spawn a development server with a default port of `5173`.
- `npm run build` — This will output a production build in the `dist` directory.
- `npm run preview` — This will run the production build locally with a default port of `5173` (this will not work if you haven't generated the production build yet).
## Custom port
You can use the `-p` flag to specify a port for development. To do this, you can either run `npm start` with an additional flag:
```
npm start -- --port 3000
```
Or edit the `start` script directly:
```
vite --port 3000
```

View File

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@ -0,0 +1,120 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="./index.css" />
<title>React Vite Micro App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/index.jsx"></script>
</body>
</html>

4384
base44-site-template-main/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
{
"name": "static",
"version": "0.1.0",
"description": "",
"license": "MIT",
"scripts": {
"start": "vite",
"preview": "vite preview",
"build": "vite build"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/vite": "^4.1.7",
"@vitejs/plugin-react": "^4.5.0",
"axios": "^1.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.511.0",
"mermaid": "^11.6.0",
"monaco-editor": "^0.52.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"svg-pan-zoom": "^3.6.2",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4.1.7"
},
"devDependencies": {
"@types/node": "^22.15.21",
"tw-animate-css": "^1.3.0",
"vite": "^6.2.0"
}
}

View File

@ -0,0 +1,8 @@
import React from "react";
import CodeVisualizerPage from "./CodeVisualizer";
export default () => (
<>
<CodeVisualizerPage/>
</>
);

View File

@ -0,0 +1,127 @@
import React, { useEffect, useRef, useState } from 'react';
import * as monaco from 'monaco-editor';
export default function CodeEditor({ code, onChangeContent, onChangeCursorPosition: onChangeCursorOffset }) {
const containerRef = useRef(null);
const [editor, setEditor] = useState(null);
useEffect(() => {
if (!containerRef.current) return;
// Define Kotlin language
if (!monaco.languages.getLanguages().some(({ id }) => id === 'kotlin')) {
monaco.languages.register({ id: 'kotlin' });
monaco.languages.setMonarchTokensProvider('kotlin', {
tokenizer: {
root: [
[/\b(package|import|class|interface|object|fun|val|var|when|if|else|for|while|return|constructor|companion|data|sealed|enum|override|open|private|protected|public|internal|final|abstract|suspend|lateinit|inline|get|set|this|super)\b/, 'keyword'],
[/\b(Int|String|Boolean|Float|Double|Long|Short|Byte|Any|Unit|Nothing)\b/, 'type'],
[/\/\/.*$/, 'comment'],
[/\/\*/, 'comment', '@comment'],
[/"/, 'string', '@string_double'],
[/'[^']*'/, 'string'],
[/\d+/, 'number'],
[/@\w+/, 'annotation'],
],
comment: [
[/[^/*]+/, 'comment'],
[/\/\*/, 'comment', '@push'],
[/\*\//, 'comment', '@pop'],
[/[/*]/, 'comment']
],
string_double: [
[/[^\\"]+/, 'string'],
[/\\./, 'string.escape'],
[/"/, 'string', '@pop']
]
}
});
}
// Define a dark dracula-inspired theme
monaco.editor.defineTheme('kotlinDarkDracula', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6272a4' },
{ token: 'keyword', foreground: 'ff79c6', fontStyle: 'bold' },
{ token: 'string', foreground: 'f1fa8c' },
{ token: 'number', foreground: 'bd93f9' },
{ token: 'type', foreground: '8be9fd' },
{ token: 'annotation', foreground: '50fa7b' },
],
colors: {
'editor.background': '#1a1c25',
'editor.foreground': '#f8f8f2',
'editor.lineHighlightBackground': '#2d303e',
'editor.selectionBackground': '#44475a',
'editorCursor.foreground': '#f8f8f2',
'editorWhitespace.foreground': '#3B3A32',
'editorIndentGuide.activeBackground': '#9D550FB0',
'editor.selectionHighlightBorder': '#222218'
}
});
// Initialize Monaco editor
const editorInstance = monaco.editor.create(containerRef.current, {
value: code,
language: 'kotlin',
theme: 'kotlinDarkDracula',
automaticLayout: true,
minimap: { enabled: true },
scrollBeyondLastLine: false,
fontSize: 14,
fontFamily: "'JetBrains Mono', 'Fira Code', Menlo, Monaco, 'Courier New', monospace",
lineNumbers: 'on',
renderLineHighlight: 'all',
scrollbar: {
useShadows: true,
verticalScrollbarSize: 10,
horizontalScrollbarSize: 10
},
padding: { top: 16 },
bracketPairColorization: { enabled: true },
autoClosingBrackets: 'always'
});
// Add event listener for content changes
const disposable = editorInstance.onDidChangeModelContent(() => {
onChangeContent(editorInstance.getValue());
});
editorInstance.onDidChangeCursorPosition((e) => {
// The editor speaks in position (line/column), the IR element speaks in offset (absolute int)
// The callback is in "IrSpeak" so we need to convert it
const offset = editorInstance.getModel().getOffsetAt(e.position)
onChangeCursorOffset(offset)
})
setEditor(editorInstance);
// Clean up
return () => {
disposable.dispose();
editorInstance.dispose();
};
}, [containerRef]);
// Update editor value when code prop changes
useEffect(() => {
if (editor && code !== editor.getValue()) {
editor.setValue(code);
}
}, [code, editor]);
return (
<div
ref={containerRef}
className="h-full w-full overflow-hidden flex-grow"
style={{
backgroundColor: '#1a1c25',
border: 'none',
outline: 'none'
}}
/>
);
}

View File

@ -0,0 +1,158 @@
import React, { useState, useEffect, useRef } from "react";
import CodeEditor from "./CodeEditor";
import GraphViewer from "./GraphViewer";
import { Loader2 } from "lucide-react";
import KotlinLogo from './KotlinLogo.svg';
import axios from "axios";
import {editor} from "monaco-editor";
export default function CodeVisualizerPage() {
// Initial example Kotlin code
const [code, setCode] = useState(`// Example Kotlin code
fun main() {
var x = 5
if (x > 1 + 2) {
x += 1
listOf(1, 2)
}
}
`);
const [mermaidGraphText, setMermaidGraphText] = useState("");
const [cursorPositionOffset, setCursorPositionOffset] = useState(0)
const [mermaidGraphTextColored, setMermaidGraphTextColored] = useState("");
const [compilerMessages, setCompilerMessages] = useState([]);
const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState(null);
const timerRef = useRef(null);
const processCode = async (codeToProcess) => {
setIsProcessing(true);
setError(null);
try {
// In a real implementation, you'd send `codeToProcess` to your backend
// which would return the Mermaid graph *text representation*.
// TODO: How can I auto-recognize when this is run via npm run start, so I can know to use the localhost? :think:
const response = await axios.post("/api/kotlinToMermaid?withOffsetComment=true", codeToProcess)
// const response = await axios.post("http://localhost:8080/api/kotlinToMermaid?withOffsetComment=true", codeToProcess)
const graphText = response.data.mermaidGraph
const messages = response.data.messages
setMermaidGraphText(graphText)
setCompilerMessages(messages)
} catch (err) {
console.error("Error processing code:", err)
setError("Failed to generate graph. Please ensure your backend is running or check the placeholder logic.")
} finally {
setIsProcessing(false)
}
};
// Debounce of 250ms, to render the graph after the user stops typing
useEffect(() => {
if (code.trim() === '') {
setMermaidGraphText('classDiagram\n Empty["Type some Kotlin code to see the diagram"]\n classDef default fill:#2a2a3f,stroke:#6272a4,stroke-width:1px,color:#f8f8f2');
return
}
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
processCode(code);
}, 250);
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [code]);
const handleCodeChange = (newCode) => {
setCode(newCode);
};
const handleCursorOffsetChange = (offset) => {
setCursorPositionOffset(offset)
};
// The actual displayed graph is the graph we got plus coloring on all elements which contain the cursor position
useEffect(() => {
if (!mermaidGraphText){ // null or empty
setMermaidGraphTextColored(null)
return
}
const newMermaidGraphTextLines = mermaidGraphText.split("\n")
.map(line => {
// Offset comment looks like: " # Offset: ${element.startOffset}-${element.endOffset}"
const offsetSplit = line.split(" %% Offset: ")
if (offsetSplit.length < 2) return line // No comment
const positions = offsetSplit[1].split("-").map(num => parseInt(num))
if (cursorPositionOffset > positions[1] || cursorPositionOffset < positions[0]) return offsetSplit[0]
return offsetSplit[0] + ":::selected" // Coloring classdef
})
const newMermaidGraphText = newMermaidGraphTextLines.join("\n") + "\n\n classDef selected stroke:#f00"
setMermaidGraphTextColored(newMermaidGraphText)
},
[cursorPositionOffset, mermaidGraphText]
)
const onWarningLocationClick = (location) => {
if (!location) return;
// Use Monaco editor's API to reveal the line
const lineNumber = location.line
const columnNumber = location.column
const editorInstance = editor.getEditors()[0]; // Assuming there's only one editor instance
if (editorInstance) {
editorInstance.revealLineInCenter(lineNumber);
editorInstance.setPosition({ lineNumber, column: columnNumber });
editorInstance.focus();
}
}
return (
<div className="flex flex-col h-screen bg-[#0f1117] text-slate-100">
<header className="sticky top-0 z-10 bg-[#1a1c25] border-b border-slate-800 px-4 py-3 flex items-center justify-between shadow-md">
<div className="flex items-center">
<div className="h-6 w-6 mr-2">
<img src={KotlinLogo}/>
</div>
<h1 className="text-xl font-semibold text-slate-100 mr-4">Kotlin IR Explorer</h1>
{isProcessing && (
<div className="flex items-center text-sky-400">
<Loader2 className="h-4 w-4 animate-spin mr-2" />
<span className="text-sm">Processing...</span>
</div>
)}
</div>
</header>
<div className="flex flex-1 overflow-hidden">
<div className="w-1/2 border-r border-slate-800 bg-[#1a1c25]">
<CodeEditor code={code} onChangeContent={handleCodeChange} onChangeCursorPosition={handleCursorOffsetChange} />
</div>
<div className="w-1/2 bg-[#1a1c25] p-4 overflow-auto">
<GraphViewer
mermaidGraphText={mermaidGraphTextColored}
compilerMessages={compilerMessages}
isProcessing={isProcessing}
error={error}
onWarningLocationClick={onWarningLocationClick}
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,140 @@
import React, {useEffect, useLayoutEffect, useRef, useState} from 'react';
import { Loader2, AlertTriangle, ImageOff } from 'lucide-react';
import mermaid from 'mermaid';
import svgPanZoom from 'svg-pan-zoom';
mermaid.initialize(
{
startOnLoad: false, // We will render manually
theme: 'dark', // Apply dark theme globally
// Dark theme configuration similar to Dracula
themeVariables: {
background: '#0f1117',
primaryColor: '#1a1c25', // Node background
primaryTextColor: '#f8f8f2',
primaryBorderColor: '#6272a4',
// lineColor: '#44475a',
secondaryColor: '#2d303e',
tertiaryColor: '#2d303e',
fontSize: '14px',
fontFamily: 'JetBrains Mono, Fira Code, Menlo, Monaco, Courier New, monospace',
// Specific for class diagrams
classText: '#f8f8f2',
},
securityLevel: 'loose', // Allow clicks etc
}
)
export default function GraphViewer({ mermaidGraphText, compilerMessages, isProcessing,
error, onWarningLocationClick }) {
const svgContainerRef = useRef(null);
const [renderError, setRenderError] = useState(null);
const [svg, setSvg] = useState(null);
const graphId = `mermaid-graph-${Date.now()}`;
// Render graph when text or library readiness changes
useEffect(() => {
if (!mermaidGraphText || isProcessing) return
setRenderError(null);
try {
// mermaid.render() returns a promise with the SVG
mermaid.render(graphId, mermaidGraphText)
.then(({ svg, bindFunctions }) => {
if (svgContainerRef.current) {
svgContainerRef.current.innerHTML = svg;
if (bindFunctions) {
bindFunctions(svgContainerRef.current); // For interactivity if any
}
}
setSvg(svg);
})
.catch(err => {
console.error("Mermaid rendering error:", err);
setRenderError(`Diagram error: ${err.message || 'Could not render graph.'}`);
if (svgContainerRef.current) {
svgContainerRef.current.innerHTML = `<div class="text-red-400 p-4">Error rendering diagram. Check console.</div>`;
}
});
} catch (err) {
console.error("Synchronous Mermaid error:", err);
setRenderError(`Diagram error: ${err.message || 'Critical error.'}`);
if (svgContainerRef.current) {
svgContainerRef.current.innerHTML = `<div class="text-red-400 p-4">Critical diagram rendering error.</div>`;
}
}
}, [mermaidGraphText, isProcessing]);
useLayoutEffect(() => {
if (!svgContainerRef.current || !svg) return;
const svgElement = svgContainerRef.current.children[0];
// make svg size to its current size - required so it's not a tiny thing
svgElement.style.width = '100%';
svgElement.style.height = '100%';
svgElement.style.visibility = 'hidden'; // Hide until pan-zoom is applied
svgPanZoom(svgElement, {
controlIconsEnabled: true
})
svgElement.style.visibility = 'visible'; // Show the SVG after pan-zoom is applied
})
let content;
if (isProcessing) {
content = (
<div className="h-full flex flex-col items-center justify-center text-slate-400">
<Loader2 className="h-12 w-12 animate-spin mb-4 text-sky-400" />
<p>{isProcessing ? "Processing Code..." : "Rendering Diagram..."}</p>
</div>
);
} else if (error || renderError) {
content = (
<div className="h-full flex flex-col items-center justify-center text-red-400">
<AlertTriangle className="h-12 w-12 mb-4" />
<p className="font-semibold">Error</p>
<p className="text-sm text-red-500 mt-1">{error || renderError}</p>
</div>
);
} else if (!mermaidGraphText || mermaidGraphText.trim() === '') {
// This condition checks for specific placeholder texts in the mermaid string
content = (
<div className="h-full w-full flex flex-col items-center justify-center text-slate-400">
<ImageOff className="h-16 w-16 mb-4 text-slate-500" />
{compilerMessages.map(message =>
<div className="mt-4 text-sm bg-[#2d303e] p-3 rounded-md text-left max-w w-full"
style={{cursor: 'pointer'}} // as
onClick={() => onWarningLocationClick(message.location)}>
{/*<p className="font-medium text-slate-300">Try a simple class:</p>*/}
<pre className="mt-2 overflow-x-auto text-xs bg-[#0f1117] p-2 rounded-md text-sky-300">
{
message.severity
+ (message.location ? ` (${message.location.line}:${message.location.column})` : '')
+": "+message.message
}
</pre>
</div>
)}
</div>
);
} else {
// The div will be populated by the useEffect hook
content = (
<div ref={svgContainerRef}
dangerouslySetInnerHTML={{__html: svg}}
className="w-full h-full flex justify-center items-center overflow-auto p-2 mermaid-live-container">
</div>
);
}
return (
<div
className="h-full w-full bg-[#0f1117] rounded-lg border border-slate-800 flex justify-center items-center overflow-hidden">
{content}
</div>
);
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<g id="Logotypes">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="500.0035" y1="579.1058" x2="-9.653803e-02" y2="1079.2058" gradientTransform="matrix(0.9998 0 0 0.9998 9.651873e-02 -578.99)">
<stop offset="3.435144e-03" style="stop-color:#E44857"/>
<stop offset="0.4689" style="stop-color:#C711E1"/>
<stop offset="1" style="stop-color:#7F52FF"/>
</linearGradient>
<polygon class="st0" points="500,500 0,500 0,0 500,0 250,250 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 904 B

View File

@ -0,0 +1,59 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View File

@ -0,0 +1,133 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}

View File

@ -0,0 +1,21 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
/>
)
}
export { Input }

View File

@ -0,0 +1,5 @@
import React from "react";
import { createRoot } from 'react-dom/client';
import App from "./App";
createRoot(document.getElementById('root')).render(<App/>)

View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
}
}

View File

@ -0,0 +1,14 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -0,0 +1,14 @@
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})