diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 643f935..8356b79 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -103,6 +103,7 @@ ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; }; ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; }; ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE529A2C1D94A4006BDB3D /* CameraView.swift */; }; + ECC4F38C2C4B9B63007EC227 /* PhotoFileObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC4F38B2C4B9B63007EC227 /* PhotoFileObject.swift */; }; ECC995A32C1E8F2800B2699A /* PhotoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC995A22C1E8F2800B2699A /* PhotoItem.swift */; }; ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; }; ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; }; @@ -244,6 +245,7 @@ ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = ""; }; ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = ""; }; ECBE529A2C1D94A4006BDB3D /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; + ECC4F38B2C4B9B63007EC227 /* PhotoFileObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoFileObject.swift; sourceTree = ""; }; ECC995A22C1E8F2800B2699A /* PhotoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoItem.swift; sourceTree = ""; }; ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = ""; }; ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = ""; }; @@ -1039,6 +1041,7 @@ EC9AB09E2C1401A40076AF58 /* EraserObject.swift */, ECD12A852C19EE3900B96E12 /* ElementObject.swift */, ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */, + ECC4F38B2C4B9B63007EC227 /* PhotoFileObject.swift */, ); path = Objects; sourceTree = ""; @@ -1162,6 +1165,7 @@ ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */, ECF7B2DA2C39169C004D2C57 /* Float++.swift in Sources */, ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */, + ECC4F38C2C4B9B63007EC227 /* PhotoFileObject.swift in Sources */, EC6E3BDE2C43D5A500DD20F3 /* SidebarVisibility.swift in Sources */, ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */, ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */, diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index e07f3aa..604e496 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -153,7 +153,7 @@ extension GraphicContext { } } case 1: - guard let photo = element.photo, photo.imageURL != nil else { return } + guard let photo = element.photo, photo.file?.imageURL != nil else { return } let _photo = Photo(object: photo) tree.insert(_photo.element, in: _photo.photoBox) default: @@ -312,23 +312,26 @@ extension GraphicContext { // MARK: - Photo extension GraphicContext { - func insertPhoto(at point: CGPoint, photoItem: PhotoItem) -> Photo { - let size = photoItem.getDimension() + func insertPhoto(at point: CGPoint, photoFile: PhotoFileObject) -> Photo { + let size = photoFile.photoDimension() let origin = point let bounds = [origin.x - size.width / 2, origin.y - size.height / 2, origin.x + size.width / 2, origin.y + size.height / 2] - let photo = Photo(url: photoItem.id, size: size, origin: origin, bounds: bounds, createdAt: .now, bookmark: photoItem.bookmark) + let photo = Photo(url: photoFile.imageURL, size: size, origin: origin, bounds: bounds, createdAt: .now, bookmark: photoFile.bookmark) tree.insert(photo.element, in: photo.photoBox) + let photoFileID = photoFile.objectID withPersistence(\.backgroundContext) { [weak _photo = photo, weak graphicContext = object] context in - guard let _photo else { return } + guard let _photo, let photoFile = context.object(with: photoFileID) as? PhotoFileObject else { + return + } let photo = PhotoObject(\.backgroundContext) - photo.imageURL = _photo.url photo.bounds = _photo.bounds photo.width = _photo.size.width photo.originY = _photo.origin.y photo.originX = _photo.origin.x photo.height = _photo.size.height photo.createdAt = _photo.createdAt - photo.bookmark = _photo.bookmark + photo.file = photoFile + photoFile.photos?.add(photo) let element = ElementObject(\.backgroundContext) element.createdAt = _photo.createdAt element.type = 1 diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index ddb9f3b..b7d065b 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -221,8 +221,8 @@ extension Canvas { // MARK: - Photo extension Canvas { - func insertPhoto(at point: CGPoint, photoItem: PhotoItem) -> Photo { - graphicContext.insertPhoto(at: point, photoItem: photoItem) + func insertPhoto(at point: CGPoint, photoFile: PhotoFileObject) -> Photo { + graphicContext.insertPhoto(at: point, photoFile: photoFile) } } diff --git a/Memola/Canvas/Elements/Photo/Photo.swift b/Memola/Canvas/Elements/Photo/Photo.swift index ed6a77c..9333297 100644 --- a/Memola/Canvas/Elements/Photo/Photo.swift +++ b/Memola/Canvas/Elements/Photo/Photo.swift @@ -37,12 +37,12 @@ final class Photo: @unchecked Sendable, Equatable { convenience init(object: PhotoObject) { self.init( - url: object.imageURL, + url: object.file?.imageURL, size: .init(width: object.width, height: object.height), origin: .init(x: object.originX, y: object.originY), bounds: object.bounds, createdAt: object.createdAt ?? .now, - bookmark: object.bookmark + bookmark: object.file?.bookmark ) self.object = object } diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index 12b1e2d..2d9c48f 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -19,7 +19,7 @@ final class Tool: NSObject, ObservableObject { @Published var selectedPen: Pen? @Published var draggedPen: Pen? // MARK: - Photo - @Published var selectedPhotoItem: PhotoItem? + @Published var selectedPhotoFile: PhotoFileObject? @Published var isLoadingPhoto: Bool = false @Published var selection: ToolSelection = .hand @@ -128,15 +128,32 @@ final class Tool: NSObject, ObservableObject { } } - func selectPhoto(_ image: Platform.Image, for canvasID: NSManagedObjectID) { + func createFile(_ image: Platform.Image, with canvas: CanvasObject) { guard let (resizedImage, dimension) = resizePhoto(of: image) else { return } - let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: canvasID) - withAnimation { - selectedPhotoItem = photoItem - isLoadingPhoto = false + guard let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: canvas.objectID) else { return } + let _dimension = photoItem.dimension + let graphicContext = canvas.graphicContext + withPersistence(\.viewContext) { [weak graphicContext = graphicContext] context in + let file = PhotoFileObject(\.viewContext) + file.imageURL = photoItem.id + file.bookmark = photoItem.bookmark + file.dimension = [_dimension.width, _dimension.height] + file.createdAt = .now + file.photos = [] + file.graphicContext = graphicContext + graphicContext?.files.add(file) + try context.saveIfNeeded() } } + func selectPhoto(_ photoFile: PhotoFileObject) { + selectedPhotoFile = photoFile + } + + func unselectPhoto() { + selectedPhotoFile = nil + } + private func resizePhoto(of image: Platform.Image) -> (Platform.Image, CGSize)? { let targetSize = CGSize(width: 512, height: 512) let size = image.size @@ -198,25 +215,10 @@ final class Tool: NSObject, ObservableObject { var photoBookmark: PhotoItem? do { let bookmark = try file.bookmarkData(options: .minimalBookmark) - photoBookmark = PhotoItem(id: file, image: image, previewImage: previewImage, dimension: dimension, bookmark: bookmark) + photoBookmark = PhotoItem(id: file, image: image, dimension: dimension, bookmark: bookmark) } catch { NSLog("[Memola] - \(error.localizedDescription)") } return photoBookmark } - - func unselectPhoto() { - guard let photoItem = selectedPhotoItem else { return } - let fileManager = FileManager.default - if let url = photoItem.bookmark.getBookmarkURL() { - do { - try fileManager.removeItem(at: url) - } catch { - NSLog("[Memola] - \(error.localizedDescription)") - } - } - withAnimation { - selectedPhotoItem = nil - } - } } diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 4aa65eb..02b96f0 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -329,17 +329,15 @@ extension CanvasViewController { } @objc private func recognizeTapGesture(_ gesture: Platform.TapGestureRecognizer) { - guard let photoItem = tool.selectedPhotoItem else { return } - withAnimation { - tool.selectedPhotoItem = nil - } + guard let photoFile = tool.selectedPhotoFile else { return } + tool.selectedPhotoFile = nil #if os(macOS) let pointInLeftBottomOrigin = gesture.location(in: drawingView) let point = CGPoint(x: pointInLeftBottomOrigin.x, y: drawingView.bounds.height - pointInLeftBottomOrigin.y) #else let point = gesture.location(in: drawingView) #endif - let photo = canvas.insertPhoto(at: point.muliply(by: drawingView.ratio), photoItem: photoItem) + let photo = canvas.insertPhoto(at: point.muliply(by: drawingView.ratio), photoFile: photoFile) history.addUndo(.photo(photo)) drawingView.draw() } diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 95935d8..f9925af 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -237,6 +237,7 @@ struct MemosView: View { markerPenObjects.first?.isSelected = true let graphicContextObject = GraphicContextObject(\.viewContext) + graphicContextObject.files = [] graphicContextObject.elements = [] memoObject.canvas = canvasObject diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index b8f3387..c98ceb8 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -167,6 +167,4 @@ struct ElementToolbar: View { .frame(maxWidth: .infinity) .transition(.move(edge: .bottom).combined(with: .blurReplace)) } - - } diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 2030334..e4aaeaf 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -75,14 +75,7 @@ struct MemoView: View { case .pen: PenDock(tool: tool, canvas: canvas) case .photo: - ZStack(alignment: .bottomTrailing) { - PhotoDock(tool: tool, canvas: canvas) - if let photoItem = tool.selectedPhotoItem { - PhotoPreview(photoItem: photoItem, tool: tool) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) - } - } - .frame(maxWidth: .infinity, alignment: .trailing) + PhotoDock(memo: memo, tool: tool, canvas: canvas) default: EmptyView() } @@ -99,13 +92,6 @@ struct MemoView: View { switch tool.selection { case .pen: PenDock(tool: tool, canvas: canvas) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) - case .photo: - if let photoItem = tool.selectedPhotoItem { - PhotoPreview(photoItem: photoItem, tool: tool) - .frame(maxWidth: .infinity, alignment: .trailing) - .transition(.move(edge: .trailing)) - } default: EmptyView() } diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index f11f4d1..3079abe 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -38,7 +38,7 @@ struct PenDock: View { VStack(alignment: .trailing, spacing: 5) { penPropertyTool penItemList - .frame(maxWidth: proxy.size.width * 0.4) + .frame(maxHeight: proxy.size.height * 0.4) } .fixedSize() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing) @@ -187,6 +187,40 @@ struct PenDock: View { .padding(.leading, 10) .contentShape(.rect) .contextMenu(if: pen.strokeStyle != .eraser) { + #if os(macOS) + Button { + tool.selectPen(pen) + } label: { + Label( + title: { Text("Select") }, + icon: { Image(systemName: "pencil.tip.crop.circle") } + ) + } + Button { + let originalPen = pen + let pen = PenObject.createObject(\.viewContext, penStyle: originalPen.style) + pen.color = originalPen.rgba + pen.thickness = originalPen.thickness + pen.isSelected = true + pen.tool = tool.object + let _pen = Pen(object: pen) + tool.duplicatePen(_pen, of: originalPen) + } label: { + Label( + title: { Text("Duplicate") }, + icon: { Image(systemName: "plus.square.on.square") } + ) + } + Button(role: .destructive) { + tool.removePen(pen) + } label: { + Label( + title: { Text("Remove") }, + icon: { Image(systemName: "trash") } + ) + } + .disabled(tool.markers.count <= 1) + #else ControlGroup { Button { tool.selectPen(pen) @@ -222,12 +256,12 @@ struct PenDock: View { .disabled(tool.markers.count <= 1) } .controlGroupStyle(.menu) + #endif } preview: { penPreview(pen) .drawingGroup() #if os(iOS) .contentShape(.contextMenuPreview, .rect(cornerRadius: 10)) - #else #endif } .onDrag(if: pen.strokeStyle != .eraser) { diff --git a/Memola/Features/Memo/PhotoDock/PhotoDock.swift b/Memola/Features/Memo/PhotoDock/PhotoDock.swift index 66f4116..9d1c469 100644 --- a/Memola/Features/Memo/PhotoDock/PhotoDock.swift +++ b/Memola/Features/Memo/PhotoDock/PhotoDock.swift @@ -11,6 +11,9 @@ import PhotosUI struct PhotoDock: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @FetchRequest private var fileObjects: FetchedResults + + private let memo: MemoObject private let size: CGFloat = 40 @ObservedObject private var tool: Tool @@ -20,15 +23,30 @@ struct PhotoDock: View { @State private var isCameraAccessDenied: Bool = false @State private var photosPickerItem: PhotosPickerItem? - init(tool: Tool, canvas: Canvas) { + init(memo: MemoObject, tool: Tool, canvas: Canvas) { + self.memo = memo self.tool = tool self.canvas = canvas + + let predicate: NSPredicate = NSPredicate(format: "graphicContext = %@", memo.canvas.graphicContext) + let descriptors: [SortDescriptor] = [SortDescriptor(\.createdAt)] + self._fileObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } var body: some View { Group { #if os(macOS) - photoOption + GeometryReader { proxy in + VStack(alignment: .trailing, spacing: 5) { + photoOption + photoItemGrid + .frame(minHeight: proxy.size.height * 0.2, maxHeight: proxy.size.height * 0.4) + } + .fixedSize() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing) + } + .padding(10) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) #else if horizontalSizeClass == .regular { photoOption @@ -41,7 +59,7 @@ struct PhotoDock: View { #if os(iOS) .fullScreenCover(isPresented: $opensCamera) { let image: Binding = Binding { - tool.selectedPhotoItem?.image + tool.selectedPhotoFile?.image } set: { image in guard let image else { return } tool.selectPhoto(image, for: canvas.canvasID) @@ -63,21 +81,19 @@ struct PhotoDock: View { } #endif .onChange(of: photosPickerItem) { oldValue, newValue in - if newValue != nil { + if let photoItem = newValue { Task { tool.isLoadingPhoto = true - let data = try? await newValue?.loadTransferable(type: Data.self) - if let data, let image = Platform.Image(data: data) { - tool.selectPhoto(image, for: canvas.canvasID) - } + await createFile(for: photoItem) photosPickerItem = nil + tool.isLoadingPhoto = false } } } } private var photoOption: some View { - VStack(spacing: 0) { + HStack(spacing: 0) { #if os(iOS) Button { openCamera() @@ -91,7 +107,11 @@ struct PhotoDock: View { #endif PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { Image(systemName: "photo.fill.on.rectangle.fill") + #if os(macOS) + .frame(width: size * 2, height: size) + #else .frame(width: size, height: size) + #endif .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) } @@ -126,9 +146,6 @@ struct PhotoDock: View { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) } - .padding(.trailing, 10) - .frame(maxHeight: .infinity) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) } private var compactPhotoOption: some View { @@ -186,6 +203,49 @@ struct PhotoDock: View { .transition(.move(edge: .bottom).combined(with: .blurReplace)) } + @ViewBuilder + private var photoItemGrid: some View { + let padding: CGFloat = 5 + let size = (self.size * 2 - (5 + padding * 2)) / 2 + let columns: [GridItem] = .init(repeating: GridItem(.flexible(), spacing: 5), count: 2) + ScrollView { + LazyVGrid(columns: columns, spacing: 5) { + ForEach(fileObjects) { file in + Group { + let previewSize = file.previewSize(size) + if let previewImage = file.previewImage { + Image(image: previewImage) + .resizable() + .frame(width: previewSize.width, height: previewSize.height) + .onTapGesture { + if tool.selectedPhotoFile == file { + tool.unselectPhoto() + } else { + tool.selectPhoto(file) + } + } + } else { + Color.gray.opacity(0.5) + } + } + .frame(width: size, height: size) + .clipShape(RoundedRectangle(cornerRadius: 5)) + .overlay { + if tool.selectedPhotoFile == file { + RoundedRectangle(cornerRadius: 5) + .stroke(Color.accentColor, lineWidth: 2.5) + } + } + } + } + .padding(padding) + } + .background { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + } + } + private func openCamera() { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { @@ -205,4 +265,11 @@ struct PhotoDock: View { isCameraAccessDenied = true } } + + private func createFile(for photoItem: PhotosPickerItem) async { + let data = try? await photoItem.loadTransferable(type: Data.self) + if let data, let image = Platform.Image(data: data) { + tool.createFile(image, with: memo.canvas) + } + } } diff --git a/Memola/Features/Memo/PhotoPreview/PhotoItem.swift b/Memola/Features/Memo/PhotoPreview/PhotoItem.swift index 821d7fb..36ba860 100644 --- a/Memola/Features/Memo/PhotoPreview/PhotoItem.swift +++ b/Memola/Features/Memo/PhotoPreview/PhotoItem.swift @@ -11,7 +11,6 @@ import Foundation struct PhotoItem: Identifiable, Equatable { var id: URL let image: Platform.Image - let previewImage: Platform.Image let dimension: CGSize let bookmark: Data diff --git a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift index 35e7a17..614b4b8 100644 --- a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift +++ b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift @@ -13,16 +13,23 @@ struct PhotoPreview: View { private let photoItem: PhotoItem @ObservedObject private var tool: Tool + private var previewWidth: CGFloat? { + horizontalSizeClass == .compact ? 80 : nil + } + + private var previewHeight: CGFloat? { + horizontalSizeClass == .compact ? nil : 100 + } + init(photoItem: PhotoItem, tool: Tool) { self.photoItem = photoItem self.tool = tool } var body: some View { - Image(image: photoItem.previewImage) + Image(image: photoItem.image) .resizable() - .scaledToFit() - .frame(width: horizontalSizeClass == .compact ? 80 : nil, height: horizontalSizeClass == .compact ? nil : 100) + .frame(width: previewWidth, height: previewHeight) .cornerRadius(5) .overlay { RoundedRectangle(cornerRadius: 5) @@ -33,7 +40,7 @@ struct PhotoPreview: View { .cornerRadius(5) .overlay(alignment: .topLeading) { Button { - tool.unselectPhoto() +// tool.unselectPhoto() } label: { Image(systemName: "xmark.circle.fill") .font(.title2) diff --git a/Memola/Persistence/Objects/GraphicContextObject.swift b/Memola/Persistence/Objects/GraphicContextObject.swift index ea26216..d2b457f 100644 --- a/Memola/Persistence/Objects/GraphicContextObject.swift +++ b/Memola/Persistence/Objects/GraphicContextObject.swift @@ -12,4 +12,5 @@ import Foundation final class GraphicContextObject: NSManagedObject { @NSManaged var canvas: CanvasObject? @NSManaged var elements: NSMutableOrderedSet + @NSManaged var files: NSMutableOrderedSet } diff --git a/Memola/Persistence/Objects/MemoObject.swift b/Memola/Persistence/Objects/MemoObject.swift index 73c2f42..5d36319 100644 --- a/Memola/Persistence/Objects/MemoObject.swift +++ b/Memola/Persistence/Objects/MemoObject.swift @@ -20,4 +20,8 @@ final class MemoObject: NSManagedObject, Identifiable { @NSManaged var preview: Data? @NSManaged var tool: ToolObject @NSManaged var canvas: CanvasObject + + var files: [PhotoFileObject] { + canvas.graphicContext.files.compactMap { $0 as? PhotoFileObject } + } } diff --git a/Memola/Persistence/Objects/PhotoFileObject.swift b/Memola/Persistence/Objects/PhotoFileObject.swift new file mode 100644 index 0000000..1ae9038 --- /dev/null +++ b/Memola/Persistence/Objects/PhotoFileObject.swift @@ -0,0 +1,40 @@ +// +// PhotoFileObject.swift +// Memola +// +// Created by Dscyre Scotti on 7/20/24. +// + +import CoreData +import Foundation + +@objc(PhotoFileObject) +final class PhotoFileObject: NSManagedObject, Identifiable { + @NSManaged var createdAt: Date? + @NSManaged var imageURL: URL? + @NSManaged var bookmark: Data? + @NSManaged var dimension: [CGFloat] + + @NSManaged var photos: NSMutableSet? + @NSManaged var graphicContext: GraphicContextObject? + + var previewImage: Platform.Image? { + guard let imageURL else { return nil } + guard let data = try? Data(contentsOf: imageURL, options: []) else { return nil } + return Platform.Image(data: data) + } + + func previewSize(_ size: CGFloat) -> (width: CGFloat, height: CGFloat) { + let minDimension = min(dimension[0], dimension[1]) + let width = size * dimension[0] / minDimension + let height = size * dimension[1] / minDimension + return (width, height) + } + + func photoDimension() -> CGSize { + let maxSize = max(dimension[0], dimension[1]) + let width = dimension[0] * 100 / maxSize + let height = dimension[1] * 100 / maxSize + return CGSize(width: width, height: height) + } +} diff --git a/Memola/Persistence/Objects/PhotoObject.swift b/Memola/Persistence/Objects/PhotoObject.swift index ac697c8..1d6baab 100644 --- a/Memola/Persistence/Objects/PhotoObject.swift +++ b/Memola/Persistence/Objects/PhotoObject.swift @@ -16,7 +16,7 @@ final class PhotoObject: NSManagedObject { @NSManaged var height: CGFloat @NSManaged var bounds: [CGFloat] @NSManaged var createdAt: Date? - @NSManaged var imageURL: URL? - @NSManaged var bookmark: Data? + + @NSManaged var file: PhotoFileObject? @NSManaged var element: ElementObject? } diff --git a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents index 317085d..10ff997 100644 --- a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents +++ b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents @@ -26,6 +26,7 @@ + @@ -46,6 +47,14 @@ + + + + + + + + @@ -56,6 +65,7 @@ +