870 lines
28 KiB
Go
870 lines
28 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Mutex to serialize calculation evaluations and capture output safely
|
|
var evalMu sync.Mutex
|
|
|
|
type CalculateResponse struct {
|
|
Output string `json:"output"`
|
|
Exit bool `json:"exit"`
|
|
}
|
|
|
|
type VariableResponse struct {
|
|
Name string `json:"name"`
|
|
Value string `json:"value"`
|
|
Dim string `json:"dim"`
|
|
}
|
|
|
|
type FunctionResponse struct {
|
|
Name string `json:"name"`
|
|
Args []string `json:"args"`
|
|
Expr string `json:"expr"`
|
|
}
|
|
|
|
// evaluateExpressionSafe redirects stdout for the duration of the evaluation to capture output safely
|
|
func evaluateExpressionSafe(line string, variablesFile string, functionsFile string) (string, bool) {
|
|
evalMu.Lock()
|
|
defer evalMu.Unlock()
|
|
|
|
// Keep backup of original stdout
|
|
oldStdout := os.Stdout
|
|
defer func() { os.Stdout = oldStdout }()
|
|
|
|
// Create pipe to intercept stdout
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
return "Error capturing stdout", false
|
|
}
|
|
os.Stdout = w
|
|
|
|
// Run evaluation (prints to stdout)
|
|
exit := handleLine(line, false, variablesFile, functionsFile)
|
|
|
|
// Close write end of pipe and read captured output
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
_, _ = io.Copy(&buf, r)
|
|
_ = r.Close()
|
|
|
|
return buf.String(), exit
|
|
}
|
|
|
|
// startWebServer boots the HTTP server serving the Web GUI and APIs
|
|
func startWebServer(port string) {
|
|
home, _ := os.UserHomeDir()
|
|
variablesFile := filepath.Join(home, ".goca_variables.json")
|
|
functionsFile := filepath.Join(home, ".goca_functions.json")
|
|
|
|
// Load persisted variables and custom functions
|
|
_ = loadUserFuncs(functionsFile)
|
|
_ = loadVariables(variablesFile)
|
|
|
|
// Fetch current currency rates in the background to ensure units work
|
|
_ = fetchRates()
|
|
|
|
// Serve the interactive web dashboard
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" && r.URL.Path != "/index.html" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
html := strings.ReplaceAll(indexHTML, "{{VERSION}}", Version)
|
|
_, _ = w.Write([]byte(html))
|
|
})
|
|
|
|
// Evaluation endpoint
|
|
http.HandleFunc("/api/calculate", func(w http.ResponseWriter, r *http.Request) {
|
|
query := r.URL.Query().Get("q")
|
|
if query == "" {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(CalculateResponse{Output: "Empty expression", Exit: false})
|
|
return
|
|
}
|
|
|
|
output, exit := evaluateExpressionSafe(query, variablesFile, functionsFile)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(CalculateResponse{
|
|
Output: output,
|
|
Exit: exit,
|
|
})
|
|
})
|
|
|
|
// List variables endpoint
|
|
http.HandleFunc("/api/variables", func(w http.ResponseWriter, r *http.Request) {
|
|
evalMu.Lock()
|
|
defer evalMu.Unlock()
|
|
|
|
list := []VariableResponse{}
|
|
for k, v := range variables {
|
|
// Skip the internal answer variables from bloating list
|
|
if k == "ans" || k == "_" {
|
|
continue
|
|
}
|
|
list = append(list, VariableResponse{
|
|
Name: k,
|
|
Value: fmt.Sprintf("%v", v.Value),
|
|
Dim: fmt.Sprintf("%v", v.Dim),
|
|
})
|
|
}
|
|
sort.Slice(list, func(i, j int) bool {
|
|
return list[i].Name < list[j].Name
|
|
})
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(list)
|
|
})
|
|
|
|
// List custom functions endpoint
|
|
http.HandleFunc("/api/functions", func(w http.ResponseWriter, r *http.Request) {
|
|
evalMu.Lock()
|
|
defer evalMu.Unlock()
|
|
|
|
list := []FunctionResponse{}
|
|
for k, v := range userFuncs {
|
|
list = append(list, FunctionResponse{
|
|
Name: k,
|
|
Args: v.Args,
|
|
Expr: v.Expr,
|
|
})
|
|
}
|
|
sort.Slice(list, func(i, j int) bool {
|
|
return list[i].Name < list[j].Name
|
|
})
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(list)
|
|
})
|
|
|
|
// Clear state endpoint
|
|
http.HandleFunc("/api/reset", func(w http.ResponseWriter, r *http.Request) {
|
|
evalMu.Lock()
|
|
defer evalMu.Unlock()
|
|
|
|
variables = make(map[string]Result)
|
|
_ = saveVariables(variablesFile)
|
|
|
|
userFuncs = make(map[string]UserFunc)
|
|
_ = saveUserFuncs(functionsFile)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"status":"success"}`))
|
|
})
|
|
|
|
fmt.Printf("Go-Ca web server started on http://localhost:%s\n", port)
|
|
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
|
fmt.Printf("❌ Error starting web server: %v\n", err)
|
|
}
|
|
}
|
|
|
|
// Beautiful cyber-retro dashboard HTML layout string
|
|
const indexHTML = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Go-Ca | Scientific Cyber Calculator</title>
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;800&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
:root {
|
|
--color-cyan: #06b6d4;
|
|
--color-pink: #f43f5e;
|
|
--color-yellow: #fbbf24;
|
|
--color-green: #10b981;
|
|
--color-blue: #3b82f6;
|
|
--color-purple: #8b5cf6;
|
|
--color-red: #ef4444;
|
|
--color-orange: #ea580c;
|
|
|
|
--bg-dark: #080c14;
|
|
--bg-card: #0f172a;
|
|
--bg-input: #020617;
|
|
|
|
--border-neon: rgba(6, 182, 212, 0.18);
|
|
--border-warm: rgba(234, 88, 12, 0.22);
|
|
|
|
--font-display: 'Outfit', -apple-system, sans-serif;
|
|
--font-mono: 'Share Tech Mono', monospace;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
background-color: var(--bg-dark);
|
|
background-image: radial-gradient(circle at top, #0f1c30 0%, #05080e 100%);
|
|
color: #f8fafc;
|
|
font-family: var(--font-display);
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* Top Navigation Header */
|
|
header {
|
|
background-color: rgba(15, 23, 42, 0.5);
|
|
border-bottom: 1px solid var(--border-neon);
|
|
backdrop-filter: blur(10px);
|
|
padding: 0.85rem 2rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
z-index: 10;
|
|
}
|
|
|
|
.header-logo {
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
letter-spacing: 0.05em;
|
|
text-shadow: 0 0 10px rgba(6, 182, 212, 0.35);
|
|
}
|
|
|
|
.header-logo span {
|
|
background: linear-gradient(135deg, var(--color-cyan) 0%, #a5f3fc 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.status-panel {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.status-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.45rem;
|
|
color: var(--color-green);
|
|
}
|
|
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
background-color: var(--color-green);
|
|
border-radius: 50%;
|
|
box-shadow: 0 0 10px var(--color-green);
|
|
animation: pulse 1.8s infinite;
|
|
}
|
|
|
|
.reset-btn {
|
|
background: rgba(244, 63, 94, 0.08);
|
|
border: 1px solid var(--color-pink);
|
|
border-radius: 4px;
|
|
color: var(--color-pink);
|
|
cursor: pointer;
|
|
font-family: var(--font-display);
|
|
font-size: 0.75rem;
|
|
font-weight: bold;
|
|
padding: 0.35rem 0.75rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.reset-btn:hover {
|
|
background: rgba(244, 63, 94, 0.2);
|
|
box-shadow: 0 0 10px rgba(244, 63, 94, 0.25);
|
|
}
|
|
|
|
.dashboard-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
min-height: 0;
|
|
padding: 1.5rem 2rem;
|
|
}
|
|
|
|
/* Column 1: Terminal Console Window */
|
|
.console-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: rgba(2, 6, 23, 0.65);
|
|
border: 1px solid var(--border-neon);
|
|
border-radius: 12px;
|
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4), inset 0 0 15px rgba(255, 255, 255, 0.01);
|
|
overflow: hidden;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.console-header {
|
|
background-color: rgba(15, 23, 42, 0.4);
|
|
border-bottom: 1px solid rgba(6, 182, 212, 0.1);
|
|
padding: 0.6rem 1.25rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.75rem;
|
|
color: var(--color-cyan);
|
|
}
|
|
|
|
.console-terminal {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 1.5rem;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.95rem;
|
|
line-height: 1.6;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(255, 255, 255, 0.05) transparent;
|
|
}
|
|
|
|
.terminal-welcome {
|
|
color: var(--color-blue);
|
|
margin-bottom: 1rem;
|
|
border-left: 2px solid var(--color-blue);
|
|
padding-left: 0.75rem;
|
|
}
|
|
|
|
.history-item {
|
|
margin-bottom: 0.85rem;
|
|
}
|
|
|
|
.history-query {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.history-output {
|
|
padding-left: 0.85rem;
|
|
}
|
|
|
|
/* Interactive Command Prompt Line */
|
|
.prompt-container {
|
|
display: flex;
|
|
align-items: center;
|
|
background-color: var(--bg-input);
|
|
border-top: 1px solid rgba(6, 182, 212, 0.1);
|
|
padding: 1rem 1.25rem;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.prompt-symbol {
|
|
color: var(--color-green);
|
|
font-family: var(--font-mono);
|
|
font-weight: bold;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.prompt-input {
|
|
flex: 1;
|
|
background: transparent;
|
|
border: none;
|
|
outline: none;
|
|
color: #f1f5f9;
|
|
font-family: var(--font-mono);
|
|
font-size: 1.05rem;
|
|
caret-color: var(--color-green);
|
|
}
|
|
|
|
.submit-btn {
|
|
background-color: var(--color-cyan);
|
|
border: none;
|
|
border-radius: 6px;
|
|
color: #020617;
|
|
cursor: pointer;
|
|
font-family: var(--font-display);
|
|
font-size: 0.8rem;
|
|
font-weight: bold;
|
|
padding: 0.5rem 1rem;
|
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.submit-btn:hover {
|
|
box-shadow: 0 0 15px var(--color-cyan);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Keyframe animations */
|
|
@keyframes pulse {
|
|
0% {
|
|
box-shadow: 0 0 5px var(--color-green);
|
|
opacity: 0.8;
|
|
}
|
|
50% {
|
|
box-shadow: 0 0 15px var(--color-green);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
box-shadow: 0 0 5px var(--color-green);
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
|
|
/* Help manual formatting */
|
|
.help-card {
|
|
background-color: rgba(15, 23, 42, 0.4);
|
|
border: 1px solid rgba(6, 182, 212, 0.15);
|
|
border-radius: 8px;
|
|
padding: 1.25rem;
|
|
margin-top: 0.5rem;
|
|
width: 100%;
|
|
max-width: 900px;
|
|
}
|
|
|
|
.help-title {
|
|
font-size: 1.1rem;
|
|
font-weight: bold;
|
|
color: var(--color-cyan);
|
|
margin-bottom: 1rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
border-bottom: 1px solid rgba(6, 182, 212, 0.2);
|
|
padding-bottom: 0.35rem;
|
|
}
|
|
|
|
.help-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.help-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.help-group-title {
|
|
font-size: 0.85rem;
|
|
font-weight: bold;
|
|
color: var(--color-yellow);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.help-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: rgba(2, 6, 23, 0.4);
|
|
border: 1px solid rgba(255, 255, 255, 0.03);
|
|
border-radius: 6px;
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: 0.75rem;
|
|
line-height: 1.4;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.help-item:hover {
|
|
background-color: rgba(6, 182, 212, 0.05);
|
|
border-color: rgba(6, 182, 212, 0.15);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.help-syntax {
|
|
font-family: var(--font-mono);
|
|
color: var(--color-cyan);
|
|
font-weight: bold;
|
|
margin-bottom: 0.10rem;
|
|
}
|
|
|
|
.help-desc {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
/* Responsive styling for tablet and mobile devices */
|
|
@media (max-width: 768px) {
|
|
.dashboard-grid {
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
header {
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
.header-logo {
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
.status-indicator span {
|
|
display: none; /* Hide status text on small screens */
|
|
}
|
|
|
|
.status-panel {
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.reset-btn {
|
|
padding: 0.35rem 0.6rem;
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.console-terminal {
|
|
padding: 1rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.prompt-container {
|
|
padding: 0.75rem 0.85rem;
|
|
}
|
|
|
|
.prompt-input {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.submit-btn {
|
|
padding: 0.45rem 0.85rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.help-card {
|
|
padding: 0.85rem;
|
|
}
|
|
.help-title {
|
|
font-size: 0.95rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
.help-grid {
|
|
gap: 0.75rem;
|
|
}
|
|
.help-item {
|
|
padding: 0.45rem 0.6rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="header-logo">
|
|
<span>Go-Ca</span> SYSTEM
|
|
</div>
|
|
|
|
<div class="status-panel">
|
|
<div class="status-indicator">
|
|
<div class="status-dot"></div>
|
|
<span>SERVER CONSOLE ACTIVE</span>
|
|
</div>
|
|
|
|
<button class="reset-btn" id="btn-reset" title="Deletes all user variables & functions">RESET STATE</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="dashboard-grid">
|
|
<!-- Calculator shell terminal -->
|
|
<div class="console-card">
|
|
<div class="console-header">
|
|
<span></span>
|
|
<span id="session-time">12:00:00</span>
|
|
</div>
|
|
|
|
<div class="console-terminal" id="terminal-scroller">
|
|
<div class="terminal-welcome">
|
|
goca {{VERSION}}, type 'help' for examples.
|
|
</div>
|
|
<div id="terminal-history"></div>
|
|
</div>
|
|
|
|
<div class="prompt-container">
|
|
<span class="prompt-symbol">goca></span>
|
|
<input type="text" id="cmd-input" class="prompt-input" autocomplete="off" autofocus placeholder="Type expression (e.g. 10 mi to km)">
|
|
<button class="submit-btn" id="btn-submit">RUN</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Local input history list tracker
|
|
const cmdInput = document.getElementById('cmd-input');
|
|
const btnSubmit = document.getElementById('btn-submit');
|
|
const btnReset = document.getElementById('btn-reset');
|
|
const historyContainer = document.getElementById('terminal-history');
|
|
const terminalScroller = document.getElementById('terminal-scroller');
|
|
|
|
const inputHistory = [];
|
|
let historyIdx = -1;
|
|
|
|
// Tick clock
|
|
setInterval(function() {
|
|
const d = new Date();
|
|
document.getElementById('session-time').textContent = d.toTimeString().split(' ')[0];
|
|
}, 1000);
|
|
|
|
// Trigger evaluate
|
|
btnSubmit.addEventListener('click', runCommand);
|
|
cmdInput.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter') {
|
|
runCommand();
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
if (inputHistory.length > 0) {
|
|
historyIdx = Math.min(historyIdx + 1, inputHistory.length - 1);
|
|
cmdInput.value = inputHistory[inputHistory.length - 1 - historyIdx];
|
|
}
|
|
} else if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
if (historyIdx > 0) {
|
|
historyIdx--;
|
|
cmdInput.value = inputHistory[inputHistory.length - 1 - historyIdx];
|
|
} else if (historyIdx === 0) {
|
|
historyIdx = -1;
|
|
cmdInput.value = '';
|
|
}
|
|
}
|
|
});
|
|
|
|
btnReset.addEventListener('click', function() {
|
|
if (confirm("Clear all user-defined variables and custom functions?")) {
|
|
fetch('/api/reset', { method: 'POST' })
|
|
.then(function(res) { return res.json(); })
|
|
.then(function() {
|
|
appendHistoryLine('unset *', '<span style="color: var(--color-pink);">All variables and functions deleted.</span>');
|
|
});
|
|
}
|
|
});
|
|
|
|
function runCommand() {
|
|
const line = cmdInput.value.trim();
|
|
if (!line) return;
|
|
|
|
inputHistory.push(line);
|
|
historyIdx = -1;
|
|
cmdInput.value = '';
|
|
|
|
if (line.toLowerCase() === 'clear') {
|
|
historyContainer.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
if (line.toLowerCase() === 'help') {
|
|
appendHistoryLine(line, getInteractiveHelpHTML());
|
|
return;
|
|
}
|
|
|
|
fetch('/api/calculate?q=' + encodeURIComponent(line))
|
|
.then(function(res) { return res.json(); })
|
|
.then(function(data) {
|
|
const formattedOutput = ansiToHtml(data.output);
|
|
appendHistoryLine(line, formattedOutput);
|
|
})
|
|
.catch(function(err) {
|
|
appendHistoryLine(line, '<span style="color: var(--color-red);">Error: Failed to connect to server.</span>');
|
|
});
|
|
}
|
|
|
|
function appendHistoryLine(query, output) {
|
|
const div = document.createElement('div');
|
|
div.className = 'history-item';
|
|
div.innerHTML = '<div class="history-query">goca> ' + escapeHtml(query) + '</div>' +
|
|
'<div class="history-output">' + output + '</div>';
|
|
historyContainer.appendChild(div);
|
|
terminalScroller.scrollTop = terminalScroller.scrollHeight;
|
|
}
|
|
|
|
// Convert Go CLI ANSI color tags to styled HTML spans
|
|
function ansiToHtml(text) {
|
|
return text
|
|
.replace(/\u001b\[1;33m/g, '<span style="color: var(--color-yellow); font-weight: bold;">')
|
|
.replace(/\u001b\[1;31m/g, '<span style="color: var(--color-red); font-weight: bold;">')
|
|
.replace(/\u001b\[1;32m/g, '<span style="color: var(--color-green); font-weight: bold;">')
|
|
.replace(/\u001b\[1;34m/g, '<span style="color: var(--color-cyan); font-weight: bold;">') // Cyan style logo
|
|
.replace(/\u001b\[1;30m/g, '<span style="color: #475569;">')
|
|
.replace(/\u001b\[1m/g, '<span style="font-weight: bold;">')
|
|
.replace(/\u001b\[0m/g, '</span>')
|
|
.replace(/\n/g, '<br>');
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
}
|
|
|
|
|
|
function insertSample(expr) {
|
|
cmdInput.value = expr;
|
|
cmdInput.focus();
|
|
}
|
|
|
|
function getInteractiveHelpHTML() {
|
|
var html = '';
|
|
html += '<div class="help-card">';
|
|
html += ' <div class="help-title">Go-Ca Interactive Manual</div>';
|
|
html += ' <div class="help-grid">';
|
|
|
|
html += ' <div class="help-group">';
|
|
html += ' <div class="help-group-title">Arithmetic & Operators</div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'5 + 3 * 2\')">';
|
|
html += ' <div class="help-syntax">5 + 3 * 2</div>';
|
|
html += ' <div class="help-desc">Basic: +, -, *, /, % (modulo)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'10 ** 3\')">';
|
|
html += ' <div class="help-syntax">10 ** 3</div>';
|
|
html += ' <div class="help-desc">Power: a ** b or pow(a, b)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'0xFF & 0b1010\')">';
|
|
html += ' <div class="help-syntax">0xFF & 0b1010</div>';
|
|
html += ' <div class="help-desc">Bitwise: &, |, ^, ~, << (Left Shift), >> (Right Shift)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'10 > 5\')">';
|
|
html += ' <div class="help-syntax">10 > 5</div>';
|
|
html += ' <div class="help-desc">Logic: ==, !=, <, >, <=, >=, ! (NOT)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'2(3 + 4)\')">';
|
|
html += ' <div class="help-syntax">2(3 + 4)</div>';
|
|
html += ' <div class="help-desc">Implicit: 2(3 + 4) or 2m (implicit multiplication)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'0xFF\')">';
|
|
html += ' <div class="help-syntax">0xFF</div>';
|
|
html += ' <div class="help-desc">Numbers: Decimal (10), Hex (0xFF), Binary (0b1010), Octal (0o77)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'x = 5.5\')">';
|
|
html += ' <div class="help-syntax">x = 5.5</div>';
|
|
html += ' <div class="help-desc">Variables: Assign (e.g. x = 5), Use \'ans\' or \'_\' for last result</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'f(x, y) = x * y + 2\')">';
|
|
html += ' <div class="help-syntax">f(x, y) = x * y + 2</div>';
|
|
html += ' <div class="help-desc">Custom Fn: f(x, y) = x * y + 2</div>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
|
|
html += ' <div class="help-group">';
|
|
html += ' <div class="help-group-title">Scientific Functions</div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'sin(90 deg)\')">';
|
|
html += ' <div class="help-syntax">sin(90 deg)</div>';
|
|
html += ' <div class="help-desc">Trig: sin(x), cos(x), tan(x) (accepts angles like 90 deg or pi rad)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'asin(1) to deg\')">';
|
|
html += ' <div class="help-syntax">asin(1) to deg</div>';
|
|
html += ' <div class="help-desc">Inv Trig: asin(x), acos(x), atan(x) (returns angles, e.g. asin(1) to deg)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'sinh(1)\')">';
|
|
html += ' <div class="help-syntax">sinh(1)</div>';
|
|
html += ' <div class="help-desc">Hyper: sinh(x), cosh(x), tanh(x)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'sqrt(16)\')">';
|
|
html += ' <div class="help-syntax">sqrt(16)</div>';
|
|
html += ' <div class="help-desc">General: sqrt(x), abs(x), exp(x), ln(x) (natural), log(x) (base 10), log2(x)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'round(5.5)\')">';
|
|
html += ' <div class="help-syntax">round(5.5)</div>';
|
|
html += ' <div class="help-desc">Rounding: ceil(x), floor(x), round(x)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'min(10, 20, 5)\')">';
|
|
html += ' <div class="help-syntax">min(10, 20, 5)</div>';
|
|
html += ' <div class="help-desc">Stats: min(a, b, ...), max(a, b, ...), mod(a, b), fact(x) (factorial)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'if(5 > 3, PI, E)\')">';
|
|
html += ' <div class="help-syntax">if(5 > 3, PI, E)</div>';
|
|
html += ' <div class="help-desc">Logic: if(cond, true_val, false_val)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'PI\')">';
|
|
html += ' <div class="help-syntax">PI</div>';
|
|
html += ' <div class="help-desc">Constants: PI, E</div>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
|
|
html += ' <div class="help-group">';
|
|
html += ' <div class="help-group-title">IP & Subnet</div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'ip(\\"192.168.1.1\\")\')">';
|
|
html += ' <div class="help-syntax">ip("192.168.1.1")</div>';
|
|
html += ' <div class="help-desc">Parse: ip("192.168.1.1"), cidr("10.0.0.0/24")</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'network(cidr(\\"10.0.0.0/24\\"))\')">';
|
|
html += ' <div class="help-syntax">network(cidr("10.0.0.0/24"))</div>';
|
|
html += ' <div class="help-desc">Subnet: network(c), broadcast(c), mask(c), hosts(c), range(c)</div>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
|
|
html += ' <div class="help-group">';
|
|
html += ' <div class="help-group-title">Units & Conversions</div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'10 mi to km\')">';
|
|
html += ' <div class="help-syntax">10 mi to km</div>';
|
|
html += ' <div class="help-desc">Syntax: <value> <unit> to/in <unit> (e.g. 10 mi to km, 1 GB in MB)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'units\')">';
|
|
html += ' <div class="help-syntax">units</div>';
|
|
html += ' <div class="help-desc">Types: Length, Mass, Time, Digital, Area, Temperature, Angle</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'0 C to K\')">';
|
|
html += ' <div class="help-syntax">0 C to K</div>';
|
|
html += ' <div class="help-desc">Example: 0 C to K, 90 deg to rad, 32 F to C</div>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
|
|
html += ' <div class="help-group">';
|
|
html += ' <div class="help-group-title">Currencies (Live Rates)</div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'100 USD to EUR\')">';
|
|
html += ' <div class="help-syntax">100 USD to EUR</div>';
|
|
html += ' <div class="help-desc">Usage: Exchange currency codes (e.g. 100 USD to EUR)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'10$ to €\')">';
|
|
html += ' <div class="help-syntax">10$ to €</div>';
|
|
html += ' <div class="help-desc">Symbols: $, €, £, ¥ (e.g. 10$ to €)</div>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
|
|
html += ' <div class="help-group">';
|
|
html += ' <div class="help-group-title">Commands</div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'help\')">';
|
|
html += ' <div class="help-syntax">help</div>';
|
|
html += ' <div class="help-desc">help: Show this help information</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'units\')">';
|
|
html += ' <div class="help-syntax">units</div>';
|
|
html += ' <div class="help-desc">units: List all supported measurement units</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'rates\')">';
|
|
html += ' <div class="help-syntax">rates</div>';
|
|
html += ' <div class="help-desc">rates: Show currency exchange rates relative to base</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'cur\')">';
|
|
html += ' <div class="help-syntax">cur</div>';
|
|
html += ' <div class="help-desc">cur: List all supported live currency codes</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'var\')">';
|
|
html += ' <div class="help-syntax">var</div>';
|
|
html += ' <div class="help-desc">var: List all user-defined variables</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'funcs\')">';
|
|
html += ' <div class="help-syntax">funcs</div>';
|
|
html += ' <div class="help-desc">funcs: List all user-defined custom functions</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'unset x\')">';
|
|
html += ' <div class="help-syntax">unset x</div>';
|
|
html += ' <div class="help-desc">unset <v>: Delete variable <v> (or \'unset *\' to delete all)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'base USD\')">';
|
|
html += ' <div class="help-syntax">base USD</div>';
|
|
html += ' <div class="help-desc">base <C>: Change base currency (e.g. base USD)</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'clear\')">';
|
|
html += ' <div class="help-syntax">clear</div>';
|
|
html += ' <div class="help-desc">clear: Clear the terminal screen</div>';
|
|
html += ' </div>';
|
|
html += ' <div class="help-item" onclick="insertSample(\'exit\')">';
|
|
html += ' <div class="help-syntax">exit</div>';
|
|
html += ' <div class="help-desc">exit/quit: Exit goca</div>';
|
|
html += ' </div>';
|
|
html += ' </div>';
|
|
|
|
html += ' </div>';
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>`
|