diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-11 14:55:46 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-11 14:55:46 +0200 |
| commit | 49766f0c78015831f8be57349317f0d1c8228327 (patch) | |
| tree | 47ceb138008134dc83ad8432c744e3174854cb2c | |
| parent | f8c877e01e6517b13ec085d9d79bf316c7637344 (diff) | |
Add QuickLook settings
| -rw-r--r-- | Map.xcodeproj/project.pbxproj | 7 | ||||
| -rw-r--r-- | Map/Data/UserPreferences.swift | 43 | ||||
| -rw-r--r-- | Map/Localizable.xcstrings | 55 | ||||
| -rw-r--r-- | Map/Map.entitlements | 4 | ||||
| -rw-r--r-- | Map/Presentation/Preferences/GeneralPreferencesView.swift | 30 | ||||
| -rw-r--r-- | QuickLook/PreviewViewController.swift | 143 | ||||
| -rw-r--r-- | QuickLook/QuickLook.entitlements | 12 |
7 files changed, 259 insertions, 35 deletions
diff --git a/Map.xcodeproj/project.pbxproj b/Map.xcodeproj/project.pbxproj index 5c97780..c168a0e 100644 --- a/Map.xcodeproj/project.pbxproj +++ b/Map.xcodeproj/project.pbxproj @@ -107,6 +107,7 @@ Business/WmapParser/Syntax.swift, Business/WmapParser/Token.swift, Business/WmapParser/TokenType.swift, + Business/WmapSyntaxHighlighter/SyntaxHighlighter.swift, Data/UserPreferences.swift, "Data/UTType+wmap.swift", Localizable.xcstrings, @@ -125,6 +126,8 @@ "Presentation/Theme/Color+theme.swift", Presentation/Theme/Dimensions.swift, "Presentation/Theme/Font+theme.swift", + "Presentation/Theme/NSColor+theme.swift", + "Presentation/Theme/NSFont+theme.swift", ); target = B5282B012E210D4400C62006 /* QuickLook */; }; @@ -508,6 +511,7 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map.QuickLook; PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -536,6 +540,7 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map.QuickLook; PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -693,6 +698,7 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map; PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 6.0; @@ -726,6 +732,7 @@ MARKETING_VERSION = "$(MARKETING_VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map; PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 6.0; diff --git a/Map/Data/UserPreferences.swift b/Map/Data/UserPreferences.swift index 56582e3..01c4f37 100644 --- a/Map/Data/UserPreferences.swift +++ b/Map/Data/UserPreferences.swift @@ -15,6 +15,24 @@ import Foundation +// MARK: - Enums +enum QuickLookPreviewStyle: String, CaseIterable, Codable { + case map = "map" + case highlightedText = "highlighted_text" + case plainText = "plain_text" + + var localizedName: String { + switch self { + case .map: + return String(localized: "preferences.quick_look.preview_style.map") + case .highlightedText: + return String(localized: "preferences.quick_look.preview_style.highlighted_text") + case .plainText: + return String(localized: "preferences.quick_look.preview_style.plain_text") + } + } +} + // MARK: - JSON Export/Import Structures struct UserPreferencesJSON: Codable { // MARK: - Map Preferences @@ -41,6 +59,9 @@ struct UserPreferencesJSON: Codable { // MARK: - UI Preferences var viewStyle: String? var zoom: Double? + + // MARK: - QuickLook Preferences + var quickLookPreviewStyle: String? } struct UserPreferences: Codable { @@ -70,6 +91,9 @@ struct UserPreferences: Codable { var viewStyle: String var zoom: Double + // MARK: - QuickLook Preferences + var quickLookPreviewStyle: String + // MARK: - Default Values struct Defaults { static let showMapBackground = true @@ -88,6 +112,8 @@ struct UserPreferences: Codable { static let viewStyle = "horizontal" static let zoom = 1.0 + static let quickLookPreviewStyle = QuickLookPreviewStyle.map.rawValue + // Computed properties for complex defaults static var mapTemplates: Data { let defaultTemplate = Template( @@ -133,6 +159,8 @@ struct UserPreferences: Codable { self.viewStyle = Defaults.viewStyle self.zoom = Defaults.zoom + + self.quickLookPreviewStyle = Defaults.quickLookPreviewStyle } // MARK: - JSON Methods @@ -157,7 +185,8 @@ struct UserPreferences: Codable { mapTemplates: templatesFromData(mapTemplates), customStages: stagesFromData(customStages), viewStyle: viewStyle, - zoom: zoom + zoom: zoom, + quickLookPreviewStyle: quickLookPreviewStyle ) let data = try encoder.encode(jsonPrefs) @@ -220,6 +249,9 @@ struct UserPreferences: Codable { if let value = jsonPrefs.zoom { self.zoom = value } + if let value = jsonPrefs.quickLookPreviewStyle { + self.quickLookPreviewStyle = value + } apply() return true @@ -267,6 +299,7 @@ struct UserPreferences: Codable { // MARK: - Apply to UserDefaults private func apply() { let defaults = UserDefaults.standard + let sharedDefaults = UserDefaults(suiteName: "group.systems.tranquil.Map") // Map Preferences defaults.set(showMapBackground, forKey: "showMapBackground") @@ -292,6 +325,10 @@ struct UserPreferences: Codable { // UI Preferences defaults.set(viewStyle, forKey: "viewStyle") defaults.set(zoom, forKey: "zoom") + + // QuickLook Preferences - write to both standard and shared UserDefaults + defaults.set(quickLookPreviewStyle, forKey: "quickLookPreviewStyle") + sharedDefaults?.set(quickLookPreviewStyle, forKey: "quickLookPreviewStyle") } // MARK: - Load from UserDefaults @@ -340,6 +377,10 @@ struct UserPreferences: Codable { preferences.viewStyle = defaults.object(forKey: "viewStyle") as? String ?? Defaults.viewStyle preferences.zoom = defaults.object(forKey: "zoom") as? Double ?? Defaults.zoom + // QuickLook Preferences + preferences.quickLookPreviewStyle = + defaults.object(forKey: "quickLookPreviewStyle") as? String ?? Defaults.quickLookPreviewStyle + return preferences } } diff --git a/Map/Localizable.xcstrings b/Map/Localizable.xcstrings index 13932a4..dda0deb 100644 --- a/Map/Localizable.xcstrings +++ b/Map/Localizable.xcstrings @@ -735,6 +735,61 @@ } } }, + "preferences.quick_look.preview_style.highlighted_text" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Highlighted Text" + } + } + } + }, + "preferences.quick_look.preview_style.map" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Map" + } + } + } + }, + "preferences.quick_look.preview_style.plain_text" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plain Text" + } + } + } + }, + "preferences.quick_look.preview_style.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preview maps in QuickLook as:" + } + } + } + }, + "preferences.quick_look.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickLook:" + } + } + } + }, "preferences.stages.column.name" : { "extractionState" : "manual", "localizations" : { diff --git a/Map/Map.entitlements b/Map/Map.entitlements index d363e1c..91680ad 100644 --- a/Map/Map.entitlements +++ b/Map/Map.entitlements @@ -4,6 +4,10 @@ <dict> <key>com.apple.security.app-sandbox</key> <true/> + <key>com.apple.security.application-groups</key> + <array> + <string>group.systems.tranquil.Map</string> + </array> <key>com.apple.security.files.user-selected.read-write</key> <true/> <key>com.apple.security.network.client</key> diff --git a/Map/Presentation/Preferences/GeneralPreferencesView.swift b/Map/Presentation/Preferences/GeneralPreferencesView.swift index a70d608..b5b781d 100644 --- a/Map/Presentation/Preferences/GeneralPreferencesView.swift +++ b/Map/Presentation/Preferences/GeneralPreferencesView.swift @@ -25,8 +25,38 @@ struct GeneralPreferencesView: View { @State private var showingExportFailedAlert = false @State private var exportShouldReset = false + @AppStorage("quickLookPreviewStyle", store: UserDefaults(suiteName: "group.systems.tranquil.Map")) + private var quickLookPreviewStyle: String = UserPreferences.Defaults.quickLookPreviewStyle + var body: some View { VStack(alignment: .center, spacing: Dimensions.Spacing.loose) { + // QuickLook section + HStack(alignment: .firstTextBaseline, spacing: Dimensions.Spacing.regular) { + Text("preferences.quick_look.title") + .font(.Theme.Body.emphasized) + .frame(maxWidth: 250, alignment: .trailing) + + VStack(alignment: .leading, spacing: Dimensions.Spacing.regular) { + + Text("preferences.quick_look.preview_style.title") + .font(.Theme.Body.regular) + + Picker("", selection: $quickLookPreviewStyle) { + ForEach(QuickLookPreviewStyle.allCases, id: \.rawValue) { style in + Text(style.localizedName) + .tag(style.rawValue) + } + } + .pickerStyle(.menu) + .frame(maxWidth: 250) + .padding(.leading, -Dimensions.Spacing.regular) + + } + .frame(maxWidth: .infinity, alignment: .leading) + } + + Divider() + HStack(alignment: .firstTextBaseline, spacing: Dimensions.Spacing.regular) { Text("preferences.general.preferences.title") .font(.Theme.Body.emphasized) diff --git a/QuickLook/PreviewViewController.swift b/QuickLook/PreviewViewController.swift index 03efe72..c93b2cb 100644 --- a/QuickLook/PreviewViewController.swift +++ b/QuickLook/PreviewViewController.swift @@ -23,10 +23,12 @@ class PreviewViewController: NSViewController, QLPreviewingController { // Read the .wmap file content let content = try String(contentsOf: url, encoding: .utf8) - // Parse the content to create a ParsedMap - let lexer = Wmap.Lexer(content) - let parser = Wmap.Parser(lexer: lexer) - let parsedMap = parser.parse() + // Get the user's preview style preference from shared UserDefaults + let sharedDefaults = + UserDefaults(suiteName: "group.systems.tranquil.Map") ?? UserDefaults.standard + let previewStyleString = + sharedDefaults.string(forKey: "quickLookPreviewStyle") ?? QuickLookPreviewStyle.map.rawValue + let previewStyle = QuickLookPreviewStyle(rawValue: previewStyleString) ?? .map await MainActor.run { // Clear any existing subviews and force cleanup @@ -39,34 +41,53 @@ class PreviewViewController: NSViewController, QLPreviewingController { // Force memory cleanup autoreleasepool { - // Create the map preview view with error handling - let mapPreview: AnyView - if parsedMap.entities.isEmpty { - // Fallback view for empty or invalid maps - mapPreview = AnyView( - VStack { - Image(systemName: "map") - .font(.system(size: 48)) - .foregroundColor(.gray) - Text("quick_look.empty_map.title") - .font(.Theme.Title.emphasized) - .foregroundColor(.Theme.UI.foreground) - Text("quick_look.empty_map.description") - .font(.Theme.Body.regular) - .foregroundColor(.Theme.UI.foreground) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .foregroundColor(.Theme.UI.background) - ) - } else { - // Limit entities for memory efficiency in QuickLook - let limitedEntities = Array(parsedMap.entities.prefix(100)) // Limit to first 100 entities - let limitedMap = Wmap.ParsedMap( - entities: limitedEntities, vertexLabels: parsedMap.vertexLabels) - mapPreview = AnyView(MapPreviewView(parsedMap: limitedMap)) + let previewContent: AnyView + + switch previewStyle { + case .plainText: + previewContent = AnyView(createPlainTextView(content: content)) + + case .highlightedText: + // Parse for syntax highlighting + let lexer = Wmap.Lexer(content) + let parser = Wmap.Parser(lexer: lexer) + let parsedMap = parser.parse() + previewContent = AnyView( + createHighlightedTextView(content: content, parsedMap: parsedMap)) + + case .map: + // Parse the content to create a ParsedMap + let lexer = Wmap.Lexer(content) + let parser = Wmap.Parser(lexer: lexer) + let parsedMap = parser.parse() + + if parsedMap.entities.isEmpty { + // Fallback view for empty or invalid maps + previewContent = AnyView( + VStack { + Image(systemName: "map") + .font(.system(size: 48)) + .foregroundColor(.gray) + Text("quick_look.empty_map.title") + .font(.Theme.Title.emphasized) + .foregroundColor(.Theme.UI.foreground) + Text("quick_look.empty_map.description") + .font(.Theme.Body.regular) + .foregroundColor(.Theme.UI.foreground) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .foregroundColor(.Theme.UI.background) + ) + } else { + // Limit entities for memory efficiency in QuickLook + let limitedEntities = Array(parsedMap.entities.prefix(100)) // Limit to first 100 entities + let limitedMap = Wmap.ParsedMap( + entities: limitedEntities, vertexLabels: parsedMap.vertexLabels) + previewContent = AnyView(MapPreviewView(parsedMap: limitedMap)) + } } - let hostingView = NSHostingView(rootView: mapPreview) + let hostingView = NSHostingView(rootView: previewContent) hostingView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingView) @@ -84,6 +105,24 @@ class PreviewViewController: NSViewController, QLPreviewingController { } } + private func createPlainTextView(content: String) -> some View { + ScrollView([.horizontal, .vertical]) { + Text(content) + .font(.monospaced(.body)()) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .padding() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.white) + } + + private func createHighlightedTextView(content: String, parsedMap: Wmap.ParsedMap) -> some View { + QuickLookTextEditor(content: content, parsedMap: parsedMap) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.white) + } + deinit { // Cleanup when view controller is deallocated view.subviews.forEach { subview in @@ -129,3 +168,47 @@ struct MapPreviewView: View { } } } + +// A read-only text editor for QuickLook that shows syntax highlighting +struct QuickLookTextEditor: NSViewRepresentable { + let content: String + let parsedMap: Wmap.ParsedMap + + func makeNSView(context: Context) -> NSScrollView { + let scrollView = NSTextView.scrollableTextView() + let textView = scrollView.documentView as! NSTextView + + // Configure the scroll view + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = true + scrollView.autohidesScrollers = false + + // Configure the text view for read-only display + textView.isEditable = false + textView.isSelectable = true + textView.backgroundColor = NSColor.textBackgroundColor + textView.font = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular) + textView.string = content + textView.isVerticallyResizable = true + textView.isHorizontallyResizable = true + textView.textContainer?.widthTracksTextView = false + textView.textContainer?.containerSize = NSSize( + width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + + // Apply syntax highlighting + if let textStorage = textView.textStorage { + let syntaxHighlighter = Wmap.SyntaxHighlighter() + syntaxHighlighter.updateSyntaxElements(from: parsedMap) + + // Apply highlighting to the entire text + let fullRange = NSRange(location: 0, length: textStorage.length) + syntaxHighlighter.applySyntaxHighlighting(textStorage: textStorage, range: fullRange) + } + + return scrollView + } + + func updateNSView(_ nsView: NSScrollView, context: Context) { + // No updates needed for read-only view + } +} diff --git a/QuickLook/QuickLook.entitlements b/QuickLook/QuickLook.entitlements index f2ef3ae..8998767 100644 --- a/QuickLook/QuickLook.entitlements +++ b/QuickLook/QuickLook.entitlements @@ -2,9 +2,13 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>com.apple.security.app-sandbox</key> - <true/> - <key>com.apple.security.files.user-selected.read-only</key> - <true/> + <key>com.apple.security.app-sandbox</key> + <true/> + <key>com.apple.security.application-groups</key> + <array> + <string>group.systems.tranquil.Map</string> + </array> + <key>com.apple.security.files.user-selected.read-only</key> + <true/> </dict> </plist> |