Files
Outpost/Outpost/Support/NSImage+JPEGData.swift
T
Puranjay Savar Mattas 077042295b feat(profile): avatar upload/remove with crop editor, name editing
Profile now has a real avatar row (AvatarBadge + Upload Photo…/Remove),
wired through last commit's presigned-upload backend: pick a file via
NSOpenPanel, crop/rotate/zoom in a new AvatarCropperView, upload,
point users.update at the result, then best-effort delete whatever
attachment the previous avatar pointed to so replacing/removing a
photo doesn't leak an orphaned blob in Outline's storage every time.

AvatarCropperView builds its on-screen preview and its final exported
image from the exact same SwiftUI view composition (just instantiated
twice — once for display, once through ImageRenderer) rather than a
separately hand-derived set of crop math for a higher resolution —
that's deliberate: there's no way to visually verify a second
independent set of transform math agrees with what the user actually
saw and confirmed without running the app, so making the export
WYSIWYG by construction was the safer choice here.

Name is now editable too (TextField + Save, users.update name-only).
Email is read-only with a note pointing to Outline's web app instead —
per instruction, changing it here isn't supported since email is
tied to sign-in.

SessionStore gained userId (never stored before — needed for every
users.update call) and applyUpdatedProfile(_:), so a successful
change reflects immediately in the account footer and everywhere
else without waiting for the next auth.info refresh.
2026-08-15 16:27:01 +01:00

15 lines
491 B
Swift

#if os(macOS)
import AppKit
extension NSImage {
/// `NSImage` has no built-in JPEG encoder (unlike `UIImage`) — routes
/// through a bitmap representation to get one.
func jpegData(compressionQuality: CGFloat) -> Data? {
guard let tiffData = tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffData) else {
return nil
}
return bitmap.representation(using: .jpeg, properties: [.compressionFactor: compressionQuality])
}
}
#endif