Full Code of NSAntoine/Samra for AI

main a25a91f3c884 cached
46 files
162.7 KB
41.0k tokens
1 requests
Download .txt
Repository: NSAntoine/Samra
Branch: main
Commit: a25a91f3c884
Files: 46
Total size: 162.7 KB

Directory structure:
gitextract_kxyr337y/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── Samra/
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Backend/
│   │   ├── AppKitPrivates/
│   │   │   ├── AppKitPrivates.h
│   │   │   └── module.modulemap
│   │   ├── AssetCatalogInput.swift
│   │   ├── ClosureMenuItem.swift
│   │   ├── DetailItem.swift
│   │   ├── Extensions.swift
│   │   ├── Preferences.swift
│   │   ├── RenditionDiff.swift
│   │   └── UI Support/
│   │       ├── AssetCatalogDocument.swift
│   │       ├── BasicLayoutAnchorsHolding.swift
│   │       ├── QuickLooKPreviewSource.swift
│   │       └── URLHandler.swift
│   ├── Info.plist
│   ├── Samra.entitlements
│   ├── UI/
│   │   ├── AboutViewController.swift
│   │   ├── ClosureBasedButton.swift
│   │   ├── CollapseNotifierSplitViewController.swift
│   │   ├── Diff/
│   │   │   ├── AssetCatalogDiffSelectionViewController.swift
│   │   │   ├── DiffFilePreviewView.swift
│   │   │   └── DiffListViewController.swift
│   │   ├── MenuableCollectionView.swift
│   │   ├── PastFilesListViewController.swift
│   │   ├── Rendition/
│   │   │   ├── AssetCatalogDetailsView.swift
│   │   │   ├── RenditionCollectionViewItem.swift
│   │   │   ├── RenditionInformationView.swift
│   │   │   ├── RenditionListViewController.swift
│   │   │   ├── RenditionTypeHeaderView.swift
│   │   │   └── TypesListViewController.swift
│   │   ├── WelcomeScreenOption.swift
│   │   └── WelcomeViewController.swift
│   └── WindowController.swift
├── Samra.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       └── IDEWorkspaceChecks.plist
│   └── xcshareddata/
│       └── xcschemes/
│           ├── Samra.xcscheme
│           └── extractutil.xcscheme
└── extractutil/
    └── main.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
ko_fi: nsantoine


================================================
FILE: .github/workflows/main.yml
================================================
name: Xcode - Build and Analyze

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    name: Build and analyse default scheme using xcodebuild command
    runs-on: macos-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Build
        run: |
          xcodebuild build -scheme Samra -project Samra.xcodeproj -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO BUILD_DIR=${{ github.workspace }}/xcodebuild
          xcodebuild build -scheme extractutil -project Samra.xcodeproj -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO BUILD_DIR=${{ github.workspace }}/xcodebuild
          
          mkdir -p ${{ github.workspace }}/product
          cp -R ${{ github.workspace }}/xcodebuild/Release/Samra.app ${{ github.workspace }}/product
          
          mv  ${{ github.workspace }}/xcodebuild/Release/extractutil ${{ github.workspace }}/product/Samra.app/Contents/MacOS
          
          cd ${{ github.workspace }}/product
          zip -r ${{ github.workspace }}/Samra.zip .
          
      - name: Upload app to artifacts
        uses: actions/upload-artifact@v3
        with:
          name: Samra
          path: ${{ github.workspace }}/Samra.zip


================================================
FILE: .gitignore
================================================
.DS_Store
Package.resolved
*.xcuserdatad


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Antoine

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Samra

A macOS Application to explore and edit Asset Catalog (.car) files on macOS, with a nicer native, modern-feeling UI that does not crash every couple of seconds.

<img width="800" alt="image" src="https://user-images.githubusercontent.com/48022799/222929188-7eb62314-8433-42c6-baf5-5d851b679998.png">

## Why Samra?
There are a few existing asset catalog viewer applications for macOS, however, none felt feature complete, Samra offers the following:
- Browse through the Asset Catalog file
- Show all types of objects (renditions) in it, not just images (colors, pdfs, image sets, etc)
- Ability to edit icons/images & colors
- Search in Asset Catalog for renditions by name
- View detailed information about each rendition, such as lookup name, width, height, appearance (if it's meant for dark mode or light mode) and other type-specific information (ie, RGB values for colors).

## What versions does this support?
macOS 10.15.1+

## How can I download this?
Download the .app from the Releases

## Preview
<img width="933" alt="image" src="https://github.com/user-attachments/assets/a5fe1256-955e-4b10-964e-78d8588bebcc">
<img width="1099" alt="image" src="https://user-images.githubusercontent.com/48022799/222929119-4f9e043c-fdbf-4e39-9137-f344fc693da4.png">
<img width="493" alt="image" src="https://user-images.githubusercontent.com/48022799/222929132-4e5ee546-18b5-492f-a4a6-5599c1b76a20.png">
<img width="753" alt="image" src="https://user-images.githubusercontent.com/48022799/222929151-6355f60a-757e-4b17-bc4a-cb25213e8e8c.png">


================================================
FILE: Samra/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    
    var showWelcomeViewController: Bool = false
    
    static func main() {
        let instance = AppDelegate()
        NSApplication.shared.delegate = instance
        NSApplication.shared.run()
    }
    
    func applicationWillFinishLaunching(_ notification: Notification) {}
    
    @objc
    func openMenuItemClicked() {
        URLHandler.shared.presentArchiveChooserPanel(insertToRecentItems: true, senderView: nil)
    }
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application        
        if Preferences.showWelcomeVCOnLaunch {
            WindowController(kind: .welcome).showWindow(self)
        }
        
        let items = RenditionType.allCases.map { type in
            let item = NSMenuItem(title: type.description, action: #selector(TypesListViewController.goToSection(menuItemSender:)))
            item.tag = type.rawValue
            item.isEnabled = false
            return item
        }
        
        NSApplication.shared.mainMenu = NSMenu(items: [
            NSMenuItem(submenuTitle: "App", items: [
                NSMenuItem(title: "About Samra",
                           action: #selector(openAboutPanel),
                           keyEquivalent: ""),
                NSMenuItem.separator(),
                NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h", keyEquivalentModifierMask: [.command, .option]),
                NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:))),
                NSMenuItem.separator(),
                NSMenuItem(title: "Quit Samra", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"),
            ]),
            NSMenuItem(submenuTitle: "File", items: [
                NSMenuItem(title: "Open...", action: #selector(openMenuItemClicked), keyEquivalent: "o"),
                NSMenuItem(title: "Export to...", action: #selector(RenditionListViewController.exportCatalog)),
                NSMenuItem.separator(),
                NSMenuItem(title: "Diff Asset Catalogs", action: #selector(openDiffViewController), keyEquivalent: "d"),
                NSMenuItem.separator(),
                NSMenuItem(title: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
            ]),
            
            NSMenuItem(submenuTitle: "Edit", items: [
                NSMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z"),
                NSMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z"),
                NSMenuItem.separator(),
                NSMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"),
                NSMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"),
                NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"),
                NSMenuItem(title: "Paste and Match Style", action: #selector(NSText.paste(_:)), keyEquivalent: "V", keyEquivalentModifierMask: [.command, .option]),
                NSMenuItem(title: "Delete", action: #selector(NSText.delete(_:))),
                NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"),
                NSMenuItem.separator(),
                NSMenuItem(
                    submenuTitle: "Find",
                    items: [
                        NSMenuItem(title: "Find…", action: #selector(NSResponder.performTextFinderAction(_:)), keyEquivalent: "f", tag: NSTextFinder.Action.showFindInterface.rawValue),
                        NSMenuItem(title: "Find and Replace…", action: #selector(NSResponder.performTextFinderAction(_:)), keyEquivalent: "f", keyEquivalentModifierMask: [.command, .option], tag: NSTextFinder.Action.replaceAndFind.rawValue),
                        NSMenuItem(title: "Find Next", action: #selector(NSResponder.performTextFinderAction(_:)), keyEquivalent: "g", tag: NSTextFinder.Action.nextMatch.rawValue),
                        NSMenuItem(title: "Find Previous", action: #selector(NSResponder.performTextFinderAction(_:)), keyEquivalent: "G", tag: NSTextFinder.Action.previousMatch.rawValue),
                        NSMenuItem(title: "Use Selection for Find", action: #selector(NSResponder.performTextFinderAction(_:)), keyEquivalent: "e", tag: NSTextFinder.Action.setSearchString.rawValue),
                        NSMenuItem(title: "Jump to Selection", action: #selector(NSResponder.centerSelectionInVisibleArea(_:)), keyEquivalent: "j"),
                    ]),
                NSMenuItem(
                    submenuTitle: "Spelling and Grammar",
                    items: [
                        NSMenuItem(title: "Show Spelling and Grammar", action: #selector(NSTextCheckingController.showGuessPanel(_:)), keyEquivalent: ":"),
                        NSMenuItem(title: "Check Document Now", action: #selector(NSTextCheckingController.checkSpelling(_:)), keyEquivalent: ";"),
                        NSMenuItem(title: "Check Spelling While Typing", action: #selector(NSTextView.toggleContinuousSpellChecking(_:))),
                        NSMenuItem(title: "Correct Spelling Automatically", action: #selector(NSTextView.toggleAutomaticSpellingCorrection(_:))),
                    ]),
                NSMenuItem(
                    submenuTitle: "Substitutions",
                    items: [
                        NSMenuItem(title: "Show Substitutions", action: #selector(NSTextCheckingController.orderFrontSubstitutionsPanel(_:))),
                        NSMenuItem.separator(),
                        NSMenuItem(title: "Smart Copy/Paste", action: #selector(NSTextView.toggleSmartInsertDelete(_:))),
                        NSMenuItem(title: "Smart Quotes", action: #selector(NSTextView.toggleAutomaticQuoteSubstitution(_:))),
                        NSMenuItem(title: "Smart Dashes", action: #selector(NSTextView.toggleAutomaticDashSubstitution(_:))),
                        NSMenuItem(title: "Smart Links", action: #selector(NSTextView.toggleAutomaticLinkDetection(_:))),
                        NSMenuItem(title: "Data Detectors", action: #selector(NSTextView.toggleAutomaticDataDetection(_:))),
                        NSMenuItem(title: "Text Replacement", action: #selector(NSTextView.toggleAutomaticTextReplacement(_:))),
                    ]),
                NSMenuItem(
                    submenuTitle: "Transformations",
                    items: [
                        NSMenuItem(title: "Make Upper Case", action: #selector(NSResponder.uppercaseWord(_:))),
                        NSMenuItem(title: "Make Lower Case", action: #selector(NSResponder.lowercaseWord(_:))),
                        NSMenuItem(title: "Capitalize", action: #selector(NSResponder.capitalizeWord(_:))),
                    ]),
                NSMenuItem(
                    submenuTitle: "Speech",
                    items: [
                        NSMenuItem(title: "Start Speaking", action: #selector(NSSpeechSynthesizer.startSpeaking(_:))),
                        NSMenuItem(title: "Stop Speaking", action: #selector(NSTextView.stopSpeaking(_:))),
                    ]),
            ]),
            NSMenuItem(submenuTitle: "Sections", items: items),
            NSMenuItem(submenuTitle: "Help", items: [
                NSMenuItem(title: "Help", action: #selector(NSApplication.showHelp(_:)), keyEquivalent: "?")
            ]),
        ])
    }
    
    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
    
    func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
        return true
    }
    
    
    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        guard !flag else {
            return true
        }
        
        if Preferences.showWelcomeVCOnLaunch {
            WindowController(kind: .welcome).showWindow(self)
        } else {
            URLHandler.shared.presentArchiveChooserPanel(insertToRecentItems: true, senderView: nil)
        }
        
        return false
    }
    
    @objc
    func openAboutPanel() {
        WindowController(kind: .aboutPanel).showWindow(self)
    }
    
    @objc
    func openDiffViewController() {
        WindowController(kind: .diffSelection).showWindow(self)
    }
    
    func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
        let items = Preferences.recentlyOpenedFilePaths
        guard !items.isEmpty else {
            return nil
        }
        
        let parentMenu = NSMenu()
        let submenu = NSMenu()
        let submenuItem = NSMenuItem()
        submenuItem.title = "Recents"
        submenuItem.submenu = submenu
        
        for (index, item) in items.enumerated() {
            let menuItem = NSMenuItem(title: URL(fileURLWithPath: item).lastPathComponent,
                                      action: #selector(openItemFromDockMenu(sender:)),
                                      keyEquivalent: "")
            menuItem.tag = index
            submenu.addItem(menuItem)
        }
        
        parentMenu.addItem(submenuItem)
        return parentMenu
    }
    
    @objc
    func openItemFromDockMenu(sender: NSMenuItem) {
        let item = Preferences.recentlyOpenedFilePaths[sender.tag]
        URLHandler.shared.handleURLChosen(urlChosen: URL(fileURLWithPath: item),
                                          senderView: nil, insertToRecentItems: true)
    }
    
    func application(_ application: NSApplication, open urls: [URL]) {
        for url in urls {
            URLHandler.shared.handleURLChosen(urlChosen: url,
                                              senderView: nil,
                                              insertToRecentItems: true,
                                              openWelcomeScreenUponError: true)
        }
    }
}



================================================
FILE: Samra/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Samra/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "size" : "16x16",
      "idiom" : "mac",
      "filename" : "icon_16x16.png",
      "scale" : "1x"
    },
    {
      "size" : "16x16",
      "idiom" : "mac",
      "filename" : "icon_16x16@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "32x32",
      "idiom" : "mac",
      "filename" : "icon_32x32.png",
      "scale" : "1x"
    },
    {
      "size" : "32x32",
      "idiom" : "mac",
      "filename" : "icon_32x32@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "128x128",
      "idiom" : "mac",
      "filename" : "icon_128x128.png",
      "scale" : "1x"
    },
    {
      "size" : "128x128",
      "idiom" : "mac",
      "filename" : "icon_128x128@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "256x256",
      "idiom" : "mac",
      "filename" : "icon_256x256.png",
      "scale" : "1x"
    },
    {
      "size" : "256x256",
      "idiom" : "mac",
      "filename" : "icon_256x256@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "512x512",
      "idiom" : "mac",
      "filename" : "icon_512x512.png",
      "scale" : "1x"
    },
    {
      "size" : "512x512",
      "idiom" : "mac",
      "filename" : "icon_512x512@2x.png",
      "scale" : "2x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "iconfly"
  }
}

================================================
FILE: Samra/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Samra/Backend/AppKitPrivates/AppKitPrivates.h
================================================
//
//  AppKitPrivates.h
//  Samra
//
//  Created by Serena on 22/02/2023.
// 

#ifndef AppKitPrivates_h
#define AppKitPrivates_h

#import <AppKit/NSSplitViewController.h>

// Never go Eric Benét
@interface NSSplitViewController (PrivateForWhateverReason)
- (void)splitViewItem:(NSSplitViewItem * _Nonnull)item didChangeCollapsed:(BOOL)didCollapse animated:(BOOL)animated;
@end

#endif /* AppKitPrivates_h */


================================================
FILE: Samra/Backend/AppKitPrivates/module.modulemap
================================================
module AppKitPrivates {
    header "AppKitPrivates.h"
}


================================================
FILE: Samra/Backend/AssetCatalogInput.swift
================================================
//
//  AssetCatalogInput.swift
//  Samra
//
//  Created by Serena on 06/03/2023.
// 

import AssetCatalogWrapper

struct AssetCatalogInput {
    let fileURL: URL
    let catalog: CUICatalog
    let collection: RenditionCollection
    
    init(fileURL: URL, catalog: CUICatalog, collection: RenditionCollection) {
        self.fileURL = fileURL
        self.catalog = catalog
        self.collection = collection
    }
    
    init(fileURL: URL) throws {
        let (catalog, collection) = try AssetCatalogWrapper.shared.renditions(forCarArchive: fileURL)
        self.catalog = catalog
        self.collection = collection
        self.fileURL = fileURL
    }
}


================================================
FILE: Samra/Backend/ClosureMenuItem.swift
================================================
//
//  ClosureMenuItem.swift
//  Samra
//
//  Created by Serena on 02/03/2023.
// 

import Cocoa

class ClosureMenuItem: NSMenuItem {
    var closure: (() -> Void)
    
    @objc
    func performClosure() {
        closure()
    }
    
    init(title: String, closure: @escaping (() -> Void)) {
        self.closure = closure
        super.init(title: title, action: #selector(performClosure), keyEquivalent: "")
        self.target = self
    }
    
    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


================================================
FILE: Samra/Backend/DetailItem.swift
================================================
//
//  DetailItem.swift
//  Samra
//
//  Created by Serena on 21/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

struct DetailItem: Hashable {
    /// The Primary Text, such as "Height"
    let primaryText: String
    
    /// The Secondary Text, such as the height itself in String form
    let secondaryText: String
    
    init(primaryText: String, secondaryText: String) {
        self.primaryText = primaryText
        self.secondaryText = secondaryText
    }
    
    init<T: CustomStringConvertible>(primaryText: String, secondaryText: T?, fallback: String = "Unknown") {
        self.primaryText = primaryText
        self.secondaryText = secondaryText?.description ?? fallback
    }
}

struct DetailItemSection: Hashable {
    let sectionHeader: String
    let items: [DetailItem]
    
    static func from(assetStorage: CUICommonAssetStorage) -> [DetailItemSection] {
        let toolSection = DetailItemSection(sectionHeader: "Authoring Tool", items: [
            DetailItem(primaryText: "Tool", secondaryText: assetStorage.authoringTool()),
            DetailItem(primaryText: "Version", secondaryText: String(cString: assetStorage.versionString())),
        ])
        
        let argumentsSection = DetailItemSection(sectionHeader: "Arguments", items: [
            DetailItem(primaryText: "Thinning Arguments", secondaryText: assetStorage.thinningArguments())
        ])
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "E, d MMM yyyy h:mm a"
        let date = Date(timeIntervalSince1970: TimeInterval(assetStorage.storageTimestamp()))
        
        let dateSection = DetailItemSection(sectionHeader: "Date", items: [
            DetailItem(primaryText: "Date", secondaryText: dateFormatter.string(from: date)),
            DetailItem(primaryText: "UNIX Timestamp", secondaryText: assetStorage.storageTimestamp())
        ])
        
        let coreUIVersionText = assetStorage.responds(to: #selector(CUICommonAssetStorage.coreuiVersion)) ? assetStorage.coreuiVersion().description : "Unknown"
        let coreUISection = DetailItemSection(sectionHeader: "Other", items: [
            DetailItem(primaryText: "CoreUI Version", secondaryText: coreUIVersionText),
            DetailItem(primaryText: "Schema Version", secondaryText: assetStorage.schemaVersion()),
        ])
        
        return [toolSection, argumentsSection, dateSection, coreUISection]
    }
    
    static func from(rendition: Rendition) -> [DetailItemSection] {
        let cuiRend = rendition.cuiRend
        let namedLookup = rendition.namedLookup
        
        let formatter = ByteCountFormatter()
        formatter.countStyle = .memory
        formatter.includesActualByteCount = true
        
        let diskSize = formatter.string(fromByteCount: Int64(cuiRend.srcData.count))
        
        let sizeOnDisk = DetailItem(primaryText: "Size On Disk", secondaryText: diskSize)
        var items: [DetailItemSection] = []

        switch rendition.type {
        case .rawData:
            items.append(DetailItemSection(sectionHeader: "Base Attributes", items: [
                DetailItem(primaryText: "Name", secondaryText: namedLookup.name),
                sizeOnDisk,
                DetailItem(primaryText: "Compression", secondaryText:cuiRend.bitmapEncoding())
            ]))
            var details : [DetailItem] = []
            if let data = cuiRend.data() {
                let size = formatter.string(fromByteCount: Int64(data.count))
                details.append(DetailItem(primaryText: "Data Length", secondaryText:size))

            }
            if let uti = cuiRend.utiType() {
                details.append(DetailItem(primaryText: "UTI", secondaryText:uti))
            }
            items.append(DetailItemSection(sectionHeader: "Data Attributes", items: details))

        case .color:
            items.append(DetailItemSection(sectionHeader: "Base Attributes", items: [
                DetailItem(primaryText: "Name", secondaryText: cuiRend.name()),
                sizeOnDisk,
            ]))
            let cgColor = cuiRend.cgColor().takeUnretainedValue()
            let nsColor = NSColor(cgColor:cgColor)?.usingColorSpace(.deviceRGB)
            var red: CGFloat = 0
            var green: CGFloat = 0
            var blue: CGFloat = 0
            var alpha: CGFloat = 0
            nsColor?.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

            items.append(DetailItemSection(sectionHeader: "Color Attributes", items: [
                DetailItem(primaryText: "Red", secondaryText: Int(red * 255)),
                DetailItem(primaryText: "Green", secondaryText: Int(green * 255)),
                DetailItem(primaryText: "Blue", secondaryText: Int(blue * 255)),
            ]))

        case .svg, .pdf:
            items.append(DetailItemSection(sectionHeader: "Base Attributes", items: [
                DetailItem(primaryText: "Rendition Name", secondaryText: cuiRend.name()),
                DetailItem(primaryText: "Lookup Name", secondaryText: namedLookup.name),
                sizeOnDisk,
            ]))
            var size = CGSizeZero
            switch rendition.type {
            case .svg:
                if let svgDoc = cuiRend.svgDocument() {
                    size = CGSVGDocumentGetCanvasSize(svgDoc)
                }
            case .pdf:
                if let pdfDoc = cuiRend.pdfDocument()?.takeUnretainedValue(), let page = pdfDoc.page(at:1) {
                    size = page.getBoxRect(.artBox).size
                }
            default:
                break
            }
            items.append(DetailItemSection(sectionHeader: "Dimensions", items: [
                DetailItem(primaryText: "Width", secondaryText: size.width),
                DetailItem(primaryText: "Height", secondaryText: size.height),
            ]))

        default:
            items.append(DetailItemSection(sectionHeader: "Base Attributes", items: [
                DetailItem(primaryText: "Rendition Name", secondaryText: cuiRend.name()),
                DetailItem(primaryText: "Lookup Name", secondaryText: namedLookup.name),
                sizeOnDisk,
                DetailItem(primaryText: "Compression", secondaryText:cuiRend.bitmapEncoding())
            ]))
            let size = cuiRend.unslicedSize()
            items.append(DetailItemSection(sectionHeader: "Dimensions", items: [
                DetailItem(primaryText: "Width", secondaryText: size.width),
                DetailItem(primaryText: "Height", secondaryText: size.height),
                DetailItem(primaryText: "Scale", secondaryText: cuiRend.scale())
            ]))
        }
        
        let key = namedLookup.key
        items.append(DetailItemSection(sectionHeader: "Rendition Information", items: [
            DetailItem(primaryText: "Display Gamut", secondaryText: Rendition.DisplayGamut(key)),
            DetailItem(primaryText: "Appearance", secondaryText: namedLookup.appearance),
            DetailItem(primaryText: "Idiom", secondaryText: Rendition.Idiom(key))
        ]))
        
        return items
    }
}


================================================
FILE: Samra/Backend/Extensions.swift
================================================
//
//  Extensions.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AssetCatalogWrapper
import UniformTypeIdentifiers

@available(macOS 11, *)
extension UTType {
    static var carFile: UTType = UTType(filenameExtension: "car")!
}

extension NSUserInterfaceItemIdentifier: ExpressibleByStringLiteral {
    public init(stringLiteral value: StringLiteralType) {
        self.init(value)
    }
}

extension NSToolbarItem.Identifier {
    static let searchBar = NSToolbarItem.Identifier("SearchBar")
}

extension NSMenu {
    convenience init(title: String? = nil, items: [NSMenuItem]?) {
        defer {
            items.flatMap {
                self.items = $0
            }
        }
        guard let title = title else {
            self.init()
            return
        }
        self.init(title: title)
    }
}

extension NSMenuItem {
    convenience init(submenuTitle: String, items: [NSMenuItem]?) {
        self.init(title: submenuTitle, action: nil, keyEquivalent: "")
        submenu = NSMenu(title: submenuTitle, items: items)
    }
    
    convenience init(title: String, action: Selector? = nil, keyEquivalent: String = "", keyEquivalentModifierMask: NSEvent.ModifierFlags? = nil, tag: Int? = nil) {
        self.init(title: title, action: action, keyEquivalent: keyEquivalent)
        keyEquivalentModifierMask.flatMap {
            self.keyEquivalentModifierMask = $0
        }
        tag.flatMap {
            self.tag = $0
        }
    }
}

extension CGImage {
    var size: CGSize {
        return CGSize(width: width, height: height)
    }
}

extension NSAlert {
    convenience init(title: String, message: String? = nil) {
        self.init()
        self.messageText = title
        self.informativeText = message ?? self.informativeText
    }
}

extension NSWindow {
    /// Makes the title bar of the NSWindow transparent and removes the window's ability to be resized
    func makeTitleBarTransparentAndUnresizable() {
        styleMask.remove(.resizable)
        titleVisibility = .hidden
        titlebarAppearsTransparent = true
    }
}

extension NSColor {
    static func _makeStandardWindowBg(appearance: NSAppearance) -> NSColor {
        switch appearance.name {
        case .aqua, .vibrantLight, .accessibilityHighContrastAqua, .accessibilityHighContrastVibrantLight: // light
            return .white
        case .darkAqua, .accessibilityHighContrastVibrantDark, .accessibilityHighContrastDarkAqua, .vibrantDark: // dark
            return NSColor(red: 0.19, green: 0.19, blue: 0.19, alpha: 1)
        default:
            fatalError()
        }
    }
    
    static var standardWindowBackgroundColor: NSColor {
        return NSColor(name: nil, dynamicProvider: _makeStandardWindowBg(appearance:))
    }
}

extension NSImage {
    convenience init?(systemName: String) {
        if #available(macOS 11, *) {
            self.init(systemSymbolName: systemName, accessibilityDescription: nil)
        } else {
            return nil
        }
    }
}


================================================
FILE: Samra/Backend/Preferences.swift
================================================
//
//  Preferences.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Foundation

@propertyWrapper
struct Storage<T> {
    let key: String
    var defaultValue: T
    
    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

enum Preferences {
    @Storage(key: "RecentlyOpenedPaths", defaultValue: [])
    static var recentlyOpenedFilePaths: [String]
    
    @Storage(key: "ShowWelcomeViewControllerOnLaunch", defaultValue: true)
    static var showWelcomeVCOnLaunch: Bool
    
    /*
    static var recentlyOpenedFilePaths: [String] {
        get {
            let arr = UserDefaults.standard.stringArray(forKey: "RecentlyOpenedPaths") ?? []
            return arr
        }
        
        set {
            UserDefaults.standard.set(newValue, forKey: "RecentlyOpenedPaths")
        }
    }
     */
}


================================================
FILE: Samra/Backend/RenditionDiff.swift
================================================
//
//  DiffKind.swift
//  Samra
//
//  Created by Serena on 07/03/2023.
// 

import AssetCatalogWrapper

struct RenditionDiff {
    let rend: Rendition
    let kind: Kind
    
    enum Kind: CustomStringConvertible {
        case added
        case removed
        
        var description: String {
            switch self {
            case .added:
                return "Added"
            case .removed:
                return "Removed"
            }
        }
    }
}

enum DiffSide: Int {
	case left = 1
	case right = 2
}


================================================
FILE: Samra/Backend/UI Support/AssetCatalogDocument.swift
================================================
//
//  AssetCatalogDocument.swift
//  Samra
//
//  Created by Serena on 02/03/2023.
// 

import Cocoa
import AssetCatalogWrapper

// this NSDocument subclass is from https://github.com/insidegui/AssetCatalogTinkerer
// (because this app is my first attempt at AppKit and I didn't really know how to do NSDocument)..
// but adjusted for Samra
class AssetCatalogDocument: NSDocument {
    override func read(from url: URL, ofType typeName: String) throws {
        // close the welcome view controller if opened
        for window in NSApplication.shared.windows {
            if window.contentViewController is WelcomeViewController {
                window.close()
            }
        }
        
        let windowController = WindowController(kind: .assetCatalog(try AssetCatalogInput(fileURL: url)))
        addWindowController(windowController)
        windowController.showWindow(nil)
    }
}


================================================
FILE: Samra/Backend/UI Support/BasicLayoutAnchorsHolding.swift
================================================
//
//  BasicLayoutAnchorsHolding.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

#if canImport(AppKit)
import AppKit
#elseif canImport(UIKit)
import UIKit
#endif

protocol BasicLayoutAnchorsHolding {
    var topAnchor: NSLayoutYAxisAnchor { get }
    var bottomAnchor: NSLayoutYAxisAnchor { get }
    var leadingAnchor: NSLayoutXAxisAnchor { get }
    var trailingAnchor: NSLayoutXAxisAnchor { get }
    var centerXAnchor: NSLayoutXAxisAnchor { get }
    var centerYAnchor: NSLayoutYAxisAnchor { get }
}

extension BasicLayoutAnchorsHolding {
    /// Activate constraints to cover the target with the current item.
    func constraintCompletely<Target: BasicLayoutAnchorsHolding>(to target: Target) {
        NSLayoutConstraint.activate([
            leadingAnchor.constraint(equalTo: target.leadingAnchor),
            trailingAnchor.constraint(equalTo: target.trailingAnchor),
            topAnchor.constraint(equalTo: target.topAnchor),
            bottomAnchor.constraint(equalTo: target.bottomAnchor)
        ])
    }
    
    /// Activate constraints to center the target with the current item.
    func centerConstraints<Target: BasicLayoutAnchorsHolding>(to target: Target) {
        NSLayoutConstraint.activate([
            centerXAnchor.constraint(equalTo: target.centerXAnchor),
            centerYAnchor.constraint(equalTo: target.centerYAnchor)
        ])
    }
}

#if canImport(UIKit)
extension UIView: BasicLayoutAnchorsHolding {}
extension UILayoutGuide: BasicLayoutAnchorsHolding {}
#else
extension NSView: BasicLayoutAnchorsHolding {}
extension NSLayoutGuide: BasicLayoutAnchorsHolding {}
#endif


================================================
FILE: Samra/Backend/UI Support/QuickLooKPreviewSource.swift
================================================
//
//  QuickLooKPreviewSource.swift
//  Samra
//
//  Created by Serena on 03/03/2023.
// 

import Cocoa
import QuickLookUI

class QuickLookPreviewSource: NSObject, QLPreviewPanelDataSource {
    let fileURL: URL
    
    init(fileURL: URL) {
        self.fileURL = fileURL
    }
    
    func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
        return 1
    }
    
    func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
        return fileURL as QLPreviewItem
    }
}


================================================
FILE: Samra/Backend/UI Support/URLHandler.swift
================================================
//
//  URLHandler.swift
//  Samra
//
//  Created by Serena on 20/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

class URLHandler {
    
    static let shared = URLHandler()
    
    private init() {}
    
    @objc
    func presentArchiveChooserPanel(insertToRecentItems: Bool = false, senderView: NSView?, handler: ((URL) -> Void)? = nil) {
        let panel = NSOpenPanel()
        let button = ClosureBasedButton(checkboxWithTitle: "Treat Bundles as directories", target: nil, action: nil)
        button.allowsMixedState = false
        button.setAction {
            switch button.state {
            case .on:
                panel.treatsFilePackagesAsDirectories = true
            case .off:
                panel.treatsFilePackagesAsDirectories = false
            default:
                print("Not supposed to be here")
            }
        }
        
        panel.accessoryView = button
        panel.accessoryView?.frame.size.height += 18
        panel.canChooseDirectories = false
        panel.allowsMultipleSelection = false
        if #available(macOS 11, *) {
            panel.allowedContentTypes = [.carFile, .application]
        } else {
            panel.allowedFileTypes = ["car", "app"]
        }
        
        if panel.runModal() == .OK {
            if let handler {
                handler(panel.urls[0])
            } else {
                handleURLChosen(urlChosen: panel.urls[0], senderView: senderView, insertToRecentItems: insertToRecentItems)
            }
        }
    }
    
    
    func handleURLChosen(urlChosen: URL,
                         senderView: NSView?,
                         insertToRecentItems: Bool = false,
                         openWelcomeScreenUponError: Bool = false) {
        
        let urlToOpen: URL
        switch urlChosen.pathExtension {
        case "car":
            // in case the URL was opened through the samra:// URL scheme,
            // let's init with URL(fileURLWithPath:),
            // to make sure that we have the file:// URL scheme
            urlToOpen = URL(fileURLWithPath: urlChosen.path)
        case "app":
            // find Assets.car file for the application
            // and make sure it exists
            urlToOpen = URL(fileURLWithPath: urlChosen.path) // set to file URL
                .appendingPathComponent("Contents/Resources/Assets.car")
            guard FileManager.default.fileExists(atPath: urlToOpen.path) else {
                NSAlert(title: "Assets.car file does not exist for Application \(urlChosen.path)").runModal()
                return
            }
            
        default:
            NSAlert(title: "File has unrecognized extension \"\(urlChosen.pathExtension)\"").runModal()
            return
        }
        
        do {
            let input = try AssetCatalogInput(fileURL: urlToOpen)
            // open new window & view controller for it
            WindowController(kind: .assetCatalog(input)).showWindow(self)
            if insertToRecentItems {
                var copy = Preferences.recentlyOpenedFilePaths
                copy.removeAll { $0 == urlChosen.path }
                copy.append(urlChosen.path)
                Preferences.recentlyOpenedFilePaths = copy
            }
            senderView?.window?.close()
        } catch {
            if openWelcomeScreenUponError {
                WindowController(kind: .welcome).showWindow(NSApplication.shared.delegate)
            }
            
            let alert = NSAlert()
            alert.messageText = "Unable to load Assets file"
            alert.informativeText = "Error: \(error.localizedDescription)"
            alert.runModal()
        }
    }
}


================================================
FILE: Samra/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDocumentTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeExtensions</key>
			<array>
				<string>car</string>
			</array>
			<key>CFBundleTypeIconFile</key>
			<string></string>
			<key>CFBundleTypeName</key>
			<string>Asset Catalog</string>
			<key>CFBundleTypeIconSystemGenerated</key>
			<integer>1</integer>
			<key>CFBundleTypeRole</key>
			<string>Viewer</string>
			<key>LSItemContentTypes</key>
			<array>
				<string>com.apple.assetcatalog</string>
			</array>
			<key>LSTypeIsPackage</key>
			<integer>0</integer>
			<key>NSDocumentClass</key>
			<string>$(PRODUCT_MODULE_NAME).AssetCatalogDocument</string>
		</dict>
	</array>
	<key>UTExportedTypeDeclarations</key>
	<array>
		<dict>
			<key>UTTypeConformsTo</key>
			<array>
				<string>public.data</string>
			</array>
			<key>UTTypeDescription</key>
			<string>Asset Catalog</string>
			<key>UTTypeIdentifier</key>
			<string>com.apple.assetcatalog</string>
			<key>UTTypeIconFile</key>
			<string></string>
			<key>UTTypeIcons</key>
			<dict>
				<key>UTTypeIconBackgroundName</key>
				<string>AssetCatalog</string>
				<key>UTTypeIconBadgeName</key>
				<string></string>
				<key>UTTypeIconText</key>
				<string></string>
			</dict>
			<key>UTTypeTagSpecification</key>
			<dict>
				<key>public.filename-extension</key>
				<array>
					<string>car</string>
				</array>
			</dict>
		</dict>
	</array>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLName</key>
			<string>com.serena.samra.openfile</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>samra</string>
			</array>
		</dict>
	</array>
</dict>
</plist>


================================================
FILE: Samra/Samra.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>


================================================
FILE: Samra/UI/AboutViewController.swift
================================================
//
//  AboutViewController.swift
//  Samra
//
//  Created by Serena on 28/02/2023.
// 

import Cocoa

class AboutViewController: NSViewController {
    override func loadView() {
        view = NSView()
        view.frame.size = CGSize(width: 530.0, height:219.0)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        let imageView = NSImageView(image: NSApplication.shared.applicationIconImage)
        
        let titleLabel = NSTextField(labelWithString: "Samra")
        titleLabel.font = .systemFont(ofSize: 38)
        
        let version = Bundle.main.infoDictionary?["CFBundleVersion"] as! String
        let versionLabel = NSTextField(labelWithString: "Version \(version)")
        versionLabel.textColor = .secondaryLabelColor
        
        let explanation = "An open source macOS Application to browse and edit Asset Catalog files, created by Antoine"
        let explanationLabel = NSTextField(wrappingLabelWithString: explanation)
        
        explanationLabel.textColor = NSColor(red: 0.60, green: 0.60, blue: 0.60, alpha: 1.00)
        if #available(macOS 11, *) {
            explanationLabel.font = .preferredFont(forTextStyle: .footnote)
        } else {
            explanationLabel.font = .systemFont(ofSize: 10)
        }
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        versionLabel.translatesAutoresizingMaskIntoConstraints = false
        explanationLabel.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(imageView)
        view.addSubview(titleLabel)
        view.addSubview(versionLabel)
        view.addSubview(explanationLabel)
        
        NSLayoutConstraint.activate([
            imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            
            titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 15),
            titleLabel.centerYAnchor.constraint(equalTo: imageView.topAnchor, constant: 32),
            
            versionLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
            versionLabel.centerYAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5),
            
            explanationLabel.leadingAnchor.constraint(equalTo: versionLabel.leadingAnchor),
            explanationLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor),
            explanationLabel.centerYAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: 20)
        ])
        
        let twitterButton = NSButton(title: "Twitter",
                                     target: self, action: #selector(openTwitter))
        let sourceCodeButton = NSButton(title: "Source Code",
                                        target: self, action: #selector(openSourceCode))
        
        twitterButton.bezelStyle = .rounded
        sourceCodeButton.bezelStyle = .rounded
        
        let buttonsStackView = NSStackView(views: [twitterButton, sourceCodeButton])
        buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(buttonsStackView)
        NSLayoutConstraint.activate([
            buttonsStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -26.4),
            buttonsStackView.centerYAnchor.constraint(equalTo: view.bottomAnchor, constant: -25),
            
//            twitterButton.widthAnchor.constraint(equalToConstant: 154),
//            sourceCodeButton.widthAnchor.constraint(equalToConstant: 160)
        ])
    }
    
    @objc
    func openSourceCode() {
        NSWorkspace.shared.open(URL(string: "https://github.com/NSAntoine/Samra")!)
    }
    
    @objc
    func openTwitter() {
        NSWorkspace.shared.open(URL(string: "https://twitter.com/NSAntoine")!)
    }
    
    override func viewDidAppear() {
        super.viewDidAppear()
        
        guard let window = view.window else { return }
        window.backgroundColor = .standardWindowBackgroundColor
        window.standardWindowButton(.miniaturizeButton)?.isEnabled = false
        window.standardWindowButton(.zoomButton)?.isEnabled = false
    }
}


================================================
FILE: Samra/UI/ClosureBasedButton.swift
================================================
//
//  ClosureBasedButton.swift
//  Samra
//
//  Created by Serena on 05/03/2024.
//  

import Cocoa

class ClosureBasedButton: NSButton {
    var closureAction: (() -> Void)?
    
    @objc
    func performClosureAction() {
        closureAction?()
    }
    
    func setAction(_ action: @escaping () -> Void) {
        self.closureAction = action
        self.action = #selector(performClosureAction)
        self.target = self
    }
}


================================================
FILE: Samra/UI/CollapseNotifierSplitViewController.swift
================================================
//
//  CollapseNotifierSplitViewController.swift
//  Samra
//
//  Created by Serena on 22/02/2023.
// 

import Cocoa
import AppKitPrivates

/// A NSSPlitViewController subclass that notifies it's reciever
/// when a collapse status changes
class CollapseNotifierSplitViewController: NSSplitViewController {
    typealias Handler = (_ item: NSSplitViewItem, _ didCollapse: Bool, _ animated: Bool) -> Void
    
    var handler: Handler? = nil
    
    /// Whether or not the view controller should focus on the search bar
    /// when the cmd+f combo is clicked
    var shouldFocusOnSearchBar: Bool = false
    
    override func splitViewItem(_ item: NSSplitViewItem, didChangeCollapsed didCollapse: Bool, animated: Bool) {
        super.splitViewItem(item, didChangeCollapsed: didCollapse, animated: animated)
        handler?(item, didCollapse, animated)
    }
}


================================================
FILE: Samra/UI/Diff/AssetCatalogDiffSelectionViewController.swift
================================================
//
//  AssetCatalogDiffSelectionViewController.swift
//  Samra
//
//  Created by Serena on 06/03/2023.
// 

import Cocoa
import AssetCatalogWrapper

/// A View Controller to select 2 files to diff them.
class AssetCatalogDiffSelectionViewController: NSViewController {
    override func loadView() {
        view = NSView()
        view.frame.size = CGSize(width: 577, height: 208)
    }
    
    typealias DataSource = NSCollectionViewDiffableDataSource<RenditionDiff.Kind, Rendition>
    var dataSource: DataSource!
    
    var leftCatalogInput: AssetCatalogInput?
    var rightCatalogInput: AssetCatalogInput?
    
    var leftCatalogPathLabel: NSTextField!
    var rightCatalogPathLabel: NSTextField!
    
    var leftCatalogPreview: DiffFilePreviewView!
    var rightCatalogPreview: DiffFilePreviewView!
    
    var diffCatalogsButton: NSButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let leftButton = NSButton(title: "Left...",
                                  target: self, action: #selector(leftOrRightButtonClicked(sender:)))
        leftButton.tag = DiffSide.left.rawValue
        
        let rightButton = NSButton(title: "Right...",
                                   target: self, action: #selector(leftOrRightButtonClicked(sender:)))
        rightButton.tag = DiffSide.right.rawValue
        
        leftButton.translatesAutoresizingMaskIntoConstraints = false
        rightButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(leftButton)
        view.addSubview(rightButton)
        
        leftCatalogPathLabel = NSTextField(labelWithString: "")
        rightCatalogPathLabel = NSTextField(labelWithString: "")
        
        leftCatalogPathLabel.translatesAutoresizingMaskIntoConstraints = false
        rightCatalogPathLabel.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(leftCatalogPathLabel)
        view.addSubview(rightCatalogPathLabel)
        
        diffCatalogsButton = NSButton(title: "Start Diff", target: self, action: #selector(diffButtonPressed))
        diffCatalogsButton.translatesAutoresizingMaskIntoConstraints = false
        diffCatalogsButton.isEnabled = false
        
        view.addSubview(diffCatalogsButton)
        
        let previewBackgroundColor = NSColor(red: 0.22, green: 0.21, blue: 0.21, alpha: 1.00)
		leftCatalogPreview = makePreview(color: previewBackgroundColor, side: .left)
        leftCatalogPreview.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(leftCatalogPreview)
        
        let leftCatalogPreviewLabel = NSTextField(labelWithString: "Left")
        leftCatalogPreviewLabel.translatesAutoresizingMaskIntoConstraints = false
        leftCatalogPreviewLabel.alignment = .center
        view.addSubview(leftCatalogPreviewLabel)
        
		rightCatalogPreview = makePreview(color: previewBackgroundColor, side: .right)
        rightCatalogPreview.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(rightCatalogPreview)
        
        let rightCatalogPreviewLabel = NSTextField(labelWithString: "Right")
        rightCatalogPreviewLabel.translatesAutoresizingMaskIntoConstraints = false
        rightCatalogPreviewLabel.alignment = .center
        view.addSubview(rightCatalogPreviewLabel)
        
        NSLayoutConstraint.activate([
            leftButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -10),
            leftButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            leftButton.widthAnchor.constraint(equalToConstant: 80),
            
            rightButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25),
            rightButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            rightButton.widthAnchor.constraint(equalToConstant: 80),
            
            rightCatalogPreview.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -13),
            rightCatalogPreview.widthAnchor.constraint(equalToConstant: 55),
            rightCatalogPreview.topAnchor.constraint(equalTo: leftButton.topAnchor),
            rightCatalogPreview.bottomAnchor.constraint(equalTo: rightButton.bottomAnchor),
            
            rightCatalogPreviewLabel.topAnchor.constraint(equalTo: rightCatalogPreview.topAnchor, constant: -20),
            rightCatalogPreviewLabel.leadingAnchor.constraint(equalTo: rightCatalogPreview.leadingAnchor),
            rightCatalogPreviewLabel.trailingAnchor.constraint(equalTo: rightCatalogPreview.trailingAnchor),
            
            leftCatalogPreview.trailingAnchor.constraint(equalTo: rightCatalogPreviewLabel.leadingAnchor, constant: -20),
            leftCatalogPreview.widthAnchor.constraint(equalToConstant: 55),
            leftCatalogPreview.topAnchor.constraint(equalTo: leftButton.topAnchor),
            leftCatalogPreview.bottomAnchor.constraint(equalTo: rightButton.bottomAnchor),
            
            leftCatalogPathLabel.centerYAnchor.constraint(equalTo: leftButton.centerYAnchor),
            leftCatalogPathLabel.leadingAnchor.constraint(equalTo: leftButton.trailingAnchor, constant: 10),
            leftCatalogPathLabel.trailingAnchor.constraint(equalTo: leftCatalogPreview.leadingAnchor, constant: -20),
            
            leftCatalogPreviewLabel.topAnchor.constraint(equalTo: leftCatalogPreview.topAnchor, constant: -20),
            leftCatalogPreviewLabel.leadingAnchor.constraint(equalTo: leftCatalogPreview.leadingAnchor),
            leftCatalogPreviewLabel.trailingAnchor.constraint(equalTo: leftCatalogPreview.trailingAnchor),
            
            rightCatalogPathLabel.centerYAnchor.constraint(equalTo: rightButton.centerYAnchor),
            rightCatalogPathLabel.leadingAnchor.constraint(equalTo: rightButton.trailingAnchor, constant: 10),
            rightCatalogPathLabel.trailingAnchor.constraint(equalTo: leftCatalogPreview.leadingAnchor, constant: -20),
            
            diffCatalogsButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -26.4),
            diffCatalogsButton.centerYAnchor.constraint(equalTo: view.bottomAnchor, constant: -25),
        ])
    }
    
    override func viewDidAppear() {
        super.viewDidAppear()
        
        view.window?.title = "Diff Catalogs"
        view.window?.styleMask.remove(.resizable)
    }
    
	func makePreview(color: NSColor, side: DiffSide) -> DiffFilePreviewView {
        let preview = DiffFilePreviewView(side: side)
		preview.delegate = self
		return preview
    }
    
    @objc
    func diffButtonPressed() {
        guard let left = leftCatalogInput, let right = rightCatalogInput else { return }
        let rightCollection = right.collection.flatMap(\.renditions)
        let leftCollection = left.collection.flatMap(\.renditions)
        
        let diff = leftCollection.difference(from: rightCollection) { rend1, rend2 in
            return rend1.namedLookup.name == rend2.namedLookup.name
        }
        
        var finalDiffs: [RenditionDiff] = []
        for meow in diff {
            switch meow {
            case .insert(_, let element, _):
                finalDiffs.append(RenditionDiff(rend: element, kind: .added))
            case .remove(_, let element, _):
                finalDiffs.append(RenditionDiff(rend: element, kind: .removed))
            }
        }
        
        WindowController(kind: .diffShow(finalDiffs, left.catalog, left.fileURL)).showWindow(nil)
    }
    
    @objc
    func leftOrRightButtonClicked(sender: NSButton) {
        URLHandler.shared.presentArchiveChooserPanel(senderView: nil) { [unowned self] url in
            validateAndProcessURL(url, forSide: DiffSide(rawValue: sender.tag)!)
        }
    }
    
    func validateAndProcessURL(_ url: URL, forSide side: DiffSide) {
        // if it's an .app, point to it's .car file
        let urlToChoose = url.pathExtension == "app" ? url.appendingPathComponent("Contents/Resources/Assets.car") : url
        guard FileManager.default.fileExists(atPath: urlToChoose.path) else {
            NSAlert(title: "Asset Catalog file \(urlToChoose.path) doesn't exist").runModal()
            return
        }
        
        do {
            switch side {
            case .left:
                leftCatalogInput = try AssetCatalogInput(fileURL: urlToChoose)
                leftCatalogPathLabel.stringValue = urlToChoose.path
				leftCatalogPreview.imageView.image = NSWorkspace.shared.icon(forFile: url.path)
            case .right:
                rightCatalogInput = try AssetCatalogInput(fileURL: urlToChoose)
                rightCatalogPathLabel.stringValue = urlToChoose.path
				rightCatalogPreview.imageView.image = NSWorkspace.shared.icon(forFile: url.path)
            }
   
            diffCatalogsButton.isEnabled = rightCatalogInput != nil && leftCatalogInput != nil
        } catch {
            NSAlert(title: "Unable to open Asset Catalog file \(urlToChoose.path)")
                .runModal()
        }
    }
    
    func setImageViewForPreview(url: URL, side: DiffSide) {
		switch side {
		case .left:
			leftCatalogPreview.imageView.image = NSWorkspace.shared.icon(forFile: url.path)
		case .right:
			rightCatalogPreview.imageView.image = NSWorkspace.shared.icon(forFile: url.path)
		}
    }
}

extension AssetCatalogDiffSelectionViewController: DiffFilePreviewDelegate {
	func diffFilePreview(_ view: DiffFilePreviewView, didGetURLDragged urlRecieved: URL) {
		validateAndProcessURL(urlRecieved, forSide: view.side)
	}
}


================================================
FILE: Samra/UI/Diff/DiffFilePreviewView.swift
================================================
//
//  DiffFilePreviewView.swift
//  Samra
//
//  Created by Serena on 03/05/2023.
//

import Cocoa

class DiffFilePreviewView: NSView {
	let side: DiffSide
	let imageView = NSImageView()
	
	weak var delegate: DiffFilePreviewDelegate?
	
	init(side: DiffSide) {
		self.side = side
		super.init(frame: .zero)
		commonInit()
	}
	
	required init?(coder: NSCoder) {
		fatalError("init(coder:) has not been implemented")
	}
	
	// setup the view
	func commonInit() {
		let previewBackgroundColor = NSColor(red: 0.22, green: 0.21, blue: 0.21, alpha: 1.00)
		
		let previewLayer = CALayer()
		previewLayer.backgroundColor = previewBackgroundColor.cgColor
		previewLayer.borderColor = NSColor.lightGray.cgColor
		previewLayer.borderWidth = 1.34
		previewLayer.cornerRadius = 8
		layer = previewLayer
		wantsLayer = true
		
		registerForDraggedTypes([.fileURL])
		
		imageView.translatesAutoresizingMaskIntoConstraints = false
		addSubview(imageView)
		
		NSLayoutConstraint.activate([
			imageView.heightAnchor.constraint(equalTo: heightAnchor),
			imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
			imageView.centerXAnchor.constraint(equalTo: centerXAnchor)
		])
	}
	
	override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
		return .generic
	}
	
	override func draggingEnded(_ sender: NSDraggingInfo) {
		sender.enumerateDraggingItems(for: nil, classes: [NSURL.self]) { [unowned self] item, t, ptr in
			let asURL = item.item as! URL
			delegate?.diffFilePreview(self, didGetURLDragged: asURL)
		}
	}
}

protocol DiffFilePreviewDelegate: AnyObject {
	func diffFilePreview(_ view: DiffFilePreviewView, didGetURLDragged: URL)
}


================================================
FILE: Samra/UI/Diff/DiffListViewController.swift
================================================
//
//  DiffListViewController.swift
//  Samra
//
//  Created by Serena on 07/03/2023.
// 

import Cocoa
import class SwiftUI.NSHostingController
import AssetCatalogWrapper

class DiffListViewController: NSViewController {
    
    typealias DataSource = NSCollectionViewDiffableDataSource<RenditionDiff.Kind, Rendition>
    var dataSource: DataSource!
    
    let diffs: [RenditionDiff]
    var catalog: CUICatalog
    var fileURL: URL
    
    init(diffs: [RenditionDiff], catalog: CUICatalog, fileURL: URL) {
        self.diffs = diffs
        self.catalog = catalog
        self.fileURL = fileURL
        
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        let collectionView = NSCollectionView()
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.collectionViewLayout = RenditionListViewController.makeLayout(layout: .horizontal)
        
        dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, rendition in
            let cell = collectionView.makeItem(withIdentifier: RenditionCollectionViewItem.reuseIdentifier,
                                               for: indexPath) as! RenditionCollectionViewItem
            cell.configure(rendition: rendition)
            return cell
        }
        
        dataSource.supplementaryViewProvider = { [unowned self] collectionView, kind, indexPath in
            guard kind == NSCollectionView.elementKindSectionHeader else {
                return nil
            }
            
            let header = collectionView.makeSupplementaryView(
                ofKind: kind,
                withIdentifier: RenditionTypeHeaderView.identifier,
                for: indexPath) as! RenditionTypeHeaderView
            let snapshot = dataSource.snapshot()
            let section = snapshot.sectionIdentifiers[indexPath.section]
            header.configure(typeLabelText: section.description, numberOfItems: snapshot.numberOfItems(inSection: section))
            return header
        }
        
        collectionView.delegate = self
        collectionView.allowsMultipleSelection = false
        collectionView.isSelectable = true
        collectionView.register(RenditionCollectionViewItem.self,
                                forItemWithIdentifier: RenditionCollectionViewItem.reuseIdentifier)
        collectionView.register(RenditionTypeHeaderView.self,
                                forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader,
                                withIdentifier: RenditionTypeHeaderView.identifier)
        
        addSnapshot(diffs: diffs)
        let scrollView = NSScrollView()
        scrollView.verticalScroller = nil
        scrollView.documentView = collectionView
        scrollView.hasHorizontalScroller = false
        view = scrollView
        view.frame.size = CGSize(width: 724, height: 676)
    }
    
    func addSnapshot(diffs: [RenditionDiff]) {
        var snapshot = NSDiffableDataSourceSnapshot<RenditionDiff.Kind, Rendition>()
        // i want to cuddle a femboyyyy 🥺
        let justSections = Set(diffs.map(\.kind)) // remove duplicates
        snapshot.appendSections(Array(justSections))
        for diff in diffs {
            snapshot.appendItems([diff.rend], toSection: diff.kind)
        }
        dataSource.apply(snapshot)
    }
    
    override func performTextFinderAction(_ sender: Any?) {
        for item in view.window?.toolbar?.items ?? [] {
            if let search = item.view as? NSSearchField {
                search.becomeFirstResponder()
                break
            }
        }
    }
    
}

extension DiffListViewController: NSCollectionViewDelegate {
    func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
        return [indexPaths.first!]
    }
    
    func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
        guard let first = indexPaths.first,
              let item = dataSource.itemIdentifier(for: first) else {
            return
        }
        
        let view = RenditionInformationView(rendition: item, catalog: catalog, fileURL: fileURL, canEdit: false, canDelete: false, changeCallback: nil) { [unowned self] in
            // done callback
            guard let currentlyBeingPresented = presentedViewControllers?.first else { return }
            dismiss(currentlyBeingPresented)
        }
        let controller = NSHostingController(rootView: view)
        controller.view.frame.size = CGSize(width: 650, height: 500)
        presentAsSheet(controller)
    }
}
extension DiffListViewController: NSSearchFieldDelegate {
    func controlTextDidChange(_ obj: Notification) {
        guard let searchText = (obj.object as? NSSearchField)?.stringValue else { return }
        if searchText.isEmpty {
            addSnapshot(diffs: diffs)
            return
        }
        
        let new = diffs.filter { diff in
            return diff.rend.name.localizedCaseInsensitiveContains(searchText)
        }
        addSnapshot(diffs: new)
    }
}


================================================
FILE: Samra/UI/MenuableCollectionView.swift
================================================
//
//  MenuableCollectionView.swift
//  Samra
//
//  Created by Serena on 02/03/2023.
// 

import Cocoa

class CollectionViewWithMenu: NSCollectionView {
    weak var menuProvider: MenuProvider?
    
    override func menu(for event: NSEvent) -> NSMenu? {
        guard event.type == .rightMouseDown,
              let indexPath = indexPathForItem(
                at: convert(event.locationInWindow, from: nil)
              ) else {
            return nil
        }
        
        return menuProvider?.collectionView(self, menuForItemAt: indexPath)
    }
}

protocol MenuProvider: AnyObject {
    func collectionView(_ collectionView: NSCollectionView, menuForItemAt: IndexPath) -> NSMenu?
}


================================================
FILE: Samra/UI/PastFilesListViewController.swift
================================================
//
//  PastFilesListViewController.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import QuickLookUI
import AssetCatalogWrapper

/// A View Controller showing the past files opened
class PastFilesListViewController: NSViewController {
    var paths: [String] = Preferences.recentlyOpenedFilePaths.reversed()
    var tableView: NSTableView!
    
    override func loadView() {
        tableView = NSTableView()
        tableView.dataSource = self
        tableView.delegate = self
        tableView.headerView = nil
        tableView.doubleAction = #selector(doubeClickedItem)
        
        let col = NSTableColumn(identifier: "Column")
        tableView.addTableColumn(col)
        
        let menu = NSMenu()
        menu.delegate = self
        menu.addItem(withTitle: "Show in Finder", action: #selector(showInFinder), keyEquivalent: "")
        menu.addItem(withTitle: "Remove", action: #selector(deleteItem), keyEquivalent: "")
        menu.autoenablesItems = false
        tableView.menu = menu
        
        let scrollView = NSScrollView()
        scrollView.documentView = tableView
        scrollView.hasHorizontalScroller = false
        view = scrollView
        view.frame.size = CGSize(width: 250, height: 0)
    }
}

extension PastFilesListViewController {
    // Menu item actions
    @objc
    func deleteItem() {
        guard tableView.clickedRow >= 0 else { return }
        paths.remove(at: tableView.clickedRow)
        Preferences.recentlyOpenedFilePaths = paths.reversed()
        tableView.removeRows(at: [tableView.clickedRow], withAnimation: [.slideRight])
    }
    
    @objc
    func showInFinder() {
        guard tableView.clickedRow >= 0 else { return }
        
        let item = paths[tableView.clickedRow]
        NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: item)])
    }
}

extension PastFilesListViewController: NSMenuDelegate {
    func menuNeedsUpdate(_ menu: NSMenu) {
        // if no item is selected, then disable the menu items
        let keepItemsEnabled = tableView.clickedRow >= 0
        
        for item in menu.items {
            item.isEnabled = keepItemsEnabled
        }
    }
    
    override func keyDown(with event: NSEvent) {
        guard tableView.selectedRow != -1 else { return }
        super.keyDown(with: event)
        
        // space, show QuickLook
        if event.characters == " " {
            if let sharedPanel = QLPreviewPanel.shared() {
                let url = URL(fileURLWithPath: paths[tableView.selectedRow])
                let source = QuickLookPreviewSource(fileURL: url)
                sharedPanel.dataSource = source
                sharedPanel.makeKeyAndOrderFront(nil)
            }
        }
        
        // carriage return, open up the item
        if event.characters == "\r" {
            doubeClickedItem()
        }
    }
}

extension PastFilesListViewController: NSTableViewDataSource, NSTableViewDelegate {
    func numberOfRows(in tableView: NSTableView) -> Int {
        return paths.count
    }
    
    func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
        let rowView = NSTableRowView()
        rowView.isEmphasized = false
        return rowView
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let item = URL(fileURLWithPath: paths[row])
        
        let cell = NSTableCellView()
        let imageView = NSImageView(image: NSWorkspace.shared.icon(forFile: item.path))
        
        let text = NSTextField(labelWithString: item.lastPathComponent)
        let subtitleText = NSTextField(labelWithString: item.deletingLastPathComponent().path)
        if #available(macOS 11, *) {
            subtitleText.font = .preferredFont(forTextStyle: .subheadline)
        } else {
            subtitleText.font = .systemFont(ofSize: 11)
        }
        
        subtitleText.lineBreakMode = .byTruncatingMiddle
        
        subtitleText.textColor = .secondaryLabelColor
        
        let titlesStackView = NSStackView(views: [text, subtitleText])
        titlesStackView.alignment = .left
        titlesStackView.distribution = .equalCentering
        titlesStackView.orientation = .vertical
        titlesStackView.spacing = 0
        
        let stackView = NSStackView(views: [imageView, titlesStackView])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        cell.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: cell.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: cell.trailingAnchor),
            stackView.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
        ])
        
        return cell
    }
    
    func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
        return 40
    }
    
    @objc
    func doubeClickedItem() {
        guard tableView.selectedRow != -1 else { return }
        var copy = Preferences.recentlyOpenedFilePaths
        let item = paths[tableView.selectedRow]
        copy.removeAll { $0 == item } // remove if exists
        copy.append(item) // add item to amke it most recent
        Preferences.recentlyOpenedFilePaths = copy
        paths = Array(copy)
        URLHandler.shared.handleURLChosen(urlChosen: URL(fileURLWithPath: item), senderView: view)
    }
}


================================================
FILE: Samra/UI/Rendition/AssetCatalogDetailsView.swift
================================================
//
//  AssetCatalogDetailsView.swift
//  Samra
//
//  Created by Serena on 27/02/2023.
// 

import SwiftUI
import AssetCatalogWrapper

/// Shows information about a given asset catalog.
struct AssetCatalogDetailsView: View {
    var assetStorage: CUICommonAssetStorage
    var doneCallback: () -> Void
    
    var body: some View {
        mainView
            .frame(width: 630, height: 450)
    }
    
    @ViewBuilder
    var mainView: some View {
        List(DetailItemSection.from(assetStorage: assetStorage), id: \.self) { section in
            Section(header: Text(section.sectionHeader)) {
                ForEach(section.items, id: \.self) { item in
                    HStack {
                        Text(item.primaryText)
                            .foregroundColor(Color(NSColor.secondaryLabelColor))
                        Spacer()
                        Text(item.secondaryText)
                            .multilineTextAlignment(.center)
                    }
                    .contextMenu {
                        Button("Copy") {
                            NSPasteboard.general.declareTypes([.string], owner: nil)
                            NSPasteboard.general.setString(item.secondaryText, forType: .string)
                        }
                    }
                }
            }
        }
        
        Spacer()
        Divider()
        Button("Done", action: doneCallback)
            .frame(height: 35, alignment: .center)
    }
}


================================================
FILE: Samra/UI/Rendition/RenditionCollectionViewItem.swift
================================================
//
//  RenditionCollectionViewItem.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

class RenditionCollectionViewItem: NSCollectionViewItem {
    
    static let reuseIdentifier = NSUserInterfaceItemIdentifier("RenditionCollectionViewItem")
    var nameLabel: NSTextField!
    var representationPreview: NSView!
    
    override func loadView() {
        view = NSView()
    }
    
    func configure(rendition: Rendition) {
        nameLabel = NSTextField(labelWithString: rendition.name)
        nameLabel.translatesAutoresizingMaskIntoConstraints = false
        nameLabel.maximumNumberOfLines = 0
        nameLabel.alignment = .center
        nameLabel.lineBreakMode = .byCharWrapping
        
        switch rendition.representation {
        case .color(let cGColor):
            let circleView = NSView()
            circleView.translatesAutoresizingMaskIntoConstraints = false
            
            let layer = CALayer()
            layer.cornerRadius = 20
            layer.cornerCurve = .circular
            layer.backgroundColor = cGColor
            circleView.wantsLayer = false
            circleView.layer = layer
            
            view.addSubview(circleView)
            NSLayoutConstraint.activate([
                circleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                circleView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -10),
                circleView.heightAnchor.constraint(equalToConstant: 40),
                circleView.widthAnchor.constraint(equalToConstant: 40)
            ])
            
            representationPreview = circleView
        case .image(let cGImage):
            let imageView = NSImageView()
            imageView.image = NSImage(cgImage: cGImage, size: cGImage.size)
            imageView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(imageView)
            
            imageView.imageScaling = .scaleProportionallyUpOrDown
            imageView.imageAlignment = .alignCenter
            
//            imageView.centerConstraints(to: view)
            NSLayoutConstraint.activate([
                imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -12.34),
            ])
            
            /*
            if #available(macOS 11, *) {
                NSLayoutConstraint.activate([
                    imageView.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor),
                    imageView.heightAnchor.constraint(equalTo: view.layoutMarginsGuide.heightAnchor)
                ])
            } else {*/
            NSLayoutConstraint.activate([
                imageView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -20),
                imageView.heightAnchor.constraint(equalTo: view.heightAnchor, constant: -34)
            ])
            /*}*/
            
            representationPreview = imageView

        case .rawData(let data):
            var visibleString = "No Preview Available"
            if let string = String(data:data, encoding:.utf8) {
                visibleString = string.count > 500 ? String(string.prefix(500)) : string
            }
            let textField = NSTextField(wrappingLabelWithString:visibleString)
            textField.isBordered = true
            textField.isEditable = false
            textField.isSelectable = false
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.maximumNumberOfLines = 5
            view.addSubview(textField)
            representationPreview = textField
            NSLayoutConstraint.activate([
                textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                textField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -12.34),
                textField.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -20),
                textField.heightAnchor.constraint(equalTo: view.heightAnchor, constant: -34)
            ])

        case nil:
            representationPreview = .init()
        }
        
        view.addSubview(nameLabel)
        NSLayoutConstraint.activate([
            nameLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8),
            nameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
            nameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10)
        ])
        
        let layer = CALayer()
        layer.borderWidth = 1.87
        layer.cornerRadius = 10
        layer.cornerCurve = .continuous
        layer.masksToBounds = true
        layer.borderColor = NSColor.systemGray.cgColor
        view.layer = layer
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        nameLabel.stringValue = ""
        representationPreview.removeFromSuperview()
        representationPreview = nil
    }
}


================================================
FILE: Samra/UI/Rendition/RenditionInformationView.swift
================================================
//
//  RenditionInformationView.swift
//  Samra
//
//  Created by Serena on 21/02/2023.
// 

import SwiftUI
import UniformTypeIdentifiers
import AssetCatalogWrapper

struct RenditionInformationView: View {
    
    @State var showDeleteAlert: Bool = false
    
    var rendition: Rendition
    var catalog: CUICatalog
    var fileURL: URL
    var canEdit: Bool
    var canDelete: Bool
    var changeCallback: ((Change) -> Void)?
    
    var doneButtonCallback: (() -> Void)?
    
    var body: some View {
        switch rendition.representation {
        case .image(let cgImage):
            //            GeometryReader { proxy in
            Image(cgImage, scale: NSScreen.main!.backingScaleFactor, label: Text(""))
                .resizable()
                .aspectRatio(contentMode: .fit)
            //                    .frame(width: proxy.size.width,
            //                           height: proxy.size.height, alignment: .center)
            //            }
            
                .frame(alignment: .center)
                .contextMenu {
                    Button("Copy Image") {
                        NSPasteboard.general.declareTypes([.tiff], owner: nil)
                        NSPasteboard.general.setData(NSImage(cgImage: cgImage, size: cgImage.size).tiffRepresentation,
                                                     forType: .tiff)
                    }
                    
                    Button("Save Image As..") {
                        let panel = NSSavePanel()
                        panel.nameFieldStringValue = rendition.cuiRend.name()
                        
                        if panel.runModal() == .OK, let chosenURL = panel.url {
                            let rep = NSBitmapImageRep(cgImage: cgImage)
                            guard let data = rep.representation(using: .png, properties: [.compressionFactor: 1]) else {
                                NSAlert(title: "Unable to generate png data for image").runModal()
                                return
                            }
                            
                            do {
                                try data.write(to: chosenURL, options: .atomic)
                            } catch {
                                NSAlert(title: "Unable to write image data to \(chosenURL.path)",
                                        message: error.localizedDescription).runModal()
                            }
                        }
                    }
                }
            /*
             .onDrag {
             }
             */
        case .color(let cgColor):
            Circle()
                .fill(Color(NSColor(cgColor: cgColor)!))
                .frame(width: 130, height: 230, alignment: .center)

        case .rawData(let data):
            if let string = String(data:data, encoding:.utf8) {
                Text(String(string.prefix(1024)))
                    .font(.body)
                    .padding(5)
            } else {
                Text("No Preview Available")
                    .font(.title.italic())
                    .padding(30)
            }


        default:
            Text("No Preview Available.")
                .font(.title.italic())
                .frame(width: 130, height: 230)
        }
        
        HStack {
            if rendition.type == .rawData, rendition.cuiRend.responds(to: #selector(CUIThemeRendition.data)) {
                Button("Export Data to...") {
                    guard let data = rendition.cuiRend.data() else { 
                        NSAlert(title: "Failed to export data", message: "Unable to get data (rendition.cuiRend.data() returned null)")
                            .runModal()
                        return
                    }
                    
                    let savePanel = NSSavePanel()
                    savePanel.nameFieldStringValue = rendition.name
                    if savePanel.runModal() == .OK, let url = savePanel.url {
                        do {
                            try data.write(to: url)
                        } catch {
                            NSAlert(title: "Error trying to write data to file \(url)", message: error.localizedDescription)
                                .runModal()
                        }
                    }
                }
            }
            
            Button("Edit") {
                switch rendition.representation {
                case .color(let cgColor):
                    let colorPanel = CallbackableColorPanel()
                    colorPanel.color = NSColor(cgColor: cgColor) ?? colorPanel.color
                    colorPanel.isContinuous = false
                    colorPanel.makeKeyAndOrderFront(nil)
                    
                    colorPanel.callback = { nsColor in
                        do {
                            try catalog.editItem(rendition, fileURL: fileURL, to: .color(nsColor.cgColor))
                            changeCallback?(.edit)
                        } catch {
                            NSAlert(title: "Failed to edit item", message: error.localizedDescription)
                                .runModal()
                        }
                    }
                    
                case .image(_):
                    let panel = NSOpenPanel()
                    panel.canChooseDirectories = false
                    panel.canChooseFiles = true
                    if #available(macOS 11, *) {
                        panel.allowedContentTypes = [.image]
                    } else {
                        panel.allowedFileTypes = [kUTTypeImage as String]
                    }
                    
                    if panel.runModal() == .OK, let chosenURL = panel.url {
                        guard let cgImage = NSImage(contentsOf: chosenURL)?.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
                            NSAlert(title: "Failed to edit item", message: "Unable to get image representation of the file selected").runModal()
                            return
                        }
                        
                        do {
                            try catalog.editItem(rendition, fileURL: fileURL, to: .image(cgImage))
                            changeCallback?(.edit)
                        } catch {
                            NSAlert(title: "Failed to edit item", message: error.localizedDescription)
                                .runModal()
                        }
                    }
                default:
                    break // never supposed to get here
                }
            }
            .disabled(!canEdit || !rendition.type.isEditable)
            
            if let doneButtonCallback {
                Button("Done", action: doneButtonCallback)
            }
            
            Button {
                showDeleteAlert = true
            } label: {
                Text("Delete")
                    .foregroundColor(.red)
            }
            .disabled(!canDelete)
        }
        
        mainView
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .alert(isPresented: $showDeleteAlert) {
                let deleteButton: Alert.Button = .destructive(Text("Delete")) {
                    do {
                        try catalog.removeItem(rendition, fileURL: fileURL)
                        changeCallback?(.delete)
                    } catch {
                        NSAlert(title: "Error encountered while trying to delete \(rendition.name)",
                                message: error.localizedDescription).runModal()
                    }
                }
                
                return Alert(title: Text("Are you sure you want to delete \(rendition.name)?"),
                             message: Text("This action cannot be undone"), primaryButton: deleteButton, secondaryButton: .cancel())
            }
    }
    
    @ViewBuilder
    var mainView: some View {
        List(DetailItemSection.from(rendition: rendition), id: \.self) { section in
            Section(header: Text(section.sectionHeader)) {
                ForEach(section.items, id: \.self) { item in
                    HStack {
                        Text(item.primaryText)
                        Spacer()
                        Text(item.secondaryText)
                            .multilineTextAlignment(.trailing)
                    }
                    .contextMenu {
                        Button("Copy") {
                            NSPasteboard.general.declareTypes([.string], owner: nil)
                            NSPasteboard.general.setString(item.secondaryText, forType: .string)
                        }
                    }
                }
            }
        }
    }
    
    enum Change {
        /// item was deleted
        case delete
        /// item was edited
        case edit
    }
}

class CallbackableColorPanel: NSColorPanel, NSWindowDelegate {
    var callback: ((NSColor) -> Void)? = nil
    
    override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
        super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
        self.delegate = self
    }
    
    func windowWillClose(_ notification: Notification) {
        callback?(color)
    }
}


================================================
FILE: Samra/UI/Rendition/RenditionListViewController.swift
================================================
//
//  RenditionListViewController.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AppKitPrivates
import class SwiftUI.NSHostingController
import AssetCatalogWrapper
import SVGWrapper

/// A View Controller displaying all the renditions of a given Asset Catalog.
class RenditionListViewController: NSViewController {
    
    static let titleHeaderIdentifier = "Identifier"
    
    typealias DataSource = NSCollectionViewDiffableDataSource<RenditionType, Rendition>
    var dataSource: DataSource!
    var collectionView: CollectionViewWithMenu!
    lazy var allItemsSnapshot = addSnapshot(collectionToAdd: collection)
    
    var itemToDeleteIndexPath: IndexPath? = nil
    
    var catalog: CUICatalog
    var collection: RenditionCollection
    let fileURL: URL
    
    private var scrollObserver: NSObjectProtocol?
    
    init(catalog: CUICatalog, collection: RenditionCollection, fileURL: URL) {
        self.catalog = catalog
        self.collection = collection
        self.fileURL = fileURL
        super.init(nibName: nil, bundle: nil)
    }
    
    var splitViewParent: CollapseNotifierSplitViewController? {
        parent as? CollapseNotifierSplitViewController
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        collectionView = CollectionViewWithMenu()
        
        dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, rendition in
            let cell = collectionView.makeItem(withIdentifier: RenditionCollectionViewItem.reuseIdentifier,
                                               for: indexPath) as! RenditionCollectionViewItem
            cell.configure(rendition: rendition)
            return cell
        }
        
#warning("Add footers for explanations for multisizeImageSet")
        dataSource.supplementaryViewProvider = { [unowned self] collectionView, kind, indexPath in
            guard kind == NSCollectionView.elementKindSectionHeader else {
                return nil
            }
            
            let header = collectionView.makeSupplementaryView(
                ofKind: kind,
                withIdentifier: RenditionTypeHeaderView.identifier,
                for: indexPath) as! RenditionTypeHeaderView
            let snapshot = dataSource.snapshot()
            let section = snapshot.sectionIdentifiers[indexPath.section]
            header.configure(typeLabelText: section.description, numberOfItems: snapshot.numberOfItems(inSection: section))
            return header
        }
        
        collectionView.allowsMultipleSelection = false
        collectionView.isSelectable = true
        collectionView.delegate = self
        collectionView.menuProvider = self
        collectionView.collectionViewLayout = Self.makeLayout(layout: .horizontal)
        collectionView.identifier = "HorizLayout"
        
        collectionView.register(RenditionCollectionViewItem.self,
                                forItemWithIdentifier: RenditionCollectionViewItem.reuseIdentifier)
        collectionView.register(RenditionTypeHeaderView.self,
                                forSupplementaryViewOfKind: NSCollectionView.elementKindSectionHeader,
                                withIdentifier: RenditionTypeHeaderView.identifier)
        addSnapshot(collectionToAdd: collection)
        
        splitViewParent?.handler = { [unowned self] item, didCollapse, _ in
            guard item.viewController.identifier == "RenditionInfo" else { return }
            collectionView.collectionViewLayout = Self.makeLayout(
                layout: didCollapse ? .horizontal : .vertical
            )
            
            collectionView.identifier = didCollapse ? "HorizLayout" : "VerticalLayout"
        }
        
        let scrollView = NSScrollView()
        scrollView.verticalScroller = nil
        scrollView.documentView = collectionView
        scrollView.hasHorizontalScroller = false
        
        view = scrollView
        view.frame.size = CGSize(width: 724, height: 676)
        
        let observer = NotificationCenter.default.addObserver(forName: NSScrollView.didEndLiveScrollNotification, object: scrollView, queue: nil) { [weak self] _ in
            guard let self = self else { return }
            let vc = self.splitViewParent?.splitViewItems[0].viewController as? TypesListViewController
            guard let vc, let currentSection = self.collectionView.indexPathsForVisibleItems().first?.section else {
                return
            }
            
            vc.ignoreChanges = true
            vc.tableView.deselectRow(vc.tableView.selectedRow)
            vc.tableView.selectRowIndexes([currentSection], byExtendingSelection: true)
            vc.ignoreChanges = false
        }
        
        self.scrollObserver = observer
        
        collectionView.registerForDraggedTypes(NSImage.imageTypes.map { .init($0) })
        collectionView.setDraggingSourceOperationMask(.every, forLocal: true)
        collectionView.setDraggingSourceOperationMask(.every, forLocal: false)
    }
    
    func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
        return true
    }
    
    func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
//        return dataSource.itemIdentifier(for: indexPath)?.name as? NSString
        switch dataSource.itemIdentifier(for: indexPath)?.representation {
        case .image(let cgImage):
            return NSImage(cgImage: cgImage, size: cgImage.size)
        default:
            return nil
        }
    }
    
    func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {}
    
    @discardableResult
    func addSnapshot(collectionToAdd: RenditionCollection) -> NSDiffableDataSourceSnapshot<RenditionType, Rendition> {
        var snapshot = NSDiffableDataSourceSnapshot<RenditionType, Rendition>()
        for item in collectionToAdd {
            snapshot.appendSections([item.type])
            snapshot.appendItems(item.renditions, toSection: item.type)
        }
        
        dataSource.apply(snapshot)
        return snapshot
    }
    
    @discardableResult
    func refreshAssetCatalog() -> Bool {
        do {
            let (newCatalog, newCollection) = try AssetCatalogWrapper.shared.renditions(forCarArchive: fileURL)
            self.catalog = newCatalog
            self.collection = newCollection
            addSnapshot(collectionToAdd: collection)
            return true
        } catch {
            NSAlert(title: "Failed to refresh Asset Catalog", message: error.localizedDescription)
                .runModal()
            return false
        }
    }
    
    deinit {
        if let observer = scrollObserver {
            NotificationCenter.default.removeObserver(observer)
        }
        print("And I'm tripping and falling..")
    }
}

extension RenditionListViewController {
    static func makeLayout(layout: LayoutMode) -> NSCollectionViewCompositionalLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                              heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                               heightDimension: .absolute(115))
        
        let group: NSCollectionLayoutGroup
        switch layout {
        case .vertical:
            group = .vertical(layoutSize: groupSize, subitems: [item]/*, count: 3*/)
        case .horizontal:
            group = .horizontal(layoutSize: groupSize, subitem: item, count: 3)
        }
        
        let spacing = CGFloat(15)
        group.interItemSpacing = .fixed(spacing)
        
        let titleHeaderSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .absolute(50)
        )
        
        let titleSupplementary = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: titleHeaderSize,
            elementKind: NSCollectionView.elementKindSectionHeader,
            alignment: .topTrailing
        )
        
        
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = spacing
        section.contentInsets = NSDirectionalEdgeInsets(top: 20,
                                                        leading: spacing,
                                                        bottom: 20,
                                                        trailing: spacing)
        section.boundarySupplementaryItems = [titleSupplementary]
        //section.orthogonalScrollingBehavior = .continuous
        return NSCollectionViewCompositionalLayout(section: section)
    }
}

extension RenditionListViewController: MenuProvider {
    
    static private func _promptToSaveImage(cgImage: CGImage, formatType: NSBitmapImageRep.FileType, defaultFileName: String, displayFormat: String) {
        let savePanel = NSSavePanel()
        savePanel.nameFieldStringValue = defaultFileName
        guard savePanel.runModal() == .OK, let urlToSaveTo = savePanel.url else { return }
        
        guard let data = NSBitmapImageRep(cgImage: cgImage).representation(using: formatType, properties: [.compressionFactor: 1]) else {
            NSAlert(title: "Failed to save Image as \(displayFormat)", message: "NSBitmapImageRep representation returned nil.").runModal()
            return
        }
        
        do {
            try data.write(to: urlToSaveTo)
        } catch {
            NSAlert(title: "Failed to save Image as \(displayFormat)", message: error.localizedDescription).runModal()
        }
    }
    
    func collectionView(_ collectionView: NSCollectionView, menuForItemAt indexPath: IndexPath) -> NSMenu? {
        guard let item = dataSource.itemIdentifier(for: indexPath) else { return nil }
        let copyName = ClosureMenuItem(title: "Copy Name") {
            NSPasteboard.general.declareTypes([.string], owner: nil)
            NSPasteboard.general.setString(item.name, forType: .string)
        }
        
        var items: [NSMenuItem] = [copyName]
        
        switch item.representation {
        case .image(let cgImage):
            let copyImage = ClosureMenuItem(title: "Copy Image") {
                NSPasteboard.general.declareTypes([.tiff], owner: nil)
                NSPasteboard.general.setData(NSImage(cgImage: cgImage, size: cgImage.size).tiffRepresentation, forType: .tiff)
            }
            items.append(copyImage)
            
            var saveImageAsItems = [
                ClosureMenuItem(title: "PNG") {
                    Self._promptToSaveImage(cgImage: cgImage, formatType: .png, defaultFileName: "image.png", displayFormat: "PNG")
                },
                
                ClosureMenuItem(title: "JPEG") {
                    Self._promptToSaveImage(cgImage: cgImage, formatType: .jpeg, defaultFileName: "image.jpeg", displayFormat: "JPEG")
                }
            ]
            
            if item.type == .svg, let svgDoc = item.cuiRend.svgDocument() {
                let asSVG = ClosureMenuItem(title: "SVG") {
                    let savePanel = NSSavePanel()
                    savePanel.nameFieldStringValue = "image.svg"
                    guard savePanel.runModal() == .OK, let urlToSaveTo = savePanel.url else { return }
                    CGSVGDocumentWriteToURL(svgDoc, urlToSaveTo as CFURL, nil)
                }
                
                saveImageAsItems.insert(asSVG, at: 0)
            }
            
            let saveImageAs = NSMenuItem(submenuTitle: "Save Image As...", items: saveImageAsItems)
            items.insert(saveImageAs, at: 0)
            items.insert(.separator(), at: 1)
        default:
            break
        }
        
        let deleteItem = ClosureMenuItem(title: "Delete") { [unowned self] in
            let alert = NSAlert(title: "Are you sure you want to delete \(item.name)?",
                                message: "This action cannot be undone")
            let deleteButton = alert.addButton(withTitle: "Delete")
            deleteButton.target = self
            deleteButton.action = #selector(deleteItem(sender:))
            
            if #available(macOS 11, *) {
                deleteButton.hasDestructiveAction = true
            }
            
            itemToDeleteIndexPath = indexPath
            alert.addButton(withTitle: "Cancel")
            alert.runModal()
        }
        
        items.append(deleteItem)
        return NSMenu(items: items)
    }
    
    @objc
    func deleteItem(sender: NSButton) {
        guard let itemToDeleteIndexPath,
                let item = dataSource.itemIdentifier(for: itemToDeleteIndexPath) else {
            return
        }
        
        do {
            try catalog.removeItem(item, fileURL: fileURL)
            NSApplication.shared.abortModal()
            refreshAssetCatalog()
        } catch {
            NSAlert(title: "Failed to remove \(item.name)", message: error.localizedDescription)
                .runModal()
            return
        }
    }
}

extension RenditionListViewController {
    @objc
    func infoButtonClicked(sender: NSButton) {
        guard let ass = CUICommonAssetStorage(path: fileURL.path, forWriting: false) else {
            NSAlert(
                title: "Failed to display details of Assets.car file",
                message: "Failed to init CUICommonAssetStorage for \(fileURL.path)"
            )
            .runModal()
            return
        }
        
        /*
        let popover = NSPopover()
        popover.behavior = .transient
        popover.contentSize = NSSize(width: 400, height: 200)
         */
        
        let detailsView = AssetCatalogDetailsView(assetStorage: ass) { [unowned self] in
            // Callback for 'Done' button
            guard let currentlyPresenting = presentedViewControllers?.first else { return }
            dismiss(currentlyPresenting)
        }
        
        presentAsSheet(NSHostingController(rootView: detailsView))
    }
    
    @objc
    func exportCatalog() {
        let panel = NSOpenPanel()
        panel.title = "Directory to export to"
        panel.canChooseDirectories = true
        panel.canCreateDirectories = true
        panel.canChooseFiles = false
        
        guard panel.runModal() == .OK, let destinationURL = panel.url else { return }
        
        do {
            try AssetCatalogWrapper.shared.extract(collection: collection, to: destinationURL)
            NSWorkspace.shared.activateFileViewerSelecting([destinationURL])
        } catch {
            NSAlert(title: "Failed to export (some) items", message: error.localizedDescription)
                .runModal()
        }
    }
}

extension RenditionListViewController {
    // MARK: - Layout
    enum LayoutMode {
        case vertical
        case horizontal
    }
}

extension RenditionListViewController: NSCollectionViewDelegate {
    func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
        return [indexPaths.first!]
    }
    
    func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
        guard let firstIndexPath = indexPaths.first,
              let item = dataSource.itemIdentifier(for: firstIndexPath),
              let parent = splitViewParent else {
            return
        }
        
        let layer = collectionView.item(at: firstIndexPath)?.view.layer
        layer?.borderColor = NSColor.controlAccentColor.cgColor
        layer?.borderWidth = 3.5 // enlargen border width when selected
        
        // if we already have an existing info vc then remove it
        if parent.splitViewItems.count == 3 {
            parent.removeSplitViewItem(parent.splitViewItems[2])
        }
        
        let view = RenditionInformationView(rendition: item, catalog: catalog, fileURL: fileURL, canEdit: true, canDelete: true) { [unowned self] change in
            switch change {
            case .delete:
                refreshAssetCatalog()
            case .edit:
                if refreshAssetCatalog() {
                    self.collectionView(collectionView, didSelectItemsAt: indexPaths)
                }
            }
        }
        
        let renditionVC = NSHostingController(rootView: view)
        renditionVC.identifier = "RenditionInfo"
        let splitViewItem = NSSplitViewItem(contentListWithViewController: renditionVC)
        splitViewItem.minimumThickness = 400
        splitViewItem.canCollapse = true
        splitViewItem.maximumThickness = 600
        splitViewItem.automaticMaximumThickness = 600
        splitViewItem.preferredThicknessFraction = 2
        
        parent.addSplitViewItem(splitViewItem)
        
        if collectionView.identifier == "HorizLayout" {
            collectionView.collectionViewLayout = Self.makeLayout(layout: .vertical)
            collectionView.identifier = "VerticalLayout"
            // scroll back here because switching between layouts may cause the item to not be visible
            // in the new layout
            collectionView.scrollToItems(at: indexPaths,
                                         scrollPosition: [.centeredVertically, .centeredHorizontally])
        }
    }
    
    func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set<IndexPath>) {
        for indexPath in indexPaths {
            let layer = collectionView.item(at: indexPath)?.view.layer
            layer?.borderColor = NSColor.systemGray.cgColor
            // item is no longer in focus, set it's border width to the standard amount 
            layer?.borderWidth = 1.87
        }
    }
    
    override func performTextFinderAction(_ sender: Any?) {
        for item in view.window?.toolbar?.items ?? [] {
            if let search = item.view as? NSSearchField {
                search.becomeFirstResponder()
                break
            }
        }
    }
    
    /*
    func collectionView(_ collectionView: NSCollectionView,
                        canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
        return indexPaths.allSatisfy { [unowned self] indxPath in
            switch dataSource.itemIdentifier(for: indxPath)?.type {
            case .image, .icon:
                return true
            default:
                return false
            }
        }
    }
    
    func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) {
        print(#function)
    }
     */
}

extension RenditionListViewController: NSSearchFieldDelegate {
    
    /// Set the types in the sidebar,
    /// if nil, then this will default to all the types
    func setSidebarTypes(_ types: [RenditionType]?) {
        if let sidebar = splitViewParent?.splitViewItems[0].viewController as? TypesListViewController {
            sidebar.types = types ?? sidebar.allTypes
            sidebar.tableView.reloadData()
        }
    }
    
    func controlTextDidChange(_ obj: Notification) {
        guard let searchText = (obj.object as? NSSearchField)?.stringValue else { return }
        
        if searchText.isEmpty {
            dataSource.apply(allItemsSnapshot)
            setSidebarTypes(nil)
            return
        }
        
        var newSidebarTypes: [RenditionType] = []
        let newCollection: RenditionCollection = collection.compactMap { type, renditions in
            // query by the renditions that have the search text in their name
            let newRends = renditions.filter { rend in
                return rend.name.localizedCaseInsensitiveContains(searchText)
            }
            
            // Don't include the section if no items match the query
            if newRends.isEmpty {
                return nil
            }
            
            // the section has renditions that match our description, add it to the sidebar
            newSidebarTypes.append(type)
            
            return (type, newRends)
        }
        
        addSnapshot(collectionToAdd: newCollection)
        
        setSidebarTypes(newSidebarTypes)
        
    }
}


================================================
FILE: Samra/UI/Rendition/RenditionTypeHeaderView.swift
================================================
//
//  RenditionTypeHeaderView.swift
//  Samra
//
//  Created by Serena on 19/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

class RenditionTypeHeaderView: NSView, NSCollectionViewElement {
    
    static let identifier = NSUserInterfaceItemIdentifier("RenditionTypeHeaderView")
    
    var typeLabel: NSTextField!
    var amountOfItemsLabel: NSTextField!
    
    func configure(typeLabelText: String, numberOfItems: Int) {
        typeLabel = NSTextField(labelWithString: typeLabelText)
        typeLabel.translatesAutoresizingMaskIntoConstraints = false
        
        addSubview(typeLabel)
        
        amountOfItemsLabel = NSTextField(labelWithString: "\(numberOfItems) Items")
        amountOfItemsLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(amountOfItemsLabel)
        
        if #available(macOS 11, *) {
            typeLabel.font = .preferredFont(forTextStyle: .largeTitle)
            amountOfItemsLabel.font = .preferredFont(forTextStyle: .caption1)
        } else {
            amountOfItemsLabel.font = .systemFont(ofSize: 10)
            typeLabel.font = .systemFont(ofSize: 26)
        }
        
        NSLayoutConstraint.activate([
            typeLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            typeLabel.topAnchor.constraint(equalTo: topAnchor),
            
            amountOfItemsLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
            amountOfItemsLabel.centerXAnchor.constraint(equalTo: typeLabel.centerXAnchor, constant: -30)
        ])
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        typeLabel.removeFromSuperview()
        typeLabel = nil
        
        amountOfItemsLabel.removeFromSuperview()
        amountOfItemsLabel = nil
    }
}


================================================
FILE: Samra/UI/Rendition/TypesListViewController.swift
================================================
//
//  TypesListViewController.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

class TypesListViewController: NSViewController {
    typealias SectionClickedHandler = (RenditionType) -> Void
    
    let changeHandler: SectionClickedHandler
    
    let allTypes: [RenditionType]
    // the types shown in the UI, if there is a search session, this may not be equal to allTypes
    // depending on if the search result's types are less than allTypes
    var types: [RenditionType]
    
    // for when manually doing select and deselectRow
    var ignoreChanges: Bool = false
    
    var tableView: NSTableView!
    
    init(types: [RenditionType], changeHandler: @escaping SectionClickedHandler) {
        self.types = types
        self.allTypes = types
        self.changeHandler = changeHandler
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        tableView = NSTableView()
        tableView.dataSource = self
        tableView.delegate = self
        tableView.target = self
        tableView.headerView = nil
        
        let col = NSTableColumn(identifier: "Column")
        tableView.addTableColumn(col)
        
        let scrollView = NSScrollView()
        scrollView.documentView = tableView
        scrollView.hasHorizontalScroller = false
        view = scrollView
        view.frame.size = CGSize(width: 200, height: 0)
        
        setupMenuBarItems()
    }
    
    override func viewDidDisappear() {
        super.viewDidDisappear()
        
        // disable section items
        for item in NSApplication.shared.mainMenu?.items ?? [] {
            guard item.title == "Sections", let submenu = item.submenu else {
                continue
            }
            
            for item in submenu.items {
                item.isEnabled = false
                item.keyEquivalent = ""
            }
        }
    }
    
    
    override func performTextFinderAction(_ sender: Any?) {
        for item in view.window?.toolbar?.items ?? [] {
            if let search = item.view as? NSSearchField {
                search.becomeFirstResponder()
                break
            }
        }
    }
    
    // Map this function to the main list vc exportCatalog function
    @objc
    func exportCatalog() {
        for item in (parent as? NSSplitViewController)?.splitViewItems ?? [] {
            if let list = item.viewController as? RenditionListViewController {
                list.exportCatalog()
                break
            }
        }
    }
    
    func setupMenuBarItems() {
        for item in NSApplication.shared.mainMenu?.items ?? [] {
            // we just want to modify the "Sections" section
            guard item.title == "Sections", let submenu = item.submenu else {
                continue
            }
            
            submenu.autoenablesItems = false
            submenu.removeAllItems()
            
            // add only the types that we have
            // to the section
            for (index, item) in allTypes.enumerated() {
                // make the keyEquivalent index + 1
                // so that it's less confusing to the user,
                // ie, if `Color` was the first section, this would make it cmd 1
                // rather than cmd 0
                let item =  NSMenuItem(title: item.description,
                                       action: #selector(goToSection),
                                       keyEquivalent: (index + 1).description, tag: index)
                submenu.addItem(item)
            }
        }
    }
    
    @objc
    func goToSection(menuItemSender: NSMenuItem) {
        changeSection(to: menuItemSender.tag)
    }
}

extension TypesListViewController: NSTableViewDataSource, NSTableViewDelegate {
    func numberOfRows(in tableView: NSTableView) -> Int {
        return types.count
    }
    
    func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
        return 30
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let type = types[row]
        let cell = NSTableCellView()
        let imageIconView = NSImageView()
        imageIconView.image = NSImage(systemName: type.displayIconName)
        
        let stackView = NSStackView(views: [imageIconView,
                                            NSTextField(labelWithString: type.description)])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        cell.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: cell.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: cell.trailingAnchor),
            stackView.centerYAnchor.constraint(equalTo: cell.centerYAnchor)
        ])
        return cell
    }
    
    func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
        return true
    }
    
    func changeSection(to index: Int) {
        if !ignoreChanges {
            changeHandler(types[index])
        }
    }
    
    
    func tableViewSelectionDidChange(_ notification: Notification) {
        changeSection(to: tableView.selectedRow)
    }
}

extension RenditionType {
    var displayIconName: String {
        switch self {
        case .image, .svg:
            return "photo"
        case .icon:
            return "app"
        case .imageSet:
            if #available(macOS 13, iOS 16, *) {
                return "photo.stack"
            }
            
            return "rectangle.stack"
        case .multiSizeImageSet:
            return "cube.box"
        case .pdf:
            return "doc.richtext"
        case .color:
            return "paintbrush"
        case .rawData:
            return "text.quote"
        case .unknown:
            return "questionmark.app"
        }
    }
}


================================================
FILE: Samra/UI/WelcomeScreenOption.swift
================================================
//
//  WelcomeScreenOption.swift
//  Samra
//
//  Created by Serena on 21/02/2023.
// 

import Cocoa

/// Represents an option on the main menu screen,
/// similar to that of Xcode's.
class WelcomeScreenOption: NSView {
    var actionClosure: () -> Void
    
    init(primaryText: String, secondaryText: String, image: NSImage?, action: @escaping () -> Void) {
        self.actionClosure = action
        
        super.init(frame: .zero)
        let finalImage: NSImage?
        if #available(macOS 11, *) {
            finalImage = image?
                .withSymbolConfiguration(.init(pointSize: 30, weight: .regular))
        } else {
            finalImage = image
        }
        
        let finalImageView = NSImageView()
        finalImageView.image = finalImage
        finalImageView.contentTintColor = .controlAccentColor
        
        let primaryTextLabel = NSTextField(labelWithString: primaryText)
        let secondaryTextLabel = NSTextField(labelWithString: secondaryText)
        secondaryTextLabel.textColor = .secondaryLabelColor
        
        if #available(macOS 11, *) {
            primaryTextLabel.font = .preferredFont(forTextStyle: .headline)
            secondaryTextLabel.font = .preferredFont(forTextStyle: .subheadline)
        } else {
            primaryTextLabel.font = .boldSystemFont(ofSize: 13)
            secondaryTextLabel.font = .systemFont(ofSize: 11)
        }
        
        let textLabelsStackView = NSStackView(views: [primaryTextLabel, secondaryTextLabel])
        textLabelsStackView.alignment = .left
        textLabelsStackView.spacing = 0.4
        textLabelsStackView.orientation = .vertical
        
        let completeStackView = NSStackView(views: [finalImageView, textLabelsStackView])
        completeStackView.addGestureRecognizer(NSClickGestureRecognizer(target: self, action: #selector(performAction)))
        completeStackView.orientation = .horizontal
        completeStackView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(completeStackView)
        completeStackView.constraintCompletely(to: self)
    }
    
    @objc func performAction() {
        actionClosure()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


================================================
FILE: Samra/UI/WelcomeViewController.swift
================================================
//
//  WelcomeViewController.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

class WelcomeViewController: NSViewController {
    
    // override so that it doesn't try to load a fucking nib
    override func loadView() {
        view = NSView()
        view.frame.size = CGSize(width: 570, height: 460)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let appIcon = NSImageView(image: NSApplication.shared.applicationIconImage)
        let welcomeTextLabel = NSTextField(labelWithString: "Welcome to Samra")
        welcomeTextLabel.font = .systemFont(ofSize: 30)
        
        let subtitleLabel = NSTextField(labelWithString: "Created by Antoine (formerly known as Serena)")
        subtitleLabel.textColor = .secondaryLabelColor
        
        let stackView = NSStackView(views: [appIcon, welcomeTextLabel, subtitleLabel])
        stackView.orientation = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.spacing = 0.3
        view.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40)
        ])
        
        let openFolderOption = WelcomeScreenOption(
            primaryText: "Open Assets File",
            secondaryText: "Browse and Edit Assets Files on your Mac",
            image: NSImage(systemName: "folder")) { [unowned self] in
                URLHandler.shared.presentArchiveChooserPanel(insertToRecentItems: true, senderView: view)
            }
        
        openFolderOption.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(openFolderOption)
        
        NSLayoutConstraint.activate([
            openFolderOption.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 40),
            openFolderOption.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
        
        let diffCatalogsOption = WelcomeScreenOption(primaryText: "Diff Catalogs", secondaryText: "Diff 2 different Asset Catalogs on your Mac", image: NSImage(systemName: "doc.plaintext")) {
            WindowController(kind: .diffSelection).showWindow(nil)
        }
        
        diffCatalogsOption.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(diffCatalogsOption)
        
        NSLayoutConstraint.activate([
            diffCatalogsOption.topAnchor.constraint(equalTo: openFolderOption.bottomAnchor, constant: 20),
            diffCatalogsOption.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
        
        let closeWindowButton = NSButton()
        closeWindowButton.image = NSImage(systemName: "xmark")
        closeWindowButton.action = #selector(closeWindowButtonClicked)
        closeWindowButton.target = self
        
        closeWindowButton.showsBorderOnlyWhileMouseInside = true
        closeWindowButton.bezelStyle = .roundRect
        closeWindowButton.bezelColor = .gray
        
        closeWindowButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(closeWindowButton)
        
        NSLayoutConstraint.activate([
            closeWindowButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
            closeWindowButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8)
        ])
        
        let showThisWindowButton = NSButton(title: "Show this window when Samra launches",
                                            target: self,
                                            action: #selector(showThisWindowOnLaunchButtonClicked(sender:)))
        showThisWindowButton.setButtonType(.switch)
        showThisWindowButton.state = Preferences.showWelcomeVCOnLaunch ? .on : .off
        showThisWindowButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(showThisWindowButton)
        
        NSLayoutConstraint.activate([
            showThisWindowButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
            showThisWindowButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
        
        // Register for when cursor is on Window
        // if it's not, hide the closeWindowButton and the showThisWindowButton
        // otherwise show it
        NSEvent.addLocalMonitorForEvents(matching: [.mouseEntered, .mouseExited]) { event in
            let newAlphaValue: CGFloat = (event.type == .mouseExited) ? 0 : 1
            
            NSAnimationContext.runAnimationGroup { context in
                context.duration = 0.2
                context.allowsImplicitAnimation = true
                
                closeWindowButton.animator().alphaValue = newAlphaValue
                showThisWindowButton.animator().alphaValue = newAlphaValue
            }
            
            return event
        }
    }
    
    @objc
    func closeWindowButtonClicked() {
        view.window?.close()
    }
    
    @objc
    func showThisWindowOnLaunchButtonClicked(sender: NSButton) {
        var newValue = Preferences.showWelcomeVCOnLaunch
        newValue.toggle()
        Preferences.showWelcomeVCOnLaunch = newValue
    }
    
    deinit {
        print("Magna Carta.. Holy Grail.")
        print("deinit called for WelcomeViewController")
    }
    
    override func viewDidAppear() {
        super.viewDidAppear()
        
        guard let window = view.window else { return }
        window.backgroundColor = .standardWindowBackgroundColor
        window.standardWindowButton(.closeButton)?.isHidden = true
        window.standardWindowButton(.zoomButton)?.isHidden = true
        window.standardWindowButton(.miniaturizeButton)?.isHidden = true
    }
}


================================================
FILE: Samra/WindowController.swift
================================================
//
//  WindowController.swift
//  Samra
//
//  Created by Serena on 18/02/2023.
// 

import Cocoa
import AssetCatalogWrapper

class WindowController: NSWindowController, NSWindowDelegate {
    
    enum Kind {
        /// The 'Welcome to Samra' screen
        case welcome
        
        /// The 'About Samra' Panel.
        case aboutPanel
        
        /// A View Controller to select 2 AssetCatalogs to diff between them
        case diffSelection
        
        /// A View Controller to show the diff between 2 asset catalogs
        case diffShow([RenditionDiff], CUICatalog, URL)
        
        /// Show a View Controller of a rendition collection
        case assetCatalog(AssetCatalogInput)
    }
    
    convenience init(kind: Kind) {
        let viewController: NSViewController
        
        switch kind {
        case .welcome:
            let splitViewController = CollapseNotifierSplitViewController()
            let welcomeViewController = WelcomeViewController()
            let list = PastFilesListViewController()
            splitViewController.addSplitViewItem(NSSplitViewItem(viewController: welcomeViewController))
            splitViewController.addSplitViewItem(NSSplitViewItem(sidebarWithViewController: list))
            viewController = splitViewController
        case .assetCatalog(let input):
            let splitViewController = CollapseNotifierSplitViewController()
            let renditionVC = RenditionListViewController(catalog: input.catalog, collection: input.collection,
                                                          fileURL: input.fileURL)
            let typesSidebar = TypesListViewController(types: input.collection.map(\.type)) { type in
                if let index = renditionVC.dataSource.snapshot().indexOfSection(type) {
                    renditionVC.collectionView.scrollToItems(at: [IndexPath(item: 0, section: index)], scrollPosition: .top)
                }
            }
            
            splitViewController.addSplitViewItem(NSSplitViewItem(sidebarWithViewController: typesSidebar))
            splitViewController.addSplitViewItem(NSSplitViewItem(viewController: renditionVC))
            viewController = splitViewController
        case .aboutPanel:
            viewController = AboutViewController()
        case .diffSelection:
            viewController = AssetCatalogDiffSelectionViewController()
        case .diffShow(let diffs, let catalog, let fileURL):
            viewController = DiffListViewController(diffs: diffs, catalog: catalog, fileURL: fileURL)
        }
        
        let window = NSWindow(contentViewController: viewController)
        window.styleMask.insert(.fullSizeContentView)
        self.init(window: window)
        
        switch kind {
        case .assetCatalog(let input):
            let toolbar = NSToolbar()
            toolbar.delegate = self
            window.toolbar = toolbar
            toolbar.insertItem(withItemIdentifier: .flexibleSpace, at: 0)
            toolbar.insertItem(withItemIdentifier: .searchBar, at: 1)
            toolbar.insertItem(withItemIdentifier: .init("infoButton"), at: 2)
            window.toolbar?.centeredItemIdentifier = .searchBar
            window.animationBehavior = .documentWindow
            window.delegate = self
            window.title = input.fileURL.lastPathComponent
            if #available(macOS 11, *) {
                window.subtitle = input.fileURL.deletingLastPathComponent().lastPathComponent
            }
        case .welcome:
            window.makeTitleBarTransparentAndUnresizable()
            window.animationBehavior = .utilityWindow
            window.title = "Samra"
        case .aboutPanel:
            window.makeTitleBarTransparentAndUnresizable()
            window.title = "Samra"
        case .diffSelection:
            window.title = "Diff"
        case .diffShow(_, _, _):
            let toolbar = NSToolbar()
            toolbar.delegate = self
            window.toolbar = toolbar
            window.title = "Diff"
            toolbar.insertItem(withItemIdentifier: .flexibleSpace, at: 0)
            toolbar.insertItem(withItemIdentifier: .searchBar, at: 1)
            window.animationBehavior = .documentWindow
            window.delegate = self
        }
    }
}

extension WindowController: NSToolbarDelegate {
    func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return []
    }
    
    func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return []
    }
    
    func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
        switch itemIdentifier {
        case .searchBar:
            let rendVC: NSSearchFieldDelegate?
            
            if let splitVC = contentViewController as? NSSplitViewController {
                rendVC = splitVC.splitViewItems[1].viewController as? NSSearchFieldDelegate
            } else {
                rendVC = contentViewController as? NSSearchFieldDelegate
            }
            
            /*
            if #available(macOS 11, *) {
                let item = NSSearchToolbarItem(itemIdentifier: .searchBar)
                item.searchField.delegate = rendVC
                return item
            }
             */
            
            let item = NSToolbarItem(itemIdentifier: .searchBar)
            let searchField = NSSearchField()
            searchField.delegate = rendVC
            item.view = searchField
            return item
        case .init("infoButton"):
            let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
            let button = NSButton()
            if #available(macOS 11, *) {
                button.image = NSImage(systemSymbolName: "info.circle", accessibilityDescription: nil)?
                    .withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 18, weight: .regular))
            } else {
                button.title = "Info"
            }
            button.action = #selector(RenditionListViewController.infoButtonClicked(sender:))
            button.target = (contentViewController as? NSSplitViewController)?.splitViewItems[1].viewController as? RenditionListViewController
            button.setButtonType(.momentaryPushIn)
            button.bezelStyle = .texturedRounded
            toolbarItem.view = button
            
//            toolbarItem.action = #selector(RenditionListViewController.infoPopoverItemClicked(sender:))
//            toolbarItem.target = (contentViewController as? NSSplitViewController)?.splitViewItems[1].viewController as? RenditionListViewController
//            toolbarItem.image = NSImage(systemSymbolName: "info.circle", accessibilityDescription: nil)
//            toolbarItem.isEnabled = true
            return toolbarItem
        case .init("flexSpace"):
            #warning("Fix this (want flexible space between search bar and sidebar)")
            let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "flexSpace"))
            return toolbarItem
        default:
            return NSToolbarItem(itemIdentifier: itemIdentifier)
        }
    }
    
    func toolbar(_ toolbar: NSToolbar, itemIdentifier: NSToolbarItem.Identifier, canBeInsertedAt index: Int) -> Bool {
        return true
    }
}


================================================
FILE: Samra.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 56;
	objects = {

/* Begin PBXBuildFile section */
		CE1126A929A556C0000AC770 /* RenditionInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1126A829A556C0000AC770 /* RenditionInformationView.swift */; };
		CE15D4BF29A3E5D5001D66E6 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE15D4BE29A3E5D5001D66E6 /* URLHandler.swift */; };
		CE1673DB29ACDF8100F94683 /* AssetCatalogDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1673DA29ACDF8100F94683 /* AssetCatalogDetailsView.swift */; };
		CE1F1D3429B0A4C1000B288C /* MenuableCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1F1D3329B0A4C1000B288C /* MenuableCollectionView.swift */; };
		CE1F1D3629B0ADCE000B288C /* ClosureMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1F1D3529B0ADCE000B288C /* ClosureMenuItem.swift */; };
		CE375E6429B65D3900CAC2F0 /* AssetCatalogDiffSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE375E6329B65D3900CAC2F0 /* AssetCatalogDiffSelectionViewController.swift */; };
		CE375E6629B6675900CAC2F0 /* AssetCatalogInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE375E6529B6675900CAC2F0 /* AssetCatalogInput.swift */; };
		CE3BC09829A0C626009823CF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BC09729A0C626009823CF /* AppDelegate.swift */; };
		CE3BC09A29A0C626009823CF /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BC09929A0C626009823CF /* WindowController.swift */; };
		CE3BC09C29A0C626009823CF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE3BC09B29A0C626009823CF /* Assets.xcassets */; };
		CE3BC0A729A0CC27009823CF /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BC0A629A0CC27009823CF /* WelcomeViewController.swift */; };
		CE3BC0A929A0D713009823CF /* PastFilesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BC0A829A0D713009823CF /* PastFilesListViewController.swift */; };
		CE3BC0AF29A0E345009823CF /* BasicLayoutAnchorsHolding.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BC0AE29A0E345009823CF /* BasicLayoutAnchorsHolding.swift */; };
		CE3BC0B129A0E990009823CF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3BC0B029A0E990009823CF /* Preferences.swift */; };
		CE3F2D052A02DABC0026A9F9 /* DiffFilePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3F2D042A02DABC0026A9F9 /* DiffFilePreviewView.swift */; };
		CE3F9C6E2CCA22C400662232 /* AssetCatalogWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = CE3F9C6D2CCA22C400662232 /* AssetCatalogWrapper */; };
		CE45E09D29C24E1F00817359 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE45E09C29C24E1F00817359 /* main.swift */; };
		CE5AF1A829A2516500C675D8 /* RenditionTypeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5AF1A729A2516500C675D8 /* RenditionTypeHeaderView.swift */; };
		CE7D54A729A1238F00862873 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7D54A629A1238F00862873 /* Extensions.swift */; };
		CE7D54AD29A1313000862873 /* TypesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7D54AC29A1313000862873 /* TypesListViewController.swift */; };
		CE7D54AF29A1370D00862873 /* RenditionListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7D54AE29A1370D00862873 /* RenditionListViewController.swift */; };
		CE7D54B129A14F2200862873 /* RenditionCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7D54B029A14F2200862873 /* RenditionCollectionViewItem.swift */; };
		CE7E5D9229B1D3AC0064B91B /* QuickLooKPreviewSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7E5D9129B1D3AC0064B91B /* QuickLooKPreviewSource.swift */; };
		CE7E5D9829B1D5D30064B91B /* QuickLookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE7E5D9729B1D5D30064B91B /* QuickLookUI.framework */; };
		CEA6629029A4E5FF00215B08 /* WelcomeScreenOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6628F29A4E5FF00215B08 /* WelcomeScreenOption.swift */; };
		CEA71A5E29AE760900BEBE93 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA71A5D29AE760900BEBE93 /* AboutViewController.swift */; };
		CEC3B27C29B14551007E853E /* AssetCatalogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC3B27B29B14551007E853E /* AssetCatalogDocument.swift */; };
		CEC5EBC729B7CCD6009BA873 /* DiffListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC5EBC629B7CCD6009BA873 /* DiffListViewController.swift */; };
		CED4DE3129A626D7008B2B8A /* CollapseNotifierSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED4DE3029A626D7008B2B8A /* CollapseNotifierSplitViewController.swift */; };
		CEE9FA3E2CCA245C00F3F356 /* AssetCatalogWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = CEE9FA3D2CCA245C00F3F356 /* AssetCatalogWrapper */; };
		CEEA6AB429A515EA00B3CEA9 /* DetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEA6AB329A515EA00B3CEA9 /* DetailItem.swift */; };
		CEEE131029B73B99009C1ACD /* RenditionDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEE130F29B73B99009C1ACD /* RenditionDiff.swift */; };
		CEF987032B974C53002177A2 /* ClosureBasedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF987022B974C53002177A2 /* ClosureBasedButton.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
		CE45E09829C24E1F00817359 /* CopyFiles */ = {
			isa = PBXCopyFilesBuildPhase;
			buildActionMask = 2147483647;
			dstPath = /usr/share/man/man1/;
			dstSubfolderSpec = 0;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 1;
		};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
		CE1126A829A556C0000AC770 /* RenditionInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenditionInformationView.swift; sourceTree = "<group>"; };
		CE15D4BE29A3E5D5001D66E6 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = "<group>"; };
		CE1673DA29ACDF8100F94683 /* AssetCatalogDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCatalogDetailsView.swift; sourceTree = "<group>"; };
		CE1F1D3329B0A4C1000B288C /* MenuableCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuableCollectionView.swift; sourceTree = "<group>"; };
		CE1F1D3529B0ADCE000B288C /* ClosureMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureMenuItem.swift; sourceTree = "<group>"; };
		CE375E6329B65D3900CAC2F0 /* AssetCatalogDiffSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCatalogDiffSelectionViewController.swift; sourceTree = "<group>"; };
		CE375E6529B6675900CAC2F0 /* AssetCatalogInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCatalogInput.swift; sourceTree = "<group>"; };
		CE3BC09429A0C626009823CF /* Samra.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Samra.app; sourceTree = BUILT_PRODUCTS_DIR; };
		CE3BC09729A0C626009823CF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		CE3BC09929A0C626009823CF /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
		CE3BC09B29A0C626009823CF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		CE3BC0A029A0C626009823CF /* Samra.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Samra.entitlements; sourceTree = "<group>"; };
		CE3BC0A629A0CC27009823CF /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
		CE3BC0A829A0D713009823CF /* PastFilesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PastFilesListViewController.swift; sourceTree = "<group>"; };
		CE3BC0AE29A0E345009823CF /* BasicLayoutAnchorsHolding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicLayoutAnchorsHolding.swift; sourceTree = "<group>"; };
		CE3BC0B029A0E990009823CF /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
		CE3F2D042A02DABC0026A9F9 /* DiffFilePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffFilePreviewView.swift; sourceTree = "<group>"; };
		CE45E09A29C24E1F00817359 /* extractutil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = extractutil; sourceTree = BUILT_PRODUCTS_DIR; };
		CE45E09C29C24E1F00817359 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
		CE5AF1A729A2516500C675D8 /* RenditionTypeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenditionTypeHeaderView.swift; sourceTree = "<group>"; };
		CE7D54A629A1238F00862873 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
		CE7D54A829A1243C00862873 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
		CE7D54AC29A1313000862873 /* TypesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypesListViewController.swift; sourceTree = "<group>"; };
		CE7D54AE29A1370D00862873 /* RenditionListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenditionListViewController.swift; sourceTree = "<group>"; };
		CE7D54B029A14F2200862873 /* RenditionCollectionViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenditionCollectionViewItem.swift; sourceTree = "<group>"; };
		CE7E5D9129B1D3AC0064B91B /* QuickLooKPreviewSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLooKPreviewSource.swift; sourceTree = "<group>"; };
		CE7E5D9529B1D5CC0064B91B /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/PrivateFrameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; };
		CE7E5D9729B1D5D30064B91B /* QuickLookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookUI.framework; path = System/Library/Frameworks/QuickLookUI.framework; sourceTree = SDKROOT; };
		CEA6628F29A4E5FF00215B08 /* WelcomeScreenOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenOption.swift; sourceTree = "<group>"; };
		CEA71A5D29AE760900BEBE93 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
		CEC3B27B29B14551007E853E /* AssetCatalogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCatalogDocument.swift; sourceTree = "<group>"; };
		CEC5EBC629B7CCD6009BA873 /* DiffListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffListViewController.swift; sourceTree = "<group>"; };
		CED4DE2E29A62566008B2B8A /* AppKitPrivates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppKitPrivates.h; sourceTree = "<group>"; };
		CED4DE2F29A62586008B2B8A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
		CED4DE3029A626D7008B2B8A /* CollapseNotifierSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapseNotifierSplitViewController.swift; sourceTree = "<group>"; };
		CEEA6AB329A515EA00B3CEA9 /* DetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailItem.swift; sourceTree = "<group>"; };
		CEEE130F29B73B99009C1ACD /* RenditionDiff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenditionDiff.swift; sourceTree = "<group>"; };
		CEF987022B974C53002177A2 /* ClosureBasedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureBasedButton.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		CE3BC09129A0C625009823CF /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				CE7E5D9829B1D5D30064B91B /* QuickLookUI.framework in Frameworks */,
				CE3F9C6E2CCA22C400662232 /* AssetCatalogWrapper in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		CE45E09729C24E1F00817359 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				CEE9FA3E2CCA245C00F3F356 /* AssetCatalogWrapper in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		CE3BC08B29A0C625009823CF = {
			isa = PBXGroup;
			children = (
				CE3BC09629A0C626009823CF /* Samra */,
				CE45E09B29C24E1F00817359 /* extractutil */,
				CE3BC09529A0C626009823CF /* Products */,
				CE7E5D9429B1D5CC0064B91B /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		CE3BC09529A0C626009823CF /* Products */ = {
			isa = PBXGroup;
			children = (
				CE3BC09429A0C626009823CF /* Samra.app */,
				CE45E09A29C24E1F00817359 /* extractutil */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		CE3BC09629A0C626009823CF /* Samra */ = {
			isa = PBXGroup;
			children = (
				CE7D54A829A1243C00862873 /* Info.plist */,
				CE3BC09729A0C626009823CF /* AppDelegate.swift */,
				CE3BC09929A0C626009823CF /* WindowController.swift */,
				CE3BC0AA29A0E2E7009823CF /* UI */,
				CE3BC0AB29A0E2F6009823CF /* Backend */,
				CE3BC09B29A0C626009823CF /* Assets.xcassets */,
				CE3BC0A029A0C626009823CF /* Samra.entitlements */,
			);
			path = Samra;
			sourceTree = "<group>";
		};
		CE3BC0AA29A0E2E7009823CF /* UI */ = {
			isa = PBXGroup;
			children = (
				CE3BC0A629A0CC27009823CF /* WelcomeViewController.swift */,
				CEA6628F29A4E5FF00215B08 /* WelcomeScreenOption.swift */,
				CE3BC0A829A0D713009823CF /* PastFilesListViewController.swift */,
				CED4DE3029A626D7008B2B8A /* CollapseNotifierSplitViewController.swift */,
				CEA71A5D29AE760900BEBE93 /* AboutViewController.swift */,
				CE1F1D3329B0A4C1000B288C /* MenuableCollectionView.swift */,
				CEF987022B974C53002177A2 /* ClosureBasedButton.swift */,
				CEC5EBC529B7CCCA009BA873 /* Diff */,
				CECA445029A23D80003222D0 /* Rendition */,
			);
			path = UI;
			sourceTree = "<group>";
		};
		CE3BC0AB29A0E2F6009823CF /* Backend */ = {
			isa = PBXGroup;
			children = (
				CE7E5D9329B1D45B0064B91B /* UI Support */,
				CED4DE2D29A62558008B2B8A /* AppKitPrivates */,
				CEEA6AB329A515EA00B3CEA9 /* DetailItem.swift */,
				CEEE130F29B73B99009C1ACD /* RenditionDiff.swift */,
				CE3BC0B029A0E990009823CF /* Preferences.swift */,
				CE7D54A629A1238F00862873 /* Extensions.swift */,
				CE375E6529B6675900CAC2F0 /* AssetCatalogInput.swift */,
				CE1F1D3529B0ADCE000B288C /* ClosureMenuItem.swift */,
			);
			path = Backend;
			sourceTree = "<group>";
		};
		CE45E09B29C24E1F00817359 /* extractutil */ = {
			isa = PBXGroup;
			children = (
				CE45E09C29C24E1F00817359 /* main.swift */,
			);
			path = extractutil;
			sourceTree = "<group>";
		};
		CE7E5D9329B1D45B0064B91B /* UI Support */ = {
			isa = PBXGroup;
			children = (
				CE3BC0AE29A0E345009823CF /* BasicLayoutAnchorsHolding.swift */,
				CE15D4BE29A3E5D5001D66E6 /* URLHandler.swift */,
				CEC3B27B29B14551007E853E /* AssetCatalogDocument.swift */,
				CE7E5D9129B1D3AC0064B91B /* QuickLooKPreviewSource.swift */,
			);
			path = "UI Support";
			sourceTree = "<group>";
		};
		CE7E5D9429B1D5CC0064B91B /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				CE7E5D9729B1D5D30064B91B /* QuickLookUI.framework */,
				CE7E5D9529B1D5CC0064B91B /* QuickLookThumbnailing.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		CEC5EBC529B7CCCA009BA873 /* Diff */ = {
			isa = PBXGroup;
			children = (
				CE375E6329B65D3900CAC2F0 /* AssetCatalogDiffSelectionViewController.swift */,
				CE3F2D042A02DABC0026A9F9 /* DiffFilePreviewView.swift */,
				CEC5EBC629B7CCD6009BA873 /* DiffListViewController.swift */,
			);
			path = Diff;
			sourceTree = "<group>";
		};
		CECA445029A23D80003222D0 /* Rendition */ = {
			isa = PBXGroup;
			children = (
				CE7D54AC29A1313000862873 /* TypesListViewController.swift */,
				CE5AF1A729A2516500C675D8 /* RenditionTypeHeaderView.swift */,
				CE7D54AE29A1370D00862873 /* RenditionListViewController.swift */,
				CE7D54B029A14F2200862873 /* RenditionCollectionViewItem.swift */,
				CE1126A829A556C0000AC770 /* RenditionInformationView.swift */,
				CE1673DA29ACDF8100F94683 /* AssetCatalogDetailsView.swift */,
			);
			path = Rendition;
			sourceTree = "<group>";
		};
		CED4DE2D29A62558008B2B8A /* AppKitPrivates */ = {
			isa = PBXGroup;
			children = (
				CED4DE2E29A62566008B2B8A /* AppKitPrivates.h */,
				CED4DE2F29A62586008B2B8A /* module.modulemap */,
			);
			path = AppKitPrivates;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		CE3BC09329A0C625009823CF /* Samra */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = CE3BC0A329A0C626009823CF /* Build configuration list for PBXNativeTarget "Samra" */;
			buildPhases = (
				CE3BC09029A0C625009823CF /* Sources */,
				CE3BC09129A0C625009823CF /* Frameworks */,
				CE3BC09229A0C625009823CF /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = Samra;
			packageProductDependencies = (
				CE3F9C6D2CCA22C400662232 /* AssetCatalogWrapper */,
			);
			productName = Samra;
			productReference = CE3BC09429A0C626009823CF /* Samra.app */;
			productType = "com.apple.product-type.application";
		};
		CE45E09929C24E1F00817359 /* extractutil */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = CE45E0A029C24E1F00817359 /* Build configuration list for PBXNativeTarget "extractutil" */;
			buildPhases = (
				CE45E09629C24E1F00817359 /* Sources */,
				CE45E09729C24E1F00817359 /* Frameworks */,
				CE45E09829C24E1F00817359 /* CopyFiles */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = extractutil;
			packageProductDependencies = (
				CEE9FA3D2CCA245C00F3F356 /* AssetCatalogWrapper */,
			);
			productName = extractutil;
			productReference = CE45E09A29C24E1F00817359 /* extractutil */;
			productType = "com.apple.product-type.tool";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		CE3BC08C29A0C625009823CF /* Project object */ = {
			isa = PBXProject;
			attributes = {
				BuildIndependentTargetsInParallel = 1;
				LastSwiftUpdateCheck = 1420;
				LastUpgradeCheck = 1420;
				TargetAttributes = {
					CE3BC09329A0C625009823CF = {
						CreatedOnToolsVersion = 14.2;
					};
					CE45E09929C24E1F00817359 = {
						CreatedOnToolsVersion = 14.2;
					};
				};
			};
			buildConfigurationList = CE3BC08F29A0C625009823CF /* Build configuration list for PBXProject "Samra" */;
			compatibilityVersion = "Xcode 14.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = CE3BC08B29A0C625009823CF;
			packageReferences = (
				CE3F9C6C2CCA22C400662232 /* XCRemoteSwiftPackageReference "PrivateKits" */,
			);
			productRefGroup = CE3BC09529A0C626009823CF /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				CE3BC09329A0C625009823CF /* Samra */,
				CE45E09929C24E1F00817359 /* extractutil */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		CE3BC09229A0C625009823CF /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				CE3BC09C29A0C626009823CF /* Assets.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		CE3BC09029A0C625009823CF /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				CE3BC0A729A0CC27009823CF /* WelcomeViewController.swift in Sources */,
				CE3BC09A29A0C626009823CF /* WindowController.swift in Sources */,
				CE3BC0B129A0E990009823CF /* Preferences.swift in Sources */,
				CE3BC0A929A0D713009823CF /* PastFilesListViewController.swift in Sources */,
				CE3BC0AF29A0E345009823CF /* BasicLayoutAnchorsHolding.swift in Sources */,
				CE1126A929A556C0000AC770 /* RenditionInformationView.swift in Sources */,
				CE15D4BF29A3E5D5001D66E6 /* URLHandler.swift in Sources */,
				CE1673DB29ACDF8100F94683 /* AssetCatalogDetailsView.swift in Sources */,
				CE7D54AD29A1313000862873 /* TypesListViewController.swift in Sources */,
				CEF987032B974C53002177A2 /* ClosureBasedButton.swift in Sources */,
				CE1F1D3429B0A4C1000B288C /* MenuableCollectionView.swift in Sources */,
				CE375E6429B65D3900CAC2F0 /* AssetCatalogDiffSelectionViewController.swift in Sources */,
				CE7D54B129A14F2200862873 /* RenditionCollectionViewItem.swift in Sources */,
				CE3BC09829A0C626009823CF /* AppDelegate.swift in Sources */,
				CEC5EBC729B7CCD6009BA873 /* DiffListViewController.swift in Sources */,
				CE5AF1A829A2516500C675D8 /* RenditionTypeHeaderView.swift in Sources */,
				CE1F1D3629B0ADCE000B288C /* ClosureMenuItem.swift in Sources */,
				CEA71A5E29AE760900BEBE93 /* AboutViewController.swift in Sources */,
				CE7D54A729A1238F00862873 /* Extensions.swift in Sources */,
				CE3F2D052A02DABC0026A9F9 /* DiffFilePreviewView.swift in Sources */,
				CED4DE3129A626D7008B2B8A /* CollapseNotifierSplitViewController.swift in Sources */,
				CE375E6629B6675900CAC2F0 /* AssetCatalogInput.swift in Sources */,
				CEEA6AB429A515EA00B3CEA9 /* DetailItem.swift in Sources */,
				CE7D54AF29A1370D00862873 /* RenditionListViewController.swift in Sources */,
				CEA6629029A4E5FF00215B08 /* WelcomeScreenOption.swift in Sources */,
				CE7E5D9229B1D3AC0064B91B /* QuickLooKPreviewSource.swift in Sources */,
				CEEE131029B73B99009C1ACD /* RenditionDiff.swift in Sources */,
				CEC3B27C29B14551007E853E /* AssetCatalogDocument.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		CE45E09629C24E1F00817359 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				CE45E09D29C24E1F00817359 /* main.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
		CE3BC0A129A0C626009823CF /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MACOSX_DEPLOYMENT_TARGET = 13.0;
				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
				MTL_FAST_MATH = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = macosx;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		CE3BC0A229A0C626009823CF /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_ENABLE_OBJC_WEAK = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				MACOSX_DEPLOYMENT_TARGET = 13.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				MTL_FAST_MATH = YES;
				SDKROOT = macosx;
				SWIFT_COMPILATION_MODE = wholemodule;
				SWIFT_OPTIMIZATION_LEVEL = "-O";
			};
			name = Release;
		};
		CE3BC0A429A0C626009823CF /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
				CODE_SIGN_ENTITLEMENTS = Samra/Samra.entitlements;
				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
				CODE_SIGN_STYLE = Automatic;
				COMBINE_HIDPI_IMAGES = YES;
				CURRENT_PROJECT_VERSION = 1.4;
				DEVELOPMENT_TEAM = L9735M962H;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_FILE = Samra/Info.plist;
				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
				INFOPLIST_KEY_NSHumanReadableCopyright = "";
				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/../Frameworks",
				);
				MACOSX_DEPLOYMENT_TARGET = 10.15.1;
				MARKETING_VERSION = 1;
				PRODUCT_BUNDLE_IDENTIFIER = com.serena.Samra;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Samra/Backend/AppKitPrivates";
				SWIFT_VERSION = 5.0;
				SYSTEM_FRAMEWORK_SEARCH_PATHS = (
					"$(inherited)",
					"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
				);
			};
			name = Debug;
		};
		CE3BC0A529A0C626009823CF /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
				ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
				CODE_SIGN_ENTITLEMENTS = Samra/Samra.entitlements;
				"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
				CODE_SIGN_STYLE = Automatic;
				COMBINE_HIDPI_IMAGES = YES;
				CURRENT_PROJECT_VERSION = 1.4;
				DEVELOPMENT_TEAM = L9735M962H;
				GENERATE_INFOPLIST_FILE = YES;
				INFOPLIST_FILE = Samra/Info.plist;
				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
				INFOPLIST_KEY_NSHumanReadableCopyright = "";
				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
				LD_RUNPATH_SEARCH_PATHS = (
					"$(inherited)",
					"@executable_path/../Frameworks",
				);
				MACOSX_DEPLOYMENT_TARGET = 10.15.1;
				MARKETING_VERSION = 1;
				PRODUCT_BUNDLE_IDENTIFIER = com.serena.Samra;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_EMIT_LOC_STRINGS = YES;
				SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Samra/Backend/AppKitPrivates";
				SWIFT_VERSION = 5.0;
				SYSTEM_FRAMEWORK_SEARCH_PATHS = (
					"$(inherited)",
					"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
				);
			};
			name = Release;
		};
		CE45E09E29C24E1F00817359 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = L9735M962H;
				ENABLE_HARDENED_RUNTIME = YES;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
			};
			name = Debug;
		};
		CE45E09F29C24E1F00817359 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CODE_SIGN_STYLE = Automatic;
				DEVELOPMENT_TEAM = L9735M962H;
				ENABLE_HARDENED_RUNTIME = YES;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 5.0;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		CE3BC08F29A0C625009823CF /* Build configuration list for PBXProject "Samra" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				CE3BC0A129A0C626009823CF /* Debug */,
				CE3BC0A229A0C626009823CF /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		CE3BC0A329A0C626009823CF /* Build configuration list for PBXNativeTarget "Samra" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				CE3BC0A429A0C626009823CF /* Debug */,
				CE3BC0A529A0C626009823CF /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		CE45E0A029C24E1F00817359 /* Build configuration list for PBXNativeTarget "extractutil" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				CE45E09E29C24E1F00817359 /* Debug */,
				CE45E09F29C24E1F00817359 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
		CE3F9C6C2CCA22C400662232 /* XCRemoteSwiftPackageReference "PrivateKits" */ = {
			isa = XCRemoteSwiftPackageReference;
			repositoryURL = "https://github.com/SerenaKit/PrivateKits";
			requirement = {
				branch = main;
				kind = branch;
			};
		};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
		CE3F9C6D2CCA22C400662232 /* AssetCatalogWrapper */ = {
			isa = XCSwiftPackageProductDependency;
			package = CE3F9C6C2CCA22C400662232 /* XCRemoteSwiftPackageReference "PrivateKits" */;
			productName = AssetCatalogWrapper;
		};
		CEE9FA3D2CCA245C00F3F356 /* AssetCatalogWrapper */ = {
			isa = XCSwiftPackageProductDependency;
			package = CE3F9C6C2CCA22C400662232 /* XCRemoteSwiftPackageReference "PrivateKits" */;
			productName = AssetCatalogWrapper;
		};
/* End XCSwiftPackageProductDependency section */
	};
	rootObject = CE3BC08C29A0C625009823CF /* Project object */;
}


================================================
FILE: Samra.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: Samra.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: Samra.xcodeproj/xcshareddata/xcschemes/Samra.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1420"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "CE3BC09329A0C625009823CF"
               BuildableName = "Samra.app"
               BlueprintName = "Samra"
               ReferencedContainer = "container:Samra.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "CE3BC09329A0C625009823CF"
            BuildableName = "Samra.app"
            BlueprintName = "Samra"
            ReferencedContainer = "container:Samra.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "CE3BC09329A0C625009823CF"
            BuildableName = "Samra.app"
            BlueprintName = "Samra"
            ReferencedContainer = "container:Samra.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Samra.xcodeproj/xcshareddata/xcschemes/extractutil.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1420"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "CE45E09929C24E1F00817359"
               BuildableName = "extractutil"
               BlueprintName = "extractutil"
               ReferencedContainer = "container:Samra.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES"
      viewDebuggingEnabled = "No">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "CE45E09929C24E1F00817359"
            BuildableName = "extractutil"
            BlueprintName = "extractutil"
            ReferencedContainer = "container:Samra.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <BuildableProductRunnable
         runnableDebuggingMode = "0">
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "CE45E09929C24E1F00817359"
            BuildableName = "extractutil"
            BlueprintName = "extractutil"
            ReferencedContainer = "container:Samra.xcodeproj">
         </BuildableReference>
      </BuildableProductRunnable>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: extractutil/main.swift
================================================
//
//  main.swift
//  extractutil
//
//  Created by Serena on 15/03/2023.
// 
// smol CommandLine tool to just extract an asset catalog :3

import Foundation
import AssetCatalogWrapper

guard CommandLine.arguments.count >= 3 else {
    fatalError("usage: \(CommandLine.arguments[0]) <catalog-url> <directory-to-extract-to>")
}

let catalogURL = URL(fileURLWithPath: CommandLine.arguments[1])
let destinationURL = URL(fileURLWithPath: CommandLine.arguments[2])

let rends: RenditionCollection

do {
    rends = try AssetCatalogWrapper.shared.renditions(forCarArchive: catalogURL).1
} catch {
    fatalError("Failed to fetch Catalog from URL \(catalogURL.path), error: \(error.localizedDescription)")
}

// try create the destination URL if it doesn't exist
if !FileManager.default.fileExists(atPath: destinationURL.path) {
    do {
        print("destination URL \(destinationURL.path) doesn't exist, will try to create")
        try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true)
    } catch {
        fatalError("Failed to create \(destinationURL.path), error: \(error.localizedDescription)")
    }
}

do {
    try AssetCatalogWrapper.shared.extract(collection: rends, to: destinationURL)
    print("Extracted catalog to \(destinationURL.path)")
} catch {
    fatalError("Failed to extract (some) items, error: \(error.localizedDescription)")
}
Download .txt
gitextract_kxyr337y/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── Samra/
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Backend/
│   │   ├── AppKitPrivates/
│   │   │   ├── AppKitPrivates.h
│   │   │   └── module.modulemap
│   │   ├── AssetCatalogInput.swift
│   │   ├── ClosureMenuItem.swift
│   │   ├── DetailItem.swift
│   │   ├── Extensions.swift
│   │   ├── Preferences.swift
│   │   ├── RenditionDiff.swift
│   │   └── UI Support/
│   │       ├── AssetCatalogDocument.swift
│   │       ├── BasicLayoutAnchorsHolding.swift
│   │       ├── QuickLooKPreviewSource.swift
│   │       └── URLHandler.swift
│   ├── Info.plist
│   ├── Samra.entitlements
│   ├── UI/
│   │   ├── AboutViewController.swift
│   │   ├── ClosureBasedButton.swift
│   │   ├── CollapseNotifierSplitViewController.swift
│   │   ├── Diff/
│   │   │   ├── AssetCatalogDiffSelectionViewController.swift
│   │   │   ├── DiffFilePreviewView.swift
│   │   │   └── DiffListViewController.swift
│   │   ├── MenuableCollectionView.swift
│   │   ├── PastFilesListViewController.swift
│   │   ├── Rendition/
│   │   │   ├── AssetCatalogDetailsView.swift
│   │   │   ├── RenditionCollectionViewItem.swift
│   │   │   ├── RenditionInformationView.swift
│   │   │   ├── RenditionListViewController.swift
│   │   │   ├── RenditionTypeHeaderView.swift
│   │   │   └── TypesListViewController.swift
│   │   ├── WelcomeScreenOption.swift
│   │   └── WelcomeViewController.swift
│   └── WindowController.swift
├── Samra.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       └── IDEWorkspaceChecks.plist
│   └── xcshareddata/
│       └── xcschemes/
│           ├── Samra.xcscheme
│           └── extractutil.xcscheme
└── extractutil/
    └── main.swift
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (177K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 17,
    "preview": "ko_fi: nsantoine\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 1326,
    "preview": "name: Xcode - Build and Analyze\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\" ]\n\njobs:\n  "
  },
  {
    "path": ".gitignore",
    "chars": 41,
    "preview": ".DS_Store\nPackage.resolved\n*.xcuserdatad\n"
  },
  {
    "path": "LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2024 Antoine\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 1550,
    "preview": "# Samra\n\nA macOS Application to explore and edit Asset Catalog (.car) files on macOS, with a nicer native, modern-feelin"
  },
  {
    "path": "Samra/AppDelegate.swift",
    "chars": 10196,
    "preview": "//\n//  AppDelegate.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport AssetCatalogWrapper\n"
  },
  {
    "path": "Samra/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "Samra/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1302,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"icon_16x16.png\",\n      \"scale\""
  },
  {
    "path": "Samra/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Samra/Backend/AppKitPrivates/AppKitPrivates.h",
    "chars": 408,
    "preview": "//\n//  AppKitPrivates.h\n//  Samra\n//\n//  Created by Serena on 22/02/2023.\n// \n\n#ifndef AppKitPrivates_h\n#define AppKitPr"
  },
  {
    "path": "Samra/Backend/AppKitPrivates/module.modulemap",
    "chars": 56,
    "preview": "module AppKitPrivates {\n    header \"AppKitPrivates.h\"\n}\n"
  },
  {
    "path": "Samra/Backend/AssetCatalogInput.swift",
    "chars": 665,
    "preview": "//\n//  AssetCatalogInput.swift\n//  Samra\n//\n//  Created by Serena on 06/03/2023.\n// \n\nimport AssetCatalogWrapper\n\nstruct"
  },
  {
    "path": "Samra/Backend/ClosureMenuItem.swift",
    "chars": 555,
    "preview": "//\n//  ClosureMenuItem.swift\n//  Samra\n//\n//  Created by Serena on 02/03/2023.\n// \n\nimport Cocoa\n\nclass ClosureMenuItem:"
  },
  {
    "path": "Samra/Backend/DetailItem.swift",
    "chars": 7124,
    "preview": "//\n//  DetailItem.swift\n//  Samra\n//\n//  Created by Serena on 21/02/2023.\n// \n\nimport Cocoa\nimport AssetCatalogWrapper\n\n"
  },
  {
    "path": "Samra/Backend/Extensions.swift",
    "chars": 3024,
    "preview": "//\n//  Extensions.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport AssetCatalogWrapper\ni"
  },
  {
    "path": "Samra/Backend/Preferences.swift",
    "chars": 995,
    "preview": "//\n//  Preferences.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Foundation\n\n@propertyWrapper\nstru"
  },
  {
    "path": "Samra/Backend/RenditionDiff.swift",
    "chars": 529,
    "preview": "//\n//  DiffKind.swift\n//  Samra\n//\n//  Created by Serena on 07/03/2023.\n// \n\nimport AssetCatalogWrapper\n\nstruct Renditio"
  },
  {
    "path": "Samra/Backend/UI Support/AssetCatalogDocument.swift",
    "chars": 899,
    "preview": "//\n//  AssetCatalogDocument.swift\n//  Samra\n//\n//  Created by Serena on 02/03/2023.\n// \n\nimport Cocoa\nimport AssetCatalo"
  },
  {
    "path": "Samra/Backend/UI Support/BasicLayoutAnchorsHolding.swift",
    "chars": 1628,
    "preview": "//\n//  BasicLayoutAnchorsHolding.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\n#if canImport(AppKit)\nimpo"
  },
  {
    "path": "Samra/Backend/UI Support/QuickLooKPreviewSource.swift",
    "chars": 520,
    "preview": "//\n//  QuickLooKPreviewSource.swift\n//  Samra\n//\n//  Created by Serena on 03/03/2023.\n// \n\nimport Cocoa\nimport QuickLook"
  },
  {
    "path": "Samra/Backend/UI Support/URLHandler.swift",
    "chars": 3685,
    "preview": "//\n//  URLHandler.swift\n//  Samra\n//\n//  Created by Serena on 20/02/2023.\n// \n\nimport Cocoa\nimport AssetCatalogWrapper\n\n"
  },
  {
    "path": "Samra/Info.plist",
    "chars": 1860,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Samra/Samra.entitlements",
    "chars": 181,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Samra/UI/AboutViewController.swift",
    "chars": 4326,
    "preview": "//\n//  AboutViewController.swift\n//  Samra\n//\n//  Created by Serena on 28/02/2023.\n// \n\nimport Cocoa\n\nclass AboutViewCon"
  },
  {
    "path": "Samra/UI/ClosureBasedButton.swift",
    "chars": 439,
    "preview": "//\n//  ClosureBasedButton.swift\n//  Samra\n//\n//  Created by Serena on 05/03/2024.\n//  \n\nimport Cocoa\n\nclass ClosureBased"
  },
  {
    "path": "Samra/UI/CollapseNotifierSplitViewController.swift",
    "chars": 864,
    "preview": "//\n//  CollapseNotifierSplitViewController.swift\n//  Samra\n//\n//  Created by Serena on 22/02/2023.\n// \n\nimport Cocoa\nimp"
  },
  {
    "path": "Samra/UI/Diff/AssetCatalogDiffSelectionViewController.swift",
    "chars": 9585,
    "preview": "//\n//  AssetCatalogDiffSelectionViewController.swift\n//  Samra\n//\n//  Created by Serena on 06/03/2023.\n// \n\nimport Cocoa"
  },
  {
    "path": "Samra/UI/Diff/DiffFilePreviewView.swift",
    "chars": 1652,
    "preview": "//\n//  DiffFilePreviewView.swift\n//  Samra\n//\n//  Created by Serena on 03/05/2023.\n//\n\nimport Cocoa\n\nclass DiffFilePrevi"
  },
  {
    "path": "Samra/UI/Diff/DiffListViewController.swift",
    "chars": 5282,
    "preview": "//\n//  DiffListViewController.swift\n//  Samra\n//\n//  Created by Serena on 07/03/2023.\n// \n\nimport Cocoa\nimport class Swi"
  },
  {
    "path": "Samra/UI/MenuableCollectionView.swift",
    "chars": 696,
    "preview": "//\n//  MenuableCollectionView.swift\n//  Samra\n//\n//  Created by Serena on 02/03/2023.\n// \n\nimport Cocoa\n\nclass Collectio"
  },
  {
    "path": "Samra/UI/PastFilesListViewController.swift",
    "chars": 5443,
    "preview": "//\n//  PastFilesListViewController.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport Quic"
  },
  {
    "path": "Samra/UI/Rendition/AssetCatalogDetailsView.swift",
    "chars": 1480,
    "preview": "//\n//  AssetCatalogDetailsView.swift\n//  Samra\n//\n//  Created by Serena on 27/02/2023.\n// \n\nimport SwiftUI\nimport AssetC"
  },
  {
    "path": "Samra/UI/Rendition/RenditionCollectionViewItem.swift",
    "chars": 5059,
    "preview": "//\n//  RenditionCollectionViewItem.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport Asse"
  },
  {
    "path": "Samra/UI/Rendition/RenditionInformationView.swift",
    "chars": 9449,
    "preview": "//\n//  RenditionInformationView.swift\n//  Samra\n//\n//  Created by Serena on 21/02/2023.\n// \n\nimport SwiftUI\nimport Unifo"
  },
  {
    "path": "Samra/UI/Rendition/RenditionListViewController.swift",
    "chars": 20701,
    "preview": "//\n//  RenditionListViewController.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport AppK"
  },
  {
    "path": "Samra/UI/Rendition/RenditionTypeHeaderView.swift",
    "chars": 1798,
    "preview": "//\n//  RenditionTypeHeaderView.swift\n//  Samra\n//\n//  Created by Serena on 19/02/2023.\n// \n\nimport Cocoa\nimport AssetCat"
  },
  {
    "path": "Samra/UI/Rendition/TypesListViewController.swift",
    "chars": 6047,
    "preview": "//\n//  TypesListViewController.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport AssetCat"
  },
  {
    "path": "Samra/UI/WelcomeScreenOption.swift",
    "chars": 2280,
    "preview": "//\n//  WelcomeScreenOption.swift\n//  Samra\n//\n//  Created by Serena on 21/02/2023.\n// \n\nimport Cocoa\n\n/// Represents an "
  },
  {
    "path": "Samra/UI/WelcomeViewController.swift",
    "chars": 5848,
    "preview": "//\n//  WelcomeViewController.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport AssetCatal"
  },
  {
    "path": "Samra/WindowController.swift",
    "chars": 7420,
    "preview": "//\n//  WindowController.swift\n//  Samra\n//\n//  Created by Serena on 18/02/2023.\n// \n\nimport Cocoa\nimport AssetCatalogWra"
  },
  {
    "path": "Samra.xcodeproj/project.pbxproj",
    "chars": 32970,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 56;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Samra.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "Samra.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Samra.xcodeproj/xcshareddata/xcschemes/Samra.xcscheme",
    "chars": 2822,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1420\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Samra.xcodeproj/xcshareddata/xcschemes/extractutil.xcscheme",
    "chars": 2880,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1420\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "extractutil/main.swift",
    "chars": 1390,
    "preview": "//\n//  main.swift\n//  extractutil\n//\n//  Created by Serena on 15/03/2023.\n// \n// smol CommandLine tool to just extract a"
  }
]

About this extraction

This page contains the full source code of the NSAntoine/Samra GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (162.7 KB), approximately 41.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!