From dc780e83d3796255986c51de9d3c448c65b2e854 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Fri, 5 Jan 2024 15:22:30 -0700 Subject: [PATCH 01/35] an incomplete job selector widget --- DLPrototype.xcodeproj/project.pbxproj | 16 ++ .../Models/CoreData/CoreDataCompanies.swift | 12 ++ DLPrototype/Utils/Navigation.swift | 18 ++- .../Views/Entities/Jobs/JobDashboard.swift | 56 ++++++- DLPrototype/Views/Entities/Jobs/JobView.swift | 2 +- .../Shared/Fancy/FancySimpleButton.swift | 2 +- .../Views/Shared/PanelGroup/Panel.swift | 147 ++++++++++++++++++ .../Shared/PanelGroup/ThreePanelGroup.swift | 62 ++++++++ 8 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 DLPrototype/Views/Shared/PanelGroup/Panel.swift create mode 100644 DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift diff --git a/DLPrototype.xcodeproj/project.pbxproj b/DLPrototype.xcodeproj/project.pbxproj index b110bd3d..0c6acc74 100644 --- a/DLPrototype.xcodeproj/project.pbxproj +++ b/DLPrototype.xcodeproj/project.pbxproj @@ -181,6 +181,8 @@ 53E918702B43671E00912C6F /* EntityViewMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E9186F2B43671E00912C6F /* EntityViewMode.swift */; }; 53E918772B43DB2F00912C6F /* Meeting.md in Resources */ = {isa = PBXBuildFile; fileRef = 53E918762B43DB0D00912C6F /* Meeting.md */; }; 53E918792B43DD4200912C6F /* List.md in Resources */ = {isa = PBXBuildFile; fileRef = 53E918782B43DCB600912C6F /* List.md */; }; + 53E9187B2B47C9A400912C6F /* ThreePanelGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E9187A2B47C9A400912C6F /* ThreePanelGroup.swift */; }; + 53E9187F2B47C9E300912C6F /* Panel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E9187E2B47C9E300912C6F /* Panel.swift */; }; 53ECD0572971F8F400A09AAB /* CoreDataRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ECD0562971F8F400A09AAB /* CoreDataRecords.swift */; }; 53EDDF9E29634852008D34C7 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EDDF9D29634852008D34C7 /* Entry.swift */; }; 53EDDFA02963487A008D34C7 /* CustomPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EDDF9F2963487A008D34C7 /* CustomPickerItem.swift */; }; @@ -400,6 +402,8 @@ 53E9186F2B43671E00912C6F /* EntityViewMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityViewMode.swift; sourceTree = ""; }; 53E918762B43DB0D00912C6F /* Meeting.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Meeting.md; sourceTree = ""; }; 53E918782B43DCB600912C6F /* List.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = List.md; sourceTree = ""; }; + 53E9187A2B47C9A400912C6F /* ThreePanelGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreePanelGroup.swift; sourceTree = ""; }; + 53E9187E2B47C9E300912C6F /* Panel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panel.swift; sourceTree = ""; }; 53ECD0562971F8F400A09AAB /* CoreDataRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataRecords.swift; sourceTree = ""; }; 53EDDF9D29634852008D34C7 /* Entry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = ""; }; 53EDDF9F2963487A008D34C7 /* CustomPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPickerItem.swift; sourceTree = ""; }; @@ -1057,9 +1061,19 @@ path = Templates; sourceTree = ""; }; + 53E9187D2B47C9CC00912C6F /* PanelGroup */ = { + isa = PBXGroup; + children = ( + 53E9187A2B47C9A400912C6F /* ThreePanelGroup.swift */, + 53E9187E2B47C9E300912C6F /* Panel.swift */, + ); + path = PanelGroup; + sourceTree = ""; + }; 53EDDF9C29634840008D34C7 /* Shared */ = { isa = PBXGroup; children = ( + 53E9187D2B47C9CC00912C6F /* PanelGroup */, 53790C012A72F15200D3FFD4 /* AppSidebar */, 53B0BE822972610C007CB663 /* Fancy */, 53EDDF9D29634852008D34C7 /* Entry.swift */, @@ -1289,6 +1303,7 @@ 53EFCE7629637CC6004E45EC /* Color.swift in Sources */, 53F5896629983FD100843B32 /* ProjectRow.swift in Sources */, 5334F2782B2CC9EE0079D2E7 /* CoreDataPerson.swift in Sources */, + 53E9187B2B47C9A400912C6F /* ThreePanelGroup.swift in Sources */, 53C3418E299968160071B855 /* JobView.swift in Sources */, 537885742B44CB3300825B78 /* TaskDashboardByProject.swift in Sources */, 535C1AEA29F4913900CD95FD /* ThisYear.swift in Sources */, @@ -1409,6 +1424,7 @@ 5335A5AE296CFF83000051B1 /* FileHelper.swift in Sources */, 53E202612A7EC96D00B4DF70 /* DashboardSidebar.swift in Sources */, 532A722F299037AC0038C18B /* RecordResult.swift in Sources */, + 53E9187F2B47C9E300912C6F /* Panel.swift in Sources */, 53F589602998335A00843B32 /* NoteRow.swift in Sources */, 53FBCC8E2A61D59B00B88539 /* FancyStaticTextField.swift in Sources */, 5363B67C2A6BB78900C2FBB8 /* CompanyView.swift in Sources */, diff --git a/DLPrototype/Models/CoreData/CoreDataCompanies.swift b/DLPrototype/Models/CoreData/CoreDataCompanies.swift index c568526d..75d1802a 100644 --- a/DLPrototype/Models/CoreData/CoreDataCompanies.swift +++ b/DLPrototype/Models/CoreData/CoreDataCompanies.swift @@ -20,6 +20,18 @@ public class CoreDataCompanies: ObservableObject { public init(moc: NSManagedObjectContext?) { self.moc = moc } + + static public func all() -> FetchRequest { + let descriptors = [ + NSSortDescriptor(keyPath: \Company.createdDate?, ascending: false) + ] + + let fetch: NSFetchRequest = Company.fetchRequest() + fetch.predicate = NSPredicate(format: "alive == true") + fetch.sortDescriptors = descriptors + + return FetchRequest(fetchRequest: fetch, animation: .easeInOut) + } public func byPid(_ id: Int) -> Company? { let predicate = NSPredicate( diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index b5027691..6d0f13d3 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -192,6 +192,7 @@ extension Navigation { public struct Forms { var note: NoteForm = NoteForm() + var jobSelector: JobSelectorForm = JobSelectorForm() struct NoteForm { var template: NoteTemplates.Template? = nil @@ -199,6 +200,13 @@ extension Navigation { var version: NoteVersion? = nil var star: Bool = false } + + struct JobSelectorForm { + var currentPosition: Panel.Position = .first + var first: FetchedResults? = nil + var middle: [Project] = [] + var last: [Job] = [] + } } public struct State { @@ -259,7 +267,7 @@ extension Navigation { HistoryPage(page: .planning, view: AnyView(Planning()), sidebar: AnyView(DefaultPlanningSidebar()), title: "Planning"), HistoryPage(page: .today, view: AnyView(Today()), sidebar: AnyView(TodaySidebar()), title: "Today"), HistoryPage(page: .companies, view: AnyView(CompanyDashboard()), sidebar: AnyView(DefaultCompanySidebar()), title: "Companies & Projects"), - HistoryPage(page: .jobs, view: AnyView(JobDashboard()), sidebar: AnyView(JobDashboardSidebar()), title: "Jobs"), + HistoryPage(page: .jobs, view: AnyView(JobDashboardRedux()), sidebar: AnyView(JobDashboardSidebar()), title: "Jobs"), HistoryPage(page: .notes, view: AnyView(NoteDashboard()), sidebar: AnyView(NoteDashboardSidebar()), title: "Notes"), HistoryPage(page: .tasks, view: AnyView(TaskDashboard()), sidebar: AnyView(TaskDashboardSidebar()), title: "Tasks"), ] @@ -272,6 +280,14 @@ extension Navigation { var sidebar: AnyView var title: String } + + public struct Breadcrumb { + var id: UUID = UUID() + var current: Page + var history: [HistoryPage] = [] + + + } } } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index a7dc7d06..ff87c5b0 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -11,8 +11,6 @@ import SwiftUI struct JobDashboard: View { var defaultSelectedJob: Job? - @StateObject public var jm: CoreDataJob = CoreDataJob(moc: PersistenceController.shared.container.viewContext) - @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation @EnvironmentObject public var updater: ViewUpdater @@ -39,7 +37,7 @@ struct JobDashboard: View { About() if let jerb = defaultSelectedJob { - JobView(job: jerb).environmentObject(jm) + JobView(job: jerb) } Spacer() @@ -49,7 +47,6 @@ struct JobDashboard: View { .background(Theme.toolbarColour) .onAppear(perform: actionOnAppear) .onChange(of: defaultSelectedJob, perform: actionOnChange) - .id(updater.get("job.dashboard")) } } @@ -78,3 +75,54 @@ extension JobDashboard { } } } + +struct JobDashboardRedux: View { + var body: some View { + VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 10) { + HStack { + FancyButtonv2( + text: "New job", + action: {}, + icon: "plus", + showLabel: true, + redirect: AnyView(JobCreate()), + pageType: .jobs, + sidebar: AnyView(JobDashboardSidebar()) + ) + } + + FancyDivider() + + JobExplorer() + + Spacer() + } + .padding() + } + .background(Theme.toolbarColour) + } +} + +struct JobExplorer: View { + @EnvironmentObject private var nav: Navigation + + @FetchRequest private var companies: FetchedResults + + var body: some View { + VStack(alignment: .leading) { + HStack { + Title(text: "Job Explorer") + } + + ThreePanelGroup(orientation: .horizontal, data: companies) + +// JobView(job: nav.session.job!) + } +// .background(Theme.toolbarColour) + } + + init() { + _companies = CoreDataCompanies.all() + } +} diff --git a/DLPrototype/Views/Entities/Jobs/JobView.swift b/DLPrototype/Views/Entities/Jobs/JobView.swift index c1e1b742..72e7fddb 100644 --- a/DLPrototype/Views/Entities/Jobs/JobView.swift +++ b/DLPrototype/Views/Entities/Jobs/JobView.swift @@ -41,7 +41,7 @@ struct JobView: View { FancyJobActiveToggle(entity: job) FancyJobSredToggle(entity: job) FancyColourPicker(initialColour: job.colour ?? Theme.rowColourAsDouble, onChange: {newColour in colour = newColour}) - + Spacer() buttonSubmit } .onAppear(perform: setEditableValues) diff --git a/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift b/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift index 84809f68..329f92c9 100644 --- a/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift +++ b/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift @@ -58,7 +58,7 @@ struct FancySimpleButton: View { .buttonStyle(.borderless) .background(highlighted ? type.highlightColour : type.colours.first) .foregroundColor(type.textColour) - .mask(RoundedRectangle(cornerRadius: 3)) // @TODO: make configurable +// .mask(RoundedRectangle(cornerRadius: 3)) // @TODO: make configurable? .useDefaultHover({ inside in highlighted = inside}) } } diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift new file mode 100644 index 00000000..aa5ac217 --- /dev/null +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -0,0 +1,147 @@ +// +// PanelGroup.swift +// DLPrototype +// +// Created by Ryan Priebe on 2024-01-04. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI + +struct Panel: View { + public var position: Position + + @State private var highlighted: Bool = false + + @EnvironmentObject private var nav: Navigation + + var body: some View { + VStack(alignment: .leading, spacing: 1) { + HStack { + switch position { + case .first: + Text("Companies").font(.title3) + Spacer() + case .middle: + Text("Projects").font(.title3) + Spacer() + FancySimpleButton( + text: "Close", + action: closePanel, + icon: "xmark", + showLabel: false, + showIcon: true + ) + case .last: + Text("Jobs").font(.title3) + Spacer() + FancySimpleButton( + text: "Close", + action: closePanel, + icon: "xmark", + showLabel: false, + showIcon: true + ) + } + } + .padding(3) + + if let firstColData = nav.forms.jobSelector.first { + if position == .first { + if !firstColData.isEmpty { + ForEach(firstColData) { company in + HStack { + FancySimpleButton( + text: company.name!, + action: {setMiddlePanel(data: company.projects!.allObjects)}, + showLabel: true, + showIcon: false, + size: .link, + type: .clear + ) + Spacer() + Image(systemName: "arrow.right") + } + .padding(3) + } + } else { + Text("No companies found") + } + } else if position == .middle { + if !nav.forms.jobSelector.middle.isEmpty { + ForEach(nav.forms.jobSelector.middle) { project in + HStack { + FancySimpleButton( + text: project.name!, + action: {setLastPanel(data: project.jobs!.allObjects)}, + showLabel: true, + showIcon: false, + size: .link, + type: .clear + ) + Spacer() + Image(systemName: "arrow.right") + } + .padding(3) + } + } else { + Text("No company selected, or company has no projects") + } + } else if position == .last { + if !nav.forms.jobSelector.last.isEmpty { + ForEach(nav.forms.jobSelector.last) { job in + HStack { + FancySimpleButton( + text: job.name ?? job.jid.string, + action: {nav.session.job = job}, + showLabel: true, + showIcon: false, + size: .link, + type: .clear + ) + Spacer() + Image(systemName: "hammer") + } + .padding(3) + } + } else { + Text("No job selected, or project has no jobs") + } + } + } + } + .background(.white.opacity(0.05)) + } +} + +extension Panel { + private func setMiddlePanel(data: [Any]) -> Void { + nav.forms.jobSelector.currentPosition = position + nav.forms.jobSelector.middle = data as! [Project] + } + + private func setLastPanel(data: [Any]) -> Void { + nav.forms.jobSelector.currentPosition = .last + nav.forms.jobSelector.last = data as! [Job] + } + + private func closePanel() -> Void { + if position == .middle { + nav.forms.jobSelector.middle = [] + } else if position == .last { + nav.forms.jobSelector.last = [] + } + + nav.session.job = nil + } +} + +extension Panel { + public enum Orientation { + case horizontal, vertical + } + + public enum Position { + case first, middle, last + } +} diff --git a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift new file mode 100644 index 00000000..8af2a82d --- /dev/null +++ b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift @@ -0,0 +1,62 @@ +// +// ThreePanel.swift +// DLPrototype +// +// Created by Ryan Priebe on 2024-01-04. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI + +// @TODO: this whole structure needs to be rebuilt to be extensible +struct ThreePanelGroup: View { + public var orientation: Panel.Orientation + public var data: any Collection + + private var columns: [GridItem] { + return Array(repeating: .init(.flexible(minimum: 100)), count: 3) + } + + @EnvironmentObject private var nav: Navigation + + var body: some View { + VStack(alignment: .leading) { + HStack { + // Icons + } + if orientation == .horizontal { + LazyVGrid(columns: columns, alignment: .leading) { + Panel(position: .first) + Panel(position: .middle) + Panel(position: .last) + } + } else { + LazyHGrid(rows: columns, alignment: .top) { + Panel(position: .first) + Panel(position: .middle) + Panel(position: .last) + } + } + } + .onAppear(perform: actionOnAppear) + } + + init(orientation: Panel.Orientation, data: any Collection) { + self.orientation = orientation + self.data = data + } +} + +extension ThreePanelGroup { + private func actionOnAppear() -> Void { + switch self.data { + case is FetchedResults: nav.forms.jobSelector.first = self.data as? FetchedResults + case is FetchedResults: nav.forms.jobSelector.middle = self.data as! [Project] + case is FetchedResults: nav.forms.jobSelector.last = self.data as! [Job] + default: + nav.forms.jobSelector.first = nil + nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] + } + } +} From daf9fe75f2b44168f1e46937f71224ff5284e18d Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Fri, 5 Jan 2024 15:22:47 -0700 Subject: [PATCH 02/35] added name to Job so you can give it a plain text description --- DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents | 1 + 1 file changed, 1 insertion(+) diff --git a/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents b/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents index 14fb7ecd..8f6f0791 100644 --- a/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents +++ b/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents @@ -37,6 +37,7 @@ + From 5e2aeffd5ecac98fc17ef63350128ab200e2d436 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Fri, 5 Jan 2024 18:47:53 -0700 Subject: [PATCH 03/35] mostly feature complete job explorer that only needs to be totally refactored --- DLPrototype/Utils/Navigation.swift | 1 + .../Views/Entities/Jobs/JobDashboard.swift | 2 +- .../Shared/Fancy/FancySimpleButton.swift | 33 +- .../Views/Shared/PanelGroup/Panel.swift | 380 ++++++++++++++---- .../Shared/PanelGroup/ThreePanelGroup.swift | 18 +- 5 files changed, 338 insertions(+), 96 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 6d0f13d3..4e5104eb 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -206,6 +206,7 @@ extension Navigation { var first: FetchedResults? = nil var middle: [Project] = [] var last: [Job] = [] + var selected: Panel.SelectedValueCoordinates? = nil } } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index ff87c5b0..2f2b96a1 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -110,7 +110,7 @@ struct JobExplorer: View { @FetchRequest private var companies: FetchedResults var body: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 10) { HStack { Title(text: "Job Explorer") } diff --git a/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift b/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift index 329f92c9..91536115 100644 --- a/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift +++ b/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift @@ -14,6 +14,7 @@ struct FancySimpleButton: View { public var icon: String = "checkmark.circle" public var showLabel: Bool = true public var showIcon: Bool = false + public var labelView: AnyView? = nil public var size: ButtonSize = .large public var type: ButtonType = .standard public var href: Page? = nil @@ -34,24 +35,28 @@ struct FancySimpleButton: View { } } } label: { - VStack(alignment: .center) { - HStack(alignment: .top, spacing: 5) { - if showIcon { - VStack(alignment: .leading) { - Image(systemName: icon) - .padding(size.padding) + if let labelView = labelView { + labelView + } else { + VStack(alignment: .center) { + HStack(alignment: .top, spacing: 5) { + if showIcon { + VStack(alignment: .leading) { + Image(systemName: icon) + .padding(size.padding) + } } - } - - if showLabel { - VStack(alignment: .leading) { - Text(text) - .font(.title3) - .padding(size.padding) + + if showLabel { + VStack(alignment: .leading) { + Text(text) + .font(.title3) + .padding(size.padding) + } } } + .font(.title3) } - .font(.title3) } } .help(text) diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index aa5ac217..1ed05134 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -8,33 +8,87 @@ import SwiftUI -struct Panel: View { - public var position: Position +// @TODO: refactor everything +public struct Panel { + public enum Orientation { + case horizontal, vertical + } + + public enum Position { + case first, middle, last + } + + public struct SelectedValueCoordinates { + var position: Position + var item: NSManagedObject + } - @State private var highlighted: Bool = false + public struct RowConfiguration { + var text: String + var action: () -> Void + var entity: NSManagedObject + var position: Panel.Position + } + + struct Row: View { + public var config: RowConfiguration + + @State private var highlighted: Bool = false + + @EnvironmentObject private var nav: Navigation - @EnvironmentObject private var nav: Navigation + var body: some View { + VStack(alignment: .leading, spacing: 1) { + FancySimpleButton( + text: config.text, + action: fireCallback, + labelView: AnyView( + HStack { + Text(config.text) + Spacer() + Image(systemName: config.position == .last ? "hammer" : "arrow.right") + } + .padding(10) + .background(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .orange : .clear : .clear) + .foregroundStyle(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .black : .white : .white) + ), + size: .link, + type: .clear + ) + } + } + } +} + +extension Panel.Row { + private func fireCallback() -> Void { + highlighted = true + nav.forms.jobSelector.selected = Panel.SelectedValueCoordinates(position: config.position, item: config.entity) + config.action() + } +} + +struct CompanyPanel: View { + public var position: Panel.Position + + @State private var showSearch: Bool = false + @State private var searchText: String = "" + + @EnvironmentObject public var nav: Navigation var body: some View { VStack(alignment: .leading, spacing: 1) { - HStack { - switch position { - case .first: + HStack(alignment: .top) { Text("Companies").font(.title3) Spacer() - case .middle: - Text("Projects").font(.title3) - Spacer() FancySimpleButton( - text: "Close", - action: closePanel, - icon: "xmark", + text: "Search", + action: {showSearch.toggle()}, + icon: "magnifyingglass", showLabel: false, - showIcon: true + showIcon: true, + type: .clear ) - case .last: - Text("Jobs").font(.title3) - Spacer() FancySimpleButton( text: "Close", action: closePanel, @@ -42,106 +96,284 @@ struct Panel: View { showLabel: false, showIcon: true ) - } + .disabled(nav.forms.jobSelector.selected == nil) + .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) + } + .padding(10) + + if showSearch { + SearchBar(text: $searchText) } - .padding(3) if let firstColData = nav.forms.jobSelector.first { if position == .first { - if !firstColData.isEmpty { - ForEach(firstColData) { company in - HStack { - FancySimpleButton( - text: company.name!, - action: {setMiddlePanel(data: company.projects!.allObjects)}, - showLabel: true, - showIcon: false, - size: .link, - type: .clear + VStack(alignment: .leading, spacing: 1) { + if !firstColData.isEmpty { + ForEach(firstColData) { company in + Panel.Row( + config: Panel.RowConfiguration( + text: company.name!, + action: {setMiddlePanel(data: company.projects!.allObjects)}, + entity: company, + position: position + ) ) - Spacer() - Image(systemName: "arrow.right") } - .padding(3) + } else { + Text("No companies found") + .padding(10) } - } else { - Text("No companies found") + Spacer() } } else if position == .middle { - if !nav.forms.jobSelector.middle.isEmpty { - ForEach(nav.forms.jobSelector.middle) { project in - HStack { - FancySimpleButton( - text: project.name!, - action: {setLastPanel(data: project.jobs!.allObjects)}, - showLabel: true, - showIcon: false, - size: .link, - type: .clear + VStack(alignment: .leading, spacing: 1) { + if !nav.forms.jobSelector.middle.isEmpty { + ForEach(nav.forms.jobSelector.middle) { project in + Panel.Row( + config: Panel.RowConfiguration( + text: project.name!, + action: {setLastPanel(data: project.jobs!.allObjects)}, + entity: project, + position: position + ) ) - Spacer() - Image(systemName: "arrow.right") } - .padding(3) + } else { + Text("No company selected, or company has no projects") + .padding(10) } - } else { - Text("No company selected, or company has no projects") + Spacer() } } else if position == .last { - if !nav.forms.jobSelector.last.isEmpty { - ForEach(nav.forms.jobSelector.last) { job in - HStack { - FancySimpleButton( - text: job.name ?? job.jid.string, - action: {nav.session.job = job}, - showLabel: true, - showIcon: false, - size: .link, - type: .clear + VStack(alignment: .leading, spacing: 1) { + if !nav.forms.jobSelector.last.isEmpty { + ForEach(nav.forms.jobSelector.last) { job in + Panel.Row( + config: Panel.RowConfiguration( + text: job.name ?? job.jid.string, + action: {nav.session.job = job}, + entity: job, + position: position + ) ) - Spacer() - Image(systemName: "hammer") } - .padding(3) + } else { + Text("No job selected, or project has no jobs") + .padding(10) } - } else { - Text("No job selected, or project has no jobs") + Spacer() } } } } - .background(.white.opacity(0.05)) + .background( + .white.opacity(0.05) + ) + .frame(minHeight: 300) } } -extension Panel { +extension CompanyPanel { private func setMiddlePanel(data: [Any]) -> Void { nav.forms.jobSelector.currentPosition = position nav.forms.jobSelector.middle = data as! [Project] + nav.forms.jobSelector.last = [] } private func setLastPanel(data: [Any]) -> Void { - nav.forms.jobSelector.currentPosition = .last + nav.forms.jobSelector.currentPosition = position nav.forms.jobSelector.last = data as! [Job] } private func closePanel() -> Void { - if position == .middle { + if position == .first { + nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] + nav.forms.jobSelector.selected = nil + } else if position == .middle { nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] } else if position == .last { nav.forms.jobSelector.last = [] + nav.session.job = nil } - nav.session.job = nil + showSearch = false } } -extension Panel { - public enum Orientation { - case horizontal, vertical +struct ProjectPanel: View { + public var position: Panel.Position + + @State private var showSearch: Bool = false + @State private var searchText: String = "" + + @EnvironmentObject public var nav: Navigation + + var body: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(alignment: .top) { + Text("Projects").font(.title3) + Spacer() + FancySimpleButton( + text: "Search", + action: {showSearch.toggle()}, + icon: "magnifyingglass", + showLabel: false, + showIcon: true, + type: .clear + ) + FancySimpleButton( + text: "Close", + action: closePanel, + icon: "xmark", + showLabel: false, + showIcon: true + ) + .disabled(nav.forms.jobSelector.middle.isEmpty) + .opacity(nav.forms.jobSelector.middle.isEmpty ? 0.4 : 1) + } + .padding(10) + + if showSearch { + SearchBar(text: $searchText) + } + + if let _ = nav.forms.jobSelector.first { + VStack(alignment: .leading, spacing: 1) { + if !nav.forms.jobSelector.middle.isEmpty { + ForEach(nav.forms.jobSelector.middle) { project in + Panel.Row( + config: Panel.RowConfiguration( + text: project.name!, + action: {setLastPanel(data: project.jobs!.allObjects)}, + entity: project, + position: position + ) + ) + } + } else { + Text("No company selected, or company has no projects") + .padding(10) + } + Spacer() + } + } + } + .background( + .white.opacity(0.05) + ) + .frame(minHeight: 300) + } +} + +extension ProjectPanel { + private func setLastPanel(data: [Any]) -> Void { + nav.forms.jobSelector.currentPosition = position + nav.forms.jobSelector.last = data as! [Job] + } + + private func closePanel() -> Void { + if position == .first { + nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] + nav.forms.jobSelector.selected = nil + } else if position == .middle { + nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] + } else if position == .last { + nav.forms.jobSelector.last = [] + nav.session.job = nil + } + + showSearch = false } +} + +public struct JobPanel: View { + public var position: Panel.Position - public enum Position { - case first, middle, last + @State private var showSearch: Bool = false + @State private var searchText: String = "" + + @EnvironmentObject public var nav: Navigation + + public var body: some View { + VStack(alignment: .leading, spacing: 1) { + HStack(alignment: .top) { + Text("Jobs").font(.title3) + Spacer() + FancySimpleButton( + text: "Search", + action: {showSearch.toggle()}, + icon: "magnifyingglass", + showLabel: false, + showIcon: true, + type: .clear + ) + FancySimpleButton( + text: "Close", + action: closePanel, + icon: "xmark", + showLabel: false, + showIcon: true + ) + .disabled(nav.forms.jobSelector.last.isEmpty) + .opacity(nav.forms.jobSelector.last.isEmpty ? 0.4 : 1) + } + .padding(10) + + if showSearch { + SearchBar(text: $searchText) + } + + if let _ = nav.forms.jobSelector.first { + VStack(alignment: .leading, spacing: 1) { + if !nav.forms.jobSelector.last.isEmpty { + ForEach(nav.forms.jobSelector.last) { job in + Panel.Row( + config: Panel.RowConfiguration( + text: job.name ?? job.jid.string, + action: {nav.session.job = job}, + entity: job, + position: position + ) + ) + } + } else { + Text("No job selected, or project has no jobs") + .padding(10) + } + Spacer() + } + } + } + .background( + .white.opacity(0.05) + ) + .frame(minHeight: 300) + } +} + +extension JobPanel { + private func setLastPanel(data: [Any]) -> Void { + nav.forms.jobSelector.currentPosition = position + nav.forms.jobSelector.last = data as! [Job] + } + + private func closePanel() -> Void { + if position == .first { + nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] + nav.forms.jobSelector.selected = nil + } else if position == .middle { + nav.forms.jobSelector.middle = [] + nav.forms.jobSelector.last = [] + } else if position == .last { + nav.forms.jobSelector.last = [] + nav.session.job = nil + } + + showSearch = false } } diff --git a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift index 8af2a82d..a22ea424 100644 --- a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift +++ b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift @@ -20,21 +20,25 @@ struct ThreePanelGroup: View { @EnvironmentObject private var nav: Navigation var body: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 1) { HStack { // Icons } if orientation == .horizontal { LazyVGrid(columns: columns, alignment: .leading) { - Panel(position: .first) - Panel(position: .middle) - Panel(position: .last) + CompanyPanel(position: .first) + ProjectPanel(position: .middle) + JobPanel(position: .last) } + .background( + .white.opacity(0.05) + ) + .frame(minHeight: 300) } else { LazyHGrid(rows: columns, alignment: .top) { - Panel(position: .first) - Panel(position: .middle) - Panel(position: .last) + CompanyPanel(position: .first) + ProjectPanel(position: .middle) + JobPanel(position: .last) } } } From 05c50f40b92b76774ee7e4dd8d76a580b749e78a Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 10:27:30 -0700 Subject: [PATCH 04/35] sort companies by name --- DLPrototype/Models/CoreData/CoreDataCompanies.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLPrototype/Models/CoreData/CoreDataCompanies.swift b/DLPrototype/Models/CoreData/CoreDataCompanies.swift index 75d1802a..0e9a8ffa 100644 --- a/DLPrototype/Models/CoreData/CoreDataCompanies.swift +++ b/DLPrototype/Models/CoreData/CoreDataCompanies.swift @@ -23,7 +23,7 @@ public class CoreDataCompanies: ObservableObject { static public func all() -> FetchRequest { let descriptors = [ - NSSortDescriptor(keyPath: \Company.createdDate?, ascending: false) + NSSortDescriptor(keyPath: \Company.name, ascending: true) ] let fetch: NSFetchRequest = Company.fetchRequest() From 66dab3265240bff96e540372b362c0450a77fd7b Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 10:28:32 -0700 Subject: [PATCH 05/35] nearly complete job dashboard, just cleaning up and testing left --- DLPrototype/Utils/Navigation.swift | 1 + .../Views/Entities/Jobs/JobDashboard.swift | 96 +++++++- DLPrototype/Views/Entities/Jobs/JobView.swift | 12 +- .../Shared/Fancy/FancySimpleButton.swift | 18 +- .../Views/Shared/PanelGroup/Panel.swift | 216 ++++++++++-------- .../Shared/PanelGroup/ThreePanelGroup.swift | 17 +- 6 files changed, 221 insertions(+), 139 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 4e5104eb..55f84090 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -120,6 +120,7 @@ public class Navigation: Identifiable, ObservableObject { parent = newParent } + // @TODO: Should attempt to set content with "@ViewBuilder content: () -> Content" instead of wrapping in AnyView public func setSidebar(_ newView: AnyView?) -> Void { if let view = newView { sidebar = view diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 2f2b96a1..b361a657 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -79,13 +79,19 @@ extension JobDashboard { struct JobDashboardRedux: View { var body: some View { VStack(alignment: .leading) { - VStack(alignment: .leading, spacing: 10) { + VStack(alignment: .leading, spacing: 1) { HStack { + HStack(spacing: 10) { + Image(systemName: "hammer") + Text("Jobs") + } + .font(.title2) + Spacer() FancyButtonv2( - text: "New job", + text: "Create", action: {}, icon: "plus", - showLabel: true, + showLabel: false, redirect: AnyView(JobCreate()), pageType: .jobs, sidebar: AnyView(JobDashboardSidebar()) @@ -105,21 +111,93 @@ struct JobDashboardRedux: View { } struct JobExplorer: View { + @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true + @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true + @EnvironmentObject private var nav: Navigation @FetchRequest private var companies: FetchedResults var body: some View { - VStack(alignment: .leading, spacing: 10) { - HStack { - Title(text: "Job Explorer") + VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: 1) { + HStack { + Text("Explorer").font(.title2) + Spacer() + FancySimpleButton( + text: explorerVisible ? "Close" : "Open", + action: {explorerVisible.toggle()}, + icon: explorerVisible ? "minus.square.fill" : "plus.square.fill", + showLabel: false, + showIcon: true, + size: .tiny, + type: .clear + ) + } + + HStack { + Text("Choose a job by first selecting the company, then project, it is associated with.") + .font(.caption) + Spacer() + } } + .padding(5) + .background(.white.opacity(0.2)) + .foregroundStyle(.white) - ThreePanelGroup(orientation: .horizontal, data: companies) + if explorerVisible { + ThreePanelGroup(orientation: .horizontal, data: companies) + } + + VStack(alignment: .leading, spacing: 1) { + HStack { + Text("Editor").font(.title2) + Spacer() + FancySimpleButton( + text: editorVisible ? "Close" : "Open", + action: {editorVisible.toggle()}, + icon: editorVisible ? "minus.square.fill" : "plus.square.fill", + showLabel: false, + showIcon: true, + size: .tiny, + type: .clear + ) + } -// JobView(job: nav.session.job!) + HStack { + Text("Modify the Active job.") + .font(.caption) + Spacer() + } + + } + .padding(5) + .background(.white.opacity(0.2)) + .foregroundStyle(.white) + + if editorVisible { + if let job = nav.session.job { + VStack { + JobView(job: job) + } + .padding(5) + .background(Theme.rowColour) + .foregroundStyle(.white) + } else { + VStack(alignment: .leading, spacing: 1) { + HStack { + Text("No job selected") + .foregroundColor(.gray) + Spacer() + } + .padding() + .background(Theme.rowColour) + + Spacer() + } + } + } } -// .background(Theme.toolbarColour) } init() { diff --git a/DLPrototype/Views/Entities/Jobs/JobView.swift b/DLPrototype/Views/Entities/Jobs/JobView.swift index 72e7fddb..e49deefe 100644 --- a/DLPrototype/Views/Entities/Jobs/JobView.swift +++ b/DLPrototype/Views/Entities/Jobs/JobView.swift @@ -26,12 +26,9 @@ struct JobView: View { @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation @EnvironmentObject public var updater: ViewUpdater - @StateObject public var jm: CoreDataJob = CoreDataJob(moc: PersistenceController.shared.container.viewContext) - + var body: some View { VStack(alignment: .leading) { - topSpace - fieldProjectLink FancyTextField(placeholder: "URL", lineLimit: 1, onSubmit: {}, showLabel: true, text: $url) @@ -44,6 +41,7 @@ struct JobView: View { Spacer() buttonSubmit } + .padding(5) .onAppear(perform: setEditableValues) .onChange(of: job) { _ in setEditableValues() @@ -63,11 +61,7 @@ struct JobView: View { ) } } - - @ViewBuilder private var topSpace: some View { - FancyDivider() - } - + @ViewBuilder private var fieldProjectLink: some View { if let project = job.project { HStack { diff --git a/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift b/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift index 91536115..4ad15fa7 100644 --- a/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift +++ b/DLPrototype/Views/Shared/Fancy/FancySimpleButton.swift @@ -29,7 +29,7 @@ struct FancySimpleButton: View { action() } - DispatchQueue.main.asyncAfter(deadline: .now()) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { if let href = href { nav.to(href) } @@ -38,21 +38,17 @@ struct FancySimpleButton: View { if let labelView = labelView { labelView } else { - VStack(alignment: .center) { + VStack(alignment: .center, spacing: 0) { HStack(alignment: .top, spacing: 5) { if showIcon { - VStack(alignment: .leading) { - Image(systemName: icon) - .padding(size.padding) - } + Image(systemName: icon) + .padding(size.padding) } if showLabel { - VStack(alignment: .leading) { - Text(text) - .font(.title3) - .padding(size.padding) - } + Text(text) + .font(.title3) + .padding(size.padding) } } .font(.title3) diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index 1ed05134..26311dcf 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -38,24 +38,23 @@ public struct Panel { @EnvironmentObject private var nav: Navigation var body: some View { - VStack(alignment: .leading, spacing: 1) { - FancySimpleButton( - text: config.text, - action: fireCallback, - labelView: AnyView( - HStack { - Text(config.text) - Spacer() - Image(systemName: config.position == .last ? "hammer" : "arrow.right") - } - .padding(10) - .background(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .orange : .clear : .clear) - .foregroundStyle(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .black : .white : .white) - ), - size: .link, - type: .clear - ) - } + FancySimpleButton( + text: config.text, + action: fireCallback, + labelView: AnyView( + HStack { + Text(config.text) + Spacer() + Image(systemName: config.position == .last ? "hammer" : "arrow.right") + } + .padding(10) + .background(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .orange : .clear : .clear) + .foregroundStyle(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .black : .white : .white) + ), + size: .link, + type: .clear + ) + .border(width: 1, edges: [.top], color: Theme.rowColour) } } } @@ -78,26 +77,28 @@ struct CompanyPanel: View { var body: some View { VStack(alignment: .leading, spacing: 1) { - HStack(alignment: .top) { - Text("Companies").font(.title3) - Spacer() - FancySimpleButton( - text: "Search", - action: {showSearch.toggle()}, - icon: "magnifyingglass", - showLabel: false, - showIcon: true, - type: .clear - ) - FancySimpleButton( - text: "Close", - action: closePanel, - icon: "xmark", - showLabel: false, - showIcon: true - ) - .disabled(nav.forms.jobSelector.selected == nil) - .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) + HStack(alignment: .center) { + Text("Companies").font(.title3) + Spacer() + FancySimpleButton( + text: "Search", + action: {showSearch.toggle()}, + icon: "magnifyingglass", + showLabel: false, + showIcon: true, + type: .clear + ) + .disabled(nav.forms.jobSelector.selected == nil) + .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) + FancySimpleButton( + text: "Close", + action: closePanel, + icon: "xmark", + showLabel: false, + showIcon: true + ) + .disabled(nav.forms.jobSelector.selected == nil) + .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) } .padding(10) @@ -109,15 +110,19 @@ struct CompanyPanel: View { if position == .first { VStack(alignment: .leading, spacing: 1) { if !firstColData.isEmpty { - ForEach(firstColData) { company in - Panel.Row( - config: Panel.RowConfiguration( - text: company.name!, - action: {setMiddlePanel(data: company.projects!.allObjects)}, - entity: company, - position: position - ) - ) + ScrollView { + VStack(alignment: .leading, spacing: 1) { + ForEach(firstColData) { company in + Panel.Row( + config: Panel.RowConfiguration( + text: company.name!, + action: {setMiddlePanel(data: company.projects!.allObjects)}, + entity: company, + position: position + ) + ) + } + } } } else { Text("No companies found") @@ -128,15 +133,19 @@ struct CompanyPanel: View { } else if position == .middle { VStack(alignment: .leading, spacing: 1) { if !nav.forms.jobSelector.middle.isEmpty { - ForEach(nav.forms.jobSelector.middle) { project in - Panel.Row( - config: Panel.RowConfiguration( - text: project.name!, - action: {setLastPanel(data: project.jobs!.allObjects)}, - entity: project, - position: position - ) - ) + ScrollView { + VStack(alignment: .leading, spacing: 1) { + ForEach(nav.forms.jobSelector.middle) { project in + Panel.Row( + config: Panel.RowConfiguration( + text: project.name!, + action: {setLastPanel(data: project.jobs!.allObjects)}, + entity: project, + position: position + ) + ) + } + } } } else { Text("No company selected, or company has no projects") @@ -147,15 +156,19 @@ struct CompanyPanel: View { } else if position == .last { VStack(alignment: .leading, spacing: 1) { if !nav.forms.jobSelector.last.isEmpty { - ForEach(nav.forms.jobSelector.last) { job in - Panel.Row( - config: Panel.RowConfiguration( - text: job.name ?? job.jid.string, - action: {nav.session.job = job}, - entity: job, - position: position - ) - ) + ScrollView { + VStack(alignment: .leading, spacing: 1) { + ForEach(nav.forms.jobSelector.last) { job in + Panel.Row( + config: Panel.RowConfiguration( + text: job.name ?? job.jid.string, + action: {nav.session.job = job}, + entity: job, + position: position + ) + ) + } + } } } else { Text("No job selected, or project has no jobs") @@ -166,9 +179,7 @@ struct CompanyPanel: View { } } } - .background( - .white.opacity(0.05) - ) + .background(Theme.rowColour) .frame(minHeight: 300) } } @@ -212,7 +223,7 @@ struct ProjectPanel: View { var body: some View { VStack(alignment: .leading, spacing: 1) { - HStack(alignment: .top) { + HStack(alignment: .center) { Text("Projects").font(.title3) Spacer() FancySimpleButton( @@ -223,6 +234,8 @@ struct ProjectPanel: View { showIcon: true, type: .clear ) + .disabled(nav.forms.jobSelector.middle.isEmpty) + .opacity(nav.forms.jobSelector.middle.isEmpty ? 0.4 : 1) FancySimpleButton( text: "Close", action: closePanel, @@ -242,15 +255,19 @@ struct ProjectPanel: View { if let _ = nav.forms.jobSelector.first { VStack(alignment: .leading, spacing: 1) { if !nav.forms.jobSelector.middle.isEmpty { - ForEach(nav.forms.jobSelector.middle) { project in - Panel.Row( - config: Panel.RowConfiguration( - text: project.name!, - action: {setLastPanel(data: project.jobs!.allObjects)}, - entity: project, - position: position - ) - ) + ScrollView { + VStack(alignment: .leading, spacing: 1) { + ForEach(nav.forms.jobSelector.middle) { project in + Panel.Row( + config: Panel.RowConfiguration( + text: project.name!, + action: {setLastPanel(data: project.jobs!.allObjects)}, + entity: project, + position: position + ) + ) + } + } } } else { Text("No company selected, or company has no projects") @@ -260,9 +277,8 @@ struct ProjectPanel: View { } } } - .background( - .white.opacity(0.05) - ) + .background(nav.forms.jobSelector.middle.isEmpty ? .black.opacity(0.1) : Theme.rowColour) + .foregroundStyle(nav.forms.jobSelector.middle.isEmpty ? .white.opacity(0.4) : .white) .frame(minHeight: 300) } } @@ -270,7 +286,7 @@ struct ProjectPanel: View { extension ProjectPanel { private func setLastPanel(data: [Any]) -> Void { nav.forms.jobSelector.currentPosition = position - nav.forms.jobSelector.last = data as! [Job] + nav.forms.jobSelector.last = (data as! [Job]).sorted(by: {$0.jid < $1.jid}) } private func closePanel() -> Void { @@ -300,7 +316,7 @@ public struct JobPanel: View { public var body: some View { VStack(alignment: .leading, spacing: 1) { - HStack(alignment: .top) { + HStack(alignment: .center) { Text("Jobs").font(.title3) Spacer() FancySimpleButton( @@ -311,6 +327,8 @@ public struct JobPanel: View { showIcon: true, type: .clear ) + .disabled(nav.forms.jobSelector.last.isEmpty) + .opacity(nav.forms.jobSelector.last.isEmpty ? 0.4 : 1) FancySimpleButton( text: "Close", action: closePanel, @@ -330,15 +348,19 @@ public struct JobPanel: View { if let _ = nav.forms.jobSelector.first { VStack(alignment: .leading, spacing: 1) { if !nav.forms.jobSelector.last.isEmpty { - ForEach(nav.forms.jobSelector.last) { job in - Panel.Row( - config: Panel.RowConfiguration( - text: job.name ?? job.jid.string, - action: {nav.session.job = job}, - entity: job, - position: position - ) - ) + ScrollView { + VStack(alignment: .leading, spacing: 1) { + ForEach(nav.forms.jobSelector.last) { job in + Panel.Row( + config: Panel.RowConfiguration( + text: job.name ?? job.jid.string, + action: {nav.session.job = job}, + entity: job, + position: position + ) + ) + } + } } } else { Text("No job selected, or project has no jobs") @@ -348,19 +370,13 @@ public struct JobPanel: View { } } } - .background( - .white.opacity(0.05) - ) + .background(nav.forms.jobSelector.last.isEmpty ? .black.opacity(0.1) : Theme.rowColour) + .foregroundStyle(nav.forms.jobSelector.last.isEmpty ? .white.opacity(0.4) : .white) .frame(minHeight: 300) } } extension JobPanel { - private func setLastPanel(data: [Any]) -> Void { - nav.forms.jobSelector.currentPosition = position - nav.forms.jobSelector.last = data as! [Job] - } - private func closePanel() -> Void { if position == .first { nav.forms.jobSelector.middle = [] diff --git a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift index a22ea424..839c82be 100644 --- a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift +++ b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift @@ -14,28 +14,25 @@ struct ThreePanelGroup: View { public var data: any Collection private var columns: [GridItem] { - return Array(repeating: .init(.flexible(minimum: 100)), count: 3) + return Array(repeating: .init(.flexible(minimum: 100), spacing: 1), count: 3) } @EnvironmentObject private var nav: Navigation var body: some View { VStack(alignment: .leading, spacing: 1) { - HStack { - // Icons - } +// HStack { +// // Icons +// } if orientation == .horizontal { - LazyVGrid(columns: columns, alignment: .leading) { + LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { CompanyPanel(position: .first) ProjectPanel(position: .middle) JobPanel(position: .last) } - .background( - .white.opacity(0.05) - ) - .frame(minHeight: 300) + .frame(height: 300) } else { - LazyHGrid(rows: columns, alignment: .top) { + LazyHGrid(rows: columns, alignment: .top, spacing: 1) { CompanyPanel(position: .first) ProjectPanel(position: .middle) JobPanel(position: .last) From 98f410852e4921ab33d5e659a6466faad0f16ec0 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 19:05:30 -0700 Subject: [PATCH 06/35] UI tweaks --- DLPrototype/Views/Entities/Jobs/JobView.swift | 21 +++++- .../Views/Shared/PanelGroup/Panel.swift | 75 ++++++++++--------- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/DLPrototype/Views/Entities/Jobs/JobView.swift b/DLPrototype/Views/Entities/Jobs/JobView.swift index e49deefe..a5ece45f 100644 --- a/DLPrototype/Views/Entities/Jobs/JobView.swift +++ b/DLPrototype/Views/Entities/Jobs/JobView.swift @@ -22,6 +22,7 @@ struct JobView: View { @State private var validJob: Bool = false @State private var validUrl: Bool = false @State private var isDeleteAlertShowing: Bool = false + @State private var projectPickerDisplayName: String = "Hello" @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation @@ -75,7 +76,16 @@ struct JobView: View { pageType: .projects, sidebar: AnyView(ProjectsDashboardSidebar()) ) + FancySimpleButton( + text: project.name!, + action: actionClearProject, + icon: "xmark", + showLabel: false, + showIcon: true + ) } + } else { + ProjectPickerUsing(onChange: {_, _ in}, displayName: $projectPickerDisplayName) } } @@ -108,7 +118,9 @@ struct JobView: View { .keyboardShortcut("s", modifiers: .command) } } - +} + +extension JobView { private func setEditableValues() -> Void { id = job.jid.string if job.project != nil { @@ -158,4 +170,11 @@ struct JobView: View { nav.setParent(.jobs) nav.setSidebar(AnyView(JobDashboardSidebar())) } + + private func actionClearProject() -> Void { + if let project = job.project { + project.removeFromJobs(job) + PersistenceController.shared.save() + } + } } diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index 26311dcf..023d3399 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -80,16 +80,17 @@ struct CompanyPanel: View { HStack(alignment: .center) { Text("Companies").font(.title3) Spacer() - FancySimpleButton( - text: "Search", - action: {showSearch.toggle()}, - icon: "magnifyingglass", - showLabel: false, - showIcon: true, - type: .clear - ) - .disabled(nav.forms.jobSelector.selected == nil) - .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) + // @TODO: uncomment and implement search +// FancySimpleButton( +// text: "Search", +// action: {showSearch.toggle()}, +// icon: "magnifyingglass", +// showLabel: false, +// showIcon: true, +// type: .clear +// ) +// .disabled(nav.forms.jobSelector.selected == nil) +// .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) FancySimpleButton( text: "Close", action: closePanel, @@ -103,7 +104,7 @@ struct CompanyPanel: View { .padding(10) if showSearch { - SearchBar(text: $searchText) + SearchBar(text: $searchText, onSubmit: search) } if let firstColData = nav.forms.jobSelector.first { @@ -180,7 +181,7 @@ struct CompanyPanel: View { } } .background(Theme.rowColour) - .frame(minHeight: 300) + .frame(height: 300) } } @@ -211,6 +212,10 @@ extension CompanyPanel { showSearch = false } + + private func search() -> Void { + + } } struct ProjectPanel: View { @@ -226,16 +231,17 @@ struct ProjectPanel: View { HStack(alignment: .center) { Text("Projects").font(.title3) Spacer() - FancySimpleButton( - text: "Search", - action: {showSearch.toggle()}, - icon: "magnifyingglass", - showLabel: false, - showIcon: true, - type: .clear - ) - .disabled(nav.forms.jobSelector.middle.isEmpty) - .opacity(nav.forms.jobSelector.middle.isEmpty ? 0.4 : 1) + // @TODO: uncomment and implement search +// FancySimpleButton( +// text: "Search", +// action: {showSearch.toggle()}, +// icon: "magnifyingglass", +// showLabel: false, +// showIcon: true, +// type: .clear +// ) +// .disabled(nav.forms.jobSelector.middle.isEmpty) +// .opacity(nav.forms.jobSelector.middle.isEmpty ? 0.4 : 1) FancySimpleButton( text: "Close", action: closePanel, @@ -279,7 +285,7 @@ struct ProjectPanel: View { } .background(nav.forms.jobSelector.middle.isEmpty ? .black.opacity(0.1) : Theme.rowColour) .foregroundStyle(nav.forms.jobSelector.middle.isEmpty ? .white.opacity(0.4) : .white) - .frame(minHeight: 300) + .frame(height: 300) } } @@ -319,16 +325,17 @@ public struct JobPanel: View { HStack(alignment: .center) { Text("Jobs").font(.title3) Spacer() - FancySimpleButton( - text: "Search", - action: {showSearch.toggle()}, - icon: "magnifyingglass", - showLabel: false, - showIcon: true, - type: .clear - ) - .disabled(nav.forms.jobSelector.last.isEmpty) - .opacity(nav.forms.jobSelector.last.isEmpty ? 0.4 : 1) + // @TODO: uncomment and implement search +// FancySimpleButton( +// text: "Search", +// action: {showSearch.toggle()}, +// icon: "magnifyingglass", +// showLabel: false, +// showIcon: true, +// type: .clear +// ) +// .disabled(nav.forms.jobSelector.last.isEmpty) +// .opacity(nav.forms.jobSelector.last.isEmpty ? 0.4 : 1) FancySimpleButton( text: "Close", action: closePanel, @@ -372,7 +379,7 @@ public struct JobPanel: View { } .background(nav.forms.jobSelector.last.isEmpty ? .black.opacity(0.1) : Theme.rowColour) .foregroundStyle(nav.forms.jobSelector.last.isEmpty ? .white.opacity(0.4) : .white) - .frame(minHeight: 300) + .frame(height: 300) } } From dd88caee205003ac0754fb2549d631f63ca3b51e Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 21:25:47 -0700 Subject: [PATCH 07/35] aabbunch of stuff --- DLPrototype/Validators/JobFormValidator.swift | 1 - .../Views/Entities/Jobs/JobCreate.swift | 2 +- .../Views/Entities/Jobs/JobDashboard.swift | 7 +- DLPrototype/Views/Entities/Jobs/JobView.swift | 105 ++++++++++++------ DLPrototype/Views/Entities/Today/Today.swift | 3 - .../Shared/Fancy/FancyJobActiveToggle.swift | 2 + .../Views/Shared/Fancy/FancyTextField.swift | 7 +- 7 files changed, 86 insertions(+), 41 deletions(-) diff --git a/DLPrototype/Validators/JobFormValidator.swift b/DLPrototype/Validators/JobFormValidator.swift index 7f6f9ae9..b9546aa0 100644 --- a/DLPrototype/Validators/JobFormValidator.swift +++ b/DLPrototype/Validators/JobFormValidator.swift @@ -47,7 +47,6 @@ public final class JobFormValidator { public func onChangeCallback(jobFieldValue: String, valid: Binding?, id: Binding?) -> Void { let filtered = jobFieldValue.filter { "0123456789\\.".contains($0) } - let (jobValid, isCurrent) = JobFormValidator(moc: moc).validateJobId(filtered) if isCurrent { diff --git a/DLPrototype/Views/Entities/Jobs/JobCreate.swift b/DLPrototype/Views/Entities/Jobs/JobCreate.swift index 52610678..dc97a86f 100644 --- a/DLPrototype/Views/Entities/Jobs/JobCreate.swift +++ b/DLPrototype/Views/Entities/Jobs/JobCreate.swift @@ -27,7 +27,6 @@ struct JobCreate: View { @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation @EnvironmentObject public var updater: ViewUpdater -// @StateObject public var jm: CoreDataJob = CoreDataJob(moc: PersistenceController.shared.container.viewContext) var body: some View { VStack(alignment: .leading) { @@ -154,6 +153,7 @@ extension JobCreate { } PersistenceController.shared.save() + nav.session.job = job } private func colourPickerChange(colour: [Double]) -> Void { diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index b361a657..577beb1c 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -176,9 +176,9 @@ struct JobExplorer: View { .foregroundStyle(.white) if editorVisible { - if let job = nav.session.job { + if nav.session.job != nil { VStack { - JobView(job: job) + JobView(job: nav.session.job!) } .padding(5) .background(Theme.rowColour) @@ -198,6 +198,9 @@ struct JobExplorer: View { } } } + .onChange(of: nav.saved) { status in + editorVisible = status + } } init() { diff --git a/DLPrototype/Views/Entities/Jobs/JobView.swift b/DLPrototype/Views/Entities/Jobs/JobView.swift index a5ece45f..4172648c 100644 --- a/DLPrototype/Views/Entities/Jobs/JobView.swift +++ b/DLPrototype/Views/Entities/Jobs/JobView.swift @@ -28,6 +28,8 @@ struct JobView: View { @EnvironmentObject public var nav: Navigation @EnvironmentObject public var updater: ViewUpdater + @FocusState private var focused: Bool + var body: some View { VStack(alignment: .leading) { fieldProjectLink @@ -44,7 +46,7 @@ struct JobView: View { } .padding(5) .onAppear(perform: setEditableValues) - .onChange(of: job) { _ in + .onChange(of: nav.session.job) { _ in setEditableValues() } .onChange(of: id) { jobId in @@ -53,6 +55,11 @@ struct JobView: View { valid: $validJob, id: $id ) + + if validJob { + job.jid = Double(jobId) ?? 0.0 + PersistenceController.shared.save() + } } .onChange(of: url) { newUrl in JobFormValidator(moc: moc).onChangeCallback( @@ -60,6 +67,24 @@ struct JobView: View { valid: $validUrl, id: $id ) + + if validUrl { + job.uri = URL(string: newUrl) + PersistenceController.shared.save() + } + } + .onChange(of: alive) { status in + job.alive = status + PersistenceController.shared.save() + } + .onChange(of: shredable) { status in + job.shredable = status + PersistenceController.shared.save() + } + .onChange(of: nav.saved) { status in + if status { + self.update() + } } } @@ -90,48 +115,61 @@ struct JobView: View { } @ViewBuilder private var buttonSubmit: some View { - HStack { - FancyButtonv2( - text: "Delete", - action: {isDeleteAlertShowing = true}, - icon: "trash", - showLabel: false, - type: .destructive - ) - .alert("Are you sure you want to delete job ID \(job.jid.string)?", isPresented: $isDeleteAlertShowing) { - Button("Yes", role: .destructive) { - hardDelete() + ZStack { + HStack { + FancyButtonv2( + text: "Delete", + action: {isDeleteAlertShowing = true}, + icon: "trash", + showLabel: false, + type: .destructive + ) + .alert("Are you sure you want to delete job ID \(job.jid.string)?", isPresented: $isDeleteAlertShowing) { + Button("Yes", role: .destructive) { + hardDelete() + } + Button("No", role: .cancel) {} } - Button("No", role: .cancel) {} + + Spacer() + FancyButtonv2( + text: "Update", + action: update, + size: .medium, + type: .primary + ) + .keyboardShortcut("s", modifiers: .command) } - - Spacer() - FancyButtonv2( - text: "Update", - action: update, - size: .medium, - type: .primary, - redirect: AnyView(JobDashboard()), - pageType: .jobs, - sidebar: AnyView(JobDashboardSidebar()) - ) - .keyboardShortcut("s", modifiers: .command) } } } extension JobView { private func setEditableValues() -> Void { - id = job.jid.string - if job.project != nil { - pName = job.project!.name! - pId = String(job.project!.pid) - } + if let job = nav.session.job { + id = job.jid.string + if job.project != nil { + pName = job.project!.name! + pId = String(job.project!.pid) + } - if job.uri != nil { - url = job.uri!.description + if job.uri != nil { + url = job.uri!.description + } else { + url = "" + } } else { - url = "" + id = job.jid.string + if job.project != nil { + pName = job.project!.name! + pId = String(job.project!.pid) + } + + if job.uri != nil { + url = job.uri!.description + } else { + url = "" + } } } @@ -153,6 +191,7 @@ extension JobView { PersistenceController.shared.save() updater.update() + nav.save() } private func softDelete() -> Void { diff --git a/DLPrototype/Views/Entities/Today/Today.swift b/DLPrototype/Views/Entities/Today/Today.swift index 8e53eb28..dc57c223 100644 --- a/DLPrototype/Views/Entities/Today/Today.swift +++ b/DLPrototype/Views/Entities/Today/Today.swift @@ -13,9 +13,6 @@ struct Today: View { @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation - @FocusState private var primaryTextFieldInFocus: Bool - - // MARK: body view var body: some View { VStack(alignment: .leading) { PostingInterface() diff --git a/DLPrototype/Views/Shared/Fancy/FancyJobActiveToggle.swift b/DLPrototype/Views/Shared/Fancy/FancyJobActiveToggle.swift index 8f54cf78..63a1c6e6 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyJobActiveToggle.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyJobActiveToggle.swift @@ -13,6 +13,7 @@ struct FancyJobActiveToggle: View { public var label: String = "Active" @State private var alive: Bool = true + @FocusState private var focused: Bool var body: some View { HStack { @@ -25,6 +26,7 @@ struct FancyJobActiveToggle: View { } .padding() .background(Theme.textBackground) + .focused($focused) } .onAppear(perform: { if entity.alive { diff --git a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift index 6aeb57f6..95521e8c 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift @@ -23,7 +23,9 @@ struct FancyTextField: View { @Binding public var text: String @AppStorage("enableAutoCorrection") public var enableAutoCorrection: Bool = false - + + @FocusState public var focused: Bool + var body: some View { HStack(spacing: 5) { if showLabel { @@ -57,6 +59,7 @@ struct FancyTextField: View { .disabled(disabled ?? false) .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) + .focused($focused) } private var oneBigLine: some View { @@ -71,6 +74,7 @@ struct FancyTextField: View { .disabled(disabled ?? false) .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) + .focused($focused) } private var multiLine: some View { @@ -86,6 +90,7 @@ struct FancyTextField: View { .disabled(disabled ?? false) .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) + .focused($focused) } private func reset() -> Void { From abe877859406fabb43d7be1c8db2788432ca1eb9 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 23:13:54 -0700 Subject: [PATCH 08/35] added session inspector panel. will become useful in future releases but is basically just dumping debug strings for now --- DLPrototype.xcodeproj/project.pbxproj | 4 +++ DLPrototype/Views/Home/Home.swift | 5 +++ .../Views/Settings/Tabs/GeneralSettings.swift | 3 +- .../Views/Shared/SessionInspector.swift | 36 +++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 DLPrototype/Views/Shared/SessionInspector.swift diff --git a/DLPrototype.xcodeproj/project.pbxproj b/DLPrototype.xcodeproj/project.pbxproj index 0c6acc74..053e2a70 100644 --- a/DLPrototype.xcodeproj/project.pbxproj +++ b/DLPrototype.xcodeproj/project.pbxproj @@ -183,6 +183,7 @@ 53E918792B43DD4200912C6F /* List.md in Resources */ = {isa = PBXBuildFile; fileRef = 53E918782B43DCB600912C6F /* List.md */; }; 53E9187B2B47C9A400912C6F /* ThreePanelGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E9187A2B47C9A400912C6F /* ThreePanelGroup.swift */; }; 53E9187F2B47C9E300912C6F /* Panel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E9187E2B47C9E300912C6F /* Panel.swift */; }; + 53E918832B4A6D9600912C6F /* SessionInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E918822B4A6D9600912C6F /* SessionInspector.swift */; }; 53ECD0572971F8F400A09AAB /* CoreDataRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ECD0562971F8F400A09AAB /* CoreDataRecords.swift */; }; 53EDDF9E29634852008D34C7 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EDDF9D29634852008D34C7 /* Entry.swift */; }; 53EDDFA02963487A008D34C7 /* CustomPickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EDDF9F2963487A008D34C7 /* CustomPickerItem.swift */; }; @@ -404,6 +405,7 @@ 53E918782B43DCB600912C6F /* List.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = List.md; sourceTree = ""; }; 53E9187A2B47C9A400912C6F /* ThreePanelGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreePanelGroup.swift; sourceTree = ""; }; 53E9187E2B47C9E300912C6F /* Panel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panel.swift; sourceTree = ""; }; + 53E918822B4A6D9600912C6F /* SessionInspector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionInspector.swift; sourceTree = ""; }; 53ECD0562971F8F400A09AAB /* CoreDataRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataRecords.swift; sourceTree = ""; }; 53EDDF9D29634852008D34C7 /* Entry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = ""; }; 53EDDF9F2963487A008D34C7 /* CustomPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPickerItem.swift; sourceTree = ""; }; @@ -1086,6 +1088,7 @@ 53561F8429F1FC7900D828AD /* ProjectPickerUsing.swift */, 53790C042A72F45900D3FFD4 /* EdgeBorder.swift */, 5326AE992B27FFB8009F1349 /* CompanyPicker.swift */, + 53E918822B4A6D9600912C6F /* SessionInspector.swift */, ); path = Shared; sourceTree = ""; @@ -1421,6 +1424,7 @@ 5334F2742B2B958D0079D2E7 /* FancyJobSredToggle.swift in Sources */, 5326AE982B27FA02009F1349 /* DefaultCompanySidebar.swift in Sources */, 537AEB9F29627DCC00385787 /* LogTable.swift in Sources */, + 53E918832B4A6D9600912C6F /* SessionInspector.swift in Sources */, 5335A5AE296CFF83000051B1 /* FileHelper.swift in Sources */, 53E202612A7EC96D00B4DF70 /* DashboardSidebar.swift in Sources */, 532A722F299037AC0038C18B /* RecordResult.swift in Sources */, diff --git a/DLPrototype/Views/Home/Home.swift b/DLPrototype/Views/Home/Home.swift index 92d3cb33..5a3ad228 100644 --- a/DLPrototype/Views/Home/Home.swift +++ b/DLPrototype/Views/Home/Home.swift @@ -18,6 +18,7 @@ struct Home: View { @AppStorage("isDatePickerPresented") public var isDatePickerPresented: Bool = false @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("general.showSessionInspector") public var showSessionInspector: Bool = false private var buttons: [PageGroup: [SidebarButton]] { [ @@ -185,6 +186,10 @@ struct Home: View { } } } + + if showSessionInspector { + SessionInspector() + } } } } diff --git a/DLPrototype/Views/Settings/Tabs/GeneralSettings.swift b/DLPrototype/Views/Settings/Tabs/GeneralSettings.swift index 60a5ce11..99001e3b 100644 --- a/DLPrototype/Views/Settings/Tabs/GeneralSettings.swift +++ b/DLPrototype/Views/Settings/Tabs/GeneralSettings.swift @@ -15,6 +15,7 @@ struct GeneralSettings: View { @AppStorage("dashboard.maxYearsPastInHistory") public var maxYearsPastInHistory: Int = 5 @AppStorage("general.syncColumns") public var syncColumns: Bool = false @AppStorage("general.defaultCompany") public var defaultCompany: Int = 0 + @AppStorage("general.showSessionInspector") public var showSessionInspector: Bool = false var body: some View { Form { @@ -28,7 +29,7 @@ struct GeneralSettings: View { Toggle("Enable experimental features (EXERCISE CAUTION)", isOn: $showExperimentalFeatures) if showExperimentalFeatures { - Text("There are no experimental features at this time") + Toggle("Enable SessionInspector panel", isOn: $showSessionInspector) } } diff --git a/DLPrototype/Views/Shared/SessionInspector.swift b/DLPrototype/Views/Shared/SessionInspector.swift new file mode 100644 index 00000000..c53b371c --- /dev/null +++ b/DLPrototype/Views/Shared/SessionInspector.swift @@ -0,0 +1,36 @@ +// +// SessionInspectorWidget.swift +// DLPrototype +// +// Created by Ryan Priebe on 2024-01-06. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI + +struct SessionInspector: View { + @EnvironmentObject private var nav: Navigation + + @State private var form: Navigation.Forms.JobSelectorForm? = nil + + var body: some View { + VStack(alignment: .leading) { + ScrollView { + if form != nil { + Text("Panel: selected items: \(form!.selectedItems.count)") + } + + Text(form.debugDescription) + } + } + .padding() + .onChange(of: nav.forms.jobSelector.selected) { _ in + form = nav.forms.jobSelector + } + .frame(width: 500) + } +} + +extension SessionInspector { + +} From 8df3a850b173f541bc5ab2d8b2aee4d734920da9 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 23:15:00 -0700 Subject: [PATCH 09/35] job explorer UX improvements. each column can have 1 highlighted value now. the job form rewrite continues --- DLPrototype/Utils/Navigation.swift | 1 + DLPrototype/Views/Entities/Jobs/JobView.swift | 10 +++--- .../Shared/Fancy/FancyJobSredToggle.swift | 6 ++-- .../Views/Shared/PanelGroup/Panel.swift | 33 ++++++++++++++----- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 55f84090..4c7f0950 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -208,6 +208,7 @@ extension Navigation { var middle: [Project] = [] var last: [Job] = [] var selected: Panel.SelectedValueCoordinates? = nil + var selectedItems: [Panel.SelectedValueCoordinates] = [] } } diff --git a/DLPrototype/Views/Entities/Jobs/JobView.swift b/DLPrototype/Views/Entities/Jobs/JobView.swift index 4172648c..a27debe1 100644 --- a/DLPrototype/Views/Entities/Jobs/JobView.swift +++ b/DLPrototype/Views/Entities/Jobs/JobView.swift @@ -35,11 +35,13 @@ struct JobView: View { fieldProjectLink FancyTextField(placeholder: "URL", lineLimit: 1, onSubmit: {}, showLabel: true, text: $url) - .background(validUrl ? Color.clear : Color.red) + .background(validUrl ? .clear : .red) FancyTextField(placeholder: "Job ID", lineLimit: 1, onSubmit: {}, showLabel: true, text: $id) - .background(validJob ? Color.clear : Color.red) + .background(validJob ? .clear : .red) FancyJobActiveToggle(entity: job) + .background(job.alive ? .green : .clear) FancyJobSredToggle(entity: job) + .background(job.shredable ? .green : .clear) FancyColourPicker(initialColour: job.colour ?? Theme.rowColourAsDouble, onChange: {newColour in colour = newColour}) Spacer() buttonSubmit @@ -77,10 +79,6 @@ struct JobView: View { job.alive = status PersistenceController.shared.save() } - .onChange(of: shredable) { status in - job.shredable = status - PersistenceController.shared.save() - } .onChange(of: nav.saved) { status in if status { self.update() diff --git a/DLPrototype/Views/Shared/Fancy/FancyJobSredToggle.swift b/DLPrototype/Views/Shared/Fancy/FancyJobSredToggle.swift index f7066c1e..63064288 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyJobSredToggle.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyJobSredToggle.swift @@ -34,8 +34,10 @@ struct FancyJobSredToggle: View { } entity.shredable = shredable - - PersistenceController.shared.save() }) + .onChange(of: shredable) { bval in + entity.shredable = bval + PersistenceController.shared.save() + } } } diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index 023d3399..005c393c 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -18,7 +18,7 @@ public struct Panel { case first, middle, last } - public struct SelectedValueCoordinates { + public struct SelectedValueCoordinates: Equatable { var position: Position var item: NSManagedObject } @@ -32,9 +32,7 @@ public struct Panel { struct Row: View { public var config: RowConfiguration - - @State private var highlighted: Bool = false - + @EnvironmentObject private var nav: Navigation var body: some View { @@ -48,8 +46,8 @@ public struct Panel { Image(systemName: config.position == .last ? "hammer" : "arrow.right") } .padding(10) - .background(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .orange : .clear : .clear) - .foregroundStyle(nav.forms.jobSelector.selected != nil ? nav.forms.jobSelector.selected!.item == config.entity ? .black : .white : .white) + .background(nav.forms.jobSelector.selectedItems.contains(where: {$0.item == config.entity}) ? .orange : .clear) + .foregroundStyle(nav.forms.jobSelector.selectedItems.contains(where: {$0.item == config.entity}) ? .black : .white) ), size: .link, type: .clear @@ -61,8 +59,25 @@ public struct Panel { extension Panel.Row { private func fireCallback() -> Void { - highlighted = true nav.forms.jobSelector.selected = Panel.SelectedValueCoordinates(position: config.position, item: config.entity) + + // changes which entity is highlighted in each column + var items = nav.forms.jobSelector.selectedItems + if items.isEmpty { + items.append(nav.forms.jobSelector.selected!) + } else { + if !items.contains(where: {$0.position == config.position}) { + items.append(nav.forms.jobSelector.selected!) + } else { + for (offset, _) in items.enumerated() { + if items[offset].position == config.position { + items[offset].item = config.entity + } + } + } + } + nav.forms.jobSelector.selectedItems = items + config.action() } } @@ -188,13 +203,13 @@ struct CompanyPanel: View { extension CompanyPanel { private func setMiddlePanel(data: [Any]) -> Void { nav.forms.jobSelector.currentPosition = position - nav.forms.jobSelector.middle = data as! [Project] + nav.forms.jobSelector.middle = (data as! [Project]).sorted(by: {$0.name! < $1.name!}) nav.forms.jobSelector.last = [] } private func setLastPanel(data: [Any]) -> Void { nav.forms.jobSelector.currentPosition = position - nav.forms.jobSelector.last = data as! [Job] + nav.forms.jobSelector.last = (data as! [Job]).sorted(by: {$0.jid < $1.jid}) } private func closePanel() -> Void { From 2a27652738e9a188616407fc71f8f73eeaf7f34e Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 6 Jan 2024 23:43:56 -0700 Subject: [PATCH 10/35] UI tweaks --- DLPrototype/Utils/Navigation.swift | 4 +-- .../Views/Entities/Jobs/JobDashboard.swift | 2 +- DLPrototype/Views/Entities/Jobs/JobView.swift | 8 ++--- .../Views/Shared/PanelGroup/Panel.swift | 34 +++++++++++-------- .../Views/Shared/SessionInspector.swift | 2 +- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 4c7f0950..15f7fb73 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -207,8 +207,8 @@ extension Navigation { var first: FetchedResults? = nil var middle: [Project] = [] var last: [Job] = [] - var selected: Panel.SelectedValueCoordinates? = nil - var selectedItems: [Panel.SelectedValueCoordinates] = [] + var tmp: Panel.SelectedValueCoordinates? = nil + var selected: [Panel.SelectedValueCoordinates] = [] } } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 577beb1c..b9115064 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -144,7 +144,7 @@ struct JobExplorer: View { .padding(5) .background(.white.opacity(0.2)) .foregroundStyle(.white) - + if explorerVisible { ThreePanelGroup(orientation: .horizontal, data: companies) } diff --git a/DLPrototype/Views/Entities/Jobs/JobView.swift b/DLPrototype/Views/Entities/Jobs/JobView.swift index a27debe1..d8d12d97 100644 --- a/DLPrototype/Views/Entities/Jobs/JobView.swift +++ b/DLPrototype/Views/Entities/Jobs/JobView.swift @@ -35,13 +35,13 @@ struct JobView: View { fieldProjectLink FancyTextField(placeholder: "URL", lineLimit: 1, onSubmit: {}, showLabel: true, text: $url) - .background(validUrl ? .clear : .red) + .background(validUrl ? .clear : .red) // @TODO: remove, this looks terrible FancyTextField(placeholder: "Job ID", lineLimit: 1, onSubmit: {}, showLabel: true, text: $id) - .background(validJob ? .clear : .red) + .background(validJob ? .clear : .red) // @TODO: remove, this looks terrible FancyJobActiveToggle(entity: job) - .background(job.alive ? .green : .clear) + .background(job.alive ? .green : .clear) // @TODO: remove, this looks terrible FancyJobSredToggle(entity: job) - .background(job.shredable ? .green : .clear) + .background(job.shredable ? .green : .clear) // @TODO: remove, this looks terrible FancyColourPicker(initialColour: job.colour ?? Theme.rowColourAsDouble, onChange: {newColour in colour = newColour}) Spacer() buttonSubmit diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index 005c393c..eabf85a0 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -28,6 +28,8 @@ public struct Panel { var action: () -> Void var entity: NSManagedObject var position: Panel.Position + var special: Bool = false + var specialIcon: String = "" } struct Row: View { @@ -41,13 +43,17 @@ public struct Panel { action: fireCallback, labelView: AnyView( HStack { + if config.special { + Image(systemName: config.specialIcon) + .help("This is your default company. Change this in Settings") + } Text(config.text) Spacer() Image(systemName: config.position == .last ? "hammer" : "arrow.right") } .padding(10) - .background(nav.forms.jobSelector.selectedItems.contains(where: {$0.item == config.entity}) ? .orange : .clear) - .foregroundStyle(nav.forms.jobSelector.selectedItems.contains(where: {$0.item == config.entity}) ? .black : .white) + .background(nav.forms.jobSelector.selected.contains(where: {$0.item == config.entity}) ? .orange : .clear) + .foregroundStyle(nav.forms.jobSelector.selected.contains(where: {$0.item == config.entity}) ? .black : .white) ), size: .link, type: .clear @@ -59,15 +65,15 @@ public struct Panel { extension Panel.Row { private func fireCallback() -> Void { - nav.forms.jobSelector.selected = Panel.SelectedValueCoordinates(position: config.position, item: config.entity) + let selected = Panel.SelectedValueCoordinates(position: config.position, item: config.entity) // changes which entity is highlighted in each column - var items = nav.forms.jobSelector.selectedItems + var items = nav.forms.jobSelector.selected if items.isEmpty { - items.append(nav.forms.jobSelector.selected!) + items.append(selected) } else { if !items.contains(where: {$0.position == config.position}) { - items.append(nav.forms.jobSelector.selected!) + items.append(selected) } else { for (offset, _) in items.enumerated() { if items[offset].position == config.position { @@ -76,7 +82,7 @@ extension Panel.Row { } } } - nav.forms.jobSelector.selectedItems = items + nav.forms.jobSelector.selected = items config.action() } @@ -104,8 +110,6 @@ struct CompanyPanel: View { // showIcon: true, // type: .clear // ) -// .disabled(nav.forms.jobSelector.selected == nil) -// .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) FancySimpleButton( text: "Close", action: closePanel, @@ -113,8 +117,6 @@ struct CompanyPanel: View { showLabel: false, showIcon: true ) - .disabled(nav.forms.jobSelector.selected == nil) - .opacity(nav.forms.jobSelector.selected == nil ? 0.4 : 1) } .padding(10) @@ -134,7 +136,9 @@ struct CompanyPanel: View { text: company.name!, action: {setMiddlePanel(data: company.projects!.allObjects)}, entity: company, - position: position + position: position, + special: company.isDefault, + specialIcon: "building.2" ) ) } @@ -216,7 +220,7 @@ extension CompanyPanel { if position == .first { nav.forms.jobSelector.middle = [] nav.forms.jobSelector.last = [] - nav.forms.jobSelector.selected = nil + nav.forms.jobSelector.selected = [] } else if position == .middle { nav.forms.jobSelector.middle = [] nav.forms.jobSelector.last = [] @@ -314,7 +318,7 @@ extension ProjectPanel { if position == .first { nav.forms.jobSelector.middle = [] nav.forms.jobSelector.last = [] - nav.forms.jobSelector.selected = nil + nav.forms.jobSelector.selected = [] } else if position == .middle { nav.forms.jobSelector.middle = [] nav.forms.jobSelector.last = [] @@ -403,7 +407,7 @@ extension JobPanel { if position == .first { nav.forms.jobSelector.middle = [] nav.forms.jobSelector.last = [] - nav.forms.jobSelector.selected = nil + nav.forms.jobSelector.selected = [] } else if position == .middle { nav.forms.jobSelector.middle = [] nav.forms.jobSelector.last = [] diff --git a/DLPrototype/Views/Shared/SessionInspector.swift b/DLPrototype/Views/Shared/SessionInspector.swift index c53b371c..6502c48d 100644 --- a/DLPrototype/Views/Shared/SessionInspector.swift +++ b/DLPrototype/Views/Shared/SessionInspector.swift @@ -17,7 +17,7 @@ struct SessionInspector: View { VStack(alignment: .leading) { ScrollView { if form != nil { - Text("Panel: selected items: \(form!.selectedItems.count)") + Text("Panel: selected items: \(form!.selected.count)") } Text(form.debugDescription) From a9e9487b2c089e85d5809ca768f00b7f6e29d6c1 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Mon, 8 Jan 2024 19:23:04 -0700 Subject: [PATCH 11/35] in progress field generration --- DLPrototype.xcodeproj/project.pbxproj | 8 ++ .../Note.xcdatamodel/contents | 5 +- DLPrototype/Extensions/Models/Job.swift | 22 +++++ DLPrototype/Extensions/Models/Project.swift | 19 ++++ DLPrototype/Utils/Navigation.swift | 93 ++++++++++++++++++- .../Views/Entities/Jobs/JobDashboard.swift | 42 ++++++++- .../Shared/Fancy/FancyColourPicker.swift | 16 +++- .../Views/Shared/Fancy/FancyTextField.swift | 19 +++- .../Views/Shared/Fancy/FancyToggle.swift | 31 +++++++ .../Views/Shared/PanelGroup/Panel.swift | 15 +-- 10 files changed, 247 insertions(+), 23 deletions(-) create mode 100644 DLPrototype/Extensions/Models/Project.swift create mode 100644 DLPrototype/Views/Shared/Fancy/FancyToggle.swift diff --git a/DLPrototype.xcodeproj/project.pbxproj b/DLPrototype.xcodeproj/project.pbxproj index 053e2a70..fdda1889 100644 --- a/DLPrototype.xcodeproj/project.pbxproj +++ b/DLPrototype.xcodeproj/project.pbxproj @@ -127,6 +127,8 @@ 537AEBA1296287B900385787 /* LogRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537AEBA0296287B900385787 /* LogRow.swift */; }; 53814A8B2A95151000A5A015 /* CoreDataPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53814A8A2A95151000A5A015 /* CoreDataPlan.swift */; }; 53814A8E2A9821C800A5A015 /* DefaultPlanningSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53814A8D2A9821C800A5A015 /* DefaultPlanningSidebar.swift */; }; + 53842B272B4C82B20029AC73 /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53842B262B4C82B20029AC73 /* Project.swift */; }; + 53842B2A2B4C9B100029AC73 /* FancyToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53842B292B4C9B100029AC73 /* FancyToggle.swift */; }; 53959A1A297377A7007A2549 /* FancyGenericToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53959A19297377A7007A2549 /* FancyGenericToolbar.swift */; }; 53959A1D2973897F007A2549 /* ProjectConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53959A1C2973897F007A2549 /* ProjectConfig.swift */; }; 53959A1F29738AAA007A2549 /* FancySubTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53959A1E29738AAA007A2549 /* FancySubTitle.swift */; }; @@ -349,6 +351,8 @@ 537AEBA0296287B900385787 /* LogRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRow.swift; sourceTree = ""; }; 53814A8A2A95151000A5A015 /* CoreDataPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataPlan.swift; sourceTree = ""; }; 53814A8D2A9821C800A5A015 /* DefaultPlanningSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultPlanningSidebar.swift; sourceTree = ""; }; + 53842B262B4C82B20029AC73 /* Project.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = ""; }; + 53842B292B4C9B100029AC73 /* FancyToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyToggle.swift; sourceTree = ""; }; 53959A19297377A7007A2549 /* FancyGenericToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyGenericToolbar.swift; sourceTree = ""; }; 53959A1C2973897F007A2549 /* ProjectConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectConfig.swift; sourceTree = ""; }; 53959A1E29738AAA007A2549 /* FancySubTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancySubTitle.swift; sourceTree = ""; }; @@ -497,6 +501,7 @@ children = ( 5331FCB22A9E6ECE005CDE0E /* Plan.swift */, 531EB6E72A9FDA5A00B3059C /* Job.swift */, + 53842B262B4C82B20029AC73 /* Project.swift */, ); path = Models; sourceTree = ""; @@ -879,6 +884,7 @@ 5334F2732B2B958D0079D2E7 /* FancyJobSredToggle.swift */, 53C000972B3A821D00D5EC04 /* FancyHelpText.swift */, 53BDA6962B41CA1C00A61BE8 /* FancySimpleButton.swift */, + 53842B292B4C9B100029AC73 /* FancyToggle.swift */, ); path = Fancy; sourceTree = ""; @@ -1270,6 +1276,7 @@ 5372C02A2A105AA1008CB120 /* FancyChip.swift in Sources */, 537628B429665F4E00DE8ECF /* Data.xcdatamodeld in Sources */, 53F5896429983FA300843B32 /* ProjectResult.swift in Sources */, + 53842B272B4C82B20029AC73 /* Project.swift in Sources */, 53EDDF9E29634852008D34C7 /* Entry.swift in Sources */, 53C000A82B41438C00D5EC04 /* NoteCreateSidebar.swift in Sources */, 53E202632A7ECC6500B4DF70 /* TodayInHistoryWidget.swift in Sources */, @@ -1437,6 +1444,7 @@ 53152CD4296A296A00A14E43 /* FancyButton.swift in Sources */, 5354C8E62AA03C40001C1779 /* Planning.Group.swift in Sources */, 5371BA372A7B412300DEEC21 /* TodaySidebar.swift in Sources */, + 53842B2A2B4C9B100029AC73 /* FancyToggle.swift in Sources */, 5363B6782A6BB2CC00C2FBB8 /* CompanyDashboard.swift in Sources */, 53E202562A7E0F1700B4DF70 /* ProjectsDashboardSidebar.swift in Sources */, 532A722B298F1CB70038C18B /* Results.swift in Sources */, diff --git a/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents b/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents index 8f6f0791..b7ffcc6c 100644 --- a/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents +++ b/DLPrototype/CoreData/Data.xcdatamodeld/Note.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -37,8 +37,9 @@ - + + diff --git a/DLPrototype/Extensions/Models/Job.swift b/DLPrototype/Extensions/Models/Job.swift index 4c007352..eb961fbd 100644 --- a/DLPrototype/Extensions/Models/Job.swift +++ b/DLPrototype/Extensions/Models/Job.swift @@ -9,6 +9,8 @@ import SwiftUI extension Job { + typealias Field = Navigation.Forms.Field + var idInt: Int { Int(exactly: jid.rounded(.toNearestOrEven)) ?? 0 } var backgroundColor: Color { @@ -21,6 +23,10 @@ extension Job { var foregroundColor: Color { self.backgroundColor.isBright() ? .black : .white } +// public static let attributes : [KeyPath] = [ +// \.name!, \.created! +// ] + // @TODO: the following are dupes and should be deprecated then removed func id_int() -> Int { return Int(exactly: jid.rounded(.toNearestOrEven)) ?? 0 @@ -38,4 +44,20 @@ extension Job { func fgColour() -> Color { return self.colour_from_stored().isBright() ? .black : .white } + + /// Create form fields for the important properties and relationships + /// - Returns: [Field] + func fields() -> [Field] { + var fields: [Field] = [] + + fields.append(Field(type: .text, label: "Job ID", value: self.jid.string, keyPath: \Job.title)) + fields.append(Field(type: .text, label: "Title", value: self.title, keyPath: \Job.title)) + fields.append(Field(type: .boolean, label: "Published", value: self.alive, keyPath: \Job.title)) + fields.append(Field(type: .text, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), keyPath: \Job.title)) + fields.append(Field(type: .text, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), keyPath: \Job.title)) + fields.append(Field(type: .colour, label: "Colour", value: self.colour, keyPath: \Job.title)) + fields.append(Field(type: .editor, label: "Description", value: self.overview, keyPath: \Job.title)) + + return fields + } } diff --git a/DLPrototype/Extensions/Models/Project.swift b/DLPrototype/Extensions/Models/Project.swift new file mode 100644 index 00000000..7d870a27 --- /dev/null +++ b/DLPrototype/Extensions/Models/Project.swift @@ -0,0 +1,19 @@ +// +// Project.swift +// DLPrototype +// +// Created by Ryan Priebe on 2024-01-08. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI + +extension Project { +// func field() -> Navigation.Forms.Field { +// let field = Navigation.Forms.Field(type: .dropdown) +// +// +// +// return field +// } +} diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 15f7fb73..a6b67f91 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -92,7 +92,7 @@ public class Navigation: Identifiable, ObservableObject { @Published public var session: Session = Session() @Published public var planning: PlanningState = PlanningState(moc: PersistenceController.shared.container.viewContext) @Published public var saved: Bool = false - @Published public var state: State = State() +// @Published public var state: State = State() @Published public var history: History = History() @Published public var forms: Forms = Forms() @@ -150,7 +150,19 @@ public class Navigation: Identifiable, ObservableObject { self.saved = false } } - + + public func save(updatedValue: String, callback: ((String) -> Void)? = nil) -> Void { + self.saved = true + + if let cb = callback { + cb(updatedValue) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) { + self.saved = false + } + } + // @TODO: this doesn't really work yet public func to(_ page: Page) -> Void { let hp = self.history.get(page: page) @@ -171,7 +183,7 @@ public class Navigation: Identifiable, ObservableObject { setId() inspector = nil session = Session() - state = State() +// state = State() history = History() forms = Forms() } @@ -207,12 +219,83 @@ extension Navigation { var first: FetchedResults? = nil var middle: [Project] = [] var last: [Job] = [] - var tmp: Panel.SelectedValueCoordinates? = nil var selected: [Panel.SelectedValueCoordinates] = [] + var editor: Editor = Editor() + + struct Editor { + var jid: String? = "" + var title: String? = "" + } + } + + public class Field: Identifiable { + public let id: UUID = UUID() + public var body: some View { Layout(field: self) } + public var label: String = "" + public var value: Any? = nil + public var keyPath: AnyKeyPath + private var type: LayoutType = .text + + init(type: LayoutType, label: String, value: Any? = nil, keyPath: AnyKeyPath) { + self.type = type + self.label = label + self.value = value + self.keyPath = keyPath + } + + public enum LayoutType { + case text, dropdown, date, boolean, colour, editor + } + + struct Layout: View { + public var field: Field + + @State private var bValue: String = "" + + @EnvironmentObject private var nav: Navigation + + var body: some View { + GridRow(alignment: field.type == .editor ? .top : .center) { + HStack { + VStack(alignment: .trailing) { + HStack(alignment: .top) { + Spacer() + // @TODO: convert to button which changes focus state + Text(field.label) + .padding(5) + } + } + .frame(width: 130) + + VStack { + switch field.type { + case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool) + case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) + case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) + default: + FancyTextField(placeholder: self.field.label, text: $bValue) + } + } + Spacer() + } + } + .onAppear(perform: onLoad) + } + + private func onLoad() -> Void { + if let value = self.field.value as? String { + bValue = value + } + } + + private func onChange(colour: Color) -> Void { + + } + } } } - public struct State { + public struct ApplicationViewState { private var phase: Phase = .ready private var hierarchy: [Phase] = [.ready, .transitioning, .complete] diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index b9115064..12d8609f 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -178,7 +178,8 @@ struct JobExplorer: View { if editorVisible { if nav.session.job != nil { VStack { - JobView(job: nav.session.job!) +// JobView(job: nav.session.job!) + JobViewRedux(job: nav.session.job!) } .padding(5) .background(Theme.rowColour) @@ -198,12 +199,45 @@ struct JobExplorer: View { } } } - .onChange(of: nav.saved) { status in - editorVisible = status - } +// .onChange(of: nav.saved) { status in +// editorVisible = status +// } } init() { _companies = CoreDataCompanies.all() } + + public struct JobViewRedux: View { + public var job: Job + private var fields: [Navigation.Forms.Field] { job.fields() } + + @EnvironmentObject private var nav: Navigation + + var body: some View { + Grid(alignment: .topLeading, horizontalSpacing: 1, verticalSpacing: 10) { + VStack { + ForEach(fields) { field in + field.body + } + } + .padding([.top, .bottom], 8) + } + .background(Theme.toolbarColour) + .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) + .padding(8) + .onChange(of: nav.saved) { status in + if status { +// job.title = nav.forms.jobSelector.editor.title +// job.setValuesForKeys([String: Any]) // @TODO: this is promising +// for field in fields { +// if field.label == "Title" { +// job.value(forKeyPath: "title") +// job.value +// } +// } + } + } + } + } } diff --git a/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift b/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift index 505e0ea3..5cdfdb65 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift @@ -11,14 +11,17 @@ import SwiftUI struct FancyColourPicker: View { public let initialColour: [Double] public let onChange: ((Color) -> Void) + public let showLabel: Bool @State private var asColor: Color @State private var asString: String = "" var body: some View { HStack(spacing: 0) { - FancyLabel(text: "Colour") - .padding([.trailing], 10) + if showLabel { + FancyLabel(text: "Colour") + .padding([.trailing], 10) + } ColorPicker("", selection: $asColor) .rotationEffect(.degrees(90)) @@ -46,9 +49,16 @@ struct FancyColourPicker: View { } extension FancyColourPicker { - init(initialColour: [Double], onChange: @escaping ((Color) -> Void)) { + init(initialColour: [Double], onChange: @escaping ((Color) -> Void), showLabel: Bool? = true) { self.initialColour = initialColour self.onChange = onChange + + if let shouldShowLabel = showLabel { + self.showLabel = shouldShowLabel + } else { + self.showLabel = true + } + asColor = Color.fromStored(initialColour) } diff --git a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift index 95521e8c..69361b7b 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift @@ -19,11 +19,13 @@ struct FancyTextField: View { public var bgColour: Color? = Theme.textBackground public var showLabel: Bool = false public var font: Font = Theme.fontTextField - + @Binding public var text: String @AppStorage("enableAutoCorrection") public var enableAutoCorrection: Bool = false + @EnvironmentObject private var nav: Navigation + @FocusState public var focused: Bool var body: some View { @@ -60,6 +62,11 @@ struct FancyTextField: View { .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) .focused($focused) + .onChange(of: focused) { state in + if !state { + nav.save(callback: setValues) + } + } } private var oneBigLine: some View { @@ -92,10 +99,18 @@ struct FancyTextField: View { .textSelection(.enabled) .focused($focused) } - +} + +extension FancyTextField { private func reset() -> Void { text = "" } + + private func setValues() -> Void { + if placeholder == "Title" { + nav.forms.jobSelector.editor.title = text + } + } } struct FancyTextFieldPreview: PreviewProvider { diff --git a/DLPrototype/Views/Shared/Fancy/FancyToggle.swift b/DLPrototype/Views/Shared/Fancy/FancyToggle.swift new file mode 100644 index 00000000..fe369491 --- /dev/null +++ b/DLPrototype/Views/Shared/Fancy/FancyToggle.swift @@ -0,0 +1,31 @@ +// +// FancyToggle.swift +// DLPrototype +// +// Created by Ryan Priebe on 2024-01-08. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI + +struct FancyToggle: View { + public let label: String + public let value: Bool + public let showLabel: Bool = false + + @State private var alive: Bool = false + + var body: some View { + HStack { + HStack { + Toggle(showLabel ? label : "", isOn: $alive) + Spacer() + } + .padding() + .background(Theme.textBackground) + } + .onAppear(perform: { + alive = value + }) + } +} diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index eabf85a0..5894c51a 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -133,7 +133,7 @@ struct CompanyPanel: View { ForEach(firstColData) { company in Panel.Row( config: Panel.RowConfiguration( - text: company.name!, + text: company.name!.capitalized, action: {setMiddlePanel(data: company.projects!.allObjects)}, entity: company, position: position, @@ -158,7 +158,7 @@ struct CompanyPanel: View { ForEach(nav.forms.jobSelector.middle) { project in Panel.Row( config: Panel.RowConfiguration( - text: project.name!, + text: project.name!.capitalized, action: {setLastPanel(data: project.jobs!.allObjects)}, entity: project, position: position @@ -181,7 +181,7 @@ struct CompanyPanel: View { ForEach(nav.forms.jobSelector.last) { job in Panel.Row( config: Panel.RowConfiguration( - text: job.name ?? job.jid.string, + text: job.title?.capitalized ?? job.jid.string, action: {nav.session.job = job}, entity: job, position: position @@ -209,6 +209,7 @@ extension CompanyPanel { nav.forms.jobSelector.currentPosition = position nav.forms.jobSelector.middle = (data as! [Project]).sorted(by: {$0.name! < $1.name!}) nav.forms.jobSelector.last = [] + nav.session.job = nil } private func setLastPanel(data: [Any]) -> Void { @@ -226,9 +227,9 @@ extension CompanyPanel { nav.forms.jobSelector.last = [] } else if position == .last { nav.forms.jobSelector.last = [] - nav.session.job = nil } + nav.session.job = nil showSearch = false } @@ -324,9 +325,9 @@ extension ProjectPanel { nav.forms.jobSelector.last = [] } else if position == .last { nav.forms.jobSelector.last = [] - nav.session.job = nil } + nav.session.job = nil showSearch = false } } @@ -379,7 +380,7 @@ public struct JobPanel: View { ForEach(nav.forms.jobSelector.last) { job in Panel.Row( config: Panel.RowConfiguration( - text: job.name ?? job.jid.string, + text: job.title?.capitalized ?? job.jid.string, action: {nav.session.job = job}, entity: job, position: position @@ -413,9 +414,9 @@ extension JobPanel { nav.forms.jobSelector.last = [] } else if position == .last { nav.forms.jobSelector.last = [] - nav.session.job = nil } + nav.session.job = nil showSearch = false } } From e614f4547e63619740c4c2af522a052498d6fdf3 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Mon, 8 Jan 2024 21:29:33 -0700 Subject: [PATCH 12/35] a lot of this is getting pruned, but each field saves when you submit it now. using a method like this I'll be able to generate forms really easily --- DLPrototype/Extensions/Models/Job.swift | 17 +++-- DLPrototype/Utils/Navigation.swift | 76 +++++++++++++++++-- .../Views/Entities/Jobs/JobDashboard.swift | 28 ++++--- .../Views/Shared/Fancy/FancyTextField.swift | 11 --- .../Views/Shared/Fancy/FancyToggle.swift | 4 + 5 files changed, 97 insertions(+), 39 deletions(-) diff --git a/DLPrototype/Extensions/Models/Job.swift b/DLPrototype/Extensions/Models/Job.swift index eb961fbd..bb25281b 100644 --- a/DLPrototype/Extensions/Models/Job.swift +++ b/DLPrototype/Extensions/Models/Job.swift @@ -50,13 +50,16 @@ extension Job { func fields() -> [Field] { var fields: [Field] = [] - fields.append(Field(type: .text, label: "Job ID", value: self.jid.string, keyPath: \Job.title)) - fields.append(Field(type: .text, label: "Title", value: self.title, keyPath: \Job.title)) - fields.append(Field(type: .boolean, label: "Published", value: self.alive, keyPath: \Job.title)) - fields.append(Field(type: .text, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), keyPath: \Job.title)) - fields.append(Field(type: .text, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), keyPath: \Job.title)) - fields.append(Field(type: .colour, label: "Colour", value: self.colour, keyPath: \Job.title)) - fields.append(Field(type: .editor, label: "Description", value: self.overview, keyPath: \Job.title)) + fields.append(Field(type: .text, label: "Job ID", value: self.jid.string, entity: self, keyPath: "jid")) + fields.append(Field(type: .text, label: "Title", value: self.title, entity: self, keyPath: "title")) + fields.append(Field(type: .boolean, label: "Published", value: self.alive, entity: self, keyPath: "alive")) + fields.append(Field(type: .boolean, label: "SRED Qualified", value: self.shredable, entity: self, keyPath: "shredable")) + // @TODO: make this a sidebar calendar selector + fields.append(Field(type: .text, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "lastUpdate")) + // @TODO: make this a sidebar calendar selector + fields.append(Field(type: .text, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "created")) + fields.append(Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour")) + fields.append(Field(type: .editor, label: "Description", value: self.overview, entity: self, keyPath: "overview")) return fields } diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index a6b67f91..c3e4e449 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -95,6 +95,7 @@ public class Navigation: Identifiable, ObservableObject { // @Published public var state: State = State() @Published public var history: History = History() @Published public var forms: Forms = Forms() + @Published public var events: SysEvents = SysEvents() public func pageTitle() -> String { if title!.isEmpty { @@ -233,13 +234,15 @@ extension Navigation { public var body: some View { Layout(field: self) } public var label: String = "" public var value: Any? = nil - public var keyPath: AnyKeyPath + public var entity: NSManagedObject? = nil + public var keyPath: String private var type: LayoutType = .text - init(type: LayoutType, label: String, value: Any? = nil, keyPath: AnyKeyPath) { + init(type: LayoutType, label: String, value: Any? = nil, entity: NSManagedObject? = nil, keyPath: String) { self.type = type self.label = label self.value = value + self.entity = entity self.keyPath = keyPath } @@ -263,17 +266,23 @@ extension Navigation { // @TODO: convert to button which changes focus state Text(field.label) .padding(5) +// if field.value as? String != bValue { +// Image(systemName: "exclamationmark.triangle.fill") +// .symbolRenderingMode(.hierarchical) +// .font(.title2) +// .padding([.trailing], 8) +// } } } .frame(width: 130) - + VStack { switch field.type { - case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool) + case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) default: - FancyTextField(placeholder: self.field.label, text: $bValue) + FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) } } Spacer() @@ -287,10 +296,65 @@ extension Navigation { bValue = value } } + + private func onChangeToggle(status: Bool) -> Void { + if status { + if let entity = field.entity { + entity.setValue(status, forKey: self.field.keyPath) + PersistenceController.shared.save() + } + } + } private func onChange(colour: Color) -> Void { - + if let entity = field.entity { + entity.setValue(colour, forKey: self.field.keyPath) + PersistenceController.shared.save() + } } + + private func onSubmit() -> Void { + if let entity = field.entity { + entity.setValue(bValue, forKey: self.field.keyPath) + PersistenceController.shared.save() + } + } + } + } + } + + public struct SysEvents: Equatable { + var id: UUID = UUID() + var stack: [SysEvent] = [] + + mutating func on(_ type: SysEvent.Types, _ callback: @escaping () -> Void) -> Void { + stack.insert(SysEvent(type: type, data: nil, callback: callback), at: 0) +// print("DERPO nav.events.stack.count=\(stack.count)") + } + + mutating func trigger(_ type: SysEvent.Types) -> Void { + if let event = stack.first(where: {$0.type == type}) { + event.callback() + stack.removeAll(where: {$0 == event}) + } + } + + public static func == (lhs: Navigation.SysEvents, rhs: Navigation.SysEvents) -> Bool { + lhs.id == rhs.id + } + + public struct SysEvent: Equatable { + var id: UUID = UUID() + var type: SysEvent.Types + var data: Any? + var callback: () -> Void + + public static func == (lhs: Navigation.SysEvents.SysEvent, rhs: Navigation.SysEvents.SysEvent) -> Bool { + lhs.id == rhs.id + } + + public enum Types { + case focusStateChange } } } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 12d8609f..c17319bd 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -207,7 +207,7 @@ struct JobExplorer: View { init() { _companies = CoreDataCompanies.all() } - + public struct JobViewRedux: View { public var job: Job private var fields: [Navigation.Forms.Field] { job.fields() } @@ -217,27 +217,25 @@ struct JobExplorer: View { var body: some View { Grid(alignment: .topLeading, horizontalSpacing: 1, verticalSpacing: 10) { VStack { - ForEach(fields) { field in - field.body + ScrollView { + ForEach(fields) { field in + field.body + } } + Spacer() + +// FancySimpleButton( +// text: "Save", +// action: {nav.save()}, +// type: .primary +// ) +// .keyboardShortcut("s", modifiers: .command) } .padding([.top, .bottom], 8) } .background(Theme.toolbarColour) .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) .padding(8) - .onChange(of: nav.saved) { status in - if status { -// job.title = nav.forms.jobSelector.editor.title -// job.setValuesForKeys([String: Any]) // @TODO: this is promising -// for field in fields { -// if field.label == "Title" { -// job.value(forKeyPath: "title") -// job.value -// } -// } - } - } } } } diff --git a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift index 69361b7b..5d30dcdf 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift @@ -62,11 +62,6 @@ struct FancyTextField: View { .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) .focused($focused) - .onChange(of: focused) { state in - if !state { - nav.save(callback: setValues) - } - } } private var oneBigLine: some View { @@ -105,12 +100,6 @@ extension FancyTextField { private func reset() -> Void { text = "" } - - private func setValues() -> Void { - if placeholder == "Title" { - nav.forms.jobSelector.editor.title = text - } - } } struct FancyTextFieldPreview: PreviewProvider { diff --git a/DLPrototype/Views/Shared/Fancy/FancyToggle.swift b/DLPrototype/Views/Shared/Fancy/FancyToggle.swift index fe369491..893bdfad 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyToggle.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyToggle.swift @@ -12,6 +12,7 @@ struct FancyToggle: View { public let label: String public let value: Bool public let showLabel: Bool = false + public let onChange: (Bool) -> Void @State private var alive: Bool = false @@ -27,5 +28,8 @@ struct FancyToggle: View { .onAppear(perform: { alive = value }) + .onChange(of: alive) { status in + self.onChange(status) + } } } From 915eceabe6ba225f3601b7b92c5b13d341589f2a Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Tue, 9 Jan 2024 17:27:10 -0700 Subject: [PATCH 13/35] in progress --- DLPrototype/Extensions/Models/Job.swift | 6 +-- DLPrototype/Utils/Navigation.swift | 38 ++++++------------ .../Views/Entities/Jobs/JobDashboard.swift | 39 +++++++++++-------- 3 files changed, 36 insertions(+), 47 deletions(-) diff --git a/DLPrototype/Extensions/Models/Job.swift b/DLPrototype/Extensions/Models/Job.swift index bb25281b..3a6e6b3d 100644 --- a/DLPrototype/Extensions/Models/Job.swift +++ b/DLPrototype/Extensions/Models/Job.swift @@ -52,13 +52,13 @@ extension Job { fields.append(Field(type: .text, label: "Job ID", value: self.jid.string, entity: self, keyPath: "jid")) fields.append(Field(type: .text, label: "Title", value: self.title, entity: self, keyPath: "title")) + fields.append(Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour")) fields.append(Field(type: .boolean, label: "Published", value: self.alive, entity: self, keyPath: "alive")) fields.append(Field(type: .boolean, label: "SRED Qualified", value: self.shredable, entity: self, keyPath: "shredable")) // @TODO: make this a sidebar calendar selector - fields.append(Field(type: .text, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "lastUpdate")) + fields.append(Field(type: .date, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "lastUpdate")) // @TODO: make this a sidebar calendar selector - fields.append(Field(type: .text, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "created")) - fields.append(Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour")) + fields.append(Field(type: .date, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "created")) fields.append(Field(type: .editor, label: "Description", value: self.overview, entity: self, keyPath: "overview")) return fields diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index c3e4e449..4f44018e 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -231,12 +231,12 @@ extension Navigation { public class Field: Identifiable { public let id: UUID = UUID() + public var type: LayoutType = .text public var body: some View { Layout(field: self) } public var label: String = "" public var value: Any? = nil public var entity: NSManagedObject? = nil public var keyPath: String - private var type: LayoutType = .text init(type: LayoutType, label: String, value: Any? = nil, entity: NSManagedObject? = nil, keyPath: String) { self.type = type @@ -259,33 +259,17 @@ extension Navigation { var body: some View { GridRow(alignment: field.type == .editor ? .top : .center) { - HStack { - VStack(alignment: .trailing) { - HStack(alignment: .top) { - Spacer() - // @TODO: convert to button which changes focus state - Text(field.label) - .padding(5) -// if field.value as? String != bValue { -// Image(systemName: "exclamationmark.triangle.fill") -// .symbolRenderingMode(.hierarchical) -// .font(.title2) -// .padding([.trailing], 8) -// } - } + VStack(alignment: .leading) { + Text(field.label) + .padding(5) + + switch field.type { + case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) + case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) + case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) + default: + FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) } - .frame(width: 130) - - VStack { - switch field.type { - case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) - case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) - case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) - default: - FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) - } - } - Spacer() } } .onAppear(perform: onLoad) diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index c17319bd..1c28f519 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -178,7 +178,6 @@ struct JobExplorer: View { if editorVisible { if nav.session.job != nil { VStack { -// JobView(job: nav.session.job!) JobViewRedux(job: nav.session.job!) } .padding(5) @@ -211,31 +210,37 @@ struct JobExplorer: View { public struct JobViewRedux: View { public var job: Job private var fields: [Navigation.Forms.Field] { job.fields() } + private var columns: [GridItem] { + Array(repeating: .init(.flexible(minimum: 300)), count: 2) + } @EnvironmentObject private var nav: Navigation var body: some View { - Grid(alignment: .topLeading, horizontalSpacing: 1, verticalSpacing: 10) { - VStack { - ScrollView { - ForEach(fields) { field in - field.body + ScrollView { + Grid(alignment: .topLeading, horizontalSpacing: 8, verticalSpacing: 10) { + LazyVGrid(columns: columns) { + VStack { + ForEach(fields.filter({$0.type != .date})) { field in + field.body + } + } + .padding([.top, .leading], 8) + + VStack { + ForEach(fields.filter({$0.type == .date})) { field in + field.body + } } + .padding([.top, .trailing], 8) } - Spacer() -// FancySimpleButton( -// text: "Save", -// action: {nav.save()}, -// type: .primary -// ) -// .keyboardShortcut("s", modifiers: .command) + FancyButtonv2(text: "Hello") } - .padding([.top, .bottom], 8) + .background(Theme.toolbarColour) + .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) + .padding(8) } - .background(Theme.toolbarColour) - .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) - .padding(8) } } } From f91b6bff84edb3f78aba2ed4ed3869977ab7b6cd Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 22:04:38 -0700 Subject: [PATCH 14/35] moved to a computed prop instead of func --- DLPrototype/Extensions/Models/Job.swift | 34 +++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/DLPrototype/Extensions/Models/Job.swift b/DLPrototype/Extensions/Models/Job.swift index 3a6e6b3d..29720276 100644 --- a/DLPrototype/Extensions/Models/Job.swift +++ b/DLPrototype/Extensions/Models/Job.swift @@ -10,6 +10,21 @@ import SwiftUI extension Job { typealias Field = Navigation.Forms.Field + + /// Field definitions used to generate user-editable forms for this object + var fields: [Field] { + [ + Field(type: .text, label: "Job ID", value: self.jid.string, entity: self, keyPath: "jid"), + Field(type: .text, label: "Title", value: self.title, entity: self, keyPath: "title"), + Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour"), + Field(type: .boolean, label: "Published", value: self.alive, entity: self, keyPath: "alive"), + Field(type: .boolean, label: "SRED Qualified", value: self.shredable, entity: self, keyPath: "shredable"), + Field(type: .date, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), entity:self, keyPath: "lastUpdate"), + Field(type: .date, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), entity: self,keyPath: "created"), + Field(type: .projectDropdown, label: "Project", value: self.project, entity: self, keyPath: "project"), + Field(type: .editor, label: "Description", value: self.overview, entity: self, keyPath: "overview") + ] + } var idInt: Int { Int(exactly: jid.rounded(.toNearestOrEven)) ?? 0 } @@ -44,23 +59,4 @@ extension Job { func fgColour() -> Color { return self.colour_from_stored().isBright() ? .black : .white } - - /// Create form fields for the important properties and relationships - /// - Returns: [Field] - func fields() -> [Field] { - var fields: [Field] = [] - - fields.append(Field(type: .text, label: "Job ID", value: self.jid.string, entity: self, keyPath: "jid")) - fields.append(Field(type: .text, label: "Title", value: self.title, entity: self, keyPath: "title")) - fields.append(Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour")) - fields.append(Field(type: .boolean, label: "Published", value: self.alive, entity: self, keyPath: "alive")) - fields.append(Field(type: .boolean, label: "SRED Qualified", value: self.shredable, entity: self, keyPath: "shredable")) - // @TODO: make this a sidebar calendar selector - fields.append(Field(type: .date, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "lastUpdate")) - // @TODO: make this a sidebar calendar selector - fields.append(Field(type: .date, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), entity: self, keyPath: "created")) - fields.append(Field(type: .editor, label: "Description", value: self.overview, entity: self, keyPath: "overview")) - - return fields - } } From 34566f3c74da31b25877e9d05f3d4e33c7b165f5 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 22:05:41 -0700 Subject: [PATCH 15/35] column left/right position now defined by a single array --- DLPrototype/Utils/Navigation.swift | 5 +++-- .../Views/Entities/Jobs/JobDashboard.swift | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 4f44018e..636eef1a 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -247,7 +247,7 @@ extension Navigation { } public enum LayoutType { - case text, dropdown, date, boolean, colour, editor + case text, dropdown, projectDropdown, date, boolean, colour, editor } struct Layout: View { @@ -258,7 +258,7 @@ extension Navigation { @EnvironmentObject private var nav: Navigation var body: some View { - GridRow(alignment: field.type == .editor ? .top : .center) { + GridRow(alignment: .center) { VStack(alignment: .leading) { Text(field.label) .padding(5) @@ -267,6 +267,7 @@ extension Navigation { case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) + case .projectDropdown: ProjectPickerUsing(onChange: {_, _ in}, size: .large, displayName: $bValue) default: FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 1c28f519..6a34f203 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -209,7 +209,8 @@ struct JobExplorer: View { public struct JobViewRedux: View { public var job: Job - private var fields: [Navigation.Forms.Field] { job.fields() } + private var fields: [Navigation.Forms.Field] { job.fields } + private let columnSplit: [Navigation.Forms.Field.LayoutType] = [.date, .projectDropdown] private var columns: [GridItem] { Array(repeating: .init(.flexible(minimum: 300)), count: 2) } @@ -221,21 +222,27 @@ struct JobExplorer: View { Grid(alignment: .topLeading, horizontalSpacing: 8, verticalSpacing: 10) { LazyVGrid(columns: columns) { VStack { - ForEach(fields.filter({$0.type != .date})) { field in + ForEach(fields.filter({!columnSplit.contains($0.type)})) { field in field.body } + Spacer() } .padding([.top, .leading], 8) VStack { - ForEach(fields.filter({$0.type == .date})) { field in + ForEach(fields.filter({columnSplit.contains($0.type)})) { field in field.body } + FancyDivider() + HStack(alignment: .bottom) { + Spacer() + FancySimpleButton(text: "Save") + .keyboardShortcut("s", modifiers: [.command]) + } + Spacer() } .padding([.top, .trailing], 8) } - - FancyButtonv2(text: "Hello") } .background(Theme.toolbarColour) .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) From 1507315888bbde7a988ea75f28d84e69747e15b8 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 22:06:04 -0700 Subject: [PATCH 16/35] updated pickers to fix some UX issues --- .../Shared/Fancy/FancyColourPicker.swift | 2 + .../Views/Shared/Fancy/FancyPicker.swift | 17 ++--- .../Views/Shared/ProjectPickerUsing.swift | 71 +++++++++++-------- 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift b/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift index 5cdfdb65..75936deb 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyColourPicker.swift @@ -38,6 +38,8 @@ struct FancyColourPicker: View { .border(Color.black.opacity(0.1), width: 2) .frame(width: 200) + Spacer() + }.frame(height: 40) .onAppear(perform: { asString = asColor.description diff --git a/DLPrototype/Views/Shared/Fancy/FancyPicker.swift b/DLPrototype/Views/Shared/Fancy/FancyPicker.swift index 79396590..b1df255c 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyPicker.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyPicker.swift @@ -16,6 +16,7 @@ struct FancyPicker: View { public var labelText: String? public var showLabel: Bool? = false public var defaultSelected: Int = 0 + public var size: PickerSize = .small @State private var selection: Int = 0 @@ -51,7 +52,8 @@ struct FancyPicker: View { } } .labelsHidden() - .frame(width: 200) + .frame(width: size == .small ? 200 : nil) + .padding([.trailing], size == .small ? 0 : 16) .font(Theme.font) .onChange(of: selection) { _ in onChange(selection, labelText) @@ -77,7 +79,8 @@ struct FancyPicker: View { NSCursor.pop() } } - .frame(width: 200) + .frame(width: size == .small ? 200 : nil) + .padding([.trailing], size == .small ? 0 : 16) .font(Theme.font) .onChange(of: selection) { _ in onChange(selection, labelText) @@ -85,13 +88,3 @@ struct FancyPicker: View { } } } - -struct FancyPickerPreview: PreviewProvider { - static var previews: some View { - FancyPicker(onChange: change, items: []) - } - - static private func change(num: Int, sender: String?) -> Void { - - } -} diff --git a/DLPrototype/Views/Shared/ProjectPickerUsing.swift b/DLPrototype/Views/Shared/ProjectPickerUsing.swift index 90c5596c..40ec584a 100644 --- a/DLPrototype/Views/Shared/ProjectPickerUsing.swift +++ b/DLPrototype/Views/Shared/ProjectPickerUsing.swift @@ -9,11 +9,16 @@ import SwiftUI import Combine +enum PickerSize { + case small, large +} + struct ProjectPickerUsing: View { public var onChange: (String, String?) -> Void public var transparent: Bool? = false public var labelText: String? public var showLabel: Bool? = false + public var size: PickerSize = .small @Binding public var displayName: String @State private var idFieldColour: Color = Color.clear @@ -39,27 +44,45 @@ struct ProjectPickerUsing: View { var body: some View { HStack { ZStack { - FancyTextField( - placeholder: "Project name", - lineLimit: 1, - onSubmit: {}, - fgColour: idFieldTextColour, - bgColour: idFieldColour, - text: $projectName - ) - .border(idFieldColour == Color.clear ? Color.black.opacity(0.1) : Color.clear, width: 2) - - HStack { -// if !id.isEmpty { -// FancyButton(text: "Reset", action: resetUi, icon: "xmark", showLabel: false) -// } - FancyPicker(onChange: pickerChange, items: pickerItems, transparent: transparent, labelText: labelText, showLabel: showLabel) + if size == .small { + FancyTextField( + placeholder: "Project name", + lineLimit: 1, + onSubmit: {}, + fgColour: idFieldTextColour, + bgColour: idFieldColour, + text: $projectName + ) + .border(idFieldColour == Color.clear ? Color.black.opacity(0.1) : Color.clear, width: 2) + + HStack { + FancyPicker( + onChange: pickerChange, + items: pickerItems, + transparent: transparent, + labelText: labelText, + showLabel: showLabel, + size: size + ) + } + .padding([.leading], 150) + } else { + HStack { + FancyPicker( + onChange: pickerChange, + items: pickerItems, + transparent: transparent, + labelText: labelText, + showLabel: showLabel, + size: size + ) + } + .padding() + .border(idFieldColour == Color.clear ? Color.black.opacity(0.1) : Color.clear, width: 2) } - .padding([.leading], 150) } } - .frame(width: 450, height: 40) - .onAppear(perform: onAppear) + .frame(width: size == .small ? 450 : nil, height: 40) } private func pickerChange(selected: Int, sender: String?) -> Void { @@ -79,17 +102,7 @@ struct ProjectPickerUsing: View { onChange(projectName, sender) } - - private func onAppear() -> Void { - // TODO: this is borked now, fix it -// if !id.isEmpty { -// let pid = (id as NSString).integerValue -// print("JERB pid \(pid)") -// -// pickerChange(selected: pid, sender: "") -// } - } - + private func resetUi() -> Void { idFieldColour = Color.clear idFieldTextColour = Color.white From 4d334965f977bf0c0cacdb0436206b8c19776f71 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 22:23:02 -0700 Subject: [PATCH 17/35] project picker now selects the already selected job by default --- DLPrototype/Utils/Navigation.swift | 2 +- .../Views/Shared/ProjectPickerUsing.swift | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 636eef1a..ea9cb008 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -267,7 +267,7 @@ extension Navigation { case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) - case .projectDropdown: ProjectPickerUsing(onChange: {_, _ in}, size: .large, displayName: $bValue) + case .projectDropdown: ProjectPickerUsing(onChange: {_, _ in}, size: .large, defaultSelection: Int((self.field.value as! Project).pid), displayName: $bValue) default: FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) } diff --git a/DLPrototype/Views/Shared/ProjectPickerUsing.swift b/DLPrototype/Views/Shared/ProjectPickerUsing.swift index 40ec584a..42d2faf2 100644 --- a/DLPrototype/Views/Shared/ProjectPickerUsing.swift +++ b/DLPrototype/Views/Shared/ProjectPickerUsing.swift @@ -19,11 +19,12 @@ struct ProjectPickerUsing: View { public var labelText: String? public var showLabel: Bool? = false public var size: PickerSize = .small + public var defaultSelection: Int = 0 @Binding public var displayName: String @State private var idFieldColour: Color = Color.clear @State private var idFieldTextColour: Color = Color.white - @State private var selectedId: String = "" + @State private var selectedId: Int = 0 @State private var projectName: String = "" @Environment(\.managedObjectContext) var moc @@ -62,6 +63,7 @@ struct ProjectPickerUsing: View { transparent: transparent, labelText: labelText, showLabel: showLabel, + defaultSelected: selectedId, size: size ) } @@ -74,6 +76,7 @@ struct ProjectPickerUsing: View { transparent: transparent, labelText: labelText, showLabel: showLabel, + defaultSelected: defaultSelection, size: size ) } @@ -83,6 +86,19 @@ struct ProjectPickerUsing: View { } } .frame(width: size == .small ? 450 : nil, height: 40) + .onAppear(perform: onLoad) + } +} + +extension ProjectPickerUsing { + private func onLoad() -> Void { + print("DERPO defaultSelection=\(defaultSelection)") + if let idx = pickerItems.first(where: {$0.tag == defaultSelection}) { + selectedId = idx.tag + print("DERPO selectedId=\(selectedId)") + } else { + print("DERPO no id") + } } private func pickerChange(selected: Int, sender: String?) -> Void { @@ -90,7 +106,7 @@ struct ProjectPickerUsing: View { projectName = item.title.replacingOccurrences(of: " - ", with: "") } - selectedId = String(selected) + selectedId = selected if let selectedJob = CoreDataProjects(moc: moc).byId(Int(exactly: selected)!) { idFieldColour = Color.fromStored(selectedJob.colour ?? Theme.rowColourAsDouble) From 4852a443fcf04b5ecceb6585e03e4ae61c4a9635 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 22:31:59 -0700 Subject: [PATCH 18/35] working on picker autosave --- .../Views/Shared/ProjectPickerUsing.swift | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/DLPrototype/Views/Shared/ProjectPickerUsing.swift b/DLPrototype/Views/Shared/ProjectPickerUsing.swift index 42d2faf2..5536e2e0 100644 --- a/DLPrototype/Views/Shared/ProjectPickerUsing.swift +++ b/DLPrototype/Views/Shared/ProjectPickerUsing.swift @@ -15,6 +15,7 @@ enum PickerSize { struct ProjectPickerUsing: View { public var onChange: (String, String?) -> Void + public var onChangeLarge: ((Int, String?) -> Void)? = nil // @TODO: refactor public var transparent: Bool? = false public var labelText: String? public var showLabel: Bool? = false @@ -92,31 +93,33 @@ struct ProjectPickerUsing: View { extension ProjectPickerUsing { private func onLoad() -> Void { - print("DERPO defaultSelection=\(defaultSelection)") - if let idx = pickerItems.first(where: {$0.tag == defaultSelection}) { - selectedId = idx.tag - print("DERPO selectedId=\(selectedId)") - } else { - print("DERPO no id") + if let item = pickerItems.first(where: {$0.tag == defaultSelection}) { + selectedId = item.tag } } private func pickerChange(selected: Int, sender: String?) -> Void { - if let item = pickerItems.filter({$0.tag == selected}).first { - projectName = item.title.replacingOccurrences(of: " - ", with: "") - } - - selectedId = selected - - if let selectedJob = CoreDataProjects(moc: moc).byId(Int(exactly: selected)!) { - idFieldColour = Color.fromStored(selectedJob.colour ?? Theme.rowColourAsDouble) - idFieldTextColour = idFieldColour.isBright() ? Color.black : Color.white - } else { - idFieldColour = Color.clear - idFieldTextColour = Color.white + if size == .small { + if let item = pickerItems.filter({$0.tag == selected}).first { + projectName = item.title.replacingOccurrences(of: " - ", with: "") + } + + selectedId = selected + + if let selectedJob = CoreDataProjects(moc: moc).byId(Int(exactly: selected)!) { + idFieldColour = Color.fromStored(selectedJob.colour ?? Theme.rowColourAsDouble) + idFieldTextColour = idFieldColour.isBright() ? Color.black : Color.white + } else { + idFieldColour = Color.clear + idFieldTextColour = Color.white + } + + onChange(projectName, sender) + } else if size == .large { + if let ocl = onChangeLarge { + ocl(selected, sender) + } } - - onChange(projectName, sender) } private func resetUi() -> Void { From 24a4b95d18029c2ad937dd029a7c2b2e06ebd56d Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 23:24:29 -0700 Subject: [PATCH 19/35] added searching by UUID --- .../Models/CoreData/CoreDataProjects.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/DLPrototype/Models/CoreData/CoreDataProjects.swift b/DLPrototype/Models/CoreData/CoreDataProjects.swift index de7c694f..555af5f7 100644 --- a/DLPrototype/Models/CoreData/CoreDataProjects.swift +++ b/DLPrototype/Models/CoreData/CoreDataProjects.swift @@ -42,7 +42,7 @@ public class CoreDataProjects: ObservableObject { return FetchRequest(fetchRequest: fetch, animation: .easeInOut) } - public func byId(_ id: Int) -> Project? { + public func byId(_ id: Int64) -> Project? { let predicate = NSPredicate( format: "pid = %d", id as CVarArg @@ -90,6 +90,24 @@ public class CoreDataProjects: ObservableObject { return results.first } + /// Find projects by UUID (aka, by id) + /// - Parameter term: A project's UUID + /// - Returns: Optional(Project) + public func byUuid(_ term: UUID) -> Project? { + let predicate = NSPredicate( + format: "name = %@", + term.uuidString + ) + + let results = query(predicate) + + if results.isEmpty { + return nil + } + + return results.first + } + public func all() -> [Project] { return query() } @@ -147,7 +165,7 @@ public class CoreDataProjects: ObservableObject { print("[error] CoreDataProjects.query Unable to find records for query") } - print(error) + print("[error] \(error)") } lock.unlock() From 873618543fb0570a3f8aff70c08d4c02739b65ef Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 23:24:51 -0700 Subject: [PATCH 20/35] minor refactor for readability --- DLPrototype/Utils/Navigation.swift | 13 ++++++++++++- DLPrototype/Views/Shared/CompanyPicker.swift | 7 ++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index ea9cb008..4dd563cd 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -267,7 +267,7 @@ extension Navigation { case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) - case .projectDropdown: ProjectPickerUsing(onChange: {_, _ in}, size: .large, defaultSelection: Int((self.field.value as! Project).pid), displayName: $bValue) + case .projectDropdown: ProjectPickerUsing(onChangeLarge: onChangeProjectDropdown, size: .large, defaultSelection: Int((self.field.value as! Project).pid), displayName: $bValue) default: FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) } @@ -291,6 +291,17 @@ extension Navigation { } } + private func onChangeProjectDropdown(selected: Project, sender: String?) -> Void { + if let entity = field.entity { + entity.setValue(selected, forKey: self.field.keyPath) + PersistenceController.shared.save() + print("DERPO saved pdropdown name=\(selected.name)") + } else { + print("DERPO vbad") + } + print("DERPO hi") + } + private func onChange(colour: Color) -> Void { if let entity = field.entity { entity.setValue(colour, forKey: self.field.keyPath) diff --git a/DLPrototype/Views/Shared/CompanyPicker.swift b/DLPrototype/Views/Shared/CompanyPicker.swift index 99f199e8..f7f4f2fe 100644 --- a/DLPrototype/Views/Shared/CompanyPicker.swift +++ b/DLPrototype/Views/Shared/CompanyPicker.swift @@ -29,7 +29,12 @@ struct CompanyPicker: View { for company in companies { if company.name != nil { - items.append(CustomPickerItem(title: company.name!.capitalized, tag: Int(company.pid))) + items.append( + CustomPickerItem( + title: company.name!.capitalized, + tag: Int(company.pid) + ) + ) } } From 35e1c8b4375a5401ab095334834960aa94bc6b2f Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 23:25:25 -0700 Subject: [PATCH 21/35] added optional project field. this will likely be refactored away in the future but I needed this to auto-save fields in the job editor --- DLPrototype/Views/Shared/CustomPickerItem.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DLPrototype/Views/Shared/CustomPickerItem.swift b/DLPrototype/Views/Shared/CustomPickerItem.swift index 88420868..09a59269 100644 --- a/DLPrototype/Views/Shared/CustomPickerItem.swift +++ b/DLPrototype/Views/Shared/CustomPickerItem.swift @@ -6,14 +6,15 @@ // Copyright © 2023 YegCollective. All rights reserved. // -import Foundation +import SwiftUI public struct CustomPickerItem: Identifiable, Hashable { public var id = UUID() public var title: String public var tag: Int public var disabled: Bool = false - + public var project: Project? = nil + static public func listFrom(_ records: [String]) -> [CustomPickerItem] { var list: [CustomPickerItem] = [] From 6d023b07576a3febf78e5e8258503c80a5c900ca Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 23:26:14 -0700 Subject: [PATCH 22/35] removed testing stuff from callback --- DLPrototype/Utils/Navigation.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 4dd563cd..d2d705de 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -295,11 +295,7 @@ extension Navigation { if let entity = field.entity { entity.setValue(selected, forKey: self.field.keyPath) PersistenceController.shared.save() - print("DERPO saved pdropdown name=\(selected.name)") - } else { - print("DERPO vbad") } - print("DERPO hi") } private func onChange(colour: Color) -> Void { From ec893d05b8d4ebe26f8d6d5db96ce01c938cb47c Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Wed, 10 Jan 2024 23:26:21 -0700 Subject: [PATCH 23/35] updates to support auto-saving --- .../Views/Shared/ProjectPickerUsing.swift | 72 ++++++++++++------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/DLPrototype/Views/Shared/ProjectPickerUsing.swift b/DLPrototype/Views/Shared/ProjectPickerUsing.swift index 5536e2e0..ef4a902d 100644 --- a/DLPrototype/Views/Shared/ProjectPickerUsing.swift +++ b/DLPrototype/Views/Shared/ProjectPickerUsing.swift @@ -14,8 +14,8 @@ enum PickerSize { } struct ProjectPickerUsing: View { - public var onChange: (String, String?) -> Void - public var onChangeLarge: ((Int, String?) -> Void)? = nil // @TODO: refactor + public var onChange: ((String, String?) -> Void)? = nil + public var onChangeLarge: ((Project, String?) -> Void)? = nil // @TODO: refactor, don't like this multiple callback func def thing public var transparent: Bool? = false public var labelText: String? public var showLabel: Bool? = false @@ -36,7 +36,13 @@ struct ProjectPickerUsing: View { for project in projects { if project.name != nil { - items.append(CustomPickerItem(title: " - \(project.name!)", tag: Int(project.pid))) + items.append( + CustomPickerItem( + title: project.name!.capitalized, + tag: Int(project.pid), + project: project + ) + ) } } @@ -59,7 +65,7 @@ struct ProjectPickerUsing: View { HStack { FancyPicker( - onChange: pickerChange, + onChange: onChangeSmallWidget, items: pickerItems, transparent: transparent, labelText: labelText, @@ -72,7 +78,7 @@ struct ProjectPickerUsing: View { } else { HStack { FancyPicker( - onChange: pickerChange, + onChange: onChangeLargeWidget, items: pickerItems, transparent: transparent, labelText: labelText, @@ -92,32 +98,48 @@ struct ProjectPickerUsing: View { } extension ProjectPickerUsing { + /// Sets the selected picker item if one was passed to the view + /// - Returns: Void private func onLoad() -> Void { if let item = pickerItems.first(where: {$0.tag == defaultSelection}) { selectedId = item.tag } } - private func pickerChange(selected: Int, sender: String?) -> Void { - if size == .small { - if let item = pickerItems.filter({$0.tag == selected}).first { - projectName = item.title.replacingOccurrences(of: " - ", with: "") - } - - selectedId = selected - - if let selectedJob = CoreDataProjects(moc: moc).byId(Int(exactly: selected)!) { - idFieldColour = Color.fromStored(selectedJob.colour ?? Theme.rowColourAsDouble) - idFieldTextColour = idFieldColour.isBright() ? Color.black : Color.white - } else { - idFieldColour = Color.clear - idFieldTextColour = Color.white - } - - onChange(projectName, sender) - } else if size == .large { - if let ocl = onChangeLarge { - ocl(selected, sender) + /// Callback that fires when a PickerSize.small widget changes selected value + /// - Parameters: + /// - selected: Index value for the selected CustomPickerItem + /// - sender: Optional sender information + /// - Returns: Void + private func onChangeSmallWidget(selected: Int, sender: String?) -> Void { + if let item = pickerItems.filter({$0.tag == selected}).first { + projectName = item.title.replacingOccurrences(of: " - ", with: "") + } + + selectedId = selected + + if let selectedJob = CoreDataProjects(moc: moc).byId(Int64(exactly: selected)!) { + idFieldColour = Color.fromStored(selectedJob.colour ?? Theme.rowColourAsDouble) + idFieldTextColour = idFieldColour.isBright() ? Color.black : Color.white + } else { + idFieldColour = Color.clear + idFieldTextColour = Color.white + } + + if let ocs = onChange { + ocs(projectName, sender) + } + } + + /// Callback that fires when a PickerSize.large widget changes selected value + /// - Parameter project: NSManagedObject + /// - Returns: Void + private func onChangeLargeWidget(selected: Int, sender: String?) -> Void { + if let ocl = onChangeLarge { + if let item = pickerItems.first(where: {$0.tag == selected}) { + if item.project != nil { + ocl(item.project!, sender) + } } } } From 175aa5e7aa6501fb3a0bc50e118b0c5edc938a95 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 00:46:24 -0700 Subject: [PATCH 24/35] mostly functional job selector --- .../Views/Entities/Jobs/JobDashboard.swift | 3 -- .../Views/Entities/Jobs/JobRowPicker.swift | 7 +---- .../Views/Shared/PanelGroup/Panel.swift | 15 ++++++---- .../Shared/PanelGroup/ThreePanelGroup.swift | 30 +++++++++++++++++++ 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 6a34f203..0a292165 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -198,9 +198,6 @@ struct JobExplorer: View { } } } -// .onChange(of: nav.saved) { status in -// editorVisible = status -// } } init() { diff --git a/DLPrototype/Views/Entities/Jobs/JobRowPicker.swift b/DLPrototype/Views/Entities/Jobs/JobRowPicker.swift index 4ed5daf7..deb72d1c 100644 --- a/DLPrototype/Views/Entities/Jobs/JobRowPicker.swift +++ b/DLPrototype/Views/Entities/Jobs/JobRowPicker.swift @@ -73,17 +73,12 @@ extension JobRowPicker { if let parent = nav.parent { if parent == .jobs { - nav.setParent(.jobs) nav.session.setJob(job) - nav.setView(AnyView(JobDashboard(defaultSelectedJob: job))) - nav.setSidebar(AnyView(JobDashboardSidebar())) } else if parent == .planning { actionUpdatePlanningStore() } else { - nav.setParent(.today) nav.session.setJob(job) - nav.setView(AnyView(Today())) - nav.setSidebar(AnyView(TodaySidebar())) + nav.to(.today) } } } diff --git a/DLPrototype/Views/Shared/PanelGroup/Panel.swift b/DLPrototype/Views/Shared/PanelGroup/Panel.swift index 5894c51a..6734ec83 100644 --- a/DLPrototype/Views/Shared/PanelGroup/Panel.swift +++ b/DLPrototype/Views/Shared/PanelGroup/Panel.swift @@ -64,10 +64,16 @@ public struct Panel { } extension Panel.Row { + /// Fire the callback INTO THE SUN + /// - Returns: Void private func fireCallback() -> Void { - let selected = Panel.SelectedValueCoordinates(position: config.position, item: config.entity) - - // changes which entity is highlighted in each column + highlightPanelEntity(Panel.SelectedValueCoordinates(position: config.position, item: config.entity)) + config.action() + } + + /// Changes which entity is highlighted in each column + /// - Returns: Void + private func highlightPanelEntity(_ selected: Panel.SelectedValueCoordinates) -> Void { var items = nav.forms.jobSelector.selected if items.isEmpty { items.append(selected) @@ -82,9 +88,8 @@ extension Panel.Row { } } } + nav.forms.jobSelector.selected = items - - config.action() } } diff --git a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift index 839c82be..180ef439 100644 --- a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift +++ b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift @@ -40,6 +40,7 @@ struct ThreePanelGroup: View { } } .onAppear(perform: actionOnAppear) + .onChange(of: nav.session.job) { job in onChangeJob(job: job) } } init(orientation: Panel.Orientation, data: any Collection) { @@ -60,4 +61,33 @@ extension ThreePanelGroup { nav.forms.jobSelector.last = [] } } + + /// Changing the current session job value requires us to change which items are highlighted in each column. This is done by modifying the + /// `nav.forms.jobSelector.selected` array + /// - Parameter job: A Job object + /// - Returns: Void + private func onChangeJob(job: Job?) -> Void { + if job != nil { + nav.forms.jobSelector.selected = [] + + if let project = job!.project { + nav.forms.jobSelector.last = (project.jobs?.allObjects as! [Job]).sorted(by: {$0.jid < $1.jid}) + setSelected(config: Panel.SelectedValueCoordinates(position: .middle, item: project)) + + if let company = project.company { + nav.forms.jobSelector.middle = (company.projects?.allObjects as! [Project]).sorted(by: {$0.name! < $1.name!}) + setSelected(config: Panel.SelectedValueCoordinates(position: .first, item: company)) + } + } + setSelected(config: Panel.SelectedValueCoordinates(position: .last, item: job!)) + } + } + + + /// Adds a panel selection value to the list of selected panel items + /// - Parameter config: A key/value pair, with position and item fields + /// - Returns: Void + private func setSelected(config: Panel.SelectedValueCoordinates) -> Void { + nav.forms.jobSelector.selected.append(config) + } } From 80147ddb720138b10bb4352cf4a9fd9439934f73 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 07:46:59 -0700 Subject: [PATCH 25/35] fixes crash during CD encoding --- DLPrototype/Utils/Navigation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index d2d705de..8ac88526 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -300,7 +300,7 @@ extension Navigation { private func onChange(colour: Color) -> Void { if let entity = field.entity { - entity.setValue(colour, forKey: self.field.keyPath) + entity.setValue(colour.toStored(), forKey: self.field.keyPath) PersistenceController.shared.save() } } From f54353d0953c53572c82cc7479b8ede2690a9b4d Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 08:49:34 -0700 Subject: [PATCH 26/35] modified dashboard view to support creating jobs from the same interface --- DLPrototype/Utils/Navigation.swift | 10 +- .../Views/Entities/Jobs/JobDashboard.swift | 132 ++++++++++++++---- .../Shared/PanelGroup/ThreePanelGroup.swift | 3 + 3 files changed, 113 insertions(+), 32 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 8ac88526..09cd5794 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -286,6 +286,7 @@ extension Navigation { if status { if let entity = field.entity { entity.setValue(status, forKey: self.field.keyPath) + entity.setValue(Date(), forKey: "lastUpdate") PersistenceController.shared.save() } } @@ -294,6 +295,7 @@ extension Navigation { private func onChangeProjectDropdown(selected: Project, sender: String?) -> Void { if let entity = field.entity { entity.setValue(selected, forKey: self.field.keyPath) + entity.setValue(Date(), forKey: "lastUpdate") PersistenceController.shared.save() } } @@ -301,13 +303,19 @@ extension Navigation { private func onChange(colour: Color) -> Void { if let entity = field.entity { entity.setValue(colour.toStored(), forKey: self.field.keyPath) + entity.setValue(Date(), forKey: "lastUpdate") PersistenceController.shared.save() } } private func onSubmit() -> Void { if let entity = field.entity { - entity.setValue(bValue, forKey: self.field.keyPath) + if self.field.keyPath == "jid" { + entity.setValue(Double(bValue), forKey: self.field.keyPath) + } else { + entity.setValue(bValue, forKey: self.field.keyPath) + } + entity.setValue(Date(), forKey: "lastUpdate") PersistenceController.shared.save() } } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 0a292165..e5c3868d 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -77,6 +77,12 @@ extension JobDashboard { } struct JobDashboardRedux: View { + @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true + @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true + + @Environment(\.managedObjectContext) var moc + @EnvironmentObject private var nav: Navigation + var body: some View { VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 1) { @@ -89,12 +95,25 @@ struct JobDashboardRedux: View { Spacer() FancyButtonv2( text: "Create", - action: {}, + action: { + editorVisible = true + explorerVisible = false + + // Creates a new job entity so the user can customize it + let newJob = Job(context: moc) + newJob.id = UUID() + newJob.jid = 1.0 + newJob.colour = Color.randomStorable() + newJob.alive = true + newJob.project = CoreDataProjects(moc: moc).alive().first(where: {$0.company?.isDefault == true}) + newJob.created = Date() + newJob.lastUpdate = newJob.created + newJob.overview = "Edit this description" + newJob.title = "Sample job title" + nav.session.job = newJob + }, icon: "plus", - showLabel: false, - redirect: AnyView(JobCreate()), - pageType: .jobs, - sidebar: AnyView(JobDashboardSidebar()) + showLabel: false ) } @@ -176,9 +195,9 @@ struct JobExplorer: View { .foregroundStyle(.white) if editorVisible { - if nav.session.job != nil { + if let job = nav.session.job { VStack { - JobViewRedux(job: nav.session.job!) + JobViewRedux(job: job) } .padding(5) .background(Theme.rowColour) @@ -205,46 +224,97 @@ struct JobExplorer: View { } public struct JobViewRedux: View { - public var job: Job - private var fields: [Navigation.Forms.Field] { job.fields } + public var job: Job? = nil + private var fields: [Navigation.Forms.Field] { job != nil ? job!.fields : [] } private let columnSplit: [Navigation.Forms.Field.LayoutType] = [.date, .projectDropdown] private var columns: [GridItem] { Array(repeating: .init(.flexible(minimum: 300)), count: 2) } + @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true + @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true + + @Environment(\.managedObjectContext) var moc @EnvironmentObject private var nav: Navigation var body: some View { ScrollView { - Grid(alignment: .topLeading, horizontalSpacing: 8, verticalSpacing: 10) { - LazyVGrid(columns: columns) { - VStack { - ForEach(fields.filter({!columnSplit.contains($0.type)})) { field in - field.body - } - Spacer() - } - .padding([.top, .leading], 8) - - VStack { - ForEach(fields.filter({columnSplit.contains($0.type)})) { field in - field.body + if job != nil { + Grid(alignment: .topLeading, horizontalSpacing: 8, verticalSpacing: 10) { + LazyVGrid(columns: columns) { + VStack { + ForEach(fields.filter({!columnSplit.contains($0.type)})) { field in + field.body + } + Spacer() } - FancyDivider() - HStack(alignment: .bottom) { + .padding([.top, .leading], 8) + + VStack { + ForEach(fields.filter({columnSplit.contains($0.type)})) { field in + field.body + } + FancyDivider() + HStack(alignment: .bottom) { + FancySimpleButton(text: "Delete", action: triggerDelete, icon: "trash", showLabel: false, showIcon: true, type: .destructive) + Spacer() + FancySimpleButton(text: "Save", action: triggerSave) + .keyboardShortcut("s", modifiers: [.command]) + } Spacer() - FancySimpleButton(text: "Save") - .keyboardShortcut("s", modifiers: [.command]) } - Spacer() + .padding([.top, .trailing], 8) } - .padding([.top, .trailing], 8) } + .background(Theme.toolbarColour) + .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) + .padding(8) + } + } + .onChange(of: nav.saved) { status in + if status { + print("DERPO saving all fields") } - .background(Theme.toolbarColour) - .border(width: 1, edges: [.top, .bottom, .leading, .trailing], color: Theme.rowColour) - .padding(8) } } } } + +extension JobExplorer.JobViewRedux { + /// Triggers a save event so all fields can be updated at once + /// - Returns: Void + private func triggerSave() -> Void { + nav.save() + } + + /// Update all fields at the same time + /// - Returns: Void + private func onSave() -> Void { + + } + + /// Deletes the current Job + /// - Returns: Void + private func triggerDelete() -> Void { + // Temporary jobs will have a default JID value, they can be safely hard-deleted + nav.session.job = nil + editorVisible = false + explorerVisible = true + + Task { + await self.onDelete() + } + } + + private func onDelete() async -> Void { + if let job = job { + if job.jid == 1.0 { + moc.delete(job) + } else { + job.alive = false + } + + PersistenceController.shared.save() + } + } +} diff --git a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift index 180ef439..82d488f9 100644 --- a/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift +++ b/DLPrototype/Views/Shared/PanelGroup/ThreePanelGroup.swift @@ -17,6 +17,8 @@ struct ThreePanelGroup: View { return Array(repeating: .init(.flexible(minimum: 100), spacing: 1), count: 3) } + @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true + @EnvironmentObject private var nav: Navigation var body: some View { @@ -68,6 +70,7 @@ extension ThreePanelGroup { /// - Returns: Void private func onChangeJob(job: Job?) -> Void { if job != nil { + editorVisible = true nav.forms.jobSelector.selected = [] if let project = job!.project { From 07199487e4158142037cf8522f953b5d26e12252 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 09:09:08 -0700 Subject: [PATCH 27/35] add delete prompt and sort projects picker by company and project name --- DLPrototype/Views/Entities/Jobs/JobDashboard.swift | 10 +++++++++- DLPrototype/Views/Shared/Fancy/FancyPicker.swift | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index e5c3868d..827cd630 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -231,6 +231,8 @@ struct JobExplorer: View { Array(repeating: .init(.flexible(minimum: 300)), count: 2) } + @State private var isDeletePresented: Bool = false + @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true @@ -256,7 +258,13 @@ struct JobExplorer: View { } FancyDivider() HStack(alignment: .bottom) { - FancySimpleButton(text: "Delete", action: triggerDelete, icon: "trash", showLabel: false, showIcon: true, type: .destructive) + FancySimpleButton(text: "Delete", action: {isDeletePresented = true}, icon: "trash", showLabel: false, showIcon: true, type: .destructive) + .alert("Are you sure you want to delete job ID \(job!.jid.string)? This is irreversible.", isPresented: $isDeletePresented) { + Button("Yes", role: .destructive) { + self.triggerDelete() + } + Button("No", role: .cancel) {} + } Spacer() FancySimpleButton(text: "Save", action: triggerSave) .keyboardShortcut("s", modifiers: [.command]) diff --git a/DLPrototype/Views/Shared/Fancy/FancyPicker.swift b/DLPrototype/Views/Shared/Fancy/FancyPicker.swift index b1df255c..b5bce2fc 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyPicker.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyPicker.swift @@ -39,8 +39,8 @@ struct FancyPicker: View { ForEach(items) { item in Text(item.title) .tag(item.tag) + // @TODO: this doesn't actually work; see https://stackoverflow.com/a/76154257 .disabled(item.disabled) - .font(Theme.font) } } .background(transparent! ? Color.clear : Theme.toolbarColour) @@ -67,8 +67,8 @@ struct FancyPicker: View { ForEach(items) { item in Text(item.title) .tag(item.tag) + // @TODO: this doesn't actually work; see https://stackoverflow.com/a/76154257 .disabled(item.disabled) - .font(Theme.font) } } .background(transparent! ? Color.clear : Theme.toolbarColour) From b8dfc266c1787c1cc02fc4478b850a7f49e163ab Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 09:09:27 -0700 Subject: [PATCH 28/35] add company labels to project picker, sort appropriately --- .../Views/Shared/ProjectPickerUsing.swift | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/DLPrototype/Views/Shared/ProjectPickerUsing.swift b/DLPrototype/Views/Shared/ProjectPickerUsing.swift index ef4a902d..c4568bcb 100644 --- a/DLPrototype/Views/Shared/ProjectPickerUsing.swift +++ b/DLPrototype/Views/Shared/ProjectPickerUsing.swift @@ -31,18 +31,30 @@ struct ProjectPickerUsing: View { @Environment(\.managedObjectContext) var moc private var pickerItems: [CustomPickerItem] { - var items: [CustomPickerItem] = [CustomPickerItem(title: "Choose a project", tag: 0)] - let projects = CoreDataProjects(moc: moc).alive() - - for project in projects { - if project.name != nil { + var items: [CustomPickerItem] = [CustomPickerItem(title: "Choose from Projects", tag: 0)] + let companies = CoreDataCompanies(moc: moc).alive() + + for company in companies.sorted(by: {$0.name! < $1.name!}) { + if company.name != nil && company.projects?.count ?? 0 > 0 { items.append( CustomPickerItem( - title: project.name!.capitalized, - tag: Int(project.pid), - project: project + title: company.name!.capitalized, + tag: Int(company.pid), + disabled: true ) ) + + if let projects = company.projects { + for project in (projects.allObjects as! [Project]).sorted(by: {$0.name! < $1.name!}) { + items.append( + CustomPickerItem( + title: " - \(project.name!.capitalized)", + tag: Int(project.pid), + project: project + ) + ) + } + } } } From fbd4b9c25a1b685e28be7798903eb7b4e2149d37 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 09:36:21 -0700 Subject: [PATCH 29/35] search now looks in new job fields overview and title --- DLPrototype/Views/Find/FindDashboard.swift | 2 +- .../Views/Find/Suggestions/Suggestions.swift | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/DLPrototype/Views/Find/FindDashboard.swift b/DLPrototype/Views/Find/FindDashboard.swift index 677e76a7..5039ff3b 100644 --- a/DLPrototype/Views/Find/FindDashboard.swift +++ b/DLPrototype/Views/Find/FindDashboard.swift @@ -559,7 +559,7 @@ extension FindDashboard { init(_ text: String) { let jr: NSFetchRequest = Job.fetchRequest() - jr.predicate = NSPredicate(format: "(uri CONTAINS[c] %@ OR jid.stringValue BEGINSWITH %@) AND alive = true", text, text) + jr.predicate = NSPredicate(format: "(uri CONTAINS[c] %@ OR jid.stringValue BEGINSWITH %@ OR overview CONTAINS[c] %@ OR title CONTAINS[c] %@) AND alive = true", text, text, text, text) jr.sortDescriptors = [ NSSortDescriptor(keyPath: \Job.created, ascending: false) ] diff --git a/DLPrototype/Views/Find/Suggestions/Suggestions.swift b/DLPrototype/Views/Find/Suggestions/Suggestions.swift index c82cab8f..7124e206 100644 --- a/DLPrototype/Views/Find/Suggestions/Suggestions.swift +++ b/DLPrototype/Views/Find/Suggestions/Suggestions.swift @@ -132,9 +132,21 @@ extension FindDashboard { ] if publishedOnly.wrappedValue { - req.predicate = NSPredicate(format: "alive == true && (jid.stringValue BEGINSWITH %@ || jid.stringValue == %@)", _searchText.wrappedValue) + req.predicate = NSPredicate( + format: "alive == true && (jid.stringValue BEGINSWITH %@ || jid.stringValue == %@ || title CONTAINS[c] %@ || overview CONTAINS[c] %@)", + _searchText.wrappedValue, + _searchText.wrappedValue, + _searchText.wrappedValue, + _searchText.wrappedValue + ) } else { - req.predicate = NSPredicate(format: "(jid.stringValue BEGINSWITH %@ || jid.stringValue == %@)", _searchText.wrappedValue) + req.predicate = NSPredicate( + format: "(jid.stringValue BEGINSWITH %@ || jid.stringValue == %@ || title CONTAINS[c] %@ || overview CONTAINS[c] %@)", + _searchText.wrappedValue, + _searchText.wrappedValue, + _searchText.wrappedValue, + _searchText.wrappedValue + ) } _items = FetchRequest(fetchRequest: req, animation: .easeInOut) From d8096c137e024bb04c0b759cd121b3e2126eb178 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Thu, 11 Jan 2024 09:46:55 -0700 Subject: [PATCH 30/35] save each field type --- .../Views/Entities/Jobs/JobDashboard.swift | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 827cd630..67566966 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -232,6 +232,7 @@ struct JobExplorer: View { } @State private var isDeletePresented: Bool = false + @State private var isInvalidJobIdWarningPresented: Bool = false @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true @@ -268,6 +269,9 @@ struct JobExplorer: View { Spacer() FancySimpleButton(text: "Save", action: triggerSave) .keyboardShortcut("s", modifiers: [.command]) + .alert("Please change the job ID to something other than 1.0", isPresented: $isInvalidJobIdWarningPresented) { + Button("Ok", role: .cancel) {} + } } Spacer() } @@ -279,11 +283,6 @@ struct JobExplorer: View { .padding(8) } } - .onChange(of: nav.saved) { status in - if status { - print("DERPO saving all fields") - } - } } } } @@ -292,13 +291,31 @@ extension JobExplorer.JobViewRedux { /// Triggers a save event so all fields can be updated at once /// - Returns: Void private func triggerSave() -> Void { - nav.save() + Task { + await self.onSave() + } } /// Update all fields at the same time /// - Returns: Void - private func onSave() -> Void { + private func onSave() async -> Void { + if job != nil && job!.jid > 1.0 { + let fields = job!.fields + + for field in fields { + if let entity = field.entity { + switch field.keyPath { + case "jid": entity.setValue(field.value as! Double, forKey: field.keyPath) + default: entity.setValue(field.value, forKey: field.keyPath) + } + entity.setValue(Date(), forKey: "lastUpdate") + PersistenceController.shared.save() + } + } + } else { + isInvalidJobIdWarningPresented = true + } } /// Deletes the current Job @@ -313,13 +330,15 @@ extension JobExplorer.JobViewRedux { await self.onDelete() } } - + + /// If an object is a temporary job (has jid=1.0), hard delete the job. If not, soft delete it. + /// - Returns: Void private func onDelete() async -> Void { - if let job = job { - if job.jid == 1.0 { - moc.delete(job) + if job != nil { + if job!.jid == 1.0 { + moc.delete(job!) } else { - job.alive = false + job!.alive = false } PersistenceController.shared.save() From 96c2d8600536b245f63c0be7d9ac592686b548bd Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Fri, 12 Jan 2024 13:36:09 -0700 Subject: [PATCH 31/35] working auto save editable text fields --- DLPrototype/Extensions/Models/Job.swift | 1 + DLPrototype/Utils/Navigation.swift | 48 ++++++++++++++++++- .../Views/Entities/Jobs/JobDashboard.swift | 16 ++----- .../Views/Shared/Fancy/FancyTextField.swift | 22 ++------- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/DLPrototype/Extensions/Models/Job.swift b/DLPrototype/Extensions/Models/Job.swift index 29720276..f2ca5ac6 100644 --- a/DLPrototype/Extensions/Models/Job.swift +++ b/DLPrototype/Extensions/Models/Job.swift @@ -16,6 +16,7 @@ extension Job { [ Field(type: .text, label: "Job ID", value: self.jid.string, entity: self, keyPath: "jid"), Field(type: .text, label: "Title", value: self.title, entity: self, keyPath: "title"), + Field(type: .text, label: "URL", value: self.uri, entity: self, keyPath: "uri"), Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour"), Field(type: .boolean, label: "Published", value: self.alive, entity: self, keyPath: "alive"), Field(type: .boolean, label: "SRED Qualified", value: self.shredable, entity: self, keyPath: "shredable"), diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 09cd5794..167120e3 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -224,6 +224,7 @@ extension Navigation { var editor: Editor = Editor() struct Editor { + var job: Job? = nil var jid: String? = "" var title: String? = "" } @@ -246,6 +247,28 @@ extension Navigation { self.keyPath = keyPath } + public func save() -> Void { + if let entity = self.entity { + switch self.keyPath { + case "jid": entity.setValue(self.value as! Double, forKey: self.keyPath) + case "colour": entity.setValue((self.value as! Color).toStored(), forKey: self.keyPath) + default: entity.setValue(self.value, forKey: self.keyPath) + } + + entity.setValue(Date(), forKey: "lastUpdate") + PersistenceController.shared.save() + } + } + + public func update(value: Any) -> Void { + if let entity = self.entity { + self.value = value + +// entity.setValue(Date(), forKey: "lastUpdate") + self.save() + } + } + public enum LayoutType { case text, dropdown, projectDropdown, date, boolean, colour, editor } @@ -254,6 +277,7 @@ extension Navigation { public var field: Field @State private var bValue: String = "" + @FocusState public var fieldSelectionChanged: Bool @EnvironmentObject private var nav: Navigation @@ -266,14 +290,34 @@ extension Navigation { switch field.type { case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) - case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue) + case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue, hasFocus: _fieldSelectionChanged) case .projectDropdown: ProjectPickerUsing(onChangeLarge: onChangeProjectDropdown, size: .large, defaultSelection: Int((self.field.value as! Project).pid), displayName: $bValue) default: - FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue) + FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue, hasFocus: _fieldSelectionChanged) } } } .onAppear(perform: onLoad) + .onChange(of: fieldSelectionChanged) { status in + print("DERPO INg status=\(status) \(self.bValue)") + if !status { +// self.test() +// if let entity = field.entity { +// entity.setValue(status, forKey: self.field.keyPath) +// entity.setValue(Date(), forKey: "lastUpdate") +// PersistenceController.shared.save() +// } + } + } + } + + private func test() -> Void { + switch field.type { + case .boolean: field.update(value: Bool(bValue)) + case .colour: field.update(value: Double(bValue)) + case .projectDropdown: field.update(value: Int(bValue)) + default: field.update(value: bValue) + } } private func onLoad() -> Void { diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 67566966..9afaa37d 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -111,6 +111,7 @@ struct JobDashboardRedux: View { newJob.overview = "Edit this description" newJob.title = "Sample job title" nav.session.job = newJob + nav.forms.jobSelector.editor.job = newJob }, icon: "plus", showLabel: false @@ -188,7 +189,6 @@ struct JobExplorer: View { .font(.caption) Spacer() } - } .padding(5) .background(.white.opacity(0.2)) @@ -300,18 +300,8 @@ extension JobExplorer.JobViewRedux { /// - Returns: Void private func onSave() async -> Void { if job != nil && job!.jid > 1.0 { - let fields = job!.fields - - for field in fields { - if let entity = field.entity { - switch field.keyPath { - case "jid": entity.setValue(field.value as! Double, forKey: field.keyPath) - default: entity.setValue(field.value, forKey: field.keyPath) - } - - entity.setValue(Date(), forKey: "lastUpdate") - PersistenceController.shared.save() - } + for field in job!.fields { + field.save() } } else { isInvalidJobIdWarningPresented = true diff --git a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift index 5d30dcdf..21d6a491 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift @@ -21,12 +21,12 @@ struct FancyTextField: View { public var font: Font = Theme.fontTextField @Binding public var text: String - + @AppStorage("enableAutoCorrection") public var enableAutoCorrection: Bool = false @EnvironmentObject private var nav: Navigation - @FocusState public var focused: Bool + @FocusState public var hasFocus: Bool var body: some View { HStack(spacing: 5) { @@ -61,7 +61,7 @@ struct FancyTextField: View { .disabled(disabled ?? false) .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) - .focused($focused) + .focused($hasFocus) } private var oneBigLine: some View { @@ -76,7 +76,7 @@ struct FancyTextField: View { .disabled(disabled ?? false) .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) - .focused($focused) + .focused($hasFocus) } private var multiLine: some View { @@ -92,7 +92,7 @@ struct FancyTextField: View { .disabled(disabled ?? false) .foregroundColor(disabled ?? false ? Color.gray : fgColour) .textSelection(.enabled) - .focused($focused) + .focused($hasFocus) } } @@ -101,15 +101,3 @@ extension FancyTextField { text = "" } } - -struct FancyTextFieldPreview: PreviewProvider { - @State static private var text: String = "Test text" - - static var previews: some View { - VStack { - FancyTextField(placeholder: "Small one", lineLimit: 1, onSubmit: {}, text: $text) - FancyTextField(placeholder: "Medium one", lineLimit: 9, onSubmit: {}, text: $text) - FancyTextField(placeholder: "Big one", lineLimit: 100, onSubmit: {}, text: $text) - } - } -} From b6e411be9bc9191a47626a2c1682e3af680f2eae Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Mon, 12 Feb 2024 18:48:18 -0700 Subject: [PATCH 32/35] uhh I forget --- DLPrototype/Utils/Navigation.swift | 54 +++++++----------------------- 1 file changed, 12 insertions(+), 42 deletions(-) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 167120e3..d43de896 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -250,21 +250,20 @@ extension Navigation { public func save() -> Void { if let entity = self.entity { switch self.keyPath { - case "jid": entity.setValue(self.value as! Double, forKey: self.keyPath) + case "jid": entity.setValue(Double(self.value as! String), forKey: self.keyPath) case "colour": entity.setValue((self.value as! Color).toStored(), forKey: self.keyPath) + case "created": entity.setValue(self.value != nil ? self.value : NSDate(), forKey: self.keyPath) + case "lastUpdate": entity.setValue(NSDate(), forKey: self.keyPath) default: entity.setValue(self.value, forKey: self.keyPath) } - entity.setValue(Date(), forKey: "lastUpdate") PersistenceController.shared.save() } } public func update(value: Any) -> Void { - if let entity = self.entity { + if self.entity != nil { self.value = value - -// entity.setValue(Date(), forKey: "lastUpdate") self.save() } } @@ -299,27 +298,12 @@ extension Navigation { } .onAppear(perform: onLoad) .onChange(of: fieldSelectionChanged) { status in - print("DERPO INg status=\(status) \(self.bValue)") if !status { -// self.test() -// if let entity = field.entity { -// entity.setValue(status, forKey: self.field.keyPath) -// entity.setValue(Date(), forKey: "lastUpdate") -// PersistenceController.shared.save() -// } + field.update(value: bValue) } } } - private func test() -> Void { - switch field.type { - case .boolean: field.update(value: Bool(bValue)) - case .colour: field.update(value: Double(bValue)) - case .projectDropdown: field.update(value: Int(bValue)) - default: field.update(value: bValue) - } - } - private func onLoad() -> Void { if let value = self.field.value as? String { bValue = value @@ -328,39 +312,25 @@ extension Navigation { private func onChangeToggle(status: Bool) -> Void { if status { - if let entity = field.entity { - entity.setValue(status, forKey: self.field.keyPath) - entity.setValue(Date(), forKey: "lastUpdate") - PersistenceController.shared.save() - } + field.update(value: status) } } private func onChangeProjectDropdown(selected: Project, sender: String?) -> Void { - if let entity = field.entity { - entity.setValue(selected, forKey: self.field.keyPath) - entity.setValue(Date(), forKey: "lastUpdate") - PersistenceController.shared.save() + if field.entity != nil { + field.update(value: selected) } } private func onChange(colour: Color) -> Void { - if let entity = field.entity { - entity.setValue(colour.toStored(), forKey: self.field.keyPath) - entity.setValue(Date(), forKey: "lastUpdate") - PersistenceController.shared.save() + if field.entity != nil { + field.update(value: colour.toStored()) } } private func onSubmit() -> Void { - if let entity = field.entity { - if self.field.keyPath == "jid" { - entity.setValue(Double(bValue), forKey: self.field.keyPath) - } else { - entity.setValue(bValue, forKey: self.field.keyPath) - } - entity.setValue(Date(), forKey: "lastUpdate") - PersistenceController.shared.save() + if field.entity != nil { + field.update(value: bValue) } } } From deb39ba844f727ea0b60358c01da1663e84cfd06 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Mon, 12 Feb 2024 19:20:24 -0700 Subject: [PATCH 33/35] fixes crash on save of date and uri fields --- DLPrototype/Helpers/UrlHelper.swift | 9 +++++++++ DLPrototype/Utils/Navigation.swift | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/DLPrototype/Helpers/UrlHelper.swift b/DLPrototype/Helpers/UrlHelper.swift index 948e717e..e92f368b 100644 --- a/DLPrototype/Helpers/UrlHelper.swift +++ b/DLPrototype/Helpers/UrlHelper.swift @@ -11,6 +11,7 @@ import SwiftUI public struct UrlParts { public var jid_double: Double? = 0.0 public var jid_string: String = "0.0" + public var isValid: Bool = false } public final class UrlHelper { @@ -19,6 +20,14 @@ public final class UrlHelper { public init(url: URL) { self.url = url } + + static public func from(uri: String) -> UrlParts { + if let _ = URL(string: uri) { + return UrlParts(isValid: true) + } + + return UrlParts(isValid: false) + } static public func parts(of url: URL) -> UrlParts { let instance = UrlHelper(url: url) diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index d43de896..86f7bdd5 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -204,6 +204,7 @@ extension Navigation { var eventStatus: EventIndicatorStatus = .ready } + // @TODO: remove from Navigation public struct Forms { var note: NoteForm = NoteForm() var jobSelector: JobSelectorForm = JobSelectorForm() @@ -250,9 +251,10 @@ extension Navigation { public func save() -> Void { if let entity = self.entity { switch self.keyPath { + case "uri": entity.setValue(UrlHelper.from(uri: self.value as! String).isValid ? self.value : URL(string: self.value as! String)?.absoluteString, forKey: self.keyPath) case "jid": entity.setValue(Double(self.value as! String), forKey: self.keyPath) case "colour": entity.setValue((self.value as! Color).toStored(), forKey: self.keyPath) - case "created": entity.setValue(self.value != nil ? self.value : NSDate(), forKey: self.keyPath) + case "created": entity.setValue(self.value != nil && !self.value.debugDescription.isEmpty ? DateHelper.date(self.value as! String) : NSDate(), forKey: self.keyPath) case "lastUpdate": entity.setValue(NSDate(), forKey: self.keyPath) default: entity.setValue(self.value, forKey: self.keyPath) } From ea1cdea2e599222caf42536fce1e5f66b2fcf16f Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 11 May 2024 13:00:41 -0600 Subject: [PATCH 34/35] mostly working job dashboard redux view --- DLPrototype.xcodeproj/project.pbxproj | 2 +- DLPrototype/Extensions/Models/Job.swift | 4 +- DLPrototype/Utils/Navigation.swift | 117 +++++++++++------- .../Views/Entities/Jobs/JobDashboard.swift | 73 ++++------- .../Views/Shared/Fancy/FancyTextField.swift | 17 ++- 5 files changed, 112 insertions(+), 101 deletions(-) diff --git a/DLPrototype.xcodeproj/project.pbxproj b/DLPrototype.xcodeproj/project.pbxproj index fdda1889..c475fce4 100644 --- a/DLPrototype.xcodeproj/project.pbxproj +++ b/DLPrototype.xcodeproj/project.pbxproj @@ -1657,7 +1657,6 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = DLPrototype/DLPrototype.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 161; @@ -1677,6 +1676,7 @@ MARKETING_VERSION = 1; PRODUCT_BUNDLE_IDENTIFIER = com.yegcollective.DLPrototype; PRODUCT_NAME = ClockWork; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = macosx; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/DLPrototype/Extensions/Models/Job.swift b/DLPrototype/Extensions/Models/Job.swift index f2ca5ac6..df89e5db 100644 --- a/DLPrototype/Extensions/Models/Job.swift +++ b/DLPrototype/Extensions/Models/Job.swift @@ -20,8 +20,8 @@ extension Job { Field(type: .colour, label: "Colour", value: self.colour, entity: self, keyPath: "colour"), Field(type: .boolean, label: "Published", value: self.alive, entity: self, keyPath: "alive"), Field(type: .boolean, label: "SRED Qualified", value: self.shredable, entity: self, keyPath: "shredable"), - Field(type: .date, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .omitted), entity:self, keyPath: "lastUpdate"), - Field(type: .date, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .omitted), entity: self,keyPath: "created"), + Field(type: .date, label: "Last update", value: self.lastUpdate?.formatted(date: .abbreviated, time: .standard), entity:self, keyPath: "lastUpdate"), + Field(type: .date, label: "Created", value: self.created?.formatted(date: .abbreviated, time: .standard), entity: self,keyPath: "created"), Field(type: .projectDropdown, label: "Project", value: self.project, entity: self, keyPath: "project"), Field(type: .editor, label: "Description", value: self.overview, entity: self, keyPath: "overview") ] diff --git a/DLPrototype/Utils/Navigation.swift b/DLPrototype/Utils/Navigation.swift index 86f7bdd5..15722c62 100644 --- a/DLPrototype/Utils/Navigation.swift +++ b/DLPrototype/Utils/Navigation.swift @@ -231,14 +231,15 @@ extension Navigation { } } - public class Field: Identifiable { + public class Field: Identifiable, Equatable { public let id: UUID = UUID() public var type: LayoutType = .text - public var body: some View { Layout(field: self) } + public var body: some View { FieldView(field: self) } public var label: String = "" public var value: Any? = nil public var entity: NSManagedObject? = nil public var keyPath: String + public var status: FieldStatus = .standard init(type: LayoutType, label: String, value: Any? = nil, entity: NSManagedObject? = nil, keyPath: String) { self.type = type @@ -248,25 +249,33 @@ extension Navigation { self.keyPath = keyPath } - public func save() -> Void { + static public func == (lhs: Navigation.Forms.Field, rhs: Navigation.Forms.Field) -> Bool { + return lhs.id == rhs.id + } + + public func update(value: Any) -> Void { if let entity = self.entity { + let en = entity as! Job + switch self.keyPath { - case "uri": entity.setValue(UrlHelper.from(uri: self.value as! String).isValid ? self.value : URL(string: self.value as! String)?.absoluteString, forKey: self.keyPath) - case "jid": entity.setValue(Double(self.value as! String), forKey: self.keyPath) - case "colour": entity.setValue((self.value as! Color).toStored(), forKey: self.keyPath) - case "created": entity.setValue(self.value != nil && !self.value.debugDescription.isEmpty ? DateHelper.date(self.value as! String) : NSDate(), forKey: self.keyPath) - case "lastUpdate": entity.setValue(NSDate(), forKey: self.keyPath) - default: entity.setValue(self.value, forKey: self.keyPath) + case "uri": self.value = URL(string: value as? String ?? "") + case "title": self.value = value as? String ?? "" + case "shredable": self.value = value as? Bool ?? false + case "overview": self.value = value as? String ?? "" + case "lastUpdate": self.value = Date() + case "jid": self.value = Double(value as! String) ?? 1.0 + case "created": self.value = DateHelper.date(value as! String) + case "colour": self.value = value as? Array + case "alive": self.value = value as? Bool ?? false + case "id": self.value = en.id + case "project": self.value = en.project // @TODO: should use self.value + default: + print("[debug][Navigation.Forms.Field] Unknown field \(self.keyPath)") } - PersistenceController.shared.save() - } - } + en.setValue(self.value, forKey: self.keyPath) - public func update(value: Any) -> Void { - if self.entity != nil { - self.value = value - self.save() + PersistenceController.shared.save() } } @@ -274,11 +283,16 @@ extension Navigation { case text, dropdown, projectDropdown, date, boolean, colour, editor } - struct Layout: View { + public enum FieldStatus { + case unsaved, saved, standard + } + + struct FieldView: View { public var field: Field - @State private var bValue: String = "" - @FocusState public var fieldSelectionChanged: Bool + @State private var oldValue: String = "" + @State private var newValue: String = "" + @State private var status: FieldStatus = .standard @EnvironmentObject private var nav: Navigation @@ -290,50 +304,65 @@ extension Navigation { switch field.type { case .boolean: FancyToggle(label: self.field.label, value: self.field.value as! Bool, onChange: self.onChangeToggle) - case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChange, showLabel: false) - case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, text: $bValue, hasFocus: _fieldSelectionChanged) - case .projectDropdown: ProjectPickerUsing(onChangeLarge: onChangeProjectDropdown, size: .large, defaultSelection: Int((self.field.value as! Project).pid), displayName: $bValue) + case .colour: FancyColourPicker(initialColour: self.field.value as! [Double], onChange: self.onChangeColour, showLabel: false) + case .editor: FancyTextField(placeholder: self.field.label, lineLimit: 10, fieldStatus: self.field.status, text: $newValue) + case .projectDropdown: ProjectPickerUsing(onChangeLarge: onChangeProjectDropdown, size: .large, defaultSelection: Int((self.field.value as! Project).pid), displayName: $newValue) default: - FancyTextField(placeholder: self.field.label, onSubmit: onSubmit, text: $bValue, hasFocus: _fieldSelectionChanged) + FancyTextField(placeholder: self.field.label, fieldStatus: self.field.status, text: $newValue) } } } .onAppear(perform: onLoad) - .onChange(of: fieldSelectionChanged) { status in - if !status { - field.update(value: bValue) + .onChange(of: newValue) { _ in + field.status = .unsaved + + if oldValue != newValue { + field.update(value: newValue) + field.status = .saved + + self.status = field.status } } } private func onLoad() -> Void { - if let value = self.field.value as? String { - bValue = value + if let entity = self.field.entity { + if let value = entity.value(forKey: self.field.keyPath) { + switch self.field.keyPath { + case "uri": self.field.value = (value as? URL)?.absoluteString + case "title": self.field.value = value as? String ?? "" + case "shredable": self.field.value = value as? Bool ?? false + case "overview": self.field.value = value as? String ?? "" + case "lastUpdate": self.field.value = Date().description + case "jid": self.field.value = (value as! Double).string + case "created": self.field.value = value + case "colour": self.field.value = value as? Array + case "alive": self.field.value = value as? Bool ?? false + default: + print("[debug] Unknown field \(self.field.keyPath)") + } + + oldValue = self.field.value as? String ?? "" + newValue = self.field.value as? String ?? "" + } } + + self.status = field.status } private func onChangeToggle(status: Bool) -> Void { - if status { - field.update(value: status) - } + field.update(value: status) + self.status = field.status } private func onChangeProjectDropdown(selected: Project, sender: String?) -> Void { - if field.entity != nil { - field.update(value: selected) - } + field.update(value: selected) + self.status = field.status } - private func onChange(colour: Color) -> Void { - if field.entity != nil { - field.update(value: colour.toStored()) - } - } - - private func onSubmit() -> Void { - if field.entity != nil { - field.update(value: bValue) - } + private func onChangeColour(colour: Color) -> Void { + field.update(value: colour.toStored()) + self.status = field.status } } } diff --git a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift index 9afaa37d..1b559d12 100644 --- a/DLPrototype/Views/Entities/Jobs/JobDashboard.swift +++ b/DLPrototype/Views/Entities/Jobs/JobDashboard.swift @@ -226,7 +226,7 @@ struct JobExplorer: View { public struct JobViewRedux: View { public var job: Job? = nil private var fields: [Navigation.Forms.Field] { job != nil ? job!.fields : [] } - private let columnSplit: [Navigation.Forms.Field.LayoutType] = [.date, .projectDropdown] + private let columnSplit: [Navigation.Forms.Field.LayoutType] = [.date, .projectDropdown, .colour] private var columns: [GridItem] { Array(repeating: .init(.flexible(minimum: 300)), count: 2) } @@ -244,38 +244,35 @@ struct JobExplorer: View { ScrollView { if job != nil { Grid(alignment: .topLeading, horizontalSpacing: 8, verticalSpacing: 10) { - LazyVGrid(columns: columns) { - VStack { - ForEach(fields.filter({!columnSplit.contains($0.type)})) { field in - field.body - } - Spacer() - } - .padding([.top, .leading], 8) - - VStack { - ForEach(fields.filter({columnSplit.contains($0.type)})) { field in - field.body + GridRow { + LazyVGrid(columns: columns) { + VStack { + ForEach(fields.filter({!columnSplit.contains($0.type)})) { field in + field.body + } + Spacer() } - FancyDivider() - HStack(alignment: .bottom) { - FancySimpleButton(text: "Delete", action: {isDeletePresented = true}, icon: "trash", showLabel: false, showIcon: true, type: .destructive) - .alert("Are you sure you want to delete job ID \(job!.jid.string)? This is irreversible.", isPresented: $isDeletePresented) { - Button("Yes", role: .destructive) { - self.triggerDelete() + .padding([.top, .leading], 8) + + VStack { + ForEach(fields.filter({columnSplit.contains($0.type)})) { field in + field.body + } + FancyDivider() + HStack(alignment: .bottom) { + Spacer() + FancySimpleButton(text: "Delete", action: {isDeletePresented = true}, icon: "trash", showLabel: false, showIcon: true, type: .destructive) + .alert("Are you sure you want to delete job ID \(job!.jid.string)? This is irreversible.", isPresented: $isDeletePresented) { + Button("Yes", role: .destructive) { + self.triggerDelete() + } + Button("No", role: .cancel) {} } - Button("No", role: .cancel) {} - } + } Spacer() - FancySimpleButton(text: "Save", action: triggerSave) - .keyboardShortcut("s", modifiers: [.command]) - .alert("Please change the job ID to something other than 1.0", isPresented: $isInvalidJobIdWarningPresented) { - Button("Ok", role: .cancel) {} - } } - Spacer() + .padding([.top, .trailing], 8) } - .padding([.top, .trailing], 8) } } .background(Theme.toolbarColour) @@ -288,26 +285,6 @@ struct JobExplorer: View { } extension JobExplorer.JobViewRedux { - /// Triggers a save event so all fields can be updated at once - /// - Returns: Void - private func triggerSave() -> Void { - Task { - await self.onSave() - } - } - - /// Update all fields at the same time - /// - Returns: Void - private func onSave() async -> Void { - if job != nil && job!.jid > 1.0 { - for field in job!.fields { - field.save() - } - } else { - isInvalidJobIdWarningPresented = true - } - } - /// Deletes the current Job /// - Returns: Void private func triggerDelete() -> Void { diff --git a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift index 21d6a491..8f0ee223 100644 --- a/DLPrototype/Views/Shared/Fancy/FancyTextField.swift +++ b/DLPrototype/Views/Shared/Fancy/FancyTextField.swift @@ -13,12 +13,14 @@ struct FancyTextField: View { public var placeholder: String public var lineLimit: Int = 1 public var onSubmit: (() -> Void)? = nil + public var onChange: ((Any) -> Void)? = nil public var transparent: Bool? = false public var disabled: Bool? = false public var fgColour: Color? = Color.white public var bgColour: Color? = Theme.textBackground public var showLabel: Bool = false public var font: Font = Theme.fontTextField + public var fieldStatus: Navigation.Forms.Field.FieldStatus = .standard @Binding public var text: String @@ -55,11 +57,12 @@ struct FancyTextField: View { .disableAutocorrection(!enableAutoCorrection) .padding() .onSubmit(onSubmit ?? {}) - .background(transparent! ? Color.clear : bgColour) + .onChange(of: text) { newText in self.onChange != nil ? self.onChange!(newText) : nil } + .background(fieldStatus == .standard ? (transparent! ? Color.clear : bgColour) : fieldStatus == .unsaved ? Color.yellow : Theme.cGreen) // sorry .frame(height: 45) .lineLimit(1) .disabled(disabled ?? false) - .foregroundColor(disabled ?? false ? Color.gray : fgColour) + .foregroundColor(disabled ?? false ? Color.gray : fieldStatus == .unsaved ? .black : fgColour) .textSelection(.enabled) .focused($hasFocus) } @@ -71,10 +74,11 @@ struct FancyTextField: View { .disableAutocorrection(!enableAutoCorrection) .padding() .onSubmit(onSubmit ?? {}) - .background(transparent! ? Color.clear : bgColour) + .onChange(of: text) { newText in self.onChange != nil ? self.onChange!(newText) : nil } + .background(fieldStatus == .standard ? (transparent! ? Color.clear : bgColour) : fieldStatus == .unsaved ? Color.yellow : Theme.cGreen) // sorry .lineLimit(lineLimit...) .disabled(disabled ?? false) - .foregroundColor(disabled ?? false ? Color.gray : fgColour) + .foregroundColor(disabled ?? false ? Color.gray : fieldStatus == .unsaved ? .black : fgColour) .textSelection(.enabled) .focused($hasFocus) } @@ -86,11 +90,12 @@ struct FancyTextField: View { .disableAutocorrection(!enableAutoCorrection) .padding() .onSubmit(onSubmit ?? {}) - .background(transparent! ? Theme.textBackground : bgColour) + .onChange(of: text) { newText in self.onChange != nil ? self.onChange!(newText) : nil } + .background(fieldStatus == .standard ? (transparent! ? Color.clear : bgColour) : fieldStatus == .unsaved ? Color.yellow : Theme.cGreen) // sorry{} .scrollContentBackground(.hidden) .lineLimit(lineLimit...) .disabled(disabled ?? false) - .foregroundColor(disabled ?? false ? Color.gray : fgColour) + .foregroundColor(disabled ?? false ? Color.gray : fieldStatus == .unsaved ? .black : fgColour) .textSelection(.enabled) .focused($hasFocus) } From 9094da4a4bc906062793ae2755f43047badf9f58 Mon Sep 17 00:00:00 2001 From: Ryan Priebe Date: Sat, 11 May 2024 13:02:13 -0600 Subject: [PATCH 35/35] bump --- DLPrototype.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DLPrototype.xcodeproj/project.pbxproj b/DLPrototype.xcodeproj/project.pbxproj index c475fce4..1105750a 100644 --- a/DLPrototype.xcodeproj/project.pbxproj +++ b/DLPrototype.xcodeproj/project.pbxproj @@ -1625,7 +1625,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 161; + CURRENT_PROJECT_VERSION = 162; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"DLPrototype/Preview Content\""; DEVELOPMENT_TEAM = 6DT7L2N5X6; @@ -1659,7 +1659,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 161; + CURRENT_PROJECT_VERSION = 162; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"DLPrototype/Preview Content\""; DEVELOPMENT_TEAM = 6DT7L2N5X6;