Show study slides as one column and fill in the empty lessons.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-08 10:23:29 +02:00
commit 3ffe01af1d
4 changed files with 175 additions and 91 deletions

View file

@ -1034,8 +1034,30 @@ fun SlidesScreen(
} }
} }
} }
slide.strs("lines").forEach { line -> slide.strs("lines").forEachIndexed { index, line ->
Text(line, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(vertical = 6.dp)) val numbered = Regex("""^(\d+)\.\s+(.*)$""").find(line)
val badge = numbered?.groupValues?.get(1) ?: "${index + 1}"
val text = numbered?.groupValues?.get(2) ?: line
Row(
Modifier
.fillMaxWidth()
.padding(vertical = 5.dp)
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f), androidx.compose.foundation.shape.RoundedCornerShape(16.dp))
.padding(horizontal = 14.dp, vertical = 12.dp),
verticalAlignment = Alignment.Top,
) {
Box(
Modifier
.padding(top = 2.dp)
.size(28.dp)
.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.14f), CircleShape),
contentAlignment = Alignment.Center,
) {
Text(badge, style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary)
}
Spacer(Modifier.width(12.dp))
Text(text, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
}
} }
Spacer(Modifier.height(24.dp)) Spacer(Modifier.height(24.dp))
} }

View file

@ -1519,84 +1519,129 @@ struct SlidesScreen: View {
} }
private func slidePage(_ slide: J) -> some View { private func slidePage(_ slide: J) -> some View {
#if os(macOS)
GeometryReader { geo in
ScrollView { ScrollView {
slideStack(slide) slideStack(slide)
.padding(.horizontal, MacChrome.pageInset) .padding(.horizontal, MacChrome.pageInset)
.padding(.vertical, 28) .padding(.top, 22)
.frame(maxWidth: .infinity, minHeight: geo.size.height, alignment: .center) .padding(.bottom, 28)
.frame(maxWidth: 860)
.frame(maxWidth: .infinity, alignment: .top)
} }
} }
#else
ScrollView {
slideStack(slide)
.padding()
.padding(.bottom, 24)
.frame(maxWidth: .infinity, alignment: .leading)
}
#endif
}
private func slideStack(_ slide: J) -> some View { private func slideStack(_ slide: J) -> some View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 18) {
Text(slide.str("kicker")) Text(slide.str("kicker"))
#if os(macOS) #if os(macOS)
.font(.subheadline.weight(.semibold)) .font(.subheadline.weight(.semibold))
#else #else
.font(.caption.weight(.semibold)) .font(.caption.weight(.semibold))
#endif #endif
.foregroundStyle(.secondary) .foregroundStyle(Color.accentColor)
Text(slide.str("title")) Text(slide.str("title"))
#if os(macOS) #if os(macOS)
.font(.title.bold()) .font(.title.bold())
#else #else
.font(.title2.bold()) .font(.title2.bold())
#endif #endif
.fixedSize(horizontal: false, vertical: true)
if let tip = slide.strOrNull("tip") { if let tip = slide.strOrNull("tip") {
GlassCard { HStack(alignment: .top, spacing: 12) {
Label(tip, systemImage: "lightbulb.fill") Image(systemName: "lightbulb.fill")
.foregroundStyle(Color.accentColor)
.padding(.top, 2)
Text(tip)
#if os(macOS)
.font(.body)
#else
.font(.subheadline)
#endif
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
} }
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.accentColor.opacity(0.12), in: RoundedRectangle(cornerRadius: 16, style: .continuous))
} }
slideLines(slide.strs("lines")) slideLines(slide.strs("lines"))
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
@ViewBuilder
private func slideLines(_ lines: [String]) -> some View { private func slideLines(_ lines: [String]) -> some View {
let points = studyPoints(lines)
return VStack(alignment: .leading, spacing: 10) {
ForEach(Array(points.enumerated()), id: \.offset) { _, point in
HStack(alignment: .top, spacing: 14) {
Text(point.0)
.font(.callout.weight(.bold))
.foregroundStyle(Color.accentColor)
.frame(minWidth: 28, minHeight: 28)
.background(Color.accentColor.opacity(0.14), in: Circle())
Text(point.1)
#if os(macOS) #if os(macOS)
if lines.count >= 3 {
LazyVGrid(
columns: [
GridItem(.flexible(), spacing: 28, alignment: .topLeading),
GridItem(.flexible(), spacing: 28, alignment: .topLeading),
],
alignment: .leading,
spacing: 12
) {
ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
Text(line)
.font(.title3) .font(.title3)
#else
.font(.body)
#endif
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 3)
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.frame(maxWidth: .infinity, alignment: .leading)
.background {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.primary.opacity(0.045))
}
.overlay {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.primary.opacity(0.08), lineWidth: 1)
} }
} }
}
}
/// Lines that end in the middle of a sentence stay one point. A leading
/// "1. " is the point number, not part of the sentence.
private func studyPoints(_ lines: [String]) -> [(String, String)] {
var chunks: [String] = []
var buffer = ""
func flush() {
let text = buffer.trimmingCharacters(in: .whitespacesAndNewlines)
if !text.isEmpty { chunks.append(text) }
buffer = ""
}
for line in lines {
let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { continue }
if buffer.isEmpty {
buffer = trimmed
} else if continuesStudyPoint(buffer) {
buffer += " " + trimmed
} else { } else {
VStack(alignment: .leading, spacing: 12) { flush()
ForEach(Array(lines.enumerated()), id: \.offset) { _, line in buffer = trimmed
Text(line)
.font(.title3)
.fixedSize(horizontal: false, vertical: true)
} }
} }
flush()
return chunks.enumerated().map { index, chunk in
let pattern = #"^(\d+)\.\s+(.*)$"#
if let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: chunk, range: NSRange(chunk.startIndex..., in: chunk)),
let number = Range(match.range(at: 1), in: chunk),
let rest = Range(match.range(at: 2), in: chunk),
!continuesStudyPoint(String(chunk[rest])) {
return (String(chunk[number]), String(chunk[rest]))
} }
#else return (String(index + 1), chunk)
ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
Text(line)
.padding(.vertical, 4)
} }
#endif }
private func continuesStudyPoint(_ text: String) -> Bool {
guard let last = text.last else { return false }
return last == "," || last == ":" || last == ";" || last == "–" || last == "-" || last == "("
} }
} }

View file

@ -877,64 +877,79 @@ GtkWidget *build_hw_unit2_page(void) {
static const NetSlide slides[HW2_SLIDES] = { static const NetSlide slides[HW2_SLIDES] = {
{ {
"1 / 6 • Přehled", "Historie počítačů", "1 / 6 • Přehled", "Historie počítačů",
"Generace se dělí podle klíčové technologie své doby.", "Generace se dělí podle součástky, která v té době počítala: relé, elektronka, tranzistor, integrovaný obvod a nakonec mikroprocesor.",
{ {
"1. Předchůdci – do 30. let 19. století", "Předchůdci do 19. století jsou mechanické pomůcky. Pascaline sčítala ozubenými koly, Babbage navrhl analytický stroj a Ada Lovelace k němu popsala postup výpočtu. Elektronika ještě chyběla.",
"2. Nultá generace – 1938–1944", "Nultá generace, zhruba 1938–1944, spíná elektromagnetická relé. Stroje jsou kusové a často vojenské.",
"3. První generace – 1944–1955", "První generace, 1944–1955, bere elektronku. Počítač zabere sál a program se dává děrnými štítky.",
"4. Druhá generace – 1955–1964", "Druhá generace, 1955–1964, nahrazuje elektronku tranzistorem. Objevují se vyšší programovací jazyky.",
"5. Třetí generace – 1964–1971", "Třetí generace, 1964–1971, staví na integrovaném obvodu. Vznikají rodiny počítačů a operační systémy.",
"Další snímky shrnují znaky jednotlivých generací.", "Čtvrtá generace od roku 1971 staví na mikroprocesoru. Počítač se vejde na stůl a později do kapsy.",
NULL, NULL,
}, },
}, },
{ {
"2 / 6 • 0. generace", "Počítače 0. generace (1938–1944)", "2 / 6 • 0. generace", "Počítače 0. generace (1938–1944)",
"Tip: počítačů bylo málo a často sloužily armádě.", "Tip: počítačů byly jednotky kusů a často sloužily armádě. Základem je relé, ne elektronka.",
{ {
"Existovalo jen několik počítačů na světě.", "Relé je elektromagnetický spínač. Je pomalejší a hlučnější než pozdější elektronka, ale umí už skládat logické obvody.",
"Většinou měly vojenský účel.", "Konrad Zuse dokončil v roce 1941 Z3: programovatelný binární stroj s plovoucí řádovou čárkou.",
"Harvard Mark I (1944) byl elektromechanický a počítal balistické tabulky pro americkou armádu.",
"Každý stroj se stavěl na zakázku. Obsluhoval ho tým, ne jeden člověk u stolu.",
"Program býval na děrné pásce nebo na přepínačích, ne v dnešní operační paměti.",
"Typická práce byla vojenská: šifry a tabulky střelby, ne kancelář nebo domácnost.",
NULL, NULL,
}, },
}, },
{ {
"3 / 6 • 1. generace", "Počítače 1. generace (1944–1955)", "3 / 6 • 1. generace", "Počítače 1. generace (1944–1955)",
"Klíčový objev: elektronka", "Klíčový objev je elektronka. Spíná rychleji než relé, ale hřeje, často se pálí a počítač kvůli ní zabere celý sál.",
{ {
"Objev elektronky umožnil tuto generaci.", "ENIAC (1945) měl kolem 18 tisíc elektronek a počítal hlavně balistiku.",
"Ještě neexistoval software v dnešním smyslu.", "Von Neumannova architektura ukládá program i data do stejné paměti. Na tom stojí i dnešní počítače.",
"Data a programy se zadávaly pomocí děrných štítků.", "UNIVAC I (1951) byl první sériově prodávaný komerční počítač v USA.",
"Program a data se děrovaly do štítků. Operátor vložil balík a čekal na výsledek.",
"Operační systém ani vyšší jazyk ještě nebyl. Psalo se ve strojovém kódu.",
"Sál potřeboval klimatizaci a stálý dohled. Do domácnosti se takový stroj nevešel.",
NULL, NULL,
}, },
}, },
{ {
"4 / 6 • 2. generace", "Počítače 2. generace (1955–1964)", "4 / 6 • 2. generace", "Počítače 2. generace (1955–1964)",
"Klíčový objev: tranzistor", "Klíčový objev je tranzistor. V Bellových laboratořích vznikl roku 1947 a do počítačů se dostal zhruba od roku 1955.",
{ {
"Objev tranzistoru nahradil elektronky.", "Tranzistor nahradí elektronku: je menší, spolehlivější a méně se hřeje.",
"Používala se hlavní externí paměť.", "Stroj už nemusí zabírat celý sál, pořád je to ale zařízení pro firmu nebo ústav.",
"Počítače měly menší rozměry než v 1. generaci.", "Vnitřní paměť bývá feritová. Vnější paměť jsou magnetické pásky a bubny.",
"FORTRAN (1957) a COBOL (1959) dovolí psát program blíž lidské řeči než strojový kód.",
"Úlohy se zpracovávají dávkově. Operátor seřadí balíky a počítač je jede postupně.",
"Typické stroje této doby jsou IBM 1401 a IBM 7090.",
NULL, NULL,
}, },
}, },
{ {
"5 / 6 • 3. generace", "Počítače 3. generace (1964–1971)", "5 / 6 • 3. generace", "Počítače 3. generace (1964–1971)",
"Klíčový objev: integrovaný obvod", "Klíčový objev je integrovaný obvod: mnoho tranzistorů na jednom křemíkovém čipu. Kilby a Noyce ho představili na konci 50. let.",
{ {
"Objev integrovaného obvodu.", "IBM System/360 (1964) je rodina počítačů se stejným instrukčním souborem. Program z menšího modelu běží i na větším.",
"Stále se používala hlavní externí paměť.", "Operační systém už řídí více úloh a periferie. Objevuje se sdílení času mezi uživateli.",
"Rozměry počítačů se dál zmenšovaly.", "Magnetický disk doplňuje pásku a zkracuje čekání na data.",
"Minipočítače jako PDP-8 se vejdou do jedné skříně. Kupují je laboratoře a firmy, ne jen stát.",
"Stroj je menší a levnější než sál první generace, pořád to ale není osobní počítač na stůl.",
"Hlavní vnější pamětí zůstává magnetické médium, ne děrný štítek.",
NULL, NULL,
}, },
}, },
{ {
"6 / 6 • 4. generace", "Počítače 4. generace", "6 / 6 • 4. generace", "Počítače 4. generace",
"Klíčové: programovatelné mikroprocesory", "Klíčová součástka je programovatelný mikroprocesor: celý procesor na jednom čipu.",
{ {
"Nastupují programovatelné mikroprocesory.", "Intel 4004 z roku 1971 je první komerční mikroprocesor. Pozdější čipy z něj udělaly mozek osobního počítače.",
"1972 – první počítačová hra", "Na stůl se počítač dostává postupně: Altair 1975, Apple II 1977 a IBM PC 1981.",
"1973 – první disketa", "Roku 1972 vychází Pong, první komerčně úspěšná arkádová hra. Lekce ji bere jako první počítačovou hru.",
"1979 – první CD", "Roku 1971 představuje IBM osmipalcovou disketu. Menší formáty přišly až v dalších letech.",
"Roku 1979 Philips a Sony předvádějí kompaktní disk. V obchodech je od roku 1982.",
"Paměti a disky rostou, takže počítač opouští sál a později se vejde i do kapsy.",
NULL, NULL,
}, },
}, },

View file

@ -1029,43 +1029,45 @@ GtkWidget *build_net_unit5_page(void) {
static const NetSlide slides[NET5_SLIDES] = { static const NetSlide slides[NET5_SLIDES] = {
{ {
"1 / 4 • Signál", "Analogový a digitální signál", "1 / 4 • Signál", "Analogový a digitální signál",
"Tip: f = 1/T", "Tip: frekvence a perioda jsou převrácené hodnoty. Platí f = 1/T.",
{ {
"Analogový: spojitý, nabývá libovolných hodnot.", "Analogový signál je spojitý. Mezi dvěma hodnotami nabývá i všech hodnot mezi nimi, třeba napětí na mikrofonu nebo teplota.",
"Digitální: diskrétní, hodnoty 0 a 1.", "Digitální signál je diskrétní. V počítači nabývá jen hodnot 0 a 1, takže se dá uložit a zkopírovat bez postupného šumu.",
"Parametry signálu:", "Perioda T je doba jednoho opakování. Frekvence f = 1/T říká, kolikrát za sekundu se průběh zopakuje. Jednotka je hertz.",
"perioda T, frekvence f = 1/T,", "Amplituda A je velikost výchylky od středu. Fázový posun Φ říká, o kolik je průběh posunutý proti jinému signálu se stejnou frekvencí.",
"amplituda A, fázový posun Φ.",
NULL, NULL,
}, },
}, },
{ {
"2 / 4 • Modulace", "Základní typy modulace", "2 / 4 • Modulace", "Základní typy modulace",
"Modulace = přenos v přeloženém pásmu", "Modulace nasadí zprávu na nosnou vlnu, aby prošla kabelem nebo éterem. Tomu se říká přenos v přeloženém pásmu.",
{ {
"AM – amplitudová modulace.", "AM, amplitudová modulace, mění velikost nosné podle zprávy. Používá ji třeba rozhlas na středních vlnách. Rušení amplitudu snadno pokazí.",
"FM – frekvenční modulace.", "FM, frekvenční modulace, mění kmitočet nosné a amplitudu nechává. VKV rádio je odolnější proti šumu než AM.",
"PM – fázová modulace.", "PM, fázová modulace, posouvá fázi nosné. Změna fáze a změna frekvence spolu souvisejí, proto se FM a PM často popisují podobně.",
"Bez modulace by pomalá zpráva, třeba hlas, na rádiové frekvenci sama neodešla. Nosná je rychlá vlna, zpráva ji jen tvaruje.",
NULL, NULL,
}, },
}, },
{ {
"3 / 4 • Kombinované", "QPSK a 256-QAM", "3 / 4 • Kombinované", "QPSK a 256-QAM",
NULL, "Jeden stav signálu, symbol, může nést víc než jeden bit. Čím víc stavů, tím víc bitů, ale tím snáz se stavy zamění.",
{ {
"QPSK: 2 bity v jednom prvku (symbolu).", "QPSK má čtyři fázové stavy. Čtyři možnosti jsou dva bity, takže jeden symbol nese 2 bity.",
"256-QAM: 8 bitů v jednom prvku.", "256-QAM skládá amplitudu i fázi do 256 stavů. 256 = 2^8, takže jeden symbol nese 8 bitů.",
"Wi-Fi a kabelové modemy QAM používají, protože za stejnou šířku pásma přenesou víc dat než prosté AM.",
"Hustší konstelace chce čistší signál. Když je rušení velké, přijímač spadne na řidší modulaci s menším počtem bitů na symbol.",
NULL, NULL,
}, },
}, },
{ {
"4 / 4 • Rychlosti", "Modulační, přenosová a šířka pásma", "4 / 4 • Rychlosti", "Modulační, přenosová a šířka pásma",
NULL, "Baud a bit za sekundu nejsou totéž. Baud počítá změny signálu, bit za sekundu počítá přenesenou informaci.",
{ {
"Modulační rychlost: počet změn signálu za sekundu [Baud, Bd/s].", "Modulační rychlost je počet změn signálu za sekundu. Jednotka je baud, Bd/s. U QPSK jedna změna nese 2 bity, takže bitů je víc než baudů.",
"Přenosová rychlost: velikost přenesené informace [bit/s, bps].", "Přenosová rychlost je množství informace za sekundu, v bit/s. Když symbol nese víc bitů, přenosová rychlost je vyšší než modulační.",
"Šířka pásma: rozsah frekvencí –", "Šířka pásma je rozsah frekvencí, který kanál propustí. U vozovky je to počet pruhů: užší pásmo znamená nižší strop rychlosti.",
"určuje maximální možnou rychlost.", "Nyquistův a Shannonův vztah z šířky pásma a šumu odvozují, kolik bitů za sekundu kanál vůbec unese. Větší pásmo nebo čistší signál strop zvedne.",
NULL, NULL,
}, },
}, },