diff --git a/KWCore/Sources/Extensions/StringProtocol.swift b/KWCore/Sources/Extensions/StringProtocol.swift new file mode 100644 index 00000000..319a4214 --- /dev/null +++ b/KWCore/Sources/Extensions/StringProtocol.swift @@ -0,0 +1,15 @@ +// +// StringProtocol.swift +// KlockWork +// +// Created by Ryan Priebe on 2024-10-28. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI + +extension StringProtocol { + // Thanks: https://stackoverflow.com/a/28288340 + public var firstUppercased: String { prefix(1).uppercased() + dropFirst() } + public var firstCapitalized: String { prefix(1).capitalized + dropFirst() } +} diff --git a/KWCore/Sources/Query/CoreDataRecords.swift b/KWCore/Sources/Query/CoreDataRecords.swift index f663f801..5ffcc91d 100644 --- a/KWCore/Sources/Query/CoreDataRecords.swift +++ b/KWCore/Sources/Query/CoreDataRecords.swift @@ -310,14 +310,18 @@ public class CoreDataRecords: ObservableObject { return count(predicate) } - public func forDate(_ date: Date) -> [LogRecord] { + public func forDate(_ date: Date, sort: NSSortDescriptor? = nil) -> [LogRecord] { let (start, end) = DateHelper.startAndEndOf(date) let predicate = NSPredicate( format: "(alive == true && timestamp > %@ && timestamp <= %@) && job.project.company.hidden == false", start as CVarArg, end as CVarArg ) - + + if sort != nil { + return query(predicate, sort: [sort!]) + } + return query(predicate) } @@ -695,12 +699,12 @@ public class CoreDataRecords: ObservableObject { return record } - private func query(_ predicate: NSPredicate) -> [LogRecord] { + private func query(_ predicate: NSPredicate, sort: [NSSortDescriptor] = [NSSortDescriptor(keyPath: \LogRecord.timestamp, ascending: false)]) -> [LogRecord] { lock.lock() var results: [LogRecord] = [] let fetch: NSFetchRequest = LogRecord.fetchRequest() - fetch.sortDescriptors = [NSSortDescriptor(keyPath: \LogRecord.timestamp, ascending: false)] + fetch.sortDescriptors = sort fetch.predicate = predicate fetch.returnsDistinctResults = true diff --git a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.Blocks.swift b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.Blocks.swift new file mode 100644 index 00000000..74d849b3 --- /dev/null +++ b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.Blocks.swift @@ -0,0 +1,81 @@ +// +// WidgetLibrary.Blocks.swift +// KlockWork +// +// Created by Ryan Priebe on 2024-10-28. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI +import KWCore + +extension WidgetLibrary.UI { + public struct Blocks { + struct Definition: View { + @EnvironmentObject public var state: Navigation + public var definition: TaxonomyTermDefinitions + @State private var isHighlighted: Bool = false + + var body: some View { + Button { + self.state.session.job = self.definition.job + self.state.session.project = self.state.session.job?.project + self.state.session.company = self.state.session.project?.company + self.state.session.definition = self.definition + self.state.to(.definitionDetail) + } label: { + HStack(alignment: .top, spacing: 10) { + Text(self.definition.definition ?? "Error: Missing definition") + Spacer() + } + .padding(8) + .background(self.definition.job?.backgroundColor ?? Theme.rowColour) + .foregroundStyle((self.definition.job?.backgroundColor ?? Theme.rowColour).isBright() ? Theme.base : Theme.lightWhite) + } + .buttonStyle(.plain) + .useDefaultHover({ hover in self.isHighlighted = hover}) + } + } + + struct Icon: View, Identifiable { + @EnvironmentObject private var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var id: UUID = UUID() + public var type: EType + public var text: String + public var colour: Color + @State private var isHighlighted: Bool = false + + var body: some View { + VStack(alignment: .center, spacing: 0) { + ZStack(alignment: .center) { + (self.viewModeIndex == 0 ? Color.gray.opacity(self.isHighlighted ? 1 : 0.7) : self.colour.opacity(self.isHighlighted ? 1 : 0.7)) + VStack(alignment: .center, spacing: 0) { + (self.isHighlighted ? self.type.selectedIcon : self.type.icon) + .symbolRenderingMode(.hierarchical) + .font(.largeTitle) +// .foregroundStyle(self.viewModeIndex == 0 ? self.colour : self.colour.isBright() ? Theme.base : .white) + .foregroundStyle(self.viewModeIndex == 0 ? self.colour : .white) + } + Spacer() + } + .frame(height: 65) + + ZStack(alignment: .center) { + (self.isHighlighted ? Color.yellow : Theme.textBackground) + VStack(alignment: .center, spacing: 0) { + Text(self.text) + .font(.system(.title3, design: .monospaced)) + .foregroundStyle(self.isHighlighted ? Theme.base : .gray) + } + .padding([.leading, .trailing], 4) + } + .frame(height: 25) + } + .frame(width: 65) + .clipShape(.rect(cornerRadius: 5)) + .useDefaultHover({ hover in self.isHighlighted = hover }) + } + } + } +} diff --git a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Buttons.swift b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Buttons.swift new file mode 100644 index 00000000..c4e8bf47 --- /dev/null +++ b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Buttons.swift @@ -0,0 +1,463 @@ +// +// WidgetLibrary.UI.Buttons.swift +// KlockWork +// +// Created by Ryan Priebe on 2024-10-28. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI +import KWCore + +extension WidgetLibrary.UI { + public struct Buttons { + // Buttons which can be used by HistoryPage's to automatically add buttons to a page's top bar + public enum UIButtonType { + case resetUserChoices, createNote, createTask, createRecord, createPerson, createCompany, createProject, + createJob, createTerm, createDefinition, historyPrevious, settings, CLIMode, CLIFilter, sidebarToggle + } + + struct ResetUserChoices: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onActionClear: (() -> Void)? + public var isAlteredForReadability: Bool = false + + var body: some View { + if self.state.session.job != nil || self.state.session.project != nil || self.state.session.company != nil { + FancyButtonv2( + text: "Reset interface to default state", + action: self.onActionClear != nil ? self.onActionClear : self.defaultClearAction, + icon: "arrow.clockwise.square.fill", + iconWhenHighlighted: "arrow.clockwise.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Reset interface to default state") + .frame(width: 25) + } else { + EmptyView() + } + } + } + + struct CreateNote: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: self.onAction, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + sidebar: AnyView(NoteCreateSidebar()), + font: .title + ) + .help("Create a new note") + .frame(width: 25) + } + } + + struct CreatePerson: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: { self.onAction?() ; self.state.to(.peopleDetail) }, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new contact") + .frame(width: 25) + } + } + + struct CreateCompany: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: { self.onAction?() ; self.state.to(.companyDetail) }, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new company") + .frame(width: 25) + } + } + + struct CreateProject: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var location: WidgetLocation = .content + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: { self.onAction?() ; self.state.to(.projectDetail) }, + icon: self.location == .sidebar ? "folder.fill.badge.plus" : "plus.square.fill", + iconWhenHighlighted: self.location == .sidebar ? "folder.badge.plus" : "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: self.location == .sidebar ? .title2 : .title + ) + .help("Create a new project") + .frame(width: 25) + } + } + + struct CreateJob: View { + @EnvironmentObject public var state: Navigation + @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true + @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: self.actionOnTap, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new job") + .frame(width: 25) + } + } + + struct CreateTask: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: { self.onAction?() ; self.state.to(.taskDetail) }, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new task") + .frame(width: 25) + } + } + + struct CreateTerm: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: { self.onAction?() ; self.state.to(.terms) }, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new taxonomy term") + .frame(width: 25) + } + } + + struct CreateDefinition: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + + var body: some View { + FancyButtonv2( + text: "Create", + action: { self.onAction?() ; self.state.to(.definitionDetail) }, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new term definition") + .frame(width: 25) + } + } + + struct CreateRecord: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + @State private var isHighlighted: Bool = false + @State private var selectedPage: Page = .dashboard + + var body: some View { + FancyButtonv2( + text: self.state.session.job != nil ? "Log to job \(self.state.session.job!.title ?? self.state.session.job!.jid.string)" : "Log", + action: self.onAction, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new record") + .frame(width: 25) + .disabled(self.state.session.job == nil) + .opacity(self.state.session.job == nil ? 0.5 : 1) + } + } + + struct CreateRecordToday: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 + public var onAction: (() -> Void)? = {} + public var isAlteredForReadability: Bool = false + @State private var isHighlighted: Bool = false + @State private var selectedPage: Page = .dashboard + + var body: some View { + FancyButtonv2( + text: self.state.session.job != nil ? "Log to job \(self.state.session.job!.title ?? self.state.session.job!.jid.string)" : "Log", + action: { + self.onAction?() + self.state.to(.today) + }, + icon: "plus.square.fill", + iconWhenHighlighted: "plus.square", + fgColour: self.viewModeIndex == 1 ? self.isAlteredForReadability ? Theme.base : .white : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Create a new record") + .frame(width: 25) + .disabled(self.state.session.job == nil) + .opacity(self.state.session.job == nil ? 0.5 : 1) + } + } + + struct HistoryPrevious: View { + @EnvironmentObject public var state: Navigation + public var onAction: (() -> Void)? = {} + @State private var isHighlighted: Bool = false + @State private var selectedPage: Page = .dashboard + private var isEmpty: Bool { self.state.history.recent.count == 0 } + + var body: some View { + Button { + if let previous = self.state.history.previous() { + self.state.to(previous.page, addToHistory: false) + self.selectedPage = previous.page + self.onAction?() + } + } label: { + HStack(alignment: .center) { + Image(systemName: "chevron.left") + Text("Back") + } + .padding(8) + .background(self.state.session.appPage.primaryColour.opacity(self.isHighlighted ? 1 : 0.6)) + .foregroundStyle(self.isHighlighted ? .white : Theme.lightWhite) + .clipShape(.rect(cornerRadius: 5)) + .padding([.top, .bottom], 10) + } + .disabled(self.isEmpty) + .keyboardShortcut(KeyEquivalent.leftArrow, modifiers: [.command]) + .buttonStyle(.plain) + .useDefaultHover({ hover in !self.isEmpty ? self.isHighlighted = hover : nil}) + } + } + + struct Settings: View { + @EnvironmentObject public var state: Navigation + public var onAction: (() -> Void)? = {} + @State private var isHighlighted: Bool = false + @State private var selectedPage: Page = .dashboard + + var body: some View { + Button { + self.onAction?() + } label: { + Image(systemName: "gear") + .font(.title) + .foregroundStyle(self.isHighlighted ? .white : Theme.lightWhite) + .padding([.leading, .trailing]) + .padding([.top, .bottom], 10) + } + .keyboardShortcut(KeyEquivalent.leftArrow, modifiers: [.command]) + .buttonStyle(.plain) + .useDefaultHover({ hover in self.isHighlighted = hover}) + } + } + + struct CLIMode: View { + @EnvironmentObject public var state: Navigation + @AppStorage("general.experimental.cli") private var cliEnabled: Bool = false + @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false + public var onAction: (() -> Void)? = {} + @State private var isHighlighted: Bool = false + @State private var selectedPage: Page = .dashboard + + var body: some View { + if self.cliEnabled { + FancyButtonv2( + text: "Command line mode", + action: {self.commandLineMode.toggle() ; self.onAction?()}, + icon: self.commandLineMode ? "apple.terminal.fill" : "apple.terminal", + iconWhenHighlighted: self.commandLineMode ? "apple.terminal" : "apple.terminal.fill", + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help(self.commandLineMode ? "Exit CLI mode" : "Enter CLI mode") + .frame(width: 25) + } + } + } + + struct CLIFilter: View { + @EnvironmentObject public var state: Navigation + @AppStorage("general.experimental.cli") private var cliEnabled: Bool = false + @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false + @AppStorage("today.cli.showFilter") private var showCLIFilter: Bool = false + + var body: some View { + if self.cliEnabled && self.commandLineMode { + FancyButtonv2( + text: "Filter", + action: {self.showCLIFilter.toggle()}, + icon: "line.3.horizontal.decrease", + bgColour: self.state.session.appPage.primaryColour.opacity(0.2), + showLabel: false, + size: .small, + type: .clear + ) + .mask(Circle()) + } + } + } + + struct Close: View { + public var action: () -> Void + + var body: some View { + FancyButtonv2( + text: "Reset", + action: self.action, + icon: "xmark.square.fill", + iconWhenHighlighted: "xmark.square", + showLabel: false, + type: .clear, + font: .title2 + ) + .frame(width: 18) + } + } + + struct SidebarToggle: View { + @EnvironmentObject public var state: Navigation + @AppStorage("widgetlibrary.ui.isSidebarPresented") private var isSidebarPresented: Bool = false + + var body: some View { + FancyButtonv2( + text: "Toggle sidebar", + action: {self.isSidebarPresented.toggle()}, + icon: "sidebar.left", + iconWhenHighlighted: "sidebar.left", + fgColour: self.isSidebarPresented ? .gray : .white, + showLabel: false, + size: .small, + type: .clear, + font: .title + ) + .help("Toggle sidebar") + .frame(width: 25) + } + } + } +} + +extension WidgetLibrary.UI.Buttons.ResetUserChoices { + private func defaultClearAction() -> Void { + self.state.session.job = nil + self.state.session.project = nil + self.state.session.company = nil + } +} + +extension WidgetLibrary.UI.Buttons.CreateJob { + /// Fires when the button is clicked/tapped. + /// - Returns: Void + private func actionOnTap() -> Void { + self.editorVisible = true + self.explorerVisible = false + + // Creates a new job entity so the user can customize it + // @TODO: move to new method CoreDataJobs.create + let newJob = Job(context: self.state.moc) + newJob.id = UUID() + newJob.jid = 1.0 + newJob.colour = Color.randomStorable() + newJob.alive = true + newJob.project = CoreDataProjects(moc: self.state.moc).alive().first(where: {$0.company?.isDefault == true}) + newJob.created = Date() + newJob.lastUpdate = newJob.created + newJob.overview = "Sample job overview" + newJob.title = "Sample job title" + self.state.session.job = newJob + self.state.forms.tp.editor.job = newJob + } +} diff --git a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Navigator.swift b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Navigator.swift index 55e3e587..4ff46eb0 100644 --- a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Navigator.swift +++ b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Navigator.swift @@ -84,7 +84,8 @@ extension WidgetLibrary.UI { } init() { - Mode.allCases.forEach { tab in + // @TODO: temp removing folders so it can be finished or removed later + Mode.allCases.filter({$0 != .folders}).forEach { tab in buttons.append(tab.button) } } @@ -115,7 +116,7 @@ extension WidgetLibrary.UI.Navigator { } var body: some View { - VStack(alignment: .leading, spacing: 1) { + VStack(alignment: .leading, spacing: 0) { VStack(spacing: 0) { self.ListHeader UI.ResourcePath( @@ -130,27 +131,30 @@ extension WidgetLibrary.UI.Navigator { if self.companies.count(where: {$0.alive == true}) > 0 { if self.type == .folders { LazyVGrid(columns: self.columns, alignment: .leading) { - switch self.depth { - case 0: - if self.companies.count > 0 { - ForEach(self.companies, id: \.objectID) { company in - Folder(entity: company) - } - } - case 1: - if self.projects.count > 0 { - ForEach(self.projects, id: \.objectID) { project in - Folder(entity: project) - } - } - case 2: - if self.jobs.count > 0 { - ForEach(self.jobs, id: \.objectID) { job in - Folder(entity: job) - } - } - default: EmptyView() + ForEach(self.companies, id: \.objectID) { company in + Folder(entity: company) } +// switch self.depth { +// case 0: +// if self.companies.count > 0 { +// ForEach(self.companies, id: \.objectID) { company in +// Folder(entity: company) +// } +// } +// case 1: +// if self.projects.count > 0 { +// ForEach(self.projects, id: \.objectID) { project in +// Folder(entity: project) +// } +// } +// case 2: +// if self.jobs.count > 0 { +// ForEach(self.jobs, id: \.objectID) { job in +// Folder(entity: job) +// } +// } +// default: EmptyView() +// } } .frame(maxWidth: 800) .padding() @@ -394,9 +398,9 @@ extension WidgetLibrary.UI.Navigator { if self.isPresented { self.colour.blendMode(.softLight) } - Image(systemName: self.isPresented ? "minus" : self.isHighlighted ? "folder.fill" : "folder") + Image(systemName: self.isPresented ? "star.fill" : self.isHighlighted ? "folder.fill" : "folder") // @TODO: create a ShapeStyle for this - .foregroundStyle(self.isPresented ? .white : self.viewModeIndex == 1 ? self.colour.isBright() ? Theme.base : .white : self.colour) + .foregroundStyle(self.isPresented ? .yellow : self.viewModeIndex == 1 ? self.colour.isBright() ? Theme.base : .white : self.colour) } .frame(width: 30, height: 30) .cornerRadius(5) @@ -444,14 +448,43 @@ extension WidgetLibrary.UI.Navigator { @State private var relatedEntities: [PageConfiguration.EntityType] = [] @State private var isPresented: Bool = false @State private var isHighlighted: Bool = false + private var columns: [GridItem] { + return Array(repeating: .init(.flexible(minimum: 100)), count: 6) + } var body: some View { + VStack { + switch self.depth { + case 0: + Text("Companies") + if self.entity is Company { + Main + } + case 1: + Text("Projects") + if self.entity is Project { + Main + } + case 2: + Text("Jobs") + if self.entity is Job { + Main + } + default: EmptyView() + } + } + .onChange(of: self.depth) { self.actionOnAppear() } + } + + var Main: some View { VStack { Button { self.actionOnTap() self.isPresented.toggle() } label: { - UI.IconBlock(type: .projects, text: self.label, colour: self.colour) +// if !self.isPresented { + UI.Blocks.Icon(type: .projects, text: self.label, colour: self.colour) +// } } .help(self.label) .buttonStyle(.plain) @@ -462,9 +495,11 @@ extension WidgetLibrary.UI.Navigator { } } } + .contextMenu { self.ContextMenu } .onAppear(perform: self.actionOnAppear) - .onChange(of: self.viewModeIndex) { self.actionOnAppear() } - .onChange(of: self.showPublished) { self.actionOnAppear() } +// .onChange(of: self.depth) { self.actionOnAppear() } +// .onChange(of: self.viewModeIndex) { self.actionOnAppear() } +// .onChange(of: self.showPublished) { self.actionOnAppear() } } var ButtonContent: some View { @@ -477,35 +512,55 @@ extension WidgetLibrary.UI.Navigator { } @ViewBuilder var Children: some View { - ForEach(self.children!, id: \.objectID) { child in - if self.state.session.gif == .focus { - switch self.entity { - case is Company: - if let child = child as? Company { - if self.state.planning.companies.contains(child) { - Row(entity: child) + LazyVGrid(columns: self.columns, alignment: .leading) { + ForEach(self.children!, id: \.objectID) { child in + if self.state.session.gif == .focus { + switch self.entity { + case is Company: + if let child = child as? Company { + if self.state.planning.companies.contains(child) { + Folder(entity: child) + } } - } - case is Project: - if let child = child as? Project { - if self.state.planning.projects.contains(child) { - Row(entity: child) + case is Project: + if let child = child as? Project { + if self.state.planning.projects.contains(child) { + Folder(entity: child) + } } - } - case is Job: - if let child = child as? Job { - if self.state.planning.jobs.contains(child) { - Row(entity: child) + case is Job: + if let child = child as? Job { + if self.state.planning.jobs.contains(child) { + Folder(entity: child) + } } + default: EmptyView() } - default: EmptyView() + } else { + Folder(entity: child) } - } else { - Row(entity: child) } } .padding(.leading) } + + @ViewBuilder var ContextMenu: some View { + switch self.entity { + case is Company: + if let entity = self.entity as? Company { + UI.GroupHeaderContextMenu(page: entity.pageDetailType, entity: entity) + } + case is Project: + if let entity = self.entity as? Project { + UI.GroupHeaderContextMenu(page: entity.pageDetailType, entity: entity) + } + case is Job: + if let entity = self.entity as? Job { + UI.GroupHeaderContextMenu(page: entity.pageDetailType, entity: entity) + } + default: EmptyView() + } + } } } } @@ -514,7 +569,6 @@ extension WidgetLibrary.UI.Navigator.List { /// Onload handler. Finds companies /// - Returns: Void private func actionOnAppear() -> Void { - self.companies = CoreDataCompanies(moc: self.state.moc).all( allowKilled: self.showPublished, allowPlanMembersOnly: self.state.session.gif == .focus, @@ -716,6 +770,7 @@ extension WidgetLibrary.UI.Navigator.List.Folder { /// Onload handler. Sets view state /// - Returns: Void private func actionOnAppear() -> Void { + print("DERPO depth=\(self.depth)") switch self.entity { case is Company: let company = self.entity as! Company @@ -737,7 +792,7 @@ extension WidgetLibrary.UI.Navigator.List.Folder { self.created = company.createdDate self.colour = company.alive ? company.backgroundColor : .gray.opacity(0.7) self.relatedEntities = [.people] - self.isPresented = company == self.state.session.company + self.isPresented = self.depth == 0 && company == self.state.session.company case is Project: let project = self.entity as! Project let jobs = (project.jobs?.allObjects as? [Job] ?? []) @@ -751,7 +806,7 @@ extension WidgetLibrary.UI.Navigator.List.Folder { self.lastModified = project.lastUpdate self.created = project.created self.colour = project.alive ? project.backgroundColor : .gray.opacity(0.7) - self.isPresented = project == self.state.session.project + self.isPresented = self.depth == 1 && project == self.state.session.project case is Job: let job = self.entity as! Job self.label = job.title ?? job.jid.string @@ -760,7 +815,7 @@ extension WidgetLibrary.UI.Navigator.List.Folder { self.created = job.created self.colour = job.alive ? job.backgroundColor : .gray.opacity(0.7) self.relatedEntities = [.tasks, .records, .notes, .definitions] - self.isPresented = job == self.state.session.job + self.isPresented = self.depth == 2 && job == self.state.session.job default: self.label = "Error: Invalid company name" self.children = [] diff --git a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.UnifiedSidebar.swift b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.UnifiedSidebar.swift index f84bd30b..3dcc6960 100644 --- a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.UnifiedSidebar.swift +++ b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.UnifiedSidebar.swift @@ -11,8 +11,6 @@ import KWCore extension WidgetLibrary.UI { struct UnifiedSidebar { - typealias UI = WidgetLibrary.UI - struct Widget: View { @EnvironmentObject public var state: Navigation @State private var companies: [Company] = [] @@ -59,40 +57,33 @@ extension WidgetLibrary.UI { var body: some View { VStack(alignment: .leading, spacing: 0) { ZStack(alignment: .trailing) { - RowButton(text: self.entity.name ?? "_COMPANY_NAME", alive: self.entity.alive, callback: { - self.state.session.company = self.entity - }, isPresented: $isPresented) + RowButton( + text: self.entity.name ?? "_COMPANY_NAME", + alive: self.entity.alive, + active: self.entity == self.state.session.company, + callback: { + self.state.session.company = self.entity + }, + isPresented: $isPresented + ) .useDefaultHover({ inside in self.highlighted = inside}) .contextMenu { UI.GroupHeaderContextMenu(page: self.entity.pageDetailType, entity: self.entity) } - - if self.entity == self.state.session.company { - FancyStarv2() - .help("Active company") - } } if self.isPresented { HStack(alignment: .center, spacing: 0) { Text(self.entity.abbreviation ?? "XXX") - .foregroundStyle(self.entity.backgroundColor.isBright() ? Theme.base : .white) .opacity(0.7) .padding(.leading) Spacer() - RowAddNavLink( - title: "+ Person", - target: AnyView(PeopleDetail()) - ) - .buttonStyle(.plain) - RowAddNavLink( - title: "+ Project", - target: AnyView(ProjectCreate()) - ) - .buttonStyle(.plain) + UI.Buttons.CreateProject(location: .sidebar, isAlteredForReadability: self.entity.backgroundColor.isBright()) + .padding(.trailing, 8) } .padding([.top, .bottom], 8) .background(Theme.base.opacity(0.6).blendMode(.softLight)) + .foregroundStyle(self.entity.backgroundColor.isBright() ? Theme.base : .white) VStack(alignment: .leading, spacing: 0) { ZStack(alignment: .topLeading) { @@ -145,36 +136,34 @@ extension WidgetLibrary.UI { var body: some View { VStack(alignment: .leading, spacing: 0) { ZStack(alignment: .trailing) { - RowButton(text: self.entity.name ?? "_PROJECT_NAME", alive: self.entity.alive, callback: { - self.state.session.company = self.entity.company - self.state.session.project = self.entity - }, isPresented: $isPresented) + RowButton( + text: self.entity.name ?? "_PROJECT_NAME", + alive: self.entity.alive, + active: self.entity == self.state.session.project, + callback: { + self.state.session.company = self.entity.company + self.state.session.project = self.entity + }, + isPresented: $isPresented + ) .useDefaultHover({ inside in self.highlighted = inside}) .contextMenu { UI.GroupHeaderContextMenu(page: self.entity.pageDetailType, entity: self.entity) } - - if self.entity == self.state.session.project { - FancyStarv2() - .help("Active project") - } } if self.isPresented { HStack(alignment: .center, spacing: 0) { Text("\(self.entity.company?.abbreviation ?? "XXX").\(self.entity.abbreviation ?? "YYY")") - .foregroundStyle(self.entity.backgroundColor.isBright() ? Theme.base : .white) .opacity(0.7) .padding(.leading) Spacer() - RowAddNavLink( - title: "+ Job", - target: AnyView(JobCreate()) - ) - .buttonStyle(.plain) + UI.Buttons.CreateJob(isAlteredForReadability: self.entity.backgroundColor.isBright()) + .padding(.trailing, 8) } .padding([.top, .bottom], 8) .background(Theme.base.opacity(0.6).blendMode(.softLight)) + .foregroundStyle(self.entity.backgroundColor.isBright() ? Theme.base : .white) VStack(alignment: .leading, spacing: 0) { ZStack(alignment: .topLeading) { @@ -224,31 +213,32 @@ extension WidgetLibrary.UI { var body: some View { VStack(alignment: .leading, spacing: 0) { ZStack(alignment: .trailing) { - RowButton(text: self.entity.title ?? self.entity.jid.string, alive: self.entity.alive, callback: { - self.state.session.setJob(self.entity) - - if self.state.parent == .planning { - self.state.planning.jobs.insert(entity) - self.state.planning.projects.insert(entity.project!) - - // projects are allowed to be unowned - if let company = entity.project!.company { - self.state.planning.companies.insert(company) + RowButton( + text: self.entity.title ?? self.entity.jid.string, + alive: self.entity.alive, + active: self.entity == self.state.session.job, + callback: { + self.state.session.setJob(self.entity) + + if self.state.parent == .planning { + self.state.planning.jobs.insert(entity) + self.state.planning.projects.insert(entity.project!) + + // projects are allowed to be unowned + if let company = entity.project!.company { + self.state.planning.companies.insert(company) + } + } else { + self.state.session.company = self.entity.project?.company + self.state.session.project = self.entity.project } - } else { - self.state.session.company = self.entity.project?.company - self.state.session.project = self.entity.project - } - }, isPresented: $isPresented) + }, + isPresented: $isPresented + ) .useDefaultHover({ inside in self.highlighted = inside}) .contextMenu { UI.GroupHeaderContextMenu(page: self.entity.pageDetailType, entity: self.entity) } - - if self.entity == self.state.session.job { - FancyStarv2() - .help("Active job") - } } if self.isPresented { @@ -312,10 +302,8 @@ extension WidgetLibrary.UI { EntityRowButton(text: "\((self.tasks?.count ?? self.childrenFromJob.count)) Tasks", isPresented: $isPresented) .useDefaultHover({ inside in self.highlighted = inside}) .disabled((self.tasks?.count ?? self.childrenFromJob.count) == 0) - RowAddNavLink( - target: AnyView(TaskDetail()) - ) - .buttonStyle(.plain) + UI.Buttons.CreateTask(isAlteredForReadability: self.job.backgroundColor.isBright()) + .padding(.trailing, 8) } if self.isPresented { @@ -369,10 +357,8 @@ extension WidgetLibrary.UI { EntityRowButton(text: "\((self.notes?.count ?? self.childrenFromJob.count)) Notes", isPresented: $isPresented) .useDefaultHover({ inside in self.highlighted = inside}) .disabled((self.notes?.count ?? self.childrenFromJob.count) == 0) - RowAddNavLink( - target: AnyView(NoteCreate()) - ) - .buttonStyle(.plain) + UI.Buttons.CreateNote(isAlteredForReadability: self.job.backgroundColor.isBright()) + .padding(.trailing, 8) } if self.isPresented { @@ -426,10 +412,8 @@ extension WidgetLibrary.UI { EntityRowButton(text: "\((self.definitions?.count ?? self.childrenFromJob.count)) Definitions", isPresented: $isPresented) .useDefaultHover({ inside in self.highlighted = inside}) .disabled((self.definitions?.count ?? self.childrenFromJob.count) == 0) - RowAddNavLink( - target: AnyView(DefinitionDetail()) - ) - .buttonStyle(.plain) + UI.Buttons.CreateDefinition(isAlteredForReadability: self.job.backgroundColor.isBright()) + .padding(.trailing, 8) } if self.isPresented { @@ -483,6 +467,8 @@ extension WidgetLibrary.UI { EntityRowButton(text: "\((self.records?.count ?? self.childrenFromJob.count)) Records", isPresented: $isPresented) .useDefaultHover({ inside in self.highlighted = inside}) .disabled((self.records?.count ?? self.childrenFromJob.count) == 0) + UI.Buttons.CreateRecordToday(isAlteredForReadability: self.job.backgroundColor.isBright()) + .padding(.trailing, 8) } if self.isPresented { @@ -533,10 +519,8 @@ extension WidgetLibrary.UI { ZStack(alignment: .trailing) { EntityRowButton(text: "People", isPresented: $isPresented) .useDefaultHover({ inside in self.highlighted = inside}) - RowAddNavLink( - target: AnyView(PeopleDetail()) - ) - .buttonStyle(.plain) + UI.Buttons.CreatePerson(isAlteredForReadability: self.entity.backgroundColor.isBright()) + .padding(.trailing, 8) } if self.isPresented { @@ -563,8 +547,10 @@ extension WidgetLibrary.UI { } struct RowButton: View { + @EnvironmentObject private var state: Navigation public let text: String public let alive: Bool + public var active: Bool public var callback: (() -> Void)? @Binding public var isPresented: Bool @@ -578,7 +564,8 @@ extension WidgetLibrary.UI { HStack(alignment: .center, spacing: 8) { ZStack(alignment: .center) { Theme.base.opacity(0.6).blendMode(.softLight) - Image(systemName: self.isPresented ? "minus" : "plus") + Image(systemName: self.active ? "star.fill" : self.isPresented ? "minus" : "plus") + .foregroundStyle(self.active ? .yellow : .white) } .frame(width: 30, height: 30) .cornerRadius(5) diff --git a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.swift b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.swift index 5be470f9..08a53f13 100644 --- a/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.swift +++ b/KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.swift @@ -20,324 +20,6 @@ extension WidgetLibrary { "excellent" ] - public struct Buttons { - public enum UIButtonType { - case resetUserChoices, createNote, createTask, createRecord, createPerson, createCompany, createProject, - createJob, createTerm, createDefinition, historyPrevious, settings, CLIMode, CLIFilter - } - - struct ResetUserChoices: View { - @EnvironmentObject public var state: Navigation - public var onActionClear: (() -> Void)? - - var body: some View { - if self.state.session.job != nil || self.state.session.project != nil || self.state.session.company != nil { - FancyButtonv2( - text: "Reset interface to default state", - action: self.onActionClear != nil ? self.onActionClear : self.defaultClearAction, - icon: "arrow.clockwise.square", - iconWhenHighlighted: "arrow.clockwise.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Reset interface to default state") - .frame(width: 25) - } else { - EmptyView() - } - } - } - - struct CreateNote: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - - var body: some View { - FancyButtonv2( - text: "Create", - action: self.onAction, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - redirect: AnyView(NoteCreate()), - pageType: .notes, - sidebar: AnyView(NoteCreateSidebar()), - font: .title - ) - .help("Create a new note") - .frame(width: 25) - } - } - - struct CreatePerson: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - - var body: some View { - FancyButtonv2( - text: "Create", - action: { self.onAction?() ; self.state.to(.peopleDetail) }, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new contact") - .frame(width: 25) - } - } - - struct CreateCompany: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - - var body: some View { - FancyButtonv2( - text: "Create", - action: { self.onAction?() ; self.state.to(.companyDetail) }, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new company") - .frame(width: 25) - } - } - - struct CreateProject: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - - var body: some View { - FancyButtonv2( - text: "Create", - action: { self.onAction?() ; self.state.to(.projectDetail) }, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new project") - .frame(width: 25) - } - } - - struct CreateJob: View { - @EnvironmentObject public var state: Navigation - @AppStorage("jobdashboard.explorerVisible") private var explorerVisible: Bool = true - @AppStorage("jobdashboard.editorVisible") private var editorVisible: Bool = true - - var body: some View { - FancyButtonv2( - text: "Create", - action: self.actionOnTap, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new job") - .frame(width: 25) - } - } - - struct CreateTerm: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - - var body: some View { - FancyButtonv2( - text: "Create", - action: { self.onAction?() ; self.state.to(.terms) }, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new taxonomy term") - .frame(width: 25) - } - } - - struct CreateDefinition: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - - var body: some View { - FancyButtonv2( - text: "Create", - action: { self.onAction?() ; self.state.to(.definitionDetail) }, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new term definition") - .frame(width: 25) - } - } - - struct CreateRecord: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - @State private var isHighlighted: Bool = false - @State private var selectedPage: Page = .dashboard - - var body: some View { - FancyButtonv2( - text: self.state.session.job != nil ? "Log to job \(self.state.session.job!.title ?? self.state.session.job!.jid.string)" : "Log", - action: self.onAction, - icon: "plus.square", - iconWhenHighlighted: "plus.square.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help("Create a new record") - .frame(width: 25) - .disabled(self.state.session.job == nil) - .opacity(self.state.session.job == nil ? 0.5 : 1) - } - } - - struct HistoryPrevious: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - @State private var isHighlighted: Bool = false - @State private var selectedPage: Page = .dashboard - private var isEmpty: Bool { self.state.history.recent.count == 0 } - - var body: some View { - Button { - if let previous = self.state.history.previous() { - self.state.to(previous.page, addToHistory: false) - self.selectedPage = previous.page - self.onAction?() - } - } label: { - HStack(alignment: .center) { - Image(systemName: "chevron.left") - Text("Back") - } - .padding(8) - .background(self.state.session.appPage.primaryColour.opacity(self.isHighlighted ? 1 : 0.6)) - .foregroundStyle(self.isHighlighted ? .white : Theme.lightWhite) - .clipShape(.rect(cornerRadius: 5)) - .padding([.top, .bottom], 10) - } - .disabled(self.isEmpty) - .keyboardShortcut(KeyEquivalent.leftArrow, modifiers: [.command]) - .buttonStyle(.plain) - .useDefaultHover({ hover in !self.isEmpty ? self.isHighlighted = hover : nil}) - } - } - - struct Settings: View { - @EnvironmentObject public var state: Navigation - public var onAction: (() -> Void)? = {} - @State private var isHighlighted: Bool = false - @State private var selectedPage: Page = .dashboard - - var body: some View { - Button { - self.onAction?() - } label: { - Image(systemName: "gear") - .font(.title) - .foregroundStyle(self.isHighlighted ? .white : Theme.lightWhite) - .padding([.leading, .trailing]) - .padding([.top, .bottom], 10) - } - .keyboardShortcut(KeyEquivalent.leftArrow, modifiers: [.command]) - .buttonStyle(.plain) - .useDefaultHover({ hover in self.isHighlighted = hover}) - } - } - - struct CLIMode: View { - @EnvironmentObject public var state: Navigation - @AppStorage("general.experimental.cli") private var cliEnabled: Bool = false - @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false - public var onAction: (() -> Void)? = {} - @State private var isHighlighted: Bool = false - @State private var selectedPage: Page = .dashboard - - var body: some View { - if self.cliEnabled { - FancyButtonv2( - text: "Command line mode", - action: {self.commandLineMode.toggle() ; self.onAction?()}, - icon: self.commandLineMode ? "apple.terminal.fill" : "apple.terminal", - iconWhenHighlighted: self.commandLineMode ? "apple.terminal" : "apple.terminal.fill", - showLabel: false, - size: .small, - type: .clear, - font: .title - ) - .help(self.commandLineMode ? "Exit CLI mode" : "Enter CLI mode") - .frame(width: 25) - } - } - } - - struct CLIFilter: View { - @EnvironmentObject public var state: Navigation - @AppStorage("general.experimental.cli") private var cliEnabled: Bool = false - @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false - @AppStorage("today.cli.showFilter") private var showCLIFilter: Bool = false - - var body: some View { - if self.cliEnabled && self.commandLineMode { - FancyButtonv2( - text: "Filter", - action: {self.showCLIFilter.toggle()}, - icon: "line.3.horizontal.decrease", - bgColour: self.state.session.appPage.primaryColour.opacity(0.2), - showLabel: false, - size: .small, - type: .clear - ) - .mask(Circle()) - } - } - } - - struct Close: View { -// @EnvironmentObject public var state: Navigation - public var action: () -> Void - - var body: some View { - FancyButtonv2( - text: "Reset", - action: self.action, - icon: "xmark", - showLabel: false, - type: .clear, - font: .title2 - ) - .frame(width: 18) - } - } - } - struct ActiveIndicator: View { public var colour: Color = .white public var action: (() -> Void)? = nil @@ -471,14 +153,14 @@ extension WidgetLibrary { case .settings: Buttons.Settings() case .createJob: Buttons.CreateJob() case .createNote: Buttons.CreateNote() -// case .createTask: Buttons.CreateTask() + case .createTask: Buttons.CreateTask() case .createTerm: Buttons.CreateTerm() case .createPerson: Buttons.CreatePerson() case .createRecord: Buttons.CreateRecord() case .createCompany: Buttons.CreateCompany() case .createProject: Buttons.CreateProject() case .createDefinition: Buttons.CreateDefinition() - default: EmptyView() + case .sidebarToggle: Buttons.SidebarToggle() } } } @@ -520,31 +202,29 @@ extension WidgetLibrary { } label: { HStack(alignment: .center) { ZStack { - RoundedRectangle(cornerRadius: 5) + RoundedRectangle(cornerRadius: 20) .strokeBorder(self.isToday ? .yellow.opacity(0.6) : .gray, lineWidth: 1) - .fill(.white.opacity(self.isHighlighted ? 0.2 : 0.1)) + .fill(self.isToday ? .yellow.opacity(self.isHighlighted ? 0.8 : 0.7) : .gray.opacity(self.isHighlighted ? 0.8 : 0.7)) if !self.showDateOverlay { HStack { Image(systemName: "calendar") - .foregroundStyle(.gray) Text(self.date) } + .foregroundStyle(self.isToday ? Theme.base : Theme.lightWhite) } } .frame(width: 200) } } - .foregroundStyle(self.isHighlighted ? .white : self.isToday ? .yellow.opacity(0.6) : .gray) .buttonStyle(.plain) .useDefaultHover({ hover in self.isHighlighted = hover}) .overlay { if self.showDateOverlay { - HStack { - DatePicker("", selection: $sDate) - Image(systemName: "xmark") - } + DatePicker("", selection: $sDate) + .foregroundStyle(self.isToday ? Theme.base : Theme.lightBase) } } + .onChange(of: self.sDate) { self.showDateOverlay.toggle() } FancyButtonv2( text: "Next day", @@ -732,46 +412,6 @@ extension WidgetLibrary { } } - struct IconBlock: View, Identifiable { - @EnvironmentObject private var state: Navigation - @AppStorage("widget.navigator.viewModeIndex") private var viewModeIndex: Int = 0 - public var id: UUID = UUID() - public var type: Conf - public var text: String - public var colour: Color - @State private var isHighlighted: Bool = false - - var body: some View { - VStack(alignment: .center, spacing: 0) { - ZStack(alignment: .center) { - (self.viewModeIndex == 0 ? Color.gray.opacity(self.isHighlighted ? 1 : 0.7) : self.colour.opacity(self.isHighlighted ? 1 : 0.7)) - VStack(alignment: .center, spacing: 0) { - (self.isHighlighted ? self.type.selectedIcon : self.type.icon) - .symbolRenderingMode(.hierarchical) - .font(.largeTitle) -// .foregroundStyle(self.viewModeIndex == 0 ? self.colour : self.colour.isBright() ? Theme.base : .white) - .foregroundStyle(self.viewModeIndex == 0 ? self.colour : .white) - } - Spacer() - } - .frame(height: 65) - - ZStack(alignment: .center) { - (self.isHighlighted ? Color.yellow : Theme.textBackground) - VStack(alignment: .center, spacing: 0) { - Text(self.text) - .font(.system(.title3, design: .monospaced)) - .foregroundStyle(self.isHighlighted ? Theme.base : .gray) - } - } - .frame(height: 25) - } - .frame(width: 65) - .clipShape(.rect(cornerRadius: 5)) - .useDefaultHover({ hover in self.isHighlighted = hover }) - } - } - struct ExploreLinks: View { @EnvironmentObject private var state: Navigation private var activities: [Activity] { @@ -1331,7 +971,7 @@ extension WidgetLibrary { @EnvironmentObject public var state: Navigation @AppStorage("searchbar.showTypes") private var showingTypes: Bool = false @AppStorage("searchbar.shared") private var searchText: String = "" - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false @AppStorage("isDatePickerPresented") public var isDatePickerPresented: Bool = false public var disabled: Bool = false public var placeholder: String? = "Search..." @@ -1377,11 +1017,12 @@ extension WidgetLibrary { } } + // MARK: BoundSearchBar struct BoundSearchBar: View { @EnvironmentObject public var state: Navigation @AppStorage("searchbar.showTypes") private var showingTypes: Bool = false @AppStorage("searchbar.shared") private var searchText: String = "" - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false @AppStorage("isDatePickerPresented") public var isDatePickerPresented: Bool = false @Binding public var text: String public var disabled: Bool = false @@ -1408,15 +1049,7 @@ extension WidgetLibrary { .foregroundStyle(self.state.session.job?.backgroundColor ?? .yellow) Spacer() if self.text.count > 0 { - FancyButtonv2( - text: "Reset", - action: self.actionOnReset, - icon: "xmark", - showLabel: false, - type: .clear, - font: .title2 - ) - .frame(width: 18) + UI.Buttons.Close(action: self.actionOnReset) } else { FancyButtonv2( text: "Entities", @@ -1438,6 +1071,7 @@ extension WidgetLibrary { } } + // MARK: ResourcePath struct ResourcePath: View { @EnvironmentObject public var state: Navigation public var company: Company? @@ -1451,15 +1085,26 @@ extension WidgetLibrary { HStack(alignment: .center, spacing: 8) { HStack(spacing: 0) { if self.showRoot { - Text("...") - .padding(7) - .background(Theme.base) - .foregroundStyle(.white.opacity(0.55)) + Button { + self.state.session.company = nil + self.state.session.project = nil + self.state.session.job = nil + } label: { + Image(systemName: "house.fill") + .padding([.top, .bottom], 7) + .padding([.leading, .trailing], 4) + .background(Theme.lightBase) + .foregroundStyle(self.company == nil ? Theme.lightWhite : self.state.theme.tint) + } + .buttonStyle(.plain) + .disabled(self.company == nil) + .useDefaultHover({ _ in}) + Image(systemName: "chevron.right") .padding([.top, .bottom], 8) .padding([.leading, .trailing], 4) - .background(Theme.base.opacity(0.9)) - .foregroundStyle(.white.opacity(0.55)) + .background(Theme.lightBase) + .foregroundStyle(Theme.lightWhite) } if let abbreviation = self.company?.abbreviation { @@ -1486,6 +1131,7 @@ extension WidgetLibrary { .foregroundStyle((self.project?.backgroundColor ?? Theme.base).isBright() ? Theme.base.opacity(0.55) : .white.opacity(0.55)) } } + .background(self.state.session.appPage.primaryColour) Text("\(self.job?.title ?? self.job?.jid.string ?? "")") .foregroundStyle((self.job?.backgroundColor ?? Theme.base).isBright() ? Theme.base : .white) @@ -1528,7 +1174,6 @@ extension WidgetLibrary { .help(self.title ?? self.eType?.label ?? "") .buttonStyle(.plain) .useDefaultHover({ hover in self.isHighlighted = hover }) - .shadow(color: self.isOn && self.title == nil ? Theme.base : .clear, radius: 1, x: 1, y: 1) } init(_ title: String? = nil, isOn: Binding, eType: PageConfiguration.EntityType? = .BruceWillis, icon: String? = nil, selectedIcon: String? = nil) { @@ -1955,38 +1600,6 @@ extension WidgetLibrary.UI.SimpleDateSelector { } } -extension WidgetLibrary.UI.Buttons.ResetUserChoices { - private func defaultClearAction() -> Void { - self.state.session.job = nil - self.state.session.project = nil - self.state.session.company = nil - } -} - -extension WidgetLibrary.UI.Buttons.CreateJob { - /// Fires when the button is clicked/tapped. - /// - Returns: Void - private func actionOnTap() -> Void { - self.editorVisible = true - self.explorerVisible = false - - // Creates a new job entity so the user can customize it - // @TODO: move to new method CoreDataJobs.create - let newJob = Job(context: self.state.moc) - newJob.id = UUID() - newJob.jid = 1.0 - newJob.colour = Color.randomStorable() - newJob.alive = true - newJob.project = CoreDataProjects(moc: self.state.moc).alive().first(where: {$0.company?.isDefault == true}) - newJob.created = Date() - newJob.lastUpdate = newJob.created - newJob.overview = "Sample job overview" - newJob.title = "Sample job title" - self.state.session.job = newJob - self.state.forms.tp.editor.job = newJob - } -} - extension WidgetLibrary.UI.Meetings { private func actionOnAppear() -> Void { if let chosenCalendar = ce.selectedCalendar() { diff --git a/KlockWork.xcodeproj/project.pbxproj b/KlockWork.xcodeproj/project.pbxproj index 2a9df2a8..fe73d6ab 100644 --- a/KlockWork.xcodeproj/project.pbxproj +++ b/KlockWork.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 530B1D782CAFA4DD0033E81F /* PeopleDashboadSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530B1D772CAFA4D50033E81F /* PeopleDashboadSidebar.swift */; }; 530B1D7A2CAFA4E40033E81F /* PeopleDashboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530B1D792CAFA4E00033E81F /* PeopleDashboard.swift */; }; 530B1D7C2CAFAFBD0033E81F /* PeopleDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530B1D7B2CAFAFBB0033E81F /* PeopleDetail.swift */; }; + 530D74782CCEFC65000B81FE /* AccessibilitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530D74772CCEFC5F000B81FE /* AccessibilitySettings.swift */; }; 53152CC929690E7300A14E43 /* LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53152CC829690E7300A14E43 /* LosslessStringConvertible.swift */; }; 53152CD4296A296A00A14E43 /* FancyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53152CD3296A296A00A14E43 /* FancyButton.swift */; }; 53152CD6296A2DA300A14E43 /* FancyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53152CD5296A2DA300A14E43 /* FancyPicker.swift */; }; @@ -296,6 +297,7 @@ 530B1D772CAFA4D50033E81F /* PeopleDashboadSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleDashboadSidebar.swift; sourceTree = ""; }; 530B1D792CAFA4E00033E81F /* PeopleDashboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleDashboard.swift; sourceTree = ""; }; 530B1D7B2CAFAFBB0033E81F /* PeopleDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleDetail.swift; sourceTree = ""; }; + 530D74772CCEFC5F000B81FE /* AccessibilitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilitySettings.swift; sourceTree = ""; }; 53152CC829690E7300A14E43 /* LosslessStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LosslessStringConvertible.swift; sourceTree = ""; }; 53152CD3296A296A00A14E43 /* FancyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyButton.swift; sourceTree = ""; }; 53152CD5296A2DA300A14E43 /* FancyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyPicker.swift; sourceTree = ""; }; @@ -544,7 +546,9 @@ Sources/Query/CoreDataTaxonomyTermDefinitions.swift, Sources/Query/CoreDataTaxonomyTerms.swift, Sources/UI/TypedListRowBackground.swift, + Sources/UI/WidgetLibrary/WidgetLibrary.Blocks.swift, Sources/UI/WidgetLibrary/WidgetLibrary.swift, + Sources/UI/WidgetLibrary/WidgetLibrary.UI.Buttons.swift, Sources/UI/WidgetLibrary/WidgetLibrary.UI.Individual.swift, Sources/UI/WidgetLibrary/WidgetLibrary.UI.Navigator.swift, Sources/UI/WidgetLibrary/WidgetLibrary.UI.Sidebar.swift, @@ -584,7 +588,9 @@ Sources/Query/CoreDataTaxonomyTermDefinitions.swift, Sources/Query/CoreDataTaxonomyTerms.swift, Sources/UI/TypedListRowBackground.swift, + Sources/UI/WidgetLibrary/WidgetLibrary.Blocks.swift, Sources/UI/WidgetLibrary/WidgetLibrary.swift, + Sources/UI/WidgetLibrary/WidgetLibrary.UI.Buttons.swift, Sources/UI/WidgetLibrary/WidgetLibrary.UI.Individual.swift, Sources/UI/WidgetLibrary/WidgetLibrary.UI.Navigator.swift, Sources/UI/WidgetLibrary/WidgetLibrary.UI.Sidebar.swift, @@ -1177,6 +1183,7 @@ 53C036ED29F8A58600539C3C /* Tabs */ = { isa = PBXGroup; children = ( + 530D74772CCEFC5F000B81FE /* AccessibilitySettings.swift */, 536043CB2CBE0E670030D72D /* NotificationSettings.swift */, 53967D8329EC6D14006616A0 /* TodaySettings.swift */, 53EFCE7829637F35004E45EC /* GeneralSettings.swift */, @@ -1682,6 +1689,7 @@ 5363B67A2A6BB75F00C2FBB8 /* CompanyBlock.swift in Sources */, 531B8DF22A847BE8005D911B /* NoteGroup.swift in Sources */, 538A535E2C704E5800711639 /* TermsDashboard.swift in Sources */, + 530D74782CCEFC65000B81FE /* AccessibilitySettings.swift in Sources */, 535C1AE129F48EF700CD95FD /* Dashboard.swift in Sources */, 53C000962B3A7BD500D5EC04 /* String.swift in Sources */, 537628A22966525500DE8ECF /* ToolbarButtons.swift in Sources */, diff --git a/KlockWork/DLPrototype.swift b/KlockWork/DLPrototype.swift index e371b17f..e0db4766 100644 --- a/KlockWork/DLPrototype.swift +++ b/KlockWork/DLPrototype.swift @@ -11,6 +11,9 @@ import KWCore import CoreSpotlight import UserNotifications +typealias UI = WidgetLibrary.UI +typealias EType = PageConfiguration.EntityType + @main struct DLPrototype: App { private let persistenceController = PersistenceController.shared diff --git a/KlockWork/MainMenu.swift b/KlockWork/MainMenu.swift index c4ff828d..49640302 100644 --- a/KlockWork/MainMenu.swift +++ b/KlockWork/MainMenu.swift @@ -10,6 +10,7 @@ import SwiftUI import KWCore struct MainMenu: Commands { + @AppStorage("widgetlibrary.ui.isSidebarPresented") private var isSidebarPresented: Bool = false public var state: Navigation public var body: some Commands { @@ -40,9 +41,16 @@ struct MainMenu: Commands { Button("Next day") {self.state.session.date += 86400} .keyboardShortcut(.rightArrow, modifiers: [.control, .shift]) Divider() - Button("Reset to today") {self.state.session.date = Date()} + Button("Reset to today") {self.state.session.date = Date.now} .keyboardShortcut("d", modifiers: [.control, .shift]) } } + + CommandGroup(after: .sidebar) { + Divider() + Button("Show/hide Sidebar") { self.isSidebarPresented.toggle() } + .keyboardShortcut("b", modifiers: [.control, .shift]) + Divider() + } } } diff --git a/KlockWork/Utils/Navigation.swift b/KlockWork/Utils/Navigation.swift index e7b9501a..e74574f7 100644 --- a/KlockWork/Utils/Navigation.swift +++ b/KlockWork/Utils/Navigation.swift @@ -94,7 +94,10 @@ public class Navigation: Identifiable, ObservableObject { @Published public var parent: Page? = .dashboard @Published public var sidebar: AnyView? = AnyView(DashboardSidebar()) @Published public var inspector: AnyView? = nil - @Published public var navButtons: [WidgetLibrary.UI.Buttons.UIButtonType] = [] + @Published public var navButtons: [WidgetLibrary.UI.Buttons.UIButtonType] = [ + .sidebarToggle, + .resetUserChoices + ] @Published public var title: String? = "" @Published public var pageId: UUID? = UUID() @Published public var session: Session = Session() @@ -577,21 +580,21 @@ extension Navigation { public let all: [HistoryPage] = [ HistoryPage(page: .dashboard, view: AnyView(Dashboard()), sidebar: AnyView(DashboardSidebar()), title: "Dashboard"), HistoryPage(page: .planning, view: AnyView(Planning()), sidebar: AnyView(DefaultPlanningSidebar()), title: "Planning"), - HistoryPage(page: .today, view: AnyView(Today()), sidebar: AnyView(TodaySidebar()), title: "Today", navButtons: [.resetUserChoices, .CLIFilter, .CLIMode]), + HistoryPage(page: .today, view: AnyView(Today()), sidebar: AnyView(TodaySidebar()), title: "Today", navButtons: [.sidebarToggle, .resetUserChoices, .CLIFilter, .CLIMode]), HistoryPage(page: .recordDetail, view: AnyView(RecordDetail()), sidebar: AnyView(TodaySidebar()), title: "Record"), - HistoryPage(page: .companies, view: AnyView(CompanyDashboard()), sidebar: AnyView(DefaultCompanySidebar()), title: "Companies & Projects", navButtons: [.resetUserChoices, .createCompany]), + HistoryPage(page: .companies, view: AnyView(CompanyDashboard()), sidebar: AnyView(DefaultCompanySidebar()), title: "Companies & Projects", navButtons: [.sidebarToggle, .resetUserChoices, .createCompany]), HistoryPage(page: .companyDetail, view: AnyView(CompanyView()), sidebar: AnyView(DefaultCompanySidebar()), title: "Company"), - HistoryPage(page: .jobs, view: AnyView(JobDashboardRedux()), sidebar: AnyView(JobDashboardSidebar()), title: "Jobs", navButtons: [.resetUserChoices, .createJob]), - HistoryPage(page: .notes, view: AnyView(NoteDashboard()), sidebar: AnyView(NoteDashboardSidebar()), title: "Notes", navButtons: [.resetUserChoices, .createNote]), + HistoryPage(page: .jobs, view: AnyView(JobDashboardRedux()), sidebar: AnyView(JobDashboardSidebar()), title: "Jobs", navButtons: [.sidebarToggle, .resetUserChoices, .createJob]), + HistoryPage(page: .notes, view: AnyView(NoteDashboard()), sidebar: AnyView(NoteDashboardSidebar()), title: "Notes", navButtons: [.sidebarToggle, .resetUserChoices, .createNote]), HistoryPage(page: .noteDetail, view: AnyView(NoteCreate()), sidebar: AnyView(NoteCreateSidebar()), title: "Note detail"), - HistoryPage(page: .tasks, view: AnyView(TaskDashboard()), sidebar: AnyView(TaskDashboardSidebar()), title: "Tasks", navButtons: [.resetUserChoices, .createTask]), - HistoryPage(page: .terms, view: AnyView(TermsDashboard()), sidebar: AnyView(TermsDashboardSidebar()), title: "Terms", navButtons: [.resetUserChoices, .createDefinition]), + HistoryPage(page: .tasks, view: AnyView(TaskDashboard()), sidebar: AnyView(TaskDashboardSidebar()), title: "Tasks", navButtons: [.sidebarToggle, .resetUserChoices, .createTask]), + HistoryPage(page: .terms, view: AnyView(TermsDashboard()), sidebar: AnyView(TermsDashboardSidebar()), title: "Terms", navButtons: [.sidebarToggle, .resetUserChoices, .createDefinition]), HistoryPage(page: .definitionDetail, view: AnyView(DefinitionDetail()), sidebar: AnyView(TermsDashboardSidebar()), title: "Definition detail"), HistoryPage(page: .taskDetail, view: AnyView(TaskDetail()), sidebar: AnyView(TermsDashboardSidebar()), title: "Task detail"), - HistoryPage(page: .people, view: AnyView(PeopleDashboard()), sidebar: AnyView(PeopleDashboardSidebar()), title: "People", navButtons: [.resetUserChoices, .createPerson]), + HistoryPage(page: .people, view: AnyView(PeopleDashboard()), sidebar: AnyView(PeopleDashboardSidebar()), title: "People", navButtons: [.sidebarToggle, .resetUserChoices, .createPerson]), HistoryPage(page: .peopleDetail, view: AnyView(PeopleDetail()), sidebar: AnyView(PeopleDashboardSidebar()), title: "Person"), HistoryPage(page: .projectDetail, view: AnyView(ProjectView()), sidebar: AnyView(DefaultCompanySidebar()), title: "Project"), - HistoryPage(page: .projects, view: AnyView(ProjectsDashboard()), sidebar: AnyView(DefaultCompanySidebar()), title: "Projects", navButtons: [.resetUserChoices, .createProject]), + HistoryPage(page: .projects, view: AnyView(ProjectsDashboard()), sidebar: AnyView(DefaultCompanySidebar()), title: "Projects", navButtons: [.sidebarToggle, .resetUserChoices, .createProject]), HistoryPage(page: .explore, view: AnyView(Explore()), sidebar: AnyView(ExploreSidebar()), title: "Explore"), HistoryPage(page: .activityFlashcards, view: AnyView(UI.FlashcardActivity()), sidebar: AnyView(ExploreSidebar()), title: "Flashcards"), HistoryPage(page: .activityCalendar, view: AnyView(UI.ActivityCalendar()), sidebar: AnyView(ExploreSidebar()), title: "Activity Calendar"), @@ -605,6 +608,7 @@ extension Navigation { var sidebar: AnyView var title: String var navButtons: [WidgetLibrary.UI.Buttons.UIButtonType] = [ + .sidebarToggle, .resetUserChoices ] } diff --git a/KlockWork/Views/Dashboard/Dashboard.swift b/KlockWork/Views/Dashboard/Dashboard.swift index 333773ae..f61bfade 100644 --- a/KlockWork/Views/Dashboard/Dashboard.swift +++ b/KlockWork/Views/Dashboard/Dashboard.swift @@ -12,7 +12,7 @@ import KWCore struct Dashboard: View { @EnvironmentObject public var nav: Navigation @EnvironmentObject public var updater: ViewUpdater - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false private let page: PageConfiguration.AppPage = .find var body: some View { diff --git a/KlockWork/Views/Dashboard/DayInHistory.swift b/KlockWork/Views/Dashboard/DayInHistory.swift index d9259124..f501b41f 100644 --- a/KlockWork/Views/Dashboard/DayInHistory.swift +++ b/KlockWork/Views/Dashboard/DayInHistory.swift @@ -9,27 +9,48 @@ import SwiftUI import KWCore -struct DayInHistory { +struct DayInHistory: View { + @EnvironmentObject public var state: Navigation public var year: Int public var date: Date public var count: Int public var highlight: Bool { return count == 0 } + public var isToday: Bool { + return DateHelper.todayShort(format: "yyyy") == String(self.year) + } + + var body: some View { + SidebarItem( + data: self.linkLabel(), + help: self.linkLabel(), + icon: "chevron.right", + orientation: .right, + action: { + self.state.session.date = self.date + self.state.to(.today) + }, + showBorder: false, + showButton: false + ) + .background(self.isToday ? .yellow.opacity(0.5) : self.highlight ? Theme.base.opacity(0.3) : Theme.cPurple) + .foregroundStyle(self.highlight ? Theme.lightWhite : .white) + } +} +extension DayInHistory { + /// Determines the correct text to display for each day block + /// - Returns: String public func linkLabel() -> String { - if count == 1 { - return "\(count) record on \(self.formatDate())" - } else if count > 0 { - return "\(count) records on \(self.formatDate())" + if self.isToday { + return "\(self.count) \(self.count == 1 ? "record" : "records") today" + } else { + if self.count > 0 { + return "\(self.count) \(self.count == 1 ? "record" : "records") from \(self.year)" + } } - return "No records from \(self.formatDate("yyyy"))" - } - - private func formatDate(_ format: String = "MMM d, yyyy") -> String { - let df = DateFormatter() - df.dateFormat = format - return df.string(from: date) + return "No records from \(self.year)" } } diff --git a/KlockWork/Views/Entities/Notes/NoteRowPlain.swift b/KlockWork/Views/Entities/Notes/NoteRowPlain.swift index 10969ef5..db28ad7e 100644 --- a/KlockWork/Views/Entities/Notes/NoteRowPlain.swift +++ b/KlockWork/Views/Entities/Notes/NoteRowPlain.swift @@ -14,7 +14,7 @@ struct NoteRowPlain: View { public var moc: NSManagedObjectContext public var icon: String = "chevron.right" - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false @EnvironmentObject public var nav: Navigation diff --git a/KlockWork/Views/Entities/Today/CommandLineInterface.swift b/KlockWork/Views/Entities/Today/CommandLineInterface.swift index f7b4c5bb..2fa1d31c 100644 --- a/KlockWork/Views/Entities/Today/CommandLineInterface.swift +++ b/KlockWork/Views/Entities/Today/CommandLineInterface.swift @@ -22,8 +22,9 @@ struct CommandLineInterface: View { @State private var showSelectorPanel: Bool = false @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false - + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 + @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation @@ -226,7 +227,13 @@ extension CommandLineInterface { } private func recordsForToday() async -> Void { - let records = CoreDataRecords(moc: moc).forDate(Date()) + let records = CoreDataRecords(moc: moc).forDate( + Date(), + sort: NSSortDescriptor( + keyPath: \LogRecord.timestamp, + ascending: self.tableSortOrder == 0 ? true : false + ) + ) if nav.session.cli.history.count <= CommandLineInterface.maxItems { for record in records.sorted(by: {$0.timestamp! <= $1.timestamp!}) { @@ -426,6 +433,7 @@ extension CommandLineInterface { struct Display: View { typealias UI = WidgetLibrary.UI @AppStorage("today.cli.showFilter") private var showCLIFilter: Bool = false + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 @State private var searchText: String = "" @State private var searchFilteredResults: [Navigation.CommandLineSession.History] = [] @State private var searchResults: [Navigation.CommandLineSession.History] = [] @@ -449,7 +457,7 @@ extension CommandLineInterface { VStack(alignment: .leading, spacing: 2) { ScrollView { VStack(spacing: 1) { - ForEach(nav.session.cli.history, id: \.id) { line in + ForEach(nav.session.cli.history.sorted(by: {self.tableSortOrder == 0 ? $0.time > $1.time : $0.time < $1.time}), id: \.id) { line in HStack(spacing: 0) { if let job = line.job { if let project = job.project { diff --git a/KlockWork/Views/Entities/Today/LogTable/LogTable.swift b/KlockWork/Views/Entities/Today/LogTable/LogTable.swift index bab6f942..0456ba33 100644 --- a/KlockWork/Views/Entities/Today/LogTable/LogTable.swift +++ b/KlockWork/Views/Entities/Today/LogTable/LogTable.swift @@ -119,6 +119,7 @@ extension Today.LogTable { @AppStorage("today.showColumnTimestamp") public var showColumnTimestamp: Bool = true @AppStorage("today.showColumnExtendedTimestamp") public var showColumnExtendedTimestamp: Bool = true @AppStorage("today.showColumnJobId") public var showColumnJobId: Bool = true + public var records: [LogRecord] var body: some View { @@ -154,6 +155,7 @@ extension Today.LogTable { public struct Chronologic: View { typealias UI = WidgetLibrary.UI @EnvironmentObject public var nav: Navigation + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 public var date: Date? = Date() private let page: PageConfiguration.AppPage = .today @State private var searchText: String = "" @@ -171,6 +173,7 @@ extension Today.LogTable { } .onAppear(perform: self.findRecords) .onChange(of: nav.session.date) { self.findRecords() } + .onChange(of: self.tableSortOrder) { self.findRecords() } .onChange(of: nav.saved) { if nav.saved { self.findRecords() @@ -201,6 +204,7 @@ extension Today.LogTable { /// A list of rows that are grouped by Job public struct Grouped: View { @EnvironmentObject public var nav: Navigation + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 private let page: PageConfiguration.AppPage = .today @State private var grouped: [FancyStaticTextField] = [] @State private var records: [LogRecord] = [] @@ -230,6 +234,7 @@ extension Today.LogTable { public struct Summarized: View { typealias UI = WidgetLibrary.UI @EnvironmentObject public var nav: Navigation + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 public var date: Date? = nil private let page: PageConfiguration.AppPage = .today // @TODO: needed? @@ -261,6 +266,7 @@ extension Today.LogTable { /// A list of events pulled from the user's connected calendar public struct Calendar: View { + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 @StateObject public var ce: CoreDataCalendarEvent = CoreDataCalendarEvent(moc: PersistenceController.shared.container.viewContext) // @TODO: needed? // @State private var searchText: String = "" @@ -285,7 +291,7 @@ extension Today.LogTable.Headers { extension Today.LogTable.TabContent.Chronologic { private func findRecords() -> Void { DispatchQueue.with(background: { - return CoreDataRecords(moc: self.nav.moc).forDate(nav.session.date) + return CoreDataRecords(moc: self.nav.moc).forDate(nav.session.date, sort: NSSortDescriptor(keyPath: \LogRecord.timestamp, ascending: self.tableSortOrder == 1 ? true : false)) }, completion: { recordsForToday in self.records = recordsForToday! }) @@ -295,7 +301,7 @@ extension Today.LogTable.TabContent.Chronologic { extension Today.LogTable.TabContent.Grouped { private func findRecords() -> Void { DispatchQueue.with(background: { - return CoreDataRecords(moc: self.nav.moc).forDate(nav.session.date) + return CoreDataRecords(moc: self.nav.moc).forDate(nav.session.date, sort: NSSortDescriptor(keyPath: \LogRecord.timestamp, ascending: self.tableSortOrder == 1 ? true : false)) }, completion: { recordsForToday in self.records = recordsForToday! grouped = CoreDataRecords(moc: self.nav.moc).createExportableGroupedRecordsAsViews(self.records) @@ -306,7 +312,7 @@ extension Today.LogTable.TabContent.Grouped { extension Today.LogTable.TabContent.Summarized { private func findRecords() -> Void { DispatchQueue.with(background: { - return CoreDataRecords(moc: self.nav.moc).forDate(nav.session.date) + return CoreDataRecords(moc: self.nav.moc).forDate(nav.session.date, sort: NSSortDescriptor(keyPath: \LogRecord.timestamp, ascending: self.tableSortOrder == 1 ? true : false)) }, completion: { recordsForToday in self.records = recordsForToday! }) diff --git a/KlockWork/Views/Entities/Today/LogTable/RowTypes/LogRow.swift b/KlockWork/Views/Entities/Today/LogTable/RowTypes/LogRow.swift index 434f282d..f99ce2f6 100644 --- a/KlockWork/Views/Entities/Today/LogTable/RowTypes/LogRow.swift +++ b/KlockWork/Views/Entities/Today/LogTable/RowTypes/LogRow.swift @@ -339,14 +339,7 @@ struct LogRow: View, Identifiable { self.nav.session.job = job if let project = job.project { self.nav.session.project = project - print("DERPO company=\(project.company?.name ?? "Invalid") stored=\(self.nav.session.company?.name ?? "Invalid")") - // @TODO: this doesn't work? company doesn't set but the other two do -// if let company = project.company { -// self.nav.session.company = company -// } - print("DERPO company=\(project.company?.name ?? "Invalid") stored=\(self.nav.session.company?.name ?? "Invalid")") - print("DERPO project=\(project.name ?? "Invalid") stored=\(self.nav.session.project?.name ?? "Invalid")") - print("DERPO job=\(job.title ?? job.jid.string ?? "Invalid") stored=\(self.nav.session.job?.jid.string ?? "Invalid")") + self.nav.session.company = project.company } } } diff --git a/KlockWork/Views/Entities/Today/PostingInterface.swift b/KlockWork/Views/Entities/Today/PostingInterface.swift index ea3e4c8e..1d1aa820 100644 --- a/KlockWork/Views/Entities/Today/PostingInterface.swift +++ b/KlockWork/Views/Entities/Today/PostingInterface.swift @@ -14,7 +14,7 @@ extension Today { @EnvironmentObject public var nav: Navigation @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false @AppStorage("general.experimental.cli") private var allowCLIMode: Bool = false - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false @FocusState private var primaryTextFieldInFocus: Bool @State private var text: String = "" @State private var errorNoJob: Bool = false diff --git a/KlockWork/Views/Find/FindDashboard.swift b/KlockWork/Views/Find/FindDashboard.swift index 3282033b..b58f9637 100644 --- a/KlockWork/Views/Find/FindDashboard.swift +++ b/KlockWork/Views/Find/FindDashboard.swift @@ -14,7 +14,7 @@ struct FindDashboard: View { typealias UI = WidgetLibrary.UI @EnvironmentObject public var nav: Navigation @AppStorage("searchbar.showTypes") private var showingTypes: Bool = false - @AppStorage("CreateEntitiesWidget.isSearching") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearching") private var isSearching: Bool = false @AppStorage("dashboard.showWelcomeHeader") private var showWelcomeHeader: Bool = true @AppStorage("widget.jobs.showPublished") private var allowAlive: Bool = true @State public var searching: Bool = false @@ -59,7 +59,7 @@ struct FindDashboard: View { onSubmit: onSubmit, onReset: onReset ) - .clipShape(.rect(topLeadingRadius: self.showWelcomeHeader ? 0 : 5, topTrailingRadius: self.showWelcomeHeader ? 0 : 5)) + .clipShape(.rect(topLeadingRadius: self.showWelcomeHeader || self.location == .sidebar ? 0 : 5, topTrailingRadius: self.showWelcomeHeader || self.location == .sidebar ? 0 : 5)) } Divider() @@ -76,16 +76,16 @@ struct FindDashboard: View { .opacity(0.3) UI.Links(location: self.location, isSearching: !searching && activeSearchText.count >= 2) } - .frame(height: self.location == .content ? 250 : 400) + .frame(height: 250) } - .background(Theme.rowColour) + .background(self.location == .content ? Theme.rowColour : .clear) .foregroundStyle(.gray) } if !searching && activeSearchText.count >= 2 { GridRow { - if location == .content { - HStack(alignment: .top, spacing: 1) { + if self.location == .content { + HStack(alignment: .top, spacing: 0) { Suggestions( searchText: $activeSearchText, publishedOnly: $allowAlive, @@ -101,8 +101,10 @@ struct FindDashboard: View { location: location ) - if nav.session.search.inspectingEntity != nil { - Inspector(entity: nav.session.search.inspectingEntity!) + if let entity = nav.session.search.inspectingEntity { + Inspector(entity: entity, location: .content) + } else if let event = nav.session.search.inspectingEvent { + Inspector(event: event, location: .content) } } } else if location == .sidebar { @@ -125,8 +127,21 @@ struct FindDashboard: View { } if showingTypes { - if location == .content { + if self.location == .content { self.TypeFilter + } else if self.location == .sidebar { + Spacer() + GridRow { + ZStack(alignment: .topLeading) { + LinearGradient(colors: [Theme.base, .clear], startPoint: .top, endPoint: .bottom) + .blendMode(.softLight) + .opacity(0.3) + UI.Links(location: self.location, isSearching: !searching && activeSearchText.count >= 2) + } + .frame(height: 300) + } + .background(.clear) + .foregroundStyle(.gray) } } diff --git a/KlockWork/Views/Find/Inspector/Inspector.swift b/KlockWork/Views/Find/Inspector/Inspector.swift index 21aaa68b..ff6d61da 100644 --- a/KlockWork/Views/Find/Inspector/Inspector.swift +++ b/KlockWork/Views/Find/Inspector/Inspector.swift @@ -12,10 +12,11 @@ import EventKit public struct Inspector: View, Identifiable { @EnvironmentObject public var nav: Navigation - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false public let id: UUID = UUID() public var entity: NSManagedObject? = nil public var event: EKEvent? = nil + public var location: WidgetLocation = .inspector private let panelWidth: CGFloat = 400 private var job: Job? = nil private var project: Project? = nil @@ -35,7 +36,8 @@ public struct Inspector: View, Identifiable { FancyButtonv2( text: "Close", action: {nav.session.search.cancel() ; nav.setInspector() ; self.isSearchStackShowing = false}, - icon: "xmark", + icon: "xmark.square.fill", + iconWhenHighlighted: "xmark.square", showLabel: false, size: .tiny, type: .clear @@ -51,8 +53,10 @@ public struct Inspector: View, Identifiable { } Spacer() } - .padding() + .padding([.trailing, .top, .bottom]) + .padding(.leading, self.location == .content ? 0 : 20) .frame(maxWidth: panelWidth) + .background(self.location == .content ? Theme.rowColour : .clear) } public var EntityInspectorBody: some View { VStack(alignment: .leading) { @@ -80,9 +84,10 @@ public struct Inspector: View, Identifiable { } } - init(entity: NSManagedObject? = nil, event: EKEvent? = nil) { + init(entity: NSManagedObject? = nil, event: EKEvent? = nil, location: WidgetLocation = .inspector) { self.entity = entity self.event = event + self.location = location if entity != nil { switch self.entity { @@ -643,6 +648,12 @@ public struct Inspector: View, Identifiable { Spacer() } Divider() + VStack(spacing: 1) { + ForEach(defs, id: \.objectID) { definition in + UI.Blocks.Definition(definition: definition) + } + } + .clipShape(.rect(cornerRadius: 5)) } Spacer() @@ -716,27 +727,17 @@ public struct Inspector: View, Identifiable { Divider() } - if let definition = self.item.definition { - HStack(alignment: .center) { - Image(systemName: "list.bullet").symbolRenderingMode(.hierarchical) - Text("Definition") - } - Divider() - HStack(alignment: .top, spacing: 10) { - Text(definition) - .contextMenu { - Button { - ClipboardHelper.copy(definition) - } label: { - Text("Copy to clipboard") - } - } - Spacer() - } - .help("Full definition text") - Divider() + HStack(alignment: .center) { + Image(systemName: "list.bullet").symbolRenderingMode(.hierarchical) + Text("Definition") } - + Divider() + VStack(alignment: .leading, spacing: 1) { + UI.Blocks.Definition(definition: self.item) + .help("Full definition text") + } + .clipShape(.rect(cornerRadius: 5)) + Divider() Spacer() HStack(alignment: .top, spacing: 10) { FancyButtonv2( diff --git a/KlockWork/Views/Find/Suggestions/Suggestions.swift b/KlockWork/Views/Find/Suggestions/Suggestions.swift index d6a67bb8..b50e93dc 100644 --- a/KlockWork/Views/Find/Suggestions/Suggestions.swift +++ b/KlockWork/Views/Find/Suggestions/Suggestions.swift @@ -12,7 +12,7 @@ import KWCore extension FindDashboard { struct Suggestions: View { @EnvironmentObject public var nav: Navigation - @AppStorage("CreateEntitiesWidget.isSearching") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearching") private var isSearching: Bool = false @Binding public var searchText: String @Binding public var publishedOnly: Bool @Binding public var showRecords: Bool @@ -40,6 +40,7 @@ extension FindDashboard { text: "", action: {self.isMinimized.toggle()}, icon: self.isMinimized ? "plus.square.fill" : "minus.square.fill", + iconWhenHighlighted: self.isMinimized ? "plus.square" : "minus.square", showLabel: false, showIcon: true, size: .tinyLink, @@ -56,7 +57,7 @@ extension FindDashboard { // @TODO: reduce this with a loop, each view is basically identical... if showRecords {SuggestedRecords(searchText: $searchText, publishedOnly: $publishedOnly)} if showNotes {SuggestedNotes(searchText: $searchText, publishedOnly: $publishedOnly)} - if showTasks {SuggestedTasks(searchText: $searchText)} + if showTasks {SuggestedTasks(searchText: $searchText, publishedOnly: $publishedOnly)} if showProjects {SuggestedProjects(searchText: $searchText, publishedOnly: $publishedOnly)} if showJobs {SuggestedJobs(searchText: $searchText, publishedOnly: $publishedOnly)} if showCompanies {SuggestedCompanies(searchText: $searchText, publishedOnly: $publishedOnly)} @@ -361,6 +362,7 @@ extension FindDashboard { typealias UI = WidgetLibrary.UI @EnvironmentObject public var nav: Navigation @Binding public var searchText: String + @Binding public var publishedOnly: Bool @State private var showChildren: Bool = false @State private var hover: Bool = false @FetchRequest private var items: FetchedResults @@ -418,19 +420,27 @@ extension FindDashboard { } } - init(searchText: Binding) { + init(searchText: Binding, publishedOnly: Binding) { _searchText = searchText + _publishedOnly = publishedOnly let req: NSFetchRequest = LogTask.fetchRequest() req.sortDescriptors = [ NSSortDescriptor(keyPath: \LogTask.created, ascending: true), ] - req.predicate = NSPredicate( - format: "content CONTAINS[cd] %@ && owner.project.company.hidden == false", - _searchText.wrappedValue - ) - + if publishedOnly.wrappedValue { + req.predicate = NSPredicate( + format: "completedDate == nil && cancelledDate == nil && (content CONTAINS[cd] %@ && owner.project.company.hidden == false)", + _searchText.wrappedValue + ) + } else { + req.predicate = NSPredicate( + format: "content CONTAINS[cd] %@ && owner.project.company.hidden == false", + _searchText.wrappedValue + ) + } + _items = FetchRequest(fetchRequest: req, animation: .easeInOut) } } diff --git a/KlockWork/Views/Home/Home.swift b/KlockWork/Views/Home/Home.swift index 8919946a..fd2b17ea 100644 --- a/KlockWork/Views/Home/Home.swift +++ b/KlockWork/Views/Home/Home.swift @@ -12,18 +12,15 @@ import KWCore struct Home: View { typealias APage = PageConfiguration.AppPage - typealias Entity = PageConfiguration.EntityType - typealias UI = WidgetLibrary.UI - @Environment(\.managedObjectContext) var moc @EnvironmentObject public var nav: Navigation - @AppStorage("isDatePickerPresented") public var isDatePickerPresented: Bool = false - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isCreateStackShowing") private var isCreateStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isCreateStackShowing") private var isCreateStackShowing: Bool = false @AppStorage("general.showSessionInspector") public var showSessionInspector: Bool = false @AppStorage("general.experimental.cli") private var cliEnabled: Bool = false @AppStorage("today.commandLineMode") private var commandLineMode: Bool = false @AppStorage("notifications.interval") private var notificationInterval: Int = 0 + @AppStorage("widgetlibrary.ui.isSidebarPresented") private var isSidebarPresented: Bool = false @State public var selectedSidebarButton: Page = .dashboard @State private var timer: Timer? = nil @@ -59,12 +56,12 @@ struct Home: View { NavigationStack { VStack(alignment: .leading, spacing: 0) { HStack(alignment: .top, spacing: 0) { - TabBackground - if nav.sidebar != nil { + if nav.sidebar != nil && self.isSidebarPresented { + TabBackground Sidebar + .border(width: 1, edges: [.trailing], color: Theme.rowColour) } - Divider().background(Theme.rowColour) ZStack(alignment: .leading) { InspectorAndMain LinearGradient(colors: [Theme.base, .clear], startPoint: .leading, endPoint: .trailing) @@ -98,15 +95,8 @@ struct Home: View { VStack(alignment: .leading, spacing: 0) { GlobalSidebarWidgets() - if !isSearchStackShowing && !isUpcomingTaskStackShowing { - if isDatePickerPresented { - ZStack { - nav.sidebar - Theme.base.opacity(0.7) - } - } else { - nav.sidebar - } + if !self.isSearchStackShowing && !self.isUpcomingTaskStackShowing { + nav.sidebar } } } @@ -143,7 +133,7 @@ struct Home: View { UI.AppNavigation() nav.view .navigationTitle(nav.pageTitle()) - .disabled(isDatePickerPresented) + .disabled(self.nav.inspector != nil) } if nav.inspector != nil { @@ -168,13 +158,12 @@ extension Home { /// Onload handler. Sets view state, finds events, creates toolbar buttons, monitors keyboard for Esc /// - Returns: Void private func actionOnAppear() -> Void { - nav.parent = selectedSidebarButton - checkForEvents() + self.nav.parent = self.selectedSidebarButton + self.checkForEvents() self.createToolbarButtons() KeyboardHelper.monitor(key: .keyDown, callback: { self.isSearchStackShowing = false - self.isDatePickerPresented = false self.isCreateStackShowing = false self.nav.session.search.reset() self.nav.session.search.inspectingEntity = nil @@ -211,7 +200,7 @@ extension Home { SidebarButton( destination: AnyView(Today()), pageType: .today, - iconAsImage: Entity.records.icon, + iconAsImage: EType.records.icon, label: "Today", sidebar: AnyView(TodaySidebar()), altMode: PageAltMode( @@ -229,8 +218,8 @@ extension Home { SidebarButton( destination: AnyView(CompanyDashboard()), pageType: .companies, - iconAsImage: Entity.companies.icon, - label: Entity.companies.label, + iconAsImage: EType.companies.icon, + label: EType.companies.label, sidebar: AnyView(DefaultCompanySidebar()) ) ) @@ -241,8 +230,8 @@ extension Home { SidebarButton( destination: AnyView(ProjectsDashboard()), pageType: .projects, - iconAsImage: Entity.projects.icon, - label: Entity.projects.label, + iconAsImage: EType.projects.icon, + label: EType.projects.label, sidebar: AnyView(DefaultCompanySidebar()) ) ) @@ -253,8 +242,8 @@ extension Home { SidebarButton( destination: AnyView(JobDashboard()), pageType: .jobs, - iconAsImage: Entity.jobs.icon, - label: Entity.jobs.label, + iconAsImage: EType.jobs.icon, + label: EType.jobs.label, sidebar: AnyView(JobDashboardSidebar()) ) ) @@ -280,7 +269,7 @@ extension Home { /// Updates the dashboard icon upcoming event indicator /// - Returns: EventIndicatorStatus private func updateIndicator() -> EventIndicatorStatus { - let ce = CoreDataCalendarEvent(moc: moc) + let ce = CoreDataCalendarEvent(moc: self.nav.moc) var upcoming: [EKEvent] = [] var inProgress: [EKEvent] = [] @@ -313,8 +302,6 @@ extension Home { /// Fires when you change companies, resets children /// - Returns: Void private func actionOnChangeCompany() -> Void { - self.nav.session.project = nil - self.nav.session.job = nil self.createToolbarButtons() } } diff --git a/KlockWork/Views/Settings/Settings.swift b/KlockWork/Views/Settings/Settings.swift index d666c578..84361612 100644 --- a/KlockWork/Views/Settings/Settings.swift +++ b/KlockWork/Views/Settings/Settings.swift @@ -12,7 +12,7 @@ import KWCore struct SettingsView: View { private enum SettingsTabs: Hashable { - case general, today, advanced, dashboard, notedashboard, notifications + case general, today, advanced, dashboard, notedashboard, notifications, accessibility } @StateObject public var ce: CoreDataCalendarEvent = CoreDataCalendarEvent(moc: PersistenceController.shared.container.viewContext) @@ -48,6 +48,13 @@ struct SettingsView: View { Label("Notifications", systemImage: "bell") } .tag(SettingsTabs.notifications) + + AccessibilitySettings() + .tabItem { + Label("Accessibility", systemImage: "accessibility") + } + .tag(SettingsTabs.accessibility) + } .padding(20) } diff --git a/KlockWork/Views/Settings/Tabs/AccessibilitySettings.swift b/KlockWork/Views/Settings/Tabs/AccessibilitySettings.swift new file mode 100644 index 00000000..a61ffdad --- /dev/null +++ b/KlockWork/Views/Settings/Tabs/AccessibilitySettings.swift @@ -0,0 +1,37 @@ +// +// AccessibilitySettings.swift +// KlockWork +// +// Created by Ryan Priebe on 2024-10-27. +// Copyright © 2024 YegCollective. All rights reserved. +// + +import SwiftUI +import KWCore + +struct AccessibilitySettings: View { + @EnvironmentObject public var state: Navigation + @AppStorage("settings.accessibility.showTabTitles") private var showTabTitles: Bool = true + + var body: some View { + HStack { + Spacer() + Form { + Section("User interface") { + Toggle("Show tab titles", isOn: $showTabTitles) + } + } + Spacer() + } + .padding(20) + .onAppear(perform: self.actionOnAppear) + } +} + +extension AccessibilitySettings { + /// Onload handler + /// - Returns: Void + private func actionOnAppear() -> Void { + + } +} diff --git a/KlockWork/Views/Settings/Tabs/NotificationSettings.swift b/KlockWork/Views/Settings/Tabs/NotificationSettings.swift index e6106e62..5b51110b 100644 --- a/KlockWork/Views/Settings/Tabs/NotificationSettings.swift +++ b/KlockWork/Views/Settings/Tabs/NotificationSettings.swift @@ -6,7 +6,6 @@ // Copyright © 2024 YegCollective. All rights reserved. // -import Foundation import SwiftUI import KWCore diff --git a/KlockWork/Views/Settings/Tabs/TodaySettings.swift b/KlockWork/Views/Settings/Tabs/TodaySettings.swift index b7d1680a..9ff45b78 100644 --- a/KlockWork/Views/Settings/Tabs/TodaySettings.swift +++ b/KlockWork/Views/Settings/Tabs/TodaySettings.swift @@ -23,7 +23,7 @@ struct TodaySettings: View { @AppStorage("today.calendar.hasAccess") public var hasAccess: Bool = false @AppStorage("today.startOfDay") public var startOfDay: Int = 9 @AppStorage("today.endOfDay") public var endOfDay: Int = 18 - @AppStorage("today.defaultTableSortOrder") private var defaultTableSortOrder: String = "DESC" + @AppStorage("today.tableSortOrder") private var tableSortOrder: Int = 0 @AppStorage("today.showColumnIndex") public var showColumnIndex: Bool = true @AppStorage("today.showColumnTimestamp") public var showColumnTimestamp: Bool = true @AppStorage("today.showColumnExtendedTimestamp") public var showColumnExtendedTimestamp: Bool = true @@ -49,9 +49,9 @@ struct TodaySettings: View { Text("Plain").tag(2) } - Picker("Default sort direction:", selection: $defaultTableSortOrder) { - Text("DESC").tag("DESC") - Text("ASC").tag("ASC") + Picker("Default sort direction:", selection: $tableSortOrder) { + Text("Newest first").tag(0) + Text("Oldest first").tag(1) } Group { diff --git a/KlockWork/Views/Shared/AppSidebar/SidebarButton.swift b/KlockWork/Views/Shared/AppSidebar/SidebarButton.swift index bfb73120..af801653 100644 --- a/KlockWork/Views/Shared/AppSidebar/SidebarButton.swift +++ b/KlockWork/Views/Shared/AppSidebar/SidebarButton.swift @@ -36,8 +36,8 @@ struct SidebarButton: View, Identifiable { @EnvironmentObject public var nav: Navigation @AppStorage("isDatePickerPresented") public var isDatePickerPresented: Bool = false - @AppStorage("CreateEntitiesWidget.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false var body: some View { VStack(spacing: 0) { diff --git a/KlockWork/Views/Shared/AppSidebar/SidebarItem.swift b/KlockWork/Views/Shared/AppSidebar/SidebarItem.swift index 6779357b..fe011d4e 100644 --- a/KlockWork/Views/Shared/AppSidebar/SidebarItem.swift +++ b/KlockWork/Views/Shared/AppSidebar/SidebarItem.swift @@ -82,7 +82,7 @@ struct SidebarItem: View, Identifiable { public var showButton: Bool = true public var contextMenu: AnyView? - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false @State private var highlighted: Bool = false diff --git a/KlockWork/Views/Shared/AppSidebar/Widgets/GlobalSidebarWidgets.swift b/KlockWork/Views/Shared/AppSidebar/Widgets/GlobalSidebarWidgets.swift index 379b0d0c..0fb0a95b 100644 --- a/KlockWork/Views/Shared/AppSidebar/Widgets/GlobalSidebarWidgets.swift +++ b/KlockWork/Views/Shared/AppSidebar/Widgets/GlobalSidebarWidgets.swift @@ -65,7 +65,6 @@ struct GlobalSidebarWidgets: View { type: .button, page: self.page ) - .disabled(nav.parent == .planning) Spacer() } .padding([.leading, .trailing], 15) @@ -193,13 +192,11 @@ struct GlobalSidebarWidgets: View { } struct FindButton: View { + @EnvironmentObject public var nav: Navigation @AppStorage("GlobalSidebarWidgets.isCreateStackShowing") private var isCreateStackShowing: Bool = false @AppStorage("GlobalSidebarWidgets.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false @AppStorage("GlobalSidebarWidgets.isSearching") private var isSearching: Bool = false - @Binding public var active: Bool - - @EnvironmentObject public var nav: Navigation var body: some View { VStack(alignment: .center) { @@ -208,7 +205,13 @@ struct GlobalSidebarWidgets: View { Theme.base.opacity(0.5) FancyButtonv2( text: "Search", - action: {active.toggle() ; isSearching.toggle() ; isCreateStackShowing = false ; self.isUpcomingTaskStackShowing = false ; nav.session.search.reset()}, + action: { + self.active.toggle() + self.isSearching.toggle() + self.isCreateStackShowing = false + self.isUpcomingTaskStackShowing = false + self.nav.session.search.reset() + }, icon: "magnifyingglass", fgColour: nav.session.job?.colour_from_stored().isBright() ?? false ? .black : .white, bgColour: nav.session.job?.colour_from_stored() ?? nil, @@ -463,7 +466,6 @@ extension GlobalSidebarWidgets { } } - extension GlobalSidebarWidgets.PlanButton { private func actionOnChangeFocus() -> Void { if nav.session.gif == .normal { diff --git a/KlockWork/Views/Shared/AppSidebar/Widgets/TodayInHistoryWidget.swift b/KlockWork/Views/Shared/AppSidebar/Widgets/TodayInHistoryWidget.swift index 10fb95ad..69da5194 100644 --- a/KlockWork/Views/Shared/AppSidebar/Widgets/TodayInHistoryWidget.swift +++ b/KlockWork/Views/Shared/AppSidebar/Widgets/TodayInHistoryWidget.swift @@ -10,56 +10,31 @@ import SwiftUI import KWCore struct TodayInHistoryWidget: View { + typealias UI = WidgetLibrary.UI @EnvironmentObject public var nav: Navigation public let title: String = "Today In History" - @State private var selectedDate: String = "" - @State private var currentDate: Date = Date() @State private var todayInHistory: [DayInHistory] = [] @AppStorage("dashboard.maxYearsPastInHistory") public var maxYearsPastInHistory: Int = 5 var body: some View { VStack(alignment: .leading, spacing: 0) { - ZStack(alignment: .bottomLeading) { - HStack(alignment: .center, spacing: 0) { - Text(self.title) - .padding(6) - .background(Theme.textBackground) - .foregroundStyle(.white) - .clipShape(RoundedRectangle(cornerRadius: 5)) - } - .padding(8) - } + UI.Sidebar.Title(text: self.title) Divider() - - VStack(alignment: .leading, spacing: 1) { - ForEach(todayInHistory, id: \.year) { day in - SidebarItem( - data: day.linkLabel(), - help: day.linkLabel(), - icon: "chevron.right", - orientation: .right, - action: {actionTodayInHistory(day)}, - showBorder: false, - showButton: false - ) - .background(day.highlight ? Theme.lightWhite : .yellow.opacity(0.8)) - .foregroundStyle(Theme.base.opacity(day.highlight ? 0.4 : 1)) - } + VStack(alignment: .leading, spacing: 0) { + ForEach(self.todayInHistory, id: \.year) { day in day } } - Divider() - } - .background(Theme.base.opacity(0.2)) - .onAppear(perform: loadWidgetData) - .onChange(of: nav.session.date) { - loadWidgetData() } + .onAppear(perform: self.loadWidgetData) + .onChange(of: self.nav.session.date) { self.loadWidgetData() } } } extension TodayInHistoryWidget { + /// Find historical data for the currently selected date + /// - Returns: Void private func findHistoricalDataForToday() async -> Void { let calendar = Calendar.autoupdatingCurrent - let current = calendar.dateComponents([.year, .month, .day], from: currentDate) + let current = calendar.dateComponents([.year, .month, .day], from: self.nav.session.date) todayInHistory = [] if current.isValidDate == false { @@ -69,29 +44,18 @@ extension TodayInHistoryWidget { let day = calendar.date(from: components) let numRecordsForDay = CoreDataRecords(moc: self.nav.moc).countForDate(day) - todayInHistory.append(DayInHistory(year: offsetYear, date: day ?? Date(), count: numRecordsForDay)) + todayInHistory.append( + DayInHistory(year: offsetYear, date: day ?? Date(), count: numRecordsForDay) + ) } } } - + + /// Calls findHistoricalDataForToday asynchronously + /// - Returns: Void private func loadWidgetData() -> Void { - let formatter = DateFormatter() - formatter.dateFormat = "EEEE, MMM d, yyyy" - formatter.timeZone = TimeZone.autoupdatingCurrent - formatter.locale = NSLocale.current - currentDate = nav.session.date - selectedDate = formatter.string(from: currentDate) - Task { await findHistoricalDataForToday() } } - - private func actionTodayInHistory(_ day: DayInHistory) -> Void { - nav.session.date = day.date - nav.parent = .today - nav.view = AnyView(Today(defaultSelectedDate: day.date).environmentObject(nav)) - nav.sidebar = AnyView(TodaySidebar(date: day.date)) - nav.pageId = UUID() - } } diff --git a/KlockWork/Views/Shared/Fancy/FancyGenericToolbar.swift b/KlockWork/Views/Shared/Fancy/FancyGenericToolbar.swift index 0c518487..48461494 100644 --- a/KlockWork/Views/Shared/Fancy/FancyGenericToolbar.swift +++ b/KlockWork/Views/Shared/Fancy/FancyGenericToolbar.swift @@ -77,35 +77,36 @@ struct FancyGenericToolbar: View { var body: some View { VStack(spacing: 0) { - GridRow { - Group { - ZStack(alignment: .bottom) { - (self.location == .content ? UIGradient() : nil) + if buttons.count > 1 { + GridRow { + Group { + ZStack(alignment: .bottom) { + (self.location == .content ? UIGradient() : nil) - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 1) { - ForEach(buttons, id: \ToolbarButton.id) { button in - TabView( - button: button, - location: location, - selected: $selected, - mode: mode, - page: self.page - ) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 1) { + ForEach(buttons, id: \ToolbarButton.id) { button in + TabView( + button: button, + location: location, + selected: $selected, + mode: mode, + page: self.page + ) - if buttons.count == 1 { - Text(buttons.first!.helpText) - .padding(.leading, 10) - .opacity(0.6) + if buttons.count == 1 { + Text(buttons.first!.helpText) + .padding(.leading, 10) + .opacity(0.6) + } } } } - .clipShape(.rect(topLeadingRadius: self.location == .content ? 5 : 0, topTrailingRadius: self.location == .content ? 5 : 0)) } } } + .frame(height: self.location == .content ? 50 : 32) } - .frame(height: self.location == .content ? 50 : 32) GridRow { Group { @@ -129,10 +130,12 @@ struct FancyGenericToolbar: View { } } } + .clipShape(.rect(cornerRadius: 5)) } struct TabView: View { @EnvironmentObject public var nav: Navigation + @AppStorage("settings.accessibility.showTabTitles") private var showTabTitles: Bool = true public var button: ToolbarButton public var location: WidgetLocation @Binding public var selected: Int @@ -182,9 +185,11 @@ struct FancyGenericToolbar: View { .padding(0) .foregroundStyle(self.selected == self.button.id ? self.nav.session.job?.backgroundColor ?? .white : .white.opacity(0.5)) } else { - button.label - .padding(0) - .foregroundStyle(self.selected == self.button.id ? .white : .white.opacity(0.5)) + if self.showTabTitles { + button.label + .padding(0) + .foregroundStyle(self.selected == self.button.id ? .white : .white.opacity(0.5)) + } } } else { if mode == .compact { @@ -193,7 +198,7 @@ struct FancyGenericToolbar: View { .foregroundStyle(self.selected == self.button.id ? self.nav.session.job?.backgroundColor ?? .white : .white.opacity(0.5)) .font(.title3) - if self.selected == self.button.id && self.button.labelText != nil { + if self.selected == self.button.id && self.button.labelText != nil && self.showTabTitles { Text(self.button.labelText!) .foregroundStyle(self.selected == self.button.id ? .white : .white.opacity(0.5)) .font(.headline) @@ -202,8 +207,10 @@ struct FancyGenericToolbar: View { .padding([.top, .bottom], 10) .padding([.leading, .trailing]) } else { - button.label.padding(16) - .foregroundStyle(self.selected == self.button.id ? .white : .white.opacity(0.5)) + if self.showTabTitles { + button.label.padding(16) + .foregroundStyle(self.selected == self.button.id ? .white : .white.opacity(0.5)) + } } } } diff --git a/KlockWork/Views/Shared/PanelGroup/Panel.swift b/KlockWork/Views/Shared/PanelGroup/Panel.swift index 0e8150c8..1586a8d3 100644 --- a/KlockWork/Views/Shared/PanelGroup/Panel.swift +++ b/KlockWork/Views/Shared/PanelGroup/Panel.swift @@ -100,7 +100,7 @@ struct CompanyPanel: View { typealias UI = WidgetLibrary.UI public var position: Panel.Position - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false @State private var showSearch: Bool = false @State private var searchText: String = "" @@ -266,7 +266,7 @@ struct ProjectPanel: View { typealias UI = WidgetLibrary.UI public var position: Panel.Position - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false @State private var showSearch: Bool = false @State private var searchText: String = "" @@ -388,7 +388,7 @@ public struct JobPanel: View { typealias UI = WidgetLibrary.UI public var position: Panel.Position - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false @State private var showSearch: Bool = false @State private var searchText: String = "" @@ -490,7 +490,7 @@ public struct NotePanel: View { typealias UI = WidgetLibrary.UI public var position: Panel.Position - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearching: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearching: Bool = false @State private var showSearch: Bool = false @State private var searchText: String = "" diff --git a/KlockWork/Views/Shared/TaskForecast.swift b/KlockWork/Views/Shared/TaskForecast.swift index 9ce239c9..6f98838b 100644 --- a/KlockWork/Views/Shared/TaskForecast.swift +++ b/KlockWork/Views/Shared/TaskForecast.swift @@ -159,9 +159,9 @@ struct Forecast: View, Identifiable { struct ForecastTypeButton: View { @EnvironmentObject private var state: Navigation - @AppStorage("CreateEntitiesWidget.isCreateStackShowing") private var isCreateStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isCreateStackShowing") private var isCreateStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false public var date: Date public var callback: (() -> Void)? = nil @FetchRequest public var upcomingTasks: FetchedResults @@ -225,9 +225,9 @@ struct Forecast: View, Identifiable { struct ForecastTypeRow: View { @EnvironmentObject private var state: Navigation - @AppStorage("CreateEntitiesWidget.isCreateStackShowing") private var isCreateStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isSearchStackShowing") private var isSearchStackShowing: Bool = false - @AppStorage("CreateEntitiesWidget.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isCreateStackShowing") private var isCreateStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isSearchStackShowing") private var isSearchStackShowing: Bool = false + @AppStorage("GlobalSidebarWidgets.isUpcomingTaskStackShowing") private var isUpcomingTaskStackShowing: Bool = false public var date: Date public var callback: (() -> Void)? = nil @FetchRequest public var upcomingTasks: FetchedResults