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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is loosely inspired by Keep a Changelog.

### Added

- **Web 0.13.11, legend in the flowchart exercise:** the PAP block takes `legende: ja` and then shows the four symbols with their meaning (Start oder Ende, Anweisung, Verzweigung, Ein- oder Ausgabe) under the chart; lesson 1 step 8 „Übung: Kuchen backen als PAP“ uses it (de/en/pt). The prepared elements now stand in a column right of the chart (sticky while scrolling) instead of below it, and the chart is only as wide as its content, so on a laptop chart and elements fit on one screen.
- **Web 0.13.10, build a flowchart yourself in lesson 1:** new lesson block `::: pap` („PAP bauen“): a flowchart with empty dashed places and the finished, labelled elements shuffled below; tap an element and then its place, or drag it there (mouse, pen, finger). „Prüfen“ marks right and wrong places, after a wrong try „Lösung zeigen“. Lesson 1 gets the new step „Übung: Kuchen backen als PAP“ (Ludwig bakes a cake: two loops, one output). Lesson 1 now introduces the English word **flowchart** for the PAP right at the start, and the quiz asks for it (de, pt).
- **Web 0.13.9, flowchart in lesson 1 explained in more detail:** the old step „Andere Darstellungen: PAP und Pseudocode“ is now three steps. „Andere Darstellungen: Der PAP“ shows the tea Struktogramm again, then the same algorithm as PAP, its symbols and advantages (easy to read, flowcharts are used outside programming too). „Nachteile des PAP: Sprünge“ shows the tea example as a program with line numbers and GOTO, what goes wrong when one line is inserted, jumps out of loops and spaghetti code (Dijkstra moved here). „Pseudocode und Vergleich“ keeps pseudocode and the comparison. New checklist lines and a quiz question on GOTO; de/en/pt.
- **Web 0.13.8, loop in lesson 1 is a „gekipptes L“:** lesson 1 no longer calls the loop shape in the Struktogramm a „Klammer links“ but a „gekipptes L“ (condition on top, strip on the left), in the text, the comparison and the quiz (en „upside-down L“, pt „L invertido“).
Expand Down
Binary file added web/laptop2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion web/lessons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ Für „Fehler finden“ eignet sich eine normale Übung mit `code: java`, deren
Element und dann seinen Platz an oder zieht es hin. `start:`/`ende:` sind Ovale, `anweisung:` Rechtecke, `eingabe:`/
`ausgabe:` Parallelogramme, `frage:` Rauten. Die Zeilen stehen von oben nach unten. Eine Frage braucht nach ` | ` ihren
zweiten Ausgang: `nein -> Text` ist ein Pfeil zurück nach oben zum Element mit diesem Text, `nein: Text` eine Anweisung
rechts daneben, von der ein Pfeil zurück zur Raute führt. Statt `nein` geht auch `ja`. Die Schlüsselwörter bleiben auch in
rechts daneben, von der ein Pfeil zurück zur Raute führt. Statt `nein` geht auch `ja`. `legende: ja` zeigt unter dem Plan die vier Symbole mit
ihrer Bedeutung. Die Schlüsselwörter bleiben auch in
en/pt deutsch, die Beschriftung ja/nein kommt aus der Oberflächensprache.

```
Expand Down
1 change: 1 addition & 0 deletions web/lessons/de/01-was-ist-ein-struktogramm.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ anweisung: Kuchen herausnehmen
ausgabe: „Der Kuchen ist fertig!“
ende: Ende
erklärung: Zwei Schleifen, zwei Pfeile zurück nach oben: Beim Rühren führt der Pfeil zurück zu „Teig rühren“, beim Backen von „5 Minuten weiterbacken“ zurück zur Frage. Die Ausgabe steht im Parallelogramm.
legende: ja
:::

## Nachteile des PAP: Sprünge
Expand Down
1 change: 1 addition & 0 deletions web/lessons/en/01-was-ist-ein-struktogramm.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ anweisung: Take the cake out
ausgabe: “The cake is ready!”
ende: End
erklärung: Two loops, two arrows back up: when stirring, the arrow leads back to “Stir the batter”; when baking, it leads from “Bake 5 more minutes” back to the question. The output sits in the parallelogram.
legende: ja
:::

## Drawbacks of the flowchart: jumps
Expand Down
1 change: 1 addition & 0 deletions web/lessons/pt/01-was-ist-ein-struktogramm.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ anweisung: Tirar o bolo do forno
ausgabe: “O bolo está pronto!”
ende: Fim
erklärung: Dois ciclos, duas setas para cima: ao mexer, a seta volta a “Mexer a massa”; ao cozer, vai de “Cozer mais 5 minutos” de volta à pergunta. A saída fica no paralelogramo.
legende: ja
:::

## Desvantagens do fluxograma: saltos
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.13.10",
"version": "0.13.11",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
6 changes: 5 additions & 1 deletion web/src/i18n/de.strings
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@
"web_learn_cloze_all_right" = "Alle Lücken richtig!";
"web_learn_cloze_solution" = "Lösung zeigen";
"web_learn_cloze_again" = "Nochmal";
"web_learn_pap_hint" = "Tippe ein Element unten an und dann seinen Platz im Plan. Du kannst es auch hinziehen. Ein belegter Platz gibt sein Element beim Antippen zurück.";
"web_learn_pap_hint" = "Tippe ein Element rechts an und dann seinen Platz im Plan. Du kannst es auch hinziehen. Ein belegter Platz gibt sein Element beim Antippen zurück.";
"web_learn_pap_score_fmt" = "%d von %d Elementen richtig";
"web_learn_pap_all_right" = "Alles richtig! Dein PAP ist fertig.";
"web_learn_pap_missing_fmt" = "Es fehlen noch %d Elemente.";
"web_learn_pap_empty" = "Leerer Platz";
"web_learn_pap_yes" = "ja";
"web_learn_pap_no" = "nein";
"web_learn_pap_legend_oval" = "Start oder Ende";
"web_learn_pap_legend_rect" = "Anweisung";
"web_learn_pap_legend_diamond" = "Verzweigung: Frage mit ja und nein";
"web_learn_pap_legend_para" = "Ein- oder Ausgabe";
"web_learn_review" = "Nachlesen";
"web_learn_terms" = "Fachbegriffe";
"web_learn_resize" = "Breite des Lernbereichs ändern (Doppelklick: zurücksetzen)";
Expand Down
6 changes: 5 additions & 1 deletion web/src/i18n/en.strings
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@
"web_learn_cloze_all_right" = "All gaps correct!";
"web_learn_cloze_solution" = "Show solution";
"web_learn_cloze_again" = "Try again";
"web_learn_pap_hint" = "Tap an element below and then its place in the chart. You can also drag it there. Tapping a filled place gives its element back.";
"web_learn_pap_hint" = "Tap an element on the right and then its place in the chart. You can also drag it there. Tapping a filled place gives its element back.";
"web_learn_pap_score_fmt" = "%d of %d elements correct";
"web_learn_pap_all_right" = "All correct! Your flowchart is finished.";
"web_learn_pap_missing_fmt" = "%d elements are still missing.";
"web_learn_pap_empty" = "Empty place";
"web_learn_pap_yes" = "yes";
"web_learn_pap_no" = "no";
"web_learn_pap_legend_oval" = "Start or end";
"web_learn_pap_legend_rect" = "Statement";
"web_learn_pap_legend_diamond" = "Decision: question with yes and no";
"web_learn_pap_legend_para" = "Input or output";
"web_learn_review" = "Review";
"web_learn_terms" = "Technical terms";
"web_learn_resize" = "Change the width of the learning area (double-click: reset)";
Expand Down
6 changes: 5 additions & 1 deletion web/src/i18n/pt-PT.strings
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,17 @@
"web_learn_cloze_all_right" = "Todos os espaços certos!";
"web_learn_cloze_solution" = "Mostrar solução";
"web_learn_cloze_again" = "De novo";
"web_learn_pap_hint" = "Toca num elemento em baixo e depois no seu lugar no fluxograma. Também o podes arrastar. Tocar num lugar ocupado devolve o elemento.";
"web_learn_pap_hint" = "Toca num elemento à direita e depois no seu lugar no fluxograma. Também o podes arrastar. Tocar num lugar ocupado devolve o elemento.";
"web_learn_pap_score_fmt" = "%d de %d elementos certos";
"web_learn_pap_all_right" = "Tudo certo! O teu fluxograma está pronto.";
"web_learn_pap_missing_fmt" = "Ainda faltam %d elementos.";
"web_learn_pap_empty" = "Lugar vazio";
"web_learn_pap_yes" = "sim";
"web_learn_pap_no" = "não";
"web_learn_pap_legend_oval" = "Início ou fim";
"web_learn_pap_legend_rect" = "Instrução";
"web_learn_pap_legend_diamond" = "Decisão: pergunta com sim e não";
"web_learn_pap_legend_para" = "Entrada ou saída";
"web_learn_review" = "Rever";
"web_learn_terms" = "Termos técnicos";
"web_learn_resize" = "Alterar a largura da área de aprendizagem (duplo clique: repor)";
Expand Down
6 changes: 5 additions & 1 deletion web/src/learn/pap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* ausgabe: „Fertig!“
* ende: Ende
* erklärung: Wird gezeigt, wenn alles stimmt.
* legende: ja
* :::
*
* Die Zeilen stehen von oben nach unten in der Hauptspalte. Eine Frage (Raute) hat zwei Ausgänge: Einer geht nach unten
Expand Down Expand Up @@ -38,6 +39,8 @@ export interface Pap {
task: string
nodes: PapNode[]
explanation?: string
/** `legende: ja` zeigt unter dem Plan, was die Symbole bedeuten. */
legend?: boolean
}

const shapeOf: Record<string, PapShape> = {
Expand All @@ -62,6 +65,7 @@ export function parsePap(inner: string[], file: string): Pap {
if (key === 'titel') pap.title = value
else if (key === 'auftrag') pap.task = value
else if (key === 'erklärung' || key === 'erklaerung') pap.explanation = value
else if (key === 'legende') pap.legend = /^(ja|yes|true|sim)$/i.test(value)
else if (key in shapeOf) {
const [text, exit] = value.split(/\s+\|\s+/)
if (!text) throw new Error(`${file}: Leeres Element im PAP: „${line}“.`)
Expand Down Expand Up @@ -112,7 +116,7 @@ const MAIN_X = 140
const MAIN_W = 180
const SIDE_X = 327
const SIDE_W = 130
const GAP = 24
const GAP = 20

export function papHeight(shape: PapShape): number {
return shape === 'raute' ? 56 : shape === 'oval' ? 30 : 36
Expand Down
44 changes: 39 additions & 5 deletions web/src/ui/papView.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { inline } from '../learn/markdown'
import { PAP_WIDTH, papFits, papLayout, papLines, type Pap, type PapBox, type PapShape } from '../learn/pap'
import { papFits, papLayout, papLines, type Pap, type PapBox, type PapShape } from '../learn/pap'
import { t } from './i18n'

/**
Expand Down Expand Up @@ -58,7 +58,7 @@ function label(text: string, x: number, y: number, yes: boolean): SVGElement {

/** Element als kleines Bild für die Ablage unten. */
function cardSVG(shape: PapShape, text: string): SVGElement {
const lines = papLines(text, 150)
const lines = papLines(text, 110)
const long = Math.max(...lines.map((l) => l.length))
const w = Math.max(70, long * 6.6 + (shape === 'raute' ? 56 : 26))
const hgt = shape === 'raute' ? 48 : lines.length > 1 ? 40 : 30
Expand All @@ -68,7 +68,7 @@ function cardSVG(shape: PapShape, text: string): SVGElement {
shapeE.setAttribute('fill', FILL[shape])
shapeE.setAttribute('stroke', '#1c1c1e')
shapeE.setAttribute('stroke-width', '1.5')
svg.append(shapeE, textEl(text, { ...b, w: lines.length > 1 ? 164 : w }, shape))
svg.append(shapeE, textEl(text, { ...b, w: lines.length > 1 ? 124 : w }, shape))
return svg
}

Expand All @@ -83,6 +83,28 @@ function shuffled(n: number): number[] {
return order
}

/** Legende: Symbol und Bedeutung für alle vier Formen. */
function legend(): HTMLElement {
const list = h('dl', { class: 'learn-pap-legend' })
const items: [PapShape, string][] = [
['oval', 'web_learn_pap_legend_oval'],
['rechteck', 'web_learn_pap_legend_rect'],
['raute', 'web_learn_pap_legend_diamond'],
['parallelogramm', 'web_learn_pap_legend_para'],
]
for (const [shape, key] of items) {
const w = shape === 'raute' ? 44 : 40
const svg = s('svg', { viewBox: `0 0 ${w + 4} 28`, width: w + 4, height: 28, 'aria-hidden': 'true' })
const el = shapeEl(shape, { x: (w + 4) / 2, y: 14, w, h: shape === 'raute' ? 24 : 18 })
el.setAttribute('fill', FILL[shape])
el.setAttribute('stroke', '#1c1c1e')
el.setAttribute('stroke-width', '1.5')
svg.append(el)
list.append(h('dt', {}, svg), h('dd', {}, t(key)))
}
return list
}

export function renderPap(pap: Pap): HTMLElement {
const box = h('section', { class: 'learn-pap' })
box.append(h('strong', { class: 'learn-pap-title' }, pap.title))
Expand All @@ -94,7 +116,12 @@ export function renderPap(pap: Pap): HTMLElement {
box.append(h('p', { class: 'learn-pap-hint' }, t('web_learn_pap_hint')))

const { boxes, height } = papLayout(pap)
const svg = s('svg', { viewBox: `0 0 ${PAP_WIDTH} ${height}`, class: 'learn-pap-plan', role: 'group', 'aria-label': pap.title })
// Nur so breit wie der Inhalt, damit der Plan in der schmalen Lernspalte möglichst groß wird.
const shown = boxes.filter(Boolean)
const minX = Math.min(...shown.map((b) => b.x - b.w / 2)) - 4
const hasRaute = pap.nodes.some((n) => n.shape === 'raute')
const maxX = Math.max(...shown.map((b) => b.x + b.w / 2), hasRaute ? Math.max(...shown.map((b) => b.x)) + 85 + 40 : 0) + 4
const svg = s('svg', { viewBox: `${minX} 0 ${maxX - minX} ${height}`, class: 'learn-pap-plan', role: 'group', 'aria-label': pap.title })
const arrowId = `pap-arrow-${++papCount}`
svg.innerHTML =
`<defs><marker id="${arrowId}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 Z" fill="#1c1c1e"/></marker></defs>`
Expand Down Expand Up @@ -275,6 +302,7 @@ export function renderPap(pap: Pap): HTMLElement {
solution.hidden = true
again.hidden = false
tray.hidden = true
work.classList.add('done')
if (explanation) explanation.hidden = false
}

Expand Down Expand Up @@ -315,11 +343,17 @@ export function renderPap(pap: Pap): HTMLElement {
check.hidden = false
again.hidden = true
tray.hidden = false
work.classList.remove('done')
if (explanation) explanation.hidden = true
})

refresh()
box.append(svg, tray, h('div', { class: 'learn-pap-actions' }, check, solution, again), status)
// Plan links, Bausteine rechts daneben: so sieht man am Laptop beides zugleich.
const plan = h('div', { class: 'learn-pap-left' })
plan.append(svg)
if (pap.legend) plan.append(legend())
const work = h('div', { class: 'learn-pap-work' }, plan, tray)
box.append(work, h('div', { class: 'learn-pap-actions' }, check, solution, again), status)
if (explanation) box.append(explanation)
return box
}
54 changes: 50 additions & 4 deletions web/src/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3423,15 +3423,61 @@ button.danger-text:not(:disabled) {
.learn-pap-slot.shown .learn-pap-shape {
stroke: #d97706;
}
.learn-pap-legend {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 10px;
align-items: center;
max-width: 420px;
margin: 0 auto 12px;
padding: 8px 10px;
border-radius: 8px;
background: var(--bg, #f2f2f7);
font-size: 0.9em;
}
.learn-pap-legend dt {
line-height: 0;
}
.learn-pap-legend dd {
margin: 0;
}
.learn-pap-work {
display: grid;
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
gap: 8px;
align-items: start;
margin-bottom: 10px;
}
.learn-pap-work.done {
grid-template-columns: minmax(0, 1fr);
}
.learn-pap-left .learn-pap-plan {
margin-bottom: 8px;
}
.learn-pap-tray {
position: sticky;
top: 0;
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: center;
margin-bottom: 10px;
flex-direction: column;
align-items: center;
gap: 4px;
max-height: 100vh;
overflow-y: auto;
padding: 6px 2px;
border-radius: 8px;
background: var(--bg, #f2f2f7);
}
.learn-pap-tray:empty,
.learn-pap-tray:not(:has(.learn-pap-card:not([hidden]))) {
display: none;
}
.learn-pap-card svg {
max-width: 100%;
height: auto;
}
.learn-pap-card {
padding: 2px;
max-width: 100%;
border: 2px solid transparent;
border-radius: 10px;
background: none;
Expand Down
1 change: 1 addition & 0 deletions web/test/lessons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ describe('PAP bauen', () => {
useContentLanguage(lang)
const paps = allBlocks().filter(({ block }) => block.kind === 'pap')
expect(paps.length, lang).toBe(1)
expect(paps[0].block.kind === 'pap' && paps[0].block.pap.legend, lang).toBe(true)
}
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@ const e=`/* Texte, die es nur in der Web-Version gibt. Alle anderen kommen aus d
"web_learn_cloze_all_right" = "Alle Lücken richtig!";
"web_learn_cloze_solution" = "Lösung zeigen";
"web_learn_cloze_again" = "Nochmal";
"web_learn_pap_hint" = "Tippe ein Element unten an und dann seinen Platz im Plan. Du kannst es auch hinziehen. Ein belegter Platz gibt sein Element beim Antippen zurück.";
"web_learn_pap_hint" = "Tippe ein Element rechts an und dann seinen Platz im Plan. Du kannst es auch hinziehen. Ein belegter Platz gibt sein Element beim Antippen zurück.";
"web_learn_pap_score_fmt" = "%d von %d Elementen richtig";
"web_learn_pap_all_right" = "Alles richtig! Dein PAP ist fertig.";
"web_learn_pap_missing_fmt" = "Es fehlen noch %d Elemente.";
"web_learn_pap_empty" = "Leerer Platz";
"web_learn_pap_yes" = "ja";
"web_learn_pap_no" = "nein";
"web_learn_pap_legend_oval" = "Start oder Ende";
"web_learn_pap_legend_rect" = "Anweisung";
"web_learn_pap_legend_diamond" = "Verzweigung: Frage mit ja und nein";
"web_learn_pap_legend_para" = "Ein- oder Ausgabe";
"web_learn_review" = "Nachlesen";
"web_learn_terms" = "Fachbegriffe";
"web_learn_resize" = "Breite des Lernbereichs ändern (Doppelklick: zurücksetzen)";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@ const e=`/* Texts that exist only in the web version. Everything else comes from
"web_learn_cloze_all_right" = "All gaps correct!";
"web_learn_cloze_solution" = "Show solution";
"web_learn_cloze_again" = "Try again";
"web_learn_pap_hint" = "Tap an element below and then its place in the chart. You can also drag it there. Tapping a filled place gives its element back.";
"web_learn_pap_hint" = "Tap an element on the right and then its place in the chart. You can also drag it there. Tapping a filled place gives its element back.";
"web_learn_pap_score_fmt" = "%d of %d elements correct";
"web_learn_pap_all_right" = "All correct! Your flowchart is finished.";
"web_learn_pap_missing_fmt" = "%d elements are still missing.";
"web_learn_pap_empty" = "Empty place";
"web_learn_pap_yes" = "yes";
"web_learn_pap_no" = "no";
"web_learn_pap_legend_oval" = "Start or end";
"web_learn_pap_legend_rect" = "Statement";
"web_learn_pap_legend_diamond" = "Decision: question with yes and no";
"web_learn_pap_legend_para" = "Input or output";
"web_learn_review" = "Review";
"web_learn_terms" = "Technical terms";
"web_learn_resize" = "Change the width of the learning area (double-click: reset)";
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Loading