Open presentation graphics as four years from Photoshop to the exam campaign.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-11 18:12:17 +02:00
commit ec7aa63159
84 changed files with 3328 additions and 4 deletions

View file

@ -249,6 +249,26 @@ final class AppModel: ObservableObject {
refresh()
}
func markPg(_ id: Int) {
progress.markPg(id)
refresh()
}
func markPg2(_ id: Int) {
progress.markPg2(id)
refresh()
}
func markPg3(_ id: Int) {
progress.markPg3(id)
refresh()
}
func markPg4(_ id: Int) {
progress.markPg4(id)
refresh()
}
func markTg4(_ id: Int) {
progress.markTg4(id)
refresh()

View file

@ -63,6 +63,10 @@ final class Content {
let tg2: [J]
let tg3: [J]
let tg4: [J]
let pg: [J]
let pg2: [J]
let pg3: [J]
let pg4: [J]
let cs: [J]
let cs2: [J]
let cs3: [J]
@ -137,6 +141,10 @@ final class Content {
tg2 = raw.arr("tg2")
tg3 = raw.arr("tg3")
tg4 = raw.arr("tg4")
pg = raw.arr("pg")
pg2 = raw.arr("pg2")
pg3 = raw.arr("pg3")
pg4 = raw.arr("pg4")
cs = raw.arr("cs")
cs2 = raw.arr("cs2")
cs3 = raw.arr("cs3")
@ -207,6 +215,10 @@ final class Content {
func tg2Lesson(_ id: Int) -> J? { tg2.first { $0.int("id") == id } }
func tg3Lesson(_ id: Int) -> J? { tg3.first { $0.int("id") == id } }
func tg4Lesson(_ id: Int) -> J? { tg4.first { $0.int("id") == id } }
func pgLesson(_ id: Int) -> J? { pg.first { $0.int("id") == id } }
func pg2Lesson(_ id: Int) -> J? { pg2.first { $0.int("id") == id } }
func pg3Lesson(_ id: Int) -> J? { pg3.first { $0.int("id") == id } }
func pg4Lesson(_ id: Int) -> J? { pg4.first { $0.int("id") == id } }
func csLesson(_ id: Int) -> J? { cs.first { $0.int("id") == id } }
func cs2Lesson(_ id: Int) -> J? { cs2.first { $0.int("id") == id } }
func cs3Lesson(_ id: Int) -> J? { cs3.first { $0.int("id") == id } }

View file

@ -102,6 +102,14 @@ final class ProgressStore {
func markTg3(_ id: Int) { mark("tg3.\(id)") }
func tg4Done(_ id: Int) -> Bool { done("tg4.\(id)") }
func markTg4(_ id: Int) { mark("tg4.\(id)") }
func pgDone(_ id: Int) -> Bool { done("pg.\(id)") }
func markPg(_ id: Int) { mark("pg.\(id)") }
func pg2Done(_ id: Int) -> Bool { done("pg2.\(id)") }
func markPg2(_ id: Int) { mark("pg2.\(id)") }
func pg3Done(_ id: Int) -> Bool { done("pg3.\(id)") }
func markPg3(_ id: Int) { mark("pg3.\(id)") }
func pg4Done(_ id: Int) -> Bool { done("pg4.\(id)") }
func markPg4(_ id: Int) { mark("pg4.\(id)") }
func csDone(_ id: Int) -> Bool { done("cs.\(id)") }
func markCs(_ id: Int) { mark("cs.\(id)") }
func cs2Done(_ id: Int) -> Bool { done("cs2.\(id)") }
@ -336,6 +344,38 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
sum.doneUnits += 1
}
}
for l in content.pg {
sum.totalEx += 1
sum.openUnits += 1
if p.pgDone(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.pg2 {
sum.totalEx += 1
sum.openUnits += 1
if p.pg2Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.pg3 {
sum.totalEx += 1
sum.openUnits += 1
if p.pg3Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.pg4 {
sum.totalEx += 1
sum.openUnits += 1
if p.pg4Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.cs {
sum.totalEx += 1
sum.openUnits += 1

View file

@ -124,6 +124,19 @@ enum Route: Hashable {
case tg4Map
case tg4Lesson(Int)
case tg4Ex(Int)
case pgYears
case pgMap
case pgLesson(Int)
case pgEx(Int)
case pg2Map
case pg2Lesson(Int)
case pg2Ex(Int)
case pg3Map
case pg3Lesson(Int)
case pg3Ex(Int)
case pg4Map
case pg4Lesson(Int)
case pg4Ex(Int)
case csYears
case csMap
case csLesson(Int)
@ -259,6 +272,19 @@ enum Route: Hashable {
case .tg4Map: return "tg4map"
case .tg4Lesson(let id): return "tg4unit\(id)"
case .tg4Ex(let id): return "tg4ex\(id)"
case .pgYears: return "pgyears"
case .pgMap: return "pgmap"
case .pgLesson(let id): return "pgunit\(id)"
case .pgEx(let id): return "pgex\(id)"
case .pg2Map: return "pg2map"
case .pg2Lesson(let id): return "pg2unit\(id)"
case .pg2Ex(let id): return "pg2ex\(id)"
case .pg3Map: return "pg3map"
case .pg3Lesson(let id): return "pg3unit\(id)"
case .pg3Ex(let id): return "pg3ex\(id)"
case .pg4Map: return "pg4map"
case .pg4Lesson(let id): return "pg4unit\(id)"
case .pg4Ex(let id): return "pg4ex\(id)"
case .csYears: return "csyears"
case .csMap: return "csmap"
case .csLesson(let id): return "csunit\(id)"
@ -518,6 +544,32 @@ enum Route: Hashable {
return [.tgYears, .tg4Map, .tg4Lesson(id)]
case .tg4Ex(let id):
return [.tgYears, .tg4Map, .tg4Lesson(id), .tg4Ex(id)]
case .pgYears:
return [.pgYears]
case .pgMap:
return [.pgYears, .pgMap]
case .pgLesson(let id):
return [.pgYears, .pgMap, .pgLesson(id)]
case .pgEx(let id):
return [.pgYears, .pgMap, .pgLesson(id), .pgEx(id)]
case .pg2Map:
return [.pgYears, .pg2Map]
case .pg2Lesson(let id):
return [.pgYears, .pg2Map, .pg2Lesson(id)]
case .pg2Ex(let id):
return [.pgYears, .pg2Map, .pg2Lesson(id), .pg2Ex(id)]
case .pg3Map:
return [.pgYears, .pg3Map]
case .pg3Lesson(let id):
return [.pgYears, .pg3Map, .pg3Lesson(id)]
case .pg3Ex(let id):
return [.pgYears, .pg3Map, .pg3Lesson(id), .pg3Ex(id)]
case .pg4Map:
return [.pgYears, .pg4Map]
case .pg4Lesson(let id):
return [.pgYears, .pg4Map, .pg4Lesson(id)]
case .pg4Ex(let id):
return [.pgYears, .pg4Map, .pg4Lesson(id), .pg4Ex(id)]
case .csYears:
return [.csYears]
case .csMap:
@ -601,6 +653,11 @@ func routeFromPage(_ page: String) -> Route? {
case "tg2map": return .tg2Map
case "tg3map": return .tg3Map
case "tg4map": return .tg4Map
case "pgyears": return .pgYears
case "pgmap": return .pgMap
case "pg2map": return .pg2Map
case "pg3map": return .pg3Map
case "pg4map": return .pg4Map
case "csyears": return .csYears
case "csmap": return .csMap
case "cs2map": return .cs2Map
@ -715,6 +772,30 @@ func routeFromPage(_ page: String) -> Route? {
if let m = page.wholeMatch(of: /fyzex(\d+)/), let n = Int(m.1) {
return .fyzEx(n)
}
if let m = page.wholeMatch(of: /pg4unit(\d+)/), let n = Int(m.1) {
return .pg4Lesson(n)
}
if let m = page.wholeMatch(of: /pg4ex(\d+)/), let n = Int(m.1) {
return .pg4Ex(n)
}
if let m = page.wholeMatch(of: /pg3unit(\d+)/), let n = Int(m.1) {
return .pg3Lesson(n)
}
if let m = page.wholeMatch(of: /pg3ex(\d+)/), let n = Int(m.1) {
return .pg3Ex(n)
}
if let m = page.wholeMatch(of: /pg2unit(\d+)/), let n = Int(m.1) {
return .pg2Lesson(n)
}
if let m = page.wholeMatch(of: /pg2ex(\d+)/), let n = Int(m.1) {
return .pg2Ex(n)
}
if let m = page.wholeMatch(of: /pgunit(\d+)/), let n = Int(m.1) {
return .pgLesson(n)
}
if let m = page.wholeMatch(of: /pgex(\d+)/), let n = Int(m.1) {
return .pgEx(n)
}
if let m = page.wholeMatch(of: /tg4unit(\d+)/), let n = Int(m.1) {
return .tg4Lesson(n)
}

View file

@ -711,6 +711,7 @@ func subjectSymbol(_ icon: String) -> String {
case "function": return "function"
case "ruler": return "ruler.fill"
case "code": return "chevron.left.forwardslash.chevron.right"
case "brush": return "paintbrush.fill"
default: return "lock.fill"
}
}

View file

@ -985,6 +985,39 @@ struct TgQuizScreen: View {
}
}
struct PgQuizScreen: View {
@ObservedObject var vm: AppModel
let year: Int
let id: Int
var body: some View {
let lesson: J? = {
switch year {
case 2: return vm.content.pg2Lesson(id)
case 3: return vm.content.pg3Lesson(id)
case 4: return vm.content.pg4Lesson(id)
default: return vm.content.pgLesson(id)
}
}()
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"))) {
switch year {
case 2: vm.markPg2(id)
case 3: vm.markPg3(id)
case 4: vm.markPg4(id)
default: vm.markPg(id)
}
}
}
}
}
struct FyzQuizScreen: View {
@ObservedObject var vm: AppModel
let year: Int

View file

@ -747,6 +747,10 @@ func routeProgress(_ vm: AppModel, _ r: Route) -> (done: Int, total: Int)? {
case .tg2Map: return tally(c.tg2, p.tg2Done)
case .tg3Map: return tally(c.tg3, p.tg3Done)
case .tg4Map: return tally(c.tg4, p.tg4Done)
case .pgMap: return tally(c.pg, p.pgDone)
case .pg2Map: return tally(c.pg2, p.pg2Done)
case .pg3Map: return tally(c.pg3, p.pg3Done)
case .pg4Map: return tally(c.pg4, p.pg4Done)
case .csMap: return tally(c.cs, p.csDone)
case .cs2Map: return tally(c.cs2, p.cs2Done)
case .cs3Map: return tally(c.cs3, p.cs3Done)
@ -810,6 +814,12 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
+ vm.content.cs2.filter { vm.progress.cs2Done($0.int("id")) }.count
+ vm.content.cs3.filter { vm.progress.cs3Done($0.int("id")) }.count
+ vm.content.cs4.filter { vm.progress.cs4Done($0.int("id")) }.count
case "pgyears", "pgmap", "pg2map", "pg3map", "pg4map":
sum.totalEx = vm.content.pg.count + vm.content.pg2.count + vm.content.pg3.count + vm.content.pg4.count
sum.doneEx = vm.content.pg.filter { vm.progress.pgDone($0.int("id")) }.count
+ vm.content.pg2.filter { vm.progress.pg2Done($0.int("id")) }.count
+ vm.content.pg3.filter { vm.progress.pg3Done($0.int("id")) }.count
+ vm.content.pg4.filter { vm.progress.pg4Done($0.int("id")) }.count
case "tgyears", "tgmap", "tg2map", "tg3map", "tg4map":
sum.totalEx = vm.content.tg.count + vm.content.tg2.count + vm.content.tg3.count + vm.content.tg4.count
sum.doneEx = vm.content.tg.filter { vm.progress.tgDone($0.int("id")) }.count
@ -1119,6 +1129,27 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz4unit\(l.int("id"))", extra: "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz4ex\(l.int("id"))")
}
add(vm.tr("Prezentační grafika"), vm.tr("search_lesson"), "pgyears", extra: "photoshop premiere illustrator")
add(vm.tr("pg_year1"), vm.tr("search_lesson"), "pgmap", extra: "rocnik")
add(vm.tr("pg_year2"), vm.tr("search_lesson"), "pg2map", extra: "rocnik")
add(vm.tr("pg_year3"), vm.tr("search_lesson"), "pg3map", extra: "rocnik")
add(vm.tr("pg_year4"), vm.tr("search_lesson"), "pg4map", extra: "rocnik")
for l in vm.content.pg {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "pgunit\(l.int("id"))", extra: "photoshop")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "pgex\(l.int("id"))")
}
for l in vm.content.pg2 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "pg2unit\(l.int("id"))", extra: "photoshop")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "pg2ex\(l.int("id"))")
}
for l in vm.content.pg3 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "pg3unit\(l.int("id"))", extra: "premiere")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "pg3ex\(l.int("id"))")
}
for l in vm.content.pg4 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "pg4unit\(l.int("id"))", extra: "illustrator")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "pg4ex\(l.int("id"))")
}
add(vm.tr("Technická grafika"), vm.tr("search_lesson"), "tgyears", extra: "autocad grafika")
add(vm.tr("tg_year1"), vm.tr("search_lesson"), "tgmap", extra: "rocnik")
add(vm.tr("tg_year2"), vm.tr("search_lesson"), "tg2map", extra: "rocnik")
@ -1945,6 +1976,47 @@ struct CsYearsScreen: View {
}
}
struct PgYearsScreen: View {
@ObservedObject var vm: AppModel
var body: some View {
#if os(macOS)
let years: [(String, String, Route)] = [
("pg_year1", "pg_sub", .pgMap),
("pg_year2", "pg2_sub", .pg2Map),
("pg_year3", "pg3_sub", .pg3Map),
("pg_year4", "pg4_sub", .pg4Map),
]
MacFillGrid(count: years.count, minWidth: 260, minHeight: 200) { i in
NavigationLink(value: years[i].2) {
MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).", progress: routeProgress(vm, years[i].2))
}
.buttonStyle(PressScaleStyle())
}
.navigationTitle(vm.tr("Prezentační grafika"))
.navigationSubtitle(vm.tr("pg_years_sub"))
#else
List {
NavigationLink(value: Route.pgMap) {
ListRowLabel(title: vm.tr("pg_year1"), subtitle: vm.tr("pg_sub"), progress: routeProgress(vm, Route.pgMap))
}
NavigationLink(value: Route.pg2Map) {
ListRowLabel(title: vm.tr("pg_year2"), subtitle: vm.tr("pg2_sub"), progress: routeProgress(vm, Route.pg2Map))
}
NavigationLink(value: Route.pg3Map) {
ListRowLabel(title: vm.tr("pg_year3"), subtitle: vm.tr("pg3_sub"), progress: routeProgress(vm, Route.pg3Map))
}
NavigationLink(value: Route.pg4Map) {
ListRowLabel(title: vm.tr("pg_year4"), subtitle: vm.tr("pg4_sub"), progress: routeProgress(vm, Route.pg4Map))
}
}
.navigationTitle(vm.tr("Prezentační grafika"))
.navigationSubtitle(vm.tr("pg_years_sub"))
.appListStyle()
#endif
}
}
struct TgYearsScreen: View {
@ObservedObject var vm: AppModel
@ -3103,6 +3175,72 @@ struct RouteDestination: View {
if let l = vm.content.cs4Lesson(id) {
CodeLessonScreen(vm: vm, lesson: l, onSuccess: { vm.markCs4(id) })
}
case .pgYears:
PgYearsScreen(vm: vm)
case .pgMap:
LessonListScreen(
vm: vm,
title: vm.tr("pg_year1"),
subtitle: vm.tr("pg_sub"),
rows: vm.content.pg.map { l in
let id = l.int("id")
return LessonRow(id: "p\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.pgDone(id), destination: .pgLesson(id))
}
)
case .pgLesson(let id):
if let l = vm.content.pgLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .pgEx(id))
}
case .pgEx(let id):
PgQuizScreen(vm: vm, year: 1, id: id)
case .pg2Map:
LessonListScreen(
vm: vm,
title: vm.tr("pg_year2"),
subtitle: vm.tr("pg2_sub"),
rows: vm.content.pg2.map { l in
let id = l.int("id")
return LessonRow(id: "p2\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.pg2Done(id), destination: .pg2Lesson(id))
}
)
case .pg2Lesson(let id):
if let l = vm.content.pg2Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .pg2Ex(id))
}
case .pg2Ex(let id):
PgQuizScreen(vm: vm, year: 2, id: id)
case .pg3Map:
LessonListScreen(
vm: vm,
title: vm.tr("pg_year3"),
subtitle: vm.tr("pg3_sub"),
rows: vm.content.pg3.map { l in
let id = l.int("id")
return LessonRow(id: "p3\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.pg3Done(id), destination: .pg3Lesson(id))
}
)
case .pg3Lesson(let id):
if let l = vm.content.pg3Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .pg3Ex(id))
}
case .pg3Ex(let id):
PgQuizScreen(vm: vm, year: 3, id: id)
case .pg4Map:
LessonListScreen(
vm: vm,
title: vm.tr("pg_year4"),
subtitle: vm.tr("pg4_sub"),
rows: vm.content.pg4.map { l in
let id = l.int("id")
return LessonRow(id: "p4\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.pg4Done(id), destination: .pg4Lesson(id))
}
)
case .pg4Lesson(let id):
if let l = vm.content.pg4Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .pg4Ex(id))
}
case .pg4Ex(let id):
PgQuizScreen(vm: vm, year: 4, id: id)
case .tgYears:
TgYearsScreen(vm: vm)
case .tgMap: