Skip to content

Commit

Permalink
feat: adds auto-save results (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinhermawan authored Jan 9, 2024
1 parent d8abfc1 commit 78d3dba
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 11 deletions.
17 changes: 15 additions & 2 deletions Canvas.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
0A03693D2B4D6FBB00D7DAD4 /* SettingsModule in Frameworks */ = {isa = PBXBuildFile; productRef = 0A03693C2B4D6FBB00D7DAD4 /* SettingsModule */; };
0A20744D2B3CD3E5008516D1 /* ModelPricingModule in Frameworks */ = {isa = PBXBuildFile; productRef = 0A20744C2B3CD3E5008516D1 /* ModelPricingModule */; };
0A809A402B39752200C9A015 /* CanvasApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A809A3F2B39752200C9A015 /* CanvasApp.swift */; };
0A809A462B39752300C9A015 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A809A452B39752300C9A015 /* Assets.xcassets */; };
Expand All @@ -32,6 +33,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
0A03693B2B4D6F7600D7DAD4 /* SettingsModule */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SettingsModule; sourceTree = "<group>"; };
0A20744B2B3CD385008516D1 /* ModelPricingModule */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = ModelPricingModule; sourceTree = "<group>"; };
0A809A3C2B39752200C9A015 /* Canvas.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Canvas.app; sourceTree = BUILT_PRODUCTS_DIR; };
0A809A3F2B39752200C9A015 /* CanvasApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanvasApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -66,6 +68,7 @@
0A809A632B397C3B00C9A015 /* ImageEditModule in Frameworks */,
0A809A612B397C3B00C9A015 /* CoreViews in Frameworks */,
0A809A652B397C3B00C9A015 /* ImageGenerationModule in Frameworks */,
0A03693D2B4D6FBB00D7DAD4 /* SettingsModule in Frameworks */,
0A809A5B2B397B6100C9A015 /* CoreViewModels in Frameworks */,
0A809A672B397C3B00C9A015 /* ImageVariationModule in Frameworks */,
0A809A752B398AEE00C9A015 /* ImagePreferencesModule in Frameworks */,
Expand All @@ -82,7 +85,6 @@
0A809A332B39752200C9A015 = {
isa = PBXGroup;
children = (
0A20744B2B3CD385008516D1 /* ModelPricingModule */,
0A809A7D2B39D65000C9A015 /* CoreExtensions */,
0A809A762B398F1600C9A015 /* CoreModels */,
0A809A592B397B2E00C9A015 /* CoreViewModels */,
Expand All @@ -91,6 +93,8 @@
0A809A5C2B397B8800C9A015 /* ImageGenerationModule */,
0A809A732B398AB200C9A015 /* ImagePreferencesModule */,
0A809A5E2B397BE800C9A015 /* ImageVariationModule */,
0A20744B2B3CD385008516D1 /* ModelPricingModule */,
0A03693B2B4D6F7600D7DAD4 /* SettingsModule */,
0A809A3E2B39752200C9A015 /* Canvas */,
0A809A522B39772800C9A015 /* Frameworks */,
0A809A3D2B39752200C9A015 /* Products */,
Expand Down Expand Up @@ -208,6 +212,7 @@
0A809A832B39EB0100C9A015 /* AppInfo */,
0AE400632B3AD5F800307732 /* Sparkle */,
0A20744C2B3CD3E5008516D1 /* ModelPricingModule */,
0A03693C2B4D6FBB00D7DAD4 /* SettingsModule */,
);
productName = Canvas;
productReference = 0A809A3C2B39752200C9A015 /* Canvas.app */;
Expand All @@ -221,7 +226,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1510;
LastUpgradeCheck = 1520;
TargetAttributes = {
0A809A3B2B39752200C9A015 = {
CreatedOnToolsVersion = 15.1;
Expand Down Expand Up @@ -316,6 +321,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand Down Expand Up @@ -380,6 +386,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand Down Expand Up @@ -411,6 +418,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Canvas/Preview Content\"";
DEVELOPMENT_TEAM = 84ZM7K56B5;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down Expand Up @@ -441,6 +449,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Canvas/Preview Content\"";
DEVELOPMENT_TEAM = 84ZM7K56B5;
ENABLE_HARDENED_RUNTIME = YES;
Expand Down Expand Up @@ -513,6 +522,10 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
0A03693C2B4D6FBB00D7DAD4 /* SettingsModule */ = {
isa = XCSwiftPackageProductDependency;
productName = SettingsModule;
};
0A20744C2B3CD3E5008516D1 /* ModelPricingModule */ = {
isa = XCSwiftPackageProductDependency;
productName = ModelPricingModule;
Expand Down
13 changes: 12 additions & 1 deletion Canvas/App/CanvasApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@

import AppInfo
import CoreViewModels
import SettingsModule
import Sparkle
import SwiftUI

@main
struct CanvasApp: App {
@State private var appUpdater: AppUpdater
private var updater: SPUUpdater

@State private var settingsManager: SettingsManager

@State private var apiKeyViewModel: APIKeyViewModel
@State private var dalleViewModel: DalleViewModel
@State private var dalleModelInfoViewModel: DalleModelInfoViewModel

init() {
self._settingsManager = State(initialValue: SettingsManager())

self._apiKeyViewModel = State(initialValue: APIKeyViewModel())
self._dalleViewModel = State(initialValue: DalleViewModel())
self._dalleModelInfoViewModel = State(initialValue: DalleModelInfoViewModel())
Expand All @@ -34,6 +39,7 @@ struct CanvasApp: App {
var body: some Scene {
WindowGroup {
AppView()
.environment(settingsManager)
.environment(apiKeyViewModel)
.environment(dalleViewModel)
.environment(dalleModelInfoViewModel)
Expand All @@ -52,5 +58,10 @@ struct CanvasApp: App {
}
}
}

Settings {
SettingsView()
.environment(settingsManager)
}
}
}
2 changes: 0 additions & 2 deletions Canvas/Resources/Canvas.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
Expand Down
19 changes: 19 additions & 0 deletions CoreExtensions/Sources/CoreExtensions/NSImage+Write.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// NSImage.swift
//
//
// Created by Kevin Hermawan on 09/01/24.
//

import Foundation
import SwiftUI

public extension NSImage {
func write(to url: URL) throws -> Void {
guard let tiff = self.tiffRepresentation else { return }
guard let bitmap = NSBitmapImageRep(data: tiff) else { return }
guard let data = bitmap.representation(using: .png, properties: [:]) else { return }

try data.write(to: url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Kevin Hermawan on 26/12/23.
//

import CoreExtensions
import Nuke
import NukeUI
import SwiftUI
Expand Down Expand Up @@ -44,11 +45,8 @@ struct ImageResultContextMenu: View {

savePanel.begin { response in
guard response == .OK, let url = savePanel.url else { return }
guard let tiffData = nsImage.tiffRepresentation else { return }
guard let bitmapImage = NSBitmapImageRep(data: tiffData) else { return }
guard let pngData = bitmapImage.representation(using: .png, properties: [:]) else { return }

try? pngData.write(to: url)

try? nsImage.write(to: url)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
// Created by Kevin Hermawan on 25/12/23.
//

import CoreExtensions
import SettingsModule
import NukeUI
import SwiftUI

public struct ImageResultListItemView: View {
@Environment(SettingsManager.self) private var settingsManager

private let url: URL

public init(url: URL) {
Expand All @@ -29,6 +33,9 @@ public struct ImageResultListItemView: View {
} else if let image = state.image, let imageContainer = state.imageContainer {
image.resizable()
.aspectRatio(contentMode: .fit)
.onAppear {
autosaveAction(for: imageContainer.image)
}
.contextMenu {
ImageResultContextMenu(
name: url.lastPathComponent,
Expand All @@ -43,4 +50,24 @@ public struct ImageResultListItemView: View {
.background(Color(nsColor: .secondarySystemFill))
.clipShape(.rect(cornerRadius: 8, style: .continuous))
}

private func autosaveAction(for image: NSImage) {
let fileManager = FileManager.default

if settingsManager.autosaveEnabled {
var isDir: ObjCBool = false
var location = settingsManager.autosaveLocation

if fileManager.fileExists(atPath: location.path, isDirectory: &isDir) {
location.append(path: url.lastPathComponent)

try? image.write(to: location)
} else {
try? fileManager.createDirectory(atPath: location.path, withIntermediateDirectories: true, attributes: nil)
location.append(path: url.lastPathComponent)

try? image.write(to: location)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@
// Created by Kevin Hermawan on 26/12/23.
//

import SettingsModule
import SwiftUI
import ViewState

struct PromptFieldFooterText: View {
@Environment(SettingsManager.self) private var settingsManager

private var viewState: ViewState?

init(viewState: ViewState? = nil) {
self.viewState = viewState
}

private var message: String {
if settingsManager.autosaveEnabled {
return "A good prompt is essential for the best possible image generation results."
}

return "Auto-save is not enabled; remember to manually save the results."
}

var body: some View {
Text("A good prompt is essential for the best possible image generation results.")
Text(message)
.when(viewState, is: .loading) {
Text("Generating image...")
}
Expand Down
8 changes: 8 additions & 0 deletions SettingsModule/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
25 changes: 25 additions & 0 deletions SettingsModule/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SettingsModule",
platforms: [.macOS(.v14)],
products: [
.library(
name: "SettingsModule",
targets: ["SettingsModule"]),
],
dependencies: [
.package(url: "https://github.com/sindresorhus/Defaults.git", .upToNextMajor(from: "7.3.1"))
],
targets: [
.target(
name: "SettingsModule",
dependencies: ["Defaults"]),
.testTarget(
name: "SettingsModuleTests",
dependencies: ["SettingsModule"]),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Defaults+Keys.swift
//
//
// Created by Kevin Hermawan on 09/01/24.
//

import Defaults
import Foundation

let fileManager = FileManager.default
let homeDirectory = fileManager.homeDirectoryForCurrentUser

public extension Defaults.Keys {
static let autosaveEnabled = Key<Bool>("autosaveEnabled", default: true)
static let autosaveLocation = Key<URL>("autosaveLocation", default: homeDirectory.appending(path: "Canvas"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// SettingsManager.swift
//
//
// Created by Kevin Hermawan on 09/01/24.
//

import Defaults
import Foundation

@Observable
public final class SettingsManager {
public var autosaveEnabled: Bool = Defaults[.autosaveEnabled] {
didSet {
Defaults[.autosaveEnabled] = autosaveEnabled
}
}

public var autosaveLocation: URL = Defaults[.autosaveLocation] {
didSet {
Defaults[.autosaveLocation] = autosaveLocation
}
}

public init() {}
}
Loading

0 comments on commit 78d3dba

Please sign in to comment.