Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
6.5 kB · 178 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179// EventEditor.swift — a native sheet for creating / editing an aesthetical// event. Fields: Title, Start, End, Note, and a private/public control,// with Add / Save / Delete buttons. Used ONLY for aesthetical (editable)// events — feed events are read-only.import AppKit
// The result the editor hands back to its owner.enum EventEditorResult { case save(uid: String?, title: String, start: Date, end: Date, note: String, visibility: String) case delete(uid: String) case cancel}
final class EventEditor: NSWindowController {
// nil uid → "Add" mode; non-nil → "Edit" mode (Save + Delete shown). private let existingUID: String? private let onDone: (EventEditorResult) -> Void
private let titleField = NSTextField() private let startPicker = NSDatePicker() private let endPicker = NSDatePicker() private let noteField = NSTextField() private let visControl = NSSegmentedControl( labels: ["private", "public"], trackingMode: .selectOne, target: nil, action: nil)
init(uid: String?, title: String, start: Date, end: Date, note: String, visibility: String, onDone: @escaping (EventEditorResult) -> Void) { self.existingUID = uid self.onDone = onDone
let panel = NSPanel( contentRect: NSRect(x: 0, y: 0, width: 360, height: 280), styleMask: [.titled], backing: .buffered, defer: false) panel.title = uid == nil ? "New Event" : "Edit Event" super.init(window: panel)
buildUI(title: title, start: start, end: end, note: note, visibility: visibility) }
required init?(coder: NSCoder) { fatalError() }
private func buildUI(title: String, start: Date, end: Date, note: String, visibility: String) { guard let cv = window?.contentView else { return } let pad: CGFloat = 16 let labelW: CGFloat = 60 let fieldX = pad + labelW + 8 let fieldW: CGFloat = 360 - fieldX - pad var y: CGFloat = 280 - 40
func addLabel(_ text: String, at yy: CGFloat) { let l = NSTextField(labelWithString: text) l.frame = NSRect(x: pad, y: yy, width: labelW, height: 20) l.alignment = .right l.font = .systemFont(ofSize: 12) l.textColor = .secondaryLabelColor cv.addSubview(l) }
// Title addLabel("Title", at: y) titleField.frame = NSRect(x: fieldX, y: y - 2, width: fieldW, height: 24) titleField.stringValue = title titleField.placeholderString = "Event title" cv.addSubview(titleField) y -= 38
// Start addLabel("Start", at: y) startPicker.frame = NSRect(x: fieldX, y: y - 2, width: fieldW, height: 24) startPicker.datePickerStyle = .textFieldAndStepper startPicker.datePickerElements = [.yearMonthDay, .hourMinute] startPicker.dateValue = start cv.addSubview(startPicker) y -= 38
// End addLabel("End", at: y) endPicker.frame = NSRect(x: fieldX, y: y - 2, width: fieldW, height: 24) endPicker.datePickerStyle = .textFieldAndStepper endPicker.datePickerElements = [.yearMonthDay, .hourMinute] endPicker.dateValue = end cv.addSubview(endPicker) y -= 38
// Note addLabel("Note", at: y) noteField.frame = NSRect(x: fieldX, y: y - 2, width: fieldW, height: 24) noteField.stringValue = note noteField.placeholderString = "Optional note" cv.addSubview(noteField) y -= 38
// Visibility addLabel("Privacy", at: y) visControl.frame = NSRect(x: fieldX, y: y - 2, width: fieldW, height: 24) visControl.selectedSegment = (visibility == "public") ? 1 : 0 cv.addSubview(visControl) y -= 44
// Buttons row. let btnH: CGFloat = 28 let btnY: CGFloat = 14 let cancel = NSButton(title: "Cancel", target: self, action: #selector(cancelAction)) cancel.bezelStyle = .rounded cancel.frame = NSRect(x: pad, y: btnY, width: 80, height: btnH) cv.addSubview(cancel)
if existingUID != nil { let del = NSButton(title: "Delete", target: self, action: #selector(deleteAction)) del.bezelStyle = .rounded del.frame = NSRect(x: pad + 88, y: btnY, width: 80, height: btnH) del.contentTintColor = .systemRed cv.addSubview(del) }
let save = NSButton(title: existingUID == nil ? "Add" : "Save", target: self, action: #selector(saveAction)) save.bezelStyle = .rounded save.keyEquivalent = "\r" save.frame = NSRect(x: 360 - pad - 90, y: btnY, width: 90, height: btnH) cv.addSubview(save) }
// ── actions ──────────────────────────────────────────────────────
@objc private func saveAction() { let title = titleField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) guard !title.isEmpty else { NSSound.beep() return } var end = endPicker.dateValue // Guard against a non-positive duration. if end <= startPicker.dateValue { end = startPicker.dateValue.addingTimeInterval(3600) } let vis = visControl.selectedSegment == 1 ? "public" : "private" dismiss() onDone(.save(uid: existingUID, title: title, start: startPicker.dateValue, end: end, note: noteField.stringValue, visibility: vis)) }
@objc private func deleteAction() { guard let uid = existingUID else { return } dismiss() onDone(.delete(uid: uid)) }
@objc private func cancelAction() { dismiss() onDone(.cancel) }
// The window we're presented on as a sheet (so we can end it cleanly). private weak var parentWindow: NSWindow?
// Present as a sheet on the given window. func present(on parent: NSWindow) { guard let w = window else { return } parentWindow = parent parent.beginSheet(w) { _ in } }
private func dismiss() { guard let w = window else { return } if let parent = parentWindow { parent.endSheet(w) } else { close() } }}