Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 96 additions & 83 deletions freewrite/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ struct ContentView: View {
@State private var isHoveringHistoryArrow = false
@State private var colorScheme: ColorScheme = .light // Add state for color scheme
@State private var isHoveringThemeToggle = false // Add state for theme toggle hover
@State private var toDeleteEntryId: UUID?

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
let entryHeight: CGFloat = 40

Expand Down Expand Up @@ -829,101 +831,112 @@ struct ContentView: View {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(entries) { entry in
Button(action: {
if selectedEntryId != entry.id {
// Save current entry before switching
if let currentId = selectedEntryId,
let currentEntry = entries.first(where: { $0.id == currentId }) {
saveEntry(entry: currentEntry)
if toDeleteEntryId == entry.id {
DeleteConfirmationView(onConfirm: {
deleteEntry(entry: entry)
toDeleteEntryId = nil
}, onCancel: {
toDeleteEntryId = nil
})
} else {
Button(action: {
if selectedEntryId != entry.id {
// Save current entry before switching
if let currentId = selectedEntryId,
let currentEntry = entries.first(where: { $0.id == currentId }) {
saveEntry(entry: currentEntry)
}

selectedEntryId = entry.id
loadEntry(entry: entry)
}

selectedEntryId = entry.id
loadEntry(entry: entry)
}
}) {
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(entry.previewText)
.font(.system(size: 13))
.lineLimit(1)
.foregroundColor(.primary)

Spacer()

// Export/Trash icons that appear on hover
if hoveredEntryId == entry.id {
HStack(spacing: 8) {
// Export PDF button
Button(action: {
exportEntryAsPDF(entry: entry)
}) {
Image(systemName: "arrow.down.circle")
.font(.system(size: 11))
.foregroundColor(hoveredExportId == entry.id ?
(colorScheme == .light ? .black : .white) :
(colorScheme == .light ? .gray : .gray.opacity(0.8)))
}
.buttonStyle(.plain)
.help("Export entry as PDF")
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
hoveredExportId = hovering ? entry.id : nil
}) {
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(entry.previewText)
.font(.system(size: 13))
.lineLimit(1)
.foregroundColor(.primary)

Spacer()

// Export/Trash icons that appear on hover
if hoveredEntryId == entry.id {
HStack(spacing: 8) {
// Export PDF button
Button(action: {
exportEntryAsPDF(entry: entry)
}) {
Image(systemName: "arrow.down.circle")
.font(.system(size: 11))
.foregroundColor(hoveredExportId == entry.id ?
(colorScheme == .light ? .black : .white) :
(colorScheme == .light ? .gray : .gray.opacity(0.8)))
}
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
.buttonStyle(.plain)
.help("Export entry as PDF")
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
hoveredExportId = hovering ? entry.id : nil
}
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

// Trash icon
Button(action: {
deleteEntry(entry: entry)
}) {
Image(systemName: "trash")
.font(.system(size: 11))
.foregroundColor(hoveredTrashId == entry.id ? .red : .gray)
}
.buttonStyle(.plain)
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
hoveredTrashId = hovering ? entry.id : nil

// Trash icon
Button(action: {
if entry.id != entries.last?.id {
toDeleteEntryId = entry.id
}
}) {
Image(systemName: "trash")
.font(.system(size: 11))
.foregroundColor(hoveredTrashId == entry.id ? .red : .gray)
}
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
.buttonStyle(.plain)
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
hoveredTrashId = hovering ? entry.id : nil
}
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}
}
}

Text(entry.date)
.font(.system(size: 12))
.foregroundColor(.secondary)
}

Text(entry.date)
.font(.system(size: 12))
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 4)
.fill(backgroundColor(for: entry))
)
}
.frame(maxWidth: .infinity)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 4)
.fill(backgroundColor(for: entry))
)
}
.buttonStyle(PlainButtonStyle())
.contentShape(Rectangle())
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
hoveredEntryId = hovering ? entry.id : nil
.buttonStyle(PlainButtonStyle())
.contentShape(Rectangle())
.onHover { hovering in
withAnimation(.easeInOut(duration: 0.2)) {
hoveredEntryId = hovering ? entry.id : nil
}
}
.onAppear {
NSCursor.pop() // Reset cursor when button appears
}
.help("Click to select this entry") // Add tooltip
}
.onAppear {
NSCursor.pop() // Reset cursor when button appears
}
.help("Click to select this entry") // Add tooltip

if entry.id != entries.last?.id {
Divider()
Expand Down Expand Up @@ -1305,4 +1318,4 @@ extension NSView {

#Preview {
ContentView()
}
}
36 changes: 36 additions & 0 deletions freewrite/components/DeleteConfirmationView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// DeleteConfirmationView.swift
// freewrite
//
// Created by Bahrawy on 15/04/2025.
//
import SwiftUI

struct DeleteConfirmationView: View {
var onConfirm: () -> Void
var onCancel: () -> Void

var body: some View {
HStack() {
Text("Delete entry?")
.fontWeight(.medium)
.foregroundColor(.primary)
.tint(.red)
Spacer()
HStack(spacing: 24) {
Button("Yes") {
onConfirm()
}
.buttonStyle(.borderless)
.tint(.red)

Button("No") {
onCancel()
}
.buttonStyle(.borderless)
}
}
.padding()
.background(Color(red: 1.0, green: 0.3, blue: 0.3, opacity: 0.1))
}
}