Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/scripts/deploy-website.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,17 @@ if ! lftp -c "$connect; get $MANIFEST -o \"$work/old\"" 2>/dev/null; then
: > "$work/old"
fi

# Die Assets des vorigen Builds bleiben eine Version lang liegen: Ein Browser, der noch
# die alte Seite offen oder im Cache hat, findet so seine Skripte (sonst bleibt sie leer).
grep '^app/assets/' "$work/old" | LC_ALL=C sort -u > "$work/keep" || true

# app/assets/ wird von Vite bei jedem Build komplett neu erzeugt (Dateinamen mit
# Hash). Was dort auf dem Server liegt und nicht mehr zum Build gehört, ist veraltet,
# auch wenn es noch von Hand hochgeladen wurde.
lftp -c "$connect; cls -1 app/assets/" 2>/dev/null | sed 's|^.*/||; s|^|app/assets/|' | grep -v '/$' >> "$work/old" || true

# Dateien, die früher hochgeladen wurden und jetzt nicht mehr dazugehören
LC_ALL=C sort -u "$work/old" | LC_ALL=C comm -23 - "$work/new" > "$work/stale"
LC_ALL=C sort -u "$work/old" | LC_ALL=C comm -23 - "$work/new" | LC_ALL=C comm -23 - "$work/keep" > "$work/stale"
: > "$work/rm.lftp"
while IFS= read -r f; do
case "$f" in
Expand Down Expand Up @@ -154,3 +158,9 @@ put "$work/htaccess" -o .htaccess
put "$work/new" -o $MANIFEST
LFTP
echo "Fertig."

# Kontrolle: Schickt der Server die Cache-Regeln aus .htaccess mit?
for url in "${SITE_URL:-https://visustruct.org}/" "${SITE_URL:-https://visustruct.org}/app/index.html"; do
cc="$(curl -sI "$url" | tr -d '\r' | grep -i '^cache-control:' | cut -d' ' -f2- || true)"
echo "::notice::$url Cache-Control: ${cc:-(keiner)}"
done
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The format is loosely inspired by Keep a Changelog.

- **Web, Code live:** the setting “Swing support” is now called “Real Java (with Swing)” in all languages.
- **Web, learning area:** an open lesson shows its number as a badge next to the title, like in the lesson list.
- **Web:** the settings menu shows the version number (now 0.3.39) right next to “About VisuStruct”.
- **Web:** the settings menu shows the version number (now 0.3.40) right next to “About VisuStruct”.
- **Web:** the settings menu (gear) is grouped into sections with headings: Display (text size, language), Code live (programming language, code keywords), Java (class, main, Swing support; only shown when Java is selected) and About VisuStruct at the bottom.
- **Web:** toolbar buttons are icons with tooltips. The top bar keeps New, Open, Save and Settings; undo, clipboard, delete, export, code and simulation moved into a toolbar above the Elements and diagram columns. Run and Stop in Code live are icons too.
- **Web:** Column widths are freely adjustable: with the diagram collapsed, Learning and Code live share the width at a draggable divider; with all three open, Learning and Code live can be dragged wider until the diagram keeps 240 px.
Expand Down
4 changes: 2 additions & 2 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "visustruct-web",
"private": true,
"version": "0.3.39",
"version": "0.3.40",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
62 changes: 57 additions & 5 deletions web/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,62 @@ import { App, applyCanvasStubTexts } from './ui/app'
import { installTooltips } from './ui/tooltip'
import { preferredLanguage, setCodeKeywords, setLanguage, storedCodeKeywords } from './ui/i18n'

// Nach einem Update kann eine noch offene oder zwischengespeicherte Seite Dateien der alten Version
// nachladen wollen, die es nicht mehr gibt. Dann einmal neu laden statt leer zu bleiben.
const reloadKey = 'visustruct.reloadedAfterError'

function reloadOnce(): boolean {
try {
const last = Number(sessionStorage.getItem(reloadKey))
if (last && Date.now() - last < 30_000) return false
sessionStorage.setItem(reloadKey, String(Date.now()))
} catch {
return false
}
location.reload()
return true
}

/** Statt einer leeren Seite: kurzer Hinweis mit Knopf zum Neuladen (Texte fest, die Übersetzungen fehlen ja evtl.). */
function showStartupError(error: unknown) {
console.error(error)
const de = (navigator.language || '').toLowerCase().startsWith('de')
const box = document.createElement('div')
box.className = 'startup-error'
const title = document.createElement('h1')
title.textContent = de ? 'VisuStruct konnte nicht starten' : 'VisuStruct could not start'
const text = document.createElement('p')
text.textContent = de
? 'Wahrscheinlich gibt es gerade eine neue Version. Lade die Seite neu. Hilft das nicht, drücke Strg+Umschalt+R (Mac: ⌘⇧R).'
: 'There is probably a new version. Reload the page. If that does not help, press Ctrl+Shift+R (Mac: ⌘⇧R).'
const button = document.createElement('button')
button.textContent = de ? 'Neu laden' : 'Reload'
button.addEventListener('click', () => location.reload())
const detail = document.createElement('pre')
detail.textContent = String(error instanceof Error ? error.message : error)
box.append(title, text, button, detail)
;(document.getElementById('app') ?? document.body).replaceWith(box)
}

window.addEventListener('vite:preloadError', (event) => {
if (reloadOnce()) event.preventDefault()
})

// Erst die Sprache (sie übernimmt ein altes „Java (Keywords)“), dann den Schalter Code-Keywords.
const language = preferredLanguage()
void Promise.all([setLanguage(language, false), setCodeKeywords(storedCodeKeywords(), false)]).then(() => {
applyCanvasStubTexts()
installTooltips()
new App(document.getElementById('app')!)
})
Promise.all([setLanguage(language, false), setCodeKeywords(storedCodeKeywords(), false)])
.then(() => {
applyCanvasStubTexts()
installTooltips()
new App(document.getElementById('app')!)
try {
sessionStorage.removeItem(reloadKey)
} catch {
/* egal */
}
})
.catch((error: unknown) => {
// Fehlende Datei nach einem Update: einmal neu laden, sonst Hinweis zeigen.
if (/dynamically imported module|Importing a module script failed|error loading dynamically/i.test(String(error)) && reloadOnce()) return
showStartupError(error)
})
22 changes: 22 additions & 0 deletions web/src/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3210,3 +3210,25 @@ button.danger-text:not(:disabled) {
.learn-mark.gruen {
background: #dcfce7;
}

/* Hinweis statt leerer Seite, wenn die App nicht starten konnte (main.ts) */
.startup-error {
max-width: 520px;
margin: 15vh auto 0;
padding: 0 16px;
font: 16px/1.5 system-ui, -apple-system, sans-serif;
color: var(--text);
}
.startup-error h1 {
font-size: 22px;
}
.startup-error button {
font-size: 16px;
padding: 8px 16px;
}
.startup-error pre {
margin-top: 24px;
color: var(--muted);
font-size: 12px;
white-space: pre-wrap;
}
1,946 changes: 973 additions & 973 deletions website/app/assets/index-Cz9Fo9zD.js → website/app/assets/index-D05tJOQz.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions website/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<title>VisuStruct Web</title>
<meta name="description" content="VisuStruct im Browser: Struktogramme nach Nassi-Shneiderman erstellen, simulieren und als Java, Python oder JavaScript ausgeben. Dateien bleiben auf deinem Gerät." />
<link rel="icon" type="image/png" href="data:image/png;base64,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" />
<script type="module" crossorigin src="./assets/index-Cz9Fo9zD.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CmgDRnXe.css">
<script type="module" crossorigin src="./assets/index-D05tJOQz.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DdxaLIVP.css">
</head>
<body>
<div id="app"></div>
Expand Down
Loading