diff --git a/FinderSyncExt/FinderSyncExt.swift b/FinderSyncExt/FinderSyncExt.swift index d5a0454..f48c361 100644 --- a/FinderSyncExt/FinderSyncExt.swift +++ b/FinderSyncExt/FinderSyncExt.swift @@ -193,11 +193,19 @@ class FinderSyncExt: FIFinderSync { menuItem.toolTip = "\(item.name)" menuItem.tag = getUniqueTag(for: item.id) - if let img = NSImage(named: item.icon) { - menuItem.image = img + if let app = item.openApp { + menuItem.image = NSWorkspace.shared.icon(forFile: app.path) } else { - logger.info("") + if !item.icon.starts(with: "icon-") { + menuItem.image = NSImage(systemSymbolName: item.icon, accessibilityDescription: item.icon)! + } else { + if let img = NSImage(named: item.icon) { + menuItem.image = img + } + } + } + submenu.addItem(menuItem) } diff --git a/RClick.xcodeproj/project.pbxproj b/RClick.xcodeproj/project.pbxproj index f767166..6e57ff0 100644 --- a/RClick.xcodeproj/project.pbxproj +++ b/RClick.xcodeproj/project.pbxproj @@ -513,7 +513,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 11.18.2; + CURRENT_PROJECT_VERSION = 11.19.2; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"RClick/Preview Content\""; DEVELOPMENT_TEAM = 4L3563XCBN; @@ -529,8 +529,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.4.5; + MACOSX_DEPLOYMENT_TARGET = 14.6; + MARKETING_VERSION = 1.4.6; PRODUCT_BUNDLE_IDENTIFIER = cn.wflixu.RClick; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -549,7 +549,7 @@ CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 11.18.2; + CURRENT_PROJECT_VERSION = 11.19.2; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"RClick/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -566,8 +566,8 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.4.5; + MACOSX_DEPLOYMENT_TARGET = 14.6; + MARKETING_VERSION = 1.4.6; PRODUCT_BUNDLE_IDENTIFIER = cn.wflixu.RClick; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = mac_app_rclick_distribution_store; @@ -584,7 +584,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 11.18.2; + CURRENT_PROJECT_VERSION = 11.19.2; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 4L3563XCBN; ENABLE_HARDENED_RUNTIME = YES; @@ -598,8 +598,8 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.4.5; + MACOSX_DEPLOYMENT_TARGET = 14.6; + MARKETING_VERSION = 1.4.6; PRODUCT_BUNDLE_IDENTIFIER = cn.wflixu.RClick.FinderSyncExt; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -616,7 +616,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 11.18.2; + CURRENT_PROJECT_VERSION = 11.19.2; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = 4L3563XCBN; @@ -631,8 +631,8 @@ "@executable_path/../Frameworks", "@executable_path/../../../../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.4.5; + MACOSX_DEPLOYMENT_TARGET = 14.6; + MARKETING_VERSION = 1.4.6; PRODUCT_BUNDLE_IDENTIFIER = cn.wflixu.RClick.FinderSyncExt; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/RClick/AppState.swift b/RClick/AppState.swift index b81066e..488bafb 100644 --- a/RClick/AppState.swift +++ b/RClick/AppState.swift @@ -56,6 +56,9 @@ class AppState: ObservableObject { logger.info("save error: \(error.localizedDescription)") } } + + + @MainActor func updateApp(id: String, itemName: String, arguments: [String], environment: [String: String]) { @@ -82,6 +85,19 @@ class AppState: ObservableObject { }) } + @MainActor func addNewFile(_ item: NewFile) { + logger.info("start add new file type") + newFiles.append(item) + + do { + try save() + // 使用 result + } catch { + // 处理错误 + logger.info("save error: \(error.localizedDescription)") + } + } + func getActionItem(rid: String) -> RCAction? { actions.first(where: { rcAtion in rcAtion.id == rid diff --git a/RClick/Localizable.xcstrings b/RClick/Localizable.xcstrings index 25d5e52..c4e75f4 100644 --- a/RClick/Localizable.xcstrings +++ b/RClick/Localizable.xcstrings @@ -109,6 +109,9 @@ } } } + }, + "Add New File Type" : { + }, "Arguments" : { @@ -128,6 +131,9 @@ }, "Cancel" : { + }, + "Change App" : { + }, "Copy Path" : { "extractionState" : "manual", @@ -145,6 +151,9 @@ } } } + }, + "Default Open App" : { + }, "Delete Direct" : { "extractionState" : "manual", @@ -168,6 +177,9 @@ }, "Edit App Properties" : { + }, + "Edit File Type" : { + }, "Enable extension" : { "localizations" : { @@ -184,6 +196,12 @@ }, "Environment:" : { + }, + "Extension" : { + + }, + "File Extension (e.g., .txt)" : { + }, "Format: KEY=VALUE, one per line" : { @@ -198,6 +216,9 @@ } } } + }, + "Icon" : { + }, "Invalid Folder Selection" : { "localizations" : { @@ -218,6 +239,9 @@ } } } + }, + "Name" : { + }, "New File" : { "localizations" : { @@ -274,6 +298,9 @@ } } } + }, + "Preview:" : { + }, "Quit" : { "localizations" : { @@ -329,6 +356,9 @@ }, "Save" : { + }, + "Select App" : { + }, "Settings" : { "localizations" : { @@ -345,6 +375,9 @@ } } } + }, + "SF Symbol name or custom icon" : { + }, "The operation of the menu can only be executed in authorized folders" : { "localizations" : { diff --git a/RClick/Model/RCBase.swift b/RClick/Model/RCBase.swift index 7333012..533900f 100644 --- a/RClick/Model/RCBase.swift +++ b/RClick/Model/RCBase.swift @@ -155,6 +155,8 @@ extension RCAction { static let deleteDirect = RCAction(id: "delete-direct", name: "Delete Direct", idx: 1, icon: "trash") } + +// New File Type struct NewFile: RCBase { static func == (lhs: NewFile, rhs: NewFile) -> Bool { lhs.id == rhs.id @@ -166,8 +168,9 @@ struct NewFile: RCBase { var idx: Int var icon: String var id: String + var openApp: URL? - init(ext: String, name: String, enabled: Bool = true, idx: Int, icon: String, id: String = UUID().uuidString) { + init(ext: String, name: String, enabled: Bool = true, idx: Int, icon: String = "document", id: String = UUID().uuidString) { self.ext = ext self.name = name self.enabled = enabled diff --git a/RClick/Settings/ActionSettingsTabView.swift b/RClick/Settings/ActionSettingsTabView.swift index e26b906..477dd7c 100644 --- a/RClick/Settings/ActionSettingsTabView.swift +++ b/RClick/Settings/ActionSettingsTabView.swift @@ -10,6 +10,8 @@ import SwiftUI struct ActionSettingsTabView: View { @EnvironmentObject var appState: AppState + let messager = Messager.shared + var body: some View { VStack { HStack { @@ -22,7 +24,7 @@ struct ActionSettingsTabView: View { .font(.body) } } - + List { ForEach($appState.actions) { $item in HStack { @@ -35,9 +37,12 @@ struct ActionSettingsTabView: View { Toggle("", isOn: $item.enabled) .onChange(of: item.enabled) { appState.toggleActionItem() + messager.sendMessage(name: "running", data: MessagePayload(action: "running", target: [])) } .toggleStyle(.switch) } + .padding(.top, 12) + .padding(.bottom, 4) } } } diff --git a/RClick/Settings/AppsSettingsTabView.swift b/RClick/Settings/AppsSettingsTabView.swift index fd8956c..eb5f7a9 100644 --- a/RClick/Settings/AppsSettingsTabView.swift +++ b/RClick/Settings/AppsSettingsTabView.swift @@ -17,6 +17,8 @@ struct AppsSettingsTabView: View { @State private var editingArguments: String = "" @State private var editingEnvironment: String = "" + let messager = Messager.shared + var body: some View { ZStack { VStack { @@ -224,6 +226,7 @@ struct AppsSettingsTabView: View { arguments: editingArguments.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }, environment: parseEnvironmentVariables(editingEnvironment) ) + messager.sendMessage(name: "running", data: MessagePayload(action: "running", target: [])) } @MainActor private func deleteApp(_ appItem: OpenWithApp) { @@ -233,5 +236,6 @@ struct AppsSettingsTabView: View { expandedAppId = nil } } + messager.sendMessage(name: "running", data: MessagePayload(action: "running", target: [])) } } diff --git a/RClick/Settings/NewFileSettingsTabView.swift b/RClick/Settings/NewFileSettingsTabView.swift index 17775c5..0b98d0e 100644 --- a/RClick/Settings/NewFileSettingsTabView.swift +++ b/RClick/Settings/NewFileSettingsTabView.swift @@ -6,41 +6,260 @@ // import SwiftUI - +import AppKit struct NewFileSettingsTabView: View { @EnvironmentObject var appState: AppState + @State private var editingFile: NewFile? + @State private var showSelectApp = false + + // 编辑状态 + @State private var editingName: String = "" + @State private var editingExt: String = "" + @State private var editingIcon: String = "document" + @State private var editingOpenApp: URL? + + // 新建状态 + @State private var isAddingNew = false + + let messager = Messager.shared var body: some View { - VStack { - HStack { - Text("New File Type").font(.title2) - Spacer() - Button { - appState.resetFiletypeItems() - } label: { - Label("Reset", systemImage: "arrow.triangle.2.circlepath") - .font(.body) + ZStack { + VStack { + HStack { + Text("New File Type").font(.title2) + Spacer() + Button { + isAddingNew = true + editingFile = NewFile(ext: "", name: "", idx: appState.newFiles.count) + resetEditingFields() + } label: { + Label("Add", systemImage: "plus") + .font(.body) + } + Button { + appState.resetFiletypeItems() + } label: { + Label("Reset", systemImage: "arrow.triangle.2.circlepath") + .font(.body) + } + } + // TODO 编辑 Button 和 Toggle 放在列表的右边 + List { + ForEach($appState.newFiles) { $item in + HStack(spacing: 12) { + // 左侧图标和名称 + HStack { + // 图标显示逻辑 + if let appUrl = item.openApp { + Image(nsImage: NSWorkspace.shared.icon(forFile: appUrl.path())) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 32, height: 32) + } else { + if item.icon.starts(with: "icon-") { + Image(item.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + } else { + Image(systemName: item.icon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + } + } + + HStack(alignment: .center) { + Text(item.name).font(.title3) + Text(item.ext) + .font(.caption) + .foregroundColor(.secondary) + } + } + + Spacer() + + // 右侧按钮组 + HStack(spacing: 16) { + Button { + editingFile = item + editingName = item.name + editingExt = item.ext + editingIcon = item.icon + editingOpenApp = item.openApp + } label: { + Image(systemName: "pencil") + .frame(width: 24, height: 24) + } + .buttonStyle(.plain) + + Toggle("", isOn: $item.enabled) + .onChange(of: item.enabled) { + appState.toggleActionItem() + messager.sendMessage(name: "running", data: MessagePayload(action: "running", target: [])) + + } + .toggleStyle(.switch) + .frame(width: 50) + } + .padding(.trailing, 4) + } + .padding(.vertical, 8) + } } } - List { - ForEach($appState.newFiles) { $item in - HStack { - Image(item.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - Text(item.name).font(.title2) - Spacer() - Toggle("", isOn: $item.enabled) - .onChange(of: item.enabled) { - appState.toggleActionItem() + // 编辑浮层 + if editingFile != nil { + Color.black.opacity(0.3) + .ignoresSafeArea() + .onTapGesture { + cancelEditing() + } + + VStack { + Text(isAddingNew ? "Add New File Type" : "Edit File Type") + .font(.title2) + .padding(.top) + + VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading) { + Text("Name").font(.headline) + TextField("Display Name", text: $editingName) + .textFieldStyle(.roundedBorder) + } + + VStack(alignment: .leading) { + Text("Extension").font(.headline) + TextField("File Extension (e.g., .txt)", text: $editingExt) + .textFieldStyle(.roundedBorder) + } + + VStack(alignment: .leading) { + Text("Icon").font(.headline) + TextField("SF Symbol name or custom icon", text: $editingIcon) + .textFieldStyle(.roundedBorder) + + if !editingIcon.isEmpty { + HStack { + Text("Preview:") + if editingIcon.starts(with: "icon-") { + Image(editingIcon) + .resizable() + .frame(width: 20, height: 20) + } else { + Image(systemName: editingIcon) + .resizable() + .frame(width: 20, height: 20) + } + } + } + } + + VStack(alignment: .leading) { + Text("Default Open App").font(.headline) + HStack { + if let appUrl = editingOpenApp { + Image(nsImage: NSWorkspace.shared.icon(forFile: appUrl.path())) + .resizable() + .frame(width: 20, height: 20) + Text(appUrl.lastPathComponent) + } + + Button { + showSelectApp = true + } label: { + Text(editingOpenApp == nil ? "Select App" : "Change App") + } + + if editingOpenApp != nil { + Button { + editingOpenApp = nil + } label: { + Image(systemName: "xmark.circle.fill") + } + .buttonStyle(.plain) + } } - .toggleStyle(.switch) + } } + .padding() + + HStack { + Button("Cancel") { + cancelEditing() + } + .keyboardShortcut(.escape) + + Button(isAddingNew ? "Add" : "Save") { + saveChanges() + } + .keyboardShortcut(.return) + .disabled(editingName.isEmpty || editingExt.isEmpty) + } + .padding(.bottom) } + .frame(width: 400) + .background(Color(NSColor.windowBackgroundColor)) + .cornerRadius(12) + .shadow(radius: 10) + } + } + .fileImporter( + isPresented: $showSelectApp, + allowedContentTypes: [.application], + allowsMultipleSelection: false + ) { result in + switch result { + case .success(let files): + if let url = files.first { + editingOpenApp = url + } + case .failure(let error): + print(error) + } + } + } + + private func resetEditingFields() { + editingName = "" + editingExt = "" + editingIcon = "document" + editingOpenApp = nil + } + + private func cancelEditing() { + editingFile = nil + isAddingNew = false + resetEditingFields() + } + + private func saveChanges() { + if isAddingNew { + var newFile = NewFile( + ext: editingExt, + name: editingName, + idx: appState.newFiles.count, + icon: editingIcon + ) + if let app = editingOpenApp { + newFile.openApp = app } + appState.addNewFile(newFile) + } else if let file = editingFile, + let index = appState.newFiles.firstIndex(where: { $0.id == file.id }) { + var updatedFile = file + updatedFile.name = editingName + updatedFile.ext = editingExt + updatedFile.icon = editingIcon + updatedFile.openApp = editingOpenApp + appState.newFiles[index] = updatedFile } + +// try? appState.save() + messager.sendMessage(name: "running", data: MessagePayload(action: "running", target: [])) + cancelEditing() } } diff --git a/RClick/Settings/SettingsView.swift b/RClick/Settings/SettingsView.swift index 0e4fb6c..440221e 100644 --- a/RClick/Settings/SettingsView.swift +++ b/RClick/Settings/SettingsView.swift @@ -48,14 +48,14 @@ struct SettingsView: View { HStack { Spacer() Text("RClick").font(.title) - Text("\(getAppVersion())") + Text("\(self.getAppVersion())") Spacer() } } .padding(.vertical) // 导航列表 - List(Tabs.allCases, selection: $selectedTab) { tab in + List(Tabs.allCases, selection: self.$selectedTab) { tab in HStack { Label(tab.rawValue, systemImage: tab.icon) .font(.title2) @@ -64,13 +64,13 @@ struct SettingsView: View { Spacer(minLength: 0) } .listRowInsets(.init(top: 0, - leading: -16, - bottom: 0, - trailing: -16)) - .background(tab == selectedTab ? Color.accentColor.opacity(0.3) : Color.clear) + leading: -16, + bottom: 0, + trailing: -16)) + .background(tab == self.selectedTab ? Color.accentColor.opacity(0.3) : Color.clear) .contentShape(Rectangle()) .onTapGesture { - selectedTab = tab + self.selectedTab = tab } } .listStyle(.sidebar) @@ -85,13 +85,13 @@ struct SettingsView: View { // 右侧内容 Group { - switch selectedTab { + switch self.selectedTab { case .general: GeneralSettingsTabView() case .apps: AppsSettingsTabView() case .actions: - actionsSection + ActionSettingsTabView() case .newFile: NewFileSettingsTabView() case .about: @@ -104,39 +104,7 @@ struct SettingsView: View { .frame(width: 800, height: 500) } - // Actions 部分 - private var actionsSection: some View { - VStack { - HStack { - Text("Action Items").font(.title2) - Spacer() - Button { - appState.resetActionItems() - } label: { - Label("Reset", systemImage: "arrow.triangle.2.circlepath") - .font(.body) - } - } - - List { - ForEach($appState.actions) { $item in - HStack { - Image(systemName: item.icon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - Text(LocalizedStringKey(item.name)).font(.title2) - Spacer() - Toggle("", isOn: $item.enabled) - .onChange(of: item.enabled) { - appState.toggleActionItem() - } - .toggleStyle(.switch) - } - } - } - } - } + func getAppVersion() -> String { if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { diff --git a/RClick/Settings/SettingsWindow.swift b/RClick/Settings/SettingsWindow.swift index fe98e12..af49bde 100644 --- a/RClick/Settings/SettingsWindow.swift +++ b/RClick/Settings/SettingsWindow.swift @@ -24,6 +24,7 @@ struct SettingsWindow: Scene { .frame(minWidth: 800, minHeight: 500) } .windowStyle(.hiddenTitleBar) + .windowResizability(.contentSize) .defaultSize(width: 800, height: 500) } diff --git a/images/store-preview.png b/images/store-preview.png index 134ea7a..16d045a 100644 Binary files a/images/store-preview.png and b/images/store-preview.png differ diff --git a/images/store-view2.png b/images/store-view2.png deleted file mode 100644 index 4891547..0000000 Binary files a/images/store-view2.png and /dev/null differ