Open software as four years from Windows and Office to policy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-11 19:25:48 +02:00
commit 264f4a73d8
84 changed files with 3327 additions and 4 deletions

View file

@ -269,6 +269,26 @@ final class AppModel: ObservableObject {
refresh()
}
func markSw(_ id: Int) {
progress.markSw(id)
refresh()
}
func markSw2(_ id: Int) {
progress.markSw2(id)
refresh()
}
func markSw3(_ id: Int) {
progress.markSw3(id)
refresh()
}
func markSw4(_ id: Int) {
progress.markSw4(id)
refresh()
}
func markTg4(_ id: Int) {
progress.markTg4(id)
refresh()

View file

@ -67,6 +67,10 @@ final class Content {
let pg2: [J]
let pg3: [J]
let pg4: [J]
let sw: [J]
let sw2: [J]
let sw3: [J]
let sw4: [J]
let cs: [J]
let cs2: [J]
let cs3: [J]
@ -145,6 +149,10 @@ final class Content {
pg2 = raw.arr("pg2")
pg3 = raw.arr("pg3")
pg4 = raw.arr("pg4")
sw = raw.arr("sw")
sw2 = raw.arr("sw2")
sw3 = raw.arr("sw3")
sw4 = raw.arr("sw4")
cs = raw.arr("cs")
cs2 = raw.arr("cs2")
cs3 = raw.arr("cs3")
@ -219,6 +227,10 @@ final class Content {
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 swLesson(_ id: Int) -> J? { sw.first { $0.int("id") == id } }
func sw2Lesson(_ id: Int) -> J? { sw2.first { $0.int("id") == id } }
func sw3Lesson(_ id: Int) -> J? { sw3.first { $0.int("id") == id } }
func sw4Lesson(_ id: Int) -> J? { sw4.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

@ -110,6 +110,14 @@ final class ProgressStore {
func markPg3(_ id: Int) { mark("pg3.\(id)") }
func pg4Done(_ id: Int) -> Bool { done("pg4.\(id)") }
func markPg4(_ id: Int) { mark("pg4.\(id)") }
func swDone(_ id: Int) -> Bool { done("sw.\(id)") }
func markSw(_ id: Int) { mark("sw.\(id)") }
func sw2Done(_ id: Int) -> Bool { done("sw2.\(id)") }
func markSw2(_ id: Int) { mark("sw2.\(id)") }
func sw3Done(_ id: Int) -> Bool { done("sw3.\(id)") }
func markSw3(_ id: Int) { mark("sw3.\(id)") }
func sw4Done(_ id: Int) -> Bool { done("sw4.\(id)") }
func markSw4(_ id: Int) { mark("sw4.\(id)") }
func csDone(_ id: Int) -> Bool { done("cs.\(id)") }
func markCs(_ id: Int) { mark("cs.\(id)") }
func cs2Done(_ id: Int) -> Bool { done("cs2.\(id)") }
@ -376,6 +384,38 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
sum.doneUnits += 1
}
}
for l in content.sw {
sum.totalEx += 1
sum.openUnits += 1
if p.swDone(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.sw2 {
sum.totalEx += 1
sum.openUnits += 1
if p.sw2Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.sw3 {
sum.totalEx += 1
sum.openUnits += 1
if p.sw3Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.sw4 {
sum.totalEx += 1
sum.openUnits += 1
if p.sw4Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.cs {
sum.totalEx += 1
sum.openUnits += 1

View file

@ -137,6 +137,19 @@ enum Route: Hashable {
case pg4Map
case pg4Lesson(Int)
case pg4Ex(Int)
case swYears
case swMap
case swLesson(Int)
case swEx(Int)
case sw2Map
case sw2Lesson(Int)
case sw2Ex(Int)
case sw3Map
case sw3Lesson(Int)
case sw3Ex(Int)
case sw4Map
case sw4Lesson(Int)
case sw4Ex(Int)
case csYears
case csMap
case csLesson(Int)
@ -285,6 +298,19 @@ enum Route: Hashable {
case .pg4Map: return "pg4map"
case .pg4Lesson(let id): return "pg4unit\(id)"
case .pg4Ex(let id): return "pg4ex\(id)"
case .swYears: return "swyears"
case .swMap: return "swmap"
case .swLesson(let id): return "swunit\(id)"
case .swEx(let id): return "swex\(id)"
case .sw2Map: return "sw2map"
case .sw2Lesson(let id): return "sw2unit\(id)"
case .sw2Ex(let id): return "sw2ex\(id)"
case .sw3Map: return "sw3map"
case .sw3Lesson(let id): return "sw3unit\(id)"
case .sw3Ex(let id): return "sw3ex\(id)"
case .sw4Map: return "sw4map"
case .sw4Lesson(let id): return "sw4unit\(id)"
case .sw4Ex(let id): return "sw4ex\(id)"
case .csYears: return "csyears"
case .csMap: return "csmap"
case .csLesson(let id): return "csunit\(id)"
@ -570,6 +596,32 @@ enum Route: Hashable {
return [.pgYears, .pg4Map, .pg4Lesson(id)]
case .pg4Ex(let id):
return [.pgYears, .pg4Map, .pg4Lesson(id), .pg4Ex(id)]
case .swYears:
return [.swYears]
case .swMap:
return [.swYears, .swMap]
case .swLesson(let id):
return [.swYears, .swMap, .swLesson(id)]
case .swEx(let id):
return [.swYears, .swMap, .swLesson(id), .swEx(id)]
case .sw2Map:
return [.swYears, .sw2Map]
case .sw2Lesson(let id):
return [.swYears, .sw2Map, .sw2Lesson(id)]
case .sw2Ex(let id):
return [.swYears, .sw2Map, .sw2Lesson(id), .sw2Ex(id)]
case .sw3Map:
return [.swYears, .sw3Map]
case .sw3Lesson(let id):
return [.swYears, .sw3Map, .sw3Lesson(id)]
case .sw3Ex(let id):
return [.swYears, .sw3Map, .sw3Lesson(id), .sw3Ex(id)]
case .sw4Map:
return [.swYears, .sw4Map]
case .sw4Lesson(let id):
return [.swYears, .sw4Map, .sw4Lesson(id)]
case .sw4Ex(let id):
return [.swYears, .sw4Map, .sw4Lesson(id), .sw4Ex(id)]
case .csYears:
return [.csYears]
case .csMap:
@ -658,6 +710,11 @@ func routeFromPage(_ page: String) -> Route? {
case "pg2map": return .pg2Map
case "pg3map": return .pg3Map
case "pg4map": return .pg4Map
case "swyears": return .swYears
case "swmap": return .swMap
case "sw2map": return .sw2Map
case "sw3map": return .sw3Map
case "sw4map": return .sw4Map
case "csyears": return .csYears
case "csmap": return .csMap
case "cs2map": return .cs2Map
@ -772,6 +829,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: /sw4unit(\d+)/), let n = Int(m.1) {
return .sw4Lesson(n)
}
if let m = page.wholeMatch(of: /sw4ex(\d+)/), let n = Int(m.1) {
return .sw4Ex(n)
}
if let m = page.wholeMatch(of: /sw3unit(\d+)/), let n = Int(m.1) {
return .sw3Lesson(n)
}
if let m = page.wholeMatch(of: /sw3ex(\d+)/), let n = Int(m.1) {
return .sw3Ex(n)
}
if let m = page.wholeMatch(of: /sw2unit(\d+)/), let n = Int(m.1) {
return .sw2Lesson(n)
}
if let m = page.wholeMatch(of: /sw2ex(\d+)/), let n = Int(m.1) {
return .sw2Ex(n)
}
if let m = page.wholeMatch(of: /swunit(\d+)/), let n = Int(m.1) {
return .swLesson(n)
}
if let m = page.wholeMatch(of: /swex(\d+)/), let n = Int(m.1) {
return .swEx(n)
}
if let m = page.wholeMatch(of: /pg4unit(\d+)/), let n = Int(m.1) {
return .pg4Lesson(n)
}

View file

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

View file

@ -1018,6 +1018,39 @@ struct PgQuizScreen: View {
}
}
struct SwQuizScreen: 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.sw2Lesson(id)
case 3: return vm.content.sw3Lesson(id)
case 4: return vm.content.sw4Lesson(id)
default: return vm.content.swLesson(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.markSw2(id)
case 3: vm.markSw3(id)
case 4: vm.markSw4(id)
default: vm.markSw(id)
}
}
}
}
}
struct FyzQuizScreen: View {
@ObservedObject var vm: AppModel
let year: Int

View file

@ -751,6 +751,10 @@ func routeProgress(_ vm: AppModel, _ r: Route) -> (done: Int, total: Int)? {
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 .swMap: return tally(c.sw, p.swDone)
case .sw2Map: return tally(c.sw2, p.sw2Done)
case .sw3Map: return tally(c.sw3, p.sw3Done)
case .sw4Map: return tally(c.sw4, p.sw4Done)
case .csMap: return tally(c.cs, p.csDone)
case .cs2Map: return tally(c.cs2, p.cs2Done)
case .cs3Map: return tally(c.cs3, p.cs3Done)
@ -820,6 +824,12 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
+ 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 "swyears", "swmap", "sw2map", "sw3map", "sw4map":
sum.totalEx = vm.content.sw.count + vm.content.sw2.count + vm.content.sw3.count + vm.content.sw4.count
sum.doneEx = vm.content.sw.filter { vm.progress.swDone($0.int("id")) }.count
+ vm.content.sw2.filter { vm.progress.sw2Done($0.int("id")) }.count
+ vm.content.sw3.filter { vm.progress.sw3Done($0.int("id")) }.count
+ vm.content.sw4.filter { vm.progress.sw4Done($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
@ -1129,6 +1139,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("Programové vybavení"), vm.tr("search_lesson"), "swyears", extra: "office windows excel mysql")
add(vm.tr("sw_year1"), vm.tr("search_lesson"), "swmap", extra: "rocnik")
add(vm.tr("sw_year2"), vm.tr("search_lesson"), "sw2map", extra: "rocnik")
add(vm.tr("sw_year3"), vm.tr("search_lesson"), "sw3map", extra: "rocnik")
add(vm.tr("sw_year4"), vm.tr("search_lesson"), "sw4map", extra: "rocnik")
for l in vm.content.sw {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "swunit\(l.int("id"))", extra: "windows")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "swex\(l.int("id"))")
}
for l in vm.content.sw2 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "sw2unit\(l.int("id"))", extra: "word excel")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "sw2ex\(l.int("id"))")
}
for l in vm.content.sw3 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "sw3unit\(l.int("id"))", extra: "access mysql")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "sw3ex\(l.int("id"))")
}
for l in vm.content.sw4 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "sw4unit\(l.int("id"))", extra: "wordpress")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "sw4ex\(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")
@ -1976,6 +2007,47 @@ struct CsYearsScreen: View {
}
}
struct SwYearsScreen: View {
@ObservedObject var vm: AppModel
var body: some View {
#if os(macOS)
let years: [(String, String, Route)] = [
("sw_year1", "sw_sub", .swMap),
("sw_year2", "sw2_sub", .sw2Map),
("sw_year3", "sw3_sub", .sw3Map),
("sw_year4", "sw4_sub", .sw4Map),
]
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("Programové vybavení"))
.navigationSubtitle(vm.tr("sw_years_sub"))
#else
List {
NavigationLink(value: Route.swMap) {
ListRowLabel(title: vm.tr("sw_year1"), subtitle: vm.tr("sw_sub"), progress: routeProgress(vm, Route.swMap))
}
NavigationLink(value: Route.sw2Map) {
ListRowLabel(title: vm.tr("sw_year2"), subtitle: vm.tr("sw2_sub"), progress: routeProgress(vm, Route.sw2Map))
}
NavigationLink(value: Route.sw3Map) {
ListRowLabel(title: vm.tr("sw_year3"), subtitle: vm.tr("sw3_sub"), progress: routeProgress(vm, Route.sw3Map))
}
NavigationLink(value: Route.sw4Map) {
ListRowLabel(title: vm.tr("sw_year4"), subtitle: vm.tr("sw4_sub"), progress: routeProgress(vm, Route.sw4Map))
}
}
.navigationTitle(vm.tr("Programové vybavení"))
.navigationSubtitle(vm.tr("sw_years_sub"))
.appListStyle()
#endif
}
}
struct PgYearsScreen: View {
@ObservedObject var vm: AppModel
@ -3175,6 +3247,72 @@ struct RouteDestination: View {
if let l = vm.content.cs4Lesson(id) {
CodeLessonScreen(vm: vm, lesson: l, onSuccess: { vm.markCs4(id) })
}
case .swYears:
SwYearsScreen(vm: vm)
case .swMap:
LessonListScreen(
vm: vm,
title: vm.tr("sw_year1"),
subtitle: vm.tr("sw_sub"),
rows: vm.content.sw.map { l in
let id = l.int("id")
return LessonRow(id: "s\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.swDone(id), destination: .swLesson(id))
}
)
case .swLesson(let id):
if let l = vm.content.swLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .swEx(id))
}
case .swEx(let id):
SwQuizScreen(vm: vm, year: 1, id: id)
case .sw2Map:
LessonListScreen(
vm: vm,
title: vm.tr("sw_year2"),
subtitle: vm.tr("sw2_sub"),
rows: vm.content.sw2.map { l in
let id = l.int("id")
return LessonRow(id: "s2\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.sw2Done(id), destination: .sw2Lesson(id))
}
)
case .sw2Lesson(let id):
if let l = vm.content.sw2Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .sw2Ex(id))
}
case .sw2Ex(let id):
SwQuizScreen(vm: vm, year: 2, id: id)
case .sw3Map:
LessonListScreen(
vm: vm,
title: vm.tr("sw_year3"),
subtitle: vm.tr("sw3_sub"),
rows: vm.content.sw3.map { l in
let id = l.int("id")
return LessonRow(id: "s3\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.sw3Done(id), destination: .sw3Lesson(id))
}
)
case .sw3Lesson(let id):
if let l = vm.content.sw3Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .sw3Ex(id))
}
case .sw3Ex(let id):
SwQuizScreen(vm: vm, year: 3, id: id)
case .sw4Map:
LessonListScreen(
vm: vm,
title: vm.tr("sw_year4"),
subtitle: vm.tr("sw4_sub"),
rows: vm.content.sw4.map { l in
let id = l.int("id")
return LessonRow(id: "s4\(id)", title: vm.tr(l.str("titleKey")), done: vm.progress.sw4Done(id), destination: .sw4Lesson(id))
}
)
case .sw4Lesson(let id):
if let l = vm.content.sw4Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .sw4Ex(id))
}
case .sw4Ex(let id):
SwQuizScreen(vm: vm, year: 4, id: id)
case .pgYears:
PgYearsScreen(vm: vm)
case .pgMap: