mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
Fill in years 2–4 of networks and hardware.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b822c57875
commit
10980f24b5
27 changed files with 6345 additions and 64 deletions
18
README.md
18
README.md
|
|
@ -63,9 +63,12 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
|
||||||
|
|
||||||
- **Deutsch** — učebnice (Neue Freunde, Aus aller Welt, Bei uns zu Hause)
|
- **Deutsch** — učebnice (Neue Freunde, Aus aller Welt, Bei uns zu Hause)
|
||||||
a 4 ročníky, 24 lekcí od A1 k B1: poslech, čtení, psaní, doplňování a kvízy
|
a 4 ročníky, 24 lekcí od A1 k B1: poslech, čtení, psaní, doplňování a kvízy
|
||||||
- **Správa počítačových sítí** — 1. ročník, 27 lekcí (VLSM + teorie a kvízy);
|
- **Správa počítačových sítí** — 4 ročníky: 1. ročník má 27 lekcí
|
||||||
ročníky 2–4 jsou zamčené
|
(adresy, modely, přepínače a bezdrát), ročníky 2–4 po 8 lekcích
|
||||||
- **Technické vybavení** — 1. ročník, 29 lekcí (architektura, data, skříň a deska, karty, disky, vstupní zařízení); ročníky 2–4 jsou zamčené
|
(VLAN, směrování, firewall, návrh a maturita)
|
||||||
|
- **Technické vybavení** — 4 ročníky: 1. ročník má 29 lekcí
|
||||||
|
(architektura, data, skříň, deska, karty, disky a vstup),
|
||||||
|
ročníky 2–4 po 8 lekcích (monitory, sestavení, RAID, servery, údržba)
|
||||||
- **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov
|
- **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov
|
||||||
středních škol), mluvnice (20 cvičení) a maturitní četba
|
středních škol), mluvnice (20 cvičení) a maturitní četba
|
||||||
(82 knih: zápisky, kvíz, sestavení děje)
|
(82 knih: zápisky, kvíz, sestavení děje)
|
||||||
|
|
@ -268,9 +271,12 @@ Intel Mac, another architecture, or hacking on the code →
|
||||||
|
|
||||||
- **Deutsch** — the textbook (Neue Freunde, Aus aller Welt, Bei uns zu Hause)
|
- **Deutsch** — the textbook (Neue Freunde, Aus aller Welt, Bei uns zu Hause)
|
||||||
plus 4 years, 24 lessons from A1 to B1: listening, reading, writing, gap-fill and quizzes
|
plus 4 years, 24 lessons from A1 to B1: listening, reading, writing, gap-fill and quizzes
|
||||||
- **Computer networks** — year 1, 27 lessons (VLSM + theory/quizzes);
|
- **Computer networks** — 4 years: year 1 has 27 lessons
|
||||||
years 2–4 are locked
|
(addresses, models, switches and wireless), years 2–4 have 8 lessons each
|
||||||
- **Computer hardware** — year 1, 29 lessons (architecture, data, case and board, cards, drives, input devices); years 2–4 are locked
|
(VLANs, routing, firewalls, design and the exam)
|
||||||
|
- **Computer hardware** — 4 years: year 1 has 29 lessons
|
||||||
|
(architecture, data, case, board, cards, drives and input),
|
||||||
|
years 2–4 have 8 lessons each (displays, assembly, RAID, servers, maintenance)
|
||||||
- **Czech language** — literature (4 years, 32 lessons from the secondary-school
|
- **Czech language** — literature (4 years, 32 lessons from the secondary-school
|
||||||
syllabus), grammar (20 exercises) and required reading
|
syllabus), grammar (20 exercises) and required reading
|
||||||
(82 books: notes, quiz, plot ordering)
|
(82 books: notes, quiz, plot ordering)
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,11 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun markCourse(course: String, id: Int) {
|
||||||
|
progress.markCourse(course, id)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun markLit(id: Int) {
|
fun markLit(id: Int) {
|
||||||
progress.markLit(id)
|
progress.markLit(id)
|
||||||
refresh()
|
refresh()
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,9 @@ import org.maturita.maturita.ui.On4QuizScreen
|
||||||
import org.maturita.maturita.ui.OnQuizScreen
|
import org.maturita.maturita.ui.OnQuizScreen
|
||||||
import org.maturita.maturita.ui.OnYearsScreen
|
import org.maturita.maturita.ui.OnYearsScreen
|
||||||
import org.maturita.maturita.ui.EnYearsScreen
|
import org.maturita.maturita.ui.EnYearsScreen
|
||||||
|
import org.maturita.maturita.ui.ChoiceLessonScreen
|
||||||
import org.maturita.maturita.ui.DeHomeScreen
|
import org.maturita.maturita.ui.DeHomeScreen
|
||||||
|
import org.maturita.maturita.ui.ItYearScreen
|
||||||
import org.maturita.maturita.ui.EngExScreen
|
import org.maturita.maturita.ui.EngExScreen
|
||||||
import org.maturita.maturita.ui.PlotScreen
|
import org.maturita.maturita.ui.PlotScreen
|
||||||
import org.maturita.maturita.ui.RoadmapScreen
|
import org.maturita.maturita.ui.RoadmapScreen
|
||||||
|
|
@ -244,6 +246,30 @@ private fun MaturitaAppBody(vm: AppViewModel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Route.NetEx -> NetQuizScreen(vm, r.id)
|
is Route.NetEx -> NetQuizScreen(vm, r.id)
|
||||||
|
Route.Net2Map -> ItYearScreen(vm, "net2", "net_year2", "net2_sub") { Route.Net2Lesson(it) }
|
||||||
|
is Route.Net2Lesson -> {
|
||||||
|
val l = vm.content.itLesson("net2", r.id)
|
||||||
|
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||||
|
vm.replace(Route.Net2Ex(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Route.Net2Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net2", r.id)) { vm.markCourse("net2", r.id) }
|
||||||
|
Route.Net3Map -> ItYearScreen(vm, "net3", "net_year3", "net3_sub") { Route.Net3Lesson(it) }
|
||||||
|
is Route.Net3Lesson -> {
|
||||||
|
val l = vm.content.itLesson("net3", r.id)
|
||||||
|
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||||
|
vm.replace(Route.Net3Ex(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Route.Net3Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net3", r.id)) { vm.markCourse("net3", r.id) }
|
||||||
|
Route.Net4Map -> ItYearScreen(vm, "net4", "net_year4", "net4_sub") { Route.Net4Lesson(it) }
|
||||||
|
is Route.Net4Lesson -> {
|
||||||
|
val l = vm.content.itLesson("net4", r.id)
|
||||||
|
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||||
|
vm.replace(Route.Net4Ex(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Route.Net4Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net4", r.id)) { vm.markCourse("net4", r.id) }
|
||||||
Route.HwYears -> HwYearsScreen(vm)
|
Route.HwYears -> HwYearsScreen(vm)
|
||||||
Route.HwMap -> LessonListScreen(
|
Route.HwMap -> LessonListScreen(
|
||||||
vm, vm.tr("hw_year1"), vm.tr("hw_sub"),
|
vm, vm.tr("hw_year1"), vm.tr("hw_sub"),
|
||||||
|
|
@ -259,6 +285,30 @@ private fun MaturitaAppBody(vm: AppViewModel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Route.HwEx -> HwQuizScreen(vm, r.id)
|
is Route.HwEx -> HwQuizScreen(vm, r.id)
|
||||||
|
Route.Hw2Map -> ItYearScreen(vm, "hw2", "hw_year2", "hw2_sub") { Route.Hw2Lesson(it) }
|
||||||
|
is Route.Hw2Lesson -> {
|
||||||
|
val l = vm.content.itLesson("hw2", r.id)
|
||||||
|
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||||
|
vm.replace(Route.Hw2Ex(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Route.Hw2Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw2", r.id)) { vm.markCourse("hw2", r.id) }
|
||||||
|
Route.Hw3Map -> ItYearScreen(vm, "hw3", "hw_year3", "hw3_sub") { Route.Hw3Lesson(it) }
|
||||||
|
is Route.Hw3Lesson -> {
|
||||||
|
val l = vm.content.itLesson("hw3", r.id)
|
||||||
|
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||||
|
vm.replace(Route.Hw3Ex(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Route.Hw3Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw3", r.id)) { vm.markCourse("hw3", r.id) }
|
||||||
|
Route.Hw4Map -> ItYearScreen(vm, "hw4", "hw_year4", "hw4_sub") { Route.Hw4Lesson(it) }
|
||||||
|
is Route.Hw4Lesson -> {
|
||||||
|
val l = vm.content.itLesson("hw4", r.id)
|
||||||
|
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
|
||||||
|
vm.replace(Route.Hw4Ex(r.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Route.Hw4Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw4", r.id)) { vm.markCourse("hw4", r.id) }
|
||||||
Route.EnYears -> EnYearsScreen(vm)
|
Route.EnYears -> EnYearsScreen(vm)
|
||||||
is Route.EnMap -> LessonListScreen(
|
is Route.EnMap -> LessonListScreen(
|
||||||
vm, vm.tr("en_year${r.year}"), vm.tr("en_y${r.year}_sub"),
|
vm, vm.tr("en_year${r.year}"), vm.tr("en_y${r.year}_sub"),
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,28 @@ sealed class Route(val page: String) {
|
||||||
data object NetMap : Route("netmap")
|
data object NetMap : Route("netmap")
|
||||||
data class NetLesson(val id: Int) : Route("netunit$id")
|
data class NetLesson(val id: Int) : Route("netunit$id")
|
||||||
data class NetEx(val id: Int) : Route("netex$id")
|
data class NetEx(val id: Int) : Route("netex$id")
|
||||||
|
data object Net2Map : Route("net2map")
|
||||||
|
data class Net2Lesson(val id: Int) : Route("net2unit$id")
|
||||||
|
data class Net2Ex(val id: Int) : Route("net2ex$id")
|
||||||
|
data object Net3Map : Route("net3map")
|
||||||
|
data class Net3Lesson(val id: Int) : Route("net3unit$id")
|
||||||
|
data class Net3Ex(val id: Int) : Route("net3ex$id")
|
||||||
|
data object Net4Map : Route("net4map")
|
||||||
|
data class Net4Lesson(val id: Int) : Route("net4unit$id")
|
||||||
|
data class Net4Ex(val id: Int) : Route("net4ex$id")
|
||||||
data object HwYears : Route("hwyears")
|
data object HwYears : Route("hwyears")
|
||||||
data object HwMap : Route("hwmap")
|
data object HwMap : Route("hwmap")
|
||||||
data class HwLesson(val id: Int) : Route("hwunit$id")
|
data class HwLesson(val id: Int) : Route("hwunit$id")
|
||||||
data class HwEx(val id: Int) : Route("hwex$id")
|
data class HwEx(val id: Int) : Route("hwex$id")
|
||||||
|
data object Hw2Map : Route("hw2map")
|
||||||
|
data class Hw2Lesson(val id: Int) : Route("hw2unit$id")
|
||||||
|
data class Hw2Ex(val id: Int) : Route("hw2ex$id")
|
||||||
|
data object Hw3Map : Route("hw3map")
|
||||||
|
data class Hw3Lesson(val id: Int) : Route("hw3unit$id")
|
||||||
|
data class Hw3Ex(val id: Int) : Route("hw3ex$id")
|
||||||
|
data object Hw4Map : Route("hw4map")
|
||||||
|
data class Hw4Lesson(val id: Int) : Route("hw4unit$id")
|
||||||
|
data class Hw4Ex(val id: Int) : Route("hw4ex$id")
|
||||||
data object OnYears : Route("onyears")
|
data object OnYears : Route("onyears")
|
||||||
data object OnMap : Route("onmap")
|
data object OnMap : Route("onmap")
|
||||||
data class OnLesson(val id: Int) : Route("onunit$id")
|
data class OnLesson(val id: Int) : Route("onunit$id")
|
||||||
|
|
@ -102,6 +120,8 @@ sealed class Route(val page: String) {
|
||||||
val isExercise: Boolean
|
val isExercise: Boolean
|
||||||
get() = this is GermanEx || this is Vocab || this is NetEx || this is HwEx ||
|
get() = this is GermanEx || this is Vocab || this is NetEx || this is HwEx ||
|
||||||
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is EnLesson || this is EnEx || this is DeLesson || this is DeEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
|
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is EnLesson || this is EnEx || this is DeLesson || this is DeEx || this is MluvEx || this is BookQuiz || this is BookPlot ||
|
||||||
|
this is Net2Lesson || this is Net2Ex || this is Net3Lesson || this is Net3Ex || this is Net4Lesson || this is Net4Ex ||
|
||||||
|
this is Hw2Lesson || this is Hw2Ex || this is Hw3Lesson || this is Hw3Ex || this is Hw4Lesson || this is Hw4Ex ||
|
||||||
this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson ||
|
this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson ||
|
||||||
this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex ||
|
this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex ||
|
||||||
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
|
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
|
||||||
|
|
@ -129,10 +149,28 @@ sealed class Route(val page: String) {
|
||||||
NetMap -> listOf(Subjects, NetYears, NetMap)
|
NetMap -> listOf(Subjects, NetYears, NetMap)
|
||||||
is NetLesson -> listOf(Subjects, NetYears, NetMap, this)
|
is NetLesson -> listOf(Subjects, NetYears, NetMap, this)
|
||||||
is NetEx -> listOf(Subjects, NetYears, NetMap, NetLesson(id), this)
|
is NetEx -> listOf(Subjects, NetYears, NetMap, NetLesson(id), this)
|
||||||
|
Net2Map -> listOf(Subjects, NetYears, Net2Map)
|
||||||
|
is Net2Lesson -> listOf(Subjects, NetYears, Net2Map, this)
|
||||||
|
is Net2Ex -> listOf(Subjects, NetYears, Net2Map, Net2Lesson(id), this)
|
||||||
|
Net3Map -> listOf(Subjects, NetYears, Net3Map)
|
||||||
|
is Net3Lesson -> listOf(Subjects, NetYears, Net3Map, this)
|
||||||
|
is Net3Ex -> listOf(Subjects, NetYears, Net3Map, Net3Lesson(id), this)
|
||||||
|
Net4Map -> listOf(Subjects, NetYears, Net4Map)
|
||||||
|
is Net4Lesson -> listOf(Subjects, NetYears, Net4Map, this)
|
||||||
|
is Net4Ex -> listOf(Subjects, NetYears, Net4Map, Net4Lesson(id), this)
|
||||||
HwYears -> listOf(Subjects, HwYears)
|
HwYears -> listOf(Subjects, HwYears)
|
||||||
HwMap -> listOf(Subjects, HwYears, HwMap)
|
HwMap -> listOf(Subjects, HwYears, HwMap)
|
||||||
is HwLesson -> listOf(Subjects, HwYears, HwMap, this)
|
is HwLesson -> listOf(Subjects, HwYears, HwMap, this)
|
||||||
is HwEx -> listOf(Subjects, HwYears, HwMap, HwLesson(id), this)
|
is HwEx -> listOf(Subjects, HwYears, HwMap, HwLesson(id), this)
|
||||||
|
Hw2Map -> listOf(Subjects, HwYears, Hw2Map)
|
||||||
|
is Hw2Lesson -> listOf(Subjects, HwYears, Hw2Map, this)
|
||||||
|
is Hw2Ex -> listOf(Subjects, HwYears, Hw2Map, Hw2Lesson(id), this)
|
||||||
|
Hw3Map -> listOf(Subjects, HwYears, Hw3Map)
|
||||||
|
is Hw3Lesson -> listOf(Subjects, HwYears, Hw3Map, this)
|
||||||
|
is Hw3Ex -> listOf(Subjects, HwYears, Hw3Map, Hw3Lesson(id), this)
|
||||||
|
Hw4Map -> listOf(Subjects, HwYears, Hw4Map)
|
||||||
|
is Hw4Lesson -> listOf(Subjects, HwYears, Hw4Map, this)
|
||||||
|
is Hw4Ex -> listOf(Subjects, HwYears, Hw4Map, Hw4Lesson(id), this)
|
||||||
OnYears -> listOf(Subjects, OnYears)
|
OnYears -> listOf(Subjects, OnYears)
|
||||||
OnMap -> listOf(Subjects, OnYears, OnMap)
|
OnMap -> listOf(Subjects, OnYears, OnMap)
|
||||||
is OnLesson -> listOf(Subjects, OnYears, OnMap, this)
|
is OnLesson -> listOf(Subjects, OnYears, OnMap, this)
|
||||||
|
|
@ -218,6 +256,12 @@ fun routeFromPage(page: String): Route? = when (page) {
|
||||||
"dehome" -> Route.DeHome
|
"dehome" -> Route.DeHome
|
||||||
"netyears" -> Route.NetYears
|
"netyears" -> Route.NetYears
|
||||||
"netmap" -> Route.NetMap
|
"netmap" -> Route.NetMap
|
||||||
|
"net2map" -> Route.Net2Map
|
||||||
|
"net3map" -> Route.Net3Map
|
||||||
|
"net4map" -> Route.Net4Map
|
||||||
|
"hw2map" -> Route.Hw2Map
|
||||||
|
"hw3map" -> Route.Hw3Map
|
||||||
|
"hw4map" -> Route.Hw4Map
|
||||||
"hwyears" -> Route.HwYears
|
"hwyears" -> Route.HwYears
|
||||||
"hwmap" -> Route.HwMap
|
"hwmap" -> Route.HwMap
|
||||||
"onyears" -> Route.OnYears
|
"onyears" -> Route.OnYears
|
||||||
|
|
@ -375,6 +419,42 @@ fun routeFromPage(page: String): Route? = when (page) {
|
||||||
Regex("""de(\d)ex(\d+)""").matchEntire(page)?.let {
|
Regex("""de(\d)ex(\d+)""").matchEntire(page)?.let {
|
||||||
return Route.DeEx(it.groupValues[1].toInt(), it.groupValues[2].toInt())
|
return Route.DeEx(it.groupValues[1].toInt(), it.groupValues[2].toInt())
|
||||||
}
|
}
|
||||||
|
Regex("""net([234])unit(\d+)""").matchEntire(page)?.let {
|
||||||
|
val year = it.groupValues[1].toInt()
|
||||||
|
val id = it.groupValues[2].toInt()
|
||||||
|
return when (year) {
|
||||||
|
3 -> Route.Net3Lesson(id)
|
||||||
|
4 -> Route.Net4Lesson(id)
|
||||||
|
else -> Route.Net2Lesson(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex("""net([234])ex(\d+)""").matchEntire(page)?.let {
|
||||||
|
val year = it.groupValues[1].toInt()
|
||||||
|
val id = it.groupValues[2].toInt()
|
||||||
|
return when (year) {
|
||||||
|
3 -> Route.Net3Ex(id)
|
||||||
|
4 -> Route.Net4Ex(id)
|
||||||
|
else -> Route.Net2Ex(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex("""hw([234])unit(\d+)""").matchEntire(page)?.let {
|
||||||
|
val year = it.groupValues[1].toInt()
|
||||||
|
val id = it.groupValues[2].toInt()
|
||||||
|
return when (year) {
|
||||||
|
3 -> Route.Hw3Lesson(id)
|
||||||
|
4 -> Route.Hw4Lesson(id)
|
||||||
|
else -> Route.Hw2Lesson(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex("""hw([234])ex(\d+)""").matchEntire(page)?.let {
|
||||||
|
val year = it.groupValues[1].toInt()
|
||||||
|
val id = it.groupValues[2].toInt()
|
||||||
|
return when (year) {
|
||||||
|
3 -> Route.Hw3Ex(id)
|
||||||
|
4 -> Route.Hw4Ex(id)
|
||||||
|
else -> Route.Hw2Ex(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
Regex("""en(\d)map""").matchEntire(page)?.let {
|
Regex("""en(\d)map""").matchEntire(page)?.let {
|
||||||
return Route.EnMap(it.groupValues[1].toInt())
|
return Route.EnMap(it.groupValues[1].toInt())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@ class Content(root: JSONObject) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val hw = raw.arr("hw")
|
val hw = raw.arr("hw")
|
||||||
|
val net2 = raw.arr("net2")
|
||||||
|
val net3 = raw.arr("net3")
|
||||||
|
val net4 = raw.arr("net4")
|
||||||
|
val hw2 = raw.arr("hw2")
|
||||||
|
val hw3 = raw.arr("hw3")
|
||||||
|
val hw4 = raw.arr("hw4")
|
||||||
val on = raw.arr("on")
|
val on = raw.arr("on")
|
||||||
val on2 = raw.arr("on2")
|
val on2 = raw.arr("on2")
|
||||||
val on3 = raw.arr("on3")
|
val on3 = raw.arr("on3")
|
||||||
|
|
@ -63,6 +69,16 @@ class Content(root: JSONObject) {
|
||||||
fun germanUnit(id: Int) = german.firstOrNull { it.int("id") == id }
|
fun germanUnit(id: Int) = german.firstOrNull { it.int("id") == id }
|
||||||
fun netLesson(id: Int) = netLessons.firstOrNull { it.int("id") == id }
|
fun netLesson(id: Int) = netLessons.firstOrNull { it.int("id") == id }
|
||||||
fun hwLesson(id: Int) = hw.firstOrNull { it.int("id") == id }
|
fun hwLesson(id: Int) = hw.firstOrNull { it.int("id") == id }
|
||||||
|
fun itLessons(course: String) = when (course) {
|
||||||
|
"net2" -> net2
|
||||||
|
"net3" -> net3
|
||||||
|
"net4" -> net4
|
||||||
|
"hw2" -> hw2
|
||||||
|
"hw3" -> hw3
|
||||||
|
"hw4" -> hw4
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
fun itLesson(course: String, id: Int) = itLessons(course).firstOrNull { it.int("id") == id }
|
||||||
fun onLesson(id: Int) = on.firstOrNull { it.int("id") == id }
|
fun onLesson(id: Int) = on.firstOrNull { it.int("id") == id }
|
||||||
fun on2Lesson(id: Int) = on2.firstOrNull { it.int("id") == id }
|
fun on2Lesson(id: Int) = on2.firstOrNull { it.int("id") == id }
|
||||||
fun on3Lesson(id: Int) = on3.firstOrNull { it.int("id") == id }
|
fun on3Lesson(id: Int) = on3.firstOrNull { it.int("id") == id }
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ class ProgressStore(context: Context) {
|
||||||
fun markEn(year: Int, id: Int) { prefs.edit().putBoolean("en.$year.$id", true).apply() }
|
fun markEn(year: Int, id: Int) { prefs.edit().putBoolean("en.$year.$id", true).apply() }
|
||||||
fun deDone(year: Int, id: Int) = prefs.getBoolean("de.$year.$id", false)
|
fun deDone(year: Int, id: Int) = prefs.getBoolean("de.$year.$id", false)
|
||||||
fun markDe(year: Int, id: Int) { prefs.edit().putBoolean("de.$year.$id", true).apply() }
|
fun markDe(year: Int, id: Int) { prefs.edit().putBoolean("de.$year.$id", true).apply() }
|
||||||
|
fun courseDone(course: String, id: Int) = prefs.getBoolean("$course.$id", false)
|
||||||
|
fun markCourse(course: String, id: Int) { prefs.edit().putBoolean("$course.$id", true).apply() }
|
||||||
|
|
||||||
fun litDone(id: Int) = prefs.getBoolean("lit.$id", false)
|
fun litDone(id: Int) = prefs.getBoolean("lit.$id", false)
|
||||||
fun markLit(id: Int) { prefs.edit().putBoolean("lit.$id", true).apply() }
|
fun markLit(id: Int) { prefs.edit().putBoolean("lit.$id", true).apply() }
|
||||||
|
|
@ -119,6 +121,16 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
|
||||||
sum.doneUnits += 1
|
sum.doneUnits += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
listOf("net2", "net3", "net4", "hw2", "hw3", "hw4").forEach { course ->
|
||||||
|
content.itLessons(course).forEach { l ->
|
||||||
|
sum.totalEx += 1
|
||||||
|
sum.openUnits += 1
|
||||||
|
if (p.courseDone(course, l.int("id"))) {
|
||||||
|
sum.doneEx += 1
|
||||||
|
sum.doneUnits += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
content.on.forEach { l ->
|
content.on.forEach { l ->
|
||||||
sum.totalEx += 1
|
sum.totalEx += 1
|
||||||
sum.openUnits += 1
|
sum.openUnits += 1
|
||||||
|
|
|
||||||
|
|
@ -1077,6 +1077,18 @@ private fun MathBoard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChoiceLessonScreen(vm: AppViewModel, lesson: J?, onDone: () -> Unit) {
|
||||||
|
if (lesson == null) return
|
||||||
|
val spec = J(org.json.JSONObject().apply {
|
||||||
|
put("type", "choice")
|
||||||
|
put("title", vm.tr(lesson.str("exTitleKey")))
|
||||||
|
put("sub", lesson.str("quizHeadKey"))
|
||||||
|
put("questions", lesson.o.optJSONArray("quiz"))
|
||||||
|
})
|
||||||
|
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey")), onDone)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FyzQuizScreen(vm: AppViewModel, year: Int, id: Int) {
|
fun FyzQuizScreen(vm: AppViewModel, year: Int, id: Int) {
|
||||||
val lesson = when (year) {
|
val lesson = when (year) {
|
||||||
|
|
|
||||||
|
|
@ -306,12 +306,16 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"netyears" -> {
|
"netyears" -> {
|
||||||
sum.totalEx = vm.content.netLessons.size
|
val extra = listOf("net2", "net3", "net4")
|
||||||
sum.doneEx = vm.content.netLessons.count { vm.progress.netDone(it.int("id")) }
|
sum.totalEx = vm.content.netLessons.size + extra.sumOf { vm.content.itLessons(it).size }
|
||||||
|
sum.doneEx = vm.content.netLessons.count { vm.progress.netDone(it.int("id")) } +
|
||||||
|
extra.sumOf { course -> vm.content.itLessons(course).count { vm.progress.courseDone(course, it.int("id")) } }
|
||||||
}
|
}
|
||||||
"hwyears", "hwmap" -> {
|
"hwyears", "hwmap" -> {
|
||||||
sum.totalEx = vm.content.hw.size
|
val extra = listOf("hw2", "hw3", "hw4")
|
||||||
sum.doneEx = vm.content.hw.count { vm.progress.hwDone(it.int("id")) }
|
sum.totalEx = vm.content.hw.size + extra.sumOf { vm.content.itLessons(it).size }
|
||||||
|
sum.doneEx = vm.content.hw.count { vm.progress.hwDone(it.int("id")) } +
|
||||||
|
extra.sumOf { course -> vm.content.itLessons(course).count { vm.progress.courseDone(course, it.int("id")) } }
|
||||||
}
|
}
|
||||||
"enyears", "en1map", "en2map", "en3map", "en4map" -> {
|
"enyears", "en1map", "en2map", "en3map", "en4map" -> {
|
||||||
sum.totalEx = (1..4).sumOf { vm.content.enYear(it).size }
|
sum.totalEx = (1..4).sumOf { vm.content.enYear(it).size }
|
||||||
|
|
@ -530,10 +534,19 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "netunit${l.int("id")}", extra = "site")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "netunit${l.int("id")}", extra = "site")
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "netex${l.int("id")}")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "netex${l.int("id")}")
|
||||||
}
|
}
|
||||||
|
listOf("net2", "net3", "net4").forEach { course ->
|
||||||
|
add(vm.tr("net_year${course.drop(3)}"), vm.tr("search_lesson"), "${course}map", extra = "rocnik site")
|
||||||
|
vm.content.itLessons(course).forEach { l ->
|
||||||
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "${course}unit${l.int("id")}", extra = "site")
|
||||||
|
}
|
||||||
|
}
|
||||||
add(vm.tr("hw_year1"), vm.tr("search_lesson"), "hwmap", extra = "rocnik")
|
add(vm.tr("hw_year1"), vm.tr("search_lesson"), "hwmap", extra = "rocnik")
|
||||||
add(vm.tr("hw_year2"), vm.tr("search_lesson"), "hwyears", locked = true, extra = "rocnik")
|
listOf("hw2", "hw3", "hw4").forEach { course ->
|
||||||
add(vm.tr("hw_year3"), vm.tr("search_lesson"), "hwyears", locked = true, extra = "rocnik")
|
add(vm.tr("hw_year${course.drop(2)}"), vm.tr("search_lesson"), "${course}map", extra = "rocnik hardware")
|
||||||
add(vm.tr("hw_year4"), vm.tr("search_lesson"), "hwyears", locked = true, extra = "rocnik")
|
vm.content.itLessons(course).forEach { l ->
|
||||||
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "${course}unit${l.int("id")}", extra = "hardware")
|
||||||
|
}
|
||||||
|
}
|
||||||
vm.content.hw.forEach { l ->
|
vm.content.hw.forEach { l ->
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "hwunit${l.int("id")}", extra = "hardware")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "hwunit${l.int("id")}", extra = "hardware")
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "hwex${l.int("id")}")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "hwex${l.int("id")}")
|
||||||
|
|
@ -718,15 +731,26 @@ fun UnitMapScreen(vm: AppViewModel, unitId: Int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItYearScreen(vm: AppViewModel, course: String, titleKey: String, subKey: String, lesson: (Int) -> Route) {
|
||||||
|
LessonListScreen(
|
||||||
|
vm, vm.tr(titleKey), vm.tr(subKey),
|
||||||
|
vm.content.itLessons(course).map { l ->
|
||||||
|
val id = l.int("id")
|
||||||
|
LessonRow(vm.tr(l.str("titleKey")), vm.progress.courseDone(course, id), lesson(id))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NetYearsScreen(vm: AppViewModel) {
|
fun NetYearsScreen(vm: AppViewModel) {
|
||||||
DetailScaffold(vm, vm.tr("net_years_title"), vm.tr("net_years_sub")) { inner ->
|
DetailScaffold(vm, vm.tr("net_years_title"), vm.tr("net_years_sub")) { inner ->
|
||||||
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
||||||
item {
|
item {
|
||||||
NavRow(title = vm.tr("net_year1"), subtitle = vm.tr("net_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.NetMap) }
|
NavRow(title = vm.tr("net_year1"), subtitle = vm.tr("net_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.NetMap) }
|
||||||
}
|
NavRow(title = vm.tr("net_year2"), subtitle = vm.tr("net2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Net2Map) }
|
||||||
items(listOf("net_year2", "net_year3", "net_year4")) { key ->
|
NavRow(title = vm.tr("net_year3"), subtitle = vm.tr("net3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Net3Map) }
|
||||||
NavRow(title = vm.tr(key), subtitle = vm.tr("net_year_locked_sub"), locked = true, onClick = null)
|
NavRow(title = vm.tr("net_year4"), subtitle = vm.tr("net4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Net4Map) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -738,9 +762,9 @@ fun HwYearsScreen(vm: AppViewModel) {
|
||||||
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
|
||||||
item {
|
item {
|
||||||
NavRow(title = vm.tr("hw_year1"), subtitle = vm.tr("hw_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.HwMap) }
|
NavRow(title = vm.tr("hw_year1"), subtitle = vm.tr("hw_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.HwMap) }
|
||||||
}
|
NavRow(title = vm.tr("hw_year2"), subtitle = vm.tr("hw2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Hw2Map) }
|
||||||
items(listOf("hw_year2", "hw_year3", "hw_year4")) { key ->
|
NavRow(title = vm.tr("hw_year3"), subtitle = vm.tr("hw3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Hw3Map) }
|
||||||
NavRow(title = vm.tr(key), subtitle = vm.tr("hw_year_locked_sub"), locked = true, onClick = null)
|
NavRow(title = vm.tr("hw_year4"), subtitle = vm.tr("hw4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Hw4Map) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@
|
||||||
# cs: bullet
|
# cs: bullet
|
||||||
# en: bullet
|
# en: bullet
|
||||||
|
|
||||||
|
--- 2026-10-07
|
||||||
|
cs: Sítě a technické vybavení: doplněné 2. až 4. ročníky, v každém 8 lekcí
|
||||||
|
cs: Výklad a kvíz od VLAN a sestavení počítače po maturitu
|
||||||
|
en: Networks and hardware: years 2 to 4 are filled in, eight lessons each
|
||||||
|
en: Slides and a quiz, from VLANs and PC assembly through to the exam
|
||||||
|
|
||||||
--- 2026-10-07
|
--- 2026-10-07
|
||||||
cs: Němčina: čtyři ročníky, 24 lekcí s poslechem, čtením a psaním
|
cs: Němčina: čtyři ročníky, 24 lekcí s poslechem, čtením a psaním
|
||||||
cs: Učebnice Neue Freunde zůstává, k ní přibyl kurz od A1 k B1
|
cs: Učebnice Neue Freunde zůstává, k ní přibyl kurz od A1 k B1
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,11 @@ final class AppModel: ObservableObject {
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func markCourse(_ course: String, _ id: Int) {
|
||||||
|
progress.markCourse(course, id)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
func markLit(_ id: Int) {
|
func markLit(_ id: Int) {
|
||||||
progress.markLit(id)
|
progress.markLit(id)
|
||||||
refresh()
|
refresh()
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ final class Content {
|
||||||
let netTasks: [J]
|
let netTasks: [J]
|
||||||
let netAnswers: [[J]]
|
let netAnswers: [[J]]
|
||||||
let hw: [J]
|
let hw: [J]
|
||||||
|
let net2: [J]
|
||||||
|
let net3: [J]
|
||||||
|
let net4: [J]
|
||||||
|
let hw2: [J]
|
||||||
|
let hw3: [J]
|
||||||
|
let hw4: [J]
|
||||||
let on: [J]
|
let on: [J]
|
||||||
let on2: [J]
|
let on2: [J]
|
||||||
let on3: [J]
|
let on3: [J]
|
||||||
|
|
@ -89,6 +95,12 @@ final class Content {
|
||||||
netAnswers = []
|
netAnswers = []
|
||||||
}
|
}
|
||||||
hw = raw.arr("hw")
|
hw = raw.arr("hw")
|
||||||
|
net2 = raw.arr("net2")
|
||||||
|
net3 = raw.arr("net3")
|
||||||
|
net4 = raw.arr("net4")
|
||||||
|
hw2 = raw.arr("hw2")
|
||||||
|
hw3 = raw.arr("hw3")
|
||||||
|
hw4 = raw.arr("hw4")
|
||||||
on = raw.arr("on")
|
on = raw.arr("on")
|
||||||
on2 = raw.arr("on2")
|
on2 = raw.arr("on2")
|
||||||
on3 = raw.arr("on3")
|
on3 = raw.arr("on3")
|
||||||
|
|
@ -124,6 +136,20 @@ final class Content {
|
||||||
func germanUnit(_ id: Int) -> J? { german.first { $0.int("id") == id } }
|
func germanUnit(_ id: Int) -> J? { german.first { $0.int("id") == id } }
|
||||||
func netLesson(_ id: Int) -> J? { netLessons.first { $0.int("id") == id } }
|
func netLesson(_ id: Int) -> J? { netLessons.first { $0.int("id") == id } }
|
||||||
func hwLesson(_ id: Int) -> J? { hw.first { $0.int("id") == id } }
|
func hwLesson(_ id: Int) -> J? { hw.first { $0.int("id") == id } }
|
||||||
|
func itLessons(_ course: String) -> [J] {
|
||||||
|
switch course {
|
||||||
|
case "net2": return net2
|
||||||
|
case "net3": return net3
|
||||||
|
case "net4": return net4
|
||||||
|
case "hw2": return hw2
|
||||||
|
case "hw3": return hw3
|
||||||
|
case "hw4": return hw4
|
||||||
|
default: return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func itLesson(_ course: String, _ id: Int) -> J? {
|
||||||
|
itLessons(course).first { $0.int("id") == id }
|
||||||
|
}
|
||||||
func onLesson(_ id: Int) -> J? { on.first { $0.int("id") == id } }
|
func onLesson(_ id: Int) -> J? { on.first { $0.int("id") == id } }
|
||||||
func on2Lesson(_ id: Int) -> J? { on2.first { $0.int("id") == id } }
|
func on2Lesson(_ id: Int) -> J? { on2.first { $0.int("id") == id } }
|
||||||
func on3Lesson(_ id: Int) -> J? { on3.first { $0.int("id") == id } }
|
func on3Lesson(_ id: Int) -> J? { on3.first { $0.int("id") == id } }
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ final class ProgressStore {
|
||||||
func markEn(_ year: Int, _ id: Int) { defaults.set(true, forKey: "en.\(year).\(id)") }
|
func markEn(_ year: Int, _ id: Int) { defaults.set(true, forKey: "en.\(year).\(id)") }
|
||||||
func deDone(_ year: Int, _ id: Int) -> Bool { defaults.bool(forKey: "de.\(year).\(id)") }
|
func deDone(_ year: Int, _ id: Int) -> Bool { defaults.bool(forKey: "de.\(year).\(id)") }
|
||||||
func markDe(_ year: Int, _ id: Int) { defaults.set(true, forKey: "de.\(year).\(id)") }
|
func markDe(_ year: Int, _ id: Int) { defaults.set(true, forKey: "de.\(year).\(id)") }
|
||||||
|
func courseDone(_ course: String, _ id: Int) -> Bool { defaults.bool(forKey: "\(course).\(id)") }
|
||||||
|
func markCourse(_ course: String, _ id: Int) { defaults.set(true, forKey: "\(course).\(id)") }
|
||||||
|
|
||||||
func litDone(_ id: Int) -> Bool { defaults.bool(forKey: "lit.\(id)") }
|
func litDone(_ id: Int) -> Bool { defaults.bool(forKey: "lit.\(id)") }
|
||||||
func markLit(_ id: Int) { defaults.set(true, forKey: "lit.\(id)") }
|
func markLit(_ id: Int) { defaults.set(true, forKey: "lit.\(id)") }
|
||||||
|
|
@ -126,6 +128,16 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
|
||||||
sum.doneUnits += 1
|
sum.doneUnits += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for course in ["net2", "net3", "net4", "hw2", "hw3", "hw4"] {
|
||||||
|
for l in content.itLessons(course) {
|
||||||
|
sum.totalEx += 1
|
||||||
|
sum.openUnits += 1
|
||||||
|
if p.courseDone(course, l.int("id")) {
|
||||||
|
sum.doneEx += 1
|
||||||
|
sum.doneUnits += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for l in content.on {
|
for l in content.on {
|
||||||
sum.totalEx += 1
|
sum.totalEx += 1
|
||||||
sum.openUnits += 1
|
sum.openUnits += 1
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,28 @@ enum Route: Hashable {
|
||||||
case netMap
|
case netMap
|
||||||
case netLesson(Int)
|
case netLesson(Int)
|
||||||
case netEx(Int)
|
case netEx(Int)
|
||||||
|
case net2Map
|
||||||
|
case net2Lesson(Int)
|
||||||
|
case net2Ex(Int)
|
||||||
|
case net3Map
|
||||||
|
case net3Lesson(Int)
|
||||||
|
case net3Ex(Int)
|
||||||
|
case net4Map
|
||||||
|
case net4Lesson(Int)
|
||||||
|
case net4Ex(Int)
|
||||||
case hwYears
|
case hwYears
|
||||||
case hwMap
|
case hwMap
|
||||||
case hwLesson(Int)
|
case hwLesson(Int)
|
||||||
case hwEx(Int)
|
case hwEx(Int)
|
||||||
|
case hw2Map
|
||||||
|
case hw2Lesson(Int)
|
||||||
|
case hw2Ex(Int)
|
||||||
|
case hw3Map
|
||||||
|
case hw3Lesson(Int)
|
||||||
|
case hw3Ex(Int)
|
||||||
|
case hw4Map
|
||||||
|
case hw4Lesson(Int)
|
||||||
|
case hw4Ex(Int)
|
||||||
case onYears
|
case onYears
|
||||||
case onMap
|
case onMap
|
||||||
case onLesson(Int)
|
case onLesson(Int)
|
||||||
|
|
@ -118,10 +136,28 @@ enum Route: Hashable {
|
||||||
case .netMap: return "netmap"
|
case .netMap: return "netmap"
|
||||||
case .netLesson(let id): return "netunit\(id)"
|
case .netLesson(let id): return "netunit\(id)"
|
||||||
case .netEx(let id): return "netex\(id)"
|
case .netEx(let id): return "netex\(id)"
|
||||||
|
case .net2Map: return "net2map"
|
||||||
|
case .net2Lesson(let id): return "net2unit\(id)"
|
||||||
|
case .net2Ex(let id): return "net2ex\(id)"
|
||||||
|
case .net3Map: return "net3map"
|
||||||
|
case .net3Lesson(let id): return "net3unit\(id)"
|
||||||
|
case .net3Ex(let id): return "net3ex\(id)"
|
||||||
|
case .net4Map: return "net4map"
|
||||||
|
case .net4Lesson(let id): return "net4unit\(id)"
|
||||||
|
case .net4Ex(let id): return "net4ex\(id)"
|
||||||
case .hwYears: return "hwyears"
|
case .hwYears: return "hwyears"
|
||||||
case .hwMap: return "hwmap"
|
case .hwMap: return "hwmap"
|
||||||
case .hwLesson(let id): return "hwunit\(id)"
|
case .hwLesson(let id): return "hwunit\(id)"
|
||||||
case .hwEx(let id): return "hwex\(id)"
|
case .hwEx(let id): return "hwex\(id)"
|
||||||
|
case .hw2Map: return "hw2map"
|
||||||
|
case .hw2Lesson(let id): return "hw2unit\(id)"
|
||||||
|
case .hw2Ex(let id): return "hw2ex\(id)"
|
||||||
|
case .hw3Map: return "hw3map"
|
||||||
|
case .hw3Lesson(let id): return "hw3unit\(id)"
|
||||||
|
case .hw3Ex(let id): return "hw3ex\(id)"
|
||||||
|
case .hw4Map: return "hw4map"
|
||||||
|
case .hw4Lesson(let id): return "hw4unit\(id)"
|
||||||
|
case .hw4Ex(let id): return "hw4ex\(id)"
|
||||||
case .onYears: return "onyears"
|
case .onYears: return "onyears"
|
||||||
case .onMap: return "onmap"
|
case .onMap: return "onmap"
|
||||||
case .onLesson(let id): return "onunit\(id)"
|
case .onLesson(let id): return "onunit\(id)"
|
||||||
|
|
@ -236,6 +272,24 @@ enum Route: Hashable {
|
||||||
return [.netYears, .netMap, .netLesson(id)]
|
return [.netYears, .netMap, .netLesson(id)]
|
||||||
case .netEx(let id):
|
case .netEx(let id):
|
||||||
return [.netYears, .netMap, .netLesson(id), .netEx(id)]
|
return [.netYears, .netMap, .netLesson(id), .netEx(id)]
|
||||||
|
case .net2Map:
|
||||||
|
return [.netYears, .net2Map]
|
||||||
|
case .net2Lesson(let id):
|
||||||
|
return [.netYears, .net2Map, .net2Lesson(id)]
|
||||||
|
case .net2Ex(let id):
|
||||||
|
return [.netYears, .net2Map, .net2Lesson(id), .net2Ex(id)]
|
||||||
|
case .net3Map:
|
||||||
|
return [.netYears, .net3Map]
|
||||||
|
case .net3Lesson(let id):
|
||||||
|
return [.netYears, .net3Map, .net3Lesson(id)]
|
||||||
|
case .net3Ex(let id):
|
||||||
|
return [.netYears, .net3Map, .net3Lesson(id), .net3Ex(id)]
|
||||||
|
case .net4Map:
|
||||||
|
return [.netYears, .net4Map]
|
||||||
|
case .net4Lesson(let id):
|
||||||
|
return [.netYears, .net4Map, .net4Lesson(id)]
|
||||||
|
case .net4Ex(let id):
|
||||||
|
return [.netYears, .net4Map, .net4Lesson(id), .net4Ex(id)]
|
||||||
case .hwYears:
|
case .hwYears:
|
||||||
return [.hwYears]
|
return [.hwYears]
|
||||||
case .hwMap:
|
case .hwMap:
|
||||||
|
|
@ -244,6 +298,24 @@ enum Route: Hashable {
|
||||||
return [.hwYears, .hwMap, .hwLesson(id)]
|
return [.hwYears, .hwMap, .hwLesson(id)]
|
||||||
case .hwEx(let id):
|
case .hwEx(let id):
|
||||||
return [.hwYears, .hwMap, .hwLesson(id), .hwEx(id)]
|
return [.hwYears, .hwMap, .hwLesson(id), .hwEx(id)]
|
||||||
|
case .hw2Map:
|
||||||
|
return [.hwYears, .hw2Map]
|
||||||
|
case .hw2Lesson(let id):
|
||||||
|
return [.hwYears, .hw2Map, .hw2Lesson(id)]
|
||||||
|
case .hw2Ex(let id):
|
||||||
|
return [.hwYears, .hw2Map, .hw2Lesson(id), .hw2Ex(id)]
|
||||||
|
case .hw3Map:
|
||||||
|
return [.hwYears, .hw3Map]
|
||||||
|
case .hw3Lesson(let id):
|
||||||
|
return [.hwYears, .hw3Map, .hw3Lesson(id)]
|
||||||
|
case .hw3Ex(let id):
|
||||||
|
return [.hwYears, .hw3Map, .hw3Lesson(id), .hw3Ex(id)]
|
||||||
|
case .hw4Map:
|
||||||
|
return [.hwYears, .hw4Map]
|
||||||
|
case .hw4Lesson(let id):
|
||||||
|
return [.hwYears, .hw4Map, .hw4Lesson(id)]
|
||||||
|
case .hw4Ex(let id):
|
||||||
|
return [.hwYears, .hw4Map, .hw4Lesson(id), .hw4Ex(id)]
|
||||||
case .onYears:
|
case .onYears:
|
||||||
return [.onYears]
|
return [.onYears]
|
||||||
case .onMap:
|
case .onMap:
|
||||||
|
|
@ -403,6 +475,12 @@ func routeFromPage(_ page: String) -> Route? {
|
||||||
case "dehome": return .deHome
|
case "dehome": return .deHome
|
||||||
case "netyears": return .netYears
|
case "netyears": return .netYears
|
||||||
case "netmap": return .netMap
|
case "netmap": return .netMap
|
||||||
|
case "net2map": return .net2Map
|
||||||
|
case "net3map": return .net3Map
|
||||||
|
case "net4map": return .net4Map
|
||||||
|
case "hw2map": return .hw2Map
|
||||||
|
case "hw3map": return .hw3Map
|
||||||
|
case "hw4map": return .hw4Map
|
||||||
case "hwyears": return .hwYears
|
case "hwyears": return .hwYears
|
||||||
case "hwmap": return .hwMap
|
case "hwmap": return .hwMap
|
||||||
case "onyears": return .onYears
|
case "onyears": return .onYears
|
||||||
|
|
@ -572,6 +650,34 @@ func routeFromPage(_ page: String) -> Route? {
|
||||||
if let m = page.wholeMatch(of: /de(\d)ex(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
if let m = page.wholeMatch(of: /de(\d)ex(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||||
return .deEx(y, n)
|
return .deEx(y, n)
|
||||||
}
|
}
|
||||||
|
if let m = page.wholeMatch(of: /net([234])unit(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||||
|
switch y {
|
||||||
|
case 3: return .net3Lesson(n)
|
||||||
|
case 4: return .net4Lesson(n)
|
||||||
|
default: return .net2Lesson(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let m = page.wholeMatch(of: /net([234])ex(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||||
|
switch y {
|
||||||
|
case 3: return .net3Ex(n)
|
||||||
|
case 4: return .net4Ex(n)
|
||||||
|
default: return .net2Ex(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let m = page.wholeMatch(of: /hw([234])unit(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||||
|
switch y {
|
||||||
|
case 3: return .hw3Lesson(n)
|
||||||
|
case 4: return .hw4Lesson(n)
|
||||||
|
default: return .hw2Lesson(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let m = page.wholeMatch(of: /hw([234])ex(\d+)/), let y = Int(m.1), let n = Int(m.2) {
|
||||||
|
switch y {
|
||||||
|
case 3: return .hw3Ex(n)
|
||||||
|
case 4: return .hw4Ex(n)
|
||||||
|
default: return .hw2Ex(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
if let m = page.wholeMatch(of: /en(\d)map/), let y = Int(m.1) {
|
if let m = page.wholeMatch(of: /en(\d)map/), let y = Int(m.1) {
|
||||||
return .enMap(y)
|
return .enMap(y)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -896,6 +896,62 @@ private struct MathGrid: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ItYearList: View {
|
||||||
|
@ObservedObject var vm: AppModel
|
||||||
|
let course: String
|
||||||
|
let titleKey: String
|
||||||
|
let subKey: String
|
||||||
|
let lesson: (Int) -> Route
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
LessonListScreen(
|
||||||
|
vm: vm,
|
||||||
|
title: vm.tr(titleKey),
|
||||||
|
subtitle: vm.tr(subKey),
|
||||||
|
rows: vm.content.itLessons(course).map { l in
|
||||||
|
let id = l.int("id")
|
||||||
|
return LessonRow(
|
||||||
|
id: "\(course)\(id)",
|
||||||
|
title: vm.tr(l.str("titleKey")),
|
||||||
|
done: vm.progress.courseDone(course, id),
|
||||||
|
destination: lesson(id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItSlides: View {
|
||||||
|
@ObservedObject var vm: AppModel
|
||||||
|
let course: String
|
||||||
|
let id: Int
|
||||||
|
let next: Route
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let l = vm.content.itLesson(course, id) {
|
||||||
|
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChoiceLessonScreen: View {
|
||||||
|
@ObservedObject var vm: AppModel
|
||||||
|
let lesson: J?
|
||||||
|
let onDone: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let lesson {
|
||||||
|
let spec = J([
|
||||||
|
"type": "choice",
|
||||||
|
"title": vm.tr(lesson.str("exTitleKey")),
|
||||||
|
"sub": lesson.str("quizHeadKey"),
|
||||||
|
"questions": lesson.rawArray("quiz"),
|
||||||
|
])
|
||||||
|
DispatchExercise(vm: vm, spec: spec, titleFallback: vm.tr(lesson.str("exTitleKey")), onDone: onDone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct FyzQuizScreen: View {
|
struct FyzQuizScreen: View {
|
||||||
@ObservedObject var vm: AppModel
|
@ObservedObject var vm: AppModel
|
||||||
let year: Int
|
let year: Int
|
||||||
|
|
|
||||||
|
|
@ -674,10 +674,18 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
|
||||||
}
|
}
|
||||||
case "netyears":
|
case "netyears":
|
||||||
sum.totalEx = vm.content.netLessons.count
|
sum.totalEx = vm.content.netLessons.count
|
||||||
|
+ vm.content.net2.count + vm.content.net3.count + vm.content.net4.count
|
||||||
sum.doneEx = vm.content.netLessons.filter { vm.progress.netDone($0.int("id")) }.count
|
sum.doneEx = vm.content.netLessons.filter { vm.progress.netDone($0.int("id")) }.count
|
||||||
|
+ ["net2", "net3", "net4"].reduce(0) { acc, course in
|
||||||
|
acc + vm.content.itLessons(course).filter { vm.progress.courseDone(course, $0.int("id")) }.count
|
||||||
|
}
|
||||||
case "hwyears", "hwmap":
|
case "hwyears", "hwmap":
|
||||||
sum.totalEx = vm.content.hw.count
|
sum.totalEx = vm.content.hw.count
|
||||||
|
+ vm.content.hw2.count + vm.content.hw3.count + vm.content.hw4.count
|
||||||
sum.doneEx = vm.content.hw.filter { vm.progress.hwDone($0.int("id")) }.count
|
sum.doneEx = vm.content.hw.filter { vm.progress.hwDone($0.int("id")) }.count
|
||||||
|
+ ["hw2", "hw3", "hw4"].reduce(0) { acc, course in
|
||||||
|
acc + vm.content.itLessons(course).filter { vm.progress.courseDone(course, $0.int("id")) }.count
|
||||||
|
}
|
||||||
case "enyears", "en1map", "en2map", "en3map", "en4map":
|
case "enyears", "en1map", "en2map", "en3map", "en4map":
|
||||||
sum.totalEx = (1...4).reduce(0) { $0 + vm.content.enYear($1).count }
|
sum.totalEx = (1...4).reduce(0) { $0 + vm.content.enYear($1).count }
|
||||||
sum.doneEx = (1...4).reduce(0) { acc, year in
|
sum.doneEx = (1...4).reduce(0) { acc, year in
|
||||||
|
|
@ -887,14 +895,23 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "netunit\(l.int("id"))", extra: "site")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "netunit\(l.int("id"))", extra: "site")
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "netex\(l.int("id"))")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "netex\(l.int("id"))")
|
||||||
}
|
}
|
||||||
|
for course in ["net2", "net3", "net4"] {
|
||||||
|
add(vm.tr("net_year\(course.dropFirst(3))"), vm.tr("search_lesson"), "\(course)map", extra: "rocnik site")
|
||||||
|
for l in vm.content.itLessons(course) {
|
||||||
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "\(course)unit\(l.int("id"))", extra: "site")
|
||||||
|
}
|
||||||
|
}
|
||||||
add(vm.tr("hw_year1"), vm.tr("search_lesson"), "hwmap", extra: "rocnik")
|
add(vm.tr("hw_year1"), vm.tr("search_lesson"), "hwmap", extra: "rocnik")
|
||||||
add(vm.tr("hw_year2"), vm.tr("search_lesson"), "hwyears", locked: true, extra: "rocnik")
|
|
||||||
add(vm.tr("hw_year3"), vm.tr("search_lesson"), "hwyears", locked: true, extra: "rocnik")
|
|
||||||
add(vm.tr("hw_year4"), vm.tr("search_lesson"), "hwyears", locked: true, extra: "rocnik")
|
|
||||||
for l in vm.content.hw {
|
for l in vm.content.hw {
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "hwunit\(l.int("id"))", extra: "hardware")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "hwunit\(l.int("id"))", extra: "hardware")
|
||||||
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "hwex\(l.int("id"))")
|
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "hwex\(l.int("id"))")
|
||||||
}
|
}
|
||||||
|
for course in ["hw2", "hw3", "hw4"] {
|
||||||
|
add(vm.tr("hw_year\(course.dropFirst(2))"), vm.tr("search_lesson"), "\(course)map", extra: "rocnik hardware")
|
||||||
|
for l in vm.content.itLessons(course) {
|
||||||
|
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "\(course)unit\(l.int("id"))", extra: "hardware")
|
||||||
|
}
|
||||||
|
}
|
||||||
add(vm.tr("on_year1"), vm.tr("search_lesson"), "onmap", extra: "rocnik")
|
add(vm.tr("on_year1"), vm.tr("search_lesson"), "onmap", extra: "rocnik")
|
||||||
add(vm.tr("on_year2"), vm.tr("search_lesson"), "on2map", extra: "rocnik")
|
add(vm.tr("on_year2"), vm.tr("search_lesson"), "on2map", extra: "rocnik")
|
||||||
add(vm.tr("on_year3"), vm.tr("search_lesson"), "on3map", extra: "rocnik")
|
add(vm.tr("on_year3"), vm.tr("search_lesson"), "on3map", extra: "rocnik")
|
||||||
|
|
@ -1178,16 +1195,17 @@ struct NetYearsScreen: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
let keys = ["net_year1", "net_year2", "net_year3", "net_year4"]
|
let years: [(String, String, Route)] = [
|
||||||
MacFillGrid(count: keys.count, minWidth: 260, minHeight: 180) { i in
|
("net_year1", "net_sub", .netMap),
|
||||||
if i == 0 {
|
("net_year2", "net2_sub", .net2Map),
|
||||||
NavigationLink(value: Route.netMap) {
|
("net_year3", "net3_sub", .net3Map),
|
||||||
MacLinkCard(title: vm.tr(keys[i]), subtitle: vm.tr("net_sub"), badge: "\(i + 1).")
|
("net_year4", "net4_sub", .net4Map),
|
||||||
|
]
|
||||||
|
MacFillGrid(count: years.count, minWidth: 260, minHeight: 180) { i in
|
||||||
|
NavigationLink(value: years[i].2) {
|
||||||
|
MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).")
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
} else {
|
|
||||||
MacLinkCard(title: vm.tr(keys[i]), subtitle: vm.tr("net_year_locked_sub"), badge: "\(i + 1).", locked: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.navigationTitle(vm.tr("net_years_title"))
|
.navigationTitle(vm.tr("net_years_title"))
|
||||||
.navigationSubtitle(vm.tr("net_years_sub"))
|
.navigationSubtitle(vm.tr("net_years_sub"))
|
||||||
|
|
@ -1196,13 +1214,14 @@ struct NetYearsScreen: View {
|
||||||
NavigationLink(value: Route.netMap) {
|
NavigationLink(value: Route.netMap) {
|
||||||
ListRowLabel(title: vm.tr("net_year1"), subtitle: vm.tr("net_sub"))
|
ListRowLabel(title: vm.tr("net_year1"), subtitle: vm.tr("net_sub"))
|
||||||
}
|
}
|
||||||
ForEach(["net_year2", "net_year3", "net_year4"], id: \.self) { key in
|
NavigationLink(value: Route.net2Map) {
|
||||||
Label {
|
ListRowLabel(title: vm.tr("net_year2"), subtitle: vm.tr("net2_sub"))
|
||||||
ListRowLabel(title: vm.tr(key), subtitle: vm.tr("net_year_locked_sub"))
|
|
||||||
} icon: {
|
|
||||||
Image(systemName: "lock.fill")
|
|
||||||
}
|
}
|
||||||
.foregroundStyle(.secondary)
|
NavigationLink(value: Route.net3Map) {
|
||||||
|
ListRowLabel(title: vm.tr("net_year3"), subtitle: vm.tr("net3_sub"))
|
||||||
|
}
|
||||||
|
NavigationLink(value: Route.net4Map) {
|
||||||
|
ListRowLabel(title: vm.tr("net_year4"), subtitle: vm.tr("net4_sub"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(vm.tr("net_years_title"))
|
.navigationTitle(vm.tr("net_years_title"))
|
||||||
|
|
@ -1217,16 +1236,17 @@ struct HwYearsScreen: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
let keys = ["hw_year1", "hw_year2", "hw_year3", "hw_year4"]
|
let years: [(String, String, Route)] = [
|
||||||
MacFillGrid(count: keys.count, minWidth: 260, minHeight: 180) { i in
|
("hw_year1", "hw_sub", .hwMap),
|
||||||
if i == 0 {
|
("hw_year2", "hw2_sub", .hw2Map),
|
||||||
NavigationLink(value: Route.hwMap) {
|
("hw_year3", "hw3_sub", .hw3Map),
|
||||||
MacLinkCard(title: vm.tr(keys[i]), subtitle: vm.tr("hw_sub"), badge: "\(i + 1).")
|
("hw_year4", "hw4_sub", .hw4Map),
|
||||||
|
]
|
||||||
|
MacFillGrid(count: years.count, minWidth: 260, minHeight: 180) { i in
|
||||||
|
NavigationLink(value: years[i].2) {
|
||||||
|
MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).")
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
} else {
|
|
||||||
MacLinkCard(title: vm.tr(keys[i]), subtitle: vm.tr("hw_year_locked_sub"), badge: "\(i + 1).", locked: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.navigationTitle(vm.tr("Technické vybavení"))
|
.navigationTitle(vm.tr("Technické vybavení"))
|
||||||
.navigationSubtitle(vm.tr("hw_years_sub"))
|
.navigationSubtitle(vm.tr("hw_years_sub"))
|
||||||
|
|
@ -1235,13 +1255,14 @@ struct HwYearsScreen: View {
|
||||||
NavigationLink(value: Route.hwMap) {
|
NavigationLink(value: Route.hwMap) {
|
||||||
ListRowLabel(title: vm.tr("hw_year1"), subtitle: vm.tr("hw_sub"))
|
ListRowLabel(title: vm.tr("hw_year1"), subtitle: vm.tr("hw_sub"))
|
||||||
}
|
}
|
||||||
ForEach(["hw_year2", "hw_year3", "hw_year4"], id: \.self) { key in
|
NavigationLink(value: Route.hw2Map) {
|
||||||
Label {
|
ListRowLabel(title: vm.tr("hw_year2"), subtitle: vm.tr("hw2_sub"))
|
||||||
ListRowLabel(title: vm.tr(key), subtitle: vm.tr("hw_year_locked_sub"))
|
|
||||||
} icon: {
|
|
||||||
Image(systemName: "lock.fill")
|
|
||||||
}
|
}
|
||||||
.foregroundStyle(.secondary)
|
NavigationLink(value: Route.hw3Map) {
|
||||||
|
ListRowLabel(title: vm.tr("hw_year3"), subtitle: vm.tr("hw3_sub"))
|
||||||
|
}
|
||||||
|
NavigationLink(value: Route.hw4Map) {
|
||||||
|
ListRowLabel(title: vm.tr("hw_year4"), subtitle: vm.tr("hw4_sub"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(vm.tr("Technické vybavení"))
|
.navigationTitle(vm.tr("Technické vybavení"))
|
||||||
|
|
@ -2256,6 +2277,24 @@ struct RouteDestination: View {
|
||||||
}
|
}
|
||||||
case .netEx(let id):
|
case .netEx(let id):
|
||||||
NetQuizScreen(vm: vm, id: id)
|
NetQuizScreen(vm: vm, id: id)
|
||||||
|
case .net2Map:
|
||||||
|
ItYearList(vm: vm, course: "net2", titleKey: "net_year2", subKey: "net2_sub") { .net2Lesson($0) }
|
||||||
|
case .net2Lesson(let id):
|
||||||
|
ItSlides(vm: vm, course: "net2", id: id, next: .net2Ex(id))
|
||||||
|
case .net2Ex(let id):
|
||||||
|
ChoiceLessonScreen(vm: vm, lesson: vm.content.itLesson("net2", id)) { vm.markCourse("net2", id) }
|
||||||
|
case .net3Map:
|
||||||
|
ItYearList(vm: vm, course: "net3", titleKey: "net_year3", subKey: "net3_sub") { .net3Lesson($0) }
|
||||||
|
case .net3Lesson(let id):
|
||||||
|
ItSlides(vm: vm, course: "net3", id: id, next: .net3Ex(id))
|
||||||
|
case .net3Ex(let id):
|
||||||
|
ChoiceLessonScreen(vm: vm, lesson: vm.content.itLesson("net3", id)) { vm.markCourse("net3", id) }
|
||||||
|
case .net4Map:
|
||||||
|
ItYearList(vm: vm, course: "net4", titleKey: "net_year4", subKey: "net4_sub") { .net4Lesson($0) }
|
||||||
|
case .net4Lesson(let id):
|
||||||
|
ItSlides(vm: vm, course: "net4", id: id, next: .net4Ex(id))
|
||||||
|
case .net4Ex(let id):
|
||||||
|
ChoiceLessonScreen(vm: vm, lesson: vm.content.itLesson("net4", id)) { vm.markCourse("net4", id) }
|
||||||
case .hwYears:
|
case .hwYears:
|
||||||
HwYearsScreen(vm: vm)
|
HwYearsScreen(vm: vm)
|
||||||
case .hwMap:
|
case .hwMap:
|
||||||
|
|
@ -2279,6 +2318,24 @@ struct RouteDestination: View {
|
||||||
}
|
}
|
||||||
case .hwEx(let id):
|
case .hwEx(let id):
|
||||||
HwQuizScreen(vm: vm, id: id)
|
HwQuizScreen(vm: vm, id: id)
|
||||||
|
case .hw2Map:
|
||||||
|
ItYearList(vm: vm, course: "hw2", titleKey: "hw_year2", subKey: "hw2_sub") { .hw2Lesson($0) }
|
||||||
|
case .hw2Lesson(let id):
|
||||||
|
ItSlides(vm: vm, course: "hw2", id: id, next: .hw2Ex(id))
|
||||||
|
case .hw2Ex(let id):
|
||||||
|
ChoiceLessonScreen(vm: vm, lesson: vm.content.itLesson("hw2", id)) { vm.markCourse("hw2", id) }
|
||||||
|
case .hw3Map:
|
||||||
|
ItYearList(vm: vm, course: "hw3", titleKey: "hw_year3", subKey: "hw3_sub") { .hw3Lesson($0) }
|
||||||
|
case .hw3Lesson(let id):
|
||||||
|
ItSlides(vm: vm, course: "hw3", id: id, next: .hw3Ex(id))
|
||||||
|
case .hw3Ex(let id):
|
||||||
|
ChoiceLessonScreen(vm: vm, lesson: vm.content.itLesson("hw3", id)) { vm.markCourse("hw3", id) }
|
||||||
|
case .hw4Map:
|
||||||
|
ItYearList(vm: vm, course: "hw4", titleKey: "hw_year4", subKey: "hw4_sub") { .hw4Lesson($0) }
|
||||||
|
case .hw4Lesson(let id):
|
||||||
|
ItSlides(vm: vm, course: "hw4", id: id, next: .hw4Ex(id))
|
||||||
|
case .hw4Ex(let id):
|
||||||
|
ChoiceLessonScreen(vm: vm, lesson: vm.content.itLesson("hw4", id)) { vm.markCourse("hw4", id) }
|
||||||
case .onYears:
|
case .onYears:
|
||||||
OnYearsScreen(vm: vm)
|
OnYearsScreen(vm: vm)
|
||||||
case .enYears:
|
case .enYears:
|
||||||
|
|
|
||||||
|
|
@ -1142,6 +1142,12 @@ def main() -> int:
|
||||||
lit4_lessons = lit_pack("lit4")
|
lit4_lessons = lit_pack("lit4")
|
||||||
chem_lessons = lit_pack("chem")
|
chem_lessons = lit_pack("chem")
|
||||||
bio_lessons = lit_pack("bio")
|
bio_lessons = lit_pack("bio")
|
||||||
|
net2_lessons = lit_pack("net2")
|
||||||
|
net3_lessons = lit_pack("net3")
|
||||||
|
net4_lessons = lit_pack("net4")
|
||||||
|
hw2_lessons = lit_pack("hw2")
|
||||||
|
hw3_lessons = lit_pack("hw3")
|
||||||
|
hw4_lessons = lit_pack("hw4")
|
||||||
fyz_lessons = lit_pack("fyz")
|
fyz_lessons = lit_pack("fyz")
|
||||||
fyz2_lessons = lit_pack("fyz2")
|
fyz2_lessons = lit_pack("fyz2")
|
||||||
fyz3_lessons = lit_pack("fyz3")
|
fyz3_lessons = lit_pack("fyz3")
|
||||||
|
|
@ -1692,6 +1698,12 @@ def main() -> int:
|
||||||
"answers": net_ans(A("net_ans")),
|
"answers": net_ans(A("net_ans")),
|
||||||
},
|
},
|
||||||
"hw": hw_lessons,
|
"hw": hw_lessons,
|
||||||
|
"net2": net2_lessons,
|
||||||
|
"net3": net3_lessons,
|
||||||
|
"net4": net4_lessons,
|
||||||
|
"hw2": hw2_lessons,
|
||||||
|
"hw3": hw3_lessons,
|
||||||
|
"hw4": hw4_lessons,
|
||||||
"on": on_lessons,
|
"on": on_lessons,
|
||||||
"lit": lit_lessons,
|
"lit": lit_lessons,
|
||||||
"lit2": lit2_lessons,
|
"lit2": lit2_lessons,
|
||||||
|
|
|
||||||
2472
scripts/gen-it-years.py
Normal file
2472
scripts/gen-it-years.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -946,6 +946,26 @@ GtkWidget *build_dehome_page(void);
|
||||||
void add_de_pages(GtkStack *stack);
|
void add_de_pages(GtkStack *stack);
|
||||||
void de_load_progress(void);
|
void de_load_progress(void);
|
||||||
void progress_for_de(ProgressSum *out);
|
void progress_for_de(ProgressSum *out);
|
||||||
|
typedef struct QuizYear QuizYear;
|
||||||
|
extern QuizYear *qy_net2;
|
||||||
|
extern QuizYear *qy_net3;
|
||||||
|
extern QuizYear *qy_net4;
|
||||||
|
extern QuizYear *qy_hw2;
|
||||||
|
extern QuizYear *qy_hw3;
|
||||||
|
extern QuizYear *qy_hw4;
|
||||||
|
void quizyear_boot(void);
|
||||||
|
void quizyear_load_all(void);
|
||||||
|
void quizyear_apply_lang(void);
|
||||||
|
void progress_for_net_years(ProgressSum *out);
|
||||||
|
void progress_for_hw_years(ProgressSum *out);
|
||||||
|
GtkWidget *quizyear_map(QuizYear *Y);
|
||||||
|
GtkWidget *quizyear_unit(QuizYear *Y, int index, const char *title_key,
|
||||||
|
const char *sub_key, const NetSlide *slides,
|
||||||
|
int n_slides);
|
||||||
|
GtkWidget *quizyear_quiz(QuizYear *Y, int index, const char *title_key,
|
||||||
|
const char *head_key, const ChoiceQ *qs,
|
||||||
|
const char **hints, int n);
|
||||||
|
void add_it_year_pages(GtkStack *stack);
|
||||||
void on2_load_progress(void);
|
void on2_load_progress(void);
|
||||||
void on2_save_progress(void);
|
void on2_save_progress(void);
|
||||||
void mark_on2_done(int lesson_id);
|
void mark_on2_done(int lesson_id);
|
||||||
|
|
|
||||||
17
src/hw.c
17
src/hw.c
|
|
@ -383,8 +383,21 @@ static GtkWidget *hw_year_button(int year, gboolean locked) {
|
||||||
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
|
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
|
||||||
gtk_box_append(GTK_BOX(row), icon);
|
gtk_box_append(GTK_BOX(row), icon);
|
||||||
} else {
|
} else {
|
||||||
|
static const char *pages[] = {
|
||||||
|
"hwmap", "hw2map", "hw3map", "hw4map",
|
||||||
|
};
|
||||||
|
static const char *subs[] = {
|
||||||
|
"hw_sub", "hw2_sub", "hw3_sub", "hw4_sub",
|
||||||
|
};
|
||||||
|
GtkWidget *sub = gtk_label_new(NULL);
|
||||||
|
|
||||||
|
i18n_bind(sub, subs[year - 1], 0);
|
||||||
|
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||||
|
gtk_widget_add_css_class(sub, "quiz-intro");
|
||||||
|
gtk_box_append(GTK_BOX(texts), sub);
|
||||||
g_object_set_data_full(G_OBJECT(btn), "target",
|
g_object_set_data_full(G_OBJECT(btn), "target",
|
||||||
g_strdup("hwmap"), g_free);
|
g_strdup(pages[year - 1]), g_free);
|
||||||
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,7 +435,7 @@ GtkWidget *build_hwyears_page(void) {
|
||||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||||
|
|
||||||
for (y = 1; y <= 4; y++)
|
for (y = 1; y <= 4; y++)
|
||||||
gtk_box_append(GTK_BOX(list), hw_year_button(y, y > 1));
|
gtk_box_append(GTK_BOX(list), hw_year_button(y, FALSE));
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1621,6 +1621,7 @@ const TrEntry tr_ui[] = {
|
||||||
"Drag the story parts into the correct order."},
|
"Drag the story parts into the correct order."},
|
||||||
#include "english_i18n.inc"
|
#include "english_i18n.inc"
|
||||||
#include "german_i18n.inc"
|
#include "german_i18n.inc"
|
||||||
|
#include "it_years_i18n.inc"
|
||||||
#include "math_i18n.inc"
|
#include "math_i18n.inc"
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
@ -2605,6 +2606,7 @@ void on3_lessons_apply_lang(void);
|
||||||
void on4_lessons_apply_lang(void);
|
void on4_lessons_apply_lang(void);
|
||||||
void en_lessons_apply_lang(void);
|
void en_lessons_apply_lang(void);
|
||||||
void de_lessons_apply_lang(void);
|
void de_lessons_apply_lang(void);
|
||||||
|
void quizyear_apply_lang(void);
|
||||||
void lit_lessons_apply_lang(void);
|
void lit_lessons_apply_lang(void);
|
||||||
void sci_lessons_apply_lang(void);
|
void sci_lessons_apply_lang(void);
|
||||||
void fyz_lessons_apply_lang(void);
|
void fyz_lessons_apply_lang(void);
|
||||||
|
|
@ -2629,6 +2631,7 @@ void apply_language(void) {
|
||||||
on4_lessons_apply_lang();
|
on4_lessons_apply_lang();
|
||||||
en_lessons_apply_lang();
|
en_lessons_apply_lang();
|
||||||
de_lessons_apply_lang();
|
de_lessons_apply_lang();
|
||||||
|
quizyear_apply_lang();
|
||||||
lit_lessons_apply_lang();
|
lit_lessons_apply_lang();
|
||||||
sci_lessons_apply_lang();
|
sci_lessons_apply_lang();
|
||||||
fyz_lessons_apply_lang();
|
fyz_lessons_apply_lang();
|
||||||
|
|
|
||||||
2606
src/it_year_pages.c
Normal file
2606
src/it_year_pages.c
Normal file
File diff suppressed because it is too large
Load diff
198
src/it_years_i18n.inc
Normal file
198
src/it_years_i18n.inc
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
{"net2_sub", "VLAN, STP, DHCP, DNS, NAT, IPv6, statické směrování a ACL.", "VLANs, STP, DHCP, DNS, NAT, IPv6, static routing and ACLs."},
|
||||||
|
{"net2_unit1", "VLAN a trunk", "VLANs and trunks"},
|
||||||
|
{"net2_unit1_sub", "Logické sítě na jednom přepínači a značkovaný spoj.", "Logical networks on one switch and a tagged link."},
|
||||||
|
{"net2_ex1_title", "Kvíz: VLAN a trunk", "Quiz: VLANs and trunks"},
|
||||||
|
{"net2_quiz1_head", "Kvíz k tématu VLAN a trunk", "Quiz on VLANs and trunks"},
|
||||||
|
{"net2_unit2", "STP a smyčky", "STP and loops"},
|
||||||
|
{"net2_unit2_sub", "Proč smyčka na druhé vrstvě shodí síť a jak ji STP přeruší.", "Why a layer-2 loop takes the network down, and how STP breaks it."},
|
||||||
|
{"net2_ex2_title", "Kvíz: STP a smyčky", "Quiz: STP and loops"},
|
||||||
|
{"net2_quiz2_head", "Kvíz k tématu STP a smyčky", "Quiz on STP and loops"},
|
||||||
|
{"net2_unit3", "DHCP a DNS", "DHCP and DNS"},
|
||||||
|
{"net2_unit3_sub", "Kdo přiděluje adresu a kdo překládá jméno.", "Who hands out an address, and who translates a name."},
|
||||||
|
{"net2_ex3_title", "Kvíz: DHCP a DNS", "Quiz: DHCP and DNS"},
|
||||||
|
{"net2_quiz3_head", "Kvíz k tématu DHCP a DNS", "Quiz on DHCP and DNS"},
|
||||||
|
{"net2_unit4", "NAT a PAT", "NAT and PAT"},
|
||||||
|
{"net2_unit4_sub", "Jedna veřejná adresa pro mnoho počítačů.", "One public address for many computers."},
|
||||||
|
{"net2_ex4_title", "Kvíz: NAT a PAT", "Quiz: NAT and PAT"},
|
||||||
|
{"net2_quiz4_head", "Kvíz k tématu NAT a PAT", "Quiz on NAT and PAT"},
|
||||||
|
{"net2_unit5", "IPv6 základy", "IPv6 basics"},
|
||||||
|
{"net2_unit5_sub", "Delší adresa, jiný zápis a co se nemění.", "A longer address, a different notation, and what stays the same."},
|
||||||
|
{"net2_ex5_title", "Kvíz: IPv6 základy", "Quiz: IPv6 basics"},
|
||||||
|
{"net2_quiz5_head", "Kvíz k tématu IPv6 základy", "Quiz on IPv6 basics"},
|
||||||
|
{"net2_unit6", "Statické směrování", "Static routing"},
|
||||||
|
{"net2_unit6_sub", "Ruční cesta, metrika a kdy přestane stačit.", "A hand-written route, the metric, and when it is no longer enough."},
|
||||||
|
{"net2_ex6_title", "Kvíz: Statické směrování", "Quiz: Static routing"},
|
||||||
|
{"net2_quiz6_head", "Kvíz k tématu Statické směrování", "Quiz on Static routing"},
|
||||||
|
{"net2_unit7", "ACL a filtrování", "ACLs and filtering"},
|
||||||
|
{"net2_unit7_sub", "Seznam, který provoz pustí a který zahodí.", "A list that permits traffic or drops it."},
|
||||||
|
{"net2_ex7_title", "Kvíz: ACL a filtrování", "Quiz: ACLs and filtering"},
|
||||||
|
{"net2_quiz7_head", "Kvíz k tématu ACL a filtrování", "Quiz on ACLs and filtering"},
|
||||||
|
{"net2_unit8", "Podniková Wi-Fi", "Enterprise Wi-Fi"},
|
||||||
|
{"net2_unit8_sub", "SSID, WPA3 a proč jedno heslo na nástěnce nestačí.", "SSIDs, WPA3, and why one password on the wall is not enough."},
|
||||||
|
{"net2_ex8_title", "Kvíz: Podniková Wi-Fi", "Quiz: Enterprise Wi-Fi"},
|
||||||
|
{"net2_quiz8_head", "Kvíz k tématu Podniková Wi-Fi", "Quiz on Enterprise Wi-Fi"},
|
||||||
|
{"net3_sub", "OSPF, směrování mezi VLAN, WAN, VPN, firewall, QoS a dohled.", "OSPF, inter-VLAN routing, WAN, VPN, firewalls, QoS and monitoring."},
|
||||||
|
{"net3_unit1", "OSPF", "OSPF"},
|
||||||
|
{"net3_unit1_sub", "Směrovače si samy řeknou, jaké sítě znají.", "Routers tell each other which networks they know."},
|
||||||
|
{"net3_ex1_title", "Kvíz: OSPF", "Quiz: OSPF"},
|
||||||
|
{"net3_quiz1_head", "Kvíz k tématu OSPF", "Quiz on OSPF"},
|
||||||
|
{"net3_unit2", "Směrování mezi VLAN", "Inter-VLAN routing"},
|
||||||
|
{"net3_unit2_sub", "Jedna brána pro každou VLAN.", "One gateway for each VLAN."},
|
||||||
|
{"net3_ex2_title", "Kvíz: Směrování mezi VLAN", "Quiz: Inter-VLAN routing"},
|
||||||
|
{"net3_quiz2_head", "Kvíz k tématu Směrování mezi VLAN", "Quiz on Inter-VLAN routing"},
|
||||||
|
{"net3_unit3", "WAN", "WAN"},
|
||||||
|
{"net3_unit3_sub", "Spoj mezi pobočkami a k poskytovateli.", "A link between sites and towards the provider."},
|
||||||
|
{"net3_ex3_title", "Kvíz: WAN", "Quiz: WAN"},
|
||||||
|
{"net3_quiz3_head", "Kvíz k tématu WAN", "Quiz on WAN"},
|
||||||
|
{"net3_unit4", "VPN", "VPN"},
|
||||||
|
{"net3_unit4_sub", "Šifrovaný tunel přes síť, které nevěříte.", "An encrypted tunnel across a network you do not trust."},
|
||||||
|
{"net3_ex4_title", "Kvíz: VPN", "Quiz: VPN"},
|
||||||
|
{"net3_quiz4_head", "Kvíz k tématu VPN", "Quiz on VPN"},
|
||||||
|
{"net3_unit5", "Firewall a zóny", "Firewalls and zones"},
|
||||||
|
{"net3_unit5_sub", "Kdo s kým smí mluvit, ne jen jestli kabel svítí.", "Who may talk to whom, not only whether the cable is lit."},
|
||||||
|
{"net3_ex5_title", "Kvíz: Firewall a zóny", "Quiz: Firewalls and zones"},
|
||||||
|
{"net3_quiz5_head", "Kvíz k tématu Firewall a zóny", "Quiz on Firewalls and zones"},
|
||||||
|
{"net3_unit6", "QoS", "QoS"},
|
||||||
|
{"net3_unit6_sub", "Když je linka plná, někdo musí dostat přednost.", "When the link is full, someone has to go first."},
|
||||||
|
{"net3_ex6_title", "Kvíz: QoS", "Quiz: QoS"},
|
||||||
|
{"net3_quiz6_head", "Kvíz k tématu QoS", "Quiz on QoS"},
|
||||||
|
{"net3_unit7", "Dohled sítě", "Watching the network"},
|
||||||
|
{"net3_unit7_sub", "SNMP, syslog a co z grafu opravdu plyne.", "SNMP, syslog, and what a graph really tells you."},
|
||||||
|
{"net3_ex7_title", "Kvíz: Dohled sítě", "Quiz: Watching the network"},
|
||||||
|
{"net3_quiz7_head", "Kvíz k tématu Dohled sítě", "Quiz on Watching the network"},
|
||||||
|
{"net3_unit8", "Záloha konfigurace", "Configuration backups"},
|
||||||
|
{"net3_unit8_sub", "Co uložit, než se přepínač vymění.", "What to save before a switch is replaced."},
|
||||||
|
{"net3_ex8_title", "Kvíz: Záloha konfigurace", "Quiz: Configuration backups"},
|
||||||
|
{"net3_quiz8_head", "Kvíz k tématu Záloha konfigurace", "Quiz on Configuration backups"},
|
||||||
|
{"net4_sub", "Návrh adres, redundance, 802.1X, hledání chyb, cloud a maturita.", "Address plans, redundancy, 802.1X, troubleshooting, cloud and the exam."},
|
||||||
|
{"net4_unit1", "Adresní plán", "An address plan"},
|
||||||
|
{"net4_unit1_sub", "Sítě se navrhují dřív, než se zapojí první kabel.", "Networks are designed before the first cable is patched."},
|
||||||
|
{"net4_ex1_title", "Kvíz: Adresní plán", "Quiz: An address plan"},
|
||||||
|
{"net4_quiz1_head", "Kvíz k tématu Adresní plán", "Quiz on An address plan"},
|
||||||
|
{"net4_unit2", "Redundance", "Redundancy"},
|
||||||
|
{"net4_unit2_sub", "Druhá cesta, která se opravdu použije.", "A second path that is actually used."},
|
||||||
|
{"net4_ex2_title", "Kvíz: Redundance", "Quiz: Redundancy"},
|
||||||
|
{"net4_quiz2_head", "Kvíz k tématu Redundance", "Quiz on Redundancy"},
|
||||||
|
{"net4_unit3", "802.1X", "802.1X"},
|
||||||
|
{"net4_unit3_sub", "Port pustí data, až když se zařízení představí.", "A port forwards data only after the device identifies itself."},
|
||||||
|
{"net4_ex3_title", "Kvíz: 802.1X", "Quiz: 802.1X"},
|
||||||
|
{"net4_quiz3_head", "Kvíz k tématu 802.1X", "Quiz on 802.1X"},
|
||||||
|
{"net4_unit4", "Hledání chyby", "Troubleshooting"},
|
||||||
|
{"net4_unit4_sub", "Postup zdola nahoru, ne náhodné restarty.", "Bottom-up steps, not random reboots."},
|
||||||
|
{"net4_ex4_title", "Kvíz: Hledání chyby", "Quiz: Troubleshooting"},
|
||||||
|
{"net4_quiz4_head", "Kvíz k tématu Hledání chyby", "Quiz on Troubleshooting"},
|
||||||
|
{"net4_unit5", "Cloud a hybrid", "Cloud and hybrid networks"},
|
||||||
|
{"net4_unit5_sub", "Část služeb není ve skříni na chodbě.", "Some services are no longer in the cupboard in the corridor."},
|
||||||
|
{"net4_ex5_title", "Kvíz: Cloud a hybrid", "Quiz: Cloud and hybrid networks"},
|
||||||
|
{"net4_quiz5_head", "Kvíz k tématu Cloud a hybrid", "Quiz on Cloud and hybrid networks"},
|
||||||
|
{"net4_unit6", "Automatizace", "Automation"},
|
||||||
|
{"net4_unit6_sub", "Opakovanou práci ať dělá skript, ne deset kliknutí.", "Let a script do the repeated work, not ten clicks."},
|
||||||
|
{"net4_ex6_title", "Kvíz: Automatizace", "Quiz: Automation"},
|
||||||
|
{"net4_quiz6_head", "Kvíz k tématu Automatizace", "Quiz on Automation"},
|
||||||
|
{"net4_unit7", "Provoz a maturita", "Operations and the exam"},
|
||||||
|
{"net4_unit7_sub", "Síť ve škole má pravidla, ne jen blikající porty.", "A school network has rules, not only blinking ports."},
|
||||||
|
{"net4_ex7_title", "Kvíz: Provoz a maturita", "Quiz: Operations and the exam"},
|
||||||
|
{"net4_quiz7_head", "Kvíz k tématu Provoz a maturita", "Quiz on Operations and the exam"},
|
||||||
|
{"net4_unit8", "Dokumentace sítě", "Network documentation"},
|
||||||
|
{"net4_unit8_sub", "Schéma, které sedí s tím, co je ve zdi.", "A diagram that matches what is actually in the wall."},
|
||||||
|
{"net4_ex8_title", "Kvíz: Dokumentace sítě", "Quiz: Network documentation"},
|
||||||
|
{"net4_quiz8_head", "Kvíz k tématu Dokumentace sítě", "Quiz on Network documentation"},
|
||||||
|
{"hw2_sub", "Monitory, tisk, sběrnice, chlazení, sestavení, UEFI a diagnostika.", "Displays, printing, buses, cooling, assembly, UEFI and diagnostics."},
|
||||||
|
{"hw2_unit1", "Monitory", "Displays"},
|
||||||
|
{"hw2_unit1_sub", "Panel, rozlišení a proč 60 Hz není jediné číslo.", "The panel, the resolution, and why 60 Hz is not the only number."},
|
||||||
|
{"hw2_ex1_title", "Kvíz: Monitory", "Quiz: Displays"},
|
||||||
|
{"hw2_quiz1_head", "Kvíz k tématu Monitory", "Quiz on Displays"},
|
||||||
|
{"hw2_unit2", "Tisk a sken", "Print and scan"},
|
||||||
|
{"hw2_unit2_sub", "Laser, inkoust a síťová fronta.", "Laser, inkjet, and a network queue."},
|
||||||
|
{"hw2_ex2_title", "Kvíz: Tisk a sken", "Quiz: Print and scan"},
|
||||||
|
{"hw2_quiz2_head", "Kvíz k tématu Tisk a sken", "Quiz on Print and scan"},
|
||||||
|
{"hw2_unit3", "Sběrnice a disky", "Buses and drives"},
|
||||||
|
{"hw2_unit3_sub", "USB, SATA a NVMe nejsou zaměnitelné jen konektorem.", "USB, SATA and NVMe are not interchangeable just because of the plug."},
|
||||||
|
{"hw2_ex3_title", "Kvíz: Sběrnice a disky", "Quiz: Buses and drives"},
|
||||||
|
{"hw2_quiz3_head", "Kvíz k tématu Sběrnice a disky", "Quiz on Buses and drives"},
|
||||||
|
{"hw2_unit4", "Chlazení", "Cooling"},
|
||||||
|
{"hw2_unit4_sub", "Vzduch musí mít kudy dovnitř a ven.", "Air needs a way in and a way out."},
|
||||||
|
{"hw2_ex4_title", "Kvíz: Chlazení", "Quiz: Cooling"},
|
||||||
|
{"hw2_quiz4_head", "Kvíz k tématu Chlazení", "Quiz on Cooling"},
|
||||||
|
{"hw2_unit5", "Sestavení", "Building a PC"},
|
||||||
|
{"hw2_unit5_sub", "Pořadí, které nerozbije desku.", "An order that does not break the board."},
|
||||||
|
{"hw2_ex5_title", "Kvíz: Sestavení", "Quiz: Building a PC"},
|
||||||
|
{"hw2_quiz5_head", "Kvíz k tématu Sestavení", "Quiz on Building a PC"},
|
||||||
|
{"hw2_unit6", "UEFI a boot", "UEFI and boot"},
|
||||||
|
{"hw2_unit6_sub", "Co se děje, než naběhne systém.", "What happens before the operating system starts."},
|
||||||
|
{"hw2_ex6_title", "Kvíz: UEFI a boot", "Quiz: UEFI and boot"},
|
||||||
|
{"hw2_quiz6_head", "Kvíz k tématu UEFI a boot", "Quiz on UEFI and boot"},
|
||||||
|
{"hw2_unit7", "Diagnostika", "Diagnostics"},
|
||||||
|
{"hw2_unit7_sub", "POST, pípání a minimum, které ještě naběhne.", "POST, beep codes, and the smallest build that still starts."},
|
||||||
|
{"hw2_ex7_title", "Kvíz: Diagnostika", "Quiz: Diagnostics"},
|
||||||
|
{"hw2_quiz7_head", "Kvíz k tématu Diagnostika", "Quiz on Diagnostics"},
|
||||||
|
{"hw2_unit8", "Porty a dokování", "Ports and docks"},
|
||||||
|
{"hw2_unit8_sub", "Co z notebooku opravdu vyleze.", "What a laptop can actually drive."},
|
||||||
|
{"hw2_ex8_title", "Kvíz: Porty a dokování", "Quiz: Ports and docks"},
|
||||||
|
{"hw2_quiz8_head", "Kvíz k tématu Porty a dokování", "Quiz on Ports and docks"},
|
||||||
|
{"hw3_sub", "RAID, NAS, servery, virtualizace, zálohy, UPS a firmware.", "RAID, NAS, servers, virtualisation, backups, UPS and firmware."},
|
||||||
|
{"hw3_unit1", "RAID", "RAID"},
|
||||||
|
{"hw3_unit1_sub", "Více disků jako jeden svazek, ne jako záloha.", "Several drives as one volume, not as a backup."},
|
||||||
|
{"hw3_ex1_title", "Kvíz: RAID", "Quiz: RAID"},
|
||||||
|
{"hw3_quiz1_head", "Kvíz k tématu RAID", "Quiz on RAID"},
|
||||||
|
{"hw3_unit2", "NAS a sítě disků", "NAS and storage networks"},
|
||||||
|
{"hw3_unit2_sub", "Disky, které nejsou uvnitř jednoho počítače.", "Drives that are not inside one computer."},
|
||||||
|
{"hw3_ex2_title", "Kvíz: NAS a sítě disků", "Quiz: NAS and storage networks"},
|
||||||
|
{"hw3_quiz2_head", "Kvíz k tématu NAS a sítě disků", "Quiz on NAS and storage networks"},
|
||||||
|
{"hw3_unit3", "Servery a rack", "Servers and racks"},
|
||||||
|
{"hw3_unit3_sub", "Skříň, do které se nedává herní počítač naležato.", "A cabinet that is not a gaming PC on its side."},
|
||||||
|
{"hw3_ex3_title", "Kvíz: Servery a rack", "Quiz: Servers and racks"},
|
||||||
|
{"hw3_quiz3_head", "Kvíz k tématu Servery a rack", "Quiz on Servers and racks"},
|
||||||
|
{"hw3_unit4", "Virtualizace", "Virtualisation"},
|
||||||
|
{"hw3_unit4_sub", "Více systémů na jednom železe.", "Several systems on one piece of hardware."},
|
||||||
|
{"hw3_ex4_title", "Kvíz: Virtualizace", "Quiz: Virtualisation"},
|
||||||
|
{"hw3_quiz4_head", "Kvíz k tématu Virtualizace", "Quiz on Virtualisation"},
|
||||||
|
{"hw3_unit5", "Zálohování", "Backups"},
|
||||||
|
{"hw3_unit5_sub", "Kopie, kterou umíte vrátit.", "A copy you can actually restore."},
|
||||||
|
{"hw3_ex5_title", "Kvíz: Zálohování", "Quiz: Backups"},
|
||||||
|
{"hw3_quiz5_head", "Kvíz k tématu Zálohování", "Quiz on Backups"},
|
||||||
|
{"hw3_unit6", "UPS", "UPS"},
|
||||||
|
{"hw3_unit6_sub", "Minuty na uložení, ne náhradní elektrárna.", "Minutes to shut down cleanly, not a spare power station."},
|
||||||
|
{"hw3_ex6_title", "Kvíz: UPS", "Quiz: UPS"},
|
||||||
|
{"hw3_quiz6_head", "Kvíz k tématu UPS", "Quiz on UPS"},
|
||||||
|
{"hw3_unit7", "Firmware", "Firmware"},
|
||||||
|
{"hw3_unit7_sub", "Aktualizace, která nejde udělat uprostřed hodiny.", "An update that is not applied in the middle of a lesson."},
|
||||||
|
{"hw3_ex7_title", "Kvíz: Firmware", "Quiz: Firmware"},
|
||||||
|
{"hw3_quiz7_head", "Kvíz k tématu Firmware", "Quiz on Firmware"},
|
||||||
|
{"hw3_unit8", "Měření výkonu", "Measuring performance"},
|
||||||
|
{"hw3_unit8_sub", "Číslo bez podmínek nic neznamená.", "A number without the conditions means nothing."},
|
||||||
|
{"hw3_ex8_title", "Kvíz: Měření výkonu", "Quiz: Measuring performance"},
|
||||||
|
{"hw3_quiz8_head", "Kvíz k tématu Měření výkonu", "Quiz on Measuring performance"},
|
||||||
|
{"hw4_sub", "Údržba, TPM, nákup, mobily, periferie, ekologie a maturita.", "Maintenance, TPM, buying, mobiles, peripherals, disposal and the exam."},
|
||||||
|
{"hw4_unit1", "Údržba", "Maintenance"},
|
||||||
|
{"hw4_unit1_sub", "Čištění a revize dřív, než něco shoří.", "Cleaning and checks before something burns."},
|
||||||
|
{"hw4_ex1_title", "Kvíz: Údržba", "Quiz: Maintenance"},
|
||||||
|
{"hw4_quiz1_head", "Kvíz k tématu Údržba", "Quiz on Maintenance"},
|
||||||
|
{"hw4_unit2", "TPM a Secure Boot", "TPM and Secure Boot"},
|
||||||
|
{"hw4_unit2_sub", "Železo, které hlídá start systému.", "Hardware that watches the system start."},
|
||||||
|
{"hw4_ex2_title", "Kvíz: TPM a Secure Boot", "Quiz: TPM and Secure Boot"},
|
||||||
|
{"hw4_quiz2_head", "Kvíz k tématu TPM a Secure Boot", "Quiz on TPM and Secure Boot"},
|
||||||
|
{"hw4_unit3", "Nákup sestavy", "Buying a computer"},
|
||||||
|
{"hw4_unit3_sub", "Nejdřív práce, potom krabice.", "The work comes first, then the box."},
|
||||||
|
{"hw4_ex3_title", "Kvíz: Nákup sestavy", "Quiz: Buying a computer"},
|
||||||
|
{"hw4_quiz3_head", "Kvíz k tématu Nákup sestavy", "Quiz on Buying a computer"},
|
||||||
|
{"hw4_unit4", "Mobilní zařízení", "Mobile devices"},
|
||||||
|
{"hw4_unit4_sub", "Tablet a telefon ve školní síti.", "Tablets and phones on the school network."},
|
||||||
|
{"hw4_ex4_title", "Kvíz: Mobilní zařízení", "Quiz: Mobile devices"},
|
||||||
|
{"hw4_quiz4_head", "Kvíz k tématu Mobilní zařízení", "Quiz on Mobile devices"},
|
||||||
|
{"hw4_unit5", "Periferie v provozu", "Peripherals in daily use"},
|
||||||
|
{"hw4_unit5_sub", "Tiskárny, projektory a co se na nich láme.", "Printers, projectors, and what actually breaks."},
|
||||||
|
{"hw4_ex5_title", "Kvíz: Periferie v provozu", "Quiz: Peripherals in daily use"},
|
||||||
|
{"hw4_quiz5_head", "Kvíz k tématu Periferie v provozu", "Quiz on Peripherals in daily use"},
|
||||||
|
{"hw4_unit6", "Ekologie a data", "Disposal and data"},
|
||||||
|
{"hw4_unit6_sub", "Vyhozený disk pořád obsahuje školu.", "A discarded drive still contains the school."},
|
||||||
|
{"hw4_ex6_title", "Kvíz: Ekologie a data", "Quiz: Disposal and data"},
|
||||||
|
{"hw4_quiz6_head", "Kvíz k tématu Ekologie a data", "Quiz on Disposal and data"},
|
||||||
|
{"hw4_unit7", "Metodika závady", "A fault method"},
|
||||||
|
{"hw4_unit7_sub", "Jedna věta, která drží celý postup.", "One sentence that holds the whole procedure."},
|
||||||
|
{"hw4_ex7_title", "Kvíz: Metodika závady", "Quiz: A fault method"},
|
||||||
|
{"hw4_quiz7_head", "Kvíz k tématu Metodika závady", "Quiz on A fault method"},
|
||||||
|
{"hw4_unit8", "Opakování k maturitě", "Exam review"},
|
||||||
|
{"hw4_unit8_sub", "Železo, které umíte pojmenovat a zdůvodnit.", "Hardware you can name and justify."},
|
||||||
|
{"hw4_ex8_title", "Kvíz: Opakování k maturitě", "Quiz: Exam review"},
|
||||||
|
{"hw4_quiz8_head", "Kvíz k tématu Opakování k maturitě", "Quiz on Exam review"},
|
||||||
|
|
@ -426,6 +426,8 @@ static gboolean run_smoke_test(gpointer data) {
|
||||||
"mluve1", "mluve20",
|
"mluve1", "mluve20",
|
||||||
"netunit1", "netex1", "netunit10", "netex10",
|
"netunit1", "netex1", "netunit10", "netex10",
|
||||||
"netunit27", "netex27",
|
"netunit27", "netex27",
|
||||||
|
"net2map", "net2unit1", "net2ex1", "net4unit8", "net4ex8",
|
||||||
|
"hw2map", "hw2unit1", "hw2ex1", "hw4unit8", "hw4ex8",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
const char *report = g_getenv("MATURITA_SMOKE");
|
const char *report = g_getenv("MATURITA_SMOKE");
|
||||||
|
|
@ -532,6 +534,7 @@ void activate(GtkApplication *app, gpointer user_data) {
|
||||||
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
|
gtk_stack_add_named(main_stack, build_netmap_page(), "netmap");
|
||||||
gtk_stack_add_named(main_stack, build_hwyears_page(), "hwyears");
|
gtk_stack_add_named(main_stack, build_hwyears_page(), "hwyears");
|
||||||
gtk_stack_add_named(main_stack, build_hwmap_page(), "hwmap");
|
gtk_stack_add_named(main_stack, build_hwmap_page(), "hwmap");
|
||||||
|
add_it_year_pages(main_stack);
|
||||||
gtk_stack_add_named(main_stack, build_onyears_page(), "onyears");
|
gtk_stack_add_named(main_stack, build_onyears_page(), "onyears");
|
||||||
gtk_stack_add_named(main_stack, build_onmap_page(), "onmap");
|
gtk_stack_add_named(main_stack, build_onmap_page(), "onmap");
|
||||||
add_on_pages(main_stack);
|
add_on_pages(main_stack);
|
||||||
|
|
|
||||||
17
src/net.c
17
src/net.c
|
|
@ -584,8 +584,21 @@ static GtkWidget *net_year_button(int year, gboolean locked) {
|
||||||
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
|
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
|
||||||
gtk_box_append(GTK_BOX(row), icon);
|
gtk_box_append(GTK_BOX(row), icon);
|
||||||
} else {
|
} else {
|
||||||
|
static const char *pages[] = {
|
||||||
|
"netmap", "net2map", "net3map", "net4map",
|
||||||
|
};
|
||||||
|
static const char *subs[] = {
|
||||||
|
"net_sub", "net2_sub", "net3_sub", "net4_sub",
|
||||||
|
};
|
||||||
|
GtkWidget *sub = gtk_label_new(NULL);
|
||||||
|
|
||||||
|
i18n_bind(sub, subs[year - 1], 0);
|
||||||
|
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||||
|
gtk_widget_add_css_class(sub, "quiz-intro");
|
||||||
|
gtk_box_append(GTK_BOX(texts), sub);
|
||||||
g_object_set_data_full(G_OBJECT(btn), "target",
|
g_object_set_data_full(G_OBJECT(btn), "target",
|
||||||
g_strdup("netmap"), g_free);
|
g_strdup(pages[year - 1]), g_free);
|
||||||
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,7 +636,7 @@ GtkWidget *build_netyears_page(void) {
|
||||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||||
|
|
||||||
for (y = 1; y <= 4; y++)
|
for (y = 1; y <= 4; y++)
|
||||||
gtk_box_append(GTK_BOX(list), net_year_button(y, y > 1));
|
gtk_box_append(GTK_BOX(list), net_year_button(y, FALSE));
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ void load_progress(void) {
|
||||||
on4_load_progress();
|
on4_load_progress();
|
||||||
en_load_progress();
|
en_load_progress();
|
||||||
de_load_progress();
|
de_load_progress();
|
||||||
|
quizyear_load_all();
|
||||||
lit_load_progress();
|
lit_load_progress();
|
||||||
sci_load_progress();
|
sci_load_progress();
|
||||||
fyz_load_progress();
|
fyz_load_progress();
|
||||||
|
|
@ -259,6 +260,7 @@ void progress_for_net(ProgressSum *out) {
|
||||||
out->done_units++;
|
out->done_units++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
progress_for_net_years(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hw_load_progress(void) {
|
void hw_load_progress(void) {
|
||||||
|
|
@ -350,6 +352,7 @@ void progress_for_hw(ProgressSum *out) {
|
||||||
out->done_units++;
|
out->done_units++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
progress_for_hw_years(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_load_progress(void) {
|
void on_load_progress(void) {
|
||||||
|
|
|
||||||
451
src/quizyear.c
Normal file
451
src/quizyear.c
Normal file
|
|
@ -0,0 +1,451 @@
|
||||||
|
#include "graduately.h"
|
||||||
|
|
||||||
|
/* Later years of networks and hardware. Year 1 keeps its own path.
|
||||||
|
* These years are a list of lessons, slides and a four-question quiz. */
|
||||||
|
|
||||||
|
#define QY_N 8
|
||||||
|
#define QY_COUNT 6
|
||||||
|
|
||||||
|
struct QuizYear {
|
||||||
|
const char *prefix;
|
||||||
|
const char *map_page;
|
||||||
|
const char *back_page;
|
||||||
|
const char *title_key;
|
||||||
|
const char *sub_key;
|
||||||
|
const char *file;
|
||||||
|
NetLesson lessons[QY_N];
|
||||||
|
char unit_page[QY_N][16];
|
||||||
|
char ex_page[QY_N][16];
|
||||||
|
char unit_key[QY_N][20];
|
||||||
|
char sub_keybuf[QY_N][24];
|
||||||
|
GtkWidget *nodes[QY_N];
|
||||||
|
};
|
||||||
|
|
||||||
|
static QuizYear years[QY_COUNT];
|
||||||
|
static gboolean qy_ready;
|
||||||
|
|
||||||
|
QuizYear *qy_net2;
|
||||||
|
QuizYear *qy_net3;
|
||||||
|
QuizYear *qy_net4;
|
||||||
|
QuizYear *qy_hw2;
|
||||||
|
QuizYear *qy_hw3;
|
||||||
|
QuizYear *qy_hw4;
|
||||||
|
|
||||||
|
static char map_store[QY_COUNT][16];
|
||||||
|
static char file_store[QY_COUNT][16];
|
||||||
|
|
||||||
|
void quizyear_boot(void) {
|
||||||
|
static const char *prefix[] = {
|
||||||
|
"net2", "net3", "net4", "hw2", "hw3", "hw4",
|
||||||
|
};
|
||||||
|
static const char *back[] = {
|
||||||
|
"netyears", "netyears", "netyears",
|
||||||
|
"hwyears", "hwyears", "hwyears",
|
||||||
|
};
|
||||||
|
static const char *title[] = {
|
||||||
|
"net_year2", "net_year3", "net_year4",
|
||||||
|
"hw_year2", "hw_year3", "hw_year4",
|
||||||
|
};
|
||||||
|
static const char *sub[] = {
|
||||||
|
"net2_sub", "net3_sub", "net4_sub",
|
||||||
|
"hw2_sub", "hw3_sub", "hw4_sub",
|
||||||
|
};
|
||||||
|
QuizYear **slot[] = {
|
||||||
|
&qy_net2, &qy_net3, &qy_net4, &qy_hw2, &qy_hw3, &qy_hw4,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (qy_ready)
|
||||||
|
return;
|
||||||
|
for (int y = 0; y < QY_COUNT; y++) {
|
||||||
|
QuizYear *Y = &years[y];
|
||||||
|
|
||||||
|
Y->prefix = prefix[y];
|
||||||
|
Y->back_page = back[y];
|
||||||
|
Y->title_key = title[y];
|
||||||
|
Y->sub_key = sub[y];
|
||||||
|
g_snprintf(map_store[y], sizeof map_store[y], "%smap", prefix[y]);
|
||||||
|
g_snprintf(file_store[y], sizeof file_store[y], "%s.conf", prefix[y]);
|
||||||
|
Y->map_page = map_store[y];
|
||||||
|
Y->file = file_store[y];
|
||||||
|
for (int i = 0; i < QY_N; i++) {
|
||||||
|
g_snprintf(Y->unit_page[i], sizeof Y->unit_page[i],
|
||||||
|
"%sunit%d", prefix[y], i + 1);
|
||||||
|
g_snprintf(Y->ex_page[i], sizeof Y->ex_page[i],
|
||||||
|
"%sex%d", prefix[y], i + 1);
|
||||||
|
g_snprintf(Y->unit_key[i], sizeof Y->unit_key[i],
|
||||||
|
"%s_unit%d", prefix[y], i + 1);
|
||||||
|
g_snprintf(Y->sub_keybuf[i], sizeof Y->sub_keybuf[i],
|
||||||
|
"%s_unit%d_sub", prefix[y], i + 1);
|
||||||
|
Y->lessons[i].unit_page = Y->unit_page[i];
|
||||||
|
Y->lessons[i].ex_page = Y->ex_page[i];
|
||||||
|
Y->lessons[i].n_slides = 3;
|
||||||
|
}
|
||||||
|
*slot[y] = Y;
|
||||||
|
}
|
||||||
|
qy_ready = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *qy_path(QuizYear *Y) {
|
||||||
|
return g_build_filename(app_progress_dir, Y->file, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_save(QuizYear *Y) {
|
||||||
|
GKeyFile *kf = g_key_file_new();
|
||||||
|
char *path = qy_path(Y);
|
||||||
|
gchar *data;
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < QY_N; i++) {
|
||||||
|
char key[8];
|
||||||
|
|
||||||
|
g_snprintf(key, sizeof key, "%d", i + 1);
|
||||||
|
g_key_file_set_boolean(kf, "done", key, Y->lessons[i].done);
|
||||||
|
}
|
||||||
|
data = g_key_file_to_data(kf, NULL, NULL);
|
||||||
|
if (!g_file_set_contents(path, data, -1, &err)) {
|
||||||
|
g_warning("quiz year: %s", err->message);
|
||||||
|
g_error_free(err);
|
||||||
|
}
|
||||||
|
g_free(data);
|
||||||
|
g_free(path);
|
||||||
|
g_key_file_free(kf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_load_one(QuizYear *Y) {
|
||||||
|
GKeyFile *kf = g_key_file_new();
|
||||||
|
char *path = qy_path(Y);
|
||||||
|
GError *err = NULL;
|
||||||
|
|
||||||
|
if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_NONE, &err)) {
|
||||||
|
if (err)
|
||||||
|
g_error_free(err);
|
||||||
|
g_key_file_free(kf);
|
||||||
|
g_free(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < QY_N; i++) {
|
||||||
|
char key[8];
|
||||||
|
|
||||||
|
g_snprintf(key, sizeof key, "%d", i + 1);
|
||||||
|
Y->lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
|
||||||
|
}
|
||||||
|
g_key_file_free(kf);
|
||||||
|
g_free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void quizyear_load_all(void) {
|
||||||
|
quizyear_boot();
|
||||||
|
for (int y = 0; y < QY_COUNT; y++)
|
||||||
|
qy_load_one(&years[y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_refresh_node(QuizYear *Y, int index) {
|
||||||
|
GtkWidget *node = Y->nodes[index];
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
if (Y->lessons[index].done) {
|
||||||
|
gtk_widget_remove_css_class(node, "current");
|
||||||
|
gtk_widget_add_css_class(node, "done");
|
||||||
|
} else {
|
||||||
|
gtk_widget_remove_css_class(node, "done");
|
||||||
|
gtk_widget_add_css_class(node, "current");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_mark(QuizYear *Y, int index) {
|
||||||
|
if (index < 0 || index >= QY_N || Y->lessons[index].done)
|
||||||
|
return;
|
||||||
|
Y->lessons[index].done = TRUE;
|
||||||
|
qy_save(Y);
|
||||||
|
qy_refresh_node(Y, index);
|
||||||
|
refresh_stats_ui();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_add(ProgressSum *out, int first, int n) {
|
||||||
|
for (int y = first; y < first + n; y++) {
|
||||||
|
out->total_ex += QY_N;
|
||||||
|
out->open_units += QY_N;
|
||||||
|
for (int i = 0; i < QY_N; i++) {
|
||||||
|
if (years[y].lessons[i].done) {
|
||||||
|
out->done_ex++;
|
||||||
|
out->done_units++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void progress_for_net_years(ProgressSum *out) {
|
||||||
|
quizyear_boot();
|
||||||
|
qy_add(out, 0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void progress_for_hw_years(ProgressSum *out) {
|
||||||
|
quizyear_boot();
|
||||||
|
qy_add(out, 3, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_slide_prev(GtkButton *button, gpointer data) {
|
||||||
|
NetLesson *L = data;
|
||||||
|
|
||||||
|
(void)button;
|
||||||
|
if (L && L->idx > 0) {
|
||||||
|
L->idx--;
|
||||||
|
skill_slide_apply(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qy_slide_next(GtkButton *button, gpointer data) {
|
||||||
|
NetLesson *L = data;
|
||||||
|
|
||||||
|
(void)button;
|
||||||
|
if (!L)
|
||||||
|
return;
|
||||||
|
if (L->idx + 1 < L->n_slides) {
|
||||||
|
L->idx++;
|
||||||
|
skill_slide_apply(L);
|
||||||
|
} else if (L->ex_page) {
|
||||||
|
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void quizyear_apply_lang(void) {
|
||||||
|
if (!qy_ready)
|
||||||
|
return;
|
||||||
|
for (int y = 0; y < QY_COUNT; y++)
|
||||||
|
for (int i = 0; i < QY_N; i++)
|
||||||
|
skill_slide_apply(&years[y].lessons[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *quizyear_unit(QuizYear *Y, int index, const char *title_key,
|
||||||
|
const char *sub_key, const NetSlide *slides,
|
||||||
|
int n_slides) {
|
||||||
|
NetLesson *L = &Y->lessons[index];
|
||||||
|
GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
GtkWidget *scroll = gtk_scrolled_window_new();
|
||||||
|
GtkWidget *nav;
|
||||||
|
|
||||||
|
net_lesson_notes_ensure(L);
|
||||||
|
net_notes_target = L;
|
||||||
|
L->n_slides = (guint)n_slides;
|
||||||
|
gtk_widget_set_margin_start(page, 28);
|
||||||
|
gtk_widget_set_margin_end(page, 28);
|
||||||
|
gtk_widget_set_margin_top(page, 20);
|
||||||
|
gtk_widget_set_margin_bottom(page, 20);
|
||||||
|
gtk_box_append(GTK_BOX(page), top_bar(Y->map_page, title_key, sub_key));
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_widget_set_margin_top(scroll, 10);
|
||||||
|
gtk_box_append(GTK_BOX(page), scroll);
|
||||||
|
L->stack = gtk_stack_new();
|
||||||
|
gtk_stack_set_transition_type(GTK_STACK(L->stack),
|
||||||
|
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), L->stack);
|
||||||
|
for (int s = 0; s < n_slides; s++) {
|
||||||
|
GtkWidget *card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||||
|
char name[16];
|
||||||
|
|
||||||
|
g_snprintf(name, sizeof name, "slide%d", s);
|
||||||
|
gtk_widget_set_margin_top(card, 8);
|
||||||
|
gtk_widget_set_margin_bottom(card, 8);
|
||||||
|
net_note_kicker(card, slides[s].kicker);
|
||||||
|
net_note_title(card, slides[s].title);
|
||||||
|
for (int i = 0; i < 8 && slides[s].line[i]; i++)
|
||||||
|
net_note_line(card, slides[s].line[i], FALSE);
|
||||||
|
if (slides[s].tip)
|
||||||
|
net_note_line(card, slides[s].tip, TRUE);
|
||||||
|
gtk_stack_add_named(GTK_STACK(L->stack), card, name);
|
||||||
|
}
|
||||||
|
nav = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||||
|
gtk_box_set_homogeneous(GTK_BOX(nav), TRUE);
|
||||||
|
gtk_widget_set_margin_top(nav, 10);
|
||||||
|
gtk_box_append(GTK_BOX(page), nav);
|
||||||
|
L->prev_btn = gtk_button_new_with_label(tr("net_slide_prev"));
|
||||||
|
gtk_widget_add_css_class(L->prev_btn, "btn-primary");
|
||||||
|
gtk_box_append(GTK_BOX(nav), L->prev_btn);
|
||||||
|
g_signal_connect(L->prev_btn, "clicked", G_CALLBACK(qy_slide_prev), L);
|
||||||
|
L->next_btn = gtk_button_new_with_label(tr("net_slide_next"));
|
||||||
|
gtk_widget_add_css_class(L->next_btn, "btn-primary");
|
||||||
|
gtk_box_append(GTK_BOX(nav), L->next_btn);
|
||||||
|
g_signal_connect(L->next_btn, "clicked", G_CALLBACK(qy_slide_next), L);
|
||||||
|
net_rescale_lesson_notes(L, 18, 32, 15);
|
||||||
|
net_notes_target = NULL;
|
||||||
|
L->idx = 0;
|
||||||
|
skill_slide_apply(L);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
QuizYear *year;
|
||||||
|
int index;
|
||||||
|
const ChoiceQ *qs;
|
||||||
|
int n;
|
||||||
|
int n_opts;
|
||||||
|
GtkWidget *feedback;
|
||||||
|
GtkToggleButton **toggles;
|
||||||
|
GtkWidget **hints;
|
||||||
|
} QyQuiz;
|
||||||
|
|
||||||
|
static void qy_check(GtkButton *button, gpointer data) {
|
||||||
|
QyQuiz *ctx = data;
|
||||||
|
int ok = 0;
|
||||||
|
|
||||||
|
(void)button;
|
||||||
|
for (int i = 0; i < ctx->n; i++) {
|
||||||
|
int active = -1;
|
||||||
|
|
||||||
|
for (int o = 0; o < ctx->n_opts; o++) {
|
||||||
|
GtkToggleButton *tb = ctx->toggles[i * ctx->n_opts + o];
|
||||||
|
|
||||||
|
gtk_widget_remove_css_class(GTK_WIDGET(tb), "ok");
|
||||||
|
gtk_widget_remove_css_class(GTK_WIDGET(tb), "wrong");
|
||||||
|
if (gtk_toggle_button_get_active(tb))
|
||||||
|
active = o;
|
||||||
|
}
|
||||||
|
if (active == ctx->qs[i].correct) {
|
||||||
|
ok++;
|
||||||
|
if (active >= 0)
|
||||||
|
gtk_widget_add_css_class(
|
||||||
|
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "ok");
|
||||||
|
} else if (active >= 0) {
|
||||||
|
gtk_widget_add_css_class(
|
||||||
|
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "wrong");
|
||||||
|
}
|
||||||
|
if (ctx->hints && ctx->hints[i])
|
||||||
|
gtk_widget_set_visible(ctx->hints[i], TRUE);
|
||||||
|
}
|
||||||
|
if (ok == ctx->n) {
|
||||||
|
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
|
||||||
|
qy_mark(ctx->year, ctx->index);
|
||||||
|
} else {
|
||||||
|
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *quizyear_quiz(QuizYear *Y, int index, const char *title_key,
|
||||||
|
const char *head_key, const ChoiceQ *qs,
|
||||||
|
const char **hints, int n) {
|
||||||
|
QyQuiz *ctx = g_new0(QyQuiz, 1);
|
||||||
|
GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
GtkWidget *scroll = gtk_scrolled_window_new();
|
||||||
|
GtkWidget *body = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14);
|
||||||
|
GtkWidget *head = gtk_label_new(NULL);
|
||||||
|
GtkWidget *intro = gtk_label_new(NULL);
|
||||||
|
GtkWidget *check;
|
||||||
|
int n_opts = qs[0].n_options;
|
||||||
|
|
||||||
|
gtk_widget_set_margin_start(page, 32);
|
||||||
|
gtk_widget_set_margin_end(page, 32);
|
||||||
|
gtk_widget_set_margin_top(page, 24);
|
||||||
|
gtk_widget_set_margin_bottom(page, 24);
|
||||||
|
gtk_box_append(GTK_BOX(page),
|
||||||
|
top_bar(Y->lessons[index].unit_page, title_key, NULL));
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_widget_set_margin_top(scroll, 12);
|
||||||
|
gtk_box_append(GTK_BOX(page), scroll);
|
||||||
|
gtk_widget_set_hexpand(body, TRUE);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
|
||||||
|
i18n_bind(head, head_key, 0);
|
||||||
|
gtk_widget_set_halign(head, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(head), TRUE);
|
||||||
|
gtk_widget_add_css_class(head, "quiz-heading");
|
||||||
|
gtk_box_append(GTK_BOX(body), head);
|
||||||
|
i18n_bind(intro, "net_quiz_intro", 0);
|
||||||
|
gtk_widget_set_halign(intro, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(intro), TRUE);
|
||||||
|
gtk_widget_add_css_class(intro, "quiz-intro");
|
||||||
|
gtk_box_append(GTK_BOX(body), intro);
|
||||||
|
|
||||||
|
ctx->year = Y;
|
||||||
|
ctx->index = index;
|
||||||
|
ctx->qs = qs;
|
||||||
|
ctx->n = n;
|
||||||
|
ctx->n_opts = n_opts;
|
||||||
|
ctx->toggles = g_new0(GtkToggleButton *, n * n_opts);
|
||||||
|
ctx->hints = g_new0(GtkWidget *, n);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
GtkWidget *card = mcq_append_question(body, i + 1, &qs[i],
|
||||||
|
&ctx->toggles[i * n_opts]);
|
||||||
|
|
||||||
|
ctx->hints[i] = meaning_add(card, hints ? hints[i] : NULL);
|
||||||
|
}
|
||||||
|
check = gtk_button_new();
|
||||||
|
i18n_bind(check, "check", 1);
|
||||||
|
gtk_widget_add_css_class(check, "btn-primary");
|
||||||
|
gtk_widget_set_halign(check, GTK_ALIGN_START);
|
||||||
|
gtk_widget_set_margin_top(check, 8);
|
||||||
|
gtk_box_append(GTK_BOX(body), check);
|
||||||
|
g_signal_connect(check, "clicked", G_CALLBACK(qy_check), ctx);
|
||||||
|
ctx->feedback = gtk_label_new("");
|
||||||
|
gtk_widget_set_halign(ctx->feedback, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(ctx->feedback), TRUE);
|
||||||
|
gtk_box_append(GTK_BOX(body), ctx->feedback);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *quizyear_map(QuizYear *Y) {
|
||||||
|
GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
GtkWidget *scroll = gtk_scrolled_window_new();
|
||||||
|
GtkWidget *list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||||
|
|
||||||
|
gtk_widget_set_hexpand(page, TRUE);
|
||||||
|
gtk_widget_set_vexpand(page, TRUE);
|
||||||
|
gtk_widget_set_margin_start(page, 32);
|
||||||
|
gtk_widget_set_margin_end(page, 32);
|
||||||
|
gtk_widget_set_margin_top(page, 24);
|
||||||
|
gtk_widget_set_margin_bottom(page, 24);
|
||||||
|
gtk_box_append(GTK_BOX(page), top_bar(Y->back_page, Y->title_key, Y->sub_key));
|
||||||
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
|
||||||
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_widget_set_margin_top(scroll, 18);
|
||||||
|
gtk_box_append(GTK_BOX(page), scroll);
|
||||||
|
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_size_request(list, 520, -1);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
|
||||||
|
for (int i = 0; i < QY_N; i++) {
|
||||||
|
GtkWidget *btn = gtk_button_new();
|
||||||
|
GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
|
||||||
|
GtkWidget *texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||||
|
GtkWidget *title = gtk_label_new(NULL);
|
||||||
|
GtkWidget *sub = gtk_label_new(NULL);
|
||||||
|
char badge[8];
|
||||||
|
GtkWidget *num;
|
||||||
|
|
||||||
|
g_snprintf(badge, sizeof badge, "%d", i + 1);
|
||||||
|
num = gtk_label_new(badge);
|
||||||
|
gtk_widget_add_css_class(btn, "unit-node");
|
||||||
|
gtk_widget_add_css_class(btn, "current");
|
||||||
|
gtk_widget_set_hexpand(btn, TRUE);
|
||||||
|
gtk_widget_set_margin_start(row, 18);
|
||||||
|
gtk_widget_set_margin_end(row, 18);
|
||||||
|
gtk_widget_set_margin_top(row, 10);
|
||||||
|
gtk_widget_set_margin_bottom(row, 10);
|
||||||
|
gtk_button_set_child(GTK_BUTTON(btn), row);
|
||||||
|
gtk_widget_add_css_class(num, "unit-number");
|
||||||
|
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
|
||||||
|
gtk_box_append(GTK_BOX(row), num);
|
||||||
|
gtk_widget_set_hexpand(texts, TRUE);
|
||||||
|
gtk_box_append(GTK_BOX(row), texts);
|
||||||
|
i18n_bind(title, Y->unit_key[i], 0);
|
||||||
|
gtk_widget_set_halign(title, GTK_ALIGN_START);
|
||||||
|
gtk_widget_add_css_class(title, "unit-name");
|
||||||
|
gtk_box_append(GTK_BOX(texts), title);
|
||||||
|
i18n_bind(sub, Y->sub_keybuf[i], 0);
|
||||||
|
gtk_widget_set_halign(sub, GTK_ALIGN_START);
|
||||||
|
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
|
||||||
|
gtk_label_set_xalign(GTK_LABEL(sub), 0);
|
||||||
|
gtk_widget_add_css_class(sub, "quiz-intro");
|
||||||
|
gtk_box_append(GTK_BOX(texts), sub);
|
||||||
|
g_object_set_data_full(G_OBJECT(btn), "target",
|
||||||
|
g_strdup(Y->unit_page[i]), g_free);
|
||||||
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
|
||||||
|
Y->nodes[i] = btn;
|
||||||
|
qy_refresh_node(Y, i);
|
||||||
|
gtk_box_append(GTK_BOX(list), btn);
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
26
src/search.c
26
src/search.c
|
|
@ -188,9 +188,9 @@ static void catalog_rebuild(void) {
|
||||||
catalog_add_key("net_years_title", "search_subject", "netyears", FALSE, 0,
|
catalog_add_key("net_years_title", "search_subject", "netyears", FALSE, 0,
|
||||||
"site network ip");
|
"site network ip");
|
||||||
catalog_add_key("net_year1", "search_lesson", "netmap", FALSE, 1, "rocnik");
|
catalog_add_key("net_year1", "search_lesson", "netmap", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("net_year2", "search_lesson", "netyears", TRUE, 1, "rocnik");
|
catalog_add_key("net_year2", "search_lesson", "net2map", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("net_year3", "search_lesson", "netyears", TRUE, 1, "rocnik");
|
catalog_add_key("net_year3", "search_lesson", "net3map", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("net_year4", "search_lesson", "netyears", TRUE, 1, "rocnik");
|
catalog_add_key("net_year4", "search_lesson", "net4map", FALSE, 1, "rocnik");
|
||||||
for (i = 1; i <= NET_LESSONS; i++) {
|
for (i = 1; i <= NET_LESSONS; i++) {
|
||||||
g_snprintf(key, sizeof(key), "net_unit%d", i);
|
g_snprintf(key, sizeof(key), "net_unit%d", i);
|
||||||
g_snprintf(page, sizeof(page), "netunit%d", i);
|
g_snprintf(page, sizeof(page), "netunit%d", i);
|
||||||
|
|
@ -198,11 +198,18 @@ static void catalog_rebuild(void) {
|
||||||
g_snprintf(page, sizeof(page), "netex%d", i);
|
g_snprintf(page, sizeof(page), "netex%d", i);
|
||||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
|
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
|
||||||
}
|
}
|
||||||
|
for (i = 2; i <= 4; i++) {
|
||||||
|
for (n = 1; n <= 8; n++) {
|
||||||
|
g_snprintf(key, sizeof key, "net%d_unit%d", i, n);
|
||||||
|
g_snprintf(page, sizeof page, "net%dunit%d", i, n);
|
||||||
|
catalog_add_key(key, "search_lesson", page, FALSE, 2, "site sit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
catalog_add_key("hw_year1", "search_lesson", "hwmap", FALSE, 1, "rocnik");
|
catalog_add_key("hw_year1", "search_lesson", "hwmap", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("hw_year2", "search_lesson", "hwyears", TRUE, 1, "rocnik");
|
catalog_add_key("hw_year2", "search_lesson", "hw2map", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("hw_year3", "search_lesson", "hwyears", TRUE, 1, "rocnik");
|
catalog_add_key("hw_year3", "search_lesson", "hw3map", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("hw_year4", "search_lesson", "hwyears", TRUE, 1, "rocnik");
|
catalog_add_key("hw_year4", "search_lesson", "hw4map", FALSE, 1, "rocnik");
|
||||||
for (i = 1; i <= HW_LESSONS; i++) {
|
for (i = 1; i <= HW_LESSONS; i++) {
|
||||||
g_snprintf(key, sizeof(key), "hw_unit%d", i);
|
g_snprintf(key, sizeof(key), "hw_unit%d", i);
|
||||||
g_snprintf(page, sizeof(page), "hwunit%d", i);
|
g_snprintf(page, sizeof(page), "hwunit%d", i);
|
||||||
|
|
@ -210,6 +217,13 @@ static void catalog_rebuild(void) {
|
||||||
g_snprintf(page, sizeof(page), "hwex%d", i);
|
g_snprintf(page, sizeof(page), "hwex%d", i);
|
||||||
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
|
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
|
||||||
}
|
}
|
||||||
|
for (i = 2; i <= 4; i++) {
|
||||||
|
for (n = 1; n <= 8; n++) {
|
||||||
|
g_snprintf(key, sizeof key, "hw%d_unit%d", i, n);
|
||||||
|
g_snprintf(page, sizeof page, "hw%dunit%d", i, n);
|
||||||
|
catalog_add_key(key, "search_lesson", page, FALSE, 2, "hardware hw");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
catalog_add_key("on_year1", "search_lesson", "onmap", FALSE, 1, "rocnik");
|
catalog_add_key("on_year1", "search_lesson", "onmap", FALSE, 1, "rocnik");
|
||||||
catalog_add_key("on_year2", "search_lesson", "on2map", FALSE, 1, "rocnik");
|
catalog_add_key("on_year2", "search_lesson", "on2map", FALSE, 1, "rocnik");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue