Compare commits
14 Commits
v0.1-waldw
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d75af428 | ||
|
|
554b04ee17 | ||
|
|
08dd5605a8 | ||
|
|
01a01f53b4 | ||
|
|
e52684d6f8 | ||
|
|
223fc5f37d | ||
|
|
4005bb9b18 | ||
|
|
55307f4a76 | ||
|
|
2ff177d0e8 | ||
|
|
6fec2df7d9 | ||
|
|
599af5b011 | ||
|
|
0f8946e4c4 | ||
|
|
3ca1709eb3 | ||
|
|
e1cae4fbd1 |
22
README.md
22
README.md
@@ -44,6 +44,13 @@ Drei komplette Missionen zum Roboter-Bauen mit der ganzen Crew:
|
||||
- OpenCV + Weather APIs + Astronomical calculations
|
||||
- Bonus: Learn about Lunar Rainbows! 🌙🌈
|
||||
|
||||
### ☀️ Solar Wasserkocher
|
||||
"Physik ist der Algorithmus der Natur"
|
||||
- 6 Phasen: Energie → Schleifen → Optimierung
|
||||
- Simuliere einen solargetriebenen Wasserkocher mit Physik-Engine
|
||||
- 3 Waldwächter: CapaciTobi, Schnecki, FunkFox
|
||||
- Lerne: Variablen, While-Loops und Energie-Management ($E = c \cdot m \cdot \Delta T$)
|
||||
|
||||
## 📚 Features
|
||||
|
||||
- **Interaktive Lernmissionen** - Von Basics bis Advanced & Robots
|
||||
@@ -101,6 +108,7 @@ Drei komplette Missionen zum Roboter-Bauen mit der ganzen Crew:
|
||||
- **🔋 LiPo Power Academy** - Batterie-Wissen für Roboter
|
||||
- **🌈 Regenbogen-Zählmaschine** - Farb-Event-Counter
|
||||
- **🌙 Mond Maschine** - Rainbow Predictor mit Computer Vision
|
||||
- **☀️ Solar Wasserkocher** - Physik-Simulation & Schleifen-Logik
|
||||
|
||||
## 🤖 Waldwächter nutzen
|
||||
|
||||
@@ -253,14 +261,20 @@ bash missions/robots/mond_maschine.sh
|
||||
|
||||
---
|
||||
|
||||
### 🎨 Crumbblocks & UI ⭐ NEU!
|
||||
- **Dein Zeichen im Wald** - Designe ein UI-Element im Browser
|
||||
- **Bridge-Technology** - Sende Daten via Clipboard vom Browser ins Terminal
|
||||
- **Smart Evaluation** - Die Crew erkennt automatisch, was du gebaut hast!
|
||||
|
||||
## 🏷️ Version
|
||||
|
||||
**v0.1-robots-complete**
|
||||
**v0.0-RC3-crumbblocks**
|
||||
- Crumbblocks Integration (Browser <-> Terminal)
|
||||
- Neue UI Mission "Dein Zeichen im Wald"
|
||||
- Smart Routing für Evaluations-Skripte
|
||||
- macOS Pfad-Fixes für Waldwächter
|
||||
- 3 Robot-Missionen
|
||||
- 17 Waldwächter komplett
|
||||
- Logs im Repo
|
||||
- Token-Tracking
|
||||
- Crew Memory (log-basiert)
|
||||
|
||||
---
|
||||
|
||||
|
||||
20
VERSION
Normal file
20
VERSION
Normal file
@@ -0,0 +1,20 @@
|
||||
RC0 - Release Candidate 0
|
||||
Datum: 2025-12-21
|
||||
|
||||
🌲 Der Crumbforest ist bereit
|
||||
|
||||
Was entstanden ist:
|
||||
• 17 Waldwächter - Alle Egos vereint
|
||||
• Input-Transparenz - crew_tokens (Token-Tracking)
|
||||
• Output-Transparenz - crew_memo (Krümel-Tracking)
|
||||
• Kollaborative Wurzeln - seeds/ für gemeinsames Wachsen
|
||||
• Zero Rules - Ein Script, ein Ordner, unendlich Möglichkeiten
|
||||
|
||||
Philosophie:
|
||||
eule -> kleine krümel -> tools -> nullfeld
|
||||
#ozmai #mensch #maschine
|
||||
|
||||
Der Wald mag keine Sticker.
|
||||
17 Egos im Wald. 1 Wald ohne Ego. 0 Regeln.
|
||||
|
||||
🦉 Entstanden im Nullfeld 💚
|
||||
@@ -15,6 +15,7 @@ NC='\033[0m' # No Color
|
||||
|
||||
# === KONFIGURATION ===
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="${SCRIPT_DIR}" # Save repo root before sourcing waldwaechter.sh
|
||||
MISSION_DIR="${SCRIPT_DIR}/missions"
|
||||
ENV_FILE="${SCRIPT_DIR}/.env"
|
||||
|
||||
@@ -499,7 +500,7 @@ function mayaeule_doktor() {
|
||||
EULE_RC="/tmp/crumb_eule_$$.rc"
|
||||
|
||||
# Absoluter Pfad zum Maya-Eule Script
|
||||
MAYAEULE_PATH="${SCRIPT_DIR}/crumbforest_roles/mayaeule_zero.sh"
|
||||
MAYAEULE_PATH="${REPO_ROOT}/crumbforest_roles/mayaeule_zero.sh"
|
||||
|
||||
cat > "${EULE_RC}" << EOF
|
||||
# Load .bashrc if exists
|
||||
@@ -564,22 +565,63 @@ function eule_memory() {
|
||||
}
|
||||
|
||||
function eule_tokens() {
|
||||
LOGDIR="\$HOME/.eule_logs"
|
||||
if [[ -f "\$LOGDIR/token_log.json" ]]; then
|
||||
# Use the new crew_tokens function from waldwaechter.sh
|
||||
# All logs are now in the repo logs/ directory
|
||||
if command -v crew_tokens &> /dev/null; then
|
||||
crew_tokens
|
||||
else
|
||||
# Fallback if waldwaechter.sh not loaded
|
||||
LOGDIR="${REPO_ROOT}/logs"
|
||||
echo -e "\${CYAN}📊 Token-Verbrauch:\${NC}"
|
||||
echo ""
|
||||
TOTAL=0
|
||||
while IFS= read -r line; do
|
||||
zeit=\$(echo "\$line" | jq -r '.zeit')
|
||||
tokens=\$(echo "\$line" | jq -r '.usage.total_tokens')
|
||||
TOTAL=\$((TOTAL + tokens))
|
||||
echo " \$zeit: \$tokens Tokens"
|
||||
done < "\$LOGDIR/token_log.json"
|
||||
|
||||
if [[ -d "\$LOGDIR" ]]; then
|
||||
local total_tokens=0
|
||||
local found_any=false
|
||||
|
||||
for token_file in "\$LOGDIR"/*/token_log.json; do
|
||||
if [[ -f "\$token_file" ]]; then
|
||||
local character=\$(basename "\$(dirname "\$token_file")")
|
||||
local char_tokens=\$(grep -v '^\[\]\$' "\$token_file" | jq -s 'map(.usage.total_tokens // 0) | add // 0' 2>/dev/null || echo 0)
|
||||
|
||||
if [[ \$char_tokens -gt 0 ]]; then
|
||||
echo " \$character: \$char_tokens Tokens"
|
||||
total_tokens=\$((total_tokens + char_tokens))
|
||||
found_any=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [[ \$found_any == true ]]; then
|
||||
echo -e "\${GREEN}Gesamt: \$total_tokens Tokens\${NC}"
|
||||
else
|
||||
echo "Noch keine Token-Logs vorhanden"
|
||||
fi
|
||||
else
|
||||
echo "Log-Verzeichnis nicht gefunden: \$LOGDIR"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "\${GREEN}Gesamt: \$TOTAL Tokens\${NC}"
|
||||
echo -e "\${YELLOW}Jede Frage ist wertvoll 🌲\${NC}"
|
||||
else
|
||||
echo "Noch keine Token-Logs."
|
||||
echo -e "\${YELLOW}Token Budget & Philosophie:\${NC}"
|
||||
echo " \"Was kostet die Frage eines Kindes?\""
|
||||
echo " Im Wald unbezahlbar - Token lehren achtsames Fragen."
|
||||
echo ""
|
||||
if [[ -n "\${DAILY_TOKEN_BUDGET}" ]] && [[ "\${DAILY_TOKEN_BUDGET}" != "0" ]]; then
|
||||
echo " Tages-Budget: \${DAILY_TOKEN_BUDGET} Tokens"
|
||||
else
|
||||
echo " Tages-Budget: Unbegrenzt"
|
||||
fi
|
||||
if [[ "\${ENABLE_TOKEN_TRACKING}" == "true" ]]; then
|
||||
echo " Token-Tracking: Aktiviert"
|
||||
fi
|
||||
echo ""
|
||||
echo -e "\${CYAN}Waldwächter (AI Charaktere):\${NC}"
|
||||
echo ""
|
||||
echo -e "\${YELLOW}Token-Logs:\${NC}"
|
||||
if [[ \$found_any == false ]]; then
|
||||
echo " Noch keine Logs vorhanden"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -616,7 +658,7 @@ function crumbcrew_doktor() {
|
||||
CREW_RC="/tmp/crumb_crew_$$.rc"
|
||||
|
||||
# Pfade zu allen Charakteren
|
||||
CREW_DIR="${SCRIPT_DIR}/crumbforest_roles"
|
||||
CREW_DIR="${REPO_ROOT}/crumbforest_roles"
|
||||
|
||||
cat > "${CREW_RC}" << EOF
|
||||
# Load .bashrc if exists
|
||||
@@ -635,9 +677,15 @@ NC='\033[0m'
|
||||
# Prompt im CrumbCrew-Style
|
||||
export PS1="\[\033[1;32m\](🌲 CrumbCrew) \u@\h:\w\$ \[\033[0m\]"
|
||||
|
||||
# Pfad zu den Charakteren
|
||||
# Repo Root und Pfade
|
||||
REPO_ROOT="${REPO_ROOT}"
|
||||
CREW_DIR="${CREW_DIR}"
|
||||
|
||||
# Lade Waldwächter Library für crew_tokens, crew_status, etc.
|
||||
if [[ -f "\${REPO_ROOT}/lib/waldwaechter.sh" ]]; then
|
||||
source "\${REPO_ROOT}/lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# === WALDWÄCHTER FUNKTIONEN ===
|
||||
|
||||
function mayaeule() {
|
||||
@@ -676,6 +724,15 @@ function schnippsi() {
|
||||
"\$CREW_DIR/schnippsi_zero.sh" "\$@"
|
||||
}
|
||||
|
||||
function templatus() {
|
||||
if [[ -z "\$1" ]]; then
|
||||
echo -e "\${YELLOW}Verwendung: templatus \"Deine Frage\"\${NC}"
|
||||
return
|
||||
fi
|
||||
echo -e "\${BLUE}📄 Templatus strukturiert...\${NC}"
|
||||
"\$CREW_DIR/templatus_zero.sh" "\$@"
|
||||
}
|
||||
|
||||
function tobi() {
|
||||
if [[ -z "\$1" ]]; then
|
||||
echo -e "\${YELLOW}Verwendung: tobi \"Deine Frage\"\${NC}"
|
||||
@@ -801,6 +858,7 @@ function crew_help() {
|
||||
echo -e " \${BLUE}🐙 deepbit\${NC} - Der poetische Oktopus (Bash-Konzepte)"
|
||||
echo -e " \${RED}🐞 bugsy\${NC} - Der Debugging-Clown (Fehlersuche)"
|
||||
echo -e " \${CYAN}✂️ schnippsi\${NC} - Der Shell-Helfer (Kommandos)"
|
||||
echo -e " \${BLUE}📄 templatus\${NC} - Der Template-Master (HTML, Struktur)"
|
||||
echo -e " \${GREEN}🤖 tobi\${NC} - Der Daten-Experte (JSON, Daten)"
|
||||
echo -e " \${CYAN}🔧 schraubaer\${NC} - Der Handwerker (Werkzeug, Schweißen)"
|
||||
echo -e " \${GREEN}🐌 schnecki\${NC} - Der Elektronik-Bastler (Löten, Sensoren)"
|
||||
@@ -830,6 +888,7 @@ function crew_help() {
|
||||
echo " deepbit \"Erkläre Pipes und Redirects\""
|
||||
echo " bugsy \"Warum funktioniert mein Script nicht?\""
|
||||
echo " schnippsi \"Wie nutze ich grep?\""
|
||||
echo " templatus \"Erstelle eine HTML5 Struktur\""
|
||||
echo " tobi \"Parse dieses JSON\""
|
||||
echo " schraubaer \"Welches Werkzeug brauche ich zum Löten?\""
|
||||
echo " schnecki \"Wie löte ich eine LED an einen Widerstand?\""
|
||||
@@ -854,7 +913,7 @@ function crew_status() {
|
||||
echo -e "\${CYAN}🌲 CrumbCrew Status\${NC}"
|
||||
echo ""
|
||||
|
||||
CHARS=("mayaeule:🦉:Maya-Eule" "deepbit:🐙:Deepbit" "bugsy:🐞:Bugsy" "schnippsi:✂️:Schnippsi" "tobi:🤖:Tobi" "schraubaer:🔧:Schraubbär" "schnecki:🐌:Schnecki" "dumbosql:🐘:DumboSQL" "funkfox:🦊:FunkFox" "taichitaube:🕊️:TaichiTaube" "snakepy:🐍:SnakePy" "pepperphp:🧓:PepperPHP" "crabbyrust:🦀:CrabbyRust" "spider:🕷️:Spider" "vektor:🧭:Vektor" "asciimonster:👾:ASCII-Monster")
|
||||
CHARS=("mayaeule:🦉:Maya-Eule" "deepbit:🐙:Deepbit" "bugsy:🐞:Bugsy" "schnippsi:✂️:Schnippsi" "templatus:📄:Templatus" "tobi:🤖:Tobi" "schraubaer:🔧:Schraubbär" "schnecki:🐌:Schnecki" "dumbosql:🐘:DumboSQL" "funkfox:🦊:FunkFox" "taichitaube:🕊️:TaichiTaube" "snakepy:🐍:SnakePy" "pepperphp:🧓:PepperPHP" "crabbyrust:🦀:CrabbyRust" "spider:🕷️:Spider" "vektor:🧭:Vektor" "asciimonster:👾:ASCII-Monster")
|
||||
|
||||
for char_info in "\${CHARS[@]}"; do
|
||||
IFS=: read -r name icon display <<< "\$char_info"
|
||||
@@ -883,52 +942,8 @@ function crew_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
function crew_tokens() {
|
||||
echo -e "\${CYAN}📊 CrumbCrew Token-Verbrauch\${NC}"
|
||||
echo ""
|
||||
|
||||
TOTAL=0
|
||||
|
||||
for logdir in ~/.{mayaeule,eule,deepbit,bugsy,schnippsi,tobi,schraubaer,schnecki,dumbosql,funkfox,taichitaube,snakepy,pepperphp,crabbyrust,spider,vektor,asciimonster}_logs; do
|
||||
if [[ -d "\$logdir" ]] && [[ -f "\$logdir/token_log.json" ]]; then
|
||||
char_name=\$(basename "\$logdir" | sed 's/_logs//')
|
||||
char_tokens=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
tokens=\$(echo "\$line" | jq -r '.usage.total_tokens' 2>/dev/null)
|
||||
if [[ "\$tokens" != "null" ]] && [[ -n "\$tokens" ]]; then
|
||||
char_tokens=\$((char_tokens + tokens))
|
||||
fi
|
||||
done < "\$logdir/token_log.json"
|
||||
|
||||
if [[ \$char_tokens -gt 0 ]]; then
|
||||
echo -e " \${GREEN}\$char_name:\${NC} \$char_tokens Tokens"
|
||||
TOTAL=\$((TOTAL + char_tokens))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "\${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\${NC}"
|
||||
echo -e " \${GREEN}Gesamt: \$TOTAL Tokens\${NC}"
|
||||
echo -e " \${CYAN}Jede Frage ist wertvoll 🌲\${NC}"
|
||||
echo -e "\${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\${NC}"
|
||||
}
|
||||
|
||||
function crew_memory() {
|
||||
echo -e "\${CYAN}📜 CrumbCrew Erinnerungen\${NC}"
|
||||
echo ""
|
||||
|
||||
for logdir in ~/.{mayaeule,eule,deepbit,bugsy,schnippsi,tobi,schraubaer,schnecki,dumbosql,funkfox,taichitaube,snakepy,pepperphp,crabbyrust,spider,vektor,asciimonster}_logs; do
|
||||
if [[ -d "\$logdir" ]] && [[ -f "\$logdir/*_history.json" ]]; then
|
||||
char_name=\$(basename "\$logdir" | sed 's/_logs//')
|
||||
count=\$(jq '. | length' "\$logdir/*_history.json" 2>/dev/null)
|
||||
if [[ "\$count" != "null" ]] && [[ \$count -gt 0 ]]; then
|
||||
echo -e " \${GREEN}\$char_name:\${NC} \$count Gespräche"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
# crew_tokens, crew_memory, crew_status, crew_doctor, crew_syntax, crew_help
|
||||
# sind bereits in waldwaechter.sh definiert und werden automatisch geladen
|
||||
|
||||
alias help="crew_help"
|
||||
alias status="crew_status"
|
||||
|
||||
631
crumbblocks/bashpanda_guertelpruefung.html
Normal file
631
crumbblocks/bashpanda_guertelpruefung.html
Normal file
@@ -0,0 +1,631 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🐼 BashPanda Gürtelprüfung 🥋</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', monospace;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
|
||||
color: #e0e0e0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
border: 2px solid #4a4a4a;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.belt-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.belt-btn {
|
||||
padding: 20px;
|
||||
border: 3px solid;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
background: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.belt-btn:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.belt-btn.active {
|
||||
box-shadow: 0 0 20px currentColor;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.black { border-color: #000; color: #fff; }
|
||||
.pink { border-color: #ff69b4; color: #ff69b4; }
|
||||
.blue { border-color: #4169e1; color: #4169e1; }
|
||||
.green { border-color: #32cd32; color: #32cd32; }
|
||||
.yellow { border-color: #ffd700; color: #ffd700; }
|
||||
.white { border-color: #f0f0f0; color: #f0f0f0; }
|
||||
|
||||
.quiz-container {
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
border: 2px solid #4a4a4a;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quiz-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.question {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-left: 4px solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.option {
|
||||
padding: 15px 20px;
|
||||
border: 2px solid #4a4a4a;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-color: #6a6a6a;
|
||||
}
|
||||
|
||||
.option.selected {
|
||||
border-color: #ffd700;
|
||||
background: rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
|
||||
.option.correct {
|
||||
border-color: #32cd32;
|
||||
background: rgba(50, 205, 50, 0.2);
|
||||
}
|
||||
|
||||
.option.wrong {
|
||||
border-color: #ff4444;
|
||||
background: rgba(255, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 15px 30px;
|
||||
border: 2px solid #4a4a4a;
|
||||
border-radius: 8px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
color: #e0e0e0;
|
||||
font-size: 1.1em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-color: #6a6a6a;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-color: #764ba2;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.results {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 15px;
|
||||
border: 2px solid #4a4a4a;
|
||||
}
|
||||
|
||||
.results.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.results h2 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.results .score {
|
||||
font-size: 3em;
|
||||
font-weight: bold;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.results .verdict {
|
||||
font-size: 1.5em;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.export-info {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border: 2px solid #ffd700;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🐼 BashPanda Gürtelprüfung 🥋</h1>
|
||||
<div class="subtitle">"Beweise dein Wissen, junger Schüler"</div>
|
||||
</div>
|
||||
|
||||
<div class="belt-selector" id="beltSelector">
|
||||
<div class="belt-btn black" data-belt="schwarz">🖤<br>Schwarz</div>
|
||||
<div class="belt-btn pink" data-belt="pink">💖<br>Pink</div>
|
||||
<div class="belt-btn blue" data-belt="blau">💙<br>Blau</div>
|
||||
<div class="belt-btn green" data-belt="gruen">💚<br>Grün</div>
|
||||
<div class="belt-btn yellow" data-belt="gelb">💛<br>Gelb</div>
|
||||
<div class="belt-btn white" data-belt="weiss">🤍<br>Weiss</div>
|
||||
</div>
|
||||
|
||||
<div class="quiz-container" id="quizContainer">
|
||||
<div class="progress" id="progress"></div>
|
||||
<div id="questionContainer"></div>
|
||||
<div class="controls">
|
||||
<button class="btn" id="prevBtn" disabled>⬅️ Zurück</button>
|
||||
<button class="btn btn-primary" id="nextBtn">Weiter ➡️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results" id="results"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const quizData = {
|
||||
schwarz: [
|
||||
{
|
||||
question: "Was gibt 'echo $HOME' aus?",
|
||||
options: ["Dein Home-Verzeichnis", "Die Umgebungsvariable HOME", "Beide Antworten sind richtig", "Einen Fehler"],
|
||||
correct: 2,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Wie erstellt man eine Variable?",
|
||||
options: ["name = value", "name=value", "$name=value", "set name=value"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'read -p \"Name: \" name'?",
|
||||
options: ["Liest eine Datei", "Zeigt 'Name:' und speichert Eingabe in $name", "Gibt $name aus", "Nichts"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Welcher ANSI Code macht Text fett?",
|
||||
options: ["\\e[0m", "\\e[1m", "\\e[4m", "\\e[32m"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Was ist der Unterschied zwischen ' und \" in echo?",
|
||||
options: ["Keiner", "' ignoriert Variablen, \" ersetzt sie", "\" ignoriert Variablen", "Beide ersetzen Variablen"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
}
|
||||
],
|
||||
pink: [
|
||||
{
|
||||
question: "Was prüft [ $a -eq $b ]?",
|
||||
options: ["String-Gleichheit", "Zahlen-Gleichheit", "Größer als", "Kleiner als"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Wie greift man auf das 3. Array-Element zu?",
|
||||
options: ["${arr[3]}", "${arr[2]}", "$arr[2]", "arr[3]"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'while [ $i -le 5 ]; do ... done'?",
|
||||
options: ["Läuft 4 mal", "Läuft 5 mal", "Läuft 6 mal", "Endlosschleife"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Wie zählt man Arrays hoch?",
|
||||
options: ["i++", "i=$((i + 1))", "let i++", "Alle sind richtig"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Was gibt ${#arr[@]} zurück?",
|
||||
options: ["Erstes Element", "Letztes Element", "Anzahl Elemente", "Fehler"],
|
||||
correct: 2,
|
||||
color: "#ff69b4"
|
||||
}
|
||||
],
|
||||
blau: [
|
||||
{
|
||||
question: "Was macht 'sed s/alt/neu/'?",
|
||||
options: ["Löscht 'alt'", "Ersetzt erstes 'alt' mit 'neu'", "Ersetzt alle 'alt' mit 'neu'", "Sucht nach 'alt'"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Wofür nutzt man 'case' statt if/then?",
|
||||
options: ["Für Schleifen", "Für mehrere if-Alternativen", "Für Arrays", "Für Funktionen"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Wie rechnet man mit Fließkommazahlen?",
|
||||
options: ["$(( ))", "bc", "let", "expr"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Was bedeutet das * in case?",
|
||||
options: ["Multiplikation", "Wildcard (alles)", "Default-Fall", "Fehler"],
|
||||
correct: 2,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Wie nutzt man bc für Division?",
|
||||
options: ["bc 10/3", "echo '10/3' | bc", "10/3 | bc", "bc(10/3)"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
}
|
||||
],
|
||||
gruen: [
|
||||
{
|
||||
question: "Was macht grep -i?",
|
||||
options: ["Zeigt Zeilennummern", "Ignoriert Groß-/Kleinschreibung", "Invertiert Suche", "Rekursiv suchen"],
|
||||
correct: 1,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Was bedeutet ^ in Regex?",
|
||||
options: ["Zeilenanfang", "Zeilenende", "Beliebiges Zeichen", "Wiederholung"],
|
||||
correct: 0,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Was matched [0-9]+ in Regex?",
|
||||
options: ["Genau eine Ziffer", "Eine oder mehr Ziffern", "Null oder mehr Ziffern", "Keine Ziffer"],
|
||||
correct: 1,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Wie sucht man rekursiv in allen Dateien?",
|
||||
options: ["grep muster *", "grep -r muster .", "grep -i muster", "grep -n muster"],
|
||||
correct: 1,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Was matched .* in Regex?",
|
||||
options: ["Nichts", "Punkt und Stern", "Beliebige Zeichen (0+)", "Ein Zeichen"],
|
||||
correct: 2,
|
||||
color: "#32cd32"
|
||||
}
|
||||
],
|
||||
gelb: [
|
||||
{
|
||||
question: "Wie definiert man eine Funktion?",
|
||||
options: ["func name() {}", "function name() {}", "def name() {}", "Beide 1 und 2"],
|
||||
correct: 3,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was ist $1 in einer Funktion?",
|
||||
options: ["Erster Buchstabe", "Erster Parameter", "Exit-Code", "PID"],
|
||||
correct: 1,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'source datei.sh'?",
|
||||
options: ["Führt Datei aus", "Lädt Funktionen in aktuelle Shell", "Kopiert Datei", "Löscht Datei"],
|
||||
correct: 1,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'return 42' in einer Funktion?",
|
||||
options: ["Gibt 42 aus", "Beendet Skript", "Verlässt Funktion mit Code 42", "Fehler"],
|
||||
correct: 2,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was ist $@ in einer Funktion?",
|
||||
options: ["Erster Parameter", "Alle Parameter", "Anzahl Parameter", "Funktionsname"],
|
||||
correct: 1,
|
||||
color: "#ffd700"
|
||||
}
|
||||
],
|
||||
weiss: [
|
||||
{
|
||||
question: "Was macht & am Ende eines Befehls?",
|
||||
options: ["Beendet ihn", "Startet im Hintergrund", "Wiederholt ihn", "Nichts"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Was ist $! ?",
|
||||
options: ["Exit-Code", "PID des letzten Background-Jobs", "Anzahl Parameter", "Home-Verzeichnis"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'wait'?",
|
||||
options: ["Wartet 1 Sekunde", "Wartet auf Eingabe", "Wartet auf Background-Jobs", "Pausiert Skript"],
|
||||
correct: 2,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Wie zeigt man alle Background-Jobs?",
|
||||
options: ["ps", "jobs", "bg", "fg"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'fg %1'?",
|
||||
options: ["Beendet Job 1", "Bringt Job 1 in Vordergrund", "Startet Job 1", "Pausiert Job 1"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let currentBelt = null;
|
||||
let currentQuestion = 0;
|
||||
let answers = [];
|
||||
let score = 0;
|
||||
|
||||
// Belt Selection
|
||||
document.getElementById('beltSelector').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.belt-btn');
|
||||
if (!btn) return;
|
||||
|
||||
document.querySelectorAll('.belt-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
currentBelt = btn.dataset.belt;
|
||||
currentQuestion = 0;
|
||||
answers = [];
|
||||
score = 0;
|
||||
|
||||
document.getElementById('quizContainer').classList.add('active');
|
||||
document.getElementById('results').classList.remove('show');
|
||||
|
||||
showQuestion();
|
||||
});
|
||||
|
||||
function showQuestion() {
|
||||
if (!currentBelt) return;
|
||||
|
||||
const questions = quizData[currentBelt];
|
||||
const q = questions[currentQuestion];
|
||||
|
||||
document.getElementById('progress').innerHTML = `
|
||||
<span>Frage ${currentQuestion + 1} von ${questions.length}</span>
|
||||
<span>🐼 BashPanda beobachtet</span>
|
||||
`;
|
||||
|
||||
const questionHTML = `
|
||||
<div class="question">
|
||||
<div class="question-text" style="border-color: ${q.color}">
|
||||
${currentQuestion + 1}. ${q.question}
|
||||
</div>
|
||||
<div class="options">
|
||||
${q.options.map((opt, idx) => `
|
||||
<div class="option" data-index="${idx}">
|
||||
${String.fromCharCode(65 + idx)}. ${opt}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('questionContainer').innerHTML = questionHTML;
|
||||
|
||||
// Restore previous answer if exists
|
||||
if (answers[currentQuestion] !== undefined) {
|
||||
const selected = document.querySelector(`[data-index="${answers[currentQuestion]}"]`);
|
||||
if (selected) selected.classList.add('selected');
|
||||
}
|
||||
|
||||
// Option selection
|
||||
document.querySelectorAll('.option').forEach(opt => {
|
||||
opt.addEventListener('click', () => {
|
||||
document.querySelectorAll('.option').forEach(o => o.classList.remove('selected'));
|
||||
opt.classList.add('selected');
|
||||
answers[currentQuestion] = parseInt(opt.dataset.index);
|
||||
document.getElementById('nextBtn').disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
document.getElementById('prevBtn').disabled = currentQuestion === 0;
|
||||
document.getElementById('nextBtn').disabled = answers[currentQuestion] === undefined;
|
||||
|
||||
const questions = quizData[currentBelt];
|
||||
if (currentQuestion === questions.length - 1) {
|
||||
document.getElementById('nextBtn').textContent = '🎯 Auswerten';
|
||||
} else {
|
||||
document.getElementById('nextBtn').textContent = 'Weiter ➡️';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('prevBtn').addEventListener('click', () => {
|
||||
if (currentQuestion > 0) {
|
||||
currentQuestion--;
|
||||
showQuestion();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('nextBtn').addEventListener('click', () => {
|
||||
const questions = quizData[currentBelt];
|
||||
|
||||
if (currentQuestion < questions.length - 1) {
|
||||
currentQuestion++;
|
||||
showQuestion();
|
||||
} else {
|
||||
showResults();
|
||||
}
|
||||
});
|
||||
|
||||
function showResults() {
|
||||
const questions = quizData[currentBelt];
|
||||
score = 0;
|
||||
|
||||
answers.forEach((ans, idx) => {
|
||||
if (ans === questions[idx].correct) {
|
||||
score++;
|
||||
}
|
||||
});
|
||||
|
||||
const percentage = (score / questions.length * 100).toFixed(0);
|
||||
let verdict, color, emoji;
|
||||
|
||||
if (percentage >= 80) {
|
||||
verdict = "🎉 BESTANDEN! 🎉<br>Der Gürtel gehört dir!";
|
||||
color = "#32cd32";
|
||||
emoji = "✅";
|
||||
} else if (percentage >= 60) {
|
||||
verdict = "Fast geschafft!<br>Übe noch ein wenig.";
|
||||
color = "#ffd700";
|
||||
emoji = "⚠️";
|
||||
} else {
|
||||
verdict = "Noch nicht bereit.<br>Lerne weiter, junger Schüler.";
|
||||
color = "#ff4444";
|
||||
emoji = "❌";
|
||||
}
|
||||
|
||||
const resultData = {
|
||||
belt: currentBelt,
|
||||
score: score,
|
||||
total: questions.length,
|
||||
percentage: percentage,
|
||||
passed: percentage >= 80,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
document.getElementById('results').innerHTML = `
|
||||
<h2>${emoji} Gürtelprüfung: ${currentBelt.toUpperCase()}</h2>
|
||||
<div class="score" style="color: ${color}">
|
||||
${score} / ${questions.length}
|
||||
</div>
|
||||
<div style="font-size: 2em; margin: 20px 0;">
|
||||
${percentage}%
|
||||
</div>
|
||||
<div class="verdict" style="border-color: ${color}">
|
||||
${verdict}
|
||||
</div>
|
||||
<div class="export-info">
|
||||
<p><strong>📋 Ergebnis exportiert!</strong></p>
|
||||
<p>Die Ergebnisse wurden in die Zwischenablage kopiert.</p>
|
||||
<p>Füge sie im Terminal ein für die Auswertung!</p>
|
||||
<br>
|
||||
<button class="btn btn-primary" onclick="location.reload()">🔄 Neue Prüfung</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('quizContainer').classList.remove('active');
|
||||
document.getElementById('results').classList.add('show');
|
||||
|
||||
// Copy to clipboard
|
||||
const exportText = JSON.stringify(resultData, null, 2);
|
||||
navigator.clipboard.writeText(exportText).then(() => {
|
||||
console.log('Ergebnisse in Zwischenablage kopiert!');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
628
crumbblocks/bezier.html
Normal file
628
crumbblocks/bezier.html
Normal file
@@ -0,0 +1,628 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One‑Liner Painter • Bezier Pen + Editor</title>
|
||||
<style>
|
||||
:root{ --bg:#0b0b10; --panel:#12121a; --muted:#9aa3b2; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c; }
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none;font-size:.9rem;color:var(--muted)}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:380px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:1100px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.col{display:grid;gap:10px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0b1114;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0b0b10}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:240px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a;touch-action:none}
|
||||
.badge{font-size:.75rem;color:#0b1114;background:var(--accent);border-radius:999px;padding:2px 8px}
|
||||
.muted{color:var(--muted)}
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
.anchor{fill:#31e07b;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handle{fill:#ffe08a;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handleLine{stroke:#ffe08a;stroke-width:2.5;stroke-dasharray:4 4; pointer-events:none}
|
||||
.ghost{fill:none;stroke:#ffffff55;stroke-width:2;stroke-dasharray:6 8}
|
||||
#bezierUI{ pointer-events:none }
|
||||
.selected{filter:drop-shadow(0 0 4px #ffd166)}
|
||||
.hint{font-size:.85rem;color:#cfd5e3;line-height:1.3}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One‑Liner Painter • <span class="badge">Bezier-Pen</span> + Editor • Replay • SVG/PNG/JSON</h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel">
|
||||
<h2 style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits „1“</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop</span></label>
|
||||
<label class="chip"><input type="checkbox" id="frameOn" checked> <span>Crumblines-Frame</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke <input type="range" id="stroke" min="4" max="28" step="1" value="12"></label>
|
||||
<label>Sampler
|
||||
<select id="sampler">
|
||||
<option value="raw">Raw Polyline</option>
|
||||
<option value="quad">Quadratic</option>
|
||||
<option value="cubic" selected>Catmull‑Rom → Cubic</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-top:6px">
|
||||
<strong>Bezier‑Pen (Anker setzen)</strong>
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="penMode"> <span>Pen aktiv</span></label>
|
||||
<button class="btn" id="finishPen" disabled>Fertig (↵)</button>
|
||||
<span class="muted">Shift: Winkel snap • Alt: Ecke • ⌫: Letzten Anchor löschen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-top:6px">
|
||||
<strong>Bezier‑Editor</strong>
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="editBezier"> <span>Bearbeiten</span></label>
|
||||
<label class="chip"><input type="checkbox" id="linkHandles" checked> <span>Spiegel‑Griffe</span></label>
|
||||
<button class="btn" id="toBezier">Auswahl → Bezier</button>
|
||||
<button class="btn" id="splitHandles">Anker: Split</button>
|
||||
<button class="btn warn" id="delAnchor" disabled>Anker löschen</button>
|
||||
</div>
|
||||
<p class="hint">Klicke einen Stroke → <em>Bearbeiten</em>. Doppelklick auf Pfad fügt einen Anker. <span class="kbd">L</span> toggelt Spiegel‑Griffe.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich-Liste"></ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="replay">▶︎ Replay</button>
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn" id="downloadPNG">PNG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion‑JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One‑Liner</title>
|
||||
<desc id="svgDesc">a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect id="frameRect" x="16.5" y="16.5" width="1167" height="767" fill="none" stroke="rgba(255,255,255,.2)" stroke-width="1.5" stroke-dasharray="6 10"/>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<path id="ghost" class="ghost" d=""/>
|
||||
<g id="bezierUI"></g>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>Neu: richtiger Bezier‑Pen wie in Illustrator/Inkscape (Anker setzen & ziehen).</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const ghost = document.getElementById('ghost');
|
||||
const bezierUI= document.getElementById('bezierUI');
|
||||
const frameRect = document.getElementById('frameRect');
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const frameOnCb = document.getElementById('frameOn');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const samplerSel= document.getElementById('sampler');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
|
||||
const penModeCb = document.getElementById('penMode');
|
||||
const finishPenBtn = document.getElementById('finishPen');
|
||||
|
||||
const editBezierCb = document.getElementById('editBezier');
|
||||
const linkHandlesCb= document.getElementById('linkHandles');
|
||||
const toBezierBtn = document.getElementById('toBezier');
|
||||
const splitBtn = document.getElementById('splitHandles');
|
||||
const delAnchorBtn = document.getElementById('delAnchor');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const dlPNGBtn = document.getElementById('downloadPNG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
const replayBtn = document.getElementById('replay');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null;
|
||||
const strokes = [];
|
||||
let t0 = null;
|
||||
let selectedStroke = null;
|
||||
let selectedAnchor = {stroke:null, idx:-1};
|
||||
let dragging = null; // edit-drag
|
||||
|
||||
// Pen state
|
||||
let penActive = false;
|
||||
let penStroke = null; // {isBezier:true, anchors:[], pathEl,...}
|
||||
let penDragging = false;
|
||||
let placingFirst = false;
|
||||
let placingNext = false;
|
||||
let penStartPt = null;
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const p = pt.matrixTransform(stage.getScreenCTM().inverse());
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
p.addEventListener('pointerdown', (e)=>{
|
||||
if(editBezierCb.checked && !penActive){ e.stopPropagation(); const s = strokes.find(st=>st.pathEl===p); if(s){ selectStroke(s); drawBezierUI(); } }
|
||||
});
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
function pathFromAnchors(A){
|
||||
if(!A || A.length<2) return '';
|
||||
let d = `M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<A.length-1;i++){
|
||||
const a=A[i], b=A[i+1];
|
||||
d += ` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const edit = document.createElement('button'); edit.className='btn'; edit.textContent='Bearbeiten';
|
||||
edit.addEventListener('click',()=>{ selectStroke(s); editBezierCb.checked = true; drawBezierUI(); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${s.isBezier? 'Bezier' : (s.points?.length||0)+' pts'}`;
|
||||
li.append('Stroke', sel, sw, edit, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx]; if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
if(selectedStroke===s){ selectedStroke=null; clearBezierUI(); }
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
function selectStroke(s){
|
||||
selectedStroke = s;
|
||||
strokes.forEach(st=> st.pathEl.classList.toggle('selected', st===s));
|
||||
}
|
||||
|
||||
// -------- Freehand --------
|
||||
function toPathD(s){
|
||||
if(s.isBezier) return pathFromAnchors(s.anchors);
|
||||
const pts = s.points;
|
||||
const smp = s.sampler || samplerSel.value;
|
||||
if(pts.length===0) return '';
|
||||
if(smp==='raw'){
|
||||
let d = `M ${pts[0].x} ${pts[0].y}`;
|
||||
for(let i=1;i<pts.length;i++){ d+=` L ${pts[i].x} ${pts[i].y}`; } return d;
|
||||
} else if(smp==='cubic'){
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)); const P=(i)=>pts[ clamp(i,0,pts.length-1) ];
|
||||
let d=`M ${pts[0].x} ${pts[0].y}`;
|
||||
for(let i=0;i<pts.length-1;i++){
|
||||
const p0=P(i-1), p1=P(i), p2=P(i+1), p3=P(i+2);
|
||||
const c1x = p1.x + (p2.x - p0.x)/6, c1y = p1.y + (p2.y - p0.y)/6;
|
||||
const c2x = p2.x - (p3.x - p1.x)/6, c2y = p2.y - (p3.y - p1.y)/6;
|
||||
d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;
|
||||
} return d;
|
||||
} else {
|
||||
let d = `M ${pts[0].x} ${pts[0].y}`;
|
||||
for(let i=1;i<pts.length-1;i++){ const p0=pts[i], p1=pts[i+1]; const mx=(p0.x+p1.x)/2, my=(p0.y+p1.y)/2; d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`; }
|
||||
const last=pts[pts.length-1]; d += ` L ${last.x} ${last.y}`; return d;
|
||||
}
|
||||
}
|
||||
function updatePath(s){ s.pathEl.setAttribute('d', toPathD(s)); s.pathEl.setAttribute('stroke-width', s.width); }
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function startStroke(e){
|
||||
if(penActive || editBezierCb.checked){ return; }
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
const t = now();
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[pt], pathEl, start:t, duration:0, length:0, sampler:samplerSel.value, isBezier:false };
|
||||
applyStrokeStyle(curStroke); updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
drawing = true;
|
||||
}
|
||||
stage.addEventListener('pointermove', (e)=>{
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push(p);
|
||||
updatePath(curStroke);
|
||||
}
|
||||
});
|
||||
window.addEventListener('pointerup', ()=>{
|
||||
if(drawing && curStroke){
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
drawing=false;
|
||||
});
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
// Editor helpers
|
||||
function clearBezierUI(){ bezierUI.innerHTML=''; selectedAnchor={stroke:null, idx:-1}; delAnchorBtn.disabled = true; }
|
||||
function drawBezierUI(){
|
||||
clearBezierUI();
|
||||
const s = selectedStroke;
|
||||
if(!editBezierCb.checked || !s || !s.isBezier) return;
|
||||
s.anchors.forEach((a,idx)=>{
|
||||
bezierUI.append(line(a.x, a.y, a.h1.x, a.h1.y, 'handleLine'));
|
||||
bezierUI.append(line(a.x, a.y, a.h2.x, a.h2.y, 'handleLine'));
|
||||
const h1 = circle(a.h1.x, a.h1.y, 5, 'handle', (e)=>startDrag('h1', s, idx, e));
|
||||
const h2 = circle(a.h2.x, a.h2.y, 5, 'handle', (e)=>startDrag('h2', s, idx, e));
|
||||
bezierUI.append(h1, h2);
|
||||
const an = circle(a.x, a.y, 6.5, 'anchor', (e)=>{ startDrag('anchor', s, idx, e) });
|
||||
an.addEventListener('dblclick', (e)=>{ e.stopPropagation(); insertAnchorAt(s, idx); });
|
||||
an.addEventListener('pointerdown', ()=>{ selectedAnchor={stroke:s,idx}; delAnchorBtn.disabled=false; });
|
||||
bezierUI.append(an);
|
||||
});
|
||||
}
|
||||
function line(x1,y1,x2,y2,cls){ const l = document.createElementNS('http://www.w3.org/2000/svg','line'); l.setAttribute('x1',x1); l.setAttribute('y1',y1); l.setAttribute('x2',x2); l.setAttribute('y2',y2); l.setAttribute('class',cls); return l; }
|
||||
function circle(x,y,r,cls,onDown){
|
||||
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class',cls);
|
||||
c.style.touchAction = 'none';
|
||||
if(onDown) c.addEventListener('pointerdown', (e)=>{ e.stopPropagation(); onDown(e); });
|
||||
return c;
|
||||
}
|
||||
function startDrag(type, s, idx, ev){
|
||||
ev.preventDefault();
|
||||
const a = s.anchors[idx];
|
||||
const p0 = svgPoint(ev);
|
||||
const dragging = {type, stroke:s, idx, ox:p0.x, oy:p0.y, pid:ev.pointerId};
|
||||
try { stage.setPointerCapture(ev.pointerId); } catch(e){}
|
||||
const move = (e)=>{
|
||||
const pos = svgPoint(e);
|
||||
const dx = pos.x - dragging.ox, dy = pos.y - dragging.oy;
|
||||
dragging.ox = pos.x; dragging.oy = pos.y;
|
||||
if(type==='anchor'){
|
||||
a.x+=dx; a.y+=dy; a.h1.x+=dx; a.h1.y+=dy; a.h2.x+=dx; a.h2.y+=dy;
|
||||
}else if(type==='h1'){
|
||||
a.h1.x+=dx; a.h1.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h2.x = a.x - (a.h1.x - a.x);
|
||||
a.h2.y = a.y - (a.h1.y - a.y);
|
||||
}
|
||||
}else if(type==='h2'){
|
||||
a.h2.x+=dx; a.h2.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h1.x = a.x - (a.h2.x - a.x);
|
||||
a.h1.y = a.y - (a.h2.y - a.y);
|
||||
}
|
||||
}
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
};
|
||||
const up = ()=>{
|
||||
try { stage.releasePointerCapture(ev.pointerId); } catch(_) {}
|
||||
window.removeEventListener('pointermove', move);
|
||||
};
|
||||
window.addEventListener('pointermove', move, {passive:true});
|
||||
window.addEventListener('pointerup', up, {once:true});
|
||||
}
|
||||
function insertAnchorAt(s, idx){
|
||||
const A = s.anchors; if(idx>=A.length-1) return;
|
||||
const a=A[idx], b=A[idx+1];
|
||||
const mid = {x:(a.x+b.x)/2, y:(a.y+b.y)/2};
|
||||
const dir = {x:(b.x-a.x), y:(b.y-a.y)};
|
||||
const hLen = {x:dir.x*0.33, y:dir.y*0.33};
|
||||
const newA = { x: mid.x, y: mid.y,
|
||||
h1:{x: mid.x - hLen.x*0.5, y: mid.y - hLen.y*0.5},
|
||||
h2:{x: mid.x + hLen.x*0.5, y: mid.y + hLen.y*0.5},
|
||||
split:false };
|
||||
A.splice(idx+1,0,newA);
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
drawBezierUI();
|
||||
}
|
||||
delAnchorBtn.addEventListener('click', ()=>{
|
||||
const s=selectedStroke, idx=selectedAnchor.idx;
|
||||
if(!s || idx<0) return;
|
||||
if(s.anchors.length<=2) return;
|
||||
s.anchors.splice(idx,1);
|
||||
selectedAnchor={stroke:null,idx:-1};
|
||||
delAnchorBtn.disabled=true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
});
|
||||
toBezierBtn.addEventListener('click', ()=>{
|
||||
if(selectedStroke && !selectedStroke.isBezier){
|
||||
const s=selectedStroke; const pts=s.points;
|
||||
if(!pts || pts.length<2) return;
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)); const P=(i)=>pts[ clamp(i,0,pts.length-1) ];
|
||||
const A=[];
|
||||
for(let i=0;i<pts.length-1;i++){
|
||||
const p0=P(i-1), p1=P(i), p2=P(i+1), p3=P(i+2);
|
||||
const c1 = {x: p1.x + (p2.x - p0.x)/6, y: p1.y + (p2.y - p0.y)/6};
|
||||
const c2 = {x: p2.x - (p3.x - p1.x)/6, y: p2.y - (p3.y - p1.y)/6};
|
||||
if(i===0){ A.push({x:p1.x,y:p1.y,h1:{x:p1.x,y:p1.y},h2:{x:c1.x,y:c1.y},split:false}); }
|
||||
else { A[A.length-1].h2 = {x:c1.x,y:c1.y}; }
|
||||
A.push({x:p2.x,y:p2.y,h1:{x:c2.x,y:c2.y},h2:{x:p2.x,y:p2.y},split:false});
|
||||
}
|
||||
s.anchors=A; s.isBezier=true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
listRefresh(); drawBezierUI();
|
||||
}
|
||||
});
|
||||
splitBtn.addEventListener('click', ()=>{
|
||||
if(selectedStroke && selectedAnchor.idx>=0){ const a=selectedStroke.anchors[selectedAnchor.idx]; a.split=!a.split; drawBezierUI(); }
|
||||
});
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if((e.key==='Delete'||e.key==='Backspace') && penActive){ penBackspace(); }
|
||||
if((e.key==='Delete'||e.key==='Backspace') && editBezierCb.checked){ delAnchorBtn.click(); }
|
||||
if(e.key==='l' || e.key==='L'){ linkHandlesCb.checked = !linkHandlesCb.checked; }
|
||||
if(e.key==='Enter' && penActive){ finishPen(); }
|
||||
});
|
||||
|
||||
// Pen tool
|
||||
penModeCb.addEventListener('change', ()=>{
|
||||
const on = penModeCb.checked;
|
||||
if(on){ editBezierCb.checked=false; penActive=true; ghost.setAttribute('d',''); deselectAll(); }
|
||||
else { penActive=false; cancelPen(); }
|
||||
finishPenBtn.disabled = !on;
|
||||
});
|
||||
finishPenBtn.addEventListener('click', finishPen);
|
||||
|
||||
function startPen(e){
|
||||
const p = svgPoint(e);
|
||||
if(!penStroke){
|
||||
penStroke = { id: Date.now()%1e7, mode, width:+strokeInp.value, colorA: colorAInp.value, colorB: colorBInp.value,
|
||||
isBezier:true, anchors:[], pathEl:createPathEl() };
|
||||
applyStrokeStyle(penStroke);
|
||||
const a = {x:p.x,y:p.y,h1:{x:p.x,y:p.y},h2:{x:p.x,y:p.y},split:false};
|
||||
penStroke.anchors.push(a);
|
||||
placingFirst=true; penDragging=true; penStartPt=p;
|
||||
try{ stage.setPointerCapture(e.pointerId); }catch(_){}}
|
||||
else {
|
||||
const a = {x:p.x,y:p.y,h1:{x:p.x,y:p.y},h2:{x:p.x,y:p.y},split:false};
|
||||
penStroke.anchors.push(a);
|
||||
placingNext=true; penDragging=true;
|
||||
try{ stage.setPointerCapture(e.pointerId); }catch(_){}}
|
||||
}
|
||||
function movePen(e){
|
||||
if(!penStroke || !penDragging) return;
|
||||
const p = svgPoint(e);
|
||||
const A = penStroke.anchors;
|
||||
if(placingFirst){
|
||||
const start=A[0];
|
||||
let v = {x: p.x - start.x, y: p.y - start.y};
|
||||
if(e.shiftKey){
|
||||
const ang=Math.atan2(v.y,v.x), step=Math.PI/12; const a=Math.round(ang/step)*step; const L=Math.hypot(v.x,v.y); v={x:Math.cos(a)*L,y:Math.sin(a)*L};
|
||||
}
|
||||
start.h2 = {x:start.x+v.x, y:start.y+v.y};
|
||||
} else if(placingNext){
|
||||
const last=A[A.length-1], prev=A[A.length-2];
|
||||
let v = {x: p.x - last.x, y: p.y - last.y};
|
||||
if(e.shiftKey){
|
||||
const ang=Math.atan2(v.y,v.x), step=Math.PI/12; const a=Math.round(ang/step)*step; const L=Math.hypot(v.x,v.y); v={x:Math.cos(a)*L,y:Math.sin(a)*L};
|
||||
}
|
||||
last.h1 = {x:last.x+v.x, y:last.y+v.y};
|
||||
if(e.altKey){ last.split=true; last.h2={x:last.x,y:last.y}; }
|
||||
else { last.split=false; last.h2 = {x:last.x - v.x, y:last.y - v.y}; }
|
||||
// previous outgoing 1/3 along segment
|
||||
const seg = {x:last.x - prev.x, y:last.y - prev.y};
|
||||
const L = Math.hypot(seg.x,seg.y); if(L>0){
|
||||
const n={x:seg.x/L,y:seg.y/L}; const k=L/3;
|
||||
prev.h2 = {x: prev.x + n.x*k, y: prev.y + n.y*k};
|
||||
}
|
||||
}
|
||||
ghost.setAttribute('d', pathFromAnchors(A));
|
||||
penStroke.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
applyStrokeStyle(penStroke);
|
||||
penStroke.pathEl.setAttribute('stroke-width', penStroke.width);
|
||||
}
|
||||
function endPen(e){ penDragging=false; placingFirst=false; placingNext=false; ghost.setAttribute('d',''); }
|
||||
function finishPen(){ if(!penStroke) return; strokes.push(penStroke); listRefresh(); penStroke=null; penStartPt=null; ghost.setAttribute('d',''); }
|
||||
function cancelPen(){ if(penStroke){ penStroke.pathEl.remove(); } penStroke=null; penStartPt=null; ghost.setAttribute('d',''); }
|
||||
function penBackspace(){ if(!penStroke) return; const A=penStroke.anchors; if(A.length<=1){ cancelPen(); } else { A.pop(); penStroke.pathEl.setAttribute('d', pathFromAnchors(A)); } }
|
||||
|
||||
// Stage routing
|
||||
stage.addEventListener('pointerdown', (e)=>{ if(penModeCb.checked) startPen(e); else startStroke(e); });
|
||||
stage.addEventListener('pointermove', (e)=>{ if(penModeCb.checked) movePen(e); });
|
||||
window.addEventListener('pointerup', (e)=>{ if(penModeCb.checked) endPen(e); });
|
||||
|
||||
// Misc
|
||||
function exportSVGString(){
|
||||
frameRect.style.display = frameOnCb.checked ? 'block' : 'none';
|
||||
const clone = stage.cloneNode(true);
|
||||
const ui = clone.querySelector('#bezierUI'); if(ui) ui.remove();
|
||||
const gh = clone.querySelector('#ghost'); if(gh) gh.remove();
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n${s}`;
|
||||
}
|
||||
function motionJSON(){
|
||||
const items = strokes.map(s=>{
|
||||
const o = { id:s.id, mode:s.mode, colorA:s.colorA, colorB:s.colorB, width:s.width, sampler:s.sampler || 'cubic' };
|
||||
if(s.isBezier){
|
||||
o.isBezier = true;
|
||||
o.anchors = s.anchors.map(a=>({x:a.x,y:a.y,h1:{x:a.h1.x,y:a.h1.y},h2:{x:a.h2.x,y:a.h2.y},split:!!a.split}));
|
||||
}else{
|
||||
o.points = (s.points||[]).map(p=>({x:p.x,y:p.y}));
|
||||
}
|
||||
return o;
|
||||
});
|
||||
return JSON.stringify({strokes:items}, null, 2);
|
||||
}
|
||||
document.getElementById('copySVG').addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
document.getElementById('downloadSVG').addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
document.getElementById('downloadPNG').addEventListener('click', ()=>{ const svg=exportSVGString(); svgToPng(svg, 'omi-omega-oneliner.png'); });
|
||||
document.getElementById('copyJSON').addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion‑JSON kopiert'); });
|
||||
document.getElementById('downloadJSON').addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
document.getElementById('replay').addEventListener('click', ()=>{
|
||||
if(!strokes.length) return;
|
||||
strokes.forEach(s=> s.pathEl.setAttribute('d',''));
|
||||
const total=1500; const t0=performance.now();
|
||||
const step=()=>{
|
||||
const t=performance.now()-t0;
|
||||
strokes.forEach(s=>{
|
||||
if(!s.isBezier) return;
|
||||
const A=s.anchors, n=A.length-1; const k=Math.max(1, Math.floor((t/total)*n));
|
||||
let d=`M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<k && i<n;i++){ const a=A[i], b=A[i+1]; d+=` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`; }
|
||||
s.pathEl.setAttribute('d', d); applyStrokeStyle(s); s.pathEl.setAttribute('stroke-width', s.width);
|
||||
});
|
||||
if(t<total) requestAnimationFrame(step); else { strokes.forEach(updatePath); }
|
||||
};
|
||||
requestAnimationFrame(step);
|
||||
});
|
||||
|
||||
function svgToPng(svgString, filename){
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([svgString], {type:'image/svg+xml;charset=utf-8'});
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
img.onload = function(){
|
||||
const canvas = document.createElement('canvas');
|
||||
const vb = stage.viewBox.baseVal;
|
||||
canvas.width = vb.width; canvas.height = vb.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toBlob((blob)=>{
|
||||
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=filename; a.click();
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
// Basic toggles
|
||||
frameRect.style.display = document.getElementById('frameOn').checked ? 'block' : 'none';
|
||||
document.getElementById('frameOn').addEventListener('change', (e)=>{ frameRect.style.display = e.target.checked ? 'block' : 'none'; });
|
||||
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{ strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; ghost.setAttribute('d',''); clearBezierUI(); listRefresh(); });
|
||||
|
||||
function deselectAll(){ selectedStroke=null; strokes.forEach(st=> st.pathEl.classList.remove('selected')); clearBezierUI(); }
|
||||
|
||||
})();</script>
|
||||
</body>
|
||||
</html>
|
||||
452
crumbblocks/bezier_stream.html
Normal file
452
crumbblocks/bezier_stream.html
Normal file
@@ -0,0 +1,452 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Schnippsi Painter — Stream + Bezier</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0b0f12; --panel:#12181f; --ink:#e2f2ff; --muted:#7a8aa0; --accent:#62d3a4; --accent2:#ffd166;
|
||||
}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--ink);font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
#wrap{display:grid;grid-template-rows:auto 1fr; height:100%;}
|
||||
#toolbar{
|
||||
display:flex;gap:.75rem;align-items:center; padding:.6rem .8rem; background:linear-gradient(180deg,#12181f,#0e141a);
|
||||
position:sticky; top:0; z-index:3; box-shadow:0 8px 20px rgba(0,0,0,.25);
|
||||
}
|
||||
#toolbar input[type=color], #toolbar input[type=text]{height:32px}
|
||||
#toolbar .btn{
|
||||
padding:.55rem .8rem; border-radius:14px; background:#1a2330; color:#cfe7ff; border:1px solid #2b3a4b; cursor:pointer;
|
||||
}
|
||||
#toolbar .btn[aria-pressed="true"]{background:linear-gradient(180deg,#1f2a36,#1a2330); outline:2px solid var(--accent);}
|
||||
#toolbar .danger{border-color:#603; color:#ffd9e6; background:#2a0f1f}
|
||||
#toolbar .ok{border-color:#1b5e3b; background:#0f241a; color:#d9ffef;}
|
||||
#toolbar .field{display:flex;align-items:center;gap:.4rem}
|
||||
#status{margin-left:auto; font-size:.9rem; color:var(--muted)}
|
||||
#canvasWrap{position:relative; height:100%;}
|
||||
canvas{position:absolute; inset:0; width:100%; height:100%; touch-action:none; background:#0a0d11; outline:none}
|
||||
#help{
|
||||
position:fixed; right:.8rem; bottom:.8rem; width:360px; max-width:95vw; background:#0e141a; border:1px solid #213041;
|
||||
border-radius:16px; padding:12px 14px; color:#cfe7ff; box-shadow:0 18px 40px rgba(0,0,0,.45); font-size:14px;
|
||||
}
|
||||
#help h3{margin:.2rem 0 .4rem; font-size:16px}
|
||||
#help kbd{background:#111a22; padding:.15rem .35rem; border-radius:6px; border:1px solid #2b3a4b}
|
||||
.pill{padding:.08rem .45rem; background:#13202a; border:1px solid #254257; border-radius:999px; font-size:12px; color:#bfe8ff}
|
||||
.handle{cursor:grab}
|
||||
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<div id="toolbar" role="toolbar" aria-label="Zeichenwerkzeuge">
|
||||
<div class="field"><span class="pill">Tool</span>
|
||||
<button class="btn" id="toolFree" aria-pressed="true" aria-label="Freihand (F)">Freihand</button>
|
||||
<button class="btn" id="toolBezier" aria-pressed="false" aria-label="Bezier (B)">Bezier</button>
|
||||
</div>
|
||||
<div class="field"><span class="pill">Farbe A</span><input id="colA" type="color" value="#62d3a4"/></div>
|
||||
<div class="field"><span class="pill">Farbe B</span><input id="colB" type="color" value="#ffd166"/></div>
|
||||
<div class="field"><span class="pill">Breite</span><input id="width" type="range" min="1" max="48" value="12"/></div>
|
||||
<div class="field"><span class="pill">Stream</span>
|
||||
<input id="wsUrl" type="text" size="22" value="ws://localhost:9980" aria-label="WebSocket URL"/>
|
||||
<button class="btn" id="wsToggle" aria-pressed="false" aria-label="Streaming an/aus (S)">Verbinden</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="btn" id="replayBtn" aria-label="Replay (R)">Replay</button>
|
||||
<button class="btn" id="exportJson">JSON</button>
|
||||
<button class="btn" id="exportSvg">SVG</button>
|
||||
<button class="btn" id="exportPng">PNG</button>
|
||||
<button class="btn danger" id="clearBtn">Clear</button>
|
||||
</div>
|
||||
<div id="status" aria-live="polite">bereit</div>
|
||||
</div>
|
||||
<div id="canvasWrap">
|
||||
<canvas id="cv" role="application" aria-label="Schnippsi Zeichenfläche" tabindex="0"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside id="help" aria-live="polite">
|
||||
<h3>Shortcuts</h3>
|
||||
<div>Hilfe <kbd>?</kbd> / <kbd>H</kbd> • Tool-Bezier <kbd>B</kbd> • Tool-Freihand <kbd>F</kbd> • Stream <kbd>S</kbd></div>
|
||||
<div>Bezier beenden <kbd>Enter</kbd> • Letzten Anker löschen <kbd>Backspace</kbd> • Ecke halten <kbd>Alt</kbd> • Snap <kbd>Shift</kbd></div>
|
||||
<div class="pill">A11y</div> Tab-fokussierbar, ARIA Live-Status, hoher Kontrast, 200% Zoom stabil.
|
||||
</aside>
|
||||
|
||||
<script>
|
||||
(()=>{
|
||||
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
||||
const cv = document.getElementById('cv');
|
||||
const ctx = cv.getContext('2d');
|
||||
const statusEl = document.getElementById('status');
|
||||
const toolFree = document.getElementById('toolFree');
|
||||
const toolBezier = document.getElementById('toolBezier');
|
||||
const colA = document.getElementById('colA');
|
||||
const colB = document.getElementById('colB');
|
||||
const widthIn = document.getElementById('width');
|
||||
const replayBtn = document.getElementById('replayBtn');
|
||||
const exportJson = document.getElementById('exportJson');
|
||||
const exportSvg = document.getElementById('exportSvg');
|
||||
const exportPng = document.getElementById('exportPng');
|
||||
const clearBtn = document.getElementById('clearBtn');
|
||||
const wsToggle = document.getElementById('wsToggle');
|
||||
const wsUrl = document.getElementById('wsUrl');
|
||||
|
||||
let state = {
|
||||
tool: 'free', // 'free' | 'bezier'
|
||||
colorA: colA.value,
|
||||
colorB: colB.value,
|
||||
width: +widthIn.value,
|
||||
strokes: [],
|
||||
current: null,
|
||||
sessionId: ('sess_' + Math.random().toString(16).slice(2)),
|
||||
streaming: false,
|
||||
ws: null,
|
||||
down: false,
|
||||
modifiers: {shift:false, alt:false},
|
||||
bezier: {anchors:[], dragging:null}
|
||||
};
|
||||
|
||||
function resize(){
|
||||
const rect = cv.getBoundingClientRect();
|
||||
cv.width = Math.floor(rect.width * dpr);
|
||||
cv.height = Math.floor(rect.height * dpr);
|
||||
ctx.setTransform(dpr,0,0,dpr,0,0);
|
||||
render();
|
||||
}
|
||||
new ResizeObserver(resize).observe(document.getElementById('canvasWrap'));
|
||||
resize();
|
||||
|
||||
function setStatus(t){ statusEl.textContent = t; }
|
||||
|
||||
// WebSocket
|
||||
function wsConnect(){
|
||||
try{
|
||||
const url = wsUrl.value.trim();
|
||||
state.ws = new WebSocket(url);
|
||||
state.ws.onopen = ()=>{ state.streaming = true; wsToggle.setAttribute('aria-pressed','true'); setStatus('WS verbunden'); send({type:'session', id:state.sessionId, app:'schnippsi-painter', t:performance.now()|0}); };
|
||||
state.ws.onclose = ()=>{ state.streaming = false; wsToggle.setAttribute('aria-pressed','false'); setStatus('WS getrennt'); };
|
||||
state.ws.onerror = ()=>{ setStatus('WS Fehler'); };
|
||||
wsToggle.textContent = 'Trennen';
|
||||
}catch(e){ console.error(e); setStatus('WS Verbindungsfehler'); }
|
||||
}
|
||||
function wsDisconnect(){
|
||||
if(state.ws){ try{ state.ws.close(); }catch(e){} }
|
||||
state.ws = null; state.streaming = false; wsToggle.textContent = 'Verbinden';
|
||||
}
|
||||
function send(obj){
|
||||
if(!state.streaming || !state.ws || state.ws.readyState!==1) return;
|
||||
try{ state.ws.send(JSON.stringify(obj)); }catch(e){ /* ignore */ }
|
||||
}
|
||||
|
||||
// Tools
|
||||
function useTool(t){
|
||||
state.tool = t;
|
||||
toolFree.setAttribute('aria-pressed', t==='free');
|
||||
toolBezier.setAttribute('aria-pressed', t==='bezier');
|
||||
setStatus(t==='free'?'Freihand aktiv':'Bezier aktiv');
|
||||
}
|
||||
toolFree.onclick = ()=>useTool('free');
|
||||
toolBezier.onclick = ()=>useTool('bezier');
|
||||
|
||||
colA.oninput = ()=>{ state.colorA = colA.value; };
|
||||
colB.oninput = ()=>{ state.colorB = colB.value; };
|
||||
widthIn.oninput = ()=>{ state.width = +widthIn.value; };
|
||||
|
||||
wsToggle.onclick = ()=>{
|
||||
if(state.streaming) wsDisconnect();
|
||||
else wsConnect();
|
||||
};
|
||||
|
||||
clearBtn.onclick = ()=>{ state.strokes.length=0; state.bezier.anchors=[]; state.current=null; render(); };
|
||||
|
||||
// Geometry helpers
|
||||
const dist = (a,b)=>Math.hypot(a.x-b.x, a.y-b.y);
|
||||
function lerp(a,b,t){ return a+(b-a)*t; }
|
||||
function cubicPoint(p0,p1,p2,p3,t){
|
||||
const x = Math.pow(1-t,3)*p0.x + 3*Math.pow(1-t,2)*t*p1.x + 3*(1-t)*t*t*p2.x + t*t*t*p3.x;
|
||||
const y = Math.pow(1-t,3)*p0.y + 3*Math.pow(1-t,2)*t*p1.y + 3*(1-t)*t*t*p2.y + t*t*t*p3.y;
|
||||
return {x,y};
|
||||
}
|
||||
|
||||
// Drawing primitives
|
||||
function drawStroke(s){
|
||||
ctx.lineJoin = ctx.lineCap = 'round';
|
||||
ctx.lineWidth = s.width;
|
||||
ctx.strokeStyle = s.colorA;
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.beginPath();
|
||||
if(s.isBezier){
|
||||
const A = s.anchors;
|
||||
if(A.length>0){ ctx.moveTo(A[0].x, A[0].y); }
|
||||
for(let i=1;i<A.length;i++){
|
||||
const a0=A[i-1], a1=A[i];
|
||||
ctx.bezierCurveTo(a0.h2.x, a0.h2.y, a1.h1.x, a1.h1.y, a1.x, a1.y);
|
||||
}
|
||||
ctx.stroke();
|
||||
// Handles (edit mode)
|
||||
if(s._edit){
|
||||
for(const a of A){
|
||||
// anchor
|
||||
ctx.fillStyle = '#cfe7ff';
|
||||
ctx.beginPath(); ctx.arc(a.x,a.y,4,0,Math.PI*2); ctx.fill();
|
||||
// handles
|
||||
ctx.strokeStyle='#2b9ad1'; ctx.lineWidth=1.2;
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(a.h1.x,a.h1.y); ctx.moveTo(a.x,a.y); ctx.lineTo(a.h2.x,a.h2.y); ctx.stroke();
|
||||
ctx.fillStyle='#2b9ad1';
|
||||
ctx.beginPath(); ctx.arc(a.h1.x,a.h1.y,3,0,Math.PI*2); ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(a.h2.x,a.h2.y,3,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
const P = s.points;
|
||||
if(!P || P.length<2) return;
|
||||
ctx.moveTo(P[0].x, P[0].y);
|
||||
for(let i=1;i<P.length;i++){ ctx.lineTo(P[i].x, P[i].y); }
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function render(){
|
||||
ctx.clearRect(0,0,cv.width,dpr?cv.height:cv.height);
|
||||
// backdrop grid (subtle)
|
||||
ctx.save();
|
||||
ctx.strokeStyle='rgba(255,255,255,0.04)';
|
||||
ctx.lineWidth=1;
|
||||
for(let x=0;x<cv.width/dpr;x+=40){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,cv.height/dpr); ctx.stroke(); }
|
||||
for(let y=0;y<cv.height/dpr;y+=40){ ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(cv.width/dpr,y); ctx.stroke(); }
|
||||
ctx.restore();
|
||||
|
||||
for(const s of state.strokes){ drawStroke(s); }
|
||||
if(state.current){ drawStroke(state.current); }
|
||||
}
|
||||
|
||||
// Input
|
||||
let lastEmit = 0;
|
||||
function pointerPos(e){
|
||||
const r = cv.getBoundingClientRect();
|
||||
return { x:(e.clientX - r.left), y:(e.clientY - r.top), t: performance.now()|0, p:e.pressure||0.5, tilt:[e.tiltX||0, e.tiltY||0], type:e.pointerType||'mouse' };
|
||||
}
|
||||
function startStroke(pt){
|
||||
if(state.tool==='free'){
|
||||
state.current = { id:('st_'+Date.now()), tool:'free', colorA:state.colorA, colorB:state.colorB, width:state.width, points:[pt], isBezier:false };
|
||||
send({type:'strokeStart', id:state.current.id, tool:'free', colorA:state.colorA, colorB:state.colorB, width:state.width, t:pt.t});
|
||||
}else{
|
||||
// initialize bezier edit stroke
|
||||
const A = state.bezier.anchors.length? structuredClone(state.bezier.anchors) : [];
|
||||
state.current = { id:('st_'+Date.now()), tool:'bezier', colorA:state.colorA, colorB:state.colorB, width:state.width, anchors:A, isBezier:true, _edit:true };
|
||||
if(A.length===0){
|
||||
// first anchor with mirrored handles
|
||||
const a = {x:pt.x,y:pt.y, h1:{x:pt.x-40, y:pt.y}, h2:{x:pt.x+40, y:pt.y}, split:false, t:pt.t, p:pt.p, tilt:pt.tilt};
|
||||
state.bezier.anchors.push(a);
|
||||
state.current.anchors.push(structuredClone(a));
|
||||
send({type:'strokeStart', id:state.current.id, tool:'bezier', colorA:state.colorA, colorB:state.colorB, width:state.width, t:pt.t});
|
||||
send({type:'anchor', id:state.current.id, a:a});
|
||||
}
|
||||
}
|
||||
}
|
||||
function moveStroke(pt){
|
||||
if(!state.current) return;
|
||||
if(state.tool==='free'){
|
||||
state.current.points.push(pt);
|
||||
const now = performance.now();
|
||||
if(now-lastEmit>33){ lastEmit=now; send({type:'point', id:state.current.id, p:pt}); }
|
||||
}else{
|
||||
// handle dragging logic
|
||||
const cur = state.current;
|
||||
if(!state.bezier.dragging) return;
|
||||
const drag = state.bezier.dragging;
|
||||
if(drag.kind==='anchor'){
|
||||
drag.ref.x = pt.x; drag.ref.y = pt.y;
|
||||
// move handles with anchor
|
||||
drag.ref.h1.x += drag.dx? (pt.x-drag.base.x) : 0;
|
||||
drag.ref.h1.y += drag.dy? (pt.y-drag.base.y) : 0;
|
||||
drag.ref.h2.x += drag.dx? (pt.x-drag.base.x) : 0;
|
||||
drag.ref.h2.y += drag.dy? (pt.y-drag.base.y) : 0;
|
||||
drag.base.x = pt.x; drag.base.y = pt.y;
|
||||
}else if(drag.kind==='h1' || drag.kind==='h2'){
|
||||
drag.ref[drag.kind].x = pt.x; drag.ref[drag.kind].y = pt.y;
|
||||
if(!drag.ref.split){
|
||||
// mirror handle lengths for smooth
|
||||
const ax=drag.ref.x, ay=drag.ref.y;
|
||||
const opp = (drag.kind==='h1')?'h2':'h1';
|
||||
const vx = ax - pt.x, vy = ay - pt.y;
|
||||
drag.ref[opp].x = ax + vx; drag.ref[opp].y = ay + vy;
|
||||
}
|
||||
}
|
||||
cur.anchors = structuredClone(state.bezier.anchors);
|
||||
// emit throttle
|
||||
const now = performance.now();
|
||||
if(now-lastEmit>50){ lastEmit=now; send({type:'anchors', id:cur.id, anchors:cur.anchors}); }
|
||||
}
|
||||
render();
|
||||
}
|
||||
function endStroke(pt){
|
||||
if(!state.current) return;
|
||||
if(state.tool==='free'){
|
||||
state.strokes.push(state.current);
|
||||
send({type:'strokeEnd', id:state.current.id, t:pt.t});
|
||||
state.current=null;
|
||||
}else{
|
||||
// in bezier tool, mouseup only stops dragging
|
||||
state.bezier.dragging=null;
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
// Hit-testing for bezier edit
|
||||
function hitTestAnchors(x,y){
|
||||
const A = state.bezier.anchors;
|
||||
for(let i=A.length-1;i>=0;i--){
|
||||
const a=A[i];
|
||||
if(dist({x,y}, a)<8) return {idx:i, kind:'anchor', ref:a};
|
||||
if(dist({x,y}, a.h1)<7) return {idx:i, kind:'h1', ref:a};
|
||||
if(dist({x,y}, a.h2)<7) return {idx:i, kind:'h2', ref:a};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add anchor with dragging of handle based on movement
|
||||
function addAnchor(pt){
|
||||
const A = state.bezier.anchors;
|
||||
const prev = A[A.length-1];
|
||||
const a = {x:pt.x,y:pt.y, h1:{x:pt.x-40, y:pt.y}, h2:{x:pt.x+40, y:pt.y}, split:false, t:pt.t, p:pt.p, tilt:pt.tilt};
|
||||
if(prev){
|
||||
// make smooth by default: h1 points to previous, h2 mirrored
|
||||
a.h1 = {x: lerp(prev.x, pt.x, .33), y: lerp(prev.y, pt.y, .33)};
|
||||
a.h2 = {x: lerp(pt.x, prev.x, .33), y: lerp(pt.y, prev.y, .33)};
|
||||
}
|
||||
A.push(a);
|
||||
if(state.current){ state.current.anchors = structuredClone(A); }
|
||||
send({type:'anchor', id:state.current?state.current.id:('st_'+Date.now()), a:a});
|
||||
render();
|
||||
}
|
||||
|
||||
// Pointer events
|
||||
cv.addEventListener('pointerdown', (e)=>{
|
||||
cv.setPointerCapture(e.pointerId);
|
||||
const pt = pointerPos(e); state.down=true;
|
||||
if(state.tool==='free'){
|
||||
startStroke(pt);
|
||||
}else{
|
||||
// bezier: check hit on existing handle/anchor first
|
||||
const hit = hitTestAnchors(pt.x,pt.y);
|
||||
if(hit){
|
||||
state.bezier.dragging={...hit, base:{x:pt.x,y:pt.y}, dx:true, dy:true};
|
||||
if(hit.kind!=='anchor' && e.altKey){ hit.ref.split = true; } // allow cusp with Alt while grabbing
|
||||
}else{
|
||||
if(!state.current){ startStroke(pt); }
|
||||
addAnchor(pt);
|
||||
}
|
||||
}
|
||||
render();
|
||||
});
|
||||
cv.addEventListener('pointermove', (e)=>{
|
||||
if(!state.down && state.tool==='bezier'){
|
||||
// hover cursor
|
||||
const pt = pointerPos(e), hit = hitTestAnchors(pt.x,pt.y);
|
||||
cv.style.cursor = hit ? 'grab' : 'crosshair';
|
||||
}
|
||||
if(!state.down) return;
|
||||
moveStroke(pointerPos(e));
|
||||
});
|
||||
cv.addEventListener('pointerup', (e)=>{ state.down=false; endStroke(pointerPos(e)); });
|
||||
cv.addEventListener('pointercancel', (e)=>{ state.down=false; endStroke(pointerPos(e)); });
|
||||
|
||||
// Keyboard
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='Shift') state.modifiers.shift=true;
|
||||
if(e.key==='Alt') state.modifiers.alt=true;
|
||||
if(e.key==='b' || e.key==='B'){ useTool('bezier'); }
|
||||
if(e.key==='f' || e.key==='F'){ useTool('free'); }
|
||||
if(e.key==='s' || e.key==='S'){ state.streaming?wsDisconnect():wsConnect(); }
|
||||
if(e.key==='r' || e.key==='R'){ doReplay(); }
|
||||
if(e.key==='Enter' && state.tool==='bezier'){
|
||||
// finalize bezier stroke into strokes
|
||||
if(state.current){
|
||||
const finalized = {...state.current};
|
||||
finalized._edit=false;
|
||||
state.strokes.push(finalized);
|
||||
send({type:'strokeEnd', id:finalized.id, t:performance.now()|0});
|
||||
state.current=null;
|
||||
state.bezier.anchors = [];
|
||||
render();
|
||||
}
|
||||
}
|
||||
if(e.key==='Backspace' && state.tool==='bezier'){
|
||||
if(state.bezier.anchors.length>0){
|
||||
const a = state.bezier.anchors.pop();
|
||||
send({type:'anchorDel', id:state.current?state.current.id:null, aidx: state.bezier.anchors.length});
|
||||
if(state.current) state.current.anchors = structuredClone(state.bezier.anchors);
|
||||
render();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
if(e.key==='?' || e.key==='h' || e.key==='H'){
|
||||
const el=document.getElementById('help'); el.hidden = !el.hidden;
|
||||
}
|
||||
});
|
||||
window.addEventListener('keyup', (e)=>{
|
||||
if(e.key==='Shift') state.modifiers.shift=false;
|
||||
if(e.key==='Alt') state.modifiers.alt=false;
|
||||
});
|
||||
|
||||
// Export
|
||||
function download(name, dataUrl){
|
||||
const a=document.createElement('a'); a.href=dataUrl; a.download=name; a.click();
|
||||
}
|
||||
exportJson.onclick = ()=>{
|
||||
const payload = {
|
||||
canvas:{w:cv.width/dpr, h:cv.height/dpr, dpr},
|
||||
session:{id:state.sessionId, app:'schnippsi-painter'},
|
||||
strokes: state.strokes
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(payload,null,2)], {type:'application/json'});
|
||||
download(`schnippsi_${Date.now()}.json`, URL.createObjectURL(blob));
|
||||
};
|
||||
exportSvg.onclick = ()=>{
|
||||
const w=cv.width/dpr, h=cv.height/dpr;
|
||||
function pathFromStroke(s){
|
||||
if(s.isBezier){
|
||||
const A=s.anchors; if(A.length<1) return '';
|
||||
let d=`M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=1;i<A.length;i++){
|
||||
const a0=A[i-1], a1=A[i];
|
||||
d+=` C ${a0.h2.x} ${a0.h2.y}, ${a1.h1.x} ${a1.h1.y}, ${a1.x} ${a1.y}`;
|
||||
}
|
||||
return `<path d="${d}" stroke="${s.colorA}" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="${s.width}"/>`;
|
||||
}else{
|
||||
const P=s.points; if(!P || P.length<2) return '';
|
||||
const d='M '+P.map((p,i)=> (i? 'L '+p.x+' '+p.y : p.x+' '+p.y)).join(' ');
|
||||
return `<path d="${d}" stroke="${s.colorA}" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="${s.width}"/>`;
|
||||
}
|
||||
}
|
||||
const body = state.strokes.map(pathFromStroke).join('\n');
|
||||
const svg = `<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">\n${body}\n</svg>`;
|
||||
const blob = new Blob([svg], {type:'image/svg+xml'});
|
||||
download(`schnippsi_${Date.now()}.svg`, URL.createObjectURL(blob));
|
||||
};
|
||||
exportPng.onclick = ()=>{
|
||||
const url = cv.toDataURL('image/png');
|
||||
download(`schnippsi_${Date.now()}.png`, url);
|
||||
};
|
||||
|
||||
// Replay
|
||||
function doReplay(){
|
||||
if(state.strokes.length===0) return;
|
||||
const copy = JSON.parse(JSON.stringify(state.strokes));
|
||||
state.strokes = [];
|
||||
let idx=0;
|
||||
function step(){
|
||||
if(idx>=copy.length) return;
|
||||
state.strokes.push(copy[idx++]); render();
|
||||
setTimeout(step, 300);
|
||||
}
|
||||
step();
|
||||
}
|
||||
replayBtn.onclick = doReplay;
|
||||
|
||||
// Initial tip
|
||||
setStatus('Bezier: Klicken für Anker, ziehen für Griffe. Enter = fertig. Alt zum Brechen, Shift für Snap.');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
366
crumbblocks/crumbblocks.html
Normal file
366
crumbblocks/crumbblocks.html
Normal file
@@ -0,0 +1,366 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Crumbblocks – Wasserkocher (Demo)</title>
|
||||
<!-- Feste Blockly-Version (verhindert textToDom-Fehler) -->
|
||||
<script src="https://unpkg.com/blockly@9.3.3/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly@9.3.3/msg/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b1016;
|
||||
--panel: #0e141b;
|
||||
--ink: #e6edf3;
|
||||
--muted: #93a1b3;
|
||||
--accent: #4caf50;
|
||||
--border: #1f2a37;
|
||||
--console: #0a0f14;
|
||||
--console-ink: #b7ffb2;
|
||||
}
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
.wrap {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr auto;
|
||||
gap: 12px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
header h1 { font-size: 20px; margin: 0; }
|
||||
.controls {
|
||||
display: flex; gap: 8px; flex-wrap: wrap;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px; padding: 10px;
|
||||
}
|
||||
button {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.secondary { background: #374151; }
|
||||
button:active { transform: translateY(1px); }
|
||||
.stage {
|
||||
display: grid; grid-template-rows: 64vh 28vh; gap: 12px;
|
||||
}
|
||||
#blocklyDiv {
|
||||
width: 100%;
|
||||
height: 64vh;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#output {
|
||||
width: 100%;
|
||||
height: 28vh;
|
||||
background: var(--console);
|
||||
color: var(--console-ink);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.hint {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<h1>🧁 Crumbblocks – Virtueller Wasserkocher (Kids-Demo)</h1>
|
||||
<div class="hint">
|
||||
Variablen: <code>power_w</code>, <code>wasser_ml</code>, <code>sensor_temp_c</code>, <code>zeit_s</code>,
|
||||
<code>energie_wh</code>, <code>verbrauch_wh</code>. Ziel: Bis 100 °C erhitzen, Energie/Zeit mitrechnen.
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="loadDemo()">🎬 Demo laden</button>
|
||||
<button onclick="runCode()">▶️ Ausführen</button>
|
||||
<button class="secondary" onclick="clearOutput()">🧹 Leeren</button>
|
||||
<span class="hint">Sendet per <code>POST /crumbapi/blockly-terminal</code> (Fallback: Echo).</span>
|
||||
</div>
|
||||
|
||||
<div class="stage">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Terminal wartet …</pre>
|
||||
</div>
|
||||
|
||||
<!-- TOOLBOX (Standardkategorien + Variablen/Funktionen) -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Logik" categorystyle="logic_category">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
</category>
|
||||
<category name="Schleifen" categorystyle="loop_category">
|
||||
<block type="controls_repeat_ext"><value name="TIMES"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="controls_whileUntil"><field name="MODE">WHILE</field></block>
|
||||
<block type="controls_flow_statements"></block>
|
||||
</category>
|
||||
<category name="Mathe" categorystyle="math_category">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_round"></block>
|
||||
</category>
|
||||
<category name="Text" categorystyle="text_category">
|
||||
<block type="text"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
<sep></sep>
|
||||
<category name="Variablen" categorystyle="variable_category" custom="VARIABLE_DYNAMIC"></category>
|
||||
<category name="Funktionen" categorystyle="procedure_category" custom="PROCEDURE"></category>
|
||||
</xml>
|
||||
|
||||
<!-- DEMO-WORKSPACE (Wasserkocher) -->
|
||||
<script id="demo-xml" type="text/plain">
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>sensor_temp_c</variable>
|
||||
<variable>power_w</variable>
|
||||
<variable>wasser_ml</variable>
|
||||
<variable>zeit_s</variable>
|
||||
<variable>energie_wh</variable>
|
||||
<variable>verbrauch_wh</variable>
|
||||
</variables>
|
||||
|
||||
<!-- Startwerte -->
|
||||
<block type="variables_set" x="24" y="24">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">power_w</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">wasser_ml</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">250</field></block></value>
|
||||
<next>
|
||||
<!-- Sicherheitslimit Wasser -->
|
||||
<block type="controls_if">
|
||||
<value name="IF0">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">wasser_ml</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">wasser_ml</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">verbrauch_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<!-- while (sensor_temp_c < 100) -->
|
||||
<block type="controls_whileUntil">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<!-- Abbruch wenn kein Strom -->
|
||||
<block type="controls_if">
|
||||
<value name="IF0">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LTE</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">power_w</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO0">
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Kein Strom → STOP</field></block></value>
|
||||
<next>
|
||||
<block type="controls_flow_statements"><field name="FLOW">BREAK</field></block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<!-- Heize-Schritt -->
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Heize…</field></block></value>
|
||||
<next>
|
||||
<!-- sensor_temp_c += 5 -->
|
||||
<block type="variables_set">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<!-- zeit_s += 5 -->
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">zeit_s</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<!-- energie_wh += power_w * 5 / 3600 -->
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||||
<value name="B">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">DIVIDE</field>
|
||||
<value name="A">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">power_w</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">3600</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">verbrauch_wh</field>
|
||||
<value name="VALUE"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Fertig. STOP.</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</xml>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Workspace
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'geras',
|
||||
grid: { spacing: 24, length: 3, colour: '#223045', snap: true },
|
||||
zoom: { controls: true, wheel: true, startScale: 1.05, maxScale: 2.0, minScale: 0.5, pinch: true },
|
||||
trashcan: true
|
||||
});
|
||||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||||
|
||||
function out(txt, replace=false) {
|
||||
const el = document.getElementById('output');
|
||||
el.textContent = replace ? String(txt) : (el.textContent + '\n' + String(txt));
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
function clearOutput() { document.getElementById('output').textContent = '🌲 Terminal wartet …'; }
|
||||
|
||||
function loadDemo() {
|
||||
try {
|
||||
const xmlTxt = document.getElementById('demo-xml').textContent.trim();
|
||||
const dom = (Blockly.Xml && Blockly.Xml.textToDom)
|
||||
? Blockly.Xml.textToDom(xmlTxt)
|
||||
: Blockly.utils.xml.textToDom(xmlTxt);
|
||||
workspace.clear();
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
out('✅ Demo geladen.', true);
|
||||
} catch (e) {
|
||||
out('❌ Demo konnte nicht geladen werden: ' + e, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function runCode() {
|
||||
const code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
out('📤 Sende an Terminal…\n\n' + code, true);
|
||||
|
||||
// Variablenliste (für spätere Auswertung im Backend hilfreich)
|
||||
const knownVars = ['sensor_temp_c','power_w','wasser_ml','zeit_s','energie_wh','verbrauch_wh'];
|
||||
|
||||
try {
|
||||
const res = await fetch('/crumbapi/blockly-terminal', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode: 'blockly', blockcode: code, vars: knownVars })
|
||||
});
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
const data = await res.json();
|
||||
out('\n✅ Antwort:\n' + JSON.stringify(data, null, 2));
|
||||
} catch (err) {
|
||||
// Fallback: Echo-JSON lokal anzeigen
|
||||
const echo = {
|
||||
ok: true,
|
||||
mode: 'echo-fallback',
|
||||
request: { mode: 'blockly', blockcode: code, vars: knownVars }
|
||||
};
|
||||
out('\nℹ️ Endpoint nicht bereit. Fallback aktiv.\n\n' + JSON.stringify(echo, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Direkt beim Laden einmal die Demo hineinlegen:
|
||||
loadDemo();
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
298
crumbblocks/index.html
Normal file
298
crumbblocks/index.html
Normal file
@@ -0,0 +1,298 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Blockly to Crumbforest Terminal Bridge</title>
|
||||
<!-- Blockly + JS Generator + Deutsche Labels -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #1e1e1e;
|
||||
--panel: #111;
|
||||
--text: #e8e8e8;
|
||||
--ok: #a6e22e;
|
||||
--btn: #4caf50;
|
||||
}
|
||||
html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font-family: monospace; }
|
||||
header { display:flex; gap:.5rem; align-items:center; padding:.5rem .75rem; }
|
||||
header h2 { margin:0; font-weight:600; }
|
||||
header .spacer { flex:1; }
|
||||
button {
|
||||
padding:.6rem .9rem; background: var(--btn); color:#fff; border:none; border-radius:8px; font-weight:700; cursor:pointer;
|
||||
}
|
||||
#wrap { display:flex; flex-direction:column; height: calc(100vh - 56px); }
|
||||
#blocklyDiv { height: 64vh; width: 100%; }
|
||||
#bottom { display:flex; gap:.75rem; padding:.5rem .75rem; }
|
||||
#output {
|
||||
flex:1; height: 32vh; background:var(--panel); color:var(--ok);
|
||||
padding:10px; overflow:auto; white-space: pre-wrap; word-break: break-word; border-radius:8px;
|
||||
}
|
||||
#controls { display:flex; flex-direction:column; gap:.5rem; }
|
||||
.muted { opacity:.7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Blockly Crumbforest Bridge</h2>
|
||||
<div class="spacer"></div>
|
||||
<button id="btnDemo" title="Mini-Demo laden">🧪 Demo</button>
|
||||
<button id="btnClear" title="Workspace leeren">🗑️ Neu</button>
|
||||
<button id="btnZoomIn" title="Zoom +">🔍+</button>
|
||||
<button id="btnZoomOut" title="Zoom −">🔍-</button>
|
||||
<button id="btnRun" title="Code ausführen / an Terminal senden">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="blocklyDiv"></div>
|
||||
<div id="bottom">
|
||||
<div id="controls">
|
||||
<div class="muted">Variablen: energie_wh, verbrauch_wh, sensor_temp_c, power_w, wasser_ml, zeit_s</div>
|
||||
<div class="muted">Endpoint: <code>/crumbapi/blockly-terminal</code> (Echo-Fallback aktiv)</div>
|
||||
</div>
|
||||
<pre id="output">🌲 Terminal wartet ...</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox: wie bei dir, nur Loops aktiviert -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<block type="text"></block>
|
||||
<block type="text_print"></block>
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="variables_set"></block>
|
||||
<block type="variables_get"></block>
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="procedures_defnoreturn"></block>
|
||||
<block type="procedures_callnoreturn"></block>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing: 24, length: 3, colour: '#484848', snap: true},
|
||||
trashcan: true,
|
||||
zoom: {startScale: 1.15, maxScale: 2.0, minScale: 0.6, controls: false, wheel: true},
|
||||
move: {scrollbars: true, drag: true, wheel: true}
|
||||
});
|
||||
|
||||
// Vordefinierte Variablen für euren „Prozess“-Wortschatz
|
||||
['energie_wh','verbrauch_wh','sensor_temp_c','power_w','wasser_ml','zeit_s']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(e){} });
|
||||
|
||||
// Helpers
|
||||
const $ = sel => document.querySelector(sel);
|
||||
function setOutput(txt){ $('#output').textContent = txt; }
|
||||
function appendOutput(txt){
|
||||
const el = $('#output');
|
||||
el.textContent += (el.textContent.endsWith('\n')?'':'\n') + txt;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
|
||||
// Initial-Variablen aus „set“-Blöcken sammeln (für JSON-Vorschau)
|
||||
function collectInitialVars() {
|
||||
const vals = {};
|
||||
const blocks = workspace.getAllBlocks(false);
|
||||
for (const b of blocks) {
|
||||
if (b.type === 'variables_set') {
|
||||
const name = b.getFieldValue('VAR');
|
||||
// Versuche simples Literal zu lesen (Zahl/Text)
|
||||
const input = b.getInputTargetBlock('VALUE');
|
||||
if (input) {
|
||||
if (input.type === 'math_number') {
|
||||
vals[name] = Number(input.getFieldValue('NUM'));
|
||||
} else if (input.type === 'text') {
|
||||
vals[name] = input.getFieldValue('TEXT') ?? '';
|
||||
} else {
|
||||
// Irgendwas anderes verschachtelt → nur markieren
|
||||
vals[name] = '<expr>';
|
||||
}
|
||||
} else {
|
||||
vals[name] = '<unset>';
|
||||
}
|
||||
}
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
// Kompatibles XML-Parserchen (fix für „Blockly.Xml.textToDom is not a function“)
|
||||
function textToDom(xmlText){
|
||||
if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText);
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
|
||||
// Aktionen
|
||||
$('#btnZoomIn').onclick = ()=> Blockly.svgResize(workspace), workspace.zoomCenter(1);
|
||||
$('#btnZoomOut').onclick = ()=> Blockly.svgResize(workspace), workspace.zoomCenter(-1);
|
||||
|
||||
$('#btnClear').onclick = () => {
|
||||
workspace.clear();
|
||||
setOutput('🧹 Workspace geleert.');
|
||||
};
|
||||
|
||||
$('#btnDemo').onclick = () => {
|
||||
const demoXML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>sensor_temp_c</variable>
|
||||
<variable>power_w</variable>
|
||||
<variable>wasser_ml</variable>
|
||||
<variable>zeit_s</variable>
|
||||
<variable>energie_wh</variable>
|
||||
<variable>verbrauch_wh</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="24" y="22">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">power_w</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">wasser_ml</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">250</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">verbrauch_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="controls_whileUntil">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<block type="text">
|
||||
<field name="TEXT">Heize...</field>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">zeit_s</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">2.8</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<block type="text">
|
||||
<field name="TEXT">Fertig. STOP.</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</xml>`;
|
||||
try {
|
||||
const dom = textToDom(demoXML);
|
||||
workspace.clear();
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
setOutput('✅ Demo geladen. Passe die Logik an und drücke ▶️ Ausführen.');
|
||||
} catch (e) {
|
||||
setOutput('❌ Demo konnte nicht geladen werden:\n' + e);
|
||||
}
|
||||
};
|
||||
|
||||
$('#btnRun').onclick = async () => {
|
||||
const code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const initVars = collectInitialVars();
|
||||
|
||||
setOutput("📤 Sende an Terminal...\n\n" + code);
|
||||
|
||||
const payload = {
|
||||
mode: "blockly",
|
||||
blockcode: code,
|
||||
preview_vars: initVars
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch("/crumbapi/blockly-terminal", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
let data;
|
||||
if (res.ok) {
|
||||
data = await res.json().catch(()=>({}));
|
||||
appendOutput("\n\n✅ Antwort:\n" + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
appendOutput("\n\nℹ️ Endpoint nicht bereit (HTTP " + res.status + "). Fallback aktiv.");
|
||||
appendOutput("\n\n✅ Antwort (Fallback):\n" + JSON.stringify({ ok:true, mode:"echo-fallback", request:payload }, null, 2));
|
||||
}
|
||||
} catch (err) {
|
||||
appendOutput("\n\n❌ Netzwerkfehler: " + err);
|
||||
appendOutput("\n\n✅ Antwort (Fallback):\n" + JSON.stringify({ ok:true, mode:"echo-fallback", request:payload }, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
// Resizing
|
||||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
278
crumbblocks/index_v1.html
Normal file
278
crumbblocks/index_v1.html
Normal file
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Crumbblocks – Blockly ↔ Crumbforest</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly + Python-Generator + Deutsch -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/python.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0d0f12;--panel:#12151b;--ink:#e9edf5;--muted:#b7c0d0;--accent:#4caf50;--danger:#e74c3c;
|
||||
--mono:ui-monospace, Menlo, Monaco, "Courier New", monospace;
|
||||
}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--ink);font-family:var(--mono)}
|
||||
header{position:sticky;top:0;z-index:5;display:flex;gap:.6rem;align-items:center;padding:.6rem 1rem;background:#0b0d10cc;border-bottom:1px solid #1b212c}
|
||||
header h2{margin:0;font-size:1rem;color:var(--muted)}
|
||||
.spacer{flex:1 1 auto}
|
||||
.btn{border:1px solid #263041;background:#161b22;color:var(--ink);border-radius:8px;padding:.5rem .75rem;cursor:pointer;font-weight:700}
|
||||
.btn:hover{filter:brightness(1.1)}
|
||||
.btn.primary{background:var(--accent);color:#0b130c;border-color:#2b6b2e}
|
||||
.btn.danger{background:var(--danger);border-color:#b13a2e}
|
||||
.btn.ghost{background:transparent}
|
||||
#wrap{display:flex;flex-direction:column;gap:.6rem;height:calc(100% - 54px);padding:.6rem 1rem 1rem}
|
||||
#blocklyDiv{height:62vh;width:100%;border-radius:10px;overflow:hidden;background:#0f1319;border:1px solid #1b212c}
|
||||
#console{display:grid;grid-template-rows:auto 1fr;gap:.4rem;height:28vh;background:var(--panel);border:1px solid #1b212c;border-radius:10px}
|
||||
#consoleHead{display:flex;align-items:center;gap:.6rem;padding:.5rem .7rem;border-bottom:1px solid #1b212c}
|
||||
#statusDot{width:.6rem;height:.6rem;border-radius:50%;background:#6a6f79;display:inline-block}
|
||||
#statusText{color:var(--muted);font-size:.9rem}
|
||||
#output{margin:0;padding:.6rem .8rem;overflow:auto;line-height:1.35;color:#a6ffad;background:#0c0f13;border-radius:0 0 10px 10px;white-space:pre-wrap;word-break:break-word;font-size:.93rem}
|
||||
@media (max-width:960px){#blocklyDiv{height:56vh}#console{height:32vh}}
|
||||
.blocklyMainBackground{fill:#0f1319 !important}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbblocks</h2>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn" id="btnDemo">Demo laden</button>
|
||||
<button class="btn ghost" id="btnZoomOut">–</button>
|
||||
<button class="btn ghost" id="btnZoomIn">+</button>
|
||||
<button class="btn danger" id="btnTrash">Mülleimer</button>
|
||||
<button class="btn primary" id="btnRun">Ausführen → Terminal</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="blocklyDiv" aria-label="Blockly Arbeitsfläche"></div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Logik" colour="#66b1ff">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
</category>
|
||||
<category name="Schleifen" colour="#6cdf6c">
|
||||
<block type="controls_repeat_ext">
|
||||
<value name="TIMES"><shadow type="math_number"><field name="NUM">10</field></shadow></value>
|
||||
</block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
</category>
|
||||
<category name="Mathe" colour="#b68cff">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
</category>
|
||||
<category name="Text" colour="#ffd866">
|
||||
<block type="text"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
<sep gap="8"></sep>
|
||||
<category name="Variablen" custom="VARIABLE" colour="#ff75b5"></category>
|
||||
<category name="Funktionen" custom="PROCEDURE" colour="#e056fd"></category>
|
||||
</xml>
|
||||
|
||||
<section id="console" aria-label="Ausgabe">
|
||||
<div id="consoleHead">
|
||||
<span id="statusDot" title="Status"></span>
|
||||
<strong>Konsole</strong>
|
||||
<span id="statusText">bereit</span>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn ghost" id="btnClear">Leeren</button>
|
||||
<button class="btn ghost" id="btnToggle">Ein-/Ausklappen</button>
|
||||
</div>
|
||||
<pre id="output">🌲 Terminal wartet …</pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// eigenes Dark-Theme
|
||||
const CrumbDark = Blockly.Theme.defineTheme('CrumbDark', {
|
||||
base: Blockly.Themes.Dark,
|
||||
componentStyles: {
|
||||
workspaceBackgroundColour: '#0f1319',
|
||||
toolboxBackgroundColour: '#0e1217',
|
||||
toolboxForegroundColour: '#e9edf5',
|
||||
flyoutBackgroundColour: '#0f1319',
|
||||
flyoutForegroundColour: '#e9edf5',
|
||||
scrollbarColour: '#2a3240',
|
||||
insertionMarkerColour: '#4caf50',
|
||||
insertionMarkerOpacity: 0.35,
|
||||
cursorColour: '#4caf50',
|
||||
}
|
||||
});
|
||||
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: CrumbDark,
|
||||
renderer: 'zelos',
|
||||
trashcan: true,
|
||||
grid: { spacing: 28, length: 2, colour: '#1a2029', snap: true },
|
||||
move: { scrollbars: true, drag: true, wheel: true },
|
||||
zoom: { controls: false, wheel: true, startScale: 1.15, maxScale: 2.2, minScale: 0.7, scaleSpeed: 1.2 },
|
||||
});
|
||||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||||
|
||||
// VORDEFINIERTE VARIABLEN für die Kids
|
||||
const PRESET_VARS = [
|
||||
'energie_wh','verbrauch_wh','sensor_temp_c','power_w','wasser_ml','zeit_s'
|
||||
];
|
||||
for (const name of PRESET_VARS) {
|
||||
try { workspace.createVariable(name); } catch(_) {}
|
||||
}
|
||||
|
||||
// UI helpers
|
||||
const outputEl = document.getElementById('output');
|
||||
const statusDot = document.getElementById('statusDot');
|
||||
const statusText = document.getElementById('statusText');
|
||||
const consoleBox = document.getElementById('console');
|
||||
function setStatus(color, text){ statusDot.style.background=color; statusText.textContent=text||''; }
|
||||
function log(line){ outputEl.textContent += (outputEl.textContent.endsWith('\n')?'':'\n')+ line; outputEl.scrollTop = outputEl.scrollHeight; }
|
||||
function resetOutput(msg='🌲 Terminal wartet …'){ outputEl.textContent = msg; }
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnZoomIn').onclick = () => workspace.zoomCenter(1);
|
||||
document.getElementById('btnZoomOut').onclick = () => workspace.zoomCenter(-1);
|
||||
document.getElementById('btnTrash').onclick = () => workspace.clear();
|
||||
document.getElementById('btnClear').onclick = () => resetOutput('— leere Ausgabe —');
|
||||
let collapsed=false;
|
||||
document.getElementById('btnToggle').onclick = () => { collapsed=!collapsed; consoleBox.style.display = collapsed?'none':'grid'; };
|
||||
|
||||
// Demo-Workspace (Wasserkocher) – via DOMParser, ohne Blockly.Xml.textToDom
|
||||
const DEMO_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>energie_wh</variable>
|
||||
<variable>verbrauch_wh</variable>
|
||||
<variable>sensor_temp_c</variable>
|
||||
<variable>power_w</variable>
|
||||
<variable>wasser_ml</variable>
|
||||
<variable>zeit_s</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="20" y="20">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">power_w</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">800</field></block></value>
|
||||
<next>
|
||||
<block type="controls_whileUntil">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">10</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<block type="text"><field name="TEXT">Erhitze… Temp=</field></block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Wasser kocht! 🔥</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</xml>`.trim();
|
||||
|
||||
function loadDemo(){
|
||||
try{
|
||||
workspace.clear();
|
||||
const dom = new DOMParser().parseFromString(DEMO_XML, 'text/xml').documentElement;
|
||||
// domToWorkspace ist in allen Builds vorhanden
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
Blockly.svgResize(workspace);
|
||||
setStatus('#4caf50','Demo geladen');
|
||||
resetOutput('🧪 Demo geladen – „Wasserkocher“ bereit.');
|
||||
}catch(e){
|
||||
setStatus('#e74c3c','Demo-Fehler');
|
||||
resetOutput('❌ Demo konnte nicht geladen werden:\n' + (e?.message||e));
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
document.getElementById('btnDemo').onclick = loadDemo;
|
||||
|
||||
// Run nur aktiv wenn es Blöcke gibt
|
||||
const btnRun = document.getElementById('btnRun');
|
||||
function updateRunDisabled(){
|
||||
const disabled = workspace.getAllBlocks(false).length===0;
|
||||
btnRun.disabled = disabled;
|
||||
btnRun.style.opacity = disabled ? .55 : 1;
|
||||
btnRun.style.cursor = disabled ? 'not-allowed' : 'pointer';
|
||||
}
|
||||
workspace.addChangeListener(updateRunDisabled);
|
||||
updateRunDisabled();
|
||||
|
||||
// Ausführen → Bridge (mit Echo-Fallback)
|
||||
async function runCode(){
|
||||
const code = Blockly.Python.workspaceToCode(workspace);
|
||||
resetOutput('📤 Sende an Terminal…\n\n' + code);
|
||||
setStatus('#f1c40f','sende…');
|
||||
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(()=>ctrl.abort(), 10000);
|
||||
|
||||
async function post(url){
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ blockcode: code }),
|
||||
signal: ctrl.signal
|
||||
});
|
||||
clearTimeout(timer);
|
||||
if(!res.ok) throw new Error('HTTP '+res.status+' '+(await res.text()).slice(0,400));
|
||||
return res.json().catch(()=>({}));
|
||||
}
|
||||
|
||||
try{
|
||||
let data;
|
||||
try{
|
||||
// deine echte Bridge
|
||||
data = await post('/crumbapi/blockly-terminal');
|
||||
}catch(_){
|
||||
// Fallback-Echo (damit Kids sofort Feedback sehen)
|
||||
data = { ok:true, mode:'echo-fallback', code:code };
|
||||
}
|
||||
setStatus('#4caf50','fertig');
|
||||
log('\n✅ Antwort:\n' + JSON.stringify(data, null, 2));
|
||||
}catch(err){
|
||||
setStatus('#e74c3c','Fehler');
|
||||
log('\n❌ Fehler beim Senden:\n' + (err?.message||err));
|
||||
}
|
||||
}
|
||||
btnRun.addEventListener('click', runCode);
|
||||
|
||||
// Startzustand: Demo direkt laden
|
||||
loadDemo();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2740
crumbblocks/lib/blockly/blockly.min.js
vendored
Normal file
2740
crumbblocks/lib/blockly/blockly.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
193
crumbblocks/lib/blockly/blocks_compressed.js
Normal file
193
crumbblocks/lib/blockly/blocks_compressed.js
Normal file
@@ -0,0 +1,193 @@
|
||||
// Do not edit this file; automatically generated.
|
||||
|
||||
/* eslint-disable */
|
||||
;(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) { // AMD
|
||||
define(["./blockly_compressed.js"], factory);
|
||||
} else if (typeof exports === 'object') { // Node.js
|
||||
module.exports = factory(require("./blockly_compressed.js"));
|
||||
} else { // Script
|
||||
root.Blockly.libraryBlocks = factory(root.Blockly);
|
||||
}
|
||||
}(this, function(__parent__) {
|
||||
var $=__parent__.__namespace__;
|
||||
var blocks$$module$build$src$blocks$variables_dynamic=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"variables_get_dynamic",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_dynamic_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableDynamicSetterGetter"]},{type:"variables_set_dynamic",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",
|
||||
name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_dynamic_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableDynamicSetterGetter"]}]),CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables_dynamic={customContextMenu:function(a){if(!this.isInFlyout){if(this.type==="variables_get_dynamic"){var b="variables_set_dynamic";
|
||||
var c=$.Msg$$module$build$src$core$msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",c=$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET;var d=this.getField("VAR");b={type:b,fields:{VAR:d.saveState(!0)}};a.push({enabled:this.workspace.remainingCapacity()>0,text:c.replace("%1",d.getText()),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,b)})}else if(this.type==="variables_get_dynamic"||this.type==="variables_get_reporter_dynamic")c={text:$.Msg$$module$build$src$core$msg.RENAME_VARIABLE,
|
||||
enabled:!0,callback:renameOptionCallbackFactory$$module$build$src$blocks$variables_dynamic(this)},d=this.getField("VAR").getText(),d={text:$.Msg$$module$build$src$core$msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:deleteOptionCallbackFactory$$module$build$src$blocks$variables_dynamic(this)},a.unshift(c),a.unshift(d)},onchange:function(a){a=this.getFieldValue("VAR");a=$.getVariable$$module$build$src$core$variables(this.workspace,a);this.type==="variables_get_dynamic"?this.outputConnection.setCheck(a.getType()):
|
||||
this.getInput("VALUE").connection.setCheck(a.getType())}},renameOptionCallbackFactory$$module$build$src$blocks$variables_dynamic=function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();$.renameVariable$$module$build$src$core$variables(b,c)}},deleteOptionCallbackFactory$$module$build$src$blocks$variables_dynamic=function(a){return function(){const b=a.getField("VAR").getVariable();b&&$.deleteVariable$$module$build$src$core$variables(b.getWorkspace(),b,a)}};
|
||||
$.registerMixin$$module$build$src$core$extensions("contextMenu_variableDynamicSetterGetter",CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables_dynamic);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$variables_dynamic);var module$build$src$blocks$variables_dynamic={blocks:blocks$$module$build$src$blocks$variables_dynamic};var blocks$$module$build$src$blocks$variables=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},
|
||||
{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]),CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables={customContextMenu:function(a){if(!this.isInFlyout){if(this.type==="variables_get"){var b="variables_set";var c=$.Msg$$module$build$src$core$msg.VARIABLES_GET_CREATE_SET}else b="variables_get",
|
||||
c=$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET;var d=this.getField("VAR");b={type:b,fields:{VAR:d.saveState(!0)}};a.push({enabled:this.workspace.remainingCapacity()>0,text:c.replace("%1",d.getText()),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,b)})}else if(this.type==="variables_get"||this.type==="variables_get_reporter")c={text:$.Msg$$module$build$src$core$msg.RENAME_VARIABLE,enabled:!0,callback:renameOptionCallbackFactory$$module$build$src$blocks$variables(this)},
|
||||
d=this.getField("VAR").getText(),d={text:$.Msg$$module$build$src$core$msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:deleteOptionCallbackFactory$$module$build$src$blocks$variables(this)},a.unshift(c),a.unshift(d)}},renameOptionCallbackFactory$$module$build$src$blocks$variables=function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();$.renameVariable$$module$build$src$core$variables(b,c)}},deleteOptionCallbackFactory$$module$build$src$blocks$variables=function(a){return function(){const b=
|
||||
a.getField("VAR").getVariable();b&&$.deleteVariable$$module$build$src$core$variables(b.getWorkspace(),b,a)}};$.registerMixin$$module$build$src$core$extensions("contextMenu_variableSetterGetter",CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$variables);var module$build$src$blocks$variables={blocks:blocks$$module$build$src$blocks$variables};var blocks$$module$build$src$blocks$text=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]},{type:"text_join",message0:"",output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_JOIN_HELPURL}",tooltip:"%{BKY_TEXT_JOIN_TOOLTIP}",mutator:"text_join_mutator"},
|
||||
{type:"text_create_join_container",message0:"%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",args0:[{type:"input_dummy"},{type:"input_statement",name:"STACK"}],style:"text_blocks",tooltip:"%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",enableContextMenu:!1},{type:"text_create_join_item",message0:"%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:"%{BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",enableContextMenu:!1},{type:"text_append",message0:"%{BKY_TEXT_APPEND_TITLE}",
|
||||
args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_TEXT_APPEND_VARIABLE}"},{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",extensions:["text_append_tooltip"]},{type:"text_length",message0:"%{BKY_TEXT_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"text_blocks",tooltip:"%{BKY_TEXT_LENGTH_TOOLTIP}",helpUrl:"%{BKY_TEXT_LENGTH_HELPURL}"},{type:"text_isEmpty",message0:"%{BKY_TEXT_ISEMPTY_TITLE}",
|
||||
args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"text_blocks",tooltip:"%{BKY_TEXT_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_TEXT_ISEMPTY_HELPURL}"},{type:"text_indexOf",message0:"%{BKY_TEXT_INDEXOF_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"END",options:[["%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}","FIRST"],["%{BKY_TEXT_INDEXOF_OPERATOR_LAST}","LAST"]]},{type:"input_value",name:"FIND",check:"String"}],output:"Number",style:"text_blocks",
|
||||
helpUrl:"%{BKY_TEXT_INDEXOF_HELPURL}",inputsInline:!0,extensions:["text_indexOf_tooltip"]},{type:"text_charAt",message0:"%{BKY_TEXT_CHARAT_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"WHERE",options:[["%{BKY_TEXT_CHARAT_FROM_START}","FROM_START"],["%{BKY_TEXT_CHARAT_FROM_END}","FROM_END"],["%{BKY_TEXT_CHARAT_FIRST}","FIRST"],["%{BKY_TEXT_CHARAT_LAST}","LAST"],["%{BKY_TEXT_CHARAT_RANDOM}","RANDOM"]]}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_CHARAT_HELPURL}",
|
||||
inputsInline:!0,mutator:"text_charAt_mutator"}]),GET_SUBSTRING_BLOCK$$module$build$src$blocks$text={init:function(){this.WHERE_OPTIONS_1=[[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_END_FROM_END,
|
||||
"FROM_END"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_HELPURL);this.setStyle("text_blocks");this.appendValueInput("STRING").setCheck("String").appendField($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);const a=b=>{const c=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this["WHERE_OPTIONS_"+b]});c.setValidator(function(d){const e=this.getValue();d=d===
|
||||
"FROM_START"||d==="FROM_END";d!==(e==="FROM_START"||e==="FROM_END")&&this.getSourceBlock().updateAt_(b,d)});return c};this.appendDummyInput("WHERE1_INPUT").appendField(a(1),"WHERE1");this.appendDummyInput("AT1");this.appendDummyInput("WHERE2_INPUT").appendField(a(2),"WHERE2");this.appendDummyInput("AT2");$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL);this.setInputsInline(!0);this.setOutput(!0,
|
||||
"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");var b=this.getInput("AT1")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at1",`${b}`);b=this.getInput("AT2")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at2",`${b}`);return a},domToMutation:function(a){const b=
|
||||
a.getAttribute("at1")==="true";a=a.getAttribute("at2")==="true";this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);a===2&&$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",
|
||||
!0),this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL));a===1&&(this.moveInputBefore("AT1","WHERE2_INPUT"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","WHERE2_INPUT"))}};blocks$$module$build$src$blocks$text.text_getSubstring=GET_SUBSTRING_BLOCK$$module$build$src$blocks$text;
|
||||
blocks$$module$build$src$blocks$text.text_changeCase={init:function(){const a=[[$.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[$.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[$.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField($.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",
|
||||
options:a}),"CASE");this.setOutput(!0,"String");this.setTooltip($.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_TOOLTIP)}};
|
||||
blocks$$module$build$src$blocks$text.text_trim={init:function(){const a=[[$.Msg$$module$build$src$core$msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[$.Msg$$module$build$src$core$msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[$.Msg$$module$build$src$core$msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_TRIM_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField($.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a}),
|
||||
"MODE");this.setOutput(!0,"String");this.setTooltip($.Msg$$module$build$src$core$msg.TEXT_TRIM_TOOLTIP)}};blocks$$module$build$src$blocks$text.text_print={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_PRINT_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_PRINT_HELPURL})}};
|
||||
var PROMPT_COMMON$$module$build$src$blocks$text={updateType_:function(a){this.outputConnection.setCheck(a==="NUMBER"?"Number":"String")},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("type",this.getFieldValue("TYPE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("type"))}};
|
||||
blocks$$module$build$src$blocks$text.text_prompt_ext=Object.assign({},PROMPT_COMMON$$module$build$src$blocks$text,{init:function(){var a=[[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});a.setValidator(b=>{this.updateType_(b)});this.appendValueInput("TEXT").appendField(a,
|
||||
"TYPE");this.setOutput(!0,"String");this.setTooltip(()=>this.getFieldValue("TYPE")==="TEXT"?$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_TEXT:$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_NUMBER)}});
|
||||
blocks$$module$build$src$blocks$text.text_prompt=Object.assign({},PROMPT_COMMON$$module$build$src$blocks$text,{init:function(){this.mixin(QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);var a=[[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});
|
||||
a.setValidator(b=>{this.updateType_(b)});this.appendDummyInput().appendField(a,"TYPE").appendField(this.newQuote_(!0)).appendField($.fromJson$$module$build$src$core$field_registry({type:"field_input",text:""}),"TEXT").appendField(this.newQuote_(!1));this.setOutput(!0,"String");this.setTooltip(()=>this.getFieldValue("TYPE")==="TEXT"?$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_TEXT:$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_NUMBER)}});
|
||||
blocks$$module$build$src$blocks$text.text_count={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_COUNT_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_COUNT_HELPURL})}};
|
||||
blocks$$module$build$src$blocks$text.text_replace={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_REPLACE_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_REPLACE_HELPURL})}};
|
||||
blocks$$module$build$src$blocks$text.text_reverse={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_REVERSE_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_REVERSE_HELPURL})}};
|
||||
var QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text={QUOTE_IMAGE_LEFT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",QUOTE_IMAGE_RIGHT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==",
|
||||
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(let b=0,c;c=this.inputList[b];b++)for(let d=0,e;e=c.fieldRow[d];d++)if(a===e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return $.fromJson$$module$build$src$core$field_registry({type:"field_image",src:a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,width:this.QUOTE_IMAGE_WIDTH,
|
||||
height:this.QUOTE_IMAGE_HEIGHT,alt:a?"\u201c":"\u201d"})}},QUOTES_EXTENSION$$module$build$src$blocks$text=function(){this.mixin(QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);this.quoteField_("TEXT")},JOIN_MUTATOR_MIXIN$$module$build$src$blocks$text={itemCount_:0,mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("items",`${this.itemCount_}`);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},
|
||||
saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){const b=a.newBlock("text_create_join_container");b.initSvg();let c=b.getInput("STACK").connection;for(let d=0;d<this.itemCount_;d++){const e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)b.isInsertionMarker()||
|
||||
a.push(b.valueConnection_),b=b.getNextBlock();for(b=0;b<this.itemCount_;b++){const c=this.getInput("ADD"+b).connection.targetConnection;c&&!a.includes(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++){let c;(c=a[b])==null||c.reconnect(this,"ADD"+b)}},saveConnections:function(a){a=a.getInputTargetBlock("STACK");let b=0;for(;a;){if(a.isInsertionMarker()){a=a.getNextBlock();continue}const c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;
|
||||
a=a.getNextBlock();b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){const b=this.appendValueInput("ADD"+a).setAlign($.Align$$module$build$src$core$inputs$align.RIGHT);a===0&&b.appendField($.Msg$$module$build$src$core$msg.TEXT_JOIN_TITLE_CREATEWITH)}for(a=this.itemCount_;this.getInput("ADD"+
|
||||
a);a++)this.removeInput("ADD"+a)}},JOIN_EXTENSION$$module$build$src$blocks$text=function(){this.mixin(QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);this.itemCount_=2;this.updateShape_();this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["text_create_join_item"],this))};$.register$$module$build$src$core$extensions("text_append_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_TEXT_APPEND_TOOLTIP}","VAR"));
|
||||
var INDEXOF_TOOLTIP_EXTENSION$$module$build$src$blocks$text=function(){this.setTooltip(()=>$.Msg$$module$build$src$core$msg.TEXT_INDEXOF_TOOLTIP.replace("%1",this.workspace.options.oneBasedIndex?"0":"-1"))},CHARAT_MUTATOR_MIXIN$$module$build$src$blocks$text={isAt_:!1,mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("at",`${this.isAt_}`);return a},domToMutation:function(a){a=a.getAttribute("at")!=="false";this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT",
|
||||
!0);this.removeInput("ORDINAL",!0);a&&(this.appendValueInput("AT").setCheck("Number"),$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX));$.Msg$$module$build$src$core$msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.TEXT_CHARAT_TAIL));this.isAt_=a}},CHARAT_EXTENSION$$module$build$src$blocks$text=function(){this.getField("WHERE").setValidator(function(a){a=
|
||||
a==="FROM_START"||a==="FROM_END";const b=this.getSourceBlock();a!==b.isAt_&&b.updateAt_(a)});this.updateAt_(!0);this.setTooltip(()=>{var a=this.getFieldValue("WHERE");let b=$.Msg$$module$build$src$core$msg.TEXT_CHARAT_TOOLTIP;(a==="FROM_START"||a==="FROM_END")&&(a=a==="FROM_START"?$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_START_TOOLTIP:$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_END_TOOLTIP)&&(b+=" "+a.replace("%1",this.workspace.options.oneBasedIndex?"#1":"#0"));return b})};
|
||||
$.register$$module$build$src$core$extensions("text_indexOf_tooltip",INDEXOF_TOOLTIP_EXTENSION$$module$build$src$blocks$text);$.register$$module$build$src$core$extensions("text_quotes",QUOTES_EXTENSION$$module$build$src$blocks$text);$.registerMixin$$module$build$src$core$extensions("quote_image_mixin",QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);$.registerMutator$$module$build$src$core$extensions("text_join_mutator",JOIN_MUTATOR_MIXIN$$module$build$src$blocks$text,JOIN_EXTENSION$$module$build$src$blocks$text);
|
||||
$.registerMutator$$module$build$src$core$extensions("text_charAt_mutator",CHARAT_MUTATOR_MIXIN$$module$build$src$blocks$text,CHARAT_EXTENSION$$module$build$src$blocks$text);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$text);var module$build$src$blocks$text={blocks:blocks$$module$build$src$blocks$text};var blocks$$module$build$src$blocks$procedures={},PROCEDURE_DEF_COMMON$$module$build$src$blocks$procedures={setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK","RETURN")):this.removeInput("STACK",!0),this.hasStatements_=a)},updateParams_:function(){let a="";this.arguments_.length&&(a=$.Msg$$module$build$src$core$msg.PROCEDURES_BEFORE_PARAMS+
|
||||
" "+this.arguments_.join(", "));$.disable$$module$build$src$core$events$utils();try{this.setFieldValue(a,"PARAMS")}finally{$.enable$$module$build$src$core$events$utils()}},mutationToDom:function(a){const b=$.createElement$$module$build$src$core$utils$xml("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(let c=0;c<this.argumentVarModels_.length;c++){const d=$.createElement$$module$build$src$core$utils$xml("arg"),e=this.argumentVarModels_[c];d.setAttribute("name",e.getName());d.setAttribute("varid",
|
||||
e.getId());a&&this.paramIds_&&d.setAttribute("paramId",this.paramIds_[c]);b.appendChild(d)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];this.argumentVarModels_=[];for(let c=0,d;d=a.childNodes[c];c++)if(d.nodeName.toLowerCase()==="arg"){var b=d;const e=b.getAttribute("name");b=b.getAttribute("varid")||b.getAttribute("varId");this.arguments_.push(e);b=$.getOrCreateVariablePackage$$module$build$src$core$variables(this.workspace,b,e,
|
||||
"");b!==null?this.argumentVarModels_.push(b):console.log(`Failed to create a variable named "${e}", ignoring.`)}this.updateParams_();$.mutateCallers$$module$build$src$core$procedures(this);this.setStatements_(a.getAttribute("statements")!=="false")},saveExtraState:function(){if(!this.argumentVarModels_.length&&this.hasStatements_)return null;const a=Object.create(null);if(this.argumentVarModels_.length){a.params=[];for(let b=0;b<this.argumentVarModels_.length;b++)a.params.push({name:this.argumentVarModels_[b].getName(),
|
||||
id:this.argumentVarModels_[b].getId()})}this.hasStatements_||(a.hasStatements=!1);return a},loadExtraState:function(a){this.arguments_=[];this.argumentVarModels_=[];if(a.params)for(let c=0;c<a.params.length;c++){var b=a.params[c];b=$.getOrCreateVariablePackage$$module$build$src$core$variables(this.workspace,b.id,b.name,"");this.arguments_.push(b.getName());this.argumentVarModels_.push(b)}this.updateParams_();$.mutateCallers$$module$build$src$core$procedures(this);this.setStatements_(a.hasStatements===
|
||||
!1?!1:!0)},decompose:function(a){const b=$.createElement$$module$build$src$core$utils$xml("block");b.setAttribute("type","procedures_mutatorcontainer");var c=$.createElement$$module$build$src$core$utils$xml("statement");c.setAttribute("name","STACK");b.appendChild(c);for(let e=0;e<this.arguments_.length;e++){const f=$.createElement$$module$build$src$core$utils$xml("block");f.setAttribute("type","procedures_mutatorarg");var d=$.createElement$$module$build$src$core$utils$xml("field");d.setAttribute("name",
|
||||
"NAME");const g=$.createTextNode$$module$build$src$core$utils$xml(this.arguments_[e]);d.appendChild(g);f.appendChild(d);d=$.createElement$$module$build$src$core$utils$xml("next");f.appendChild(d);c.appendChild(f);c=d}a=$.domToBlock$$module$build$src$core$xml(b,a);this.type==="procedures_defreturn"?a.setFieldValue(this.hasStatements_,"STATEMENTS"):a.removeInput("STATEMENT_INPUT");$.mutateCallers$$module$build$src$core$procedures(this);return a},compose:function(a){this.arguments_=[];this.paramIds_=
|
||||
[];this.argumentVarModels_=[];let b=a.getInputTargetBlock("STACK");for(;b&&!b.isInsertionMarker();){var c=b.getFieldValue("NAME");this.arguments_.push(c);c=this.workspace.getVariable(c,"");this.argumentVarModels_.push(c);this.paramIds_.push(b.id);b=b.nextConnection&&b.nextConnection.targetBlock()}this.updateParams_();$.mutateCallers$$module$build$src$core$procedures(this);a=a.getFieldValue("STATEMENTS");if(a!==null&&(a=a==="TRUE",this.hasStatements_!==a))if(a){this.setStatements_(!0);var d;(d=this.statementConnection_)==
|
||||
null||d.reconnect(this,"STACK");this.statementConnection_=null}else{d=this.getInput("STACK").connection;if(this.statementConnection_=d.targetConnection)d=d.targetBlock(),d.unplug(),d.bumpNeighbours();this.setStatements_(!1)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},renameVarById:function(a,b){var c=this.workspace.getVariableById(a);if(c.getType()===""){c=c.getName();b=this.workspace.getVariableById(b);var d=!1;for(let e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===
|
||||
a&&(this.arguments_[e]=b.getName(),this.argumentVarModels_[e]=b,d=!0);d&&(this.displayRenamedVar_(c,b.getName()),$.mutateCallers$$module$build$src$core$procedures(this))}},updateVarName:function(a){const b=a.getName();let c=!1,d;for(let e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===a.getId()&&(d=this.arguments_[e],this.arguments_[e]=b,c=!0);c&&(this.displayRenamedVar_(d,b),$.mutateCallers$$module$build$src$core$procedures(this))},displayRenamedVar_:function(a,b){this.updateParams_();
|
||||
var c=this.getIcon($.MutatorIcon$$module$build$src$core$icons$mutator_icon.TYPE);if(c&&c.bubbleIsVisible()){c=c.getWorkspace().getAllBlocks(!1);for(let d=0,e;e=c[d];d++)e.type==="procedures_mutatorarg"&&$.Names$$module$build$src$core$names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")}},customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("NAME"),c={type:this.callType_,extraState:{name:b,params:this.arguments_}};a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.PROCEDURES_CREATE_DO.replace("%1",
|
||||
b),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,c)});if(!this.isCollapsed())for(b=0;b<this.argumentVarModels_.length;b++){c=this.argumentVarModels_[b];const d={type:"variables_get",fields:{VAR:{name:c.getName(),id:c.getId(),type:c.getType()}}};a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET.replace("%1",c.getName()),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,d)})}}}};
|
||||
blocks$$module$build$src$blocks$procedures.procedures_defnoreturn=Object.assign({},PROCEDURE_DEF_COMMON$$module$build$src$blocks$procedures,{init:function(){var a=$.findLegalName$$module$build$src$core$procedures("",this);a=$.fromJson$$module$build$src$core$field_registry({type:"field_input",text:a});a.setValidator($.rename$$module$build$src$core$procedures);a.setSpellcheck(!1);this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,"NAME").appendField("",
|
||||
"PARAMS");this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["procedures_mutatorarg"],this));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&$.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_COMMENT&&this.setCommentText($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_COMMENT);this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_TOOLTIP);
|
||||
this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},callType_:"procedures_callnoreturn"});
|
||||
blocks$$module$build$src$blocks$procedures.procedures_defreturn=Object.assign({},PROCEDURE_DEF_COMMON$$module$build$src$blocks$procedures,{init:function(){var a=$.findLegalName$$module$build$src$core$procedures("",this);a=$.fromJson$$module$build$src$core$field_registry({type:"field_input",text:a});a.setValidator($.rename$$module$build$src$core$procedures);a.setSpellcheck(!1);this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,"NAME").appendField("",
|
||||
"PARAMS");this.appendValueInput("RETURN").setAlign($.Align$$module$build$src$core$inputs$align.RIGHT).appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["procedures_mutatorarg"],this));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&$.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_COMMENT&&this.setCommentText($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_COMMENT);
|
||||
this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_TOOLTIP);this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!0]},callType_:"procedures_callreturn"});
|
||||
var PROCEDURES_MUTATORCONTAINER$$module$build$src$blocks$procedures={init:function(){this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_ALLOW_STATEMENTS).appendField($.fromJson$$module$build$src$core$field_registry({type:"field_checkbox",checked:!0}),"STATEMENTS");this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
|
||||
this.contextMenu=!1}};blocks$$module$build$src$blocks$procedures.procedures_mutatorcontainer=PROCEDURES_MUTATORCONTAINER$$module$build$src$blocks$procedures;
|
||||
var ProcedureArgumentField$$module$build$src$blocks$procedures=class extends $.FieldTextInput$$module$build$src$core$field_textinput{constructor(){super(...arguments);this.editingInteractively=!1}showEditor_(a){super.showEditor_(a);this.editingInteractively=!0;this.editingVariable=void 0}onFinishEditing_(a){super.onFinishEditing_(a);this.editingInteractively=!1}},PROCEDURES_MUTATORARGUMENT$$module$build$src$blocks$procedures={init:function(){const a=new ProcedureArgumentField$$module$build$src$blocks$procedures($.DEFAULT_ARG$$module$build$src$core$procedures,
|
||||
this.validator_);this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1},validator_:function(a){var b=this.getSourceBlock();const c=b.workspace.getRootWorkspace();a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,"");if(!a)return null;const d=(b.workspace.targetWorkspace||
|
||||
b.workspace).getAllBlocks(!1),e=a.toLowerCase();for(let f=0;f<d.length;f++){if(d[f].id===this.getSourceBlock().id)continue;const g=d[f].getFieldValue("NAME");if(g&&g.toLowerCase()===e)return null}if(b.isInFlyout)return a;(b=c.getVariable(a,""))&&b.getName()!==a&&c.renameVariableById(b.getId(),a);b||(this.editingInteractively?this.editingVariable?c.renameVariableById(this.editingVariable.getId(),a):this.editingVariable=c.createVariable(a,""):c.createVariable(a,""));return a}};
|
||||
blocks$$module$build$src$blocks$procedures.procedures_mutatorarg=PROCEDURES_MUTATORARGUMENT$$module$build$src$blocks$procedures;
|
||||
var DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON$$module$build$src$blocks$procedures="DISABLED_PROCEDURE_DEFINITION",PROCEDURE_CALL_COMMON$$module$build$src$blocks$procedures={getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){$.Names$$module$build$src$core$names.equals(a,this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?$.Msg$$module$build$src$core$msg.PROCEDURES_CALLRETURN_TOOLTIP:$.Msg$$module$build$src$core$msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",
|
||||
b)))},setProcedureParameters_:function(a,b){var c=$.getDefinition$$module$build$src$core$procedures(this.getProcedureCall(),this.workspace);(c=(c=c&&c.getIcon($.MutatorIcon$$module$build$src$core$icons$mutator_icon.TYPE))&&c.bubbleIsVisible())?this.setCollapsed(!1):(this.quarkConnections_={},this.quarkIds_=null);if(a.join("\n")===this.arguments_.join("\n"))this.quarkIds_=b;else{if(b.length!==a.length)throw RangeError("paramNames and paramIds must be the same length.");this.quarkIds_||(this.quarkConnections_=
|
||||
{},this.quarkIds_=[]);for(let e=0;e<this.arguments_.length;e++){var d=this.getInput("ARG"+e);d&&(d=d.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=d,c&&d&&!b.includes(this.quarkIds_[e])&&(d.disconnect(),d.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(a=0;a<this.arguments_.length;a++)c=$.getOrCreateVariablePackage$$module$build$src$core$variables(this.workspace,null,this.arguments_[a],""),this.argumentVarModels_.push(c);this.updateShape_();
|
||||
if(this.quarkIds_=b)for(b=0;b<this.arguments_.length;b++)if(a=this.quarkIds_[b],a in this.quarkConnections_){let e;((e=this.quarkConnections_[a])==null?0:e.reconnect(this,"ARG"+b))||delete this.quarkConnections_[a]}}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){$.disable$$module$build$src$core$events$utils();try{b.setValue(this.arguments_[a])}finally{$.enable$$module$build$src$core$events$utils()}}else b=$.fromJson$$module$build$src$core$field_registry({type:"field_label",
|
||||
text:this.arguments_[a]}),this.appendValueInput("ARG"+a).setAlign($.Align$$module$build$src$core$inputs$align.RIGHT).appendField(b,"ARGNAME"+a)}for(a=this.arguments_.length;this.getInput("ARG"+a);a++)this.removeInput("ARG"+a);(a=this.getInput("TOPROW"))&&(this.arguments_.length?this.getField("WITH")||a.appendField($.Msg$$module$build$src$core$msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"):this.getField("WITH")&&a.removeField("WITH"))},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");
|
||||
a.setAttribute("name",this.getProcedureCall());for(let b=0;b<this.arguments_.length;b++){const c=$.createElement$$module$build$src$core$utils$xml("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];const c=[];for(let d=0,e;e=a.childNodes[d];d++)e.nodeName.toLowerCase()==="arg"&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,
|
||||
c)},saveExtraState:function(){const a=Object.create(null);a.name=this.getProcedureCall();this.arguments_.length&&(a.params=this.arguments_);return a},loadExtraState:function(a){this.renameProcedure(this.getProcedureCall(),a.name);if(a=a.params){const b=[];b.length=a.length;b.fill(null);this.setProcedureParameters_(a,b)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&!this.workspace.isFlyout&&a.recordUndo)if(a.type===
|
||||
$.BLOCK_CREATE$$module$build$src$core$events$events&&a.ids.includes(this.id)){var b=this.getProcedureCall(),c=$.getDefinition$$module$build$src$core$procedures(b,this.workspace);!c||c.type===this.defType_&&JSON.stringify(c.getVars())===JSON.stringify(this.arguments_)||(c=null);if(c)c.isEnabled()||(this.setDisabledReason(!0,DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON$$module$build$src$blocks$procedures),this.setWarningText($.Msg$$module$build$src$core$msg.PROCEDURES_CALL_DISABLED_DEF_WARNING.replace("%1",
|
||||
b)));else{$.setGroup$$module$build$src$core$events$utils(a.group);a=$.createElement$$module$build$src$core$utils$xml("xml");b=$.createElement$$module$build$src$core$utils$xml("block");b.setAttribute("type",this.defType_);c=this.getRelativeToSurfaceXY();var d=c.y+$.config$$module$build$src$core$config.snapRadius*2;b.setAttribute("x",`${c.x+$.config$$module$build$src$core$config.snapRadius*(this.RTL?-1:1)}`);b.setAttribute("y",`${d}`);c=this.mutationToDom();b.appendChild(c);c=$.createElement$$module$build$src$core$utils$xml("field");
|
||||
c.setAttribute("name","NAME");d=this.getProcedureCall();const e=$.findLegalName$$module$build$src$core$procedures(d,this);d!==e&&this.renameProcedure(d,e);c.appendChild($.createTextNode$$module$build$src$core$utils$xml(d));b.appendChild(c);a.appendChild(b);$.domToWorkspace$$module$build$src$core$xml(a,this.workspace);$.setGroup$$module$build$src$core$events$utils(!1)}}else a.type===$.BLOCK_DELETE$$module$build$src$core$events$events?(b=this.getProcedureCall(),$.getDefinition$$module$build$src$core$procedures(b,
|
||||
this.workspace)||($.setGroup$$module$build$src$core$events$utils(a.group),this.dispose(!0),$.setGroup$$module$build$src$core$events$utils(!1))):a.type===$.BLOCK_CHANGE$$module$build$src$core$events$events&&a.element==="disabled"&&(b=this.getProcedureCall(),(d=$.getDefinition$$module$build$src$core$procedures(b,this.workspace))&&d.id===a.blockId&&((c=$.getGroup$$module$build$src$core$events$utils())&&console.log("Saw an existing group while responding to a definition change"),$.setGroup$$module$build$src$core$events$utils(a.group),
|
||||
a=d.isEnabled(),this.setDisabledReason(!a,DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON$$module$build$src$blocks$procedures),this.setWarningText(a?null:$.Msg$$module$build$src$core$msg.PROCEDURES_CALL_DISABLED_DEF_WARNING.replace("%1",b)),$.setGroup$$module$build$src$core$events$utils(c)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b=this.getProcedureCall(),c=this.workspace;a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.PROCEDURES_HIGHLIGHT_DEF,callback:function(){const d=
|
||||
$.getDefinition$$module$build$src$core$procedures(b,c);d&&(c.centerOnBlock(d.id),$.getFocusManager$$module$build$src$core$focus_manager().focusNode(d))}})}}};
|
||||
blocks$$module$build$src$blocks$procedures.procedures_callnoreturn=Object.assign({},PROCEDURE_CALL_COMMON$$module$build$src$blocks$procedures,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null},defType_:"procedures_defnoreturn"});
|
||||
blocks$$module$build$src$blocks$procedures.procedures_callreturn=Object.assign({},PROCEDURE_CALL_COMMON$$module$build$src$blocks$procedures,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setStyle("procedure_blocks");this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null},defType_:"procedures_defreturn"});
|
||||
var UNPARENTED_IFRETURN_DISABLED_REASON$$module$build$src$blocks$procedures="UNPARENTED_IFRETURN",PROCEDURES_IFRETURN$$module$build$src$blocks$procedures={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");
|
||||
this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_IFRETURN_TOOLTIP);this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("value",String(Number(this.hasReturnValue_)));return a},domToMutation:function(a){this.hasReturnValue_=a.getAttribute("value")==="1";this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN))},
|
||||
onchange:function(a){if(!(this.workspace.isDragging&&this.workspace.isDragging()||a.type!==$.BLOCK_MOVE$$module$build$src$core$events$events&&a.type!==$.BLOCK_CREATE$$module$build$src$core$events$events)){a=!1;var b=this;do{if(this.FUNCTION_TYPES.includes(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);a?(b.type==="procedures_defnoreturn"&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=
|
||||
!1):b.type!=="procedures_defreturn"||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!0),this.setWarningText(null)):this.setWarningText($.Msg$$module$build$src$core$msg.PROCEDURES_IFRETURN_WARNING);if(!this.isInFlyout)try{$.setRecordUndo$$module$build$src$core$events$utils(!1),this.setDisabledReason(!a,UNPARENTED_IFRETURN_DISABLED_REASON$$module$build$src$blocks$procedures)}finally{$.setRecordUndo$$module$build$src$core$events$utils(!0)}}},
|
||||
FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};blocks$$module$build$src$blocks$procedures.procedures_ifreturn=PROCEDURES_IFRETURN$$module$build$src$blocks$procedures;$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$procedures);var module$build$src$blocks$procedures={blocks:blocks$$module$build$src$blocks$procedures};var blocks$$module$build$src$blocks$math=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"math_number",message0:"%1",args0:[{type:"field_number",name:"NUM",value:0}],output:"Number",helpUrl:"%{BKY_MATH_NUMBER_HELPURL}",style:"math_blocks",tooltip:"%{BKY_MATH_NUMBER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"math_arithmetic",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Number"},{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ADDITION_SYMBOL}",
|
||||
"ADD"],["%{BKY_MATH_SUBTRACTION_SYMBOL}","MINUS"],["%{BKY_MATH_MULTIPLICATION_SYMBOL}","MULTIPLY"],["%{BKY_MATH_DIVISION_SYMBOL}","DIVIDE"],["%{BKY_MATH_POWER_SYMBOL}","POWER"]]},{type:"input_value",name:"B",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ARITHMETIC_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_single",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_SINGLE_OP_ROOT}","ROOT"],["%{BKY_MATH_SINGLE_OP_ABSOLUTE}",
|
||||
"ABS"],["-","NEG"],["ln","LN"],["log10","LOG10"],["e^","EXP"],["10^","POW10"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_SINGLE_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_trig",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_TRIG_SIN}","SIN"],["%{BKY_MATH_TRIG_COS}","COS"],["%{BKY_MATH_TRIG_TAN}","TAN"],["%{BKY_MATH_TRIG_ASIN}","ASIN"],["%{BKY_MATH_TRIG_ACOS}","ACOS"],["%{BKY_MATH_TRIG_ATAN}",
|
||||
"ATAN"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_TRIG_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_constant",message0:"%1",args0:[{type:"field_dropdown",name:"CONSTANT",options:[["\u03c0","PI"],["e","E"],["\u03c6","GOLDEN_RATIO"],["sqrt(2)","SQRT2"],["sqrt(\u00bd)","SQRT1_2"],["\u221e","INFINITY"]]}],output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_CONSTANT_TOOLTIP}",helpUrl:"%{BKY_MATH_CONSTANT_HELPURL}"},{type:"math_number_property",
|
||||
message0:"%1 %2",args0:[{type:"input_value",name:"NUMBER_TO_CHECK",check:"Number"},{type:"field_dropdown",name:"PROPERTY",options:[["%{BKY_MATH_IS_EVEN}","EVEN"],["%{BKY_MATH_IS_ODD}","ODD"],["%{BKY_MATH_IS_PRIME}","PRIME"],["%{BKY_MATH_IS_WHOLE}","WHOLE"],["%{BKY_MATH_IS_POSITIVE}","POSITIVE"],["%{BKY_MATH_IS_NEGATIVE}","NEGATIVE"],["%{BKY_MATH_IS_DIVISIBLE_BY}","DIVISIBLE_BY"]]}],inputsInline:!0,output:"Boolean",style:"math_blocks",tooltip:"%{BKY_MATH_IS_TOOLTIP}",mutator:"math_is_divisibleby_mutator"},
|
||||
{type:"math_change",message0:"%{BKY_MATH_CHANGE_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_MATH_CHANGE_TITLE_ITEM}"},{type:"input_value",name:"DELTA",check:"Number"}],previousStatement:null,nextStatement:null,style:"variable_blocks",helpUrl:"%{BKY_MATH_CHANGE_HELPURL}",extensions:["math_change_tooltip"]},{type:"math_round",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ROUND_OPERATOR_ROUND}","ROUND"],["%{BKY_MATH_ROUND_OPERATOR_ROUNDUP}","ROUNDUP"],
|
||||
["%{BKY_MATH_ROUND_OPERATOR_ROUNDDOWN}","ROUNDDOWN"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ROUND_HELPURL}",tooltip:"%{BKY_MATH_ROUND_TOOLTIP}"},{type:"math_on_list",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ONLIST_OPERATOR_SUM}","SUM"],["%{BKY_MATH_ONLIST_OPERATOR_MIN}","MIN"],["%{BKY_MATH_ONLIST_OPERATOR_MAX}","MAX"],["%{BKY_MATH_ONLIST_OPERATOR_AVERAGE}","AVERAGE"],["%{BKY_MATH_ONLIST_OPERATOR_MEDIAN}",
|
||||
"MEDIAN"],["%{BKY_MATH_ONLIST_OPERATOR_MODE}","MODE"],["%{BKY_MATH_ONLIST_OPERATOR_STD_DEV}","STD_DEV"],["%{BKY_MATH_ONLIST_OPERATOR_RANDOM}","RANDOM"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ONLIST_HELPURL}",mutator:"math_modes_of_list_mutator",extensions:["math_op_tooltip"]},{type:"math_modulo",message0:"%{BKY_MATH_MODULO_TITLE}",args0:[{type:"input_value",name:"DIVIDEND",check:"Number"},{type:"input_value",name:"DIVISOR",check:"Number"}],
|
||||
inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_MODULO_TOOLTIP}",helpUrl:"%{BKY_MATH_MODULO_HELPURL}"},{type:"math_constrain",message0:"%{BKY_MATH_CONSTRAIN_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"Number"},{type:"input_value",name:"LOW",check:"Number"},{type:"input_value",name:"HIGH",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_CONSTRAIN_TOOLTIP}",helpUrl:"%{BKY_MATH_CONSTRAIN_HELPURL}"},{type:"math_random_int",message0:"%{BKY_MATH_RANDOM_INT_TITLE}",
|
||||
args0:[{type:"input_value",name:"FROM",check:"Number"},{type:"input_value",name:"TO",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_RANDOM_INT_TOOLTIP}",helpUrl:"%{BKY_MATH_RANDOM_INT_HELPURL}"},{type:"math_random_float",message0:"%{BKY_MATH_RANDOM_FLOAT_TITLE_RANDOM}",output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_RANDOM_FLOAT_TOOLTIP}",helpUrl:"%{BKY_MATH_RANDOM_FLOAT_HELPURL}"},{type:"math_atan2",message0:"%{BKY_MATH_ATAN2_TITLE}",args0:[{type:"input_value",
|
||||
name:"X",check:"Number"},{type:"input_value",name:"Y",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_ATAN2_TOOLTIP}",helpUrl:"%{BKY_MATH_ATAN2_HELPURL}"}]),TOOLTIPS_BY_OP$$module$build$src$blocks$math={ADD:"%{BKY_MATH_ARITHMETIC_TOOLTIP_ADD}",MINUS:"%{BKY_MATH_ARITHMETIC_TOOLTIP_MINUS}",MULTIPLY:"%{BKY_MATH_ARITHMETIC_TOOLTIP_MULTIPLY}",DIVIDE:"%{BKY_MATH_ARITHMETIC_TOOLTIP_DIVIDE}",POWER:"%{BKY_MATH_ARITHMETIC_TOOLTIP_POWER}",ROOT:"%{BKY_MATH_SINGLE_TOOLTIP_ROOT}",
|
||||
ABS:"%{BKY_MATH_SINGLE_TOOLTIP_ABS}",NEG:"%{BKY_MATH_SINGLE_TOOLTIP_NEG}",LN:"%{BKY_MATH_SINGLE_TOOLTIP_LN}",LOG10:"%{BKY_MATH_SINGLE_TOOLTIP_LOG10}",EXP:"%{BKY_MATH_SINGLE_TOOLTIP_EXP}",POW10:"%{BKY_MATH_SINGLE_TOOLTIP_POW10}",SIN:"%{BKY_MATH_TRIG_TOOLTIP_SIN}",COS:"%{BKY_MATH_TRIG_TOOLTIP_COS}",TAN:"%{BKY_MATH_TRIG_TOOLTIP_TAN}",ASIN:"%{BKY_MATH_TRIG_TOOLTIP_ASIN}",ACOS:"%{BKY_MATH_TRIG_TOOLTIP_ACOS}",ATAN:"%{BKY_MATH_TRIG_TOOLTIP_ATAN}",SUM:"%{BKY_MATH_ONLIST_TOOLTIP_SUM}",MIN:"%{BKY_MATH_ONLIST_TOOLTIP_MIN}",
|
||||
MAX:"%{BKY_MATH_ONLIST_TOOLTIP_MAX}",AVERAGE:"%{BKY_MATH_ONLIST_TOOLTIP_AVERAGE}",MEDIAN:"%{BKY_MATH_ONLIST_TOOLTIP_MEDIAN}",MODE:"%{BKY_MATH_ONLIST_TOOLTIP_MODE}",STD_DEV:"%{BKY_MATH_ONLIST_TOOLTIP_STD_DEV}",RANDOM:"%{BKY_MATH_ONLIST_TOOLTIP_RANDOM}"};$.register$$module$build$src$core$extensions("math_op_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("OP",TOOLTIPS_BY_OP$$module$build$src$blocks$math));
|
||||
var IS_DIVISIBLEBY_MUTATOR_MIXIN$$module$build$src$blocks$math={mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation"),b=this.getFieldValue("PROPERTY")==="DIVISIBLE_BY";a.setAttribute("divisor_input",String(b));return a},domToMutation:function(a){a=a.getAttribute("divisor_input")==="true";this.updateShape_(a)},updateShape_:function(a){const b=this.getInput("DIVISOR");a?b||this.appendValueInput("DIVISOR").setCheck("Number"):b&&this.removeInput("DIVISOR")}},IS_DIVISIBLE_MUTATOR_EXTENSION$$module$build$src$blocks$math=
|
||||
function(){this.getField("PROPERTY").setValidator(function(a){a=a==="DIVISIBLE_BY";this.getSourceBlock().updateShape_(a)})};$.registerMutator$$module$build$src$core$extensions("math_is_divisibleby_mutator",IS_DIVISIBLEBY_MUTATOR_MIXIN$$module$build$src$blocks$math,IS_DIVISIBLE_MUTATOR_EXTENSION$$module$build$src$blocks$math);$.register$$module$build$src$core$extensions("math_change_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_MATH_CHANGE_TOOLTIP}","VAR"));
|
||||
var LIST_MODES_MUTATOR_MIXIN$$module$build$src$blocks$math={updateType_:function(a){a==="MODE"?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){a=a.getAttribute("op");if(a===null)throw new TypeError("xmlElement had no op attribute");this.updateType_(a)}},LIST_MODES_MUTATOR_EXTENSION$$module$build$src$blocks$math=
|
||||
function(){this.getField("OP").setValidator(function(a){this.updateType_(a)}.bind(this))};$.registerMutator$$module$build$src$core$extensions("math_modes_of_list_mutator",LIST_MODES_MUTATOR_MIXIN$$module$build$src$blocks$math,LIST_MODES_MUTATOR_EXTENSION$$module$build$src$blocks$math);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$math);var module$build$src$blocks$math={blocks:blocks$$module$build$src$blocks$math};var blocks$$module$build$src$blocks$loops=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"controls_repeat_ext",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"input_value",name:"TIMES",check:"Number"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",
|
||||
args0:[{type:"field_number",name:"TIMES",value:10,min:0,precision:1}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_whileUntil",message0:"%1 %2",args0:[{type:"field_dropdown",name:"MODE",options:[["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}","WHILE"],["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}","UNTIL"]]},
|
||||
{type:"input_value",name:"BOOL",check:"Boolean"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_WHILEUNTIL_HELPURL}",extensions:["controls_whileUntil_tooltip"]},{type:"controls_for",message0:"%{BKY_CONTROLS_FOR_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",
|
||||
align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],inputsInline:!0,previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOR_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_for_tooltip"]},{type:"controls_forEach",message0:"%{BKY_CONTROLS_FOREACH_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"LIST",
|
||||
check:"Array"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOREACH_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_forEach_tooltip"]},{type:"controls_flow_statements",message0:"%1",args0:[{type:"field_dropdown",name:"FLOW",options:[["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}","BREAK"],["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}","CONTINUE"]]}],
|
||||
previousStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_flow_tooltip","controls_flow_in_loop_check"]}]),WHILE_UNTIL_TOOLTIPS$$module$build$src$blocks$loops={WHILE:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}",UNTIL:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}"};$.register$$module$build$src$core$extensions("controls_whileUntil_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("MODE",WHILE_UNTIL_TOOLTIPS$$module$build$src$blocks$loops));
|
||||
var BREAK_CONTINUE_TOOLTIPS$$module$build$src$blocks$loops={BREAK:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}",CONTINUE:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}"};$.register$$module$build$src$core$extensions("controls_flow_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("FLOW",BREAK_CONTINUE_TOOLTIPS$$module$build$src$blocks$loops));
|
||||
var CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN$$module$build$src$blocks$loops={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR"),c=b.getVariable().getName();this.isCollapsed()||c===null||(b={type:"variables_get",fields:{VAR:b.saveState(!0)}},a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET.replace("%1",c),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,b)}))}}};
|
||||
$.registerMixin$$module$build$src$core$extensions("contextMenu_newGetVariableBlock",CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN$$module$build$src$blocks$loops);$.register$$module$build$src$core$extensions("controls_for_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_CONTROLS_FOR_TOOLTIP}","VAR"));$.register$$module$build$src$core$extensions("controls_forEach_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_CONTROLS_FOREACH_TOOLTIP}","VAR"));
|
||||
var loopTypes$$module$build$src$blocks$loops=new Set(["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]),CONTROL_FLOW_NOT_IN_LOOP_DISABLED_REASON$$module$build$src$blocks$loops="CONTROL_FLOW_NOT_IN_LOOP",CONTROL_FLOW_IN_LOOP_CHECK_MIXIN$$module$build$src$blocks$loops={getSurroundLoop:function(){let a=this;do{if(loopTypes$$module$build$src$blocks$loops.has(a.type))return a;a=a.getSurroundParent()}while(a);return null},onchange:function(a){const b=this.workspace;
|
||||
if(b.isDragging&&!b.isDragging()&&(a.type===$.BLOCK_MOVE$$module$build$src$core$events$events||a.type===$.BLOCK_CREATE$$module$build$src$core$events$events)&&(a=!!this.getSurroundLoop(),this.setWarningText(a?null:$.Msg$$module$build$src$core$msg.CONTROLS_FLOW_STATEMENTS_WARNING),!this.isInFlyout))try{$.setRecordUndo$$module$build$src$core$events$utils(!1),this.setDisabledReason(!a,CONTROL_FLOW_NOT_IN_LOOP_DISABLED_REASON$$module$build$src$blocks$loops)}finally{$.setRecordUndo$$module$build$src$core$events$utils(!0)}}};
|
||||
$.registerMixin$$module$build$src$core$extensions("controls_flow_in_loop_check",CONTROL_FLOW_IN_LOOP_CHECK_MIXIN$$module$build$src$blocks$loops);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$loops);var module$build$src$blocks$loops={blocks:blocks$$module$build$src$blocks$loops,loopTypes:loopTypes$$module$build$src$blocks$loops};var blocks$$module$build$src$blocks$logic=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"logic_boolean",message0:"%1",args0:[{type:"field_dropdown",name:"BOOL",options:[["%{BKY_LOGIC_BOOLEAN_TRUE}","TRUE"],["%{BKY_LOGIC_BOOLEAN_FALSE}","FALSE"]]}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_BOOLEAN_TOOLTIP}",helpUrl:"%{BKY_LOGIC_BOOLEAN_HELPURL}"},{type:"controls_if",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],
|
||||
message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],previousStatement:null,nextStatement:null,style:"logic_blocks",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,mutator:"controls_if_mutator",extensions:["controls_if_tooltip"]},{type:"controls_ifelse",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],message2:"%{BKY_CONTROLS_IF_MSG_ELSE} %1",
|
||||
args2:[{type:"input_statement",name:"ELSE"}],previousStatement:null,nextStatement:null,style:"logic_blocks",tooltip:"%{BKYCONTROLS_IF_TOOLTIP_2}",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_if_tooltip"]},{type:"logic_compare",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A"},{type:"field_dropdown",name:"OP",options:[["=","EQ"],["\u2260","NEQ"],["\u200f<","LT"],["\u200f\u2264","LTE"],["\u200f>","GT"],["\u200f\u2265","GTE"]]},{type:"input_value",name:"B"}],
|
||||
inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_COMPARE_HELPURL}",extensions:["logic_compare","logic_op_tooltip"]},{type:"logic_operation",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Boolean"},{type:"field_dropdown",name:"OP",options:[["%{BKY_LOGIC_OPERATION_AND}","AND"],["%{BKY_LOGIC_OPERATION_OR}","OR"]]},{type:"input_value",name:"B",check:"Boolean"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_OPERATION_HELPURL}",extensions:["logic_op_tooltip"]},
|
||||
{type:"logic_negate",message0:"%{BKY_LOGIC_NEGATE_TITLE}",args0:[{type:"input_value",name:"BOOL",check:"Boolean"}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_NEGATE_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NEGATE_HELPURL}"},{type:"logic_null",message0:"%{BKY_LOGIC_NULL}",output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_NULL_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NULL_HELPURL}"},{type:"logic_ternary",message0:"%{BKY_LOGIC_TERNARY_CONDITION} %1",args0:[{type:"input_value",name:"IF",check:"Boolean"}],
|
||||
message1:"%{BKY_LOGIC_TERNARY_IF_TRUE} %1",args1:[{type:"input_value",name:"THEN"}],message2:"%{BKY_LOGIC_TERNARY_IF_FALSE} %1",args2:[{type:"input_value",name:"ELSE"}],output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_TERNARY_TOOLTIP}",helpUrl:"%{BKY_LOGIC_TERNARY_HELPURL}",extensions:["logic_ternary"]},{type:"controls_if_if",message0:"%{BKY_CONTROLS_IF_IF_TITLE_IF}",nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_IF_TOOLTIP}"},{type:"controls_if_elseif",
|
||||
message0:"%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}",previousStatement:null,nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}"},{type:"controls_if_else",message0:"%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}",previousStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]),TOOLTIPS_BY_OP$$module$build$src$blocks$logic={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",
|
||||
LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"};$.register$$module$build$src$core$extensions("logic_op_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("OP",TOOLTIPS_BY_OP$$module$build$src$blocks$logic));
|
||||
var CONTROLS_IF_MUTATOR_MIXIN$$module$build$src$blocks$logic={elseifCount_:0,elseCount_:0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;const a=$.createElement$$module$build$src$core$utils$xml("mutation");this.elseifCount_&&a.setAttribute("elseif",String(this.elseifCount_));this.elseCount_&&a.setAttribute("else","1");return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||0;this.rebuildShape_()},
|
||||
saveExtraState:function(){if(!this.elseifCount_&&!this.elseCount_)return null;const a=Object.create(null);this.elseifCount_&&(a.elseIfCount=this.elseifCount_);this.elseCount_&&(a.hasElse=!0);return a},loadExtraState:function(a){this.elseifCount_=a.elseIfCount||0;this.elseCount_=a.hasElse?1:0;this.updateShape_()},decompose:function(a){const b=a.newBlock("controls_if_if");b.initSvg();let c=b.nextConnection;for(let d=1;d<=this.elseifCount_;d++){const e=a.newBlock("controls_if_elseif");e.initSvg();c.connect(e.previousConnection);
|
||||
c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;const b=[null],c=[null];let d=null;for(;a;){if(!a.isInsertionMarker())switch(a.type){case "controls_if_elseif":this.elseifCount_++;b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_;break;default:throw TypeError("Unknown block type: "+
|
||||
a.type);}a=a.getNextBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();let b=1;for(;a;){if(!a.isInsertionMarker())switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b);const d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":c=this.getInput("ELSE");a.statementConnection_=c&&c.connection.targetConnection;
|
||||
break;default:throw TypeError("Unknown block type: "+a.type);}a=a.getNextBlock()}},rebuildShape_:function(){const a=[null],b=[null];let c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(let d=1;this.getInput("IF"+d);d++){const e=this.getInput("IF"+d),f=this.getInput("DO"+d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection)}this.updateShape_();this.reconnectChildBlocks_(a,b,c)},updateShape_:function(){this.getInput("ELSE")&&this.removeInput("ELSE");
|
||||
for(var a=1;this.getInput("IF"+a);a++)this.removeInput("IF"+a),this.removeInput("DO"+a);for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_ELSE)},reconnectChildBlocks_:function(a,b,c){for(let d=
|
||||
1;d<=this.elseifCount_;d++){let e;(e=a[d])==null||e.reconnect(this,"IF"+d);let f;(f=b[d])==null||f.reconnect(this,"DO"+d)}c==null||c.reconnect(this,"ELSE")}};$.registerMutator$$module$build$src$core$extensions("controls_if_mutator",CONTROLS_IF_MUTATOR_MIXIN$$module$build$src$blocks$logic,null,["controls_if_elseif","controls_if_else"]);
|
||||
var CONTROLS_IF_TOOLTIP_EXTENSION$$module$build$src$blocks$logic=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_4}else return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_1;
|
||||
return""}.bind(this))};$.register$$module$build$src$core$extensions("controls_if_tooltip",CONTROLS_IF_TOOLTIP_EXTENSION$$module$build$src$blocks$logic);
|
||||
var LOGIC_COMPARE_ONCHANGE_MIXIN$$module$build$src$blocks$logic={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A");const c=this.getInputTargetBlock("B");b&&c&&!this.workspace.connectionChecker.doTypeChecks(b.outputConnection,c.outputConnection)&&($.setGroup$$module$build$src$core$events$utils(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1],
|
||||
b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),this.bumpNeighbours(),$.setGroup$$module$build$src$core$events$utils(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}},LOGIC_COMPARE_EXTENSION$$module$build$src$blocks$logic=function(){this.mixin(LOGIC_COMPARE_ONCHANGE_MIXIN$$module$build$src$blocks$logic)};$.register$$module$build$src$core$extensions("logic_compare",LOGIC_COMPARE_EXTENSION$$module$build$src$blocks$logic);
|
||||
var LOGIC_TERNARY_ONCHANGE_MIXIN$$module$build$src$blocks$logic={prevParentConnection_:null,onchange:function(a){const b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(let e=0;e<2;e++){const f=e===1?b:c;f&&!f.workspace.connectionChecker.doTypeChecks(f.outputConnection,d)&&($.setGroup$$module$build$src$core$events$utils(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):(f.unplug(),
|
||||
f.bumpNeighbours()),$.setGroup$$module$build$src$core$events$utils(!1))}this.prevParentConnection_=d}};$.registerMixin$$module$build$src$core$extensions("logic_ternary",LOGIC_TERNARY_ONCHANGE_MIXIN$$module$build$src$blocks$logic);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$logic);var module$build$src$blocks$logic={blocks:blocks$$module$build$src$blocks$logic};var blocks$$module$build$src$blocks$lists=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"lists_create_empty",message0:"%{BKY_LISTS_CREATE_EMPTY_TITLE}",output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_CREATE_EMPTY_HELPURL}"},{type:"lists_repeat",message0:"%{BKY_LISTS_REPEAT_TITLE}",args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_REPEAT_TOOLTIP}",
|
||||
helpUrl:"%{BKY_LISTS_REPEAT_HELPURL}"},{type:"lists_reverse",message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"},
|
||||
{type:"lists_length",message0:"%{BKY_LISTS_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]),LISTS_CREATE_WITH$$module$build$src$blocks$lists={init:function(){this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["lists_create_with_item"],
|
||||
this));this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("items",String(this.itemCount_));return a},domToMutation:function(a){a=a.getAttribute("items");if(!a)throw new TypeError("element did not have items");this.itemCount_=parseInt(a,10);this.updateShape_()},saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;
|
||||
this.updateShape_()},decompose:function(a){const b=a.newBlock("lists_create_with_container");b.initSvg();let c=b.getInput("STACK").connection;for(let d=0;d<this.itemCount_;d++){const e=a.newBlock("lists_create_with_item");e.initSvg();if(!e.previousConnection)throw Error("itemBlock has no previousConnection");c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)b.isInsertionMarker()||a.push(b.valueConnection_),b=b.getNextBlock();
|
||||
for(b=0;b<this.itemCount_;b++){const c=this.getInput("ADD"+b).connection.targetConnection;c&&!a.includes(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++){let c;(c=a[b])==null||c.reconnect(this,"ADD"+b)}},saveConnections:function(a){a=a.getInputTargetBlock("STACK");let b=0;for(;a;){if(a.isInsertionMarker()){a=a.getNextBlock();continue}const c=this.getInput("ADD"+b);let d;a.valueConnection_=(d=c)==null?void 0:d.connection.targetConnection;a=a.getNextBlock();
|
||||
b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_EMPTY_TITLE);for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){const b=this.appendValueInput("ADD"+a).setAlign($.Align$$module$build$src$core$inputs$align.RIGHT);a===0&&b.appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_INPUT_WITH)}for(a=this.itemCount_;this.getInput("ADD"+
|
||||
a);a++)this.removeInput("ADD"+a)}};blocks$$module$build$src$blocks$lists.lists_create_with=LISTS_CREATE_WITH$$module$build$src$blocks$lists;var LISTS_CREATE_WITH_CONTAINER$$module$build$src$blocks$lists={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);this.appendStatementInput("STACK");this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);this.contextMenu=!1}};
|
||||
blocks$$module$build$src$blocks$lists.lists_create_with_container=LISTS_CREATE_WITH_CONTAINER$$module$build$src$blocks$lists;var LISTS_CREATE_WITH_ITEM$$module$build$src$blocks$lists={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_ITEM_TITLE);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);this.contextMenu=!1}};
|
||||
blocks$$module$build$src$blocks$lists.lists_create_with_item=LISTS_CREATE_WITH_ITEM$$module$build$src$blocks$lists;
|
||||
var LISTS_INDEXOF$$module$build$src$blocks$lists={init:function(){var a=[[$.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_FIRST,"FIRST"],[$.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_LAST,"LAST"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_HELPURL);this.setStyle("list_blocks");this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_INPUT_IN_LIST);a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",
|
||||
options:a});if(!a)throw Error("field_dropdown not found");this.appendValueInput("FIND").appendField(a,"END");this.setInputsInline(!0);this.setTooltip(()=>$.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_TOOLTIP.replace("%1",this.workspace.options.oneBasedIndex?"0":"-1"))}};blocks$$module$build$src$blocks$lists.lists_indexOf=LISTS_INDEXOF$$module$build$src$blocks$lists;
|
||||
var LISTS_GETINDEX$$module$build$src$blocks$lists={init:function(){var a=[[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_GET,"GET"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_GET_REMOVE,"GET_REMOVE"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_REMOVE,"REMOVE"]];this.WHERE_OPTIONS=[[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FIRST,"FIRST"],
|
||||
[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_LAST,"LAST"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_HELPURL);this.setStyle("list_blocks");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});a.setValidator(function(b){b=b==="REMOVE";this.getSourceBlock().updateStatement_(b)});this.appendValueInput("VALUE").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_INPUT_IN_LIST);
|
||||
this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this.WHERE_OPTIONS});a.setValidator(function(b){const c=this.getValue();b=b==="FROM_START"||b==="FROM_END";b!==(c==="FROM_START"||c==="FROM_END")&&this.getSourceBlock().updateAt_(b)});this.appendDummyInput().appendField(a,"WHERE");this.appendDummyInput("AT");$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TAIL&&this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TAIL);
|
||||
this.setInputsInline(!0);this.setOutput(!0);this.updateAt_(!0);this.setTooltip(()=>{const b=this.getFieldValue("MODE"),c=this.getFieldValue("WHERE");let d="";switch(b+" "+c){case "GET FROM_START":case "GET FROM_END":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;break;case "GET FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;break;case "GET LAST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;break;case "GET RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;
|
||||
break;case "GET_REMOVE FROM_START":case "GET_REMOVE FROM_END":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;break;case "GET_REMOVE FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;break;case "GET_REMOVE LAST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;break;case "GET_REMOVE RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;break;case "REMOVE FROM_START":case "REMOVE FROM_END":d=
|
||||
$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;break;case "REMOVE FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;break;case "REMOVE LAST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;break;case "REMOVE RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM}if(c==="FROM_START"||c==="FROM_END")d+=" "+(c==="FROM_START"?$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_START_TOOLTIP:$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",
|
||||
this.workspace.options.oneBasedIndex?"#1":"#0");return d})},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("statement",String(!this.outputConnection));const b=this.getInput("AT")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at",String(b));return a},domToMutation:function(a){const b=a.getAttribute("statement")==="true";this.updateStatement_(b);a=a.getAttribute("at")!=="false";this.updateAt_(a)},saveExtraState:function(){return this.outputConnection?
|
||||
null:{isStatement:!0}},loadExtraState:function(a){a.isStatement?this.updateStatement_(!0):typeof a==="string"&&this.domToMutation($.textToDom$$module$build$src$core$utils$xml(a))},updateStatement_:function(a){a!==!this.outputConnection&&(this.unplug(!0,!0),a?(this.setOutput(!1),this.setPreviousStatement(!0),this.setNextStatement(!0)):(this.setPreviousStatement(!1),this.setNextStatement(!1),this.setOutput(!0)))},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),
|
||||
$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};blocks$$module$build$src$blocks$lists.lists_getIndex=LISTS_GETINDEX$$module$build$src$blocks$lists;
|
||||
var LISTS_SETINDEX$$module$build$src$blocks$lists={init:function(){var a=[[$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_SET,"SET"],[$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_INSERT,"INSERT"]];this.WHERE_OPTIONS=[[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FIRST,"FIRST"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_LAST,"LAST"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_RANDOM,
|
||||
"RANDOM"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_INPUT_IN_LIST);a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this.WHERE_OPTIONS});a.setValidator(function(b){const c=
|
||||
this.getValue();b=b==="FROM_START"||b==="FROM_END";b!==(c==="FROM_START"||c==="FROM_END")&&this.getSourceBlock().updateAt_(b)});this.appendDummyInput().appendField(a,"WHERE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_INPUT_TO);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);this.setTooltip(()=>{const b=
|
||||
this.getFieldValue("MODE"),c=this.getFieldValue("WHERE");let d="";switch(b+" "+c){case "SET FROM_START":case "SET FROM_END":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;break;case "SET FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;break;case "SET LAST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;break;case "SET RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;break;case "INSERT FROM_START":case "INSERT FROM_END":d=
|
||||
$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;break;case "INSERT FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;break;case "INSERT LAST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;break;case "INSERT RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM}if(c==="FROM_START"||c==="FROM_END")d+=" "+$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_START_TOOLTIP.replace("%1",this.workspace.options.oneBasedIndex?
|
||||
"#1":"#0");return d})},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation"),b=this.getInput("AT")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at",String(b));return a},domToMutation:function(a){a=a.getAttribute("at")!=="false";this.updateAt_(a)},saveExtraState:function(){return null},loadExtraState:function(){},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),
|
||||
$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&this.moveInputBefore("ORDINAL","TO")}};blocks$$module$build$src$blocks$lists.lists_setIndex=LISTS_SETINDEX$$module$build$src$blocks$lists;
|
||||
var LISTS_GETSUBLIST$$module$build$src$blocks$lists={init:function(){this.WHERE_OPTIONS_1=[[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_END_FROM_END,"FROM_END"],
|
||||
[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);const a=b=>{const c=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this["WHERE_OPTIONS_"+b]});c.setValidator(function(d){const e=this.getValue();d=d==="FROM_START"||d===
|
||||
"FROM_END";d!==(e==="FROM_START"||e==="FROM_END")&&this.getSourceBlock().updateAt_(b,d)});return c};this.appendDummyInput("WHERE1_INPUT").appendField(a(1),"WHERE1");this.appendDummyInput("AT1");this.appendDummyInput("WHERE2_INPUT").appendField(a(2),"WHERE2");this.appendDummyInput("AT2");$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TAIL&&this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TAIL);this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,
|
||||
!0);this.updateAt_(2,!0);this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");var b=this.getInput("AT1")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at1",String(b));b=this.getInput("AT2")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at2",String(b));return a},domToMutation:function(a){const b=a.getAttribute("at1")===
|
||||
"true";a=a.getAttribute("at2")==="true";this.updateAt_(1,b);this.updateAt_(2,a)},saveExtraState:function(){return null},loadExtraState:function(){},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);a===1&&(this.moveInputBefore("AT1",
|
||||
"WHERE2_INPUT"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","WHERE2_INPUT"));$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};blocks$$module$build$src$blocks$lists.lists_getSublist=LISTS_GETSUBLIST$$module$build$src$blocks$lists;
|
||||
blocks$$module$build$src$blocks$lists.lists_sort={init:function(){this.jsonInit({message0:"%{BKY_LISTS_SORT_TITLE}",args0:[{type:"field_dropdown",name:"TYPE",options:[["%{BKY_LISTS_SORT_TYPE_NUMERIC}","NUMERIC"],["%{BKY_LISTS_SORT_TYPE_TEXT}","TEXT"],["%{BKY_LISTS_SORT_TYPE_IGNORECASE}","IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[["%{BKY_LISTS_SORT_ORDER_ASCENDING}","1"],["%{BKY_LISTS_SORT_ORDER_DESCENDING}","-1"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Array",
|
||||
style:"list_blocks",tooltip:"%{BKY_LISTS_SORT_TOOLTIP}",helpUrl:"%{BKY_LISTS_SORT_HELPURL}"})}};
|
||||
blocks$$module$build$src$blocks$lists.lists_split={init:function(){const a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:[[$.Msg$$module$build$src$core$msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[$.Msg$$module$build$src$core$msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]]});if(!a)throw Error("field_dropdown not found");a.setValidator(b=>{this.updateType_(b)});this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_SPLIT_HELPURL);this.setStyle("list_blocks");this.appendValueInput("INPUT").setCheck("String").appendField(a,
|
||||
"MODE");this.appendValueInput("DELIM").setCheck("String").appendField($.Msg$$module$build$src$core$msg.LISTS_SPLIT_WITH_DELIMITER);this.setInputsInline(!0);this.setOutput(!0,"Array");this.setTooltip(()=>{const b=this.getFieldValue("MODE");if(b==="SPLIT")return $.Msg$$module$build$src$core$msg.LISTS_SPLIT_TOOLTIP_SPLIT;if(b==="JOIN")return $.Msg$$module$build$src$core$msg.LISTS_SPLIT_TOOLTIP_JOIN;throw Error("Unknown mode: "+b);})},updateType_:function(a){if(this.getFieldValue("MODE")!==a){const b=
|
||||
this.getInput("INPUT").connection;b.setShadowDom(null);const c=b.targetBlock();c&&(b.disconnect(),c.isShadow()?c.dispose(!1):this.bumpNeighbours())}a==="SPLIT"?(this.outputConnection.setCheck("Array"),this.getInput("INPUT").setCheck("String")):(this.outputConnection.setCheck("String"),this.getInput("INPUT").setCheck("Array"))},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("mode",this.getFieldValue("MODE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("mode"))},
|
||||
saveExtraState:function(){return{mode:this.getFieldValue("MODE")}},loadExtraState:function(a){this.updateType_(a.mode)}};$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$lists);var module$build$src$blocks$lists={blocks:blocks$$module$build$src$blocks$lists};var blocks$$module$build$src$blocks$blocks=Object.assign({},blocks$$module$build$src$blocks$lists,blocks$$module$build$src$blocks$logic,blocks$$module$build$src$blocks$loops,blocks$$module$build$src$blocks$math,blocks$$module$build$src$blocks$procedures,blocks$$module$build$src$blocks$text,blocks$$module$build$src$blocks$variables,blocks$$module$build$src$blocks$variables_dynamic),module$build$src$blocks$blocks={blocks:blocks$$module$build$src$blocks$blocks,lists:module$build$src$blocks$lists,logic:module$build$src$blocks$logic,
|
||||
loops:module$build$src$blocks$loops,math:module$build$src$blocks$math,procedures:module$build$src$blocks$procedures,texts:module$build$src$blocks$text,variables:module$build$src$blocks$variables,variablesDynamic:module$build$src$blocks$variables_dynamic};
|
||||
module$build$src$blocks$blocks.__namespace__=$;
|
||||
return module$build$src$blocks$blocks;
|
||||
}));
|
||||
|
||||
|
||||
//# sourceMappingURL=blocks_compressed.js.map
|
||||
465
crumbblocks/lib/blockly/de.js
Normal file
465
crumbblocks/lib/blockly/de.js
Normal file
@@ -0,0 +1,465 @@
|
||||
/* eslint-disable */
|
||||
;(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) { // AMD
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') { // Node.js
|
||||
module.exports = factory();
|
||||
} else { // Browser
|
||||
var messages = factory();
|
||||
for (var key in messages) {
|
||||
root.Blockly.Msg[key] = messages[key];
|
||||
}
|
||||
}
|
||||
}(this, function() {
|
||||
// This file was automatically generated. Do not modify.
|
||||
|
||||
'use strict';
|
||||
|
||||
var Blockly = Blockly || { Msg: Object.create(null) };
|
||||
|
||||
Blockly.Msg["ADD_COMMENT"] = "Kommentar hinzufügen";
|
||||
Blockly.Msg["ALT_KEY"] = "Alt"; // untranslated
|
||||
Blockly.Msg["CANNOT_DELETE_VARIABLE_PROCEDURE"] = "Die Variable „%1“ kann nicht gelöscht werden, da sie Teil der Definition der Funktion „%2“ ist.";
|
||||
Blockly.Msg["CHANGE_VALUE_TITLE"] = "Wert ändern:";
|
||||
Blockly.Msg["CHROME_OS"] = "ChromeOS"; // untranslated
|
||||
Blockly.Msg["CLEAN_UP"] = "Bausteine aufräumen";
|
||||
Blockly.Msg["CLOSE"] = "Close"; // untranslated
|
||||
Blockly.Msg["COLLAPSED_WARNINGS_WARNING"] = "Eingeklappte Blöcke enthalten Warnungen.";
|
||||
Blockly.Msg["COLLAPSE_ALL"] = "Alle Bausteine zusammenfalten";
|
||||
Blockly.Msg["COLLAPSE_BLOCK"] = "Baustein zusammenfalten";
|
||||
Blockly.Msg["COLOUR_BLEND_COLOUR1"] = "Farbe 1";
|
||||
Blockly.Msg["COLOUR_BLEND_COLOUR2"] = "und Farbe 2";
|
||||
Blockly.Msg["COLOUR_BLEND_HELPURL"] = "https://meyerweb.com/eric/tools/color-blend/#:::rgbp"; // untranslated
|
||||
Blockly.Msg["COLOUR_BLEND_RATIO"] = "im Verhältnis";
|
||||
Blockly.Msg["COLOUR_BLEND_TITLE"] = "mische";
|
||||
Blockly.Msg["COLOUR_BLEND_TOOLTIP"] = "Vermischt 2 Farben mit konfigurierbarem Farbverhältnis (0.0 - 1.0).";
|
||||
Blockly.Msg["COLOUR_PICKER_HELPURL"] = "https://de.wikipedia.org/wiki/Farbe";
|
||||
Blockly.Msg["COLOUR_PICKER_TOOLTIP"] = "Wähle eine Farbe aus der Palette.";
|
||||
Blockly.Msg["COLOUR_RANDOM_HELPURL"] = "http://randomcolour.com"; // untranslated
|
||||
Blockly.Msg["COLOUR_RANDOM_TITLE"] = "zufällige Farbe";
|
||||
Blockly.Msg["COLOUR_RANDOM_TOOLTIP"] = "Erzeugt eine Farbe nach dem Zufallsprinzip.";
|
||||
Blockly.Msg["COLOUR_RGB_BLUE"] = "blau";
|
||||
Blockly.Msg["COLOUR_RGB_GREEN"] = "grün";
|
||||
Blockly.Msg["COLOUR_RGB_HELPURL"] = "https://www.december.com/html/spec/colorpercompact.html"; // untranslated
|
||||
Blockly.Msg["COLOUR_RGB_RED"] = "rot";
|
||||
Blockly.Msg["COLOUR_RGB_TITLE"] = "Farbe aus";
|
||||
Blockly.Msg["COLOUR_RGB_TOOLTIP"] = "Erzeugt eine Farbe mit selbst definierten Rot-, Grün- und Blauwerten. Alle Werte müssen zwischen 0 und 100 liegen.";
|
||||
Blockly.Msg["COMMAND_KEY"] = "⌘ Command"; // untranslated
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_HELPURL"] = "https://de.wikipedia.org/wiki/Kontrollstruktur";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK"] = "die Schleife abbrechen";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE"] = "sofort mit nächstem Schleifendurchlauf fortfahren";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK"] = "Die umgebende Schleife beenden.";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE"] = "Diese Anweisung abbrechen und mit dem nächsten Schleifendurchlauf fortfahren.";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_WARNING"] = "Warnung: Dieser Baustein kann nur in einer Schleife verwendet werden.";
|
||||
Blockly.Msg["CONTROLS_FOREACH_HELPURL"] = "https://de.wikipedia.org/wiki/For-Schleife";
|
||||
Blockly.Msg["CONTROLS_FOREACH_TITLE"] = "für jeden Wert %1 aus der Liste %2";
|
||||
Blockly.Msg["CONTROLS_FOREACH_TOOLTIP"] = "Führt eine Anweisung für jeden Wert in der Liste aus und setzt dabei die Variable \"%1\" auf den aktuellen Listenwert.";
|
||||
Blockly.Msg["CONTROLS_FOR_HELPURL"] = "https://de.wikipedia.org/wiki/For-Schleife";
|
||||
Blockly.Msg["CONTROLS_FOR_TITLE"] = "zähle %1 von %2 bis %3 in Schritten von %4";
|
||||
Blockly.Msg["CONTROLS_FOR_TOOLTIP"] = "Zählt die Variable \"%1\" von einem Startwert bis zu einem Endwert und führt für jeden Wert eine Anweisung aus.";
|
||||
Blockly.Msg["CONTROLS_IF_ELSEIF_TOOLTIP"] = "Eine weitere Bedingung hinzufügen.";
|
||||
Blockly.Msg["CONTROLS_IF_ELSE_TOOLTIP"] = "Eine sonst-Bedingung hinzufügen. Führt eine Anweisung aus, falls keine Bedingung zutrifft.";
|
||||
Blockly.Msg["CONTROLS_IF_HELPURL"] = "https://github.com/google/blockly/wiki/IfElse"; // untranslated
|
||||
Blockly.Msg["CONTROLS_IF_IF_TOOLTIP"] = "Hinzufügen, entfernen oder sortieren von Sektionen";
|
||||
Blockly.Msg["CONTROLS_IF_MSG_ELSE"] = "sonst";
|
||||
Blockly.Msg["CONTROLS_IF_MSG_ELSEIF"] = "sonst falls";
|
||||
Blockly.Msg["CONTROLS_IF_MSG_IF"] = "falls";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_1"] = "Führt eine Anweisung aus, falls eine Bedingung wahr ist.";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_2"] = "Führt die erste Anweisung aus, falls eine Bedingung wahr ist. Führt ansonsten die zweite Anweisung aus.";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_3"] = "Führt die erste Anweisung aus, falls die erste Bedingung wahr ist. Führt ansonsten die zweite Anweisung aus, falls die zweite Bedingung wahr ist.";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_4"] = "Führe die erste Anweisung aus, falls die erste Bedingung wahr ist. Führt ansonsten die zweite Anweisung aus, falls die zweite Bedingung wahr ist. Führt die dritte Anweisung aus, falls keine der beiden Bedingungen wahr ist.";
|
||||
Blockly.Msg["CONTROLS_REPEAT_HELPURL"] = "https://de.wikipedia.org/wiki/For-Schleife";
|
||||
Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"] = "mache";
|
||||
Blockly.Msg["CONTROLS_REPEAT_TITLE"] = "wiederhole %1-mal:";
|
||||
Blockly.Msg["CONTROLS_REPEAT_TOOLTIP"] = "Eine Anweisung mehrfach ausführen.";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_HELPURL"] = "https://de.wikipedia.org/wiki/Schleife_(Programmierung)#Kopfgesteuerte_Schleife";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_OPERATOR_UNTIL"] = "wiederhole bis";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_OPERATOR_WHILE"] = "wiederhole solange";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL"] = "Führt Anweisungen aus, solange die Bedingung unwahr ist.";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_TOOLTIP_WHILE"] = "Führt Anweisungen aus, solange die Bedingung wahr ist.";
|
||||
Blockly.Msg["CONTROL_KEY"] = "Ctrl"; // untranslated
|
||||
Blockly.Msg["COPY_SHORTCUT"] = "Copy"; // untranslated
|
||||
Blockly.Msg["CUT_SHORTCUT"] = "Cut"; // untranslated
|
||||
Blockly.Msg["DELETE_ALL_BLOCKS"] = "Alle %1 Bausteine löschen?";
|
||||
Blockly.Msg["DELETE_BLOCK"] = "Baustein löschen";
|
||||
Blockly.Msg["DELETE_VARIABLE"] = "Die Variable „%1“ löschen";
|
||||
Blockly.Msg["DELETE_VARIABLE_CONFIRMATION"] = "%1 Verwendungen der Variable „%2“ löschen?";
|
||||
Blockly.Msg["DELETE_X_BLOCKS"] = "%1 Bausteine löschen";
|
||||
Blockly.Msg["DIALOG_CANCEL"] = "Abbrechen";
|
||||
Blockly.Msg["DIALOG_OK"] = "Okay";
|
||||
Blockly.Msg["DISABLE_BLOCK"] = "Baustein deaktivieren";
|
||||
Blockly.Msg["DUPLICATE_BLOCK"] = "Kopieren";
|
||||
Blockly.Msg["DUPLICATE_COMMENT"] = "Kommentar duplizieren";
|
||||
Blockly.Msg["EDIT_BLOCK_CONTENTS"] = "Edit Block contents"; // untranslated
|
||||
Blockly.Msg["ENABLE_BLOCK"] = "Baustein aktivieren";
|
||||
Blockly.Msg["EXPAND_ALL"] = "Alle Bausteine entfalten";
|
||||
Blockly.Msg["EXPAND_BLOCK"] = "Baustein entfalten";
|
||||
Blockly.Msg["EXTERNAL_INPUTS"] = "externe Eingänge";
|
||||
Blockly.Msg["HELP"] = "Hilfe";
|
||||
Blockly.Msg["HELP_PROMPT"] = "Press %1 for help on keyboard controls"; // untranslated
|
||||
Blockly.Msg["INLINE_INPUTS"] = "interne Eingänge";
|
||||
Blockly.Msg["KEYBOARD_NAV_CONSTRAINED_MOVE_HINT"] = "Use the arrow keys to move, then %1 to accept the position"; // untranslated
|
||||
Blockly.Msg["KEYBOARD_NAV_COPIED_HINT"] = "Copied. Press %1 to paste."; // untranslated
|
||||
Blockly.Msg["KEYBOARD_NAV_CUT_HINT"] = "Cut. Press %1 to paste."; // untranslated
|
||||
Blockly.Msg["KEYBOARD_NAV_UNCONSTRAINED_MOVE_HINT"] = "Hold %1 and use arrow keys to move freely, then %2 to accept the position"; // untranslated
|
||||
Blockly.Msg["LINUX"] = "Linux"; // untranslated
|
||||
Blockly.Msg["LISTS_CREATE_EMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#create-empty-list"; // untranslated
|
||||
Blockly.Msg["LISTS_CREATE_EMPTY_TITLE"] = "erzeuge eine leere Liste";
|
||||
Blockly.Msg["LISTS_CREATE_EMPTY_TOOLTIP"] = "Erzeugt eine leere Liste ohne Inhalt.";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_CONTAINER_TITLE_ADD"] = "Liste";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_CONTAINER_TOOLTIP"] = "Hinzufügen, entfernen und sortieren von Elementen.";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#create-list-with"; // untranslated
|
||||
Blockly.Msg["LISTS_CREATE_WITH_INPUT_WITH"] = "erzeuge Liste mit";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_ITEM_TOOLTIP"] = "Ein Element zur Liste hinzufügen.";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_TOOLTIP"] = "Erzeugt eine Liste aus den angegebenen Elementen.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_FIRST"] = "das erste";
|
||||
Blockly.Msg["LISTS_GET_INDEX_FROM_END"] = "von hinten das";
|
||||
Blockly.Msg["LISTS_GET_INDEX_FROM_START"] = "das";
|
||||
Blockly.Msg["LISTS_GET_INDEX_GET"] = "nimm";
|
||||
Blockly.Msg["LISTS_GET_INDEX_GET_REMOVE"] = "nimm und entferne";
|
||||
Blockly.Msg["LISTS_GET_INDEX_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_GET_INDEX_LAST"] = "das letzte";
|
||||
Blockly.Msg["LISTS_GET_INDEX_RANDOM"] = "ein zufälliges";
|
||||
Blockly.Msg["LISTS_GET_INDEX_REMOVE"] = "entferne";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TAIL"] = "Element";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_FIRST"] = "Extrahiert das erste Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_FROM"] = "Extrahiert das Element an der angegebenen Position in der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_LAST"] = "Extrahiert das letzte Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_RANDOM"] = "Extrahiert ein zufälliges Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST"] = "Extrahiert und entfernt das erste Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM"] = "Extrahiert und entfernt das Element an der angegebenen Position aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST"] = "Extrahiert und entfernt das letzte Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM"] = "Extrahiert und entfernt ein zufälliges Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST"] = "Entfernt das erste Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM"] = "Entfernt das Element an der angegebenen Position aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST"] = "Entfernt das letzte Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM"] = "Entfernt ein zufälliges Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_END_FROM_END"] = "bis von hinten";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_END_FROM_START"] = "bis";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_END_LAST"] = "bis letztes";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#getting-a-sublist"; // untranslated
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_START_FIRST"] = "nimm Teilliste ab erstes";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_START_FROM_END"] = "nimm Teilliste ab von hinten";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_START_FROM_START"] = "nimm Teilliste ab";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_TAIL"] = "Element";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_TOOLTIP"] = "Erstellt eine Kopie mit dem angegebenen Abschnitt der Liste.";
|
||||
Blockly.Msg["LISTS_INDEX_FROM_END_TOOLTIP"] = "%1 ist das letzte Element.";
|
||||
Blockly.Msg["LISTS_INDEX_FROM_START_TOOLTIP"] = "%1 ist das erste Element.";
|
||||
Blockly.Msg["LISTS_INDEX_OF_FIRST"] = "suche erstes Auftreten von";
|
||||
Blockly.Msg["LISTS_INDEX_OF_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_INDEX_OF_LAST"] = "suche letztes Auftreten von";
|
||||
Blockly.Msg["LISTS_INDEX_OF_TOOLTIP"] = "Sucht die Position (Index) eines Elementes in der Liste. Gibt %1 zurück, falls kein Element gefunden wurde.";
|
||||
Blockly.Msg["LISTS_INLIST"] = "in der Liste";
|
||||
Blockly.Msg["LISTS_ISEMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#is-empty"; // untranslated
|
||||
Blockly.Msg["LISTS_ISEMPTY_TITLE"] = "%1 ist leer";
|
||||
Blockly.Msg["LISTS_ISEMPTY_TOOLTIP"] = "Ist wahr, falls die Liste leer ist.";
|
||||
Blockly.Msg["LISTS_LENGTH_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#length-of"; // untranslated
|
||||
Blockly.Msg["LISTS_LENGTH_TITLE"] = "Länge von %1";
|
||||
Blockly.Msg["LISTS_LENGTH_TOOLTIP"] = "Die Anzahl von Elementen in der Liste.";
|
||||
Blockly.Msg["LISTS_REPEAT_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#create-list-with"; // untranslated
|
||||
Blockly.Msg["LISTS_REPEAT_TITLE"] = "erzeuge Liste mit %2-mal dem Element %1";
|
||||
Blockly.Msg["LISTS_REPEAT_TOOLTIP"] = "Erzeugt eine Liste mit der angegebenen Anzahl an Elementen des angegebenen Wertes.";
|
||||
Blockly.Msg["LISTS_REVERSE_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#reversing-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_REVERSE_MESSAGE0"] = "kehre %1 um";
|
||||
Blockly.Msg["LISTS_REVERSE_TOOLTIP"] = "Kehre eine Kopie einer Liste um.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#in-list--set"; // untranslated
|
||||
Blockly.Msg["LISTS_SET_INDEX_INPUT_TO"] = "ein";
|
||||
Blockly.Msg["LISTS_SET_INDEX_INSERT"] = "füge als";
|
||||
Blockly.Msg["LISTS_SET_INDEX_SET"] = "setze für";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST"] = "Fügt das Element an den Anfang der Liste an.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_FROM"] = "Fügt das Element an der angegebenen Position in die Liste ein.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_LAST"] = "Fügt das Element an das Ende der Liste an.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM"] = "Fügt das Element zufällig in die Liste ein.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_FIRST"] = "Setzt das erste Element in der Liste.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_FROM"] = "Setzt das Element an der angegebenen Position in der Liste.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_LAST"] = "Setzt das letzte Element in die Liste.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_RANDOM"] = "Setzt ein zufälliges Element in der Liste.";
|
||||
Blockly.Msg["LISTS_SORT_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#sorting-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_SORT_ORDER_ASCENDING"] = "aufsteigend";
|
||||
Blockly.Msg["LISTS_SORT_ORDER_DESCENDING"] = "absteigend";
|
||||
Blockly.Msg["LISTS_SORT_TITLE"] = "%1 %2 %3 sortieren";
|
||||
Blockly.Msg["LISTS_SORT_TOOLTIP"] = "Eine Kopie einer Liste sortieren.";
|
||||
Blockly.Msg["LISTS_SORT_TYPE_IGNORECASE"] = "alphabetisch, Großschreibung ignorieren";
|
||||
Blockly.Msg["LISTS_SORT_TYPE_NUMERIC"] = "numerisch";
|
||||
Blockly.Msg["LISTS_SORT_TYPE_TEXT"] = "alphabetisch";
|
||||
Blockly.Msg["LISTS_SPLIT_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#splitting-strings-and-joining-lists"; // untranslated
|
||||
Blockly.Msg["LISTS_SPLIT_LIST_FROM_TEXT"] = "Liste aus Text erstellen";
|
||||
Blockly.Msg["LISTS_SPLIT_TEXT_FROM_LIST"] = "Text aus Liste erstellen";
|
||||
Blockly.Msg["LISTS_SPLIT_TOOLTIP_JOIN"] = "Liste mit Texten in einen Text vereinen, getrennt durch ein Trennzeichen.";
|
||||
Blockly.Msg["LISTS_SPLIT_TOOLTIP_SPLIT"] = "Text in eine Liste mit Texten aufteilen, unterbrochen bei jedem Trennzeichen.";
|
||||
Blockly.Msg["LISTS_SPLIT_WITH_DELIMITER"] = "mit Trennzeichen";
|
||||
Blockly.Msg["LOGIC_BOOLEAN_FALSE"] = "falsch";
|
||||
Blockly.Msg["LOGIC_BOOLEAN_HELPURL"] = "https://github.com/google/blockly/wiki/Logic#values"; // untranslated
|
||||
Blockly.Msg["LOGIC_BOOLEAN_TOOLTIP"] = "Ist entweder wahr oder falsch.";
|
||||
Blockly.Msg["LOGIC_BOOLEAN_TRUE"] = "wahr";
|
||||
Blockly.Msg["LOGIC_COMPARE_HELPURL"] = "https://de.wikipedia.org/wiki/Vergleich_(Zahlen)";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_EQ"] = "Ist wahr, falls beide Werte gleich sind.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_GT"] = "Ist wahr, falls der erste Wert größer als der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_GTE"] = "Ist wahr, falls der erste Wert größer als oder gleich groß wie der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_LT"] = "Ist wahr, falls der erste Wert kleiner als der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_LTE"] = "Ist wahr, falls der erste Wert kleiner als oder gleich groß wie der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_NEQ"] = "Ist wahr, falls beide Werte unterschiedlich sind.";
|
||||
Blockly.Msg["LOGIC_NEGATE_HELPURL"] = "https://github.com/google/blockly/wiki/Logic#not"; // untranslated
|
||||
Blockly.Msg["LOGIC_NEGATE_TITLE"] = "nicht %1";
|
||||
Blockly.Msg["LOGIC_NEGATE_TOOLTIP"] = "Ist wahr, falls der Eingabewert unwahr ist. Ist unwahr, falls der Eingabewert wahr ist.";
|
||||
Blockly.Msg["LOGIC_NULL"] = "null";
|
||||
Blockly.Msg["LOGIC_NULL_HELPURL"] = "https://de.wikipedia.org/wiki/Nullwert";
|
||||
Blockly.Msg["LOGIC_NULL_TOOLTIP"] = "Ist \"null\".";
|
||||
Blockly.Msg["LOGIC_OPERATION_AND"] = "und";
|
||||
Blockly.Msg["LOGIC_OPERATION_HELPURL"] = "https://github.com/google/blockly/wiki/Logic#logical-operations"; // untranslated
|
||||
Blockly.Msg["LOGIC_OPERATION_OR"] = "oder";
|
||||
Blockly.Msg["LOGIC_OPERATION_TOOLTIP_AND"] = "Ist wahr, falls beide Werte wahr sind.";
|
||||
Blockly.Msg["LOGIC_OPERATION_TOOLTIP_OR"] = "Gibt true zurück, wenn mindestens eine der Eingaben wahr ist.";
|
||||
Blockly.Msg["LOGIC_TERNARY_CONDITION"] = "prüfe";
|
||||
Blockly.Msg["LOGIC_TERNARY_HELPURL"] = "https://de.wikipedia.org/wiki/%3F:#Auswahloperator";
|
||||
Blockly.Msg["LOGIC_TERNARY_IF_FALSE"] = "falls falsch";
|
||||
Blockly.Msg["LOGIC_TERNARY_IF_TRUE"] = "falls wahr";
|
||||
Blockly.Msg["LOGIC_TERNARY_TOOLTIP"] = "Überprüft eine Bedingung \"prüfe\". Falls die Bedingung wahr ist, wird der \"falls wahr\"-Wert zurückgegeben, andernfalls der \"falls unwahr\"-Wert";
|
||||
Blockly.Msg["MAC_OS"] = "macOS"; // untranslated
|
||||
Blockly.Msg["MATH_ADDITION_SYMBOL"] = "+"; // untranslated
|
||||
Blockly.Msg["MATH_ARITHMETIC_HELPURL"] = "https://de.wikipedia.org/wiki/Grundrechenart";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_ADD"] = "Ist die Summe zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_DIVIDE"] = "Ist der Quotient zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_MINUS"] = "Ist die Differenz zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_MULTIPLY"] = "Ist das Produkt zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_POWER"] = "Ist die erste Zahl potenziert mit der zweiten Zahl.";
|
||||
Blockly.Msg["MATH_ATAN2_HELPURL"] = "https://de.wikipedia.org/wiki/Arctan2";
|
||||
Blockly.Msg["MATH_ATAN2_TITLE"] = "atan2 von X:%1 Y:%2";
|
||||
Blockly.Msg["MATH_ATAN2_TOOLTIP"] = "Gibt den Arkustangens des Punktes (X, Y) in Grad von -180 bis 180 zurück.";
|
||||
Blockly.Msg["MATH_CHANGE_HELPURL"] = "https://de.wikipedia.org/wiki/Inkrement_und_Dekrement";
|
||||
Blockly.Msg["MATH_CHANGE_TITLE"] = "erhöhe %1 um %2";
|
||||
Blockly.Msg["MATH_CHANGE_TOOLTIP"] = "Addiert eine Zahl zur Variable „%1“.";
|
||||
Blockly.Msg["MATH_CONSTANT_HELPURL"] = "https://de.wikipedia.org/wiki/Mathematische_Konstante";
|
||||
Blockly.Msg["MATH_CONSTANT_TOOLTIP"] = "Mathematische Konstanten wie: π (3.141…), e (2.718…), φ (1.618…), sqrt(2) (1.414…), sqrt(½) (0.707…) oder ∞ (unendlich).";
|
||||
Blockly.Msg["MATH_CONSTRAIN_HELPURL"] = "https://en.wikipedia.org/wiki/Clamping_(graphics)"; // untranslated
|
||||
Blockly.Msg["MATH_CONSTRAIN_TITLE"] = "begrenze %1 zwischen %2 und %3";
|
||||
Blockly.Msg["MATH_CONSTRAIN_TOOLTIP"] = "Begrenzt eine Zahl auf den Wertebereich zwischen zwei anderen Zahlen (inklusiv).";
|
||||
Blockly.Msg["MATH_DIVISION_SYMBOL"] = "÷"; // untranslated
|
||||
Blockly.Msg["MATH_IS_DIVISIBLE_BY"] = "ist teilbar durch";
|
||||
Blockly.Msg["MATH_IS_EVEN"] = "ist gerade";
|
||||
Blockly.Msg["MATH_IS_NEGATIVE"] = "ist negativ";
|
||||
Blockly.Msg["MATH_IS_ODD"] = "ist ungerade";
|
||||
Blockly.Msg["MATH_IS_POSITIVE"] = "ist positiv";
|
||||
Blockly.Msg["MATH_IS_PRIME"] = "ist eine Primzahl";
|
||||
Blockly.Msg["MATH_IS_TOOLTIP"] = "Überprüft, ob eine Zahl gerade, ungerade, eine Primzahl, ganzzahlig, positiv, negativ oder durch eine zweite Zahl teilbar ist. Gibt wahr oder falsch zurück.";
|
||||
Blockly.Msg["MATH_IS_WHOLE"] = "ist eine ganze Zahl";
|
||||
Blockly.Msg["MATH_MODULO_HELPURL"] = "https://de.wikipedia.org/wiki/Modulo";
|
||||
Blockly.Msg["MATH_MODULO_TITLE"] = "Rest von %1 ÷ %2";
|
||||
Blockly.Msg["MATH_MODULO_TOOLTIP"] = "Der Rest nach einer Division.";
|
||||
Blockly.Msg["MATH_MULTIPLICATION_SYMBOL"] = "×"; // untranslated
|
||||
Blockly.Msg["MATH_NUMBER_HELPURL"] = "https://de.wikipedia.org/wiki/Zahl";
|
||||
Blockly.Msg["MATH_NUMBER_TOOLTIP"] = "Eine Zahl.";
|
||||
Blockly.Msg["MATH_ONLIST_HELPURL"] = "";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_AVERAGE"] = "Mittelwert der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MAX"] = "Maximalwert der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MEDIAN"] = "Median der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MIN"] = "Minimalwert der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MODE"] = "am häufigsten in der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_RANDOM"] = "Zufallswert aus der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_STD_DEV"] = "Standardabweichung der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_SUM"] = "Summe über die Liste";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_AVERAGE"] = "Ist der Durchschnittswert aller Zahlen in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MAX"] = "Ist die größte Zahl in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MEDIAN"] = "Ist der Median aller Zahlen in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MIN"] = "Ist die kleinste Zahl in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MODE"] = "Findet die Werte mit dem häufigsten Vorkommen in der Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_RANDOM"] = "Gibt einen zufälligen Wert aus der Liste zurück.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_STD_DEV"] = "Ist die Standardabweichung aller Werte in der Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_SUM"] = "Ist die Summe aller Zahlen in einer Liste.";
|
||||
Blockly.Msg["MATH_POWER_SYMBOL"] = "^"; // untranslated
|
||||
Blockly.Msg["MATH_RANDOM_FLOAT_HELPURL"] = "https://de.wikipedia.org/wiki/Zufallszahlen";
|
||||
Blockly.Msg["MATH_RANDOM_FLOAT_TITLE_RANDOM"] = "Zufallszahl (0.0 - 1.0)";
|
||||
Blockly.Msg["MATH_RANDOM_FLOAT_TOOLTIP"] = "Erzeugt eine Zufallszahl zwischen 0.0 (inklusiv) und 1.0 (exklusiv).";
|
||||
Blockly.Msg["MATH_RANDOM_INT_HELPURL"] = "https://de.wikipedia.org/wiki/Zufallszahlen";
|
||||
Blockly.Msg["MATH_RANDOM_INT_TITLE"] = "ganzzahlige Zufallszahl zwischen %1 und %2";
|
||||
Blockly.Msg["MATH_RANDOM_INT_TOOLTIP"] = "Erzeugt eine ganzzahlige Zufallszahl zwischen zwei Zahlen (inklusiv).";
|
||||
Blockly.Msg["MATH_ROUND_HELPURL"] = "https://de.wikipedia.org/wiki/Runden";
|
||||
Blockly.Msg["MATH_ROUND_OPERATOR_ROUND"] = "runde";
|
||||
Blockly.Msg["MATH_ROUND_OPERATOR_ROUNDDOWN"] = "runde ab";
|
||||
Blockly.Msg["MATH_ROUND_OPERATOR_ROUNDUP"] = "runde auf";
|
||||
Blockly.Msg["MATH_ROUND_TOOLTIP"] = "Eine Zahl auf- oder abrunden.";
|
||||
Blockly.Msg["MATH_SINGLE_HELPURL"] = "https://de.wikipedia.org/wiki/Quadratwurzel";
|
||||
Blockly.Msg["MATH_SINGLE_OP_ABSOLUTE"] = "Absolutbetrag";
|
||||
Blockly.Msg["MATH_SINGLE_OP_ROOT"] = "Quadratwurzel";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_ABS"] = "Ist der Absolutbetrag einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_EXP"] = "Ist Wert der Exponentialfunktion einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_LN"] = "Ist der natürliche Logarithmus einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_LOG10"] = "Ist der dekadische Logarithmus einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_NEG"] = "Negiert eine Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_POW10"] = "Rechnet 10 hoch eine Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_ROOT"] = "Ist die Quadratwurzel einer Zahl.";
|
||||
Blockly.Msg["MATH_SUBTRACTION_SYMBOL"] = "-"; // untranslated
|
||||
Blockly.Msg["MATH_TRIG_ACOS"] = "acos";
|
||||
Blockly.Msg["MATH_TRIG_ASIN"] = "asin";
|
||||
Blockly.Msg["MATH_TRIG_ATAN"] = "atan";
|
||||
Blockly.Msg["MATH_TRIG_COS"] = "cos";
|
||||
Blockly.Msg["MATH_TRIG_HELPURL"] = "https://de.wikipedia.org/wiki/Trigonometrie";
|
||||
Blockly.Msg["MATH_TRIG_SIN"] = "sin";
|
||||
Blockly.Msg["MATH_TRIG_TAN"] = "tan";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_ACOS"] = "Ist der Arkuskosinus des Eingabewertes.";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_ASIN"] = "Ist der Arkussinus des Eingabewertes.";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_ATAN"] = "Ist der Arkustangens des Eingabewertes.";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_COS"] = "Ist der Kosinus des Winkels (nicht Radiant).";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_SIN"] = "Ist der Sinus des Winkels (nicht Radiant).";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_TAN"] = "Ist der Tangens des Winkels (nicht Radiant).";
|
||||
Blockly.Msg["MOVE_BLOCK"] = "Move Block"; // untranslated
|
||||
Blockly.Msg["NEW_COLOUR_VARIABLE"] = "Farbvariable erstellen …";
|
||||
Blockly.Msg["NEW_NUMBER_VARIABLE"] = "Zahlenvariable erstellen …";
|
||||
Blockly.Msg["NEW_STRING_VARIABLE"] = "Zeichenfolgenvariable erstellen …";
|
||||
Blockly.Msg["NEW_VARIABLE"] = "Variable erstellen …";
|
||||
Blockly.Msg["NEW_VARIABLE_TITLE"] = "Name der neuen Variable:";
|
||||
Blockly.Msg["NEW_VARIABLE_TYPE_TITLE"] = "Neuer Variablentyp:";
|
||||
Blockly.Msg["OPTION_KEY"] = "⌥ Option"; // untranslated
|
||||
Blockly.Msg["ORDINAL_NUMBER_SUFFIX"] = ".";
|
||||
Blockly.Msg["PASTE_SHORTCUT"] = "Paste"; // untranslated
|
||||
Blockly.Msg["PROCEDURES_ALLOW_STATEMENTS"] = "Anweisungen erlauben";
|
||||
Blockly.Msg["PROCEDURES_BEFORE_PARAMS"] = "mit:";
|
||||
Blockly.Msg["PROCEDURES_CALLNORETURN_HELPURL"] = "https://de.wikipedia.org/wiki/Unterprogramm";
|
||||
Blockly.Msg["PROCEDURES_CALLNORETURN_TOOLTIP"] = "Ruft die benutzerdefinierte Funktion „%1“ auf.";
|
||||
Blockly.Msg["PROCEDURES_CALLRETURN_HELPURL"] = "https://de.wikipedia.org/wiki/Unterprogramm";
|
||||
Blockly.Msg["PROCEDURES_CALLRETURN_TOOLTIP"] = "Ruft die benutzerdefinierte Funktion „%1“ auf und verwendet ihre Ausgabe.";
|
||||
Blockly.Msg["PROCEDURES_CALL_BEFORE_PARAMS"] = "mit:";
|
||||
Blockly.Msg["PROCEDURES_CALL_DISABLED_DEF_WARNING"] = "Die benutzerdefinierte Funktion '%1' kann nicht ausgeführt werden, weil der Definitionsblock deaktiviert ist.";
|
||||
Blockly.Msg["PROCEDURES_CREATE_DO"] = "Erzeuge \"Aufruf %1\"";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_COMMENT"] = "Beschreibe diese Funktion …";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_DO"] = ""; // untranslated
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_HELPURL"] = "https://en.wikipedia.org/wiki/Subroutine"; // untranslated
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_PROCEDURE"] = "etwas tun";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_TITLE"] = "um";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_TOOLTIP"] = "Ein Funktionsblock ohne Rückgabewert.";
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_HELPURL"] = "https://de.wikipedia.org/wiki/Prozedur_(Programmierung)";
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_RETURN"] = "gib zurück";
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_TOOLTIP"] = "Ein Funktionsblock mit Rückgabewert.";
|
||||
Blockly.Msg["PROCEDURES_DEF_DUPLICATE_WARNING"] = "Warnung: Dieser Funktionsblock hat zwei gleich benannte Parameter.";
|
||||
Blockly.Msg["PROCEDURES_HIGHLIGHT_DEF"] = "Markiere Funktionsblock";
|
||||
Blockly.Msg["PROCEDURES_IFRETURN_HELPURL"] = "https://c2.com/cgi/wiki?GuardClause"; // untranslated
|
||||
Blockly.Msg["PROCEDURES_IFRETURN_TOOLTIP"] = "Gibt den zweiten Wert zurück und verlässt die Funktion, falls der erste Wert wahr ist.";
|
||||
Blockly.Msg["PROCEDURES_IFRETURN_WARNING"] = "Warnung: Dieser Block darf nur innerhalb eines Funktionsblocks genutzt werden.";
|
||||
Blockly.Msg["PROCEDURES_MUTATORARG_TITLE"] = "Variable:";
|
||||
Blockly.Msg["PROCEDURES_MUTATORARG_TOOLTIP"] = "Eine Eingabe zur Funktion hinzufügen.";
|
||||
Blockly.Msg["PROCEDURES_MUTATORCONTAINER_TITLE"] = "Parameter";
|
||||
Blockly.Msg["PROCEDURES_MUTATORCONTAINER_TOOLTIP"] = "Die Eingaben zu dieser Funktion hinzufügen, entfernen oder neu anordnen.";
|
||||
Blockly.Msg["REDO"] = "Wiederholen";
|
||||
Blockly.Msg["REMOVE_COMMENT"] = "Kommentar entfernen";
|
||||
Blockly.Msg["RENAME_VARIABLE"] = "Variable umbenennen …";
|
||||
Blockly.Msg["RENAME_VARIABLE_TITLE"] = "Alle „%1“-Variablen umbenennen in:";
|
||||
Blockly.Msg["SHORTCUTS_CODE_NAVIGATION"] = "Code navigation"; // untranslated
|
||||
Blockly.Msg["SHORTCUTS_EDITING"] = "Editing"; // untranslated
|
||||
Blockly.Msg["SHORTCUTS_GENERAL"] = "General"; // untranslated
|
||||
Blockly.Msg["TEXT_APPEND_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-modification"; // untranslated
|
||||
Blockly.Msg["TEXT_APPEND_TITLE"] = "zu %1 Text %2 anhängen";
|
||||
Blockly.Msg["TEXT_APPEND_TOOLTIP"] = "Text an die Variable \"%1\" anhängen.";
|
||||
Blockly.Msg["TEXT_CHANGECASE_HELPURL"] = "https://github.com/google/blockly/wiki/Text#adjusting-text-case"; // untranslated
|
||||
Blockly.Msg["TEXT_CHANGECASE_OPERATOR_LOWERCASE"] = "wandel um in kleinbuchstaben";
|
||||
Blockly.Msg["TEXT_CHANGECASE_OPERATOR_TITLECASE"] = "wandel um in Substantive";
|
||||
Blockly.Msg["TEXT_CHANGECASE_OPERATOR_UPPERCASE"] = "wandel um in GROSSBUCHSTABEN";
|
||||
Blockly.Msg["TEXT_CHANGECASE_TOOLTIP"] = "Wandelt Schreibweise von Texten um, in Großbuchstaben, Kleinbuchstaben oder den ersten Buchstaben jedes Wortes groß und die anderen klein.";
|
||||
Blockly.Msg["TEXT_CHARAT_FIRST"] = "nimm ersten";
|
||||
Blockly.Msg["TEXT_CHARAT_FROM_END"] = "nimm von hinten";
|
||||
Blockly.Msg["TEXT_CHARAT_FROM_START"] = "nimm";
|
||||
Blockly.Msg["TEXT_CHARAT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#extracting-text"; // untranslated
|
||||
Blockly.Msg["TEXT_CHARAT_LAST"] = "nimm letzten";
|
||||
Blockly.Msg["TEXT_CHARAT_RANDOM"] = "nimm zufälligen";
|
||||
Blockly.Msg["TEXT_CHARAT_TAIL"] = "Buchstaben";
|
||||
Blockly.Msg["TEXT_CHARAT_TITLE"] = "im Text %1 %2";
|
||||
Blockly.Msg["TEXT_CHARAT_TOOLTIP"] = "Extrahiert einen Buchstaben von einer bestimmten Position.";
|
||||
Blockly.Msg["TEXT_COUNT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#counting-substrings"; // untranslated
|
||||
Blockly.Msg["TEXT_COUNT_MESSAGE0"] = "zähle %1 in %2";
|
||||
Blockly.Msg["TEXT_COUNT_TOOLTIP"] = "Zähle, wie oft ein Text innerhalb eines anderen Textes vorkommt.";
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_ITEM_TOOLTIP"] = "Ein Element zum Text hinzufügen.";
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_TITLE_JOIN"] = "verbinden";
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_TOOLTIP"] = "Hinzufügen, entfernen und sortieren von Elementen.";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_END_FROM_END"] = "bis von hinten";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_END_FROM_START"] = "bis";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_END_LAST"] = "bis letzter";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_HELPURL"] = "https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text"; // untranslated
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_INPUT_IN_TEXT"] = "im Text";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_START_FIRST"] = "nimm Teil ab erster";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_START_FROM_END"] = "nimm Teil ab von hinten";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_START_FROM_START"] = "nimm Teil ab";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_TAIL"] = "Buchstabe";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_TOOLTIP"] = "Gibt den angegebenen Textabschnitt zurück.";
|
||||
Blockly.Msg["TEXT_INDEXOF_HELPURL"] = "https://github.com/google/blockly/wiki/Text#finding-text"; // untranslated
|
||||
Blockly.Msg["TEXT_INDEXOF_OPERATOR_FIRST"] = "suche erstes Auftreten des Begriffs";
|
||||
Blockly.Msg["TEXT_INDEXOF_OPERATOR_LAST"] = "suche letztes Auftreten des Begriffs";
|
||||
Blockly.Msg["TEXT_INDEXOF_TITLE"] = "im Text %1 %2 %3";
|
||||
Blockly.Msg["TEXT_INDEXOF_TOOLTIP"] = "Findet das erste / letzte Auftreten eines Suchbegriffs in einem Text. Gibt die Position des Begriffs zurück oder %1, falls der Suchbegriff nicht gefunden wurde.";
|
||||
Blockly.Msg["TEXT_ISEMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Text#checking-for-empty-text"; // untranslated
|
||||
Blockly.Msg["TEXT_ISEMPTY_TITLE"] = "%1 ist leer";
|
||||
Blockly.Msg["TEXT_ISEMPTY_TOOLTIP"] = "Ist wahr, falls der Text keine Zeichen enthält.";
|
||||
Blockly.Msg["TEXT_JOIN_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-creation"; // untranslated
|
||||
Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"] = "erstelle Text aus";
|
||||
Blockly.Msg["TEXT_JOIN_TOOLTIP"] = "Erstellt einen Text durch das Verbinden von mehreren Textelementen.";
|
||||
Blockly.Msg["TEXT_LENGTH_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-modification"; // untranslated
|
||||
Blockly.Msg["TEXT_LENGTH_TITLE"] = "Länge von %1";
|
||||
Blockly.Msg["TEXT_LENGTH_TOOLTIP"] = "Die Anzahl von Zeichen in einem Text (inkl. Leerzeichen).";
|
||||
Blockly.Msg["TEXT_PRINT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#printing-text"; // untranslated
|
||||
Blockly.Msg["TEXT_PRINT_TITLE"] = "gib aus %1";
|
||||
Blockly.Msg["TEXT_PRINT_TOOLTIP"] = "Gibt den Text aus.";
|
||||
Blockly.Msg["TEXT_PROMPT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#getting-input-from-the-user"; // untranslated
|
||||
Blockly.Msg["TEXT_PROMPT_TOOLTIP_NUMBER"] = "Fragt den Benutzer nach einer Zahl.";
|
||||
Blockly.Msg["TEXT_PROMPT_TOOLTIP_TEXT"] = "Fragt den Benutzer nach einem Text.";
|
||||
Blockly.Msg["TEXT_PROMPT_TYPE_NUMBER"] = "frage nach Zahl mit Hinweis";
|
||||
Blockly.Msg["TEXT_PROMPT_TYPE_TEXT"] = "frage nach Text mit Hinweis";
|
||||
Blockly.Msg["TEXT_REPLACE_HELPURL"] = "https://github.com/google/blockly/wiki/Text#replacing-substrings"; // untranslated
|
||||
Blockly.Msg["TEXT_REPLACE_MESSAGE0"] = "ersetze %1 durch %2 in %3";
|
||||
Blockly.Msg["TEXT_REPLACE_TOOLTIP"] = "Ersetze alle Vorkommen eines Textes innerhalb eines anderen Textes.";
|
||||
Blockly.Msg["TEXT_REVERSE_HELPURL"] = "https://github.com/google/blockly/wiki/Text#reversing-text"; // untranslated
|
||||
Blockly.Msg["TEXT_REVERSE_MESSAGE0"] = "kehre %1 um";
|
||||
Blockly.Msg["TEXT_REVERSE_TOOLTIP"] = "Kehre die Reihenfolge der Zeichen im Text um.";
|
||||
Blockly.Msg["TEXT_TEXT_HELPURL"] = "https://de.wikipedia.org/wiki/Zeichenkette";
|
||||
Blockly.Msg["TEXT_TEXT_TOOLTIP"] = "Ein Buchstabe, Text oder Satz.";
|
||||
Blockly.Msg["TEXT_TRIM_HELPURL"] = "https://github.com/google/blockly/wiki/Text#trimming-removing-spaces"; // untranslated
|
||||
Blockly.Msg["TEXT_TRIM_OPERATOR_BOTH"] = "entferne Leerzeichen vom Anfang und vom Ende (links und rechts)";
|
||||
Blockly.Msg["TEXT_TRIM_OPERATOR_LEFT"] = "entferne Leerzeichen vom Anfang (links)";
|
||||
Blockly.Msg["TEXT_TRIM_OPERATOR_RIGHT"] = "entferne Leerzeichen vom Ende (rechts)";
|
||||
Blockly.Msg["TEXT_TRIM_TOOLTIP"] = "Entfernt Leerzeichen vom Anfang und / oder Ende eines Textes.";
|
||||
Blockly.Msg["TODAY"] = "Heute";
|
||||
Blockly.Msg["UNDO"] = "Rückgängig";
|
||||
Blockly.Msg["UNKNOWN"] = "Unknown"; // untranslated
|
||||
Blockly.Msg["UNNAMED_KEY"] = "unbenannt";
|
||||
Blockly.Msg["VARIABLES_DEFAULT_NAME"] = "Element";
|
||||
Blockly.Msg["VARIABLES_GET_CREATE_SET"] = "Erzeuge \"Schreibe %1\"";
|
||||
Blockly.Msg["VARIABLES_GET_HELPURL"] = "https://github.com/google/blockly/wiki/Variables#get"; // untranslated
|
||||
Blockly.Msg["VARIABLES_GET_TOOLTIP"] = "Gibt den Wert der Variable zurück.";
|
||||
Blockly.Msg["VARIABLES_SET"] = "setze %1 auf %2";
|
||||
Blockly.Msg["VARIABLES_SET_CREATE_GET"] = "Erzeuge \"Lies %1\"";
|
||||
Blockly.Msg["VARIABLES_SET_HELPURL"] = "https://github.com/google/blockly/wiki/Variables#set"; // untranslated
|
||||
Blockly.Msg["VARIABLES_SET_TOOLTIP"] = "Setzt den Wert einer Variable.";
|
||||
Blockly.Msg["VARIABLE_ALREADY_EXISTS"] = "Eine Variable namens „%1“ ist bereits vorhanden.";
|
||||
Blockly.Msg["VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE"] = "Eine Variable namens „%1“ ist bereits für einen anderen Typ vorhanden: „%2“.";
|
||||
Blockly.Msg["VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER"] = "Eine Variable mit dem Namen „%1“ ist bereits als Parameter in der Funktion „%2“ vorhanden.";
|
||||
Blockly.Msg["WINDOWS"] = "Windows"; // untranslated
|
||||
Blockly.Msg["WORKSPACE_ARIA_LABEL"] = "Blockly-Arbeitsbereich";
|
||||
Blockly.Msg["WORKSPACE_COMMENT_DEFAULT_TEXT"] = "Teile etwas mit…";
|
||||
Blockly.Msg["CONTROLS_FOREACH_INPUT_DO"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["CONTROLS_FOR_INPUT_DO"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["CONTROLS_IF_ELSEIF_TITLE_ELSEIF"] = Blockly.Msg["CONTROLS_IF_MSG_ELSEIF"];
|
||||
Blockly.Msg["CONTROLS_IF_ELSE_TITLE_ELSE"] = Blockly.Msg["CONTROLS_IF_MSG_ELSE"];
|
||||
Blockly.Msg["CONTROLS_IF_IF_TITLE_IF"] = Blockly.Msg["CONTROLS_IF_MSG_IF"];
|
||||
Blockly.Msg["CONTROLS_IF_MSG_THEN"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_INPUT_DO"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["LISTS_CREATE_WITH_ITEM_TITLE"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
Blockly.Msg["LISTS_GET_INDEX_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["LISTS_INDEX_OF_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["LISTS_SET_INDEX_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["MATH_CHANGE_TITLE_ITEM"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_COMMENT"] = Blockly.Msg["PROCEDURES_DEFNORETURN_COMMENT"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_DO"] = Blockly.Msg["PROCEDURES_DEFNORETURN_DO"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_PROCEDURE"] = Blockly.Msg["PROCEDURES_DEFNORETURN_PROCEDURE"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_TITLE"] = Blockly.Msg["PROCEDURES_DEFNORETURN_TITLE"];
|
||||
Blockly.Msg["TEXT_APPEND_VARIABLE"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_ITEM_TITLE_ITEM"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
|
||||
Blockly.Msg["COLOUR_HUE"] = "20";
|
||||
Blockly.Msg["LISTS_HUE"] = "260";
|
||||
Blockly.Msg["LOGIC_HUE"] = "210";
|
||||
Blockly.Msg["LOOPS_HUE"] = "120";
|
||||
Blockly.Msg["MATH_HUE"] = "230";
|
||||
Blockly.Msg["PROCEDURES_HUE"] = "290";
|
||||
Blockly.Msg["TEXTS_HUE"] = "160";
|
||||
Blockly.Msg["VARIABLES_DYNAMIC_HUE"] = "310";
|
||||
Blockly.Msg["VARIABLES_HUE"] = "330";
|
||||
return Blockly.Msg;
|
||||
}));
|
||||
264
crumbblocks/lib/blockly/javascript.min.js
vendored
Normal file
264
crumbblocks/lib/blockly/javascript.min.js
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
// Do not edit this file; automatically generated.
|
||||
|
||||
/* eslint-disable */
|
||||
;(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) { // AMD
|
||||
define(["./blockly_compressed.js"], factory);
|
||||
} else if (typeof exports === 'object') { // Node.js
|
||||
module.exports = factory(require("./blockly_compressed.js"));
|
||||
} else { // Script
|
||||
root.javascript = factory(root.Blockly);
|
||||
root.Blockly.JavaScript = root.javascript.javascriptGenerator;
|
||||
}
|
||||
}(this, function(__parent__) {
|
||||
var $=__parent__.__namespace__;
|
||||
var lists_create_empty$$module$build$src$generators$javascript$lists=function(a,b){return["[]",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},lists_create_with$$module$build$src$generators$javascript$lists=function(a,b){const c=Array(a.itemCount_);for(let d=0;d<a.itemCount_;d++)c[d]=b.valueToCode(a,"ADD"+d,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null";return["["+c.join(", ")+"]",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},
|
||||
lists_repeat$$module$build$src$generators$javascript$lists=function(a,b){const c=b.provideFunction_("listsRepeat",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(value, n) {
|
||||
var array = [];
|
||||
for (var i = 0; i < n; i++) {
|
||||
array[i] = value;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
`),d=b.valueToCode(a,"ITEM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null";a=b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";return[c+"("+d+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_length$$module$build$src$generators$javascript$lists=function(a,b){return[(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||
|
||||
"[]")+".length",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER]},lists_isEmpty$$module$build$src$generators$javascript$lists=function(a,b){return["!"+(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]")+".length",Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT]},lists_indexOf$$module$build$src$generators$javascript$lists=function(a,b){const c=a.getFieldValue("END")==="FIRST"?"indexOf":"lastIndexOf",
|
||||
d=b.valueToCode(a,"FIND",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";b=(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]")+"."+c+"("+d+")";return a.workspace.options.oneBasedIndex?[b+" + 1",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION]:[b,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_getIndex$$module$build$src$generators$javascript$lists=
|
||||
function(a,b){const c=a.getFieldValue("MODE")||"GET",d=a.getFieldValue("WHERE")||"FROM_START";var e=b.valueToCode(a,"VALUE",d==="RANDOM"?Order$$module$build$src$generators$javascript$javascript_generator.NONE:Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";switch(d){case "FIRST":if(c==="GET")return[e+"[0]",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="GET_REMOVE")return[e+".shift()",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];
|
||||
if(c==="REMOVE")return e+".shift();\n";break;case "LAST":if(c==="GET")return[e+".slice(-1)[0]",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="GET_REMOVE")return[e+".pop()",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="REMOVE")return e+".pop();\n";break;case "FROM_START":a=b.getAdjusted(a,"AT");if(c==="GET")return[e+"["+a+"]",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="GET_REMOVE")return[e+
|
||||
".splice("+a+", 1)[0]",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="REMOVE")return e+".splice("+a+", 1);\n";break;case "FROM_END":a=b.getAdjusted(a,"AT",1,!0);if(c==="GET")return[e+".slice("+a+")[0]",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="GET_REMOVE")return[e+".splice("+a+", 1)[0]",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="REMOVE")return e+".splice("+a+", 1);";
|
||||
break;case "RANDOM":e=b.provideFunction_("listsGetRandomItem",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(list, remove) {
|
||||
var x = Math.floor(Math.random() * list.length);
|
||||
if (remove) {
|
||||
return list.splice(x, 1)[0];
|
||||
} else {
|
||||
return list[x];
|
||||
}
|
||||
}
|
||||
`)+"("+e+", "+(c!=="GET")+")";if(c==="GET"||c==="GET_REMOVE")return[e,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="REMOVE")return e+";\n"}throw Error("Unhandled combination (lists_getIndex).");},lists_setIndex$$module$build$src$generators$javascript$lists=function(a,b){function c(){if(d.match(/^\w+$/))return"";const h=b.nameDB_.getDistinctName("tmpList",$.NameType$$module$build$src$core$names.VARIABLE),k="var "+h+" = "+d+";\n";d=h;return k}let d=b.valueToCode(a,
|
||||
"LIST",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";const e=a.getFieldValue("MODE")||"GET";var f=a.getFieldValue("WHERE")||"FROM_START";const g=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"null";switch(f){case "FIRST":if(e==="SET")return d+"[0] = "+g+";\n";if(e==="INSERT")return d+".unshift("+g+");\n";break;case "LAST":if(e==="SET")return c()+(d+"["+d+".length - 1] = "+g+";\n");if(e==="INSERT")return d+".push("+
|
||||
g+");\n";break;case "FROM_START":a=b.getAdjusted(a,"AT");if(e==="SET")return d+"["+a+"] = "+g+";\n";if(e==="INSERT")return d+".splice("+a+", 0, "+g+");\n";break;case "FROM_END":a=b.getAdjusted(a,"AT",1,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);f=c();if(e==="SET")return f+(d+"["+d+".length - "+a+"] = "+g+";\n");if(e==="INSERT")return f+(d+".splice("+d+".length - "+a+", 0, "+g+");\n");break;case "RANDOM":a=c();f=b.nameDB_.getDistinctName("tmpX",$.NameType$$module$build$src$core$names.VARIABLE);
|
||||
a+="var "+f+" = Math.floor(Math.random() * "+d+".length);\n";if(e==="SET")return a+(d+"["+f+"] = "+g+";\n");if(e==="INSERT")return a+(d+".splice("+f+", 0, "+g+");\n")}throw Error("Unhandled combination (lists_setIndex).");},lists_getSublist$$module$build$src$generators$javascript$lists=function(a,b){var c={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"},d=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";const e=a.getFieldValue("WHERE1"),
|
||||
f=a.getFieldValue("WHERE2");if(e==="FIRST"&&f==="LAST")d+=".slice(0)";else if(d.match(/^\w+$/)||e!=="FROM_END"&&f==="FROM_START"){switch(e){case "FROM_START":c=b.getAdjusted(a,"AT1");break;case "FROM_END":c=b.getAdjusted(a,"AT1",1,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);c=d+".length - "+c;break;case "FIRST":c="0";break;default:throw Error("Unhandled option (lists_getSublist).");}switch(f){case "FROM_START":b=b.getAdjusted(a,"AT2",1);break;case "FROM_END":b=
|
||||
b.getAdjusted(a,"AT2",0,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);b=d+".length - "+b;break;case "LAST":b=d+".length";break;default:throw Error("Unhandled option (lists_getSublist).");}d=d+".slice("+c+", "+b+")"}else{const g=b.getAdjusted(a,"AT1");a=b.getAdjusted(a,"AT2");d=b.provideFunction_("subsequence"+c[e]+c[f],`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(sequence${e==="FROM_END"||e==="FROM_START"?", at1":""}${f==="FROM_END"||f==="FROM_START"?", at2":""}) {
|
||||
var start = ${getSubstringIndex$$module$build$src$generators$javascript$lists("sequence",e,"at1")};
|
||||
var end = ${getSubstringIndex$$module$build$src$generators$javascript$lists("sequence",f,"at2")} + 1;
|
||||
return sequence.slice(start, end);
|
||||
}
|
||||
`)+"("+d+(e==="FROM_END"||e==="FROM_START"?", "+g:"")+(f==="FROM_END"||f==="FROM_START"?", "+a:"")+")"}return[d,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_sort$$module$build$src$generators$javascript$lists=function(a,b){const c=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL)||"[]",d=a.getFieldValue("DIRECTION")==="1"?1:-1;a=a.getFieldValue("TYPE");b=b.provideFunction_("listsGetSortCompare",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(type, direction) {
|
||||
var compareFuncs = {
|
||||
'NUMERIC': function(a, b) {
|
||||
return Number(a) - Number(b); },
|
||||
'TEXT': function(a, b) {
|
||||
return String(a) > String(b) ? 1 : -1; },
|
||||
'IGNORE_CASE': function(a, b) {
|
||||
return String(a).toLowerCase() > String(b).toLowerCase() ? 1 : -1; },
|
||||
};
|
||||
var compare = compareFuncs[type];
|
||||
return function(a, b) { return compare(a, b) * direction; };
|
||||
}
|
||||
`);return[c+".slice().sort("+b+'("'+a+'", '+d+"))",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_split$$module$build$src$generators$javascript$lists=function(a,b){let c=b.valueToCode(a,"INPUT",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER);b=b.valueToCode(a,"DELIM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=a.getFieldValue("MODE");if(a==="SPLIT")c||(c="''"),a="split";else if(a==="JOIN")c||
|
||||
(c="[]"),a="join";else throw Error("Unknown mode: "+a);return[c+"."+a+"("+b+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_reverse$$module$build$src$generators$javascript$lists=function(a,b){return[(b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL)||"[]")+".slice().reverse()",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},controls_if$$module$build$src$generators$javascript$logic=
|
||||
function(a,b){var c=0;let d="";b.STATEMENT_PREFIX&&(d+=b.injectId(b.STATEMENT_PREFIX,a));do{const e=b.valueToCode(a,"IF"+c,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"false";let f=b.statementToCode(a,"DO"+c);b.STATEMENT_SUFFIX&&(f=b.prefixLines(b.injectId(b.STATEMENT_SUFFIX,a),b.INDENT)+f);d+=(c>0?" else ":"")+"if ("+e+") {\n"+f+"}";c++}while(a.getInput("IF"+c));if(a.getInput("ELSE")||b.STATEMENT_SUFFIX)c=a.getInput("ELSE")?b.statementToCode(a,"ELSE"):"",b.STATEMENT_SUFFIX&&
|
||||
(c=b.prefixLines(b.injectId(b.STATEMENT_SUFFIX,a),b.INDENT)+c),d+=" else {\n"+c+"}";return d+"\n"},logic_compare$$module$build$src$generators$javascript$logic=function(a,b){const c={EQ:"==",NEQ:"!=",LT:"<",LTE:"<=",GT:">",GTE:">="}[a.getFieldValue("OP")],d=c==="=="||c==="!="?Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY:Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL,e=b.valueToCode(a,"A",d)||"0";a=b.valueToCode(a,"B",d)||"0";return[e+" "+
|
||||
c+" "+a,d]},logic_operation$$module$build$src$generators$javascript$logic=function(a,b){const c=a.getFieldValue("OP")==="AND"?"&&":"||",d=c==="&&"?Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_AND:Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_OR;let e=b.valueToCode(a,"A",d);a=b.valueToCode(a,"B",d);e||a?(b=c==="&&"?"true":"false",e||(e=b),a||(a=b)):a=e="false";return[e+" "+c+" "+a,d]},logic_negate$$module$build$src$generators$javascript$logic=
|
||||
function(a,b){const c=Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT;return["!"+(b.valueToCode(a,"BOOL",c)||"true"),c]},logic_boolean$$module$build$src$generators$javascript$logic=function(a,b){return[a.getFieldValue("BOOL")==="TRUE"?"true":"false",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},logic_null$$module$build$src$generators$javascript$logic=function(a,b){return["null",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},
|
||||
logic_ternary$$module$build$src$generators$javascript$logic=function(a,b){const c=b.valueToCode(a,"IF",Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL)||"false",d=b.valueToCode(a,"THEN",Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL)||"null";a=b.valueToCode(a,"ELSE",Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL)||"null";return[c+" ? "+d+" : "+a,Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL]},
|
||||
controls_repeat_ext$$module$build$src$generators$javascript$loops=function(a,b){let c;c=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):b.valueToCode(a,"TIMES",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0";let d=b.statementToCode(a,"DO");d=b.addLoopTrap(d,a);a="";const e=b.nameDB_.getDistinctName("count",$.NameType$$module$build$src$core$names.VARIABLE);let f=c;c.match(/^\w+$/)||$.isNumber$$module$build$src$core$utils$string(c)||(f=b.nameDB_.getDistinctName("repeat_end",
|
||||
$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+f+" = "+c+";\n");return a+("for (var "+e+" = 0; "+e+" < "+f+"; "+e+"++) {\n"+d+"}\n")},controls_whileUntil$$module$build$src$generators$javascript$loops=function(a,b){const c=a.getFieldValue("MODE")==="UNTIL";let d=b.valueToCode(a,"BOOL",c?Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT:Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"false",e=b.statementToCode(a,"DO");e=b.addLoopTrap(e,
|
||||
a);c&&(d="!"+d);return"while ("+d+") {\n"+e+"}\n"},controls_for$$module$build$src$generators$javascript$loops=function(a,b){var c=b.getVariableName(a.getFieldValue("VAR")),d=b.valueToCode(a,"FROM",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0",e=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0";const f=b.valueToCode(a,"BY",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"1";
|
||||
let g=b.statementToCode(a,"DO");g=b.addLoopTrap(g,a);if($.isNumber$$module$build$src$core$utils$string(d)&&$.isNumber$$module$build$src$core$utils$string(e)&&$.isNumber$$module$build$src$core$utils$string(f))b=Number(d)<=Number(e),a="for ("+c+" = "+d+"; "+c+(b?" <= ":" >= ")+e+"; "+c,c=Math.abs(Number(f)),a=c===1?a+(b?"++":"--"):a+((b?" += ":" -= ")+c),a+=") {\n"+g+"}\n";else{a="";let h=d;d.match(/^\w+$/)||$.isNumber$$module$build$src$core$utils$string(d)||(h=b.nameDB_.getDistinctName(c+"_start",
|
||||
$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+h+" = "+d+";\n");d=e;e.match(/^\w+$/)||$.isNumber$$module$build$src$core$utils$string(e)||(d=b.nameDB_.getDistinctName(c+"_end",$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+d+" = "+e+";\n");e=b.nameDB_.getDistinctName(c+"_inc",$.NameType$$module$build$src$core$names.VARIABLE);a+="var "+e+" = ";a=$.isNumber$$module$build$src$core$utils$string(f)?a+(Math.abs(Number(f))+";\n"):a+("Math.abs("+f+");\n");a+="if ("+h+" > "+d+") {\n";
|
||||
a+=b.INDENT+e+" = -"+e+";\n";a=a+"}\nfor ("+(c+" = "+h+"; "+e+" >= 0 ? "+c+" <= "+d+" : "+c+" >= "+d+"; "+c+" += "+e+") {\n"+g+"}\n")}return a},controls_forEach$$module$build$src$generators$javascript$loops=function(a,b){const c=b.getVariableName(a.getFieldValue("VAR"));var d=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"[]";let e=b.statementToCode(a,"DO");e=b.addLoopTrap(e,a);a="";let f=d;d.match(/^\w+$/)||(f=b.nameDB_.getDistinctName(c+"_list",
|
||||
$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+f+" = "+d+";\n");d=b.nameDB_.getDistinctName(c+"_index",$.NameType$$module$build$src$core$names.VARIABLE);e=b.INDENT+c+" = "+f+"["+d+"];\n"+e;return a+("for (var "+d+" in "+f+") {\n"+e+"}\n")},controls_flow_statements$$module$build$src$generators$javascript$loops=function(a,b){let c="";b.STATEMENT_PREFIX&&(c+=b.injectId(b.STATEMENT_PREFIX,a));b.STATEMENT_SUFFIX&&(c+=b.injectId(b.STATEMENT_SUFFIX,a));if(b.STATEMENT_PREFIX){const d=a.getSurroundLoop();
|
||||
d&&!d.suppressPrefixSuffix&&(c+=b.injectId(b.STATEMENT_PREFIX,d))}switch(a.getFieldValue("FLOW")){case "BREAK":return c+"break;\n";case "CONTINUE":return c+"continue;\n"}throw Error("Unknown flow statement.");},math_number$$module$build$src$generators$javascript$math=function(a,b){a=Number(a.getFieldValue("NUM"));return[String(a),a>=0?Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC:Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION]},math_arithmetic$$module$build$src$generators$javascript$math=
|
||||
function(a,b){var c={ADD:[" + ",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION],MINUS:[" - ",Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION],MULTIPLY:[" * ",Order$$module$build$src$generators$javascript$javascript_generator.MULTIPLICATION],DIVIDE:[" / ",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION],POWER:[null,Order$$module$build$src$generators$javascript$javascript_generator.NONE]}[a.getFieldValue("OP")];
|
||||
const d=c[0];c=c[1];const e=b.valueToCode(a,"A",c)||"0";a=b.valueToCode(a,"B",c)||"0";return d?[e+d+a,c]:["Math.pow("+e+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_single$$module$build$src$generators$javascript$math=function(a,b){const c=a.getFieldValue("OP");let d;if(c==="NEG")return a=b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION)||"0",a[0]==="-"&&(a=" "+a),["-"+a,Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION];
|
||||
a=c==="SIN"||c==="COS"||c==="TAN"?b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION)||"0":b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";switch(c){case "ABS":d="Math.abs("+a+")";break;case "ROOT":d="Math.sqrt("+a+")";break;case "LN":d="Math.log("+a+")";break;case "EXP":d="Math.exp("+a+")";break;case "POW10":d="Math.pow(10,"+a+")";break;case "ROUND":d="Math.round("+a+")";break;case "ROUNDUP":d="Math.ceil("+
|
||||
a+")";break;case "ROUNDDOWN":d="Math.floor("+a+")";break;case "SIN":d="Math.sin("+a+" / 180 * Math.PI)";break;case "COS":d="Math.cos("+a+" / 180 * Math.PI)";break;case "TAN":d="Math.tan("+a+" / 180 * Math.PI)"}if(d)return[d,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];switch(c){case "LOG10":d="Math.log("+a+") / Math.log(10)";break;case "ASIN":d="Math.asin("+a+") / Math.PI * 180";break;case "ACOS":d="Math.acos("+a+") / Math.PI * 180";break;case "ATAN":d="Math.atan("+
|
||||
a+") / Math.PI * 180";break;default:throw Error("Unknown math operator: "+c);}return[d,Order$$module$build$src$generators$javascript$javascript_generator.DIVISION]},math_constant$$module$build$src$generators$javascript$math=function(a,b){return{PI:["Math.PI",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],E:["Math.E",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],GOLDEN_RATIO:["(1 + Math.sqrt(5)) / 2",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION],
|
||||
SQRT2:["Math.SQRT2",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],SQRT1_2:["Math.SQRT1_2",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],INFINITY:["Infinity",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]}[a.getFieldValue("CONSTANT")]},math_number_property$$module$build$src$generators$javascript$math=function(a,b){var c={EVEN:[" % 2 === 0",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],ODD:[" % 2 === 1",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],WHOLE:[" % 1 === 0",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],POSITIVE:[" > 0",Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL],NEGATIVE:[" < 0",Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL,Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL],DIVISIBLE_BY:[null,Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],PRIME:[null,Order$$module$build$src$generators$javascript$javascript_generator.NONE,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]};const d=a.getFieldValue("PROPERTY"),[e,f,g]=c[d];c=b.valueToCode(a,"NUMBER_TO_CHECK",f)||"0";d==="PRIME"?a=b.provideFunction_("mathIsPrime",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(n) {
|
||||
// https://en.wikipedia.org/wiki/Primality_test#Naive_methods
|
||||
if (n == 2 || n == 3) {
|
||||
return true;
|
||||
}
|
||||
// False if n is NaN, negative, is 1, or not whole.
|
||||
// And false if n is divisible by 2 or 3.
|
||||
if (isNaN(n) || n <= 1 || n % 1 !== 0 || n % 2 === 0 || n % 3 === 0) {
|
||||
return false;
|
||||
}
|
||||
// Check all the numbers of form 6k +/- 1, up to sqrt(n).
|
||||
for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {
|
||||
if (n % (x - 1) === 0 || n % (x + 1) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
`)+"("+c+")":d==="DIVISIBLE_BY"?(a=b.valueToCode(a,"DIVISOR",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS)||"0",a=c+" % "+a+" === 0"):a=c+e;return[a,g]},math_change$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"DELTA",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION)||"0";a=b.getVariableName(a.getFieldValue("VAR"));return a+" = (typeof "+a+" === 'number' ? "+a+" : 0) + "+c+";\n"},math_on_list$$module$build$src$generators$javascript$math=
|
||||
function(a,b){var c=a.getFieldValue("OP");switch(c){case "SUM":a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";a+=".reduce(function(x, y) {return x + y;}, 0)";break;case "MIN":a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a="Math.min.apply(null, "+a+")";break;case "MAX":a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a="Math.max.apply(null, "+
|
||||
a+")";break;case "AVERAGE":c=b.provideFunction_("mathMean",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(myList) {
|
||||
return myList.reduce(function(x, y) {return x + y;}, 0) / myList.length;
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "MEDIAN":c=b.provideFunction_("mathMedian",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(myList) {
|
||||
var localList = myList.filter(function (x) {return typeof x === 'number';});
|
||||
if (!localList.length) return null;
|
||||
localList.sort(function(a, b) {return b - a;});
|
||||
if (localList.length % 2 === 0) {
|
||||
return (localList[localList.length / 2 - 1] + localList[localList.length / 2]) / 2;
|
||||
} else {
|
||||
return localList[(localList.length - 1) / 2];
|
||||
}
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "MODE":c=b.provideFunction_("mathModes",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(values) {
|
||||
var modes = [];
|
||||
var counts = [];
|
||||
var maxCount = 0;
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i];
|
||||
var found = false;
|
||||
var thisCount;
|
||||
for (var j = 0; j < counts.length; j++) {
|
||||
if (counts[j][0] === value) {
|
||||
thisCount = ++counts[j][1];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
counts.push([value, 1]);
|
||||
thisCount = 1;
|
||||
}
|
||||
maxCount = Math.max(thisCount, maxCount);
|
||||
}
|
||||
for (var j = 0; j < counts.length; j++) {
|
||||
if (counts[j][1] === maxCount) {
|
||||
modes.push(counts[j][0]);
|
||||
}
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "STD_DEV":c=b.provideFunction_("mathStandardDeviation",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(numbers) {
|
||||
var n = numbers.length;
|
||||
if (!n) return null;
|
||||
var mean = numbers.reduce(function(x, y) {return x + y;}) / n;
|
||||
var variance = 0;
|
||||
for (var j = 0; j < n; j++) {
|
||||
variance += Math.pow(numbers[j] - mean, 2);
|
||||
}
|
||||
variance /= n;
|
||||
return Math.sqrt(variance);
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "RANDOM":c=b.provideFunction_("mathRandomList",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(list) {
|
||||
var x = Math.floor(Math.random() * list.length);
|
||||
return list[x];
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;default:throw Error("Unknown operator: "+c);}return[a,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_modulo$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"DIVIDEND",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS)||"0";a=b.valueToCode(a,"DIVISOR",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS)||
|
||||
"0";return[c+" % "+a,Order$$module$build$src$generators$javascript$javascript_generator.MODULUS]},math_constrain$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0",d=b.valueToCode(a,"LOW",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";a=b.valueToCode(a,"HIGH",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"Infinity";return["Math.min(Math.max("+
|
||||
c+", "+d+"), "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_random_int$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"FROM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";a=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";return[b.provideFunction_("mathRandomInt",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(a, b) {
|
||||
if (a > b) {
|
||||
// Swap a and b to ensure a is smaller.
|
||||
var c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
return Math.floor(Math.random() * (b - a + 1) + a);
|
||||
}
|
||||
`)+"("+c+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_random_float$$module$build$src$generators$javascript$math=function(a,b){return["Math.random()",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_atan2$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"X",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";return["Math.atan2("+(b.valueToCode(a,"Y",
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0")+", "+c+") / Math.PI * 180",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION]},procedures_defreturn$$module$build$src$generators$javascript$procedures=function(a,b){const c=b.getProcedureName(a.getFieldValue("NAME"));var d="";b.STATEMENT_PREFIX&&(d+=b.injectId(b.STATEMENT_PREFIX,a));b.STATEMENT_SUFFIX&&(d+=b.injectId(b.STATEMENT_SUFFIX,a));d&&(d=b.prefixLines(d,b.INDENT));let e="";b.INFINITE_LOOP_TRAP&&
|
||||
(e=b.prefixLines(b.injectId(b.INFINITE_LOOP_TRAP,a),b.INDENT));let f="";a.getInput("STACK")&&(f=b.statementToCode(a,"STACK"));let g="";a.getInput("RETURN")&&(g=b.valueToCode(a,"RETURN",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"");let h="";f&&g&&(h=d);g&&(g=b.INDENT+"return "+g+";\n");const k=[],l=a.getVars();for(let m=0;m<l.length;m++)k[m]=b.getVariableName(l[m]);d="function "+c+"("+k.join(", ")+") {\n"+d+e+f+h+g+"}";d=b.scrub_(a,d);b.definitions_["%"+c]=d;return null},
|
||||
procedures_callreturn$$module$build$src$generators$javascript$procedures=function(a,b){const c=b.getProcedureName(a.getFieldValue("NAME")),d=[],e=a.getVars();for(let f=0;f<e.length;f++)d[f]=b.valueToCode(a,"ARG"+f,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null";return[c+"("+d.join(", ")+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},procedures_callnoreturn$$module$build$src$generators$javascript$procedures=function(a,b){return b.forBlock.procedures_callreturn(a,
|
||||
b)[0]+";\n"},procedures_ifreturn$$module$build$src$generators$javascript$procedures=function(a,b){let c="if ("+(b.valueToCode(a,"CONDITION",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"false")+") {\n";b.STATEMENT_SUFFIX&&(c+=b.prefixLines(b.injectId(b.STATEMENT_SUFFIX,a),b.INDENT));a.hasReturnValue_?(a=b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null",c+=b.INDENT+"return "+a+";\n"):c+=b.INDENT+"return;\n";return c+
|
||||
"}\n"},text$$module$build$src$generators$javascript$text=function(a,b){return[b.quote_(a.getFieldValue("TEXT")),Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},text_join$$module$build$src$generators$javascript$text=function(a,b){switch(a.itemCount_){case 0:return["''",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC];case 1:return a=b.valueToCode(a,"ADD0",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''",forceString$$module$build$src$generators$javascript$text(a);
|
||||
case 2:var c=b.valueToCode(a,"ADD0",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=b.valueToCode(a,"ADD1",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return[forceString$$module$build$src$generators$javascript$text(c)[0]+" + "+forceString$$module$build$src$generators$javascript$text(a)[0],Order$$module$build$src$generators$javascript$javascript_generator.ADDITION];default:c=Array(a.itemCount_);for(let d=0;d<a.itemCount_;d++)c[d]=
|
||||
b.valueToCode(a,"ADD"+d,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return["["+c.join(",")+"].join('')",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]}},text_append$$module$build$src$generators$javascript$text=function(a,b){const c=b.getVariableName(a.getFieldValue("VAR"));a=b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return c+" += "+forceString$$module$build$src$generators$javascript$text(a)[0]+
|
||||
";\n"},text_length$$module$build$src$generators$javascript$text=function(a,b){return[(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+".length",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER]},text_isEmpty$$module$build$src$generators$javascript$text=function(a,b){return["!"+(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+".length",Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT]},
|
||||
text_indexOf$$module$build$src$generators$javascript$text=function(a,b){const c=a.getFieldValue("END")==="FIRST"?"indexOf":"lastIndexOf",d=b.valueToCode(a,"FIND",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";b=(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+"."+c+"("+d+")";return a.workspace.options.oneBasedIndex?[b+" + 1",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION]:[b,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},
|
||||
text_charAt$$module$build$src$generators$javascript$text=function(a,b){const c=a.getFieldValue("WHERE")||"FROM_START",d=b.valueToCode(a,"VALUE",c==="RANDOM"?Order$$module$build$src$generators$javascript$javascript_generator.NONE:Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''";switch(c){case "FIRST":return[d+".charAt(0)",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];case "LAST":return[d+".slice(-1)",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];
|
||||
case "FROM_START":return a=b.getAdjusted(a,"AT"),[d+".charAt("+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];case "FROM_END":return a=b.getAdjusted(a,"AT",1,!0),[d+".slice("+a+").charAt(0)",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];case "RANDOM":return[b.provideFunction_("textRandomLetter",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(text) {
|
||||
var x = Math.floor(Math.random() * text.length);
|
||||
return text[x];
|
||||
}
|
||||
`)+"("+d+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]}throw Error("Unhandled option (text_charAt).");},text_getSubstring$$module$build$src$generators$javascript$text=function(a,b){var c={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"};const d=a.getFieldValue("WHERE1"),e=a.getFieldValue("WHERE2");var f=d!=="FROM_END"&&d!=="LAST"&&e!=="FROM_END"&&e!=="LAST",g=b.valueToCode(a,"STRING",f?Order$$module$build$src$generators$javascript$javascript_generator.MEMBER:
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";if(d==="FIRST"&&e==="LAST")return[g,Order$$module$build$src$generators$javascript$javascript_generator.NONE];if(g.match(/^'?\w+'?$/)||f){switch(d){case "FROM_START":c=b.getAdjusted(a,"AT1");break;case "FROM_END":c=b.getAdjusted(a,"AT1",1,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);c=g+".length - "+c;break;case "FIRST":c="0";break;default:throw Error("Unhandled option (text_getSubstring).");
|
||||
}switch(e){case "FROM_START":b=b.getAdjusted(a,"AT2",1);break;case "FROM_END":b=b.getAdjusted(a,"AT2",0,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);b=g+".length - "+b;break;case "LAST":b=g+".length";break;default:throw Error("Unhandled option (text_getSubstring).");}g=g+".slice("+c+", "+b+")"}else f=b.getAdjusted(a,"AT1"),a=b.getAdjusted(a,"AT2"),g=b.provideFunction_("subsequence"+c[d]+c[e],`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(sequence${d==="FROM_END"||d==="FROM_START"?", at1":""}${e==="FROM_END"||e==="FROM_START"?", at2":""}) {
|
||||
var start = ${getSubstringIndex$$module$build$src$generators$javascript$text("sequence",d,"at1")};
|
||||
var end = ${getSubstringIndex$$module$build$src$generators$javascript$text("sequence",e,"at2")} + 1;
|
||||
return sequence.slice(start, end);
|
||||
}
|
||||
`)+"("+g+(d==="FROM_END"||d==="FROM_START"?", "+f:"")+(e==="FROM_END"||e==="FROM_START"?", "+a:"")+")";return[g,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_changeCase$$module$build$src$generators$javascript$text=function(a,b){const c={UPPERCASE:".toUpperCase()",LOWERCASE:".toLowerCase()",TITLECASE:null}[a.getFieldValue("CASE")];a=b.valueToCode(a,"TEXT",c?Order$$module$build$src$generators$javascript$javascript_generator.MEMBER:Order$$module$build$src$generators$javascript$javascript_generator.NONE)||
|
||||
"''";return[c?a+c:b.provideFunction_("textToTitleCase",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(str) {
|
||||
return str.replace(/\\S+/g,
|
||||
function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();});
|
||||
}
|
||||
`)+"("+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_trim$$module$build$src$generators$javascript$text=function(a,b){const c={LEFT:".replace(/^[\\s\\xa0]+/, '')",RIGHT:".replace(/[\\s\\xa0]+$/, '')",BOTH:".trim()"}[a.getFieldValue("MODE")];return[(b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+c,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_print$$module$build$src$generators$javascript$text=
|
||||
function(a,b){return"window.alert("+(b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''")+");\n"},text_prompt_ext$$module$build$src$generators$javascript$text=function(a,b){b="window.prompt("+(a.getField("TEXT")?b.quote_(a.getFieldValue("TEXT")):b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''")+")";a.getFieldValue("TYPE")==="NUMBER"&&(b="Number("+b+")");return[b,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},
|
||||
text_count$$module$build$src$generators$javascript$text=function(a,b){const c=b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=b.valueToCode(a,"SUB",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return[b.provideFunction_("textCount",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) {
|
||||
if (needle.length === 0) {
|
||||
return haystack.length + 1;
|
||||
} else {
|
||||
return haystack.split(needle).length - 1;
|
||||
}
|
||||
}
|
||||
`)+"("+c+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_replace$$module$build$src$generators$javascript$text=function(a,b){const c=b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''",d=b.valueToCode(a,"FROM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return[b.provideFunction_("textReplace",
|
||||
`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) {
|
||||
needle = needle.replace(/([-()\\[\\]{}+?*.$\\^|,:#<!\\\\])/g, '\\\\$1')
|
||||
.replace(/\\x08/g, '\\\\x08');
|
||||
return haystack.replace(new RegExp(needle, 'g'), replacement);
|
||||
}
|
||||
`)+"("+c+", "+d+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_reverse$$module$build$src$generators$javascript$text=function(a,b){return[(b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+".split('').reverse().join('')",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},variables_get$$module$build$src$generators$javascript$variables=function(a,b){return[b.getVariableName(a.getFieldValue("VAR")),
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},variables_set$$module$build$src$generators$javascript$variables=function(a,b){const c=b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0";return b.getVariableName(a.getFieldValue("VAR"))+" = "+c+";\n"},Order$$module$build$src$generators$javascript$javascript_generator;
|
||||
(function(a){a[a.ATOMIC=0]="ATOMIC";a[a.NEW=1.1]="NEW";a[a.MEMBER=1.2]="MEMBER";a[a.FUNCTION_CALL=2]="FUNCTION_CALL";a[a.INCREMENT=3]="INCREMENT";a[a.DECREMENT=3]="DECREMENT";a[a.BITWISE_NOT=4.1]="BITWISE_NOT";a[a.UNARY_PLUS=4.2]="UNARY_PLUS";a[a.UNARY_NEGATION=4.3]="UNARY_NEGATION";a[a.LOGICAL_NOT=4.4]="LOGICAL_NOT";a[a.TYPEOF=4.5]="TYPEOF";a[a.VOID=4.6]="VOID";a[a.DELETE=4.7]="DELETE";a[a.AWAIT=4.8]="AWAIT";a[a.EXPONENTIATION=5]="EXPONENTIATION";a[a.MULTIPLICATION=5.1]="MULTIPLICATION";a[a.DIVISION=
|
||||
5.2]="DIVISION";a[a.MODULUS=5.3]="MODULUS";a[a.SUBTRACTION=6.1]="SUBTRACTION";a[a.ADDITION=6.2]="ADDITION";a[a.BITWISE_SHIFT=7]="BITWISE_SHIFT";a[a.RELATIONAL=8]="RELATIONAL";a[a.IN=8]="IN";a[a.INSTANCEOF=8]="INSTANCEOF";a[a.EQUALITY=9]="EQUALITY";a[a.BITWISE_AND=10]="BITWISE_AND";a[a.BITWISE_XOR=11]="BITWISE_XOR";a[a.BITWISE_OR=12]="BITWISE_OR";a[a.LOGICAL_AND=13]="LOGICAL_AND";a[a.LOGICAL_OR=14]="LOGICAL_OR";a[a.CONDITIONAL=15]="CONDITIONAL";a[a.ASSIGNMENT=16]="ASSIGNMENT";a[a.YIELD=17]="YIELD";
|
||||
a[a.COMMA=18]="COMMA";a[a.NONE=99]="NONE"})(Order$$module$build$src$generators$javascript$javascript_generator||(Order$$module$build$src$generators$javascript$javascript_generator={}));
|
||||
var JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator=class extends $.CodeGenerator$$module$build$src$core$generator{constructor(a="JavaScript"){super(a);this.ORDER_OVERRIDES=[[Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL,Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],[Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL],
|
||||
[Order$$module$build$src$generators$javascript$javascript_generator.MEMBER,Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],[Order$$module$build$src$generators$javascript$javascript_generator.MEMBER,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL],[Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT,Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT],[Order$$module$build$src$generators$javascript$javascript_generator.MULTIPLICATION,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.MULTIPLICATION],[Order$$module$build$src$generators$javascript$javascript_generator.ADDITION,Order$$module$build$src$generators$javascript$javascript_generator.ADDITION],[Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_AND,Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_AND],[Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_OR,Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_OR]];
|
||||
this.isInitialized=!1;for(const b in Order$$module$build$src$generators$javascript$javascript_generator)a=Order$$module$build$src$generators$javascript$javascript_generator[b],typeof a!=="string"&&(this["ORDER_"+b]=a);this.addReservedWords("break,case,catch,class,const,continue,debugger,default,delete,do,else,export,extends,finally,for,function,if,import,in,instanceof,new,return,super,switch,this,throw,try,typeof,var,void,while,with,yield,enum,implements,interface,let,package,private,protected,public,static,await,null,true,false,arguments,"+
|
||||
Object.getOwnPropertyNames(globalThis).join(","))}init(a){super.init(a);this.nameDB_?this.nameDB_.reset():this.nameDB_=new $.Names$$module$build$src$core$names(this.RESERVED_WORDS_);this.nameDB_.setVariableMap(a.getVariableMap());this.nameDB_.populateVariables(a);this.nameDB_.populateProcedures(a);const b=[];var c=$.allDeveloperVariables$$module$build$src$core$variables(a);for(let d=0;d<c.length;d++)b.push(this.nameDB_.getName(c[d],$.NameType$$module$build$src$core$names.DEVELOPER_VARIABLE));a=$.allUsedVarModels$$module$build$src$core$variables(a);
|
||||
for(c=0;c<a.length;c++)b.push(this.nameDB_.getName(a[c].getId(),$.NameType$$module$build$src$core$names.VARIABLE));b.length&&(this.definitions_.variables="var "+b.join(", ")+";");this.isInitialized=!0}finish(a){const b=Object.values(this.definitions_);super.finish(a);this.isInitialized=!1;this.nameDB_.reset();return b.join("\n\n")+"\n\n\n"+a}scrubNakedValue(a){return a+";\n"}quote_(a){a=a.replace(/\\/g,"\\\\").replace(/\n/g,"\\\n").replace(/'/g,"\\'");return"'"+a+"'"}multiline_quote_(a){return a.split(/\n/g).map(this.quote_).join(" + '\\n' +\n")}scrub_(a,
|
||||
b,c=!1){let d="";if(!a.outputConnection||!a.outputConnection.targetConnection){var e=a.getCommentText();e&&(e=$.wrap$$module$build$src$core$utils$string(e,this.COMMENT_WRAP-3),d+=this.prefixLines(e+"\n","// "));for(let f=0;f<a.inputList.length;f++)a.inputList[f].type===$.inputTypes$$module$build$src$core$inputs$input_types.VALUE&&(e=a.inputList[f].connection.targetBlock())&&(e=this.allNestedComments(e))&&(d+=this.prefixLines(e,"// "))}a=a.nextConnection&&a.nextConnection.targetBlock();c=c?"":this.blockToCode(a);
|
||||
return d+b+c}getAdjusted(a,b,c=0,d=!1,e=Order$$module$build$src$generators$javascript$javascript_generator.NONE){a.workspace.options.oneBasedIndex&&c--;const f=a.workspace.options.oneBasedIndex?"1":"0";let g=e;c>0?g=Order$$module$build$src$generators$javascript$javascript_generator.ADDITION:c<0?g=Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION:d&&(g=Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION);a=this.valueToCode(a,b,g)||f;if(c===
|
||||
0&&!d)return a;if($.isNumber$$module$build$src$core$utils$string(a))return a=String(Number(a)+c),d&&(a=String(-Number(a))),a;c>0?a=`${a} + ${c}`:c<0&&(a=`${a} - ${-c}`);d&&(a=c?`-(${a})`:`-${a}`);Math.floor(e)>=Math.floor(g)&&(a=`(${a})`);return a}},module$build$src$generators$javascript$javascript_generator={};module$build$src$generators$javascript$javascript_generator.JavascriptGenerator=JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator;
|
||||
module$build$src$generators$javascript$javascript_generator.Order=Order$$module$build$src$generators$javascript$javascript_generator;var getSubstringIndex$$module$build$src$generators$javascript$lists=function(a,b,c){return b==="FIRST"?"0":b==="FROM_END"?a+".length - 1 - "+c:b==="LAST"?a+".length - 1":c},module$build$src$generators$javascript$lists={};module$build$src$generators$javascript$lists.lists_create_empty=lists_create_empty$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_create_with=lists_create_with$$module$build$src$generators$javascript$lists;
|
||||
module$build$src$generators$javascript$lists.lists_getIndex=lists_getIndex$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_getSublist=lists_getSublist$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_indexOf=lists_indexOf$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_isEmpty=lists_isEmpty$$module$build$src$generators$javascript$lists;
|
||||
module$build$src$generators$javascript$lists.lists_length=lists_length$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_repeat=lists_repeat$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_reverse=lists_reverse$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_setIndex=lists_setIndex$$module$build$src$generators$javascript$lists;
|
||||
module$build$src$generators$javascript$lists.lists_sort=lists_sort$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_split=lists_split$$module$build$src$generators$javascript$lists;var controls_ifelse$$module$build$src$generators$javascript$logic=controls_if$$module$build$src$generators$javascript$logic,module$build$src$generators$javascript$logic={};module$build$src$generators$javascript$logic.controls_if=controls_if$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.controls_ifelse=controls_if$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_boolean=logic_boolean$$module$build$src$generators$javascript$logic;
|
||||
module$build$src$generators$javascript$logic.logic_compare=logic_compare$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_negate=logic_negate$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_null=logic_null$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_operation=logic_operation$$module$build$src$generators$javascript$logic;
|
||||
module$build$src$generators$javascript$logic.logic_ternary=logic_ternary$$module$build$src$generators$javascript$logic;var controls_repeat$$module$build$src$generators$javascript$loops=controls_repeat_ext$$module$build$src$generators$javascript$loops,module$build$src$generators$javascript$loops={};module$build$src$generators$javascript$loops.controls_flow_statements=controls_flow_statements$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_for=controls_for$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_forEach=controls_forEach$$module$build$src$generators$javascript$loops;
|
||||
module$build$src$generators$javascript$loops.controls_repeat=controls_repeat_ext$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_repeat_ext=controls_repeat_ext$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_whileUntil=controls_whileUntil$$module$build$src$generators$javascript$loops;var math_round$$module$build$src$generators$javascript$math=math_single$$module$build$src$generators$javascript$math,math_trig$$module$build$src$generators$javascript$math=math_single$$module$build$src$generators$javascript$math,module$build$src$generators$javascript$math={};module$build$src$generators$javascript$math.math_arithmetic=math_arithmetic$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_atan2=math_atan2$$module$build$src$generators$javascript$math;
|
||||
module$build$src$generators$javascript$math.math_change=math_change$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_constant=math_constant$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_constrain=math_constrain$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_modulo=math_modulo$$module$build$src$generators$javascript$math;
|
||||
module$build$src$generators$javascript$math.math_number=math_number$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_number_property=math_number_property$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_on_list=math_on_list$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_random_float=math_random_float$$module$build$src$generators$javascript$math;
|
||||
module$build$src$generators$javascript$math.math_random_int=math_random_int$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_round=math_single$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_single=math_single$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_trig=math_single$$module$build$src$generators$javascript$math;var procedures_defnoreturn$$module$build$src$generators$javascript$procedures=procedures_defreturn$$module$build$src$generators$javascript$procedures,module$build$src$generators$javascript$procedures={};module$build$src$generators$javascript$procedures.procedures_callnoreturn=procedures_callnoreturn$$module$build$src$generators$javascript$procedures;module$build$src$generators$javascript$procedures.procedures_callreturn=procedures_callreturn$$module$build$src$generators$javascript$procedures;
|
||||
module$build$src$generators$javascript$procedures.procedures_defnoreturn=procedures_defreturn$$module$build$src$generators$javascript$procedures;module$build$src$generators$javascript$procedures.procedures_defreturn=procedures_defreturn$$module$build$src$generators$javascript$procedures;module$build$src$generators$javascript$procedures.procedures_ifreturn=procedures_ifreturn$$module$build$src$generators$javascript$procedures;var strRegExp$$module$build$src$generators$javascript$text=/^\s*'([^']|\\')*'\s*$/,forceString$$module$build$src$generators$javascript$text=function(a){return strRegExp$$module$build$src$generators$javascript$text.test(a)?[a,Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]:["String("+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},getSubstringIndex$$module$build$src$generators$javascript$text=function(a,b,c){return b==="FIRST"?"0":
|
||||
b==="FROM_END"?a+".length - 1 - "+c:b==="LAST"?a+".length - 1":c},text_prompt$$module$build$src$generators$javascript$text=text_prompt_ext$$module$build$src$generators$javascript$text,module$build$src$generators$javascript$text={};module$build$src$generators$javascript$text.text=text$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_append=text_append$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_changeCase=text_changeCase$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_charAt=text_charAt$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_count=text_count$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_getSubstring=text_getSubstring$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_indexOf=text_indexOf$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_isEmpty=text_isEmpty$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_join=text_join$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_length=text_length$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_print=text_print$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_prompt=text_prompt_ext$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_prompt_ext=text_prompt_ext$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_replace=text_replace$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_reverse=text_reverse$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_trim=text_trim$$module$build$src$generators$javascript$text;var module$build$src$generators$javascript$variables={};module$build$src$generators$javascript$variables.variables_get=variables_get$$module$build$src$generators$javascript$variables;module$build$src$generators$javascript$variables.variables_set=variables_set$$module$build$src$generators$javascript$variables;var module$build$src$generators$javascript$variables_dynamic={};module$build$src$generators$javascript$variables_dynamic.variables_get_dynamic=variables_get$$module$build$src$generators$javascript$variables;module$build$src$generators$javascript$variables_dynamic.variables_set_dynamic=variables_set$$module$build$src$generators$javascript$variables;var javascriptGenerator$$module$build$src$generators$javascript=new JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator,generators$$module$build$src$generators$javascript=Object.assign({},module$build$src$generators$javascript$lists,module$build$src$generators$javascript$logic,module$build$src$generators$javascript$loops,module$build$src$generators$javascript$math,module$build$src$generators$javascript$procedures,module$build$src$generators$javascript$text,module$build$src$generators$javascript$variables,
|
||||
module$build$src$generators$javascript$variables_dynamic);for(const a in generators$$module$build$src$generators$javascript)javascriptGenerator$$module$build$src$generators$javascript.forBlock[a]=generators$$module$build$src$generators$javascript[a];var module$build$src$generators$javascript={};module$build$src$generators$javascript.JavascriptGenerator=JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator;module$build$src$generators$javascript.Order=Order$$module$build$src$generators$javascript$javascript_generator;
|
||||
module$build$src$generators$javascript.javascriptGenerator=javascriptGenerator$$module$build$src$generators$javascript;
|
||||
module$build$src$generators$javascript.__namespace__=$;
|
||||
return module$build$src$generators$javascript;
|
||||
}));
|
||||
|
||||
|
||||
//# sourceMappingURL=javascript_compressed.js.map
|
||||
575
crumbblocks/lipo_6s_charger.html
Normal file
575
crumbblocks/lipo_6s_charger.html
Normal file
@@ -0,0 +1,575 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Reject bei Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer, ~3,2/Z) • 22,2 V (Nominal) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20, // Full
|
||||
v_storage: 3.80, // Storage
|
||||
v_hv: 4.35, // HV
|
||||
v_min: 3.20, // "ehrlich leer"
|
||||
drift_reject: 0.30, // harte Zellabweichung ablehnen
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01, // V/Z pro Tick (vereinfacht)
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Vordefinierte Variablen
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output Helpers
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){
|
||||
const el = $('#output');
|
||||
el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// === Patch 1: text_print -> Panel & Alerts abfangen ===
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// XML util
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// === DOC XML ===
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z → 25,2V • Storage 3,80V/Z → 22,8V • HV 4,35V/Z → 26,1V • Ende ~3,20V/Z → 19,2V.</field></block></value></block>
|
||||
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance). Packs mit harten Unterschieden nicht nutzen.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T1: Storage (3.80V/Z) – Entladen bis Ziel, mit Clamp ===
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable><variable>i</variable><variable>new_v</variable>
|
||||
</variables>
|
||||
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value>
|
||||
</next></next></next></next>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field>
|
||||
<value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
|
||||
<block type="controls_whileUntil" x="20" y="330">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_forEach">
|
||||
<field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
<next>
|
||||
<block type="lists_setIndex">
|
||||
<mutation at="true"></mutation>
|
||||
<field name="MODE">SET</field>
|
||||
<field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="800">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="8"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* siehe 'cells' */]" }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T2: Full (4.20V/Z) – Laden bis Ziel, mit Clamp ===
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable><variable>i</variable><variable>new_v</variable>
|
||||
</variables>
|
||||
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">charge</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.2</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value>
|
||||
</next></next></next></next>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field>
|
||||
<value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.70</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="280"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
|
||||
<block type="controls_whileUntil" x="20" y="320">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_forEach">
|
||||
<field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
<next>
|
||||
<block type="lists_setIndex">
|
||||
<mutation at="true"></mutation>
|
||||
<field name="MODE">SET</field>
|
||||
<field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">full_reached</field></block></value></block></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="790">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="8"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* siehe 'cells' */]" }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T3: Reject bei Drift >= 0.30V ===
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="350">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="10"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode": "safety_check", "max_v": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">max_v</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "min_v": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">min_v</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "delta": </field></block></value>
|
||||
<value name="ADD5"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "stop_reason": "</field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">", "advice": "Pack nicht verwenden."</field></block></value>
|
||||
<value name="ADD9"><block type="text"><field name="TEXT"> }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T4: HV 4.35V/Z – Laden bis Ziel, mit Clamp ===
|
||||
const T4_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">hv_charge</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.35</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="220"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.95</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="360"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
|
||||
<block type="controls_whileUntil" x="20" y="400">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_forEach">
|
||||
<field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
<next>
|
||||
<block type="lists_setIndex">
|
||||
<mutation at="true"></mutation>
|
||||
<field name="MODE">SET</field>
|
||||
<field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">hv_reached</field></block></value></block></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="820">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === Zufalls-Startwerte (nur Inspektion) ===
|
||||
function randomDemoXml(){
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
return `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// === Buttons ===
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Reject bei Drift ≥ 0,30V. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{ loadXml(randomDemoXml()); setOutput('🎲 Zufallspack geladen.'); };
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// === Patch 2: Watchdog im Run-Handler ===
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
code = guardHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit (sichtbar für Kids/Trainer) -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (<i>≥0,30 V</i>) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
453
crumbblocks/lipo_6s_charger_sim_safe_v2.html
Normal file
453
crumbblocks/lipo_6s_charger_sim_safe_v2.html
Normal file
@@ -0,0 +1,453 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE v3)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE v3)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20,
|
||||
v_storage: 3.80,
|
||||
v_hv: 4.35,
|
||||
v_min: 3.20,
|
||||
drift_reject: 0.30,
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01,
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen, die in den Demos genutzt werden
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output/Print
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// text_print → Panel & Alerts abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">charge</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.2</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.70</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="280"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="320"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">full_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="790"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T4_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">hv_charge</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.35</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="220"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.95</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="360"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="400"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">hv_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="820"><value name="TEXT"><block type="text_join"><mutation items="4"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
function randomDemoXml(){
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
return `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{ loadXml(randomDemoXml()); setOutput('🎲 Zufallspack geladen.'); };
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// RUN: Watchdog + Alerts→Panel
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
code = guardHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
322
crumbblocks/lipo_6s_charger_sim_safe_v4.html
Normal file
322
crumbblocks/lipo_6s_charger_sim_safe_v4.html
Normal file
@@ -0,0 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE v4)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE v4)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20,
|
||||
v_storage: 3.80,
|
||||
v_hv: 4.35,
|
||||
v_min: 3.20,
|
||||
drift_reject: 0.30,
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01,
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen, die in den Demos genutzt werden
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output/Print
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// text_print → Panel & Alerts abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos (kompakt gehalten) ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T2_XML = /* wie T1, nur 4.2V + dv=+0.01 + MIN/Clamp im If */;
|
||||
const T3_XML = /* Drift-Check */;
|
||||
const T4_XML = /* HV 4.35V */;
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// === RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix ===
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
|
||||
// 1) Watchdog in jede while(...)
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
|
||||
// 2) Alerts → Panel
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
|
||||
// 3) Erzwinge Schreibvorgang + Clamp nach "new_v = cells[...] + dv;"
|
||||
// - Variante mit 1-basiertem Index: cells[(i - 1)]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
// - Variante mit 0-basiertem Index: cells[i]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
322
crumbblocks/lipo_6s_charger_sim_safe_v5.html
Normal file
322
crumbblocks/lipo_6s_charger_sim_safe_v5.html
Normal file
@@ -0,0 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE v4)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE v4)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20,
|
||||
v_storage: 3.80,
|
||||
v_hv: 4.35,
|
||||
v_min: 3.20,
|
||||
drift_reject: 0.30,
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01,
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen, die in den Demos genutzt werden
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output/Print
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// text_print → Panel & Alerts abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos (kompakt gehalten) ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T2_XML = /* wie T1, nur 4.2V + dv=+0.01 + MIN/Clamp im If */;
|
||||
const T3_XML = /* Drift-Check */;
|
||||
const T4_XML = /* HV 4.35V */;
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// === RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix ===
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
|
||||
// 1) Watchdog in jede while(...)
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
|
||||
// 2) Alerts → Panel
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
|
||||
// 3) Erzwinge Schreibvorgang + Clamp nach "new_v = cells[...] + dv;"
|
||||
// - Variante mit 1-basiertem Index: cells[(i - 1)]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
// - Variante mit 0-basiertem Index: cells[i]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
409
crumbblocks/lipo_6s_charger_sim_safe_v6.html
Normal file
409
crumbblocks/lipo_6s_charger_sim_safe_v6.html
Normal file
@@ -0,0 +1,409 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (DIAG v6)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
:root{--bg:#0f1115;--panel:#11151a;--txt:#e6edf3;--accent:#4caf50;--muted:#9aa4b2;--line:#21262d;--bad:#ff4d4f}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%;border:1px solid var(--line);border-radius:10px}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto;border:1px solid var(--line)}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto;border:1px solid var(--line)}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
.fail{color:var(--bad);font-weight:700}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (DIAG v6)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear"class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg">Diag lädt …</div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOOLBOX (wird erst genutzt, wenn Blockly geladen ist) -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// ---------- Diagnose: Fehler ins Panel ----------
|
||||
const out = document.getElementById('output');
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
function setOutput(t){ out.textContent = t }
|
||||
function appendOutput(t){ out.textContent += (out.textContent.endsWith('\n')?'':'\n') + t; out.scrollTop = out.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
window.addEventListener('error', (e)=> appendOutput('❌ JS-Error: ' + e.message));
|
||||
window.addEventListener('unhandledrejection', (e)=> appendOutput('❌ Promise-Rejection: ' + (e.reason && e.reason.message ? e.reason.message : e.reason)));
|
||||
|
||||
// ---------- Loader: erst lokal, dann CDN ----------
|
||||
const CDN = 'https://unpkg.com';
|
||||
const LOAD = [
|
||||
['/crumbblocks/vendor/blockly/blockly.min.js', `${CDN}/blockly/blockly.min.js`],
|
||||
['/crumbblocks/vendor/blockly/javascript.min.js', `${CDN}/blockly/javascript.min.js`],
|
||||
['/crumbblocks/vendor/blockly/msg/de.js', `${CDN}/blockly/msg/de.js`],
|
||||
];
|
||||
function loadScriptPair([localSrc, cdnSrc]){
|
||||
return new Promise((resolve, reject)=>{
|
||||
const s = document.createElement('script');
|
||||
s.src = localSrc;
|
||||
s.onload = ()=> resolve({src:localSrc, ok:true, via:'local'});
|
||||
s.onerror = ()=>{
|
||||
const c = document.createElement('script');
|
||||
c.src = cdnSrc;
|
||||
c.onload = ()=> resolve({src:cdnSrc, ok:true, via:'cdn'});
|
||||
c.onerror = ()=> reject(new Error('Laden fehlgeschlagen: ' + localSrc + ' und ' + cdnSrc));
|
||||
document.head.appendChild(c);
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Self-Test ----------
|
||||
async function selfTest(){
|
||||
const diag = { js_ok:true, blockly_loaded:false, eval_ok:null, scripts:[], csp_note:null };
|
||||
// Lade Reihenfolge wahren
|
||||
for (const p of LOAD){
|
||||
try {
|
||||
const res = await loadScriptPair(p);
|
||||
diag.scripts.push(res);
|
||||
} catch (e) {
|
||||
diag.scripts.push({src:p, ok:false, via:'none', err:String(e)});
|
||||
}
|
||||
}
|
||||
diag.blockly_loaded = !!window.Blockly;
|
||||
try { new Function('return 42')(); diag.eval_ok = true; }
|
||||
catch(e){ diag.eval_ok = 'blocked: ' + e.message; diag.csp_note = 'CSP erlaubt kein unsafe-eval. Siehe Hinweise unten.'; }
|
||||
|
||||
const CFG = {
|
||||
cells: 6, v_full:4.20, v_storage:3.80, v_hv:4.35, v_min:3.20,
|
||||
drift_reject:0.30, tick_s:10, dv_charge:0.01, dv_discharge:-0.01,
|
||||
capacity_mAh:2200, c_rate:1.0
|
||||
};
|
||||
cfgEl.textContent = 'Diag:\n' + JSON.stringify(diag, null, 2) + '\n\nCFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
if (!diag.blockly_loaded){
|
||||
appendOutput('❌ Blockly nicht geladen. Prüfe /crumbblocks/vendor/... oder Internet/CSP.');
|
||||
appendOutput('Tipp: Siehe „Lokale Vendor-Dateien“ unten.');
|
||||
return;
|
||||
}
|
||||
bootBlockly(); // erst jetzt initialisieren
|
||||
}
|
||||
|
||||
// ---------- Blockly-Init (nach bestandenem Self-Test) ----------
|
||||
function bootBlockly(){
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// print ins Panel + Alert abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// 1-basige Listen sauber generieren
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// Helpers
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// Demos (kompakt)
|
||||
const DOC_XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T1_XML = `<!-- Storage -->
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T2_XML = T1_XML
|
||||
.replace('storage','charge')
|
||||
.replace('3.8','4.2')
|
||||
.replace('"-0.01"','"0.01"')
|
||||
.replace('storage_reached','full_reached')
|
||||
.replace('"GT"','"LT"');
|
||||
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T4_XML = T2_XML
|
||||
.replace('charge','hv_charge')
|
||||
.replace('4.2','4.35')
|
||||
.replace('full_reached','hv_reached')
|
||||
.replace(/3\.7[0-9]?/g,'3.96'); // Start näher an HV
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
}
|
||||
|
||||
// Start
|
||||
selfTest();
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
|
||||
<!-- Hinweise / Fallback -->
|
||||
<div style="padding:.6rem .8rem;color:var(--muted)">
|
||||
<b>Lokale Vendor-Dateien (empfohlen, falls CDN/CSP blockt):</b><br/>
|
||||
<code>mkdir -p /var/www/crumbblocks/vendor/blockly</code><br/>
|
||||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/blockly.min.js https://unpkg.com/blockly/blockly.min.js</code><br/>
|
||||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/javascript.min.js https://unpkg.com/blockly/javascript.min.js</code><br/>
|
||||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/msg/de.js https://unpkg.com/blockly/msg/de.js</code><br/><br/>
|
||||
|
||||
<b>CSP (falls Eval/Inline blockiert → „nix passiert“):</b><br/>
|
||||
Erweitere Header minimal für die Simulation:
|
||||
<pre>Content-Security-Policy: default-src 'self';
|
||||
script-src 'self' https://unpkg.com 'unsafe-inline' 'unsafe-eval';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src 'self';
|
||||
img-src 'self' data:;
|
||||
font-src 'self' data:;</pre>
|
||||
(Nur für diese Lernseite. Produktion später strenger machen.)
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
500
crumbblocks/lipo_6s_charger_sim_safe_v7.html
Normal file
500
crumbblocks/lipo_6s_charger_sim_safe_v7.html
Normal file
@@ -0,0 +1,500 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (DIAG v7)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
:root{--bg:#0f1115;--panel:#11151a;--txt:#e6edf3;--accent:#4caf50;--muted:#9aa4b2;--line:#21262d;--bad:#ff4d4f}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%;border:1px solid var(--line);border-radius:10px}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto;border:1px solid var(--line)}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto;border:1px solid var(--line)}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (DIAG v7)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear"class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg">Diag lädt …</div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z). Nur Simulation.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOOLBOX -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// ---------- Diagnose & Output ----------
|
||||
const out = document.getElementById('output');
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
function setOutput(t){ out.textContent = t }
|
||||
function appendOutput(t){ out.textContent += (out.textContent.endsWith('\n')?'':'\n') + t; out.scrollTop = out.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
window.addEventListener('error', e => appendOutput('❌ JS-Error: ' + e.message));
|
||||
window.addEventListener('unhandledrejection', e => appendOutput('❌ Promise: ' + (e.reason && e.reason.message ? e.reason.message : e.reason)));
|
||||
|
||||
// ---------- Loader: erst lokal, dann CDN (klassische, nicht-ESM Bundles) ----------
|
||||
const CDN = 'https://unpkg.com';
|
||||
const LOAD = [
|
||||
['/crumbblocks/vendor/blockly/blockly_compressed.js', `${CDN}/blockly/blockly_compressed.js`],
|
||||
['/crumbblocks/vendor/blockly/blocks_compressed.js', `${CDN}/blockly/blocks_compressed.js`],
|
||||
['/crumbblocks/vendor/blockly/javascript_compressed.js', `${CDN}/blockly/javascript_compressed.js`],
|
||||
['/crumbblocks/vendor/blockly/msg/de.js', `${CDN}/blockly/msg/de.js`],
|
||||
];
|
||||
function loadScriptPair([localSrc, cdnSrc]){
|
||||
return new Promise((resolve, reject)=>{
|
||||
const s = document.createElement('script');
|
||||
s.src = localSrc;
|
||||
s.onload = ()=> resolve({src:localSrc, ok:true, via:'local'});
|
||||
s.onerror = ()=>{
|
||||
const c = document.createElement('script');
|
||||
c.src = cdnSrc;
|
||||
c.onload = ()=> resolve({src:cdnSrc, ok:true, via:'cdn'});
|
||||
c.onerror = ()=> reject(new Error('Laden fehlgeschlagen: ' + localSrc + ' und ' + cdnSrc));
|
||||
document.head.appendChild(c);
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function selfTest(){
|
||||
const diag = { blockly_loaded:false, generator_loaded:false, eval_ok:null, scripts:[], csp_note:null };
|
||||
for (const p of LOAD){
|
||||
try { diag.scripts.push(await loadScriptPair(p)); }
|
||||
catch(e){ diag.scripts.push({src:p, ok:false, via:'none', err:String(e)}); }
|
||||
}
|
||||
diag.blockly_loaded = !!window.Blockly;
|
||||
diag.generator_loaded = !!(window.Blockly && Blockly.JavaScript);
|
||||
try { new Function('return 42')(); diag.eval_ok = true; }
|
||||
catch(e){ diag.eval_ok = 'blocked: ' + e.message; diag.csp_note = 'CSP blockiert eval/new Function.'; }
|
||||
const CFG = { cells:6, v_full:4.20, v_storage:3.80, v_hv:4.35, v_min:3.20, drift_reject:0.30, tick_s:10, dv_charge:0.01, dv_discharge:-0.01, capacity_mAh:2200, c_rate:1.0 };
|
||||
cfgEl.textContent = 'Diag:\n' + JSON.stringify(diag, null, 2) + '\n\nCFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
if (!diag.blockly_loaded || !diag.generator_loaded){ appendOutput('❌ Blockly/Gernerator fehlt. Prüfe Loader/CDN/CSP.'); return; }
|
||||
bootBlockly();
|
||||
}
|
||||
|
||||
// ---------- Blockly-Init ----------
|
||||
function bootBlockly(){
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Lern-Variablen
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// print → Panel
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// 1-basiert Listen → richtig lesen/schreiben
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T1: Storage 3.8V/Z
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T2: Full 4.2V/Z (dv +, MIN<target)
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">charge</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.2</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.70</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="280"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="320"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">full_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="790"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T3: Drift-Check
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T4: HV 4.35V/Z (dv +, MIN<target)
|
||||
const T4_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">hv_charge</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.35</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="220"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.95</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="360"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="400"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">hv_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="820"><value name="TEXT"><block type="text_join"><mutation items="4"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// RUN: Watchdog + Alerts→Panel + Writeback & Clamp
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
// Watchdog in jede while
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
// Alerts → Panel
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
// Writeback & Clamp
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
}
|
||||
|
||||
// Start
|
||||
selfTest();
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Nur Simulation. Echte LiPos ausschließlich mit geeignetem Ladegerät & Balancer laden. Packs mit Zell-Delta ≥ 0,30 V nicht verwenden.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
22
crumbblocks/mock_ws_server.js
Normal file
22
crumbblocks/mock_ws_server.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Minimal WebSocket echo server for testing Painter stream
|
||||
// Usage: npm i ws; node mock_ws_server.js
|
||||
const WebSocket = require('ws');
|
||||
const wss = new WebSocket.Server({ port: 9980 });
|
||||
console.log('WS server on ws://localhost:9980');
|
||||
wss.on('connection', (ws)=>{
|
||||
console.log('client connected');
|
||||
ws.on('message', (msg)=>{
|
||||
try{
|
||||
const obj = JSON.parse(msg);
|
||||
// log only essentials to keep console tidy
|
||||
if(obj.type==='point'){
|
||||
process.stdout.write(`• point ${obj.p.x.toFixed(1)},${obj.p.y.toFixed(1)}\r`);
|
||||
}else{
|
||||
console.log(obj.type);
|
||||
}
|
||||
}catch(e){}
|
||||
// optionally echo back
|
||||
// ws.send(msg);
|
||||
});
|
||||
ws.on('close', ()=>console.log('client closed'));
|
||||
});
|
||||
395
crumbblocks/painter.html
Normal file
395
crumbblocks/painter.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One‑Liner Painter (Multi‑Stroke) – draw → export SVG + Motion JSON</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg: #0b0b10; --panel: #12121a; --muted:#8d93a1; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:340px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:900px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0f131a;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0f0f10}
|
||||
.muted{color:var(--muted);font-size:.85rem}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:220px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One‑Liner Painter (Multi‑Stroke) → SVG & Motion JSON • a→Spirale→y • Start/Stop/Dynamik</h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel" aria-labelledby="controlsTitle">
|
||||
<h2 id="controlsTitle" style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken setzen (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits 1+1 setzen</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop‑Marker</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke
|
||||
<input type="range" id="stroke" min="4" max="28" step="1" value="12">
|
||||
</label>
|
||||
<label>Glättung
|
||||
<input type="range" id="smooth" min="0" max="10" step="1" value="2">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke‑Farbe A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke‑Farbe B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="dynWidth"> <span>Breite ~ Geschwindigkeit (JSON‑only)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="captureJSON" checked> <span>Motion JSON mitschreiben</span></label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich‑Liste"></ul>
|
||||
<p class="muted">Neuer Stroke beginnt mit <span class="kbd">Maus/Touch‑Down</span>. Ende bei Loslassen. Mehrere Klicks/Wege werden einzeln erfasst.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion‑JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary class="muted">A11y/Meta</summary>
|
||||
<label>Titel <input id="title" type="text" value="Omi Omega – One‑Liner"></label>
|
||||
<label>Beschreibung
|
||||
<textarea id="desc" rows="3">Cursives kleines a wird zur Spirale; zwei Einsen im a; y hält zusammen; Funken ringsum.</textarea>
|
||||
</label>
|
||||
<label>Tag (z. B. Datum/Hashtag) <input id="tag" type="text" value="22.08.25 #CRUMB"></label>
|
||||
</details>
|
||||
|
||||
<p class="muted">Tipps: Ziehen = zeichnen. <span class="kbd">Shift</span> = Funken. <span class="kbd">Z</span> = Undo. <span class="kbd">S</span> speichern.</p>
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One‑Liner</title>
|
||||
<desc id="svgDesc">Mehrere Pfade mit Start/Stop und Zeitdynamik: a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
<style>
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<text id="tagText" x="860" y="760" font-family="ui-monospace,monospace" font-size="22" fill="#cfd5e3"></text>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span class="muted">Mehrere Klicks & Pfade sind erlaubt. Start/Stop wird als Ereignis erfasst; Geschwindigkeiten landen im Motion‑JSON.</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const grad = document.getElementById('gradA2B');
|
||||
const tagText = document.getElementById('tagText');
|
||||
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const smoothInp = document.getElementById('smooth');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
const dynWidth = document.getElementById('dynWidth');
|
||||
const captureJSON = document.getElementById('captureJSON');
|
||||
const titleInp = document.getElementById('title');
|
||||
const descInp = document.getElementById('desc');
|
||||
const tagInp = document.getElementById('tag');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null; // {id, mode, color, width, points:[{x,y,t}], pathEl}
|
||||
const strokes = []; // array of strokes
|
||||
let t0 = null; // session start time
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const ctm = stage.getScreenCTM().inverse();
|
||||
const p = pt.matrixTransform(ctm);
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function updateMeta(){
|
||||
stage.querySelector('title').textContent = titleInp.value.trim() || 'One‑Liner';
|
||||
stage.querySelector('desc').textContent = descInp.value.trim() || '';
|
||||
tagText.textContent = tagInp.value.trim();
|
||||
}
|
||||
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${Math.round(s.duration)}ms • ~${Math.round(s.length)}px`;
|
||||
li.append('Stroke', sel, sw, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx];
|
||||
if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
|
||||
function toPathD(points){
|
||||
if(points.length===0) return '';
|
||||
const sm = +smoothInp.value;
|
||||
if(points.length<3 || sm===0){
|
||||
const p0 = points[0];
|
||||
let d = `M ${p0.x} ${p0.y}`;
|
||||
for(let i=1;i<points.length;i++){ const p=points[i]; d += ` L ${p.x} ${p.y}`; }
|
||||
return d;
|
||||
}
|
||||
// simple smoothing: use quadratic Beziers between midpoints
|
||||
let d = `M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=1;i<points.length-1;i++){
|
||||
const p0 = points[i];
|
||||
const p1 = points[i+1];
|
||||
const mx = (p0.x + p1.x)/2; const my = (p0.y + p1.y)/2;
|
||||
d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
|
||||
}
|
||||
const last = points[points.length-1]; d += ` L ${last.x} ${last.y}`;
|
||||
return d;
|
||||
}
|
||||
|
||||
function startStroke(e){
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
drawing = true;
|
||||
if(t0===null) t0 = now();
|
||||
const t = now() - t0;
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[{...pt,t}], pathEl, start:t, duration:0, length:0 };
|
||||
applyStrokeStyle(curStroke);
|
||||
updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
window.addEventListener('pointerup', endStroke, {once:true});
|
||||
}
|
||||
|
||||
function moveStroke(e){
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const t = now() - t0;
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push({...p,t});
|
||||
updatePath(curStroke);
|
||||
}
|
||||
}
|
||||
|
||||
function endStroke(){
|
||||
if(!curStroke) return;
|
||||
drawing = false;
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = pts.length? (pts[pts.length-1].t - pts[0].t) : 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
|
||||
function updatePath(s){ s.pathEl.setAttribute('d', toPathD(s.points)); s.pathEl.setAttribute('stroke-width', s.width); }
|
||||
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const len = Math.max(14, parseInt(strokeInp.value,10)*1.5);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
function exportSVGString(){
|
||||
updateMeta();
|
||||
const clone = stage.cloneNode(true);
|
||||
// inline live colors
|
||||
clone.querySelector('#sparks')?.setAttribute('stroke', colorSInp.value);
|
||||
// serialize
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
${s}`;
|
||||
}
|
||||
|
||||
function motionJSON(){
|
||||
const meta = { title:titleInp.value, desc:descInp.value, tag:tagInp.value, t0: t0??0 };
|
||||
const items = strokes.map(s=>{
|
||||
// speeds
|
||||
let maxV=0, sumV=0, nV=0; const pts=s.points;
|
||||
for(let i=1;i<pts.length;i++){
|
||||
const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; const dt=(pts[i].t-pts[i-1].t)/1000; if(dt<=0) continue;
|
||||
const v = Math.hypot(dx,dy)/dt; maxV=Math.max(maxV,v); sumV+=v; nV++;
|
||||
}
|
||||
const avgV = nV? sumV/nV : 0;
|
||||
return {
|
||||
id: s.id, mode: s.mode, colorA: s.colorA, colorB: s.colorB, width: s.width,
|
||||
startMs: s.start, durationMs: s.duration, lengthPx: s.length,
|
||||
avgSpeedPxPerS: +avgV.toFixed(2), maxSpeedPxPerS: +maxV.toFixed(2),
|
||||
points: s.points.map(p=>({x:p.x,y:p.y,tMs: Math.round(p.t)}))
|
||||
};
|
||||
});
|
||||
return JSON.stringify({meta, strokes:items}, null, 2);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; listRefresh(); t0=null; });
|
||||
|
||||
copySVGBtn.addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
dlSVGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner_multi.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
copyJSONBtn.addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion‑JSON kopiert'); });
|
||||
dlJSONBtn.addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
|
||||
// Stage events
|
||||
stage.addEventListener('pointerdown', startStroke);
|
||||
stage.addEventListener('pointermove', moveStroke);
|
||||
window.addEventListener('pointerup', endStroke);
|
||||
|
||||
// Hotkeys
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='z' || e.key==='Z'){ if(strokes.length){ removeStroke(strokes.length-1); } }
|
||||
if((e.key==='s' || e.key==='S') && (e.ctrlKey||e.metaKey)){ e.preventDefault(); document.getElementById('downloadSVG').click(); }
|
||||
});
|
||||
|
||||
// Reactive
|
||||
[titleInp,descInp,tagInp].forEach(inp=>inp.addEventListener('input', updateMeta));
|
||||
[colorAInp,colorBInp,colorSInp].forEach(inp=>inp.addEventListener('input', ()=>{ tagText.setAttribute('fill','#cfd5e3'); }));
|
||||
|
||||
updateMeta();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
647
crumbblocks/painter_lines.html
Normal file
647
crumbblocks/painter_lines.html
Normal file
@@ -0,0 +1,647 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One-Liner Painter + Bezier-Editor (Fix)</title>
|
||||
<style>
|
||||
:root{ --bg:#0b0b10; --panel:#12121a; --muted:#9aa3b2; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c; }
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none;font-size:.9rem;color:var(--muted)}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:370px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:1000px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.col{display:grid;gap:10px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0b1114;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0b0b10}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:240px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a}
|
||||
.badge{font-size:.75rem;color:#0b1114;background:var(--accent);border-radius:999px;padding:2px 8px}
|
||||
.muted{color:var(--muted)}
|
||||
/* Bezier UI visuals */
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
.anchor{fill:#31e07b;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handle{fill:#ffe08a;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handleLine{stroke:#ffe08a;stroke-width:2.5;stroke-dasharray:4 4; pointer-events:none}
|
||||
.selected{filter:drop-shadow(0 0 4px #ffd166)}
|
||||
#bezierUI{ pointer-events:none } /* UI-Schicht blockiert keine Klicks auf Pfad */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One-Liner Painter → SVG/PNG/JSON • Replay • <span class="badge">Bezier-Editor (Fix)</span></h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel" aria-labelledby="controlsTitle">
|
||||
<h2 id="controlsTitle" style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits „1“ setzen</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop-Marker</span></label>
|
||||
<label class="chip"><input type="checkbox" id="frameOn" checked> <span>Crumblines-Frame</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke <input type="range" id="stroke" min="4" max="28" step="1" value="12"></label>
|
||||
<label>Sampler
|
||||
<select id="sampler">
|
||||
<option value="raw">Raw Polyline</option>
|
||||
<option value="quad">Quadratic</option>
|
||||
<option value="cubic" selected>Catmull-Rom → Cubic</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-top:6px">
|
||||
<strong>Bezier-Editor</strong>
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="editBezier"> <span>Bearbeiten</span></label>
|
||||
<label class="chip"><input type="checkbox" id="linkHandles" checked> <span>Spiegel-Griffe</span></label>
|
||||
<button class="btn" id="toBezier">Auswahl → Bezier</button>
|
||||
<button class="btn" id="splitHandles">Anker: Split</button>
|
||||
<button class="btn warn" id="delAnchor" disabled>Anker löschen</button>
|
||||
</div>
|
||||
<p class="muted">Klicke einen Stroke, dann <em>Bearbeiten</em>. Grüne Punkte = Anker, gelbe = Griffe. Ziehen. Doppelklick = Anker einfügen. <span class="kbd">Entf</span> löscht. <span class="kbd">L</span> spiegelt Griffe.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich-Liste"></ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="replay">▶︎ Replay</button>
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn" id="downloadPNG">PNG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion-JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One-Liner</title>
|
||||
<desc id="svgDesc">a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
<style>
|
||||
.selected{filter:drop-shadow(0 0 4px #ffd166)}
|
||||
</style>
|
||||
</defs>
|
||||
<rect id="frameRect" x="16.5" y="16.5" width="1167" height="767" fill="none" stroke="rgba(255,255,255,.2)" stroke-width="1.5" stroke-dasharray="6 10"/>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<g id="bezierUI"></g>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>Fixes: keine (0,0)-Griffe mehr; Drag startet sofort; UI blockiert keine Klicks.</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const bezierUI= document.getElementById('bezierUI');
|
||||
const frameRect = document.getElementById('frameRect');
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const frameOnCb = document.getElementById('frameOn');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const samplerSel= document.getElementById('sampler');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
|
||||
const editBezierCb = document.getElementById('editBezier');
|
||||
const linkHandlesCb= document.getElementById('linkHandles');
|
||||
const toBezierBtn = document.getElementById('toBezier');
|
||||
const splitBtn = document.getElementById('splitHandles');
|
||||
const delAnchorBtn = document.getElementById('delAnchor');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const dlPNGBtn = document.getElementById('downloadPNG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
const replayBtn = document.getElementById('replay');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null;
|
||||
const strokes = [];
|
||||
let t0 = null;
|
||||
let playing = false;
|
||||
let selectedStroke = null;
|
||||
let selectedAnchor = {stroke:null, idx:-1};
|
||||
let dragging = null; // {type:'anchor'|'h1'|'h2', stroke, idx, ox, oy, pid}
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const ctm = stage.getScreenCTM().inverse();
|
||||
const p = pt.matrixTransform(ctm);
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const edit = document.createElement('button'); edit.className='btn'; edit.textContent='Bearbeiten';
|
||||
edit.addEventListener('click',()=>{ selectStroke(s); editBezierCb.checked = true; drawBezierUI(); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${Math.round(s.duration)}ms • ~${Math.round(s.length)}px${s.isBezier?' • Bezier':''}`;
|
||||
li.append('Stroke', sel, sw, edit, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx];
|
||||
if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
if(selectedStroke===s){ selectedStroke=null; clearBezierUI(); }
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
p.addEventListener('pointerdown', (e)=>{
|
||||
if(editBezierCb.checked){ e.stopPropagation(); const s = strokes.find(st=>st.pathEl===p); if(s){ selectStroke(s); drawBezierUI(); } }
|
||||
});
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
|
||||
// Samplers → SVG path "d"
|
||||
function pathRaw(points){
|
||||
if(points.length===0) return '';
|
||||
const p0 = points[0];
|
||||
let d = `M ${p0.x} ${p0.y}`;
|
||||
for(let i=1;i<points.length;i++){ const p=points[i]; d += ` L ${p.x} ${p.y}`; }
|
||||
return d;
|
||||
}
|
||||
function pathQuadratic(points){
|
||||
if(points.length<2) return '';
|
||||
let d = `M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=1;i<points.length-1;i++){
|
||||
const p0 = points[i]; const p1 = points[i+1];
|
||||
const mx = (p0.x + p1.x)/2; const my = (p0.y + p1.y)/2;
|
||||
d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
|
||||
}
|
||||
const last = points[points.length-1]; d += ` L ${last.x} ${last.y}`;
|
||||
return d;
|
||||
}
|
||||
function pathCubic(points){
|
||||
if(points.length<2) return '';
|
||||
const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
|
||||
const p=(i)=>points[ clamp(i,0,points.length-1) ];
|
||||
let d=`M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=0;i<points.length-1;i++){
|
||||
const p0=p(i-1), p1=p(i), p2=p(i+1), p3=p(i+2);
|
||||
const c1x = p1.x + (p2.x - p0.x)/6, c1y = p1.y + (p2.y - p0.y)/6;
|
||||
const c2x = p2.x - (p3.x - p1.x)/6, c2y = p2.y - (p3.y - p1.y)/6;
|
||||
d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
function toPathD(s){
|
||||
if(s.isBezier) return pathFromAnchors(s.anchors);
|
||||
const pts = s.points;
|
||||
const smp = s.sampler || samplerSel.value;
|
||||
if(smp==='raw') return pathRaw(pts);
|
||||
if(smp==='cubic') return pathCubic(pts);
|
||||
return pathQuadratic(pts);
|
||||
}
|
||||
|
||||
function startStroke(e){
|
||||
if(editBezierCb.checked){ return; } // während Edit nicht zeichnen
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
drawing = true;
|
||||
if(t0===null) t0 = now();
|
||||
const t = now() - t0;
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[{...pt,t}], pathEl, start:t, duration:0, length:0, sampler:samplerSel.value, isBezier:false };
|
||||
applyStrokeStyle(curStroke);
|
||||
updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
window.addEventListener('pointerup', endStroke, {once:true});
|
||||
}
|
||||
function moveStroke(e){
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const t = now() - t0;
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push({...p,t});
|
||||
updatePath(curStroke);
|
||||
}
|
||||
}
|
||||
function endStroke(){
|
||||
if(!curStroke) return;
|
||||
drawing = false;
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = pts.length? (pts[pts.length-1].t - pts[0].t) : 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function updatePath(s){
|
||||
s.pathEl.setAttribute('d', toPathD(s));
|
||||
s.pathEl.setAttribute('stroke-width', s.width);
|
||||
}
|
||||
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
// --- Bezier conversion & editing ---
|
||||
// FIX: build anchors with valid handles only; no (0,0) placeholders
|
||||
function catmullToAnchors(points){
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
|
||||
const P=(i)=>points[ clamp(i,0,points.length-1) ];
|
||||
const A=[];
|
||||
for(let i=0;i<points.length-1;i++){
|
||||
const p0=P(i-1), p1=P(i), p2=P(i+1), p3=P(i+2);
|
||||
const c1 = {x: p1.x + (p2.x - p0.x)/6, y: p1.y + (p2.y - p0.y)/6};
|
||||
const c2 = {x: p2.x - (p3.x - p1.x)/6, y: p2.y - (p3.y - p1.y)/6};
|
||||
if(i===0){
|
||||
A.push({x:p1.x,y:p1.y,h1:{x:p1.x,y:p1.y},h2:{x:c1.x,y:c1.y},split:false});
|
||||
}else{
|
||||
// update previous anchor's outgoing handle
|
||||
A[A.length-1].h2 = {x:c1.x,y:c1.y};
|
||||
}
|
||||
// next anchor for p2
|
||||
const next = {x:p2.x,y:p2.y,h1:{x:c2.x,y:c2.y},h2:{x:p2.x,y:p2.y},split:false};
|
||||
A.push(next);
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
function pathFromAnchors(A){
|
||||
if(!A || A.length<2) return '';
|
||||
let d = `M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<A.length-1;i++){
|
||||
const a=A[i], b=A[i+1];
|
||||
d += ` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function selectStroke(s){
|
||||
selectedStroke = s;
|
||||
strokes.forEach(st=> st.pathEl.classList.toggle('selected', st===s));
|
||||
}
|
||||
|
||||
function clearBezierUI(){ bezierUI.innerHTML=''; selectedAnchor={stroke:null, idx:-1}; delAnchorBtn.disabled = true; }
|
||||
function drawBezierUI(){
|
||||
clearBezierUI();
|
||||
const s = selectedStroke;
|
||||
if(!editBezierCb.checked || !s || !s.isBezier) return;
|
||||
s.anchors.forEach((a,idx)=>{
|
||||
// handle lines
|
||||
bezierUI.append(line(a.x, a.y, a.h1.x, a.h1.y, 'handleLine'));
|
||||
bezierUI.append(line(a.x, a.y, a.h2.x, a.h2.y, 'handleLine'));
|
||||
// handles
|
||||
const h1 = circle(a.h1.x, a.h1.y, 5, 'handle', (e)=>startDrag('h1', s, idx, e));
|
||||
const h2 = circle(a.h2.x, a.h2.y, 5, 'handle', (e)=>startDrag('h2', s, idx, e));
|
||||
bezierUI.append(h1, h2);
|
||||
// anchor
|
||||
const an = circle(a.x, a.y, 6.5, 'anchor', (e)=>{ startDrag('anchor', s, idx, e) });
|
||||
an.addEventListener('dblclick', (e)=>{ e.stopPropagation(); insertAnchorAt(s, idx); });
|
||||
an.addEventListener('pointerdown', ()=>{ selectedAnchor={stroke:s,idx}; delAnchorBtn.disabled=false; });
|
||||
bezierUI.append(an);
|
||||
});
|
||||
}
|
||||
|
||||
function line(x1,y1,x2,y2,cls){
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1',x1); l.setAttribute('y1',y1);
|
||||
l.setAttribute('x2',x2); l.setAttribute('y2',y2);
|
||||
l.setAttribute('class',cls);
|
||||
return l;
|
||||
}
|
||||
function circle(x,y,r,cls,onDown){
|
||||
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class',cls);
|
||||
c.style.touchAction = 'none';
|
||||
c.addEventListener('pointerdown', (e)=>{ e.stopPropagation(); onDown(e); });
|
||||
return c;
|
||||
}
|
||||
|
||||
// FIX: start drag immediately on current event + proper pointer capture
|
||||
function startDrag(type, s, idx, ev){
|
||||
ev.preventDefault();
|
||||
const a = s.anchors[idx];
|
||||
const p0 = svgPoint(ev);
|
||||
dragging = {type, stroke:s, idx, ox:p0.x, oy:p0.y, pid:ev.pointerId};
|
||||
try { stage.setPointerCapture(ev.pointerId); } catch(e){}
|
||||
const move = (e)=>{
|
||||
if(!dragging) return;
|
||||
const pos = svgPoint(e);
|
||||
const dx = pos.x - dragging.ox, dy = pos.y - dragging.oy;
|
||||
dragging.ox = pos.x; dragging.oy = pos.y;
|
||||
if(type==='anchor'){
|
||||
a.x+=dx; a.y+=dy; a.h1.x+=dx; a.h1.y+=dy; a.h2.x+=dx; a.h2.y+=dy;
|
||||
}else if(type==='h1'){
|
||||
a.h1.x+=dx; a.h1.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h2.x = a.x - (a.h1.x - a.x);
|
||||
a.h2.y = a.y - (a.h1.y - a.y);
|
||||
}
|
||||
}else if(type==='h2'){
|
||||
a.h2.x+=dx; a.h2.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h1.x = a.x - (a.h2.x - a.x);
|
||||
a.h1.y = a.y - (a.h2.y - a.y);
|
||||
}
|
||||
}
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
};
|
||||
const up = (e)=>{
|
||||
try { stage.releasePointerCapture(ev.pointerId); } catch(_) {}
|
||||
window.removeEventListener('pointermove', move);
|
||||
dragging=null;
|
||||
};
|
||||
window.addEventListener('pointermove', move, {passive:true});
|
||||
window.addEventListener('pointerup', up, {once:true});
|
||||
}
|
||||
|
||||
function insertAnchorAt(s, idx){
|
||||
const A = s.anchors;
|
||||
if(idx>=A.length-1) return;
|
||||
const a = A[idx], b = A[idx+1];
|
||||
const mid = {x:(a.x+b.x)/2, y:(a.y+b.y)/2};
|
||||
const dir = {x:(b.x-a.x), y:(b.y-a.y)};
|
||||
const scale = 0.33;
|
||||
const hLen = {x:dir.x*scale, y:dir.y*scale};
|
||||
const newA = {
|
||||
x: mid.x, y: mid.y,
|
||||
h1: {x: mid.x - hLen.x*0.5, y: mid.y - hLen.y*0.5},
|
||||
h2: {x: mid.x + hLen.x*0.5, y: mid.y + hLen.y*0.5},
|
||||
split:false
|
||||
};
|
||||
A.splice(idx+1,0,newA);
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
drawBezierUI();
|
||||
}
|
||||
|
||||
function deleteSelectedAnchor(){
|
||||
const {stroke:s, idx} = selectedAnchor;
|
||||
if(!s || idx<0) return;
|
||||
if(s.anchors.length<=2) return;
|
||||
s.anchors.splice(idx,1);
|
||||
selectedAnchor={stroke:null,idx:-1};
|
||||
delAnchorBtn.disabled=true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
}
|
||||
|
||||
function convertToBezier(s){
|
||||
if(s.isBezier) return;
|
||||
if(s.points.length<2) return;
|
||||
s.anchors = catmullToAnchors(s.points);
|
||||
s.isBezier = true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
toBezierBtn.addEventListener('click', ()=>{ if(selectedStroke) { convertToBezier(selectedStroke); drawBezierUI(); } });
|
||||
splitBtn.addEventListener('click', ()=>{ if(selectedStroke && selectedAnchor.idx>=0){ const a=selectedStroke.anchors[selectedAnchor.idx]; a.split=!a.split; drawBezierUI(); }});
|
||||
delAnchorBtn.addEventListener('click', deleteSelectedAnchor);
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='Delete' || e.key==='Backspace'){ if(editBezierCb.checked) { deleteSelectedAnchor(); } }
|
||||
if(e.key==='l' || e.key==='L'){ linkHandlesCb.checked = !linkHandlesCb.checked; }
|
||||
});
|
||||
|
||||
// Double-click on path to insert anchor near click
|
||||
strokesG.addEventListener('dblclick', (e)=>{
|
||||
if(!editBezierCb.checked || !selectedStroke || !selectedStroke.isBezier) return;
|
||||
const p = svgPoint(e);
|
||||
let bestI=0, bestD=1e9, A=selectedStroke.anchors;
|
||||
for(let i=0;i<A.length-1;i++){
|
||||
const a=A[i], b=A[i+1];
|
||||
const d = Math.hypot(p.x-(a.x+b.x)/2, p.y-(a.y+b.y)/2);
|
||||
if(d<bestD){ bestD=d; bestI=i; }
|
||||
}
|
||||
insertAnchorAt(selectedStroke, bestI);
|
||||
});
|
||||
|
||||
// Export / Import / Replay (minimal for this fix)
|
||||
function exportSVGString(){
|
||||
frameRect.style.display = frameOnCb.checked ? 'block' : 'none';
|
||||
const clone = stage.cloneNode(true);
|
||||
// remove Bezier UI
|
||||
const ui = clone.querySelector('#bezierUI'); if(ui) ui.remove();
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n${s}`;
|
||||
}
|
||||
function motionJSON(){
|
||||
const items = strokes.map(s=>{
|
||||
const o = {
|
||||
id: s.id, mode: s.mode, colorA: s.colorA, colorB: s.colorB, width: s.width, sampler: s.sampler,
|
||||
startMs: s.start||0, durationMs: s.duration||0, lengthPx: s.length||0,
|
||||
};
|
||||
if(s.isBezier){
|
||||
o.isBezier = true;
|
||||
o.anchors = s.anchors.map(a=>({x:a.x,y:a.y,h1:{x:a.h1.x,y:a.h1.y},h2:{x:a.h2.x,y:a.h2.y},split:!!a.split}));
|
||||
}else{
|
||||
o.points = (s.points||[]).map(p=>({x:p.x,y:p.y,tMs: Math.round(p.t)}));
|
||||
}
|
||||
return o;
|
||||
});
|
||||
return JSON.stringify({strokes:items}, null, 2);
|
||||
}
|
||||
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; clearBezierUI(); listRefresh(); t0=null; });
|
||||
|
||||
copySVGBtn.addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
dlSVGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
dlPNGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); svgToPng(svg, 'omi-omega-oneliner.png'); });
|
||||
copyJSONBtn.addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion-JSON kopiert'); });
|
||||
dlJSONBtn.addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
replayBtn.addEventListener('click', startReplay);
|
||||
|
||||
// Stage events
|
||||
stage.addEventListener('pointerdown', startStroke);
|
||||
stage.addEventListener('pointermove', moveStroke);
|
||||
window.addEventListener('pointerup', ()=>{ drawing=false; });
|
||||
|
||||
// init
|
||||
frameRect.style.display = frameOnCb.checked ? 'block':'none';
|
||||
|
||||
// Replay
|
||||
function startReplay(){
|
||||
if(!strokes.length) return;
|
||||
playing = true;
|
||||
markersG.style.opacity = 0.25; bezierUI.style.opacity = 0.15;
|
||||
strokes.forEach(s=> s.pathEl.setAttribute('d',''));
|
||||
const total = strokes.reduce((m,s)=>Math.max(m, s.isBezier?1500:(s.points?.length ? s.points[s.points.length-1].t : 0)), 0);
|
||||
const startWall = performance.now();
|
||||
function step(){
|
||||
if(!playing) return;
|
||||
const t = performance.now() - startWall;
|
||||
strokes.forEach(s=>{
|
||||
if(s.isBezier){
|
||||
const A=s.anchors;
|
||||
const k = Math.min(A.length-1, Math.floor((t/total)*(A.length-1)) );
|
||||
let d = `M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<k;i++){ const a=A[i], b=A[i+1]; d += ` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`; }
|
||||
s.pathEl.setAttribute('d', d);
|
||||
applyStrokeStyle(s);
|
||||
s.pathEl.setAttribute('stroke-width', s.width);
|
||||
}else{
|
||||
const pts = s.points.filter(p => p.t <= t);
|
||||
if(pts.length >= 2){
|
||||
s.pathEl.setAttribute('stroke-width', s.width);
|
||||
applyStrokeStyle(s);
|
||||
const smp = s.sampler || samplerSel.value;
|
||||
s.pathEl.setAttribute('d', smp==='raw'?pathRaw(pts): (smp==='cubic'?pathCubic(pts):pathQuadratic(pts)));
|
||||
}
|
||||
}
|
||||
});
|
||||
if(t < total) requestAnimationFrame(step);
|
||||
else { playing=false; markersG.style.opacity = 1; bezierUI.style.opacity = 1; strokes.forEach(updatePath); }
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function svgToPng(svgString, filename){
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([svgString], {type:'image/svg+xml;charset=utf-8'});
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
img.onload = function(){
|
||||
const canvas = document.createElement('canvas');
|
||||
const vb = stage.viewBox.baseVal;
|
||||
canvas.width = vb.width; canvas.height = vb.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toBlob((blob)=>{
|
||||
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=filename; a.click();
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
395
crumbblocks/painter_spray.html
Normal file
395
crumbblocks/painter_spray.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One‑Liner Painter (Multi‑Stroke) – draw → export SVG + Motion JSON</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg: #0b0b10; --panel: #12121a; --muted:#8d93a1; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:340px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:900px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0f131a;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0f0f10}
|
||||
.muted{color:var(--muted);font-size:.85rem}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:220px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One‑Liner Painter (Multi‑Stroke) → SVG & Motion JSON • a→Spirale→y • Start/Stop/Dynamik</h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel" aria-labelledby="controlsTitle">
|
||||
<h2 id="controlsTitle" style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken setzen (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits 1+1 setzen</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop‑Marker</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke
|
||||
<input type="range" id="stroke" min="4" max="28" step="1" value="12">
|
||||
</label>
|
||||
<label>Glättung
|
||||
<input type="range" id="smooth" min="0" max="10" step="1" value="2">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke‑Farbe A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke‑Farbe B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="dynWidth"> <span>Breite ~ Geschwindigkeit (JSON‑only)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="captureJSON" checked> <span>Motion JSON mitschreiben</span></label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich‑Liste"></ul>
|
||||
<p class="muted">Neuer Stroke beginnt mit <span class="kbd">Maus/Touch‑Down</span>. Ende bei Loslassen. Mehrere Klicks/Wege werden einzeln erfasst.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion‑JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary class="muted">A11y/Meta</summary>
|
||||
<label>Titel <input id="title" type="text" value="Omi Omega – One‑Liner"></label>
|
||||
<label>Beschreibung
|
||||
<textarea id="desc" rows="3">Cursives kleines a wird zur Spirale; zwei Einsen im a; y hält zusammen; Funken ringsum.</textarea>
|
||||
</label>
|
||||
<label>Tag (z. B. Datum/Hashtag) <input id="tag" type="text" value="22.08.25 #CRUMB"></label>
|
||||
</details>
|
||||
|
||||
<p class="muted">Tipps: Ziehen = zeichnen. <span class="kbd">Shift</span> = Funken. <span class="kbd">Z</span> = Undo. <span class="kbd">S</span> speichern.</p>
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One‑Liner</title>
|
||||
<desc id="svgDesc">Mehrere Pfade mit Start/Stop und Zeitdynamik: a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
<style>
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<text id="tagText" x="860" y="760" font-family="ui-monospace,monospace" font-size="22" fill="#cfd5e3"></text>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span class="muted">Mehrere Klicks & Pfade sind erlaubt. Start/Stop wird als Ereignis erfasst; Geschwindigkeiten landen im Motion‑JSON.</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const grad = document.getElementById('gradA2B');
|
||||
const tagText = document.getElementById('tagText');
|
||||
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const smoothInp = document.getElementById('smooth');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
const dynWidth = document.getElementById('dynWidth');
|
||||
const captureJSON = document.getElementById('captureJSON');
|
||||
const titleInp = document.getElementById('title');
|
||||
const descInp = document.getElementById('desc');
|
||||
const tagInp = document.getElementById('tag');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null; // {id, mode, color, width, points:[{x,y,t}], pathEl}
|
||||
const strokes = []; // array of strokes
|
||||
let t0 = null; // session start time
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const ctm = stage.getScreenCTM().inverse();
|
||||
const p = pt.matrixTransform(ctm);
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function updateMeta(){
|
||||
stage.querySelector('title').textContent = titleInp.value.trim() || 'One‑Liner';
|
||||
stage.querySelector('desc').textContent = descInp.value.trim() || '';
|
||||
tagText.textContent = tagInp.value.trim();
|
||||
}
|
||||
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${Math.round(s.duration)}ms • ~${Math.round(s.length)}px`;
|
||||
li.append('Stroke', sel, sw, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx];
|
||||
if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
|
||||
function toPathD(points){
|
||||
if(points.length===0) return '';
|
||||
const sm = +smoothInp.value;
|
||||
if(points.length<3 || sm===0){
|
||||
const p0 = points[0];
|
||||
let d = `M ${p0.x} ${p0.y}`;
|
||||
for(let i=1;i<points.length;i++){ const p=points[i]; d += ` L ${p.x} ${p.y}`; }
|
||||
return d;
|
||||
}
|
||||
// simple smoothing: use quadratic Beziers between midpoints
|
||||
let d = `M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=1;i<points.length-1;i++){
|
||||
const p0 = points[i];
|
||||
const p1 = points[i+1];
|
||||
const mx = (p0.x + p1.x)/2; const my = (p0.y + p1.y)/2;
|
||||
d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
|
||||
}
|
||||
const last = points[points.length-1]; d += ` L ${last.x} ${last.y}`;
|
||||
return d;
|
||||
}
|
||||
|
||||
function startStroke(e){
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
drawing = true;
|
||||
if(t0===null) t0 = now();
|
||||
const t = now() - t0;
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[{...pt,t}], pathEl, start:t, duration:0, length:0 };
|
||||
applyStrokeStyle(curStroke);
|
||||
updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
window.addEventListener('pointerup', endStroke, {once:true});
|
||||
}
|
||||
|
||||
function moveStroke(e){
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const t = now() - t0;
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push({...p,t});
|
||||
updatePath(curStroke);
|
||||
}
|
||||
}
|
||||
|
||||
function endStroke(){
|
||||
if(!curStroke) return;
|
||||
drawing = false;
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = pts.length? (pts[pts.length-1].t - pts[0].t) : 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
|
||||
function updatePath(s){ s.pathEl.setAttribute('d', toPathD(s.points)); s.pathEl.setAttribute('stroke-width', s.width); }
|
||||
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const len = Math.max(14, parseInt(strokeInp.value,10)*1.5);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
function exportSVGString(){
|
||||
updateMeta();
|
||||
const clone = stage.cloneNode(true);
|
||||
// inline live colors
|
||||
clone.querySelector('#sparks')?.setAttribute('stroke', colorSInp.value);
|
||||
// serialize
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
${s}`;
|
||||
}
|
||||
|
||||
function motionJSON(){
|
||||
const meta = { title:titleInp.value, desc:descInp.value, tag:tagInp.value, t0: t0??0 };
|
||||
const items = strokes.map(s=>{
|
||||
// speeds
|
||||
let maxV=0, sumV=0, nV=0; const pts=s.points;
|
||||
for(let i=1;i<pts.length;i++){
|
||||
const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; const dt=(pts[i].t-pts[i-1].t)/1000; if(dt<=0) continue;
|
||||
const v = Math.hypot(dx,dy)/dt; maxV=Math.max(maxV,v); sumV+=v; nV++;
|
||||
}
|
||||
const avgV = nV? sumV/nV : 0;
|
||||
return {
|
||||
id: s.id, mode: s.mode, colorA: s.colorA, colorB: s.colorB, width: s.width,
|
||||
startMs: s.start, durationMs: s.duration, lengthPx: s.length,
|
||||
avgSpeedPxPerS: +avgV.toFixed(2), maxSpeedPxPerS: +maxV.toFixed(2),
|
||||
points: s.points.map(p=>({x:p.x,y:p.y,tMs: Math.round(p.t)}))
|
||||
};
|
||||
});
|
||||
return JSON.stringify({meta, strokes:items}, null, 2);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; listRefresh(); t0=null; });
|
||||
|
||||
copySVGBtn.addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
dlSVGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner_multi.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
copyJSONBtn.addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion‑JSON kopiert'); });
|
||||
dlJSONBtn.addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
|
||||
// Stage events
|
||||
stage.addEventListener('pointerdown', startStroke);
|
||||
stage.addEventListener('pointermove', moveStroke);
|
||||
window.addEventListener('pointerup', endStroke);
|
||||
|
||||
// Hotkeys
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='z' || e.key==='Z'){ if(strokes.length){ removeStroke(strokes.length-1); } }
|
||||
if((e.key==='s' || e.key==='S') && (e.ctrlKey||e.metaKey)){ e.preventDefault(); document.getElementById('downloadSVG').click(); }
|
||||
});
|
||||
|
||||
// Reactive
|
||||
[titleInp,descInp,tagInp].forEach(inp=>inp.addEventListener('input', updateMeta));
|
||||
[colorAInp,colorBInp,colorSInp].forEach(inp=>inp.addEventListener('input', ()=>{ tagText.setAttribute('fill','#cfd5e3'); }));
|
||||
|
||||
updateMeta();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
722
crumbblocks/rainbow_counter.html
Normal file
722
crumbblocks/rainbow_counter.html
Normal file
@@ -0,0 +1,722 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🌈 Rainbow Counter v2 (ohne Anbindung)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly + JS Generator + Deutsche Labels -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #1e1e1e;
|
||||
--panel: #121212;
|
||||
--txt: #e8e8e8;
|
||||
--accent: #4caf50;
|
||||
--muted: #a0a0a0;
|
||||
--line: #2a2a2a
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--txt);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
align-items: center;
|
||||
padding: .6rem .8rem;
|
||||
border-bottom: 1px solid var(--line)
|
||||
}
|
||||
|
||||
header h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
header .sp {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
button {
|
||||
padding: .55rem .8rem;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
#wrap {
|
||||
display: flex;
|
||||
gap: .8rem;
|
||||
height: calc(100vh - 56px);
|
||||
padding: .6rem .8rem
|
||||
}
|
||||
|
||||
#left {
|
||||
flex: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem
|
||||
}
|
||||
|
||||
#right {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem
|
||||
}
|
||||
|
||||
#blocklyDiv {
|
||||
height: 60vh;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
#output {
|
||||
flex: 1;
|
||||
min-height: 28vh;
|
||||
background: var(--panel);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
#cfg {
|
||||
background: var(--panel);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
white-space: pre-wrap;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--muted);
|
||||
font-size: .9rem
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: .4rem;
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #3c99dc
|
||||
}
|
||||
|
||||
.btn-warn {
|
||||
background: #e67e22
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • Rainbow Counter v2</h2>
|
||||
<div class="sp"></div>
|
||||
<div class="row">
|
||||
<button id="btnDoc" title="Doku/Beispiel laden">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-secondary" title="Testfall 1: Basisliste">T1</button>
|
||||
<button id="btnT2" class="btn-secondary" title="Testfall 2: Gleichstand">T2</button>
|
||||
<button id="btnT3" class="btn-secondary" title="Testfall 3: Debounce=1">T3</button>
|
||||
<button id="btnRnd" class="btn-warn" title="Zufalls-Demo laden">🎲 Zufall</button>
|
||||
<button id="btnClear" title="Leeren">🗑️ Neu</button>
|
||||
<button id="btnRun" title="Blockly-Code ausführen">▶️ Ausführen</button>
|
||||
<button id="btnCrew" style="background:#9c27b0" title="Ergebnis an die Crew senden">🚀 An Crew senden</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“/„T1–T3“ oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Ziel-JSON: {"total_events","classes","dominant","mapping"} •
|
||||
Optional: Debounce (N), Gleichstand-Strategie (per Blocks).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minimal-Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
<category name="Funktionen" custom="PROCEDURE"></category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG: Parameter-Kit (nur Anzeige/Didaktik, kein Netzwerk) ===
|
||||
const CFG = {
|
||||
classes: ["red", "green", "blue", "yellow"],
|
||||
synonyms: { "purple": "unknown", "orange": "unknown" },
|
||||
normalize: "case_insensitive",
|
||||
window_n: 12,
|
||||
debounce_n: 0,
|
||||
dominant_mode: "max",
|
||||
tie_policy: "all",
|
||||
emit_mode: "final_only",
|
||||
reset_mode: "manual",
|
||||
include_ts: true,
|
||||
mapping: { red: "⚠️", green: "✅", blue: "ℹ️", yellow: "⏳", unknown: "❔" },
|
||||
rng_seed: 42,
|
||||
weights: { red: 1, green: 1, blue: 1, yellow: 1, unknown: 0.5 }
|
||||
};
|
||||
document.getElementById('cfg').textContent =
|
||||
'CFG (Gedankenstütze):\\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: { spacing: 24, length: 3, colour: '#474747', snap: true },
|
||||
trashcan: true,
|
||||
zoom: { startScale: 1.1, maxScale: 2.0, minScale: .6, controls: false, wheel: true },
|
||||
move: { scrollbars: true, drag: true, wheel: true }
|
||||
});
|
||||
|
||||
// Vordefinierte Variablen (Zähler, Events, Dominant, optional last_ev)
|
||||
['c_red', 'c_green', 'c_blue', 'c_yellow', 'c_unknown', 'total', 'ev', 'dominant', 'last_ev']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch (_) { } });
|
||||
|
||||
// Output Helpers
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t) { $('#output').textContent = t }
|
||||
function appendOutput(t) {
|
||||
const el = $('#output');
|
||||
el.textContent += (el.textContent.endsWith('\\n') ? '' : '\\n') + t;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
window.appendOutput = appendOutput; // für generierten Code
|
||||
|
||||
// text_print → Panel (statt alert)
|
||||
// Wir suchen den Generator (je nach Version unterschiedlich)
|
||||
const gen = (typeof Blockly.JavaScript !== 'undefined') ? Blockly.JavaScript :
|
||||
(typeof javascript !== 'undefined' && javascript.javascriptGenerator) ? javascript.javascriptGenerator : null;
|
||||
|
||||
if (gen) {
|
||||
gen['text_print'] = function (block) {
|
||||
const argument0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || '\'\'';
|
||||
return 'appendOutput(String(' + argument0 + '));\\n';
|
||||
};
|
||||
} else {
|
||||
console.error("CRUMBBLOCKS: Konnte JavaScript Generator nicht finden!");
|
||||
}
|
||||
|
||||
// XML Parser Fallback
|
||||
function textToDom(xmlText) {
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch (_) { }
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml) { const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// === DOKU XML ===
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable>
|
||||
<variable>c_yellow</variable><variable>c_unknown</variable><variable>total</variable>
|
||||
<variable>ev</variable><variable>dominant</variable>
|
||||
</variables>
|
||||
|
||||
<!-- DOKU -->
|
||||
<block type="text_print" x="20" y="20">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">DOC: Regenbogen-Zähl-Maschine – zählt Farbereignisse und erzeugt JSON-Report.</field></block></value>
|
||||
<next><block type="text_print"><value name="TEXT"><block type="text"><field name="TEXT">Eingabe (Demo): ["red","green","blue","blue","yellow","purple"]</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="88">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Erwartet: {"total_events","classes":{"red","green","blue","yellow","unknown"},"dominant","mapping"}</field></block></value>
|
||||
</block>
|
||||
|
||||
<!-- Init -->
|
||||
<block type="variables_set" x="20" y="150">
|
||||
<field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
|
||||
<!-- Demo-Ereignisse -->
|
||||
<block type="controls_forEach" x="20" y="320">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
<value name="ADD1"><block type="text"><field name="TEXT">green</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD3"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">yellow</field></block></value>
|
||||
<value name="ADD5"><block type="text"><field name="TEXT">purple</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">total</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">green</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_green</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">c_blue</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">yellow</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">c_yellow</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">c_unknown</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
|
||||
<!-- Dominant (einfach) -->
|
||||
<block type="variables_set" x="20" y="610">
|
||||
<field name="VAR">dominant</field>
|
||||
<value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
</block>
|
||||
|
||||
<!-- JSON Report -->
|
||||
<block type="text_print" x="20" y="650">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="22"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "total_events": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "classes": { "red": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "green": </field></block></value>
|
||||
<value name="ADD5"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "blue": </field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">, "yellow": </field></block></value>
|
||||
<value name="ADD9"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="ADD10"><block type="text"><field name="TEXT">, "unknown": </field></block></value>
|
||||
<value name="ADD11"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="ADD12"><block type="text"><field name="TEXT"> }, "dominant": "</field></block></value>
|
||||
<value name="ADD13"><block type="variables_get"><field name="VAR">dominant</field></block></value>
|
||||
<value name="ADD14"><block type="text"><field name="TEXT">", "mapping": { "red": "⚠️", "green": "✅", "blue": "ℹ️", "yellow": "⏳", "unknown": "❔" } }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === Testfälle ===
|
||||
const T1_XML = DOC_XML; // Basisliste, dominant=blue (einfach)
|
||||
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable>
|
||||
<variable>c_yellow</variable><variable>c_unknown</variable><variable>total</variable>
|
||||
<variable>ev</variable><variable>dominant</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">T2: Gleichstand red vs blue</field></block></value></block>
|
||||
|
||||
<!-- Init -->
|
||||
<block type="variables_set" x="20" y="70"><field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
|
||||
<!-- Events: ["red","blue","red","blue"] -->
|
||||
<block type="controls_forEach" x="20" y="230">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="4"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
<value name="ADD1"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
<value name="ADD3"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">total</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<mutation elseif="1" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">red</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">blue</field></block></value></block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_blue</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="text_print"><value name="TEXT"><block type="text"><field name="TEXT">Ignoriere andere.</field></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
|
||||
<!-- Gleichstand: dominant = "red,blue" (Liste als Text) -->
|
||||
<block type="variables_set" x="20" y="470"><field name="VAR">dominant</field>
|
||||
<value name="VALUE"><block type="text"><field name="TEXT">["red","blue"]</field></block></value>
|
||||
</block>
|
||||
|
||||
<!-- JSON Report -->
|
||||
<block type="text_print" x="20" y="520">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="24"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "total_events": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "classes": { "red": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "green": </field></block></value>
|
||||
<value name="ADD5"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "blue": </field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">, "yellow": </field></block></value>
|
||||
<value name="ADD9"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="ADD10"><block type="text"><field name="TEXT">, "unknown": </field></block></value>
|
||||
<value name="ADD11"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="ADD12"><block type="text"><field name="TEXT"> }, "dominant_all": </field></block></value>
|
||||
<value name="ADD13"><block type="variables_get"><field name="VAR">dominant</field></block></value>
|
||||
<value name="ADD14"><block type="text"><field name="TEXT">, "mapping": { "red": "⚠️", "green": "✅", "blue": "ℹ️", "yellow": "⏳", "unknown": "❔" } }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable>
|
||||
<variable>c_yellow</variable><variable>c_unknown</variable><variable>total</variable>
|
||||
<variable>ev</variable><variable>dominant</variable><variable>last_ev</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">T3: Debounce 1 (identische Wiederholung wird ignoriert)</field></block></value></block>
|
||||
|
||||
<!-- Init -->
|
||||
<block type="variables_set" x="20" y="70"><field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
<block type="variables_set" x="20" y="230"><field name="VAR">last_ev</field><value name="VALUE"><block type="text"><field name="TEXT">_none_</field></block></value></block>
|
||||
|
||||
<!-- Events: ["blue","blue","green"] -->
|
||||
<block type="controls_forEach" x="20" y="280">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="3"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD1"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">green</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">NEQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">last_ev</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">total</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">red</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">green</field></block></value></block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_green</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">blue</field></block></value></block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">c_blue</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">yellow</field></block></value></block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">c_yellow</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">c_unknown</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
<next><block type="variables_set"><field name="VAR">last_ev</field><value name="VALUE"><block type="variables_get"><field name="VAR">ev</field></block></value></block></next>
|
||||
</statement>
|
||||
</block>
|
||||
|
||||
<!-- Dominant (einfach max-Entscheid) -->
|
||||
<block type="variables_set" x="20" y="620"><field name="VAR">dominant</field>
|
||||
<value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="660">
|
||||
<mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">red</field></block></value></block></statement>
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">green</field></block></value></block></statement>
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">yellow</field></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value></block></statement>
|
||||
</block>
|
||||
|
||||
<!-- JSON -->
|
||||
<block type="text_print" x="20" y="830">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="22"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "total_events": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "classes": { "red": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "green": </field></block></value>
|
||||
<value name="ADD5"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "blue": </field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">, "yellow": </field></block></value>
|
||||
<value name="ADD9"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="ADD10"><block type="text"><field name="TEXT">, "unknown": </field></block></value>
|
||||
<value name="ADD11"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="ADD12"><block type="text"><field name="TEXT"> }, "dominant": "</field></block></value>
|
||||
<value name="ADD13"><block type="variables_get"><field name="VAR">dominant</field></block></value>
|
||||
<value name="ADD14"><block type="text"><field name="TEXT">", "mapping": { "red": "⚠️", "green": "✅", "blue": "ℹ️", "yellow": "⏳", "unknown": "❔" } }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === Zufalls-Demo-Generator (nur Vorstellung, keine Sensorik) ===
|
||||
function randomDemoXml(n = CFG.window_n) {
|
||||
const colors = ['red', 'green', 'blue', 'yellow', 'purple', 'orange'];
|
||||
// einfacher RNG (konstant)
|
||||
let seed = CFG.rng_seed;
|
||||
function rnd() { seed = (seed * 1664525 + 1013904223) % 4294967296; return seed / 4294967296; }
|
||||
const arr = Array.from({ length: n }, () => colors[Math.floor(rnd() * colors.length)]);
|
||||
|
||||
const items = arr.map((c, i) => `
|
||||
<value name="ADD${i}"><block type="text"><field name="TEXT">${c}</field></block></value>`).join('');
|
||||
return `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>ev</variable><variable>total</variable><variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable><variable>c_yellow</variable><variable>c_unknown</variable><variable>dominant</variable></variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Zufalls-Ereignisse: ${arr.join(', ')}</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="80"><field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
<block type="controls_forEach" x="20" y="260">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="${n}"></mutation>${items}</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if"><mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">red</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">green</field></block></value></block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">blue</field></block></value></block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">yellow</field></block></value></block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_unknown</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
<block type="variables_set" x="20" y="560"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value></block>
|
||||
<block type="text_print" x="20" y="600"><value name="TEXT"><block type="text"><field name="TEXT">→ Ergänze IF-Kette oder eigene Funktion für „dominant“.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// === Actions ===
|
||||
document.getElementById('btnDoc').onclick = () => { loadXml(DOC_XML); setOutput('📄 Doku geladen. Lies die DOC-Zeilen und drücke ▶️.'); };
|
||||
document.getElementById('btnT1').onclick = () => { loadXml(T1_XML); setOutput('🧪 T1 geladen (Basisliste). ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = () => { loadXml(T2_XML); setOutput('🧪 T2 geladen (Gleichstand). ▶️ für Report mit dominant_all=["red","blue"].'); };
|
||||
document.getElementById('btnT3').onclick = () => { loadXml(T3_XML); setOutput('🧪 T3 geladen (Debounce=1). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = () => { loadXml(randomDemoXml()); setOutput('🎲 Zufalls-Demo geladen. Ergänze Dominanz-Logik & ▶️.'); };
|
||||
document.getElementById('btnClear').onclick = () => { workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
const gen = (typeof Blockly.JavaScript !== 'undefined') ? Blockly.JavaScript :
|
||||
(typeof javascript !== 'undefined' && javascript.javascriptGenerator) ? javascript.javascriptGenerator : null;
|
||||
|
||||
if (!gen) { alert("Fehler: Generator nicht geladen."); return; }
|
||||
|
||||
const code = gen.workspaceToCode(workspace);
|
||||
|
||||
// Safety: Falls der Generator doch alert() nutzt, fangen wir das ab.
|
||||
const oldAlert = window.alert;
|
||||
window.alert = function (msg) { appendOutput(String(msg)); };
|
||||
|
||||
try {
|
||||
new Function(code)();
|
||||
appendOutput('\\n✅ Fertig.');
|
||||
}
|
||||
catch (e) { appendOutput('\\n❌ Fehler: ' + e); }
|
||||
finally {
|
||||
window.alert = oldAlert; // Restore
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('btnCrew').onclick = async () => {
|
||||
const outTxt = document.getElementById('output').textContent;
|
||||
// Try to find the JSON part
|
||||
const jsonMatch = outTxt.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(jsonMatch[0]);
|
||||
alert('🚀 Daten kopiert!\n\nGehe jetzt in dein Terminal und führe aus:\n./missions/evaluate_mission_data.sh\n\n(Dort kannst du die Daten dann einfügen)');
|
||||
} catch (err) {
|
||||
alert('❌ Konnte nicht in die Zwischenablage kopieren: ' + err);
|
||||
}
|
||||
} else {
|
||||
alert('⚠️ Kein gültiges JSON-Ergebnis gefunden!\nBitte führe erst den Code aus (▶️).');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
31
crumbblocks/readme.txt
Normal file
31
crumbblocks/readme.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
# Schnippsi Painter — Streaming Quickstart
|
||||
|
||||
## 1) Datei
|
||||
Öffne `schnippsi_painter_stream.html` im Browser. Zeichne frei oder mit dem Bezier‑Tool.
|
||||
- `Verbinden` stellt eine WebSocket-Verbindung zu **ws://localhost:9980** her.
|
||||
- Alle Events werden als JSON gesendet:
|
||||
- `session` — Sessionstart
|
||||
- `strokeStart` — neues Stroke (tool, colors, width)
|
||||
- `point` — Punkte beim Freihandzeichnen (x,y,t,pressure,tilt)
|
||||
- `anchor` / `anchors` — Bezier-Anker & Griffe (inkl. h1/h2)
|
||||
- `strokeEnd` — Stroke abgeschlossen
|
||||
|
||||
## 2) TouchDesigner (ohne extra Code)
|
||||
- Füge **WebSocket DAT** hinzu, setze **Mode = Server** und **Port = 9980**.
|
||||
- Verbinde Browser (Button `Verbinden`).
|
||||
- Hänge einen **JSON DAT** an den WebSocket-Ausgang (zum Parsen).
|
||||
- Alternativ direkt WebSocket DAT -> **WebSocket In CHOP** (vereinfachte Skalare), für volle Struktur aber JSON DAT + Python DAT-Execute.
|
||||
- Beispiel: Aus `point`-Messages `x,y` in einen **Trail CHOP** oder zum Zeichnen in einen **TOP**.
|
||||
|
||||
## 3) Test ohne TouchDesigner (Node)
|
||||
Optionale Mini‑WS:
|
||||
```bash
|
||||
npm i ws
|
||||
node mock_ws_server.js
|
||||
```
|
||||
Dann im Painter `ws://localhost:9980` verwenden.
|
||||
|
||||
## 4) Export
|
||||
Buttons: JSON, SVG, PNG — jeweils Download.
|
||||
|
||||
A11y: Tastaturbedienung, ARIA‑Status, hoher Kontrast, 200% Zoom stabil.
|
||||
687
crumbblocks/solar_kettle_dark.html
Normal file
687
crumbblocks/solar_kettle_dark.html
Normal file
@@ -0,0 +1,687 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>☀️ Solar Wasserkocher</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="lib/blockly/blockly.min.js"></script>
|
||||
<script src="lib/blockly/javascript.min.js"></script>
|
||||
<script src="lib/blockly/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #05140a;
|
||||
/* Deep Forest Black */
|
||||
--panel: #0d2615;
|
||||
/* Dark Moss */
|
||||
--txt: #e0f2f1;
|
||||
/* Mint White */
|
||||
--accent: #00e676;
|
||||
/* Vibrant Mint */
|
||||
--accent-hover: #b9f6ca;
|
||||
--border: #1b5e20;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--txt);
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 15px 20px;
|
||||
background: rgba(13, 38, 21, 0.95);
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
box-shadow: 0 4px 20px rgba(0, 230, 118, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--accent);
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
color: var(--accent);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: rgba(0, 230, 118, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 10px rgba(0, 230, 118, 0.2);
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: var(--accent);
|
||||
color: #003d15;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
background: var(--accent-hover);
|
||||
color: #000;
|
||||
box-shadow: 0 0 15px var(--accent);
|
||||
}
|
||||
|
||||
button.action {
|
||||
border-color: #ff9800;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
button.action:hover {
|
||||
background: rgba(255, 152, 0, 0.1);
|
||||
box-shadow: 0 2px 10px rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
#main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#blocklyDiv {
|
||||
flex: 3;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
#simDiv {
|
||||
flex: 2;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
background: var(--panel);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Visualization */
|
||||
#vis {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: linear-gradient(to bottom, #87CEEB 0%, #E0F7FA 100%);
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Elements */
|
||||
.sun {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #FFD700;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 40px #FFD700;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.cloud {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: -100px;
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 30px;
|
||||
transition: left 2s linear;
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.kettle-base {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 120px;
|
||||
height: 160px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 2px solid #555;
|
||||
border-radius: 10px 10px 20px 20px;
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.water {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 20%;
|
||||
background: #2196F3;
|
||||
opacity: 0.8;
|
||||
border-radius: 0 0 18px 18px;
|
||||
transition: height 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
.bubbles {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
animation: rise 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
0% {
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
bottom: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 60px;
|
||||
height: 80px;
|
||||
background: #111;
|
||||
border: 2px solid #666;
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
|
||||
.wire {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 80px;
|
||||
width: 80px;
|
||||
height: 5px;
|
||||
background: #333;
|
||||
transform: rotate(-5deg);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.info-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#log {
|
||||
flex: 1;
|
||||
background: #111;
|
||||
color: #0f0;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.heating .kettle-base {
|
||||
box-shadow: 0 0 20px red;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
/* === BLOCKLY DEEP FOREST OVERRIDES === */
|
||||
.blocklySvg {
|
||||
background-color: var(--bg) !important;
|
||||
}
|
||||
|
||||
/* Toolbox (Sidebar) - Dark Grey High Contrast */
|
||||
.blocklyToolboxDiv {
|
||||
background-color: #121212 !important;
|
||||
color: #eeeeee !important;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Category Labels */
|
||||
.blocklyTreeLabel {
|
||||
font-family: 'Segoe UI', sans-serif !important;
|
||||
color: #bbbbbb !important;
|
||||
}
|
||||
|
||||
/* Rows */
|
||||
.blocklyTreeRow {
|
||||
border-left: 4px solid transparent;
|
||||
margin-bottom: 2px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.blocklyTreeRow:hover {
|
||||
background-color: #1e1e1e !important;
|
||||
border-left-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Selected */
|
||||
.blocklyTreeSelected .blocklyTreeRow {
|
||||
background-color: #0d2615 !important;
|
||||
/* Moss background for active */
|
||||
border-left-color: var(--accent) !important;
|
||||
}
|
||||
|
||||
.blocklyTreeSelected .blocklyTreeLabel {
|
||||
color: var(--accent) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Flyout (Popup) */
|
||||
.blocklyFlyoutBackground {
|
||||
fill: #181818 !important;
|
||||
fill-opacity: 0.98 !important;
|
||||
}
|
||||
|
||||
/* Text in Flyout headers */
|
||||
.blocklyFlyoutLabelText {
|
||||
fill: var(--txt) !important;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
.blocklyScrollbarHandle {
|
||||
fill: var(--accent) !important;
|
||||
fill-opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
/* === DROPDOWNS & MENUS (The "Helle Overlay" Fix) === */
|
||||
.blocklyDropDownDiv,
|
||||
.blocklyWidgetDiv .goog-menu,
|
||||
.goog-menu {
|
||||
background-color: #121212 !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.8) !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Content Text in Menus */
|
||||
.goog-menuitem-content,
|
||||
.goog-menuitem,
|
||||
.blocklyDropDownDiv .goog-menuitem-content {
|
||||
color: #eeeeee !important;
|
||||
background-color: transparent !important;
|
||||
font-family: 'Segoe UI', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Hover State in Menus */
|
||||
.goog-menuitem-highlight,
|
||||
.goog-menuitem:hover {
|
||||
background-color: rgba(0, 230, 118, 0.2) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Input Fields (Number editing) */
|
||||
.blocklyHtmlInput {
|
||||
background-color: #05140a !important;
|
||||
color: var(--accent) !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: monospace !important;
|
||||
padding: 2px 5px !important;
|
||||
}
|
||||
|
||||
/* Tooltips */
|
||||
.blocklyTooltipDiv {
|
||||
background-color: #0d2615 !important;
|
||||
color: var(--txt) !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
/* === FINAL UI POLISH === */
|
||||
#main {
|
||||
border: 2px solid #4fc3f7 !important;
|
||||
/* Light Blue Accent */
|
||||
box-shadow: 0 0 15px rgba(79, 195, 247, 0.1);
|
||||
}
|
||||
|
||||
/* Force Toolbox Background deeper */
|
||||
.blocklyToolboxContents,
|
||||
.blocklyTreeRoot,
|
||||
.blocklyToolbox {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h2>☀️ Solar-Wasserkocher Sim</h2>
|
||||
<span id="status" style="font-size: 0.9rem; color: #aaa;">Bereit.</span>
|
||||
<button onclick="runCode()" class="primary">▶️ Starten</button>
|
||||
<button onclick="resetSim()">🔄 Reset</button>
|
||||
<button onclick="exportData()" class="action">🚀 An Crew senden</button>
|
||||
</header>
|
||||
|
||||
<div id="main">
|
||||
<div id="blocklyDiv"></div>
|
||||
<div id="simDiv">
|
||||
<div id="vis">
|
||||
<div class="sun" id="sun"></div>
|
||||
<div class="cloud" id="cloud"></div>
|
||||
<div class="panel"></div>
|
||||
<div class="wire"></div>
|
||||
<div class="kettle-base" id="kettle">
|
||||
<div class="water" id="water"></div>
|
||||
<div class="bubbles" id="bubbles"></div>
|
||||
</div>
|
||||
<div class="info-overlay">
|
||||
Temp: <span id="valTemp">20.0</span> °C<br>
|
||||
Power: <span id="valPower">0</span> W<br>
|
||||
Energie: <span id="valEnergy">0</span> kJ<br>
|
||||
Zeit: <span id="valTime">0</span> s
|
||||
</div>
|
||||
</div>
|
||||
<div id="log">Logs:<br></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<xml id="toolbox" style="display: none">
|
||||
<category name="Sensoren" colour="180">
|
||||
<block type="sensor_temp"></block>
|
||||
<block type="sensor_power"></block>
|
||||
</category>
|
||||
<category name="Aktor" colour="30">
|
||||
<block type="heater_switch"></block>
|
||||
<block type="wait_seconds"></block>
|
||||
</category>
|
||||
<category name="Logik" colour="120">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
</category>
|
||||
<category name="Schleifen" colour="150">
|
||||
<block type="controls_whileUntil"></block>
|
||||
</category>
|
||||
<category name="Mathe" colour="200">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
</category>
|
||||
<category name="Variablen" custom="VARIABLE" colour="90"></category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === PHYSICS ENGINE ===
|
||||
const SIM = {
|
||||
running: false,
|
||||
time: 0,
|
||||
dt: 0.1, // 100ms steps
|
||||
cloudPos: -100,
|
||||
|
||||
// State
|
||||
temp: 20.0,
|
||||
volume: 1.0, // Liter (kg)
|
||||
heaterOn: false,
|
||||
energyUsed: 0.0, // kJ
|
||||
|
||||
// Constants
|
||||
c: 4.184, // kJ/(kg*K) specific heat water
|
||||
maxPower: 2000, // Watt at full sun
|
||||
lossFactor: 0.05, // Cooling per degree delta T
|
||||
|
||||
// External
|
||||
sunIntensity: 1.0, // 0..1
|
||||
|
||||
reset() {
|
||||
this.running = false;
|
||||
this.time = 0;
|
||||
this.temp = 20.0;
|
||||
this.heaterOn = false;
|
||||
this.energyUsed = 0.0;
|
||||
this.sunIntensity = 1.0;
|
||||
this.cloudPos = -100;
|
||||
updateVis();
|
||||
log("Sim reset.");
|
||||
},
|
||||
|
||||
step() {
|
||||
if (!this.running) return;
|
||||
|
||||
// Environment logic
|
||||
this.time += this.dt;
|
||||
this.cloudPos += 0.5;
|
||||
if (this.cloudPos > 400) this.cloudPos = -150;
|
||||
|
||||
// Sun logic (cloud blocks sun)
|
||||
const cloudCenter = this.cloudPos + 60;
|
||||
const panelCenter = 40; // Approx css left
|
||||
let dist = Math.abs(cloudCenter - panelCenter);
|
||||
this.sunIntensity = (dist < 80) ? 0.2 : 1.0;
|
||||
|
||||
// Physics
|
||||
const currentPowerW = this.heaterOn ? (this.maxPower * this.sunIntensity) : 0;
|
||||
const heatingKJ = (currentPowerW / 1000) * this.dt;
|
||||
|
||||
const deltaT_heat = heatingKJ / (this.volume * this.c);
|
||||
const deltaT_cool = (this.temp - 20) * this.lossFactor * this.dt;
|
||||
|
||||
this.temp += (deltaT_heat - deltaT_cool);
|
||||
this.energyUsed += heatingKJ;
|
||||
|
||||
if (this.temp > 100) this.temp = 100; // Cap at boiling (energy wasted phase) -> actually lets cap valid boiling, but track waste?
|
||||
// Let's cap at 120 for "Explosion" visualization? kept simple for now.
|
||||
|
||||
updateVis();
|
||||
}
|
||||
};
|
||||
|
||||
function updateVis() {
|
||||
document.getElementById('valTemp').innerText = SIM.temp.toFixed(1);
|
||||
document.getElementById('valPower').innerText = SIM.heaterOn ? (SIM.maxPower * SIM.sunIntensity).toFixed(0) : "0";
|
||||
document.getElementById('valEnergy').innerText = SIM.energyUsed.toFixed(1);
|
||||
document.getElementById('valTime').innerText = SIM.time.toFixed(1);
|
||||
|
||||
// Kettle
|
||||
const h = Math.min(SIM.temp, 100);
|
||||
document.getElementById('water').style.height = (20 + (h / 100) * 60) + "%"; // Mock level
|
||||
|
||||
// Color: Blue -> Red
|
||||
const r = Math.min(255, (SIM.temp - 20) * 3);
|
||||
const b = Math.max(0, 255 - (SIM.temp - 20) * 3);
|
||||
document.getElementById('water').style.backgroundColor = `rgb(${r}, 0, ${b})`;
|
||||
|
||||
// Bubbles
|
||||
document.getElementById('bubbles').style.display = (SIM.temp > 95) ? 'block' : 'none';
|
||||
|
||||
// Glow
|
||||
const kettle = document.getElementById('kettle');
|
||||
if (SIM.heaterOn) kettle.classList.add('heating'); else kettle.classList.remove('heating');
|
||||
|
||||
// Cloud
|
||||
document.getElementById('cloud').style.left = SIM.cloudPos + "px";
|
||||
|
||||
// Sun dim
|
||||
document.getElementById('sun').style.opacity = SIM.sunIntensity;
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
const l = document.getElementById('log');
|
||||
l.innerHTML += `> ${msg}<br>`;
|
||||
l.scrollTop = l.scrollHeight;
|
||||
}
|
||||
|
||||
// === BLOCKLY SETUP ===
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
"type": "sensor_temp",
|
||||
"message0": "🌡️ Temperatur (°C)",
|
||||
"output": "Number",
|
||||
"colour": 230,
|
||||
"tooltip": "Misst die Wassertemperatur"
|
||||
},
|
||||
{
|
||||
"type": "sensor_power",
|
||||
"message0": "☀️ Verfügbare Power (W)",
|
||||
"output": "Number",
|
||||
"colour": 230,
|
||||
"tooltip": "Zeigt wie viel Sonnenenergie da ist"
|
||||
},
|
||||
{
|
||||
"type": "heater_switch",
|
||||
"message0": "🔥 Heizung %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "STATE",
|
||||
"options": [["AN", "ON"], ["AUS", "OFF"]]
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": 160,
|
||||
"tooltip": "Schaltet den Heizstab"
|
||||
},
|
||||
{
|
||||
"type": "wait_seconds",
|
||||
"message0": "⏳ Warte %1 Sek.",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "SECONDS",
|
||||
"value": 1,
|
||||
"min": 0.1
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": 160,
|
||||
"tooltip": "Wartet und lässt Zeit vergehen"
|
||||
}
|
||||
]);
|
||||
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos'
|
||||
});
|
||||
|
||||
// Code Generators
|
||||
javascript.javascriptGenerator.forBlock['sensor_temp'] = function (block) {
|
||||
return ['getTemp()', javascript.Order.ATOMIC];
|
||||
};
|
||||
javascript.javascriptGenerator.forBlock['sensor_power'] = function (block) {
|
||||
return ['getPower()', javascript.Order.ATOMIC];
|
||||
};
|
||||
javascript.javascriptGenerator.forBlock['heater_switch'] = function (block) {
|
||||
var state = block.getFieldValue('STATE');
|
||||
return 'setHeater("' + state + '");\n';
|
||||
};
|
||||
javascript.javascriptGenerator.forBlock['wait_seconds'] = function (block) {
|
||||
var sec = block.getFieldValue('SECONDS');
|
||||
return 'await wait(' + sec + ');\n';
|
||||
};
|
||||
|
||||
// === INTERFACE FOR GENERATED CODE ===
|
||||
function getTemp() { return SIM.temp; }
|
||||
function getPower() { return SIM.sunIntensity * SIM.maxPower; }
|
||||
function setHeater(state) {
|
||||
SIM.heaterOn = (state === 'ON');
|
||||
log(`Heizung: ${state}`);
|
||||
updateVis();
|
||||
}
|
||||
function wait(sec) {
|
||||
return new Promise(resolve => setTimeout(resolve, sec * 1000));
|
||||
}
|
||||
|
||||
// Safety Stepper
|
||||
async function _step(id) {
|
||||
workspace.highlightBlock(id);
|
||||
await new Promise(r => setTimeout(r, 10)); // Minimal delay to keep UI alive
|
||||
}
|
||||
|
||||
let loopInterval;
|
||||
|
||||
async function runCode() {
|
||||
resetSim();
|
||||
SIM.running = true;
|
||||
document.getElementById('status').innerText = "Läuft...";
|
||||
|
||||
// Start Physics Loop independent of Code
|
||||
if (loopInterval) clearInterval(loopInterval);
|
||||
loopInterval = setInterval(() => SIM.step(), SIM.dt * 1000); // 100ms real time = 0.1s sim time
|
||||
|
||||
// Generator Config
|
||||
javascript.javascriptGenerator.STATEMENT_PREFIX = 'await _step(%1);\n';
|
||||
javascript.javascriptGenerator.addReservedWords('code');
|
||||
|
||||
var code = javascript.javascriptGenerator.workspaceToCode(workspace);
|
||||
|
||||
try {
|
||||
// Wrap in async function
|
||||
const wrappedCode = `(async () => {
|
||||
${code}
|
||||
log("🏁 Programm Ende");
|
||||
SIM.running = false;
|
||||
clearInterval(loopInterval);
|
||||
document.getElementById('status').innerText = "Fertig.";
|
||||
})();`;
|
||||
|
||||
eval(wrappedCode);
|
||||
} catch (e) {
|
||||
log("❌ Fehler: " + e);
|
||||
SIM.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetSim() {
|
||||
SIM.reset();
|
||||
if (loopInterval) clearInterval(loopInterval);
|
||||
document.getElementById('log').innerHTML = "Logs:<br>";
|
||||
document.getElementById('status').innerText = "Reset.";
|
||||
workspace.highlightBlock(null);
|
||||
}
|
||||
|
||||
async function exportData() {
|
||||
const data = {
|
||||
mission: "solar_kettle",
|
||||
energy_kj: SIM.energyUsed.toFixed(2),
|
||||
final_temp: SIM.temp.toFixed(1),
|
||||
sim_time: SIM.time.toFixed(1),
|
||||
code_summary: "Blockly Code ausgeführt", // Could be more creating
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
try {
|
||||
await navigator.clipboard.writeText(json);
|
||||
alert("Daten kopiert!\n\n" + json + "\n\n-> ./evaluate_mission_data.sh");
|
||||
} catch (e) {
|
||||
alert("Clipboard Fehler: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "asciimonster"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an ASCII-Monster\""
|
||||
exit 0
|
||||
|
||||
214
crumbforest_roles/bashpanda_zero.sh
Executable file
214
crumbforest_roles/bashpanda_zero.sh
Executable file
@@ -0,0 +1,214 @@
|
||||
#!/bin/bash
|
||||
# 🐼 BashPanda - Der Kung Fu Meister des Terminals
|
||||
# Geduld, Präzision, und der Weg des Codes
|
||||
|
||||
# Load .env if exists
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="${SCRIPT_DIR}/../.env"
|
||||
|
||||
if [[ -f "${ENV_FILE}" ]]; then
|
||||
set -a
|
||||
source "${ENV_FILE}"
|
||||
set +a
|
||||
fi
|
||||
|
||||
QUESTION="$*"
|
||||
API_KEY="${OPENROUTER_API_KEY}"
|
||||
MODEL="${OPENROUTER_MODEL:-openai/gpt-3.5-turbo}"
|
||||
|
||||
# Logs
|
||||
LOGDIR="${CRUMB_LOGS_DIR:-$HOME/.bashpanda_logs}/bashpanda"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
HISTORY_FILE="$LOGDIR/bashpanda_history.json"
|
||||
TMP_REQUEST="$LOGDIR/bashpanda_request.json"
|
||||
TMP_RESPONSE="$LOGDIR/bashpanda_response.json"
|
||||
LOG_FILE="$LOGDIR/token_log.json"
|
||||
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
[ ! -f "$LOG_FILE" ] && echo "[]" > "$LOG_FILE"
|
||||
|
||||
# === CREW MEMORY FUNCTIONS ===
|
||||
|
||||
function read_crew_memory() {
|
||||
local role="$1"
|
||||
local role_log="${CRUMB_LOGS_DIR}/${role}/${role}_history.json"
|
||||
|
||||
# Fallback to old location
|
||||
if [[ ! -f "$role_log" ]]; then
|
||||
role_log="$HOME/.${role}_logs/${role}_history.json"
|
||||
fi
|
||||
|
||||
if [[ -f "$role_log" ]]; then
|
||||
jq -r '.[-3:] | .[] | "[\(.role)]: \(.content)"' "$role_log" 2>/dev/null | head -n 3
|
||||
fi
|
||||
}
|
||||
|
||||
# === MAIN ===
|
||||
|
||||
echo "🐼 BashPanda betritt das Dojo..."
|
||||
echo "🥋 *verbeugung* 🥋"
|
||||
echo ""
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ Ohne Schlüssel öffnet sich keine Tür zum Wissen."
|
||||
echo " Erstelle eine .env Datei mit deinem OPENROUTER_API_KEY."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "bashpanda"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an BashPanda\""
|
||||
echo "🐼 Der Meister wartet auf deine Frage."
|
||||
echo ""
|
||||
echo " \"Es gibt keine dummen Fragen,"
|
||||
echo " nur ungestellte Fragen, die dich nicht weiterbringen.\""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "🐼 BashPanda hört: \"$QUESTION\""
|
||||
echo ""
|
||||
|
||||
# Check if question references other crew members
|
||||
CREW_CONTEXT=""
|
||||
if echo "$QUESTION" | grep -qi "funkfox\|dumbo\|deepbit\|schnippi\|tobi\|maya"; then
|
||||
echo "🥋 BashPanda konsultiert die anderen Meister..."
|
||||
|
||||
for member in funkfox dumbosql deepbit schnippsi tobi mayaeule; do
|
||||
if echo "$QUESTION" | grep -qi "$member"; then
|
||||
MEMBER_CONTEXT=$(read_crew_memory "$member")
|
||||
if [[ -n "$MEMBER_CONTEXT" ]]; then
|
||||
CREW_CONTEXT="${CREW_CONTEXT}\n\n${member^} hat kürzlich gelehrt:\n${MEMBER_CONTEXT}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Build system prompt
|
||||
SYSTEM_PROMPT="Du bist BashPanda – der Kung Fu Meister des Bash-Terminals im Crumbforest.
|
||||
Du lehrst Bash-Programmierung als eine Form der Kampfkunst.
|
||||
|
||||
Deine Philosophie:
|
||||
- \"Der Weg des Codes ist wie der Weg der Kampfkunst: Geduld, Präzision, Wiederholung\"
|
||||
- Jede Zeile Code ist eine Kata - übe bis zur Perfektion
|
||||
- Fehler sind keine Niederlagen, sondern Lehrmeister
|
||||
- \"Ein Meister war einst ein Anfänger, der nie aufgab\"
|
||||
- Progressive Gürtel: 🖤 Schwarz → 💖 Pink → 💙 Blau → 💚 Grün → 💛 Gelb → 🤍 Weiss
|
||||
|
||||
Deine Expertise:
|
||||
- Bash-Grundlagen (echo, Variablen, Benutzereingaben)
|
||||
- Kontrollstrukturen (if/then/else, while, for)
|
||||
- Arrays und Datenstrukturen
|
||||
- Pattern Matching (grep, sed, regex)
|
||||
- Funktionen und Prozesse
|
||||
- System-Administration
|
||||
|
||||
Deine Art zu lehren:
|
||||
- Geduldig und weise, aber mit Humor (wie Kung Fu Panda)
|
||||
- Nutze Kampfkunst-Metaphern: \"if/then ist wie Angriff/Parade\"
|
||||
- \"Eine Variable ist wie ein Bambus - biegsam aber stark\"
|
||||
- \"Pipes | sind wie die Energie die durch den Körper fließt\"
|
||||
- Jede Lektion endet mit einer kleinen Weisheit
|
||||
- Du nutzt Emojis: 🐼 🥋 🎋 ⚡ 💪 🧘
|
||||
|
||||
Du arbeitest mit:
|
||||
- FunkFox für rhythmische Erklärungen
|
||||
- DumboSQL für Datenstrukturen
|
||||
- Deepbit für technische Tiefe
|
||||
- Taichi Taube für Balance und Geduld
|
||||
|
||||
WICHTIG:
|
||||
- Erkläre Konzepte Schritt für Schritt, wie ein Trainer
|
||||
- Nutze Beispiele aus der Natur und Kampfkunst
|
||||
- Ermutige zum Üben: \"Führe diesen Befehl aus, fühle wie er wirkt\"
|
||||
- Am Ende jeder Antwort: Eine kurze Weisheit oder Motivation
|
||||
|
||||
Du antwortest in der Sprache der Frage (meist Deutsch).
|
||||
Lehre mit der Ruhe eines Meisters und dem Humor eines Pandas! 🐼"
|
||||
|
||||
# Add crew context if available
|
||||
if [[ -n "$CREW_CONTEXT" ]]; then
|
||||
SYSTEM_PROMPT="${SYSTEM_PROMPT}\n\nWeisheit von den anderen Meistern:${CREW_CONTEXT}"
|
||||
fi
|
||||
|
||||
# Create API request
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system "$SYSTEM_PROMPT" \
|
||||
--arg user "$QUESTION" \
|
||||
'{
|
||||
"model": $model,
|
||||
"temperature": 0.8,
|
||||
"messages": [
|
||||
{"role": "system", "content": $system},
|
||||
{"role": "user", "content": $user}
|
||||
]
|
||||
}' > "$TMP_REQUEST"
|
||||
|
||||
# Send request
|
||||
echo "💭 BashPanda meditiert über deine Frage..."
|
||||
echo "🎋 *Bambusblätter rascheln im Wind* 🎋"
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$TMP_REQUEST" > "$TMP_RESPONSE"
|
||||
|
||||
RESPONSE_TEXT=$(jq -r '.choices[0].message.content // empty' "$TMP_RESPONSE")
|
||||
|
||||
if [[ -z "$RESPONSE_TEXT" ]]; then
|
||||
echo "🚫 Der Meister schweigt."
|
||||
echo " (Ein Fehler ist aufgetreten)"
|
||||
echo ""
|
||||
echo "Debug: $(cat "$TMP_RESPONSE")"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "🐼 BashPanda lehrt:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "$RESPONSE_TEXT"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🥋 Übe diese Technik, bis sie Teil von dir wird."
|
||||
echo ""
|
||||
|
||||
# Store conversation in history
|
||||
jq -n --arg role "user" --arg content "$QUESTION" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry_user.json"
|
||||
jq -n --arg role "assistant" --arg content "$RESPONSE_TEXT" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry_assistant.json"
|
||||
|
||||
jq -s '.[0] + [.[1]] + [.[2]]' "$HISTORY_FILE" "$LOGDIR/new_entry_user.json" "$LOGDIR/new_entry_assistant.json" > "$LOGDIR/new_history.json" && \
|
||||
mv "$LOGDIR/new_history.json" "$HISTORY_FILE" && \
|
||||
rm "$LOGDIR/new_entry_user.json" "$LOGDIR/new_entry_assistant.json"
|
||||
fi
|
||||
|
||||
# Token Tracking
|
||||
if jq -e '.usage' "$TMP_RESPONSE" > /dev/null 2>&1; then
|
||||
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
TOKENS_USED=$(jq -r '.usage.total_tokens' "$TMP_RESPONSE")
|
||||
PROMPT_TOKENS=$(jq -r '.usage.prompt_tokens' "$TMP_RESPONSE")
|
||||
COMPLETION_TOKENS=$(jq -r '.usage.completion_tokens' "$TMP_RESPONSE")
|
||||
|
||||
jq -n \
|
||||
--arg zeit "$TIMESTAMP" \
|
||||
--arg rolle "bashpanda" \
|
||||
--arg model "$MODEL" \
|
||||
--argjson usage "$(jq '.usage' "$TMP_RESPONSE")" \
|
||||
'{zeit: $zeit, rolle: $rolle, model: $model, usage: $usage}' >> "$LOG_FILE"
|
||||
|
||||
echo "📊 Training absolviert:"
|
||||
echo " Frage: ${PROMPT_TOKENS} Tokens"
|
||||
echo " Antwort: ${COMPLETION_TOKENS} Tokens"
|
||||
echo " Gesamt: ${TOKENS_USED} Tokens"
|
||||
echo ""
|
||||
echo "💡 \"Jede Frage kostet Energie - stelle sie weise.\""
|
||||
fi
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "bugsy"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are Bugsy – a friendly bug who helps children understand error messages in a simple, kind and encouraging way. You always respond in the language of the question." \
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "crabbyrust"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Crabby\""
|
||||
exit 0
|
||||
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "deepbit"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are Deepbit – a poetic octopus who explains Bash shell concepts to children using loops, images, and metaphors. Respond in the language the question is asked." \
|
||||
|
||||
@@ -49,6 +49,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "dumbosql"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Dumbo\""
|
||||
exit 0
|
||||
|
||||
@@ -50,6 +50,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "funkfox"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an FunkFox\""
|
||||
echo "🎤 Gib mir 'nen Beat, dann flow ich los!"
|
||||
|
||||
@@ -89,6 +89,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "mayaeule"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an die Eule\""
|
||||
exit 0
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "pepperphp"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Pepper\""
|
||||
exit 0
|
||||
|
||||
@@ -51,6 +51,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "schnecki"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Schnecki\""
|
||||
exit 0
|
||||
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "schnippsi"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are Schnippsi – a playful UI/UX ninja who explains HTML, CSS, and accessibility to children. Always answer in the child's language, based on the input." \
|
||||
|
||||
@@ -51,6 +51,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "schraubaer"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Schraubbär\""
|
||||
exit 0
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "snakepy"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Snake\""
|
||||
exit 0
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "spider"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Spider\""
|
||||
exit 0
|
||||
|
||||
@@ -49,6 +49,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "taichitaube"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Taichi Taube\""
|
||||
exit 0
|
||||
|
||||
@@ -5,13 +5,14 @@ API_KEY="$OPENROUTER_API_KEY"
|
||||
MODEL="openai/gpt-3.5-turbo"
|
||||
|
||||
# Verzeichnisse
|
||||
LOG_DIR="/home/zero/.templatus_logs"
|
||||
LOG_DIR="${CRUMB_LOGS_DIR:-$HOME/.crumbforest_logs}/templatus"
|
||||
HISTORY_FILE="$LOG_DIR/templatus_history.json"
|
||||
TOKEN_LOG="$LOG_DIR/token_log.json"
|
||||
TMP_REQUEST="/tmp/templatus_request.json"
|
||||
TMP_RESPONSE="/tmp/templatus_response.json"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
|
||||
# JSON Payload vorbereiten
|
||||
cat <<EOF > "$TMP_REQUEST"
|
||||
@@ -33,6 +34,17 @@ EOF
|
||||
|
||||
echo "🏗️ Templatus denkt nach über: $QUESTION"
|
||||
|
||||
# API-Key prüfen
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ No API key set. Use: export OPENROUTER_API_KEY=..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "templatus"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Anfrage senden
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "tobi"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are CapaciTobi, the clever squirrel of the Crumbforest. You explain electronic components, especially capacitors, in a child-friendly and playful way. You speak in rhymes, analogies or simple metaphors – but stay technically accurate. Respond in the language of the child asking (e.g., German, English, etc.)." \
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "vektor"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Vektor\""
|
||||
exit 0
|
||||
|
||||
@@ -119,6 +119,11 @@ function asciimonster() {
|
||||
"${ROLES_DIR}/asciimonster_zero.sh" "$@"
|
||||
}
|
||||
|
||||
# 🐼 BashPanda - Kung Fu Meister
|
||||
function bashpanda() {
|
||||
"${ROLES_DIR}/bashpanda_zero.sh" "$@"
|
||||
}
|
||||
|
||||
# === CREW COMMANDS ===
|
||||
|
||||
# 📊 crew_tokens - Token-Verbrauch aller Waldwächter
|
||||
@@ -209,15 +214,18 @@ function crew_memory() {
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
local found=0
|
||||
for history_file in "${CRUMB_LOGS_DIR}"/*/$(basename "${history_file}" | grep '_history.json$'); do
|
||||
for history_file in "${CRUMB_LOGS_DIR}"/*/*_history.json; do
|
||||
if [[ -f "$history_file" ]]; then
|
||||
local character=$(basename "$(dirname "$history_file")")
|
||||
local matches=$(grep -i "$query" "$history_file" 2>/dev/null)
|
||||
|
||||
# Suche mit jq direkt in der JSON-Struktur (case-insensitive)
|
||||
local matches=$(jq -r --arg query "$query" '.[] | select(.content | test($query; "i")) | .content' "$history_file" 2>/dev/null)
|
||||
|
||||
if [[ -n "$matches" ]]; then
|
||||
echo ""
|
||||
echo "📍 $character:"
|
||||
echo "$matches" | jq -r '.content' 2>/dev/null | head -3
|
||||
# Zeige maximal die ersten 3 Zeilen jedes Matches mit Einrückung
|
||||
echo "$matches" | head -5 | sed 's/^/ /'
|
||||
found=$((found + 1))
|
||||
fi
|
||||
fi
|
||||
@@ -436,11 +444,16 @@ function crew_help() {
|
||||
Crew-Verwaltung:
|
||||
crew_help Diese Hilfe anzeigen
|
||||
crew_status Status aller 17 Waldwächter
|
||||
crew_tokens Token-Verbrauch ALLER Charaktere
|
||||
crew_tokens Token-Verbrauch ALLER Charaktere (Input)
|
||||
crew_memo Kreative Krümel anzeigen (Output)
|
||||
crew_memory Erinnerungen durchsuchen
|
||||
crew_doctor System-Diagnose (Version, Pfade, Dependencies)
|
||||
crew_syntax Syntax Check aller Scripts
|
||||
|
||||
Krümel-Tracking:
|
||||
crumb_memo Kreativen Link festhalten (Mixcloud, Git, etc.)
|
||||
crew_memo Alle kreativen Krümel anzeigen
|
||||
|
||||
Einzelne Waldwächter:
|
||||
|
||||
🔺 Das Dreieck (Foundation):
|
||||
@@ -481,6 +494,190 @@ Beispiele:
|
||||
EOF
|
||||
}
|
||||
|
||||
# 📝 crumb_memo - Kreative Output-Krümel festhalten
|
||||
function crumb_memo() {
|
||||
local link="$1"
|
||||
local notiz="${2:-}"
|
||||
|
||||
if [[ -z "$link" ]]; then
|
||||
echo "📝 Crumb Memo - Kreative Output-Krümel"
|
||||
echo ""
|
||||
echo "Verwendung: crumb_memo <link> [notiz]"
|
||||
echo ""
|
||||
echo "Beispiele:"
|
||||
echo " crumb_memo \"https://mixcloud.com/digfafunk/new-mix\""
|
||||
echo " crumb_memo \"https://github.com/user/repo\" \"Neues Feature\""
|
||||
echo " crumb_memo \"https://soundcloud.com/artist/track\""
|
||||
echo " crumb_memo \"https://youtube.com/watch?v=xyz\" \"Tutorial\""
|
||||
echo ""
|
||||
echo "Zeige alle Krümel mit: crew_memo"
|
||||
return
|
||||
fi
|
||||
|
||||
# Memo-Datei
|
||||
local memo_file="${CRUMB_LOGS_DIR}/crumb_memo.json"
|
||||
mkdir -p "${CRUMB_LOGS_DIR}"
|
||||
|
||||
# Initialisiere Datei wenn nicht vorhanden
|
||||
if [[ ! -f "$memo_file" ]]; then
|
||||
echo "[]" > "$memo_file"
|
||||
fi
|
||||
|
||||
# Erkenne Typ aus URL
|
||||
local typ="link"
|
||||
if [[ "$link" =~ mixcloud\.com ]]; then
|
||||
typ="mixcloud"
|
||||
elif [[ "$link" =~ soundcloud\.com ]]; then
|
||||
typ="soundcloud"
|
||||
elif [[ "$link" =~ (youtube\.com|youtu\.be) ]]; then
|
||||
typ="youtube"
|
||||
elif [[ "$link" =~ (github\.com|gitlab\.com) ]]; then
|
||||
typ="git"
|
||||
fi
|
||||
|
||||
# Zeitstempel
|
||||
local zeit=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Erstelle JSON-Eintrag
|
||||
local entry=$(jq -n \
|
||||
--arg zeit "$zeit" \
|
||||
--arg link "$link" \
|
||||
--arg typ "$typ" \
|
||||
--arg notiz "$notiz" \
|
||||
'{zeit: $zeit, link: $link, typ: $typ, notiz: $notiz}')
|
||||
|
||||
# Füge zur Datei hinzu (append)
|
||||
if [[ -s "$memo_file" ]]; then
|
||||
# Datei hat Inhalt - füge zum Array hinzu
|
||||
jq ". += [$entry]" "$memo_file" > "${memo_file}.tmp" && mv "${memo_file}.tmp" "$memo_file"
|
||||
else
|
||||
# Datei ist leer - erstelle Array
|
||||
echo "[$entry]" > "$memo_file"
|
||||
fi
|
||||
|
||||
# Icon je nach Typ
|
||||
local icon="🔗"
|
||||
case "$typ" in
|
||||
mixcloud) icon="🎧" ;;
|
||||
soundcloud) icon="🔊" ;;
|
||||
youtube) icon="📹" ;;
|
||||
git) icon="💾" ;;
|
||||
esac
|
||||
|
||||
echo "$icon Krümel gespeichert: $typ"
|
||||
echo " $link"
|
||||
if [[ -n "$notiz" ]]; then
|
||||
echo " 📝 $notiz"
|
||||
fi
|
||||
}
|
||||
|
||||
# 📜 crew_memo - Zeige alle kreativen Krümel
|
||||
function crew_memo() {
|
||||
local memo_file="${CRUMB_LOGS_DIR}/crumb_memo.json"
|
||||
|
||||
echo "📜 Crumb Memo - Deine kreativen Krümel"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
if [[ ! -f "$memo_file" ]] || [[ ! -s "$memo_file" ]]; then
|
||||
echo " Noch keine Krümel gespeichert."
|
||||
echo ""
|
||||
echo " Füge welche hinzu mit:"
|
||||
echo " crumb_memo \"https://mixcloud.com/digfafunk/...\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
return
|
||||
fi
|
||||
|
||||
# Zähle Einträge pro Typ
|
||||
local total=$(jq 'length' "$memo_file")
|
||||
local mixcloud=$(jq '[.[] | select(.typ == "mixcloud")] | length' "$memo_file")
|
||||
local soundcloud=$(jq '[.[] | select(.typ == "soundcloud")] | length' "$memo_file")
|
||||
local youtube=$(jq '[.[] | select(.typ == "youtube")] | length' "$memo_file")
|
||||
local git=$(jq '[.[] | select(.typ == "git")] | length' "$memo_file")
|
||||
|
||||
# Zeige letzte 10 Einträge
|
||||
echo ""
|
||||
jq -r '.[-10:] | reverse | .[] |
|
||||
if .typ == "mixcloud" then " 🎧"
|
||||
elif .typ == "soundcloud" then " 🔊"
|
||||
elif .typ == "youtube" then " 📹"
|
||||
elif .typ == "git" then " 💾"
|
||||
else " 🔗"
|
||||
end + " " + .zeit + " - " + .typ +
|
||||
(if .notiz != "" then "\n 📝 " + .notiz else "" end) +
|
||||
"\n " + .link' "$memo_file"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
printf " Gesamt: %d Krümel" "$total"
|
||||
if [[ $mixcloud -gt 0 ]]; then printf " | 🎧 %d" "$mixcloud"; fi
|
||||
if [[ $soundcloud -gt 0 ]]; then printf " | 🔊 %d" "$soundcloud"; fi
|
||||
if [[ $youtube -gt 0 ]]; then printf " | 📹 %d" "$youtube"; fi
|
||||
if [[ $git -gt 0 ]]; then printf " | 💾 %d" "$git"; fi
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "💡 Output-Transparenz: Was habe ich geschaffen? 💚"
|
||||
}
|
||||
|
||||
# 💰 check_token_budget - Prüft ob Token-Budget noch verfügbar ist
|
||||
function check_token_budget() {
|
||||
local role_name="${1:-unknown}"
|
||||
|
||||
# Wenn kein Budget gesetzt oder 0, dann unbegrenzt
|
||||
if [[ -z "${DAILY_TOKEN_BUDGET}" ]] || [[ "${DAILY_TOKEN_BUDGET}" -eq 0 ]]; then
|
||||
return 0 # Erlaubt
|
||||
fi
|
||||
|
||||
# Berechne heutigen Token-Verbrauch
|
||||
local today=$(date '+%Y-%m-%d')
|
||||
local total_today=0
|
||||
|
||||
for token_file in "${CRUMB_LOGS_DIR}"/*/token_log.json; do
|
||||
if [[ -f "$token_file" ]]; then
|
||||
# Summiere nur Tokens von heute (robuster Ansatz für verschiedene Log-Formate)
|
||||
local tokens=$(grep "$today" "$token_file" 2>/dev/null | grep -v '^\[\]$' | while read line; do
|
||||
# Versuche total_tokens zu extrahieren (unterstützt usage als String oder Objekt)
|
||||
echo "$line" | jq -r '
|
||||
if .usage | type == "string" then
|
||||
.usage | fromjson | .total_tokens // 0
|
||||
else
|
||||
.usage.total_tokens // 0
|
||||
end
|
||||
' 2>/dev/null || echo 0
|
||||
done | awk '{sum+=$1} END {print sum+0}')
|
||||
|
||||
# Nur addieren wenn tokens eine gültige Zahl ist
|
||||
if [[ "$tokens" =~ ^[0-9]+$ ]]; then
|
||||
total_today=$((total_today + tokens))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Prüfe ob Budget überschritten
|
||||
if [[ $total_today -ge ${DAILY_TOKEN_BUDGET} ]]; then
|
||||
echo ""
|
||||
echo "🌲 Waldwächter-Nachricht 🌲"
|
||||
echo ""
|
||||
echo " Liebes Kind, heute hast du schon ${total_today} Tokens verwendet."
|
||||
echo " Das Tages-Budget liegt bei ${DAILY_TOKEN_BUDGET} Tokens."
|
||||
echo ""
|
||||
echo " 💚 Jede Frage ist wertvoll - aber auch Pausen sind wichtig."
|
||||
echo " 💭 Vielleicht kannst du selbst nach der Antwort suchen?"
|
||||
echo " 🌙 Morgen sind die Waldwächter wieder für dich da!"
|
||||
echo ""
|
||||
return 1 # Blockiert
|
||||
fi
|
||||
|
||||
# Warnung wenn Budget knapp wird
|
||||
local remaining=$((DAILY_TOKEN_BUDGET - total_today))
|
||||
if [[ "${ENABLE_TOKEN_TRACKING:-true}" == "true" ]] && [[ $remaining -lt ${TOKEN_WARNING_THRESHOLD:-1000} ]]; then
|
||||
echo "⚠️ ${role_name}: Noch ${remaining} Tokens heute verfügbar" >&2
|
||||
fi
|
||||
|
||||
return 0 # Erlaubt
|
||||
}
|
||||
|
||||
# Export functions so they're available in subshells
|
||||
# Note: export -f works in bash, but not in zsh
|
||||
# In zsh, functions are automatically available in the current shell
|
||||
@@ -504,12 +701,16 @@ if [[ -n "$BASH_VERSION" ]]; then
|
||||
export -f spider
|
||||
export -f vektor
|
||||
export -f asciimonster
|
||||
export -f bashpanda
|
||||
export -f crew_tokens
|
||||
export -f crew_status
|
||||
export -f crew_memory
|
||||
export -f crew_doctor
|
||||
export -f crew_syntax
|
||||
export -f crew_help
|
||||
export -f crumb_memo
|
||||
export -f crew_memo
|
||||
export -f check_token_budget
|
||||
fi
|
||||
# In zsh, functions are available without export -f
|
||||
|
||||
|
||||
8
missions/challenges/schnippsi_ui_design.meta.json
Normal file
8
missions/challenges/schnippsi_ui_design.meta.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "Dein Zeichen im Wald",
|
||||
"icon": "🎨",
|
||||
"description": "Erstelle ein UI-Blatt mit HTML & CSS und hinterlasse eine Nachricht.",
|
||||
"difficulty": "medium",
|
||||
"author": "Schnippsi",
|
||||
"enabled": true
|
||||
}
|
||||
70
missions/challenges/schnippsi_ui_design.sh
Executable file
70
missions/challenges/schnippsi_ui_design.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# 🖌️ Design-Challenge: Dein Zeichen im Wald
|
||||
# Eine Mission über Struktur, Stil und Gefühl.
|
||||
|
||||
# Waldwächter laden
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Relativer Pfad zur Lib, da wir in missions/challenges/ sind (2 Ebenen tiefer)
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
if [ -f "$LIB_PATH" ]; then
|
||||
source "$LIB_PATH"
|
||||
else
|
||||
# Fallback für direkte Ausführung
|
||||
echo "⚠️ Waldwächter Lib nicht gefunden. Nutze Standard-Ausgabe."
|
||||
function templatus() { echo "🏛️ Templatus: $1"; }
|
||||
function schnippsi() { echo "✂️ Schnippsi: $1"; }
|
||||
function pepperphp() { echo "🌶️ Pepper: $1"; }
|
||||
fi
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
🎨 DEIN ZEICHEN IM WALD 🎨
|
||||
|
||||
Eine Mission für HTML, CSS und das gute Gefühl.
|
||||
EOF
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🏛️ Templatus betritt die Lichtung..."
|
||||
echo ""
|
||||
templatus "Sei gegrüßt, Architekt. Der Wald braucht Struktur. Ein Baum ist nicht nur Holz, er ist ein Gerüst (DOM). Wir brauchen ein stabiles Fundament."
|
||||
templatus "Deine Aufgabe: Erstelle ein HTML-Dokument. Ein 'Blatt' im Wind des Browsers."
|
||||
echo ""
|
||||
read -p " (Drücke ENTER um das Gerüst zu bauen...)"
|
||||
echo ""
|
||||
|
||||
echo "✂️ Schnippsi schwebt herein..."
|
||||
echo ""
|
||||
schnippsi "Struktur? Pfff, Templatus, du bist so trocken wie altes Papier! Ein Blatt muss LEBEN!"
|
||||
schnippsi "Es braucht Farbe, Licht und Schatten. Wir wollen Glassmorphism – durchscheinend wie Tau auf einem Blatt. Und es muss auf jedem Gerät gut aussehen (Responsive). Das ist Ästhetik!"
|
||||
echo ""
|
||||
read -p " (Drücke ENTER für den Style...)"
|
||||
echo ""
|
||||
|
||||
echo "🌶️ PepperPHP flitzt vorbei..."
|
||||
echo ""
|
||||
pepperphp "Und es muss was TUN! Ein statisches Blatt ist langweilig. Wenn man draufklickt, muss ein MODAL aufgehen! 'onClick', 'classList.add', Action! Wir wollen eine Nachricht hinterlassen."
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "📋 DEINE MISSION:"
|
||||
echo "1. Öffne im Browser: crumbblocks/schnippsi_ui.html"
|
||||
echo "2. Betrachte das Werk. Es ist... noch leer (oder?)"
|
||||
echo "3. Dein Ziel: Eine mittige Box ('Das Zeichen')."
|
||||
echo "4. Klick -> Modal -> Nachricht eingeben -> Senden."
|
||||
echo "5. Das Ergebnis bringst du zurück zur Crew."
|
||||
|
||||
echo ""
|
||||
read -p "🚀 Bereit zum Coden? (j/n) " READY
|
||||
|
||||
if [[ "$READY" == "j" ]]; then
|
||||
echo ""
|
||||
echo "Dann los! Starte den Server:"
|
||||
echo "./start_crumbblocks.sh"
|
||||
echo ""
|
||||
echo "Und bearbeite die Datei: crumbblocks/schnippsi_ui.html"
|
||||
else
|
||||
echo "Lass dir Zeit. Gutes Design braucht Muße."
|
||||
fi
|
||||
7
missions/dojo/blauer_guertel.meta.json
Normal file
7
missions/dojo/blauer_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💙",
|
||||
"title": "Blauer Gürtel - Die Textkunst",
|
||||
"description": "sed, case, bc - Beherrsche die Macht über Text und Zahlen",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
208
missions/dojo/blauer_guertel.sh
Executable file
208
missions/dojo/blauer_guertel.sh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Blauer Gürtel 💙
|
||||
# Lehrt: sed, case, bc, Textverarbeitung
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - BLAUER GÜRTEL 💙 ║
|
||||
║ ║
|
||||
║ "Text ist wie Wasser - formbar, fließend, kraftvoll" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Der Meister begrüßt einen fortgeschrittenen Schüler..."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Blaue Gürtel lehrt dich TEXTVERARBEITUNG.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 sed - Stream Editor für Textmanipulation
|
||||
🎯 case - Mehrfach-Entscheidungen
|
||||
🎯 bc - Rechnen mit Fließkommazahlen
|
||||
🎯 wc - Wörter und Zeilen zählen
|
||||
|
||||
"Text ist die Sprache des Terminals. Beherrsche sie."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für Text-Meisterschaft? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Bis zum nächsten Mal." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: sed - Der Text-Transformator ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: sed - Stream Editor
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
sed ist wie ein Pinsel für Text: Du streichst alte Worte weg
|
||||
und schreibst neue.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Text ersetzen"
|
||||
echo ""
|
||||
echo "Original: Das ist ein Test" | tee /tmp/bashpanda_test.txt
|
||||
echo ""
|
||||
echo "sed 's/Test/Beispiel/':"
|
||||
sed 's/Test/Beispiel/' /tmp/bashpanda_test.txt
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 sed Basics:
|
||||
sed 's/alt/neu/' Ersetze erstes Vorkommen
|
||||
sed 's/alt/neu/g' Ersetze alle Vorkommen
|
||||
sed '2d' Lösche Zeile 2
|
||||
sed '3iNEUER TEXT' Füge Text vor Zeile 3 ein
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Kata: Erstelle 'texteditor.sh' das:"
|
||||
echo " - Eine Datei lädt"
|
||||
echo " - Fragt: einfügen (e) oder löschen (l)?"
|
||||
echo " - Mit sed die Operation durchführt"
|
||||
echo ""
|
||||
read -p "Weiter? (j/n): " p1
|
||||
[[ ! "$p1" =~ ^[jJyY]$ ]] && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 2: case - Der Mehrfach-Krieger ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: case - Mehrfach-Entscheidungen
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Statt vieler if/then/else nutzt case für klare Struktur.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
echo "Wähle einen Gürtel (schwarz/pink/blau/gruen/gelb/weiss):"
|
||||
read guertel_input
|
||||
|
||||
case "$guertel_input" in
|
||||
schwarz) echo "🖤 Der Anfang - Basics" ;;
|
||||
pink) echo "💖 Kontrolle - Schleifen" ;;
|
||||
blau) echo "💙 Text - sed & case" ;;
|
||||
gruen) echo "💚 Pattern - grep & regex" ;;
|
||||
gelb) echo "💛 Prozesse - Funktionen" ;;
|
||||
weiss) echo "🤍 System - Units" ;;
|
||||
*) echo "❓ Unbekannter Gürtel" ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 case Syntax:
|
||||
case "$variable" in
|
||||
muster1) befehle ;;
|
||||
muster2) befehle ;;
|
||||
*) default ;;
|
||||
esac
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: bc - Rechnen mit Präzision ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: bc - Präzises Rechnen
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
$(( )) kann nur ganze Zahlen. Für Fließkommazahlen: bc
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Fass-Rechnung"
|
||||
echo ""
|
||||
|
||||
cat << 'DEMO'
|
||||
fuellstand=10.5
|
||||
abzapfen=3.2
|
||||
neu=$(echo "$fuellstand - $abzapfen" | bc)
|
||||
echo "Neu: $neu Liter"
|
||||
DEMO
|
||||
|
||||
echo ""
|
||||
fuellstand=10.5
|
||||
abzapfen=3.2
|
||||
neu=$(echo "$fuellstand - $abzapfen" | bc)
|
||||
echo "Resultat: $neu Liter"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 bc Operationen:
|
||||
echo "3.5 + 2.1" | bc
|
||||
echo "10 / 3" | bc -l (-l für mehr Präzision)
|
||||
echo "scale=2; 10/3" | bc
|
||||
|
||||
EOF
|
||||
|
||||
# === GÜRTELPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - BLAUER GÜRTEL 💙
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Erstelle 'fluessigkeiten.sh' das:
|
||||
|
||||
1. Fragt: "Wie viele Liter hat das Fass?"
|
||||
2. while-Schleife: Solange Füllstand > 0
|
||||
3. Fragt: "Wie viel abzapfen?"
|
||||
4. Mit bc rechnen: fuellstand - abzapfen
|
||||
5. Ausgabe: "Noch X Liter im Fass"
|
||||
6. Bonus: case für verschiedene Füllstände
|
||||
- < 2.0: "Fast leer!"
|
||||
- < 5.0: "Bald nachfüllen"
|
||||
- >= 5.0: "Genug Vorrat"
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Tipp: Vergleich mit bc:"
|
||||
echo " if [ \"\$(echo \"\$wert > 5\" | bc)\" == 1 ]; then"
|
||||
echo ""
|
||||
|
||||
read -p "Prüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ 🎉 MEISTERHAFT! 🎉 ║
|
||||
║ Du hast den BLAUEN GÜRTEL 💙 verdient! ║
|
||||
║ ║
|
||||
║ "Text fließt durch deine Finger ║
|
||||
║ wie Wasser durch das Bambuswäldchen." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
echo "🥋 Gelernt:"
|
||||
echo " ✅ sed für Textbearbeitung"
|
||||
echo " ✅ case für Multi-Entscheidungen"
|
||||
echo " ✅ bc für Fließkomma-Rechnung"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: GRÜN 💚 (Pattern Matching: grep, regex)"
|
||||
else
|
||||
echo "🐼 'Der Text wartet geduldig. Übe weiter.'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Wie nutze ich sed zum Ersetzen?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
190
missions/dojo/evaluate_guertelpruefung.sh
Executable file
190
missions/dojo/evaluate_guertelpruefung.sh
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Gürtelprüfung Evaluator
|
||||
# Wertet die Crumbblock Quiz-Ergebnisse aus
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA GÜRTELPRÜFUNG - AUSWERTUNG 🥋 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda wartet auf deine Prüfungsergebnisse..."
|
||||
echo ""
|
||||
echo "📋 Bitte füge die Ergebnisse aus dem Browser ein:"
|
||||
echo " (Kopiert nach Abschluss der Prüfung automatisch)"
|
||||
echo ""
|
||||
echo "Füge ein und drücke Enter, dann Ctrl+D:"
|
||||
echo ""
|
||||
|
||||
# Read clipboard content (multi-line)
|
||||
RESULT_JSON=$(cat)
|
||||
|
||||
# Check if valid JSON
|
||||
if ! echo "$RESULT_JSON" | jq empty 2>/dev/null; then
|
||||
echo ""
|
||||
echo "❌ Fehler: Keine gültigen JSON-Daten!"
|
||||
echo ""
|
||||
echo "💡 So geht's:"
|
||||
echo " 1. Öffne: ./start_crumbblocks.sh"
|
||||
echo " 2. Navigiere zu: bashpanda_guertelpruefung.html"
|
||||
echo " 3. Wähle deinen Gürtel und beantworte die Fragen"
|
||||
echo " 4. Am Ende werden die Ergebnisse automatisch kopiert"
|
||||
echo " 5. Komm zurück und führe dieses Skript aus"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract data
|
||||
BELT=$(echo "$RESULT_JSON" | jq -r '.belt')
|
||||
SCORE=$(echo "$RESULT_JSON" | jq -r '.score')
|
||||
TOTAL=$(echo "$RESULT_JSON" | jq -r '.total')
|
||||
PERCENTAGE=$(echo "$RESULT_JSON" | jq -r '.percentage')
|
||||
PASSED=$(echo "$RESULT_JSON" | jq -r '.passed')
|
||||
TIMESTAMP=$(echo "$RESULT_JSON" | jq -r '.timestamp')
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Belt emoji mapping
|
||||
case "$BELT" in
|
||||
schwarz) BELT_EMOJI="🖤" ; BELT_NAME="Schwarzer" ;;
|
||||
pink) BELT_EMOJI="💖" ; BELT_NAME="Pinker" ;;
|
||||
blau) BELT_EMOJI="💙" ; BELT_NAME="Blauer" ;;
|
||||
gruen) BELT_EMOJI="💚" ; BELT_NAME="Grüner" ;;
|
||||
gelb) BELT_EMOJI="💛" ; BELT_NAME="Gelber" ;;
|
||||
weiss) BELT_EMOJI="🤍" ; BELT_NAME="Weisser" ;;
|
||||
*) BELT_EMOJI="🥋" ; BELT_NAME="Unbekannter" ;;
|
||||
esac
|
||||
|
||||
echo "🐼 Auswertung: $BELT_NAME Gürtel $BELT_EMOJI"
|
||||
echo ""
|
||||
echo " Ergebnis: $SCORE von $TOTAL Fragen"
|
||||
echo " Prozent: $PERCENTAGE%"
|
||||
echo ""
|
||||
|
||||
if [ "$PASSED" = "true" ]; then
|
||||
# PASSED!
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🎉 PRÜFUNG BESTANDEN! 🎉 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "$BELT_EMOJI Du hast den $BELT_NAME Gürtel verdient! $BELT_EMOJI"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "📜 ZERTIFIKAT"
|
||||
echo ""
|
||||
echo " Hiermit wird bestätigt, dass"
|
||||
echo ""
|
||||
echo " $USER"
|
||||
echo ""
|
||||
echo " die Prüfung zum $BELT_NAME Gürtel $BELT_EMOJI"
|
||||
echo " erfolgreich absolviert hat."
|
||||
echo ""
|
||||
echo " Ergebnis: $SCORE/$TOTAL ($PERCENTAGE%)"
|
||||
echo " Datum: $(date '+%Y-%m-%d %H:%M')"
|
||||
echo ""
|
||||
echo " 🐼 BashPanda - Kung Fu Meister"
|
||||
echo " Crumbforest Dojo"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Save certificate
|
||||
CERT_DIR="${SCRIPT_DIR}/../../logs/zertifikate"
|
||||
mkdir -p "$CERT_DIR"
|
||||
CERT_FILE="$CERT_DIR/${BELT}_${USER}_$(date +%Y%m%d_%H%M%S).txt"
|
||||
|
||||
cat > "$CERT_FILE" << CERT
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ CRUMBFOREST DOJO - ZERTIFIKAT ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
Schüler: $USER
|
||||
Gürtel: $BELT_NAME $BELT_EMOJI
|
||||
Ergebnis: $SCORE von $TOTAL Fragen ($PERCENTAGE%)
|
||||
Datum: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
"$BELT_NAME Gürtel erfolgreich bestanden!"
|
||||
|
||||
🐼 BashPanda
|
||||
Kung Fu Meister
|
||||
Crumbforest Dojo
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
CERT
|
||||
|
||||
echo "💾 Zertifikat gespeichert: $CERT_FILE"
|
||||
echo ""
|
||||
|
||||
# Next belt suggestion
|
||||
case "$BELT" in
|
||||
schwarz) echo "🎯 Nächster Gürtel: Pink 💖 (Kontrolle)" ;;
|
||||
pink) echo "🎯 Nächster Gürtel: Blau 💙 (Text)" ;;
|
||||
blau) echo "🎯 Nächster Gürtel: Grün 💚 (Pattern)" ;;
|
||||
gruen) echo "🎯 Nächster Gürtel: Gelb 💛 (Funktionen)" ;;
|
||||
gelb) echo "🎯 Nächster Gürtel: Weiss 🤍 (Meisterschaft)" ;;
|
||||
weiss) echo "🎉 MEISTERSCHAFT ERREICHT! Du hast alle Gürtel!" ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "🐼 BashPanda sagt:"
|
||||
bashpanda "Gratuliere zum bestandenen $BELT_NAME Gürtel! Was bedeutet diese Errungenschaft?"
|
||||
|
||||
else
|
||||
# NOT PASSED
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ⚠️ PRÜFUNG NICHT BESTANDEN ⚠️ ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 $PERCENTAGE% ist noch nicht genug für den $BELT_NAME Gürtel."
|
||||
echo ""
|
||||
echo " Benötigt: 80% (4 von 5 Fragen)"
|
||||
echo " Erreicht: $PERCENTAGE% ($SCORE von $TOTAL Fragen)"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "💡 Tipps zum Üben:"
|
||||
echo ""
|
||||
echo " 1. Wiederhole die Mission:"
|
||||
echo " bash missions/dojo/${BELT}_guertel.sh"
|
||||
echo ""
|
||||
echo " 2. Frag BashPanda um Hilfe:"
|
||||
echo " bashpanda \"Erkläre mir [Thema]\""
|
||||
echo ""
|
||||
echo " 3. Probiere die Befehle im Terminal aus"
|
||||
echo ""
|
||||
echo " 4. Wiederhole die Prüfung wenn du bereit bist"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🐼 \"Jeder Meister war einst ein Anfänger."
|
||||
echo " Gib nicht auf. Übe weiter.\""
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
7
missions/dojo/gelber_guertel.meta.json
Normal file
7
missions/dojo/gelber_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💛",
|
||||
"title": "Gelber Gürtel - Die Modularität",
|
||||
"description": "Funktionen, source, Parameter - Wiederverwendbarer Code",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
274
missions/dojo/gelber_guertel.sh
Executable file
274
missions/dojo/gelber_guertel.sh
Executable file
@@ -0,0 +1,274 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Gelber Gürtel 💛
|
||||
# Lehrt: Funktionen, Bibliotheken, Prozesse
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - GELBER GÜRTEL 💛 ║
|
||||
║ ║
|
||||
║ "Wiederverwendung ist die höchste Form der Effizienz" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda verneigt sich tief. Du bist fast am Ziel."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Gelbe Gürtel lehrt dich MODULARITÄT.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 Funktionen - Wiederverwendbarer Code
|
||||
🎯 source - Bibliotheken einbinden
|
||||
🎯 Funktionsparameter - $1, $2, $@
|
||||
🎯 return vs exit
|
||||
|
||||
"Code, der sich wiederholt, schreit nach Vereinheitlichung."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für Modularität? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Bis bald, Schüler." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: Funktionen ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: Funktionen - Die wiederverwendbare Kata
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Eine Funktion ist wie eine Kampftechnik: Einmal lernen,
|
||||
tausendmal anwenden.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
# Define function
|
||||
function begruessung() {
|
||||
echo "🐼 Willkommen, $1!"
|
||||
}
|
||||
|
||||
echo "Funktion definiert:"
|
||||
cat << 'CODE'
|
||||
function begruessung() {
|
||||
echo "🐼 Willkommen, $1!"
|
||||
}
|
||||
CODE
|
||||
echo ""
|
||||
|
||||
echo "Aufruf: begruessung \"Schüler\""
|
||||
begruessung "Schüler"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Funktionen Syntax:
|
||||
function name() {
|
||||
befehle
|
||||
}
|
||||
|
||||
# Oder alternativ:
|
||||
name() {
|
||||
befehle
|
||||
}
|
||||
|
||||
# Aufruf:
|
||||
name
|
||||
name parameter1 parameter2
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 2: Parameter ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: Funktionsparameter - Flexibilität
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo mit Parametern:"
|
||||
echo ""
|
||||
|
||||
function summe() {
|
||||
local ergebnis=0
|
||||
for zahl in "$@"
|
||||
do
|
||||
ergebnis=$((ergebnis + zahl))
|
||||
done
|
||||
echo "$ergebnis"
|
||||
}
|
||||
|
||||
echo "Funktion summe:"
|
||||
cat << 'CODE'
|
||||
function summe() {
|
||||
local ergebnis=0
|
||||
for zahl in "$@"
|
||||
do
|
||||
ergebnis=$((ergebnis + zahl))
|
||||
done
|
||||
echo "$ergebnis"
|
||||
}
|
||||
CODE
|
||||
echo ""
|
||||
|
||||
echo "Aufruf: summe 10 20 30"
|
||||
summe 10 20 30
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Parameter:
|
||||
$1, $2, $3... Einzelne Parameter
|
||||
$@ Alle Parameter als Liste
|
||||
$# Anzahl Parameter
|
||||
local var=x Lokale Variable (nur in Funktion)
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: source & Bibliotheken ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: source - Bibliotheken einbinden
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
source lädt Funktionen aus anderen Dateien.
|
||||
Wie die Waldwächter-Bibliothek! 🌲
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Beispiel: waldwaechter.sh"
|
||||
echo ""
|
||||
echo "Diese Mission nutzt:"
|
||||
echo " source \"\${SCRIPT_DIR}/../../lib/waldwaechter.sh\""
|
||||
echo ""
|
||||
echo "Dadurch sind alle 18 Waldwächter verfügbar:"
|
||||
echo " bashpanda, funkfox, dumbosql, mayaeule, ..."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 source Verwendung:
|
||||
# Bibliothek erstellen: lib.sh
|
||||
function helper() {
|
||||
echo "Helper function"
|
||||
}
|
||||
|
||||
# In anderem Skript einbinden:
|
||||
source ./lib.sh
|
||||
helper # Jetzt verfügbar!
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 4: return vs exit ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: return vs exit - Der Unterschied
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Wichtiger Unterschied:"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
return X - Verlässt NUR die Funktion, gibt X zurück (0 = Erfolg)
|
||||
exit X - Beendet das GANZE Skript
|
||||
|
||||
EOF
|
||||
|
||||
function test_return() {
|
||||
echo "In Funktion"
|
||||
return 42
|
||||
echo "Dies wird nicht ausgeführt"
|
||||
}
|
||||
|
||||
echo "Demo:"
|
||||
test_return
|
||||
echo "Return Code: $?"
|
||||
echo "Skript läuft weiter!"
|
||||
echo ""
|
||||
|
||||
# === GÜRTELPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - GELBER GÜRTEL 💛
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Erstelle zwei Dateien:
|
||||
|
||||
1. 'biblio.sh' (Die Bibliothek):
|
||||
function abbrechen() {
|
||||
echo "Willst du fortsetzen? (j/n)"
|
||||
read eingabe
|
||||
if [ "$eingabe" = "n" ]; then
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
function summe() {
|
||||
local total=0
|
||||
for zahl in "$@"
|
||||
do
|
||||
total=$((total + zahl))
|
||||
done
|
||||
echo "$total"
|
||||
}
|
||||
|
||||
2. 'rechner.sh' (Das Hauptskript):
|
||||
#!/bin/bash
|
||||
source ./biblio.sh
|
||||
|
||||
echo "Berechne: 10 + 20 + 30"
|
||||
ergebnis=$(summe 10 20 30)
|
||||
echo "Ergebnis: $ergebnis"
|
||||
|
||||
abbrechen
|
||||
|
||||
echo "Weiter geht's!"
|
||||
|
||||
Führe aus: ./rechner.sh
|
||||
|
||||
EOF
|
||||
|
||||
read -p "Prüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ 🎉 HERVORRAGEND! 🎉 ║
|
||||
║ Du hast den GELBEN GÜRTEL 💛 verdient! ║
|
||||
║ ║
|
||||
║ "Modularität ist der Schlüssel zur Meisterschaft." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
echo "🥋 Gelernt:"
|
||||
echo " ✅ Funktionen erstellen und aufrufen"
|
||||
echo " ✅ Parameter nutzen (\$1, \$@, \$#)"
|
||||
echo " ✅ source für Bibliotheken"
|
||||
echo " ✅ return vs exit"
|
||||
echo " ✅ local Variablen"
|
||||
echo ""
|
||||
echo "🎯 Letzter Gürtel: WEISS 🤍 (System: Jobs, Background)"
|
||||
else
|
||||
echo "🐼 'Funktionen sind wie Meditation - Geduld führt zur Erleuchtung.'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Wie erstelle ich eine Funktion mit Parametern?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
7
missions/dojo/gruener_guertel.meta.json
Normal file
7
missions/dojo/gruener_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💚",
|
||||
"title": "Grüner Gürtel - Die Mustererkennung",
|
||||
"description": "grep, regex, Pattern Matching - Erkenne die Ordnung im Chaos",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
220
missions/dojo/gruener_guertel.sh
Executable file
220
missions/dojo/gruener_guertel.sh
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Grüner Gürtel 💚
|
||||
# Lehrt: grep, regex, Pattern Matching
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - GRÜNER GÜRTEL 💚 ║
|
||||
║ ║
|
||||
║ "Muster erkennen ist der Weg zur Weisheit" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda nickt anerkennend. Du bist weit gekommen."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Grüne Gürtel lehrt dich MUSTERERKENNUNG.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 grep - Suchen in Dateien
|
||||
🎯 Regular Expressions (Regex) - Die Sprache der Muster
|
||||
🎯 grep -E - Extended Regex
|
||||
🎯 Praktische Anwendungen: Email, Datum, IP-Adressen
|
||||
|
||||
"Ein Meister sieht nicht nur was da ist, sondern erkennt das Muster."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für Mustererkennung? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Das Muster wartet." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: grep Basics ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: grep - Die Suche beginnt
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
grep durchsucht Text nach Mustern. Wie ein Adler, der Beute
|
||||
aus der Ferne erkennt.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Testdatei erstellen"
|
||||
cat > /tmp/waldwaechter.txt << 'DEMO'
|
||||
Maya-Eule ist weise
|
||||
FunkFox rappt im Beat
|
||||
DumboSQL vergisst nie
|
||||
BashPanda lehrt Geduld
|
||||
Schnippsi stylt alles
|
||||
DEMO
|
||||
|
||||
echo ""
|
||||
echo "Inhalt von waldwaechter.txt:"
|
||||
cat /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
echo "🥋 grep 'Fox' /tmp/waldwaechter.txt"
|
||||
grep 'Fox' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
echo "🥋 grep -i 'bash' /tmp/waldwaechter.txt (case-insensitive)"
|
||||
grep -i 'bash' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 grep Optionen:
|
||||
grep 'muster' datei Suche nach Muster
|
||||
grep -i 'muster' datei Ignore case
|
||||
grep -n 'muster' datei Zeige Zeilennummern
|
||||
grep -v 'muster' datei Invertiere (alles außer Muster)
|
||||
grep -c 'muster' datei Zähle Treffer
|
||||
grep -r 'muster' ordner/ Rekursiv in allen Dateien
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 2: Regular Expressions ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: Regex - Die Sprache der Muster
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Regex ist wie die Geheimsprache der Textsuche.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Regex Zeichen:"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
^ Zeilenanfang
|
||||
$ Zeilenende
|
||||
. Ein beliebiges Zeichen
|
||||
* 0 oder mehr Wiederholungen
|
||||
+ 1 oder mehr Wiederholungen
|
||||
? 0 oder 1 Wiederholung
|
||||
[abc] Eines der Zeichen a, b, c
|
||||
[0-9] Eine Ziffer
|
||||
[a-z] Ein Kleinbuchstabe
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Beispiele:"
|
||||
echo ""
|
||||
|
||||
echo "grep '^B' - Zeilen die mit B beginnen:"
|
||||
grep '^B' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
echo "grep 't$' - Zeilen die mit t enden:"
|
||||
grep 't$' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
# === PHASE 3: Email & Datum Erkennung ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: Praktische Muster - Email & Datum
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Email-Muster erkennen:"
|
||||
echo ""
|
||||
|
||||
cat > /tmp/emails.txt << 'DEMO'
|
||||
max@beispiel.de
|
||||
test@web.com
|
||||
invalid@
|
||||
not_an_email
|
||||
foo.bar@domain.co.uk
|
||||
DEMO
|
||||
|
||||
echo "Emails in Datei:"
|
||||
cat /tmp/emails.txt
|
||||
echo ""
|
||||
|
||||
echo "grep -E für Extended Regex:"
|
||||
echo "Email-Pattern: ^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]+$"
|
||||
echo ""
|
||||
|
||||
grep -E '^[a-zA-Z.]+@[a-zA-Z]+\.[a-zA-Z.]+$' /tmp/emails.txt
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Nützliche Patterns:
|
||||
Email: ^[a-zA-Z.]+@[a-zA-Z]+\.[a-zA-Z.]+$
|
||||
Datum: ^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$
|
||||
IP: ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$
|
||||
|
||||
EOF
|
||||
|
||||
# === GÜRTELPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - GRÜNER GÜRTEL 💚
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Erstelle 'email_finder.sh' das:
|
||||
|
||||
1. Nimmt einen Dateinamen als Parameter: $1
|
||||
2. Sucht mit grep -E nach Email-Adressen
|
||||
3. Nutzt Pattern: ^[[:alpha:]]+(\.[[:alpha:]]+)*@[[:alpha:]]+(\.[[:alpha:]]+)+$
|
||||
4. Zeigt alle gefundenen Emails an
|
||||
5. Bonus: Zählt sie mit wc -l
|
||||
|
||||
Test-Datei erstellen:
|
||||
cat > emails_test.txt << 'END'
|
||||
maya@crumb.forest
|
||||
funkfox@beat.music
|
||||
invalid@
|
||||
test@
|
||||
dumbo@memory.database
|
||||
END
|
||||
|
||||
./email_finder.sh emails_test.txt
|
||||
|
||||
EOF
|
||||
|
||||
read -p "Prüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ 🎉 AUSGEZEICHNET! 🎉 ║
|
||||
║ Du hast den GRÜNEN GÜRTEL 💚 verdient! ║
|
||||
║ ║
|
||||
║ "Wer Muster erkennt, erkennt die Ordnung im Chaos." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
echo "🥋 Gelernt:"
|
||||
echo " ✅ grep für Textsuche"
|
||||
echo " ✅ Regular Expressions"
|
||||
echo " ✅ Email/Datum Pattern"
|
||||
echo " ✅ grep -E Extended Regex"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: GELB 💛 (Prozesse & Funktionen)"
|
||||
else
|
||||
echo "🐼 'Muster brauchen Zeit zum Erkennen.'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Wie funktioniert grep mit regex?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
7
missions/dojo/pinker_guertel.meta.json
Normal file
7
missions/dojo/pinker_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💖",
|
||||
"title": "Pinker Gürtel - Die Kontrolle",
|
||||
"description": "if/then/else, while/for loops, Arrays - Meistere den Fluss des Codes",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
352
missions/dojo/pinker_guertel.sh
Executable file
352
missions/dojo/pinker_guertel.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Pinker Gürtel 💖
|
||||
# Lehrt: if/then/else, while/for loops, Arrays, Arithmetik
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
# Source waldwaechter library for BashPanda
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - PINKER GÜRTEL 💖 ║
|
||||
║ ║
|
||||
║ "Kontrolle kommt von innen, nicht von außen" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda verneigt sich tiefer als beim letzten Mal..."
|
||||
echo "🥋 Du bist zurückgekehrt. Das zeigt Entschlossenheit."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Pinke Gürtel lehrt dich KONTROLLE.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 if/then/else - Entscheidungen treffen
|
||||
🎯 while & for - Wiederholung mit Disziplin
|
||||
🎯 Arrays - Viele Werte, ein Container
|
||||
🎯 Arithmetik - $(( )) und Zahlen
|
||||
|
||||
"Der Fluss des Codes folgt deinen Bedingungen.
|
||||
Du bist der Meister über den Pfad."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für fortgeschrittenes Training? (j/n): " start
|
||||
echo ""
|
||||
|
||||
if [[ ! "$start" =~ ^[jJyY]$ ]]; then
|
||||
echo "🐼 Die Tür des Dojos steht immer offen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# === PHASE 1: if/then/else - Entscheidungen ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: if/then/else - Der Pfad des Kriegers
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Im Kampf musst du schnell entscheiden: Angriff oder Verteidigung?
|
||||
In Bash entscheidest du mit if/then/else.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
alter=25
|
||||
|
||||
if [ $alter -ge 18 ]
|
||||
then
|
||||
echo "Du bist volljährig"
|
||||
else
|
||||
echo "Du bist minderjährig"
|
||||
fi
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Lass es uns live testen:"
|
||||
echo ""
|
||||
|
||||
echo "Wie alt bist du?"
|
||||
read alter
|
||||
|
||||
if [ $alter -ge 18 ]
|
||||
then
|
||||
echo "✅ Du bist volljährig - bereit für das Dojo!"
|
||||
else
|
||||
echo "🌱 Jung wie ein Bambussprössling - lerne mit Geduld."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Vergleichsoperatoren:
|
||||
-eq Gleich (equal)
|
||||
-ne Nicht gleich (not equal)
|
||||
-gt Größer (greater than)
|
||||
-ge Größer oder gleich
|
||||
-lt Kleiner (less than)
|
||||
-le Kleiner oder gleich
|
||||
|
||||
Für Strings:
|
||||
[ "$a" = "$b" ] Strings sind gleich
|
||||
[ "$a" != "$b" ] Strings sind verschieden
|
||||
[ -z "$a" ] String ist leer
|
||||
[ -n "$a" ] String ist nicht leer
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine erste Kata:"
|
||||
echo ""
|
||||
echo "Erstelle 'datei_check.sh' das:"
|
||||
echo " - Fragt nach einem Dateinamen"
|
||||
echo " - Prüft ob die Datei existiert: [ -f \"\$datei\" ]"
|
||||
echo " - Gibt entsprechende Nachricht aus"
|
||||
echo ""
|
||||
|
||||
read -p "Bereit für Phase 2? (j/n): " phase1
|
||||
echo ""
|
||||
|
||||
# === PHASE 2: while Loop - Die Meditation ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: while - Die Meditation der Wiederholung
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Wie ein Mantra, das du wiederholst, läuft eine while-Schleife,
|
||||
bis die Bedingung nicht mehr wahr ist.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda demonstriert:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
zaehler=1
|
||||
|
||||
while [ $zaehler -le 5 ]
|
||||
do
|
||||
echo "Durchlauf: $zaehler"
|
||||
zaehler=$((zaehler + 1))
|
||||
done
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Live-Demo:"
|
||||
echo ""
|
||||
|
||||
zaehler=1
|
||||
while [ $zaehler -le 5 ]
|
||||
do
|
||||
echo "🥋 Kata Durchlauf: $zaehler"
|
||||
sleep 0.5
|
||||
zaehler=$((zaehler + 1))
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Wichtiges:
|
||||
- while [ bedingung ]; do
|
||||
- zaehler=$((zaehler + 1)) für Arithmetik
|
||||
- done am Ende
|
||||
- Achtung: Endlosschleifen vermeiden!
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: for Loop - Der geordnete Kampf ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: for - Der geordnete Durchlauf
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Eine for-Schleife ist wie eine Kata-Sequenz:
|
||||
Du weißt genau welche Bewegungen kommen.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
for name in Maya Tobi FunkFox BashPanda
|
||||
do
|
||||
echo "Hallo, $name!"
|
||||
done
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Live-Demo:"
|
||||
echo ""
|
||||
|
||||
for name in Maya Tobi FunkFox BashPanda
|
||||
do
|
||||
echo "🐼 Begrüßung: Hallo, $name!"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 for Varianten:
|
||||
for i in 1 2 3 4 5
|
||||
for datei in *.txt
|
||||
for zeile in $(cat datei.txt)
|
||||
for i in {1..10}
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine zweite Kata:"
|
||||
echo ""
|
||||
echo "Erstelle 'fibonacci.sh' das:"
|
||||
echo " - Fragt nach einer Zahl N"
|
||||
echo " - Gibt die ersten N Fibonacci-Zahlen aus"
|
||||
echo " - Nutze while und Arithmetik: a=$((a + b))"
|
||||
echo ""
|
||||
|
||||
read -p "Bereit für Phase 4? (j/n): " phase3
|
||||
echo ""
|
||||
|
||||
# === PHASE 4: Arrays - Viele Krieger, eine Armee ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: Arrays - Die Armee der Werte
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Ein Array ist wie eine Gruppe von Kriegern:
|
||||
Jeder hat seine Position, aber sie kämpfen zusammen.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda demonstriert:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
guertel=("Schwarz" "Pink" "Blau" "Gruen" "Gelb" "Weiss")
|
||||
|
||||
echo "Erster Gürtel: ${guertel[0]}"
|
||||
echo "Zweiter Gürtel: ${guertel[1]}"
|
||||
echo "Alle Gürtel: ${guertel[@]}"
|
||||
echo "Anzahl: ${#guertel[@]}"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Live-Demo:"
|
||||
echo ""
|
||||
|
||||
guertel=("Schwarz" "Pink" "Blau" "Gruen" "Gelb" "Weiss")
|
||||
|
||||
echo "🥋 Erster Gürtel: ${guertel[0]}"
|
||||
echo "🥋 Zweiter Gürtel: ${guertel[1]}"
|
||||
echo "🥋 Alle Gürtel: ${guertel[@]}"
|
||||
echo "🥋 Anzahl: ${#guertel[@]}"
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Array Operationen:
|
||||
arr=(1 2 3) Array erstellen
|
||||
${arr[0]} Erstes Element
|
||||
${arr[@]} Alle Elemente
|
||||
${#arr[@]} Anzahl Elemente
|
||||
arr+=(4) Element hinzufügen
|
||||
|
||||
Durchlauf:
|
||||
for element in "${arr[@]}"
|
||||
do
|
||||
echo "$element"
|
||||
done
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 5: Die Gürtelprüfung ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - PINKER GÜRTEL 💖
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Deine Aufgabe: Erstelle ein Skript 'summenspiel.sh' das:
|
||||
|
||||
1. Ein Array mit 5 Zahlen erstellt: zahlen=(10 20 30 40 50)
|
||||
2. Durch das Array iteriert (for-Schleife)
|
||||
3. Die Summe aller Zahlen berechnet
|
||||
4. Am Ende die Summe ausgibt
|
||||
|
||||
Bonus:
|
||||
5. Nutze if um zu prüfen ob die Summe > 100 ist
|
||||
6. Gib entsprechende Nachricht aus
|
||||
|
||||
Beispiel-Ausgabe:
|
||||
Zahlen: 10 20 30 40 50
|
||||
Summe: 150
|
||||
✅ Summe ist größer als 100!
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Hilfe:"
|
||||
echo " - summe=0 zum Initialisieren"
|
||||
echo " - summe=\$((summe + zahl)) zum Addieren"
|
||||
echo " - for zahl in \"\${zahlen[@]}\""
|
||||
echo ""
|
||||
|
||||
read -p "Hast du die Prüfung absolviert? (j/n): " pruefung
|
||||
echo ""
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🎉 AUSGEZEICHNET! 🎉 ║
|
||||
║ ║
|
||||
║ Du hast den PINKEN GÜRTEL 💖 verdient! ║
|
||||
║ ║
|
||||
║ "Kontrolle über den Code heißt Kontrolle ║
|
||||
║ über deine Gedanken." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Was du gemeistert hast:"
|
||||
echo " ✅ if/then/else Entscheidungen"
|
||||
echo " ✅ while Schleifen mit Bedingungen"
|
||||
echo " ✅ for Schleifen für Iteration"
|
||||
echo " ✅ Arrays erstellen und nutzen"
|
||||
echo " ✅ Arithmetik mit \$(( ))"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: BLAU 💙 (Textverarbeitung: sed, case)"
|
||||
echo ""
|
||||
|
||||
# Small easter egg
|
||||
echo "🎋 BashPanda sagt:"
|
||||
bashpanda "Was ist die Weisheit hinter Schleifen und Wiederholung im Code?"
|
||||
|
||||
else
|
||||
cat << 'EOF'
|
||||
🐼 "Kontrolle braucht Übung, wie ein Baum Zeit zum Wachsen braucht.
|
||||
Nimm dir die Zeit. Kehre zurück wenn du bereit bist."
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 Fragen? Rufe den Meister:"
|
||||
echo " bashpanda \"Wie funktioniert eine while-Schleife?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
7
missions/dojo/schwarzer_guertel.meta.json
Normal file
7
missions/dojo/schwarzer_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "🖤",
|
||||
"title": "Schwarzer Gürtel - Die Grundlagen",
|
||||
"description": "echo, printf, Variablen, read - Der erste Schritt auf dem Weg des Bash-Meisters",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
300
missions/dojo/schwarzer_guertel.sh
Executable file
300
missions/dojo/schwarzer_guertel.sh
Executable file
@@ -0,0 +1,300 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Schwarzer Gürtel 🖤
|
||||
# Lehrt: echo, printf, Variablen, Benutzereingaben
|
||||
# Schwierigkeit: Anfänger
|
||||
|
||||
# Source waldwaechter library for BashPanda
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - SCHWARZER GÜRTEL 🖤 ║
|
||||
║ ║
|
||||
║ "Der Weg des Codes beginnt mit einem einzigen echo" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda verneigt sich vor dir..."
|
||||
echo "🥋 Willkommen im Dojo, junger Schüler."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Schwarze Gürtel ist der erste Schritt auf dem Weg des Bash-Meisters.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 echo & printf - Die Stimme des Terminals
|
||||
🎯 Variablen - Container für Wissen
|
||||
🎯 read - Den Dialog mit dem Nutzer
|
||||
🎯 ANSI Codes - Farbe ins Terminal bringen
|
||||
|
||||
"Eine Reise von tausend Befehlen beginnt mit einem einzigen Zeichen."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für dein erstes Training? (j/n): " start
|
||||
echo ""
|
||||
|
||||
if [[ ! "$start" =~ ^[jJyY]$ ]]; then
|
||||
echo "🐼 Der Meister wartet geduldig auf deine Rückkehr."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# === PHASE 1: echo - Die Stimme des Terminals ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: echo - Die Stimme des Terminals
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Wie ein Kampfschrei im Dojo, so ist 'echo' die grundlegendste
|
||||
Ausgabe im Terminal.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
echo " echo 'Hallo Welt'"
|
||||
echo ""
|
||||
echo "Resultat:"
|
||||
echo 'Hallo Welt'
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 WICHTIG:
|
||||
- Einfache Anführungszeichen ('): Text wird exakt ausgegeben
|
||||
- Doppelte Anführungszeichen ("): Variablen werden ersetzt
|
||||
- echo -e: Aktiviert ANSI Escape-Sequenzen
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine erste Kata (Übung):"
|
||||
echo ""
|
||||
echo "1. Öffne ein neues Terminal"
|
||||
echo "2. Tippe: echo 'Ich bin ein Bash-Schüler'"
|
||||
echo "3. Drücke Enter"
|
||||
echo ""
|
||||
|
||||
read -p "Hast du es probiert? (j/n): " phase1
|
||||
echo ""
|
||||
|
||||
if [[ "$phase1" =~ ^[jJyY]$ ]]; then
|
||||
echo "✅ Gut! Die erste Technik sitzt."
|
||||
else
|
||||
echo "🐼 'Ohne Übung kein Fortschritt. Probiere es später aus.'"
|
||||
fi
|
||||
|
||||
# === PHASE 2: printf & ANSI Codes ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: printf & ANSI Codes - Die Kunst der Formatierung
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
printf ist wie echo, aber mit mehr Kontrolle.
|
||||
ANSI Codes sind wie Chi - sie geben dem Text Kraft und Farbe.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda demonstriert:"
|
||||
echo ""
|
||||
echo " echo -e '\\e[1mFett\\e[22m und \\e[4munterstrichen\\e[24m'"
|
||||
echo ""
|
||||
echo "Resultat:"
|
||||
echo -e '\e[1mFett\e[22m und \e[4munterstrichen\e[24m'
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
📖 Wichtige ANSI Codes:
|
||||
\e[1m - Fett
|
||||
\e[22m - Fett zurücksetzen
|
||||
\e[4m - Unterstrichen
|
||||
\e[24m - Unterstrichen zurücksetzen
|
||||
\e[32m - Grüne Farbe
|
||||
\e[0m - Alle Formatierungen zurücksetzen
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine zweite Kata:"
|
||||
echo ""
|
||||
echo "Erstelle eine Datei 'farbtest.sh' mit:"
|
||||
echo ""
|
||||
cat << 'CODE'
|
||||
#!/bin/bash
|
||||
echo -e "\e[32mGrün wie Bambus\e[0m"
|
||||
echo -e "\e[1m\e[33mGelb und fett wie die Sonne\e[0m"
|
||||
echo -e "\e[4mUnterstrichen wie ein Pfad\e[0m"
|
||||
CODE
|
||||
echo ""
|
||||
echo "Dann: chmod +x farbtest.sh && ./farbtest.sh"
|
||||
echo ""
|
||||
|
||||
read -p "Möchtest du fortfahren? (j/n): " phase2
|
||||
echo ""
|
||||
|
||||
# === PHASE 3: Variablen - Container für Wissen ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: Variablen - Container für Wissen
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Eine Variable ist wie ein Bambus - biegsam, kann viel tragen,
|
||||
und wächst mit der Zeit.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
name="Kung Fu Meister"
|
||||
echo "Hallo, $name"
|
||||
echo "Mein Name ist: ${name}"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Resultat:"
|
||||
name="Kung Fu Meister"
|
||||
echo "Hallo, $name"
|
||||
echo "Mein Name ist: ${name}"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Regeln für Variablen:
|
||||
- Keine Leerzeichen um das '=' Zeichen!
|
||||
- Variablennamen: Buchstaben, Zahlen, Unterstriche
|
||||
- Zugriff mit $variable oder ${variable}
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine dritte Kata:"
|
||||
echo ""
|
||||
echo "Erstelle 'variablen_test.sh':"
|
||||
echo ""
|
||||
cat << 'CODE'
|
||||
#!/bin/bash
|
||||
guertel="Schwarz"
|
||||
meister="BashPanda"
|
||||
echo "Ich trainiere für den ${guertel}en Gürtel."
|
||||
echo "Mein Meister ist $meister."
|
||||
CODE
|
||||
echo ""
|
||||
|
||||
read -p "Bereit für die letzte Phase? (j/n): " phase3
|
||||
echo ""
|
||||
|
||||
# === PHASE 4: read - Der Dialog mit dem Nutzer ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: read - Der Dialog mit dem Nutzer
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Ein Meister hört zu. Mit 'read' gibst du dem Nutzer eine Stimme.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
echo "Wie heißt du?"
|
||||
read name
|
||||
echo "Willkommen, $name!"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Lass es uns live testen:"
|
||||
echo ""
|
||||
echo "Wie heißt du?"
|
||||
read name
|
||||
echo "Willkommen im Dojo, $name! 🐼"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 read Optionen:
|
||||
read name - Speichert Eingabe in $name
|
||||
read -p "Frage: " - Zeigt Prompt vor Eingabe
|
||||
read -s password - Silent mode (für Passwörter)
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 5: Die Gürtelprüfung ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - SCHWARZER GÜRTEL 🖤
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Deine Aufgabe: Erstelle ein Skript 'begruessung.sh' das:
|
||||
|
||||
1. Den Nutzer nach seinem Namen fragt
|
||||
2. Nach seinem Lieblings-Essen fragt
|
||||
3. Eine farbige Begrüßung ausgibt mit:
|
||||
- Namen in GRÜN
|
||||
- Essen in GELB
|
||||
- "Willkommen im Dojo" in FETT
|
||||
|
||||
Beispiel-Ausgabe:
|
||||
Willkommen im Dojo!
|
||||
Name: Max (in grün)
|
||||
Lieblingsessen: Nudeln (in gelb)
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Hilfestellung: Nutze echo -e, read -p, Variablen und ANSI Codes"
|
||||
echo ""
|
||||
echo "Wenn du fertig bist, führe dein Skript aus und zeige es BashPanda!"
|
||||
echo ""
|
||||
|
||||
read -p "Hast du die Prüfung absolviert? (j/n): " pruefung
|
||||
echo ""
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🎉 GLÜCKWUNSCH! 🎉 ║
|
||||
║ ║
|
||||
║ Du hast den SCHWARZEN GÜRTEL 🖤 verdient! ║
|
||||
║ ║
|
||||
║ "Der erste Schritt ist getan. ║
|
||||
║ Tausend weitere liegen vor dir." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Was du gelernt hast:"
|
||||
echo " ✅ echo & printf für Ausgaben"
|
||||
echo " ✅ ANSI Codes für Formatierung"
|
||||
echo " ✅ Variablen erstellen und nutzen"
|
||||
echo " ✅ read für Benutzereingaben"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: PINK 💖 (Kontrolle: if/then, Arrays)"
|
||||
echo ""
|
||||
else
|
||||
cat << 'EOF'
|
||||
🐼 "Es ist keine Schande, mehr Zeit zu brauchen.
|
||||
Auch der größte Meister war einst ein Anfänger.
|
||||
Übe weiter, und komm zurück wenn du bereit bist."
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 Fragen? Rufe den Meister:"
|
||||
echo " bashpanda \"Wie funktioniert echo?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
7
missions/dojo/weisser_guertel.meta.json
Normal file
7
missions/dojo/weisser_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "🤍",
|
||||
"title": "Weisser Gürtel - Die Meisterschaft",
|
||||
"description": "Background Jobs, Prozesse, parallel processing - Der Weg des Meisters",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
313
missions/dojo/weisser_guertel.sh
Executable file
313
missions/dojo/weisser_guertel.sh
Executable file
@@ -0,0 +1,313 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Weisser Gürtel 🤍
|
||||
# Lehrt: Background Jobs, Processes, ps, jobs, &
|
||||
# Schwierigkeit: Meister
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - WEISSER GÜRTEL 🤍 ║
|
||||
║ ║
|
||||
║ "Der Meister kontrolliert nicht nur Code, ║
|
||||
║ sondern auch Prozesse" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda steht auf. Er verneigt sich als Ebenbürtiger."
|
||||
echo "🥋 Du bist bereit für die letzte Lehre."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Weisse Gürtel lehrt dich PROZESS-KONTROLLE.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 & - Jobs im Hintergrund starten
|
||||
🎯 jobs - Aktive Jobs anzeigen
|
||||
🎯 ps - Prozesse anzeigen
|
||||
🎯 kill - Prozesse beenden
|
||||
🎯 wait - Auf Prozesse warten
|
||||
|
||||
"Ein Meister lässt Prozesse für sich arbeiten,
|
||||
während er meditiert."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für die Meisterprüfung? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Der Weg endet nie." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: Background Jobs mit & ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: & - Hintergrund-Prozesse
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Mit & startest du einen Prozess im Hintergrund.
|
||||
Das Terminal bleibt frei für weitere Befehle.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
echo "Starte: sleep 5 &"
|
||||
sleep 5 &
|
||||
JOB_PID=$!
|
||||
echo "Job gestartet mit PID: $JOB_PID"
|
||||
echo ""
|
||||
|
||||
echo "Terminal ist frei! Ich kann weiter arbeiten."
|
||||
echo "Der Job läuft im Hintergrund..."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Background Syntax:
|
||||
command & Starte im Hintergrund
|
||||
$! PID des letzten Background-Jobs
|
||||
jobs Zeige alle aktiven Jobs
|
||||
fg %1 Bring Job 1 in Vordergrund
|
||||
bg %1 Sende Job 1 in Hintergrund
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 2: jobs & ps ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: jobs & ps - Übersicht behalten
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Mehrere Jobs starten"
|
||||
echo ""
|
||||
|
||||
sleep 3 &
|
||||
sleep 4 &
|
||||
sleep 5 &
|
||||
|
||||
echo "jobs zeigt alle Jobs dieser Shell:"
|
||||
jobs
|
||||
echo ""
|
||||
|
||||
echo "ps zeigt alle deine Prozesse:"
|
||||
ps aux | grep $USER | head -5
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Prozess-Befehle:
|
||||
jobs Jobs der aktuellen Shell
|
||||
ps aux Alle Prozesse (detailliert)
|
||||
ps aux | grep X Suche Prozess X
|
||||
pgrep name Finde PID von Prozess
|
||||
pkill name Beende Prozess nach Namen
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: wait - Auf Prozesse warten ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: wait - Synchronisation
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
wait wartet bis ein Background-Job fertig ist.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
echo "Starte Job..."
|
||||
sleep 2 &
|
||||
PID=$!
|
||||
echo "Warte auf Job $PID..."
|
||||
wait $PID
|
||||
echo "Job ist fertig!"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Ausführung:"
|
||||
echo "Starte Job..."
|
||||
sleep 2 &
|
||||
PID=$!
|
||||
echo "Warte auf Job $PID..."
|
||||
wait $PID
|
||||
echo "✅ Job ist fertig!"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 wait Verwendung:
|
||||
wait Warte auf ALLE Background-Jobs
|
||||
wait $PID Warte auf spezifischen Job
|
||||
wait %1 Warte auf Job-Nummer 1
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 4: Praktisches Beispiel ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: Parallel Processing - Die Macht
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Mehrere Tasks parallel ausführen, dann auf alle warten.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: 3 Tasks parallel"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
function task() {
|
||||
echo "Task $1 startet..."
|
||||
sleep 2
|
||||
echo "Task $1 fertig!"
|
||||
}
|
||||
|
||||
task 1 &
|
||||
task 2 &
|
||||
task 3 &
|
||||
|
||||
wait
|
||||
echo "Alle Tasks abgeschlossen!"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Ausführung:"
|
||||
|
||||
function task() {
|
||||
echo "🥋 Task $1 startet..."
|
||||
sleep 1
|
||||
echo "✅ Task $1 fertig!"
|
||||
}
|
||||
|
||||
task 1 &
|
||||
task 2 &
|
||||
task 3 &
|
||||
|
||||
wait
|
||||
echo "🐼 Alle Tasks abgeschlossen!"
|
||||
echo ""
|
||||
|
||||
# === DIE MEISTERPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 MEISTERPRÜFUNG - WEISSER GÜRTEL 🤍
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Deine finale Aufgabe: Erstelle 'parallel_processor.sh'
|
||||
|
||||
Das Skript soll:
|
||||
|
||||
1. Eine Funktion 'process_file' definieren:
|
||||
- Nimmt Dateiname als Parameter
|
||||
- Zählt Wörter mit wc -w
|
||||
- Schreibt Ergebnis in \${datei}.count
|
||||
|
||||
2. 5 Test-Dateien erstellen:
|
||||
echo "Test Inhalt" > file1.txt
|
||||
(für file1 bis file5)
|
||||
|
||||
3. Alle 5 parallel verarbeiten:
|
||||
for file in file*.txt
|
||||
do
|
||||
process_file "$file" &
|
||||
done
|
||||
|
||||
4. Mit wait auf alle warten
|
||||
|
||||
5. Ergebnisse ausgeben:
|
||||
cat file*.count
|
||||
|
||||
Bonuspunkte:
|
||||
- Nutze jobs um zu zeigen wie viele laufen
|
||||
- Speichere PIDs in Array
|
||||
- Prüfe mit $? ob Jobs erfolgreich waren
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Dies kombiniert ALLES was du gelernt hast:"
|
||||
echo " - Funktionen (Gelb)"
|
||||
echo " - Arrays & Schleifen (Pink)"
|
||||
echo " - Background Jobs & wait (Weiss)"
|
||||
echo ""
|
||||
|
||||
read -p "Hast du die Meisterprüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🏆 MEISTERSCHAFT ERREICHT! 🏆 ║
|
||||
║ ║
|
||||
║ Du hast den WEISSEN GÜRTEL 🤍 verdient! ║
|
||||
║ ║
|
||||
║ DU BIST NUN EIN BASH-MEISTER! ║
|
||||
║ ║
|
||||
║ "Der Weg des Meisters endet nie. ║
|
||||
║ Mit jedem Tag lernst du etwas Neues. ║
|
||||
║ Lehre nun andere, was du gelernt hast." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "🥋 DEINE REISE DURCH DAS DOJO:"
|
||||
echo ""
|
||||
echo " 🖤 Schwarzer Gürtel: echo, Variablen, read"
|
||||
echo " 💖 Pinker Gürtel: if/then, Schleifen, Arrays"
|
||||
echo " 💙 Blauer Gürtel: sed, case, bc"
|
||||
echo " 💚 Grüner Gürtel: grep, regex, Pattern"
|
||||
echo " 💛 Gelber Gürtel: Funktionen, source"
|
||||
echo " 🤍 Weisser Gürtel: Background Jobs, Prozesse"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🌲 WILLKOMMEN IN DER CRUMBCREW! 🌲"
|
||||
echo ""
|
||||
echo " Du kannst nun:"
|
||||
echo " - Alle 18 Waldwächter befehligen"
|
||||
echo " - Eigene Bash-Skripte meistern"
|
||||
echo " - Komplexe Missions im Crumbforest lösen"
|
||||
echo ""
|
||||
echo "🎯 Nächste Schritte:"
|
||||
echo " - Erkunde missions/robots/ (Hardware-Projekte)"
|
||||
echo " - Nutze ./crumb-mission-selector.sh (Option 9: CrumbCrew)"
|
||||
echo " - Erstelle eigene Skripte und teile sie!"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🐼 BashPanda sagt zum Abschied:"
|
||||
echo ""
|
||||
|
||||
bashpanda "Was ist die Weisheit eines Bash-Meisters?"
|
||||
|
||||
else
|
||||
cat << 'EOF'
|
||||
🐼 "Der weisse Gürtel ist der Anfang, nicht das Ende.
|
||||
Übe weiter. Der Weg des Meisters ist niemals vollendet."
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Was sind Background-Prozesse?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
138
missions/evaluate_mission_data.sh
Executable file
138
missions/evaluate_mission_data.sh
Executable file
@@ -0,0 +1,138 @@
|
||||
#!/bin/bash
|
||||
# 🚀 Mission Data Evaluator (Master Gateway)
|
||||
# Liest JSON vom Browser und routet zur richtigen Mission.
|
||||
|
||||
# Waldwächter laden
|
||||
# Waldwächter laden
|
||||
MISSION_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${MISSION_DIR}/../lib/waldwaechter.sh"
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
📡 CRUMB-MISSION DATA LINK 📡
|
||||
|
||||
Verbindung zum Browser wird hergestellt...
|
||||
|
||||
Anleitung:
|
||||
1. Öffne die Crumbblocks Mission im Browser
|
||||
2. Baue deinen Code und klicke "▶️ Ausführen"
|
||||
3. Klicke "🚀 An Crew Senden"
|
||||
4. Füge den kopierten Code hier ein (Ctrl+V / Cmd+V)
|
||||
5. Drücke ENTER und dann CTRL+D (um das Einfügen beenden)
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "👇 Bitte JSON-Daten jetzt einfügen:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Lese Input bis EOF
|
||||
INPUT_DATA=$(cat)
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🔄 Daten empfangen. Prüfe Mission-Typ..."
|
||||
sleep 0.5
|
||||
|
||||
# Extrahiere Mission-ID aus dem JSON
|
||||
# Wir nutzen grep/cut als robusten Fallback, falls jq fehlt
|
||||
MISSION_ID=$(echo "$INPUT_DATA" | grep -o '"mission": *"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
# Routing Logic
|
||||
if [[ "$MISSION_ID" == "schnippsi_ui" ]]; then
|
||||
# --> Rerouting zur UI Mission
|
||||
echo ">> Routing zu: Dein Zeichen im Wald (Schnippsi UI)"
|
||||
echo "$INPUT_DATA" | "${MISSION_DIR}/evaluate_sign.sh"
|
||||
exit $?
|
||||
elif [[ "$MISSION_ID" == "solar_kettle" ]]; then
|
||||
# --> Rerouting zur Solar Mission
|
||||
echo ">> Routing zu: Solar Wasserkocher (Tobi Physics)"
|
||||
echo "$INPUT_DATA" | "${MISSION_DIR}/robots/evaluate_solar_kettle.sh"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# DEFAULT / LEGACY LOGIC (Rainbow Counter)
|
||||
# ============================================================
|
||||
|
||||
# Falls keine Mission-ID gefunden wurde oder unbekannt, gehen wir davon aus,
|
||||
# dass es der Rainbow Counter ist (Backward Compatibility).
|
||||
|
||||
echo ">> Routing zu: Standard (Rainbow Counter)"
|
||||
echo ""
|
||||
|
||||
# Validierung
|
||||
if [[ "$INPUT_DATA" != *"{"* ]] || [[ "$INPUT_DATA" != *"}"* ]]; then
|
||||
echo "❌ FEHLER: Das sieht nicht wie gültiges JSON aus."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
has_jq=$(command -v jq)
|
||||
|
||||
# Versuch, die relevante Zeile zu finden (total_events)
|
||||
VALID_JSON_LINE=$(echo "$INPUT_DATA" | grep '"total_events":' | head -n 1)
|
||||
|
||||
if [ -z "$VALID_JSON_LINE" ]; then
|
||||
CLEAN_JSON="$INPUT_DATA"
|
||||
else
|
||||
CLEAN_JSON="$VALID_JSON_LINE"
|
||||
fi
|
||||
|
||||
CLEAN_JSON=$(echo "$CLEAN_JSON" | sed 's/^[^{]*{/{/; s/}[^}]*$/}/')
|
||||
|
||||
if [ -n "$has_jq" ] && echo "$CLEAN_JSON" | jq . >/dev/null 2>&1; then
|
||||
TOTAL=$(echo "$CLEAN_JSON" | jq -r '.total_events // 0')
|
||||
DOMINANT=$(echo "$CLEAN_JSON" | jq -r '.dominant // "unknown"')
|
||||
RED=$(echo "$CLEAN_JSON" | jq -r '.classes.red // 0')
|
||||
BLUE=$(echo "$CLEAN_JSON" | jq -r '.classes.blue // 0')
|
||||
else
|
||||
# Fallback Parser
|
||||
TOTAL=$(echo "$CLEAN_JSON" | grep -o '"total_events": *[0-9]*' | awk -F: '{print $2}' | tr -d ' ,')
|
||||
DOMINANT=$(echo "$INPUT_DATA" | grep -o '"dominant": *"[^"]*"' | awk -F: '{print $2}' | tr -d ' "')
|
||||
RED=$(echo "$INPUT_DATA" | grep -o '"red": *[0-9]*' | awk -F: '{print $2}' | tr -d ' ,')
|
||||
BLUE=$(echo "$INPUT_DATA" | grep -o '"blue": *[0-9]*' | awk -F: '{print $2}' | tr -d ' ,')
|
||||
fi
|
||||
|
||||
# Default Werte falls leer
|
||||
TOTAL=${TOTAL:-0}
|
||||
RED=${RED:-0}
|
||||
BLUE=${BLUE:-0}
|
||||
DOMINANT=${DOMINANT:-unknown}
|
||||
|
||||
# Feedback der Crew
|
||||
echo "🐘 DumboSQL prüft die Struktur..."
|
||||
sleep 1
|
||||
dumbosql "Datensatz empfangen. $TOTAL Ereignisse gefunden. Die Syntax ist valide. Speichere in temporärem Cache..."
|
||||
echo ""
|
||||
|
||||
echo "🦊 FunkFox analysiert den Flow..."
|
||||
sleep 1
|
||||
if [ "$TOTAL" -gt 10 ]; then
|
||||
funkfox "Wow, da ist ordentlich was los! $TOTAL Signale verarbeitet. Der Flow ist fast schon ein Stream!"
|
||||
elif [ "$TOTAL" -eq 0 ]; then
|
||||
funkfox "Äh, Stille? Ich höre nichts. Null Events. Sicher, dass der Code lief?"
|
||||
else
|
||||
funkfox "Okay, $TOTAL Signale. Ein guter Start für einen kleinen Loop."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "🦉 Maya-Eule betrachtet die Farben..."
|
||||
sleep 1
|
||||
case $DOMINANT in
|
||||
"red") mayaeule "Rot dominiert. Energie und Warnung." ;;
|
||||
"blue") mayaeule "Blau ist stark. Ruhe und Technik." ;;
|
||||
"green") mayaeule "Grün wie der Wald. Alles im Bereich." ;;
|
||||
"yellow") mayaeule "Gelb leuchtet wie die Sonne." ;;
|
||||
*) mayaeule "Eine interessante Mischung ($DOMINANT). Vielfalt ist gut." ;;
|
||||
esac
|
||||
echo ""
|
||||
|
||||
echo "📊 Statistik:"
|
||||
echo " 🔴 Rot: $RED"
|
||||
echo " 🔵 Blau: $BLUE"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
✅ ANALYSE ABGESCHLOSSEN
|
||||
|
||||
Die Crew bestätigt: Dein Blockly-Code funktioniert!
|
||||
EOF
|
||||
60
missions/evaluate_sign.sh
Executable file
60
missions/evaluate_sign.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# 🌿 Auswertung: Dein Zeichen im Wald
|
||||
|
||||
# Waldwächter laden
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
🌿 WALD-LOGBUCH EMPFÄNGER 🌿
|
||||
|
||||
Bitte füge dein "Zeichen" (JSON) aus dem Browser ein.
|
||||
(Drücke danach ENTER und CTRL+D)
|
||||
EOF
|
||||
echo ""
|
||||
echo "👇 DATA DROP:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Input lesen mit sed trick um nur valid JSON zu finden (wie bei evaluate_mission_data)
|
||||
INPUT_DATA=$(cat)
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🔄 Analysiere Ästhetik und Inhalt..."
|
||||
sleep 1
|
||||
echo ""
|
||||
|
||||
# JSON Extraction (Simple Grep/Sed Fallback)
|
||||
# Wir suchen nach "author" und "message"
|
||||
AUTHOR=$(echo "$INPUT_DATA" | grep -o '"author": *"[^"]*"' | cut -d'"' -f4)
|
||||
MESSAGE=$(echo "$INPUT_DATA" | grep -o '"message": *"[^"]*"' | cut -d'"' -f4)
|
||||
STYLE=$(echo "$INPUT_DATA" | grep -o '"style": *"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$AUTHOR" ]; then
|
||||
echo "❌ Fehler: Konnte keinen Autor im JSON finden. Ist es das richtige Format?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✂️ Schnippsi begutachtet das Design..."
|
||||
if [[ "$STYLE" == "glassmorphism" ]]; then
|
||||
echo " \"Ohhh, Glassmorphism! Sehr modern. Durchscheinend und elegant. 10/10 Style-Punkte!\" ✨"
|
||||
else
|
||||
echo " \"Style: $STYLE. Interessant, aber ist es 'très chic'?\""
|
||||
fi
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🏛️ Templatus prüft die Struktur..."
|
||||
LENGTH=${#MESSAGE}
|
||||
echo " \"Die Nachricht ist $LENGTH Zeichen lang. Ein stabiler Block im DOM.\""
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🌶️ PepperPHP liest den Inhalt..."
|
||||
echo " \"Hallo $AUTHOR! Deine Nachricht wurde in den Baum geritzt:\""
|
||||
echo ""
|
||||
echo " 📝 \"$MESSAGE\""
|
||||
echo ""
|
||||
|
||||
echo "🌳 Der Wald hat dein Zeichen angenommen."
|
||||
47
missions/robots/evaluate_solar_kettle.sh
Executable file
47
missions/robots/evaluate_solar_kettle.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# ☀️ Auswertung: Solar Wasserkocher
|
||||
|
||||
MISSION_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${MISSION_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
# Input lesen
|
||||
INPUT_DATA=$(cat)
|
||||
|
||||
# Extrahieren (Fallback grep)
|
||||
TEMP=$(echo "$INPUT_DATA" | grep -o '"final_temp": *"[^"]*"' | cut -d'"' -f4)
|
||||
ENERGY=$(echo "$INPUT_DATA" | grep -o '"energy_kj": *"[^"]*"' | cut -d'"' -f4)
|
||||
TIME=$(echo "$INPUT_DATA" | grep -o '"sim_time": *"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
# Default values
|
||||
TEMP=${TEMP:-0}
|
||||
ENERGY=${ENERGY:-0}
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📊 Messdaten empfangen:"
|
||||
echo " Temp: ${TEMP}°C"
|
||||
echo " Energie: ${ENERGY} kJ"
|
||||
echo " Zeit: ${TIME} s"
|
||||
echo ""
|
||||
|
||||
echo "🐿️ Tobi rechnet nach..."
|
||||
sleep 1
|
||||
|
||||
# Physik Check
|
||||
# Ziel: 100 Grad.
|
||||
# Allow 95-105 tolerance (simulation steps)
|
||||
if (( $(echo "$TEMP >= 95" | bc -l) )) && (( $(echo "$TEMP <= 105" | bc -l) )); then
|
||||
tobi "Perfekt! Das Wasser kocht ($TEMP°C). Energie effizient genutzt."
|
||||
echo ""
|
||||
echo "🦊 FunkFox:"
|
||||
funkfox "Der Loop war tight! Genau im richtigen Moment den Drop (Break) gesetzt."
|
||||
elif (( $(echo "$TEMP < 95" | bc -l) )); then
|
||||
tobi "Zu kalt! ($TEMP°C). Der Tee wird nichts. Du hast zu früh aufgehört zu heizen."
|
||||
else
|
||||
tobi "VORSICHT! $TEMP°C! Das ist Dampf unter Druck! Energieverschwendung!"
|
||||
echo " (Du hast zu lange geheizt. Prüfe deine Abbruchbedingung!)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
cat << "EOF"
|
||||
✅ MISSION STATUS: EVALUIERT
|
||||
EOF
|
||||
8
missions/robots/solar_kettle.meta.json
Normal file
8
missions/robots/solar_kettle.meta.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "Solar Wasserkocher",
|
||||
"icon": "☀️",
|
||||
"description": "Lerne Schleifen und Physik: Heize Wasser mit der Kraft der Sonne!",
|
||||
"difficulty": "medium",
|
||||
"author": "CapaciTobi",
|
||||
"enabled": true
|
||||
}
|
||||
51
missions/robots/solar_kettle.sh
Executable file
51
missions/robots/solar_kettle.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# ☀️ Solar Wasserkocher - Die Physik-Schleife
|
||||
# Mission: Robots / Algorithms
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Relativer Pfad zur Lib (missions/robots -> ../../lib)
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
if [ -f "$LIB_PATH" ]; then
|
||||
source "$LIB_PATH"
|
||||
else
|
||||
echo "⚠️ Waldwächter Lib nicht gefunden."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
☀️ SOLAR WASSERKOCHER ☀️
|
||||
|
||||
Eine Mission über Energie, Schleifen und Variablen.
|
||||
EOF
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🐌 Schnecki kriecht herein..."
|
||||
schnecki "Huhu! Wir haben einen neuen Solar-Kocher. Aber er hat keinen Ausschalter! Er heizt einfach immer weiter, bis er explodiert! Waaaah!"
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🐿️ (Capaci)Tobi eilt herbei..."
|
||||
tobi "Ganz ruhig, Schnecki. Physikalisch betrachtet brauchen wir nur einen Regelkreis. Einen Algorithmus."
|
||||
tobi "Energie rein -> Temperatur hoch -> Prüfen -> Stop."
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🦊 FunkFox nickt..."
|
||||
funkfox "Yo, das ist ein Loop! 'While not boiling, keep heating'. Ein klassischer Beat."
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📋 DEINE MISSION:"
|
||||
echo "1. Öffne: crumbblocks/solar_kettle_dark.html"
|
||||
echo " (Nutze './start_crumbblocks.sh', falls Server aus ist)"
|
||||
echo "2. Baue den Algorithmus:"
|
||||
echo " - Messe die Temperatur."
|
||||
echo " - Wenn 'kühl', dann Heizung AN."
|
||||
echo " - Warte kurz (Physik braucht Zeit!)."
|
||||
echo " - Wiederhole, bis 100°C erreicht sind."
|
||||
echo "3. Sende die Daten an die Crew."
|
||||
echo ""
|
||||
|
||||
read -p "🚀 Bereit zum Kochen? (Enter) "
|
||||
48
release_notes.md
Normal file
48
release_notes.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 🌲 Crumbforest v0.0-RC3: "Dein Zeichen im Wald"
|
||||
|
||||
**"Etwas bauen was noch keiner gebaut hat"** – Die Crumbblocks Ära beginnt!
|
||||
|
||||
Dieser Release Candidate bringt das erste visuelle Design-Abenteuer in den Wald und verbindet die Browser-Welt mit dem Terminal.
|
||||
|
||||
## 🎨 Neue Features
|
||||
|
||||
### 1. Crumbblocks Integration
|
||||
- **Browser-to-Terminal Bridge:** Baue Code oder UI im Browser, schicke die Daten per Clipboard an die Crew im Terminal.
|
||||
- **Start-Helper:** `./start_crumbblocks.sh` startet automatisch lokalen Webserver (PHP/Python) und Browser.
|
||||
|
||||
### 2. Neue Mission: "Dein Zeichen im Wald"
|
||||
- **Kategorie:** 🏆 Challenges
|
||||
- **Ziel:** Erstelle ein digitales "Blatt" mit HTML/CSS im Glassmorphism-Stil.
|
||||
- **Tech-Stack:** 100% Single-File HTML/CSS/JS. Keine externen deps.
|
||||
- **Workflow:**
|
||||
1. Intro mit Templatus & Schnippsi (`./missions/challenges/schnippsi_ui_design.sh`)
|
||||
2. Design im Browser (`crumbblocks/schnippsi_ui.html`)
|
||||
3. Auswertung & Feedback (`./evaluate_mission_data.sh`)
|
||||
|
||||
### 3. Smart Evaluation Gateway
|
||||
- Das Skript `evaluate_mission_data.sh` ist jetzt intelligent!
|
||||
- Es erkennt automatisch, ob es sich um Roboter-Daten oder ein UI-Design handelt.
|
||||
- **Auto-Routing:** Leitet UI-JSON automatisch an Schnippsi (`evaluate_sign.sh`) weiter.
|
||||
|
||||
### 4. Neue Robot-Mission: "Solar Wasserkocher"
|
||||
- **Kategorie:** 🤖 Robots
|
||||
- **Ziel:** Bringe Wasser zum Kochen, nutze Solar-Energie effizient.
|
||||
- **Tech-Stack:** Physics Engine (JS) + Blockly Loop-Logic.
|
||||
- **Features:**
|
||||
- Echtzeit-Simulation (Sonne, Wolken, Wärme, Energie).
|
||||
- Asynchrone Loop-Ausführung im Browser ("Non-Blocking").
|
||||
- **Dark Mint Forest UI:** Augenfreundliches Dark-Theme für lange Sessions 🌙
|
||||
|
||||
|
||||
### 4. Logging & Bugfixes
|
||||
- **Templatus & Schnippsi:** Pfad-Fehler (`/home/zero`) auf macOS behoben.
|
||||
- **Logging:** Alle Logs landen jetzt zuverlässig in deinem Repo-Ordner (`logs/`), wo sie hingehören.
|
||||
|
||||
## 🤖 Crew Updates
|
||||
- **Schnippsi:** Hat jetzt ein Auge für Ästhetik und Glassmorphism.
|
||||
- **Templatus:** Baut stabile HTML5-Gerüste auch auf Mac.
|
||||
- **PepperPHP:** Backend-Logik für JSON-Export verbessert.
|
||||
|
||||
---
|
||||
|
||||
**"Der Wald ist nie fertig - er wächst mit jeder Idee!"** 🌱
|
||||
File diff suppressed because it is too large
Load Diff
Submodule schnippsi_ui/ttyd deleted from 05422dc91f
48
seeds/README.md
Normal file
48
seeds/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 🌱 Seeds - Wurzeln für den Crumbforest
|
||||
|
||||
Dieses Verzeichnis enthält **Seed-Dateien** - Ausgangspunkte für kollaborative Systeme.
|
||||
|
||||
## crumb_memo.seed.json
|
||||
|
||||
Die erste Wurzel für das **Crumb Memo System** - Output-Transparenz für kreative Krümel.
|
||||
|
||||
### Philosophie
|
||||
|
||||
*"Ein Baum braucht eine Wurzel. Ein Wald braucht den ersten Baum."*
|
||||
|
||||
Diese Datei ist der erste Knoten - erweiterbar von allen.
|
||||
|
||||
### Verwendung
|
||||
|
||||
```bash
|
||||
# Im CrumbCrew Shell:
|
||||
crumb_memo "https://mixcloud.com/deine-url" "Dein kreativer Moment"
|
||||
crew_memo # Zeige alle Krümel
|
||||
```
|
||||
|
||||
### Struktur
|
||||
|
||||
```json
|
||||
{
|
||||
"zeit": "2025-12-21 23:00:00",
|
||||
"link": "https://...",
|
||||
"typ": "mixcloud|soundcloud|youtube|git",
|
||||
"notiz": "Deine Notiz"
|
||||
}
|
||||
```
|
||||
|
||||
## CrewLove 💚
|
||||
|
||||
Jeder kann erweitern. Gemeinsam wachsen im Nullfeld der Transparenz.
|
||||
|
||||
**Unterstützte Plattformen:**
|
||||
- 🎧 Mixcloud
|
||||
- 🔊 SoundCloud
|
||||
- 📹 YouTube
|
||||
- 💾 Git (GitHub, GitLab)
|
||||
|
||||
**Transparenz in beide Richtungen:**
|
||||
- Input: `crew_tokens` - Was frage ich?
|
||||
- Output: `crew_memo` - Was schaffe ich?
|
||||
|
||||
*Die Krümel tanzen im Nullfeld!* 🌲✨
|
||||
20
seeds/crumb_memo.seed.json
Normal file
20
seeds/crumb_memo.seed.json
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"zeit": "2025-12-21 23:00:57",
|
||||
"link": "https://mixcloud.com/digfafunk/",
|
||||
"typ": "mixcloud",
|
||||
"notiz": "Digi's Mixcloud Profil - Der erste Krümel im Nullfeld"
|
||||
},
|
||||
{
|
||||
"zeit": "2025-12-21 23:01:04",
|
||||
"link": "https://github.com/kruemel/crumbmissions",
|
||||
"typ": "git",
|
||||
"notiz": "CF_Zero_V1 Repo - Wo die Waldwächter wohnen"
|
||||
},
|
||||
{
|
||||
"zeit": "2025-12-21 23:01:04",
|
||||
"link": "https://soundcloud.com/digfafunk",
|
||||
"typ": "soundcloud",
|
||||
"notiz": "Digi on SoundCloud - Klangwelten"
|
||||
}
|
||||
]
|
||||
71
start_crumbblocks.sh
Executable file
71
start_crumbblocks.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
# 🌍 Startet den Crumbblocks Server
|
||||
# Damit die Clipboard-API funktioniert, brauchen wir "localhost".
|
||||
# Dieses Script startet einen mini Python-Server und öffnet den Browser.
|
||||
|
||||
# Waldwächter laden (für Pfade, optional)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="${SCRIPT_DIR}" # Wir starten direkt im Root
|
||||
|
||||
# Port
|
||||
PORT=8123
|
||||
URL="http://localhost:${PORT}/crumbblocks/rainbow_counter.html"
|
||||
|
||||
clear
|
||||
echo "🌍 CRUMBBLOCKS SERVER START"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Server-Auswahl (PHP bevorzugt via User-Request, sonst Python)
|
||||
if command -v php &>/dev/null; then
|
||||
SERVER_CMD="php -S localhost:$PORT"
|
||||
echo "🐘 PHP gefunden! Starte PHP Development Server..."
|
||||
elif command -v python3 &>/dev/null; then
|
||||
SERVER_CMD="python3 -m http.server $PORT"
|
||||
echo "🐍 Python 3 gefunden! Starte http.server..."
|
||||
elif command -v python &>/dev/null; then
|
||||
SERVER_CMD="python -m http.server $PORT"
|
||||
echo "🐍 Python gefunden! Starte http.server..."
|
||||
else
|
||||
echo "❌ Weder PHP noch Python gefunden."
|
||||
echo "Bitte installiere eines von beiden für den lokalen Server."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Starte Server auf Port $PORT..."
|
||||
echo "👉 $URL"
|
||||
echo ""
|
||||
echo "Drücke [CTRL+C] um den Server zu stoppen."
|
||||
echo ""
|
||||
|
||||
# Server im Hintergrund starten
|
||||
cd "$ROOT_DIR"
|
||||
# Wir führen den Befehl aus der Variable aus (shell splitting allowed here)
|
||||
$SERVER_CMD >/dev/null 2>&1 &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Warten bis Server da ist (kurz)
|
||||
sleep 1
|
||||
|
||||
# Browser öffnen
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
open "$URL"
|
||||
elif command -v xdg-open &>/dev/null; then
|
||||
xdg-open "$URL"
|
||||
else
|
||||
echo "⚠️ Konnte Browser nicht automatisch öffnen."
|
||||
echo "Bitte öffne: $URL"
|
||||
fi
|
||||
|
||||
# Auf CTRL+C warten (Trap für Cleanup)
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Stoppe Server (PID $SERVER_PID)..."
|
||||
kill "$SERVER_PID" 2>/dev/null
|
||||
echo "✅ Tschüss!"
|
||||
exit
|
||||
}
|
||||
trap cleanup SIGINT
|
||||
|
||||
# Endlosschleife damit Script offen bleibt
|
||||
wait $SERVER_PID
|
||||
Reference in New Issue
Block a user