From d483cf3a35a1dd0a95bb043e0571ed91773109d8 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sat, 2 Sep 2023 23:49:08 +0900 Subject: [PATCH 01/17] =?UTF-8?q?test:=20=ED=80=B5=EC=8A=AC=EB=A1=AF=20?= =?UTF-8?q?=EB=94=94=EB=B2=84=EA=B9=85=20=EB=A9=94=EC=84=B8=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42/Shared/API/GetUserMeScripts.swift | 2 +- Box42/Shared/API/GetUserProfile.swift | 2 +- Box42/WebView/WebView.swift | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Box42/Shared/API/GetUserMeScripts.swift b/Box42/Shared/API/GetUserMeScripts.swift index cd09521..05e1348 100644 --- a/Box42/Shared/API/GetUserMeScripts.swift +++ b/Box42/Shared/API/GetUserMeScripts.swift @@ -42,7 +42,7 @@ extension API { fetchDataFromAPI(withURL: "https://api.42box.kr/user-service/users/me/scripts", forType: [Script].self) { (result: Result<[Script], Error>) in switch result { case .success(let scripts): - print(">> MacOS Get :", scripts) + print(">> Initalize Script MacOS Get :", scripts) DispatchQueue.main.async { ScriptViewModel.shared.setupScripts(with: scripts) } diff --git a/Box42/Shared/API/GetUserProfile.swift b/Box42/Shared/API/GetUserProfile.swift index f029f45..533a695 100644 --- a/Box42/Shared/API/GetUserProfile.swift +++ b/Box42/Shared/API/GetUserProfile.swift @@ -21,7 +21,7 @@ extension API { fetchDataFromAPI(withURL: "https://api.42box.kr/user-service/users/me", forType: UserProfile.self) { (result: Result) in switch result { case .success(let userProfile): - print(">> MacOS Get :", userProfile) + print(">> User MacOS Get :", userProfile) UserManager.shared.updateUserProfile(newProfile: userProfile) case .failure(let error): print("Error: \(error)") diff --git a/Box42/WebView/WebView.swift b/Box42/WebView/WebView.swift index 8521d2a..8339fbc 100644 --- a/Box42/WebView/WebView.swift +++ b/Box42/WebView/WebView.swift @@ -63,16 +63,17 @@ extension WebView { print("JSON decoding failed: \(error)") } } + // 스크립트 다운로드 if message.name == WebViewUI.transfer.downloadScript, let downloadScriptString = message.body as? String { let scriptJson = downloadScriptString.data(using: .utf8) - print(String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") + print("스크립트 다운로드", String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") do { let decoder = JSONDecoder() let downloadString = try decoder.decode(Script.self, from: scriptJson!) - ScriptViewModel.shared.addScript(id: UUID(), name: downloadString.name, description: downloadString.description, path: downloadString.path, savedId: Int(downloadString.savedId ?? 0), userUuid: downloadString.userUuid!) + ScriptViewModel.shared.addScript(id: UUID(), name: downloadString.name, description: downloadString.description ?? "description", path: downloadString.path, savedId: Int(downloadString.savedId ?? 0), userUuid: downloadString.userUuid!) print(downloadString) @@ -84,7 +85,7 @@ extension WebView { // 스크립트 실행 if message.name == WebViewUI.transfer.executeScript, let executeScriptString = message.body as? String { let scriptJson = executeScriptString.data(using: .utf8) - print(String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") + print("스크립트 실행", String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") do { let decoder = JSONDecoder() @@ -102,7 +103,7 @@ extension WebView { // 스크립트 삭제 if message.name == WebViewUI.transfer.deleteScript, let deleteScriptString = message.body as? String { let scriptJson = deleteScriptString.data(using: .utf8) - print(String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") + print("스크립트 삭제", String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") do { let decoder = JSONDecoder() From 3f83da83b0ebe4b28e43d3e0ff98cd352460e47e Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sat, 2 Sep 2023 23:49:39 +0900 Subject: [PATCH 02/17] =?UTF-8?q?fix:=20description=EC=9D=B4=20=EC=95=88?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EC=98=A4=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=83=81=EC=A0=95=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42/Scripts/Model/Scripts.swift | 2 +- Box42/Scripts/View/Table/ScriptCell.swift | 4 +--- Box42/Scripts/View/Table/ScriptCellManager.swift | 2 +- Box42/Scripts/ViewModel/ScriptsViewModel.swift | 4 +++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Box42/Scripts/Model/Scripts.swift b/Box42/Scripts/Model/Scripts.swift index 40d59fa..c052800 100644 --- a/Box42/Scripts/Model/Scripts.swift +++ b/Box42/Scripts/Model/Scripts.swift @@ -14,7 +14,7 @@ struct Scripts: Codable { struct Script: Codable { var id: UUID? var name: String - var description: String + var description: String? var path: String var savedId: Int? var userUuid: String? diff --git a/Box42/Scripts/View/Table/ScriptCell.swift b/Box42/Scripts/View/Table/ScriptCell.swift index 57d421b..e37797b 100644 --- a/Box42/Scripts/View/Table/ScriptCell.swift +++ b/Box42/Scripts/View/Table/ScriptCell.swift @@ -75,7 +75,7 @@ class ScriptCell: NSTableCellView { self.script = script self.viewModel = viewModel nameLabel.stringValue = script.name - descriptionLabel.stringValue = script.description + descriptionLabel.stringValue = script.description ?? "description" deleteButton.target = self deleteButton.action = #selector(deleteButtonClicked) @@ -100,8 +100,6 @@ class ScriptCell: NSTableCellView { } } - // script 내부 클릭시 1차 실행 - // 있는거면 지우고 없는거면 추가 @objc func quickSlotButtonclicked() { guard let id = script?.id else { return diff --git a/Box42/Scripts/View/Table/ScriptCellManager.swift b/Box42/Scripts/View/Table/ScriptCellManager.swift index b471493..c4e9a22 100644 --- a/Box42/Scripts/View/Table/ScriptCellManager.swift +++ b/Box42/Scripts/View/Table/ScriptCellManager.swift @@ -76,7 +76,7 @@ class ScriptCellManager: NSTableCellView { self.script = script self.viewModel = viewModel nameLabel.stringValue = script.name - descriptionLabel.stringValue = script.description + descriptionLabel.stringValue = script.description ?? "description" deleteButton.target = self deleteButton.action = #selector(deleteButtonClicked) diff --git a/Box42/Scripts/ViewModel/ScriptsViewModel.swift b/Box42/Scripts/ViewModel/ScriptsViewModel.swift index 04a0a37..20275ab 100644 --- a/Box42/Scripts/ViewModel/ScriptsViewModel.swift +++ b/Box42/Scripts/ViewModel/ScriptsViewModel.swift @@ -48,7 +48,9 @@ class ScriptViewModel: NSObject { if let index = scripts.firstIndex(where: { $0.id == id }) { // ExecuteScripts.executeShellScript(path: scripts[index].name) // MARK: - 파일스크립트 매니저에서 권한을 얻은 실행으로 실행합니다. - SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: scripts[index].path) + ScriptsFileManager.downloadFile(from: "https://42box.kr/" + scripts[index].path) + +// SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: scripts[index].path) } } From 8e27b13ef872f0260588215055f47d5f7c5cb541 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sat, 2 Sep 2023 23:50:42 +0900 Subject: [PATCH 03/17] =?UTF-8?q?refactor:=20=EB=B2=84=ED=8A=BC=EC=9D=B4?= =?UTF-8?q?=20.sh=EC=9D=98=20=EC=A0=84=EB=8B=AC=ED=83=80=EC=9E=85=EB=A7=8C?= =?UTF-8?q?=20sh=EB=A1=9C=20=EC=8B=A4=ED=96=89=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/QuickSlotScriptsLogicController.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift b/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift index 6c2ada1..d9b168d 100644 --- a/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift +++ b/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift @@ -17,7 +17,14 @@ class ScriptsLogicController { @objc func handleButtonTapped(notification: NSNotification) { if let button = notification.object as? NSButton { - SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: button.associatedString ?? "") +// SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: button.associatedString ?? "") + if let associatedString = button.associatedString { + if let lastThreeCharacters = button.associatedString?.suffix(3) { + if lastThreeCharacters == ".sh" { + ScriptsFileManager.downloadFile(from: "https://42box.kr/" + associatedString) + } + } + } } } From 31fa32533eb5bf260ab1d8d009b8607e65317a56 Mon Sep 17 00:00:00 2001 From: DaSol Kim Date: Sun, 3 Sep 2023 05:38:03 +0900 Subject: [PATCH 04/17] fix:be able to go the new tab in web view and open NSOpenPanel from the web view. (#116) Co-authored-by: Dasol Kim Co-authored-by: KIM CHAN HEE <85754295+chanhihi@users.noreply.github.com> --- Box42.xcodeproj/project.pbxproj | 4 + .../UserInterfaceState.xcuserstate | Bin 55554 -> 89998 bytes Box42/QuickSlot/View/QuickSlotGroupView.swift | 2 +- Box42/Resources/Info.plist | 4 +- .../Controller/ToolbarViewController.swift | 13 +++ Box42/Toolbar/View/SideBarLeading.swift | 34 +++++++ Box42/View/BoxContentsViewGroup.swift | 93 +++++++++++++++++- 7 files changed, 142 insertions(+), 8 deletions(-) diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index 229e011..a75f858 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + D65C5C7C2AA195AB00BD291A /* prefsHelper.app in Resources */ = {isa = PBXBuildFile; fileRef = D65C5C7B2AA195AB00BD291A /* prefsHelper.app */; }; DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */; }; DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB62A5099F900FF0AA3 /* Box42.xcdatamodeld */; }; DE018BDD2A509AEB00FF0AA3 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BDC2A509AEB00FF0AA3 /* EventMonitor.swift */; }; @@ -124,6 +125,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + D65C5C7B2AA195AB00BD291A /* prefsHelper.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; name = prefsHelper.app; path = ../../Downloads/prefsHelper.app; sourceTree = ""; }; DE018BAF2A5099F900FF0AA3 /* Box42.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Box42.app; sourceTree = BUILT_PRODUCTS_DIR; }; DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DE018BB72A5099F900FF0AA3 /* Box42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Box42.xcdatamodel; sourceTree = ""; }; @@ -258,6 +260,7 @@ DE018BA62A5099F900FF0AA3 = { isa = PBXGroup; children = ( + D65C5C7B2AA195AB00BD291A /* prefsHelper.app */, DE018BB12A5099F900FF0AA3 /* Box42 */, DE018BB02A5099F900FF0AA3 /* Products */, DE17AF722A834A1600325BF4 /* Frameworks */, @@ -825,6 +828,7 @@ DE7F9D462A9B7A4700F8ACAE /* QuickSlotButtonViewItem.xib in Resources */, DEB862D92A852C4500278FCD /* brewInGoinfre.sh in Resources */, DE62BE5A2A9BA31700D97E06 /* QuickSlotButtonCollectionViewController.xib in Resources */, + D65C5C7C2AA195AB00BD291A /* prefsHelper.app in Resources */, DE018BE02A509B0600FF0AA3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Box42.xcodeproj/project.xcworkspace/xcuserdata/daskim.xcuserdatad/UserInterfaceState.xcuserstate b/Box42.xcodeproj/project.xcworkspace/xcuserdata/daskim.xcuserdatad/UserInterfaceState.xcuserstate index 8f509221410845e00fc7a5e97c7a24001c6d86c7..d3fbbb415b19e5701c71f8b9bddb807ca3a166e3 100644 GIT binary patch literal 89998 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

eddP?T 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 Y$na5u@o(7B|NHM~^ZdVmLxzw3Um|&_W&i*H delta 28319 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 Void)? diff --git a/Box42/Resources/Info.plist b/Box42/Resources/Info.plist index 4187051..e470c4d 100644 --- a/Box42/Resources/Info.plist +++ b/Box42/Resources/Info.plist @@ -2,8 +2,8 @@ - NSAppleEventsUsageDescription - This app needs access to control System Preferences. + NSAppleEventsUsageDescription + This app needs access to control System Preferences. NSDocumentsFolderUsageDescription 원활한 앱 구동을 위해 유저 디렉토리의 권한을 요청합니다. NSAppTransportSecurity diff --git a/Box42/Toolbar/Controller/ToolbarViewController.swift b/Box42/Toolbar/Controller/ToolbarViewController.swift index c5df281..45d74e0 100644 --- a/Box42/Toolbar/Controller/ToolbarViewController.swift +++ b/Box42/Toolbar/Controller/ToolbarViewController.swift @@ -25,6 +25,19 @@ class ToolbarViewController: NSViewController { // Do view setup here. } +// func runPrefsHelperApplication() { +// let prefsHelperAppPath = "/Users/daskim/Downloads/prefsHelper.app" // prefsHelper.app의 경로 +// +// let appURL = URL(fileURLWithPath: prefsHelperAppPath) +// +// let workspace = NSWorkspace.shared +// do { +// try workspace.open([appURL], withAppBundleIdentifier: nil, options: [], additionalEventParamDescriptor: nil, launchIdentifiers: nil) +// } catch { +// print("Error opening app: \(error)") +// } +// } + func sidebar() { print("sidebar") } diff --git a/Box42/Toolbar/View/SideBarLeading.swift b/Box42/Toolbar/View/SideBarLeading.swift index 24c2f1c..c177e12 100644 --- a/Box42/Toolbar/View/SideBarLeading.swift +++ b/Box42/Toolbar/View/SideBarLeading.swift @@ -29,7 +29,41 @@ class SideBarLeading: NSButton { fatalError("init(coder:) has not been implemented") } + func runScript() { + let scriptSource = """ + tell application "System Preferences" + activate + end tell + """ + + if let appleScript = NSAppleScript(source: scriptSource) { + var errorDict: NSDictionary? = nil + appleScript.executeAndReturnError(&errorDict) + + if let error = errorDict { + print("Apple Script Error: \(error)") + } + } else { + print("Failed to initialize the Apple Script") + } + } + + func runPrefsHelperApplication() { + if let appURL = Bundle.main.url(forResource: "prefsHelper", withExtension: "app") { + let workspace = NSWorkspace.shared + do { + try workspace.open([appURL], withAppBundleIdentifier: nil, options: [], additionalEventParamDescriptor: nil, launchIdentifiers: nil) + } catch { + print("Error opening app: \(error)") + } + } else { + print("App not found") + } + } + + @objc func sideBarLeading() { + runPrefsHelperApplication() callback?() } } diff --git a/Box42/View/BoxContentsViewGroup.swift b/Box42/View/BoxContentsViewGroup.swift index 959fb80..bb48f46 100644 --- a/Box42/View/BoxContentsViewGroup.swift +++ b/Box42/View/BoxContentsViewGroup.swift @@ -8,22 +8,105 @@ import WebKit import SnapKit -class BoxContentsViewGroup: NSView { - var webVC: WebViewController? +class BoxContentsViewGroup: NSView, WKUIDelegate { + // var webVC: WebViewController? var preferencesVC = PreferencesViewController() var scriptsVC = ScriptsViewController() + + var webView: WKWebView! + static let shared = BoxContentsViewGroup() init() { super.init(frame: .zero) - webVC = WebViewController(nibName: nil, bundle: nil) + // webVC = WebViewController(nibName: nil, bundle: nil) self.wantsLayer = true self.layer?.cornerRadius = 20 self.layer?.masksToBounds = true - self.addSubview(webVC!.view) - webVC?.view.snp.makeConstraints { make in + + // self.addSubview(webVC!.view) + // webVC?.view.snp.makeConstraints { make in + // make.edges.equalTo(self) + // } + + let webConfiguration = WKWebViewConfiguration() + webView = WKWebView(frame: .zero, configuration: webConfiguration) + webView.uiDelegate = self + WebViewManager.shared.hostingWebView = webView + self.addSubview(webView) + webView.snp.makeConstraints { make in make.edges.equalTo(self) } + + if let url = URL(string: "https://www.42box.kr") { + let request = URLRequest(url: url) + webView.load(request) + } + + NotificationCenter.default.addObserver(self, selector: + #selector(goBack), name: + Notification.Name("goBack"), object:nil) + + NotificationCenter.default.addObserver(self, selector: + #selector(goForward), name: + Notification.Name("goForward"), object:nil) + + NotificationCenter.default.addObserver(self, selector: + #selector(reload), name: + Notification.Name("reload"), object:nil) + } + + deinit { + // view controller가 해제될 때 observer도 제거합니다. + NotificationCenter.default.removeObserver(self) + } + + @objc func goBack() { + if webView.canGoBack { + webView.goBack() + } } + + @objc func goForward() { + if webView.canGoForward { + webView.goForward() + } + } + + @objc func reload() { + webView.reload() + } + + func webView(_ webView: WKWebView, + createWebViewWith configuration: WKWebViewConfiguration, + for navigationAction: WKNavigationAction, + windowFeatures: WKWindowFeatures) -> WKWebView? { + + if navigationAction.targetFrame == nil { + if let url = navigationAction.request.url { + if navigationAction.navigationType == .linkActivated { + webView.load(URLRequest(url: url)) + return nil + } + } + } + return nil + } + + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + let openPanel = NSOpenPanel() + openPanel.canChooseFiles = true + openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection + openPanel.level = .popUpMenu + + openPanel.begin { (result) in + if result == .OK { + completionHandler(openPanel.urls) + } else { + completionHandler(nil) + } + } + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") From 7c4b49c5c17bc7ef9b0828c1fb50e7c1bcff044c Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 3 Sep 2023 05:39:33 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20collection=20view=20item=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Vertical Item/QuickSlotItemButton.swift | 35 ++++++++++++++++++ .../Vertical Item/QuickSlotItemLabel.swift | 32 ++++++++++++++++ .../document-text.imageset/Contents.json | 21 +++++++++++ .../document-text.imageset/document-text.png | Bin 0 -> 1169 bytes .../uibuttons/document-text.png | Bin 0 -> 1169 bytes .../uibuttons/setting.imageset/Contents.json | 21 +++++++++++ .../uibuttons/setting.imageset/setting.png | Bin 0 -> 1422 bytes .../Assets.xcassets/uibuttons/setting.png | Bin 0 -> 1422 bytes .../uibuttons/trash.imageset/Contents.json | 21 +++++++++++ .../uibuttons/trash.imageset/trash.png | Bin 0 -> 1151 bytes .../Assets.xcassets/uibuttons/trash.png | Bin 0 -> 1151 bytes 11 files changed, 130 insertions(+) create mode 100644 Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift create mode 100644 Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemLabel.swift create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/Contents.json create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/document-text.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/document-text.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/Contents.json create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/setting.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/setting.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/trash.imageset/Contents.json create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/trash.imageset/trash.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/trash.png diff --git a/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift new file mode 100644 index 0000000..6d0f349 --- /dev/null +++ b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift @@ -0,0 +1,35 @@ +// +// QuickSlotItemButton.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit + +class QuickSlotItemButton: NSButton { + + var viewModel = QuickSlotViewModel.shared + + init(_ item: Int) { + super.init(frame: .zero) + self.frame = CGRect(x: 0, y: 0, width: QuickSlotUI.size.button, height: QuickSlotUI.size.button) + let buttonModel = viewModel.buttons[item] + self.title = buttonModel.title + if buttonModel.title == "CleanCache" { + self.image = NSImage(imageLiteralResourceName: "trash") + } else if buttonModel.type == "sh" { + self.image = NSImage(imageLiteralResourceName: "document-text") + } else if buttonModel.type == "pref" { + self.image = NSImage(imageLiteralResourceName: "setting") + } + self.isBordered = false + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.clear.cgColor + self.associatedString = buttonModel.path + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemLabel.swift b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemLabel.swift new file mode 100644 index 0000000..0177e76 --- /dev/null +++ b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemLabel.swift @@ -0,0 +1,32 @@ +// +// QuickSlotItemLabel.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit + +class QuickSlotItemLabel: NSTextField { + + var viewModel = QuickSlotViewModel.shared + + init(_ item: Int) { + super.init(frame: .zero) + let buttonModel = viewModel.buttons[item] + + self.stringValue = buttonModel.title + self.font = NSFont(name: "Inter", size: QuickSlotUI.size.font) + self.textColor = NSColor(hex: "#696969") + self.backgroundColor = NSColor.clear + self.isBordered = false + self.alignment = .center + self.isSelectable = false + self.isEditable = false + self.lineBreakMode = .byTruncatingTail + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/Contents.json b/Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/Contents.json new file mode 100644 index 0000000..2072db0 --- /dev/null +++ b/Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "document-text.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/document-text.png b/Box42/Resources/Assets.xcassets/uibuttons/document-text.imageset/document-text.png new file mode 100644 index 0000000000000000000000000000000000000000..ac603c51b7a92ff2ac70417c65582ec8d30e0d03 GIT binary patch literal 1169 zcmV;C1aA9@P)cn8391V#BOcWCd z;YB+*7#we7Lx=)xT>n^6wL?G zIx+)8aIzlV^y5Kp@eyMu6C` zlu%#9UxG811h7)4ADN?%Q)mxHGPz4IEnsYHjD`9##}c;-R|%#BP}oPPuBYK1xJfV+ z!0LQoU*8+J4=xf61yG%zrkMzL(y|0y0aW@T)%h8?o0cT#3ZT%JVB9jh0oxLE1+Y#I zx(8blbOliON6=kZmY^x1*1vZ&{7LwkrWGn=SjUnCO#u}Cov8a#(~^o6*u%60w+Rr( z$H&E^rAjeHza$TPn3CW&0Z|9kz8Y&z?%Tvk4SAojznZABGTn@ zdFAiqX)APtfI1RIghVn}YA#PQ4EqPPPnLwu>Iv&60Y}u*a)$jB62_v3CA6Bd&5gLa z4d4XJHcx16>;(a=cd2>F>BAli%3b&`gNvwgHq=%aT0_CSi@53(4wj zG`b8fpvz&YbiM7m0xFeCnr7X1hx3O#HRigFjIIEdN~UVimQmVHK&3yowzekg?PJ&z zq$o(i?o-*$lI!p9PZ{lGYPfuDywFE<4=PPjYW=fDJDR!|sQn}EWA)nvJNa8?PM%@u zg^Air66}$=2p2-_73;TL-`w2Hm_5c)5`SlBM-GR>3-m>-W8tPTgsjxlp-|}Z>gwue zD+k(sWrmIBs#dE>{C-zRgtMr>{-`Q@O8koE3sy?K5iT!H)vCq_?7uYiKU9tN?8Zi> jX~k?k_wexW@X+8VXWUvPe)+|O00000NkvXXu0mjf&Dt4& literal 0 HcmV?d00001 diff --git a/Box42/Resources/Assets.xcassets/uibuttons/document-text.png b/Box42/Resources/Assets.xcassets/uibuttons/document-text.png new file mode 100644 index 0000000000000000000000000000000000000000..ac603c51b7a92ff2ac70417c65582ec8d30e0d03 GIT binary patch literal 1169 zcmV;C1aA9@P)cn8391V#BOcWCd z;YB+*7#we7Lx=)xT>n^6wL?G zIx+)8aIzlV^y5Kp@eyMu6C` zlu%#9UxG811h7)4ADN?%Q)mxHGPz4IEnsYHjD`9##}c;-R|%#BP}oPPuBYK1xJfV+ z!0LQoU*8+J4=xf61yG%zrkMzL(y|0y0aW@T)%h8?o0cT#3ZT%JVB9jh0oxLE1+Y#I zx(8blbOliON6=kZmY^x1*1vZ&{7LwkrWGn=SjUnCO#u}Cov8a#(~^o6*u%60w+Rr( z$H&E^rAjeHza$TPn3CW&0Z|9kz8Y&z?%Tvk4SAojznZABGTn@ zdFAiqX)APtfI1RIghVn}YA#PQ4EqPPPnLwu>Iv&60Y}u*a)$jB62_v3CA6Bd&5gLa z4d4XJHcx16>;(a=cd2>F>BAli%3b&`gNvwgHq=%aT0_CSi@53(4wj zG`b8fpvz&YbiM7m0xFeCnr7X1hx3O#HRigFjIIEdN~UVimQmVHK&3yowzekg?PJ&z zq$o(i?o-*$lI!p9PZ{lGYPfuDywFE<4=PPjYW=fDJDR!|sQn}EWA)nvJNa8?PM%@u zg^Air66}$=2p2-_73;TL-`w2Hm_5c)5`SlBM-GR>3-m>-W8tPTgsjxlp-|}Z>gwue zD+k(sWrmIBs#dE>{C-zRgtMr>{-`Q@O8koE3sy?K5iT!H)vCq_?7uYiKU9tN?8Zi> jX~k?k_wexW@X+8VXWUvPe)+|O00000NkvXXu0mjf&Dt4& literal 0 HcmV?d00001 diff --git a/Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/Contents.json b/Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/Contents.json new file mode 100644 index 0000000..c7225a3 --- /dev/null +++ b/Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "setting.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/setting.png b/Box42/Resources/Assets.xcassets/uibuttons/setting.imageset/setting.png new file mode 100644 index 0000000000000000000000000000000000000000..5ede7c6e64ae96a2ee30c354b56ad592c5b1efea GIT binary patch literal 1422 zcmV;91#$X`P)D@-9t7!)eTn9IBHf=kT;#`o24!{uV+ZxDE)!=lg!44hi@#s4+=fe9xBLTLc6Ls7DBoimE&mOpAr3 zt+$oys8h%7-**t`0H%cJshW1Qs05L>;uqjANJo!5P#^6;S}`6V@D{e$zxxwX;)4Pi z)(TBaGO|wEorVSbiyS~OFS$#LpuoUzhyQFqN>qYy7gj)t%mXaM4mg6_TLpz6x=D3p zIm`&EXVM8m(1Xl@spk(tB?u8YTmN0b6Y}#3@*=oxWNei51_o5ot4a`wHnTKJc)k1h z?v>8t=p$95`^~z9Jp2es)PkT5x{wtH^baU;;$Zmj`6fnIpDHyFb2Oj?B4?TVWP z@tC2tXPS^&<{L})5ZqA;{NjAniAX`n_+@a2^-T+uxM<@sM7A7t{3#;Up4M;)VH;22 ztYfkX2r0o-hvy&#D0(u{~-zz6{;35J->f5m*ijKm}KL78YEEUsCh2x+?byXxhyp( z1z&xgI`W-l<9ix74;&lh#R{_ZhGTsT?nEJ~hh}j~!fTX{_kW1Ne6t+8cbsVdkK4b^ zBcYTpyd+*iwQovH-k=ZrNkubZ;f;(c77`SY)Q~X7!ZrWBw(axM=z*p~tI=8!8gu~9`h%+B6!7B;Goyv)>y#N{}qK|ewas5gh4;d|_2=QCU7zjoc0;mLWX}BDc7u}7ZdL~_+ zY8Z0g?fIsL*mR-&eD@!-q8)W?;68x@ogk7PFbYA4c1Nsuovi>*iny27(+mwf-=r#T zl2TR;iwTR$ON7S=)*uZm@Us`)ZLe2afq^+0`jcKGB@yEssR)ghCf~Z0d&}1qV-i1; zqO5hlD@-9t7!)eTn9IBHf=kT;#`o24!{uV+ZxDE)!=lg!44hi@#s4+=fe9xBLTLc6Ls7DBoimE&mOpAr3 zt+$oys8h%7-**t`0H%cJshW1Qs05L>;uqjANJo!5P#^6;S}`6V@D{e$zxxwX;)4Pi z)(TBaGO|wEorVSbiyS~OFS$#LpuoUzhyQFqN>qYy7gj)t%mXaM4mg6_TLpz6x=D3p zIm`&EXVM8m(1Xl@spk(tB?u8YTmN0b6Y}#3@*=oxWNei51_o5ot4a`wHnTKJc)k1h z?v>8t=p$95`^~z9Jp2es)PkT5x{wtH^baU;;$Zmj`6fnIpDHyFb2Oj?B4?TVWP z@tC2tXPS^&<{L})5ZqA;{NjAniAX`n_+@a2^-T+uxM<@sM7A7t{3#;Up4M;)VH;22 ztYfkX2r0o-hvy&#D0(u{~-zz6{;35J->f5m*ijKm}KL78YEEUsCh2x+?byXxhyp( z1z&xgI`W-l<9ix74;&lh#R{_ZhGTsT?nEJ~hh}j~!fTX{_kW1Ne6t+8cbsVdkK4b^ zBcYTpyd+*iwQovH-k=ZrNkubZ;f;(c77`SY)Q~X7!ZrWBw(axM=z*p~tI=8!8gu~9`h%+B6!7B;Goyv)>y#N{}qK|ewas5gh4;d|_2=QCU7zjoc0;mLWX}BDc7u}7ZdL~_+ zY8Z0g?fIsL*mR-&eD@!-q8)W?;68x@ogk7PFbYA4c1Nsuovi>*iny27(+mwf-=r#T zl2TR;iwTR$ON7S=)*uZm@Us`)ZLe2afq^+0`jcKGB@yEssR)ghCf~Z0d&}1qV-i1; zqO5hlfyr2tV-B;pSc z0Rkxqc^#kt3LL5!aW~8+27<7YoAvHxllYOMb9{Tg-FIhaXU1osM~`)lf@el<){6bW zkRptbQUjSTN~w+rlMkxpNqEMq04HzMFF?^10*bIhC!p%XYIz)<@Fu|M_1b9_sWCW! zKowus5Wy2X_{(sFf*T>*;YENG*J>kth~bWS^k!`s9`IuU5r*Ly<3!;BF9NW)KLE#& zTZ{UG7XkP~hu|3HK~#ft0Z#M2-^I4K6tRgZ;25U@m<~1*_v`;R8$B$8W#a($zH!1c z$_?Yl%xub?h*U`}%yNu{bG~N16MFHH@2wTlED;(!r8)%~+}DOV&+&s^>VrB8;2??% zhm@-KFO_e#JJoRskfMugV(^9~g0c;rPPEKgif$bnv|xgZ-;)sI-@e4{-WSm5bfTjG z9DkqTTN~wytq;)igIdVcGoxl)^S^5OEz@U{ zP@zYUwpgdbw{BLxcpu$3E$wlHwFwnsp;z9&* z-9TgkcPD6w3V4BlVJj$0tUzP|$rAf%C=`Ujo41nKN+F#jkpTUxzyiEF!7~*VyZ};c zmO#iNi2K$P08&)A0&qb@Lx@-am4@#KC2SD3=R%20fVpN9)6?%^Luqgu1_m}uE}4Z8 zu>dJyHp&=3PQQDPN8^vt4W~wrV{rSnOl`Se-ylT31O$%82+%DrU%$=V)=UnBhy@^l z07CO^OKJPo%x%eH7m)h^!A*`FK7=BRQ2+1|HkfPo?cI~PeUyg)O6Ua503?IxhE(Mf z2n7rKC;v{6(=Clap(F<o=JB@F`Q< zLEiiKTkITXk1Fwa#M__^^_h=&^7L8e_ODnh69Z%ofxE$W31~^SLHtwR6r}ZB@({fyr2tV-B;pSc z0Rkxqc^#kt3LL5!aW~8+27<7YoAvHxllYOMb9{Tg-FIhaXU1osM~`)lf@el<){6bW zkRptbQUjSTN~w+rlMkxpNqEMq04HzMFF?^10*bIhC!p%XYIz)<@Fu|M_1b9_sWCW! zKowus5Wy2X_{(sFf*T>*;YENG*J>kth~bWS^k!`s9`IuU5r*Ly<3!;BF9NW)KLE#& zTZ{UG7XkP~hu|3HK~#ft0Z#M2-^I4K6tRgZ;25U@m<~1*_v`;R8$B$8W#a($zH!1c z$_?Yl%xub?h*U`}%yNu{bG~N16MFHH@2wTlED;(!r8)%~+}DOV&+&s^>VrB8;2??% zhm@-KFO_e#JJoRskfMugV(^9~g0c;rPPEKgif$bnv|xgZ-;)sI-@e4{-WSm5bfTjG z9DkqTTN~wytq;)igIdVcGoxl)^S^5OEz@U{ zP@zYUwpgdbw{BLxcpu$3E$wlHwFwnsp;z9&* z-9TgkcPD6w3V4BlVJj$0tUzP|$rAf%C=`Ujo41nKN+F#jkpTUxzyiEF!7~*VyZ};c zmO#iNi2K$P08&)A0&qb@Lx@-am4@#KC2SD3=R%20fVpN9)6?%^Luqgu1_m}uE}4Z8 zu>dJyHp&=3PQQDPN8^vt4W~wrV{rSnOl`Se-ylT31O$%82+%DrU%$=V)=UnBhy@^l z07CO^OKJPo%x%eH7m)h^!A*`FK7=BRQ2+1|HkfPo?cI~PeUyg)O6Ua503?IxhE(Mf z2n7rKC;v{6(=Clap(F<o=JB@F`Q< zLEiiKTkITXk1Fwa#M__^^_h=&^7L8e_ODnh69Z%ofxE$W31~^SLHtwR6r}ZB@({ Date: Sun, 3 Sep 2023 05:40:11 +0900 Subject: [PATCH 06/17] =?UTF-8?q?refactor:=20script=20uuid=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42.xcodeproj/project.pbxproj | 24 +++++++-- Box42/Scripts/Model/Scripts.swift | 6 +-- Box42/Scripts/View/Table/ScriptCell.swift | 8 +-- .../View/Table/ScriptCellManager.swift | 6 +-- .../Scripts/ViewModel/ScriptsViewModel.swift | 51 ++++++++++--------- Box42/WebView/WebView.swift | 2 +- 6 files changed, 57 insertions(+), 40 deletions(-) diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index 229e011..ebb8da7 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -58,7 +58,7 @@ DE4408052A923EC00091937A /* QuitButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE4408042A923EC00091937A /* QuitButtonView.swift */; }; DE4408082A9240300091937A /* BoxFunctionButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE4408072A9240300091937A /* BoxFunctionButtonView.swift */; }; DE44080C2A924B520091937A /* BoxFunctionViewGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE44080B2A924B520091937A /* BoxFunctionViewGroup.swift */; }; - DE4408152A92750D0091937A /* keyDown+BoxBaseContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE4408142A92750D0091937A /* keyDown+BoxBaseContainerViewController.swift */; }; + DE4408152A92750D0091937A /* keyDown+Appdelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE4408142A92750D0091937A /* keyDown+Appdelegate.swift */; }; DE44081D2A928F760091937A /* Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE44081C2A928F760091937A /* Divider.swift */; }; DE62BE5A2A9BA31700D97E06 /* QuickSlotButtonCollectionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DE62BE582A9BA31700D97E06 /* QuickSlotButtonCollectionViewController.xib */; }; DE62BE672A9BA92E00D97E06 /* QuickSlotButtonViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7F9D432A9B7A4700F8ACAE /* QuickSlotButtonViewItem.swift */; }; @@ -120,6 +120,10 @@ DEB862EB2A853F7F00278FCD /* BoxWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEB862E92A853F7F00278FCD /* BoxWindowController.swift */; }; DEE0FA962A9A554F00085A65 /* FunctionButtonUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEE0FA952A9A554F00085A65 /* FunctionButtonUI.swift */; }; DEF0761B2AA33671005700E5 /* DeleteUserMeScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0761A2AA33671005700E5 /* DeleteUserMeScript.swift */; }; + DEF076262AA3B0C1005700E5 /* QuickSlotItemButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF076252AA3B0C1005700E5 /* QuickSlotItemButton.swift */; }; + DEF0762A2AA3B955005700E5 /* PutUserMeQuickSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF076292AA3B955005700E5 /* PutUserMeQuickSlot.swift */; }; + DEF0762D2AA3C34C005700E5 /* GetUserMeQuickSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0762C2AA3C34C005700E5 /* GetUserMeQuickSlot.swift */; }; + DEF076302AA3CF8A005700E5 /* QuickSlotItemLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0762F2AA3CF8A005700E5 /* QuickSlotItemLabel.swift */; }; DEF749322A85657600D987C8 /* NSScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF749312A85657600D987C8 /* NSScreen.swift */; }; /* End PBXBuildFile section */ @@ -178,7 +182,7 @@ DE4408042A923EC00091937A /* QuitButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuitButtonView.swift; sourceTree = ""; }; DE4408072A9240300091937A /* BoxFunctionButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxFunctionButtonView.swift; sourceTree = ""; }; DE44080B2A924B520091937A /* BoxFunctionViewGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxFunctionViewGroup.swift; sourceTree = ""; }; - DE4408142A92750D0091937A /* keyDown+BoxBaseContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "keyDown+BoxBaseContainerViewController.swift"; path = "Main/keyDown+BoxBaseContainerViewController.swift"; sourceTree = ""; }; + DE4408142A92750D0091937A /* keyDown+Appdelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "keyDown+Appdelegate.swift"; sourceTree = ""; }; DE44081C2A928F760091937A /* Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Divider.swift; sourceTree = ""; }; DE62BE312A9B869900D97E06 /* QuickSlotButtonCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotButtonCollectionViewController.swift; sourceTree = ""; }; DE62BE582A9BA31700D97E06 /* QuickSlotButtonCollectionViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuickSlotButtonCollectionViewController.xib; sourceTree = ""; }; @@ -239,6 +243,10 @@ DEB862E92A853F7F00278FCD /* BoxWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxWindowController.swift; sourceTree = ""; }; DEE0FA952A9A554F00085A65 /* FunctionButtonUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionButtonUI.swift; sourceTree = ""; }; DEF0761A2AA33671005700E5 /* DeleteUserMeScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteUserMeScript.swift; sourceTree = ""; }; + DEF076252AA3B0C1005700E5 /* QuickSlotItemButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotItemButton.swift; sourceTree = ""; }; + DEF076292AA3B955005700E5 /* PutUserMeQuickSlot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutUserMeQuickSlot.swift; sourceTree = ""; }; + DEF0762C2AA3C34C005700E5 /* GetUserMeQuickSlot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserMeQuickSlot.swift; sourceTree = ""; }; + DEF0762F2AA3CF8A005700E5 /* QuickSlotItemLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotItemLabel.swift; sourceTree = ""; }; DEF7492E2A85603700D987C8 /* nodeInstall.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = nodeInstall.sh; sourceTree = ""; }; DEF749312A85657600D987C8 /* NSScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSScreen.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -386,7 +394,6 @@ DE0A916B2A8E7DC700D1D6F1 /* UI */, DE4408202A9297EE0091937A /* View */, DE1F1A192A8B50C500A88DD8 /* BoxBaseContainerViewController.swift */, - DE4408142A92750D0091937A /* keyDown+BoxBaseContainerViewController.swift */, DEB862E92A853F7F00278FCD /* BoxWindowController.swift */, ); name = Main; @@ -464,6 +471,8 @@ DE9457272A9F6E4400B0B768 /* GetUserMeScripts.swift */, DE94572B2A9F75D500B0B768 /* API.swift */, DEF0761A2AA33671005700E5 /* DeleteUserMeScript.swift */, + DEF076292AA3B955005700E5 /* PutUserMeQuickSlot.swift */, + DEF0762C2AA3C34C005700E5 /* GetUserMeQuickSlot.swift */, ); path = API; sourceTree = ""; @@ -552,6 +561,8 @@ children = ( DE7F9D432A9B7A4700F8ACAE /* QuickSlotButtonViewItem.swift */, DE7F9D442A9B7A4700F8ACAE /* QuickSlotButtonViewItem.xib */, + DEF076252AA3B0C1005700E5 /* QuickSlotItemButton.swift */, + DEF0762F2AA3CF8A005700E5 /* QuickSlotItemLabel.swift */, ); path = "Vertical Item"; sourceTree = ""; @@ -750,6 +761,7 @@ DE98E83A2A98DB6000F8744A /* RotateImage+NSImage.swift */, DE97CA682A9A6364001073DE /* PixelConversion+CGFloat.swift */, DE94571B2A9EFB7800B0B768 /* Associated+NSButton.swift */, + DE4408142A92750D0091937A /* keyDown+Appdelegate.swift */, ); path = Extensions; sourceTree = ""; @@ -842,6 +854,7 @@ DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */, DEF0761B2AA33671005700E5 /* DeleteUserMeScript.swift in Sources */, DE62BE672A9BA92E00D97E06 /* QuickSlotButtonViewItem.swift in Sources */, + DEF076302AA3CF8A005700E5 /* QuickSlotItemLabel.swift in Sources */, DE874F542A591F1400FC3B77 /* PreferencesView.swift in Sources */, DE98E83B2A98DB6000F8744A /* RotateImage+NSImage.swift in Sources */, DE0A91982A8F977F00D1D6F1 /* ToolbarViewController.swift in Sources */, @@ -872,6 +885,7 @@ DE77BA512A82580400713683 /* MenubarViewModel.swift in Sources */, DE44080C2A924B520091937A /* BoxFunctionViewGroup.swift in Sources */, DE97CA7C2A9A7199001073DE /* QuickSlotGroupView.swift in Sources */, + DEF0762A2AA3B955005700E5 /* PutUserMeQuickSlot.swift in Sources */, DE018BE42A509B1700FF0AA3 /* CPU.swift in Sources */, DE77BBA22A9DDC40006CC98B /* ScriptsFileManager.swift in Sources */, DE874F5F2A5935CC00FC3B77 /* String.swift in Sources */, @@ -914,7 +928,7 @@ DE94570C2A9E69EB00B0B768 /* ScriptExcuteButton.swift in Sources */, DE9457162A9E6D3000B0B768 /* ScriptQuickSlotButton.swift in Sources */, DE97CA7F2A9A73A9001073DE /* QuickSlotUI.swift in Sources */, - DE4408152A92750D0091937A /* keyDown+BoxBaseContainerViewController.swift in Sources */, + DE4408152A92750D0091937A /* keyDown+Appdelegate.swift in Sources */, DE018BED2A509B2600FF0AA3 /* URLModel.swift in Sources */, DE97CA692A9A6364001073DE /* PixelConversion+CGFloat.swift in Sources */, DE7886282A9D186700FE21DD /* ScriptsViewController.swift in Sources */, @@ -934,10 +948,12 @@ DE24E6382A8FE10400E29F5D /* BoxBaseSplitView.swift in Sources */, DE44081D2A928F760091937A /* Divider.swift in Sources */, DE98E8432A98DDFD00F8744A /* QuickSlotViewController.swift in Sources */, + DEF0762D2AA3C34C005700E5 /* GetUserMeQuickSlot.swift in Sources */, DE94574B2AA0A70500B0B768 /* NetworkView.swift in Sources */, DE1F1A2E2A8BCC9800A88DD8 /* Storage.swift in Sources */, DE3FF36B2A978A57009C88EF /* WindowButtonViewController.swift in Sources */, DE1F1A312A8BD68F00A88DD8 /* Double.swift in Sources */, + DEF076262AA3B0C1005700E5 /* QuickSlotItemButton.swift in Sources */, DE0A917F2A8F865400D1D6F1 /* BoxToolbarViewGroup.swift in Sources */, DE018BEA2A509B2100FF0AA3 /* WebViewModel.swift in Sources */, ); diff --git a/Box42/Scripts/Model/Scripts.swift b/Box42/Scripts/Model/Scripts.swift index c052800..d22fc03 100644 --- a/Box42/Scripts/Model/Scripts.swift +++ b/Box42/Scripts/Model/Scripts.swift @@ -12,15 +12,15 @@ struct Scripts: Codable { } struct Script: Codable { - var id: UUID? + var scriptUuid: UUID? var name: String var description: String? var path: String var savedId: Int? var userUuid: String? - init(id: UUID = UUID(), name: String, description: String, path: String, savedId: Int, userUuid: String?) { - self.id = id + init(id: UUID?, name: String, description: String?, path: String, savedId: Int?, userUuid: String?) { + self.scriptUuid = id self.name = name self.description = description self.path = path diff --git a/Box42/Scripts/View/Table/ScriptCell.swift b/Box42/Scripts/View/Table/ScriptCell.swift index e37797b..c8be376 100644 --- a/Box42/Scripts/View/Table/ScriptCell.swift +++ b/Box42/Scripts/View/Table/ScriptCell.swift @@ -89,23 +89,23 @@ class ScriptCell: NSTableCellView { } @objc func deleteButtonClicked() { - if let id = script?.id { + if let id = script?.scriptUuid { viewModel?.deleteScript(id: id) } } @objc func excuteButtonClicked() { - if let id = script?.id { + if let id = script?.scriptUuid { viewModel?.excuteScript(id: id) } } @objc func quickSlotButtonclicked() { - guard let id = script?.id else { + guard let id = script?.scriptUuid else { return } - let alreadyExists = QuickSlotViewModel.shared.buttons.contains { $0.id == id } + let alreadyExists = QuickSlotViewModel.shared.buttons.contains { $0.scriptUuid == id } if alreadyExists { QuickSlotViewModel.shared.removeButton(id) diff --git a/Box42/Scripts/View/Table/ScriptCellManager.swift b/Box42/Scripts/View/Table/ScriptCellManager.swift index c4e9a22..a7567ae 100644 --- a/Box42/Scripts/View/Table/ScriptCellManager.swift +++ b/Box42/Scripts/View/Table/ScriptCellManager.swift @@ -90,19 +90,19 @@ class ScriptCellManager: NSTableCellView { } @objc func deleteButtonClicked() { - if let id = script?.id { + if let id = script?.scriptUuid { viewModel?.deleteScript(id: id) } } @objc func excuteButtonClicked() { - if let id = script?.id { + if let id = script?.scriptUuid { viewModel?.excuteScript(id: id) } } @objc func quickSlotButtonclicked() { - if let id = script?.id { + if let id = script?.scriptUuid { viewModel?.quickSlotScript(id: id) } } diff --git a/Box42/Scripts/ViewModel/ScriptsViewModel.swift b/Box42/Scripts/ViewModel/ScriptsViewModel.swift index 20275ab..9abc40e 100644 --- a/Box42/Scripts/ViewModel/ScriptsViewModel.swift +++ b/Box42/Scripts/ViewModel/ScriptsViewModel.swift @@ -15,24 +15,25 @@ class ScriptViewModel: NSObject { private override init() { self.scripts = [ - Script(name: "cleanCache", + Script(id: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3"), + name: "CleanCache", description: "Cleaning cache", path: Bundle.main.path(forResource: "cleanCache", ofType: "sh") ?? "", savedId: -1 , userUuid: nil), -// Script(name: "brewInGoinfre", -// description: "Brew download in goinfre", -// path: Bundle.main.path(forResource: "brewInGoinfre", ofType: "sh") ?? "", savedId: -1, userUuid: nil), -// Script(name: "exportMacOSInfo", -// description: "export setting MacOS Info", -// path: Bundle.main.path(forResource: "exportMacOSInfo", ofType: "sh") ?? "", savedId: -1, userUuid: nil), -// Script(name: "importMacOSInfo", -// description: "import MacOS Info", -// path: Bundle.main.path(forResource: "importMacOSInfo", ofType: "sh") ?? "", savedId: -1, userUuid: nil), -// Script(name: "key Mapping", -// description: "key Mapping", -// path: Bundle.main.path(forResource: "keyMapping", ofType: "sh") ?? "", savedId: -1, userUuid: nil), -// Script(name: "nodeInstall", -// description: "node Install", -// path: Bundle.main.path(forResource: "nodeInstall", ofType: "sh") ?? "", savedId: -1, userUuid: nil) + // Script(name: "brewInGoinfre", + // description: "Brew download in goinfre", + // path: Bundle.main.path(forResource: "brewInGoinfre", ofType: "sh") ?? "", savedId: -1, userUuid: nil), + // Script(name: "exportMacOSInfo", + // description: "export setting MacOS Info", + // path: Bundle.main.path(forResource: "exportMacOSInfo", ofType: "sh") ?? "", savedId: -1, userUuid: nil), + // Script(name: "importMacOSInfo", + // description: "import MacOS Info", + // path: Bundle.main.path(forResource: "importMacOSInfo", ofType: "sh") ?? "", savedId: -1, userUuid: nil), + // Script(name: "key Mapping", + // description: "key Mapping", + // path: Bundle.main.path(forResource: "keyMapping", ofType: "sh") ?? "", savedId: -1, userUuid: nil), + // Script(name: "nodeInstall", + // description: "node Install", + // path: Bundle.main.path(forResource: "nodeInstall", ofType: "sh") ?? "", savedId: -1, userUuid: nil) ] API.initializeUserMeScripts(WebViewManager.shared.getCookieWebKit) } @@ -45,18 +46,18 @@ class ScriptViewModel: NSObject { // Read func excuteScript(id: UUID) { - if let index = scripts.firstIndex(where: { $0.id == id }) { -// ExecuteScripts.executeShellScript(path: scripts[index].name) + if let index = scripts.firstIndex(where: { $0.scriptUuid == id }) { + // ExecuteScripts.executeShellScript(path: scripts[index].name) // MARK: - 파일스크립트 매니저에서 권한을 얻은 실행으로 실행합니다. ScriptsFileManager.downloadFile(from: "https://42box.kr/" + scripts[index].path) - -// SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: scripts[index].path) + + // SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: scripts[index].path) } } // Update func updateScript(id: UUID, newName: String, newDescription: String) { - if let index = scripts.firstIndex(where: { $0.id == id }) { + if let index = scripts.firstIndex(where: { $0.scriptUuid == id }) { scripts[index].name = newName scripts[index].description = newDescription } @@ -64,11 +65,11 @@ class ScriptViewModel: NSObject { // Delete func deleteScript(id: UUID) { - if let script = scripts.first(where: { $0.id == id }) { + if let script = scripts.first(where: { $0.scriptUuid == id }) { API.deleteUserMeScripts(WebViewManager.shared.getCookieWebKit, savedId: script.savedId!) { result in switch result { case .success(_): - self.scripts.removeAll(where: { $0.id == id }) + self.scripts.removeAll(where: { $0.scriptUuid == id }) QuickSlotViewModel.shared.removeButton(id) case .failure(let error): @@ -85,12 +86,12 @@ class ScriptViewModel: NSObject { // VM class 시작시 최초 1회 실행되는 메소드 func setupScripts(with newScripts: [Script]) { - self.scripts += newScripts + self.scripts = newScripts } // 스크립트안에서 해당하는 스크립트를 찾아서 quickslotVM에 추가 func quickSlotScript(id: UUID) { - if let index = scripts.firstIndex(where: { $0.id == id }) { + if let index = scripts.firstIndex(where: { $0.scriptUuid == id }) { let button = QuickSlotButtonModel(id: id, title: scripts[index].name, path: scripts[index].path) QuickSlotViewModel.shared.addButton(button) } diff --git a/Box42/WebView/WebView.swift b/Box42/WebView/WebView.swift index 8339fbc..ac76554 100644 --- a/Box42/WebView/WebView.swift +++ b/Box42/WebView/WebView.swift @@ -112,7 +112,7 @@ extension WebView { print(deleteScript) - ScriptViewModel.shared.deleteScript(id: deleteScript.id!) + ScriptViewModel.shared.deleteScript(id: deleteScript.scriptUuid!) } catch { print("JSON decoding failed: \(error)") } From affd4f94d8115f2d106980739fd913aac8514b34 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 3 Sep 2023 05:42:08 +0900 Subject: [PATCH 07/17] =?UTF-8?q?feat:=20quick=20slot=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EB=8F=99=EA=B8=B0=ED=99=94=EB=A5=BC=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/QuickSlotButtonModel.swift | 12 +++- Box42/QuickSlot/Model/QuickSlotUI.swift | 2 +- Box42/QuickSlot/View/QuickSlotGroupView.swift | 1 - .../ViewModel/QuickSlotViewModel.swift | 41 +++++++++++--- Box42/Resources/AppDelegate.swift | 8 +-- Box42/Shared/API/API.swift | 24 ++++++++ Box42/Shared/API/GetUserMeQuickSlot.swift | 8 +++ Box42/Shared/API/GetUserProfile.swift | 1 + Box42/Shared/API/PutUserMeQuickSlot.swift | 55 +++++++++++++++++++ Box42/Shared/User/UserManager.swift | 2 +- Box42/Shared/User/UserProfile.swift | 2 +- 11 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 Box42/Shared/API/GetUserMeQuickSlot.swift create mode 100644 Box42/Shared/API/PutUserMeQuickSlot.swift diff --git a/Box42/QuickSlot/Model/QuickSlotButtonModel.swift b/Box42/QuickSlot/Model/QuickSlotButtonModel.swift index 9a55e20..2665c32 100644 --- a/Box42/QuickSlot/Model/QuickSlotButtonModel.swift +++ b/Box42/QuickSlot/Model/QuickSlotButtonModel.swift @@ -7,15 +7,21 @@ import Foundation +struct QuickSlotModels: Codable { + let quickSlotList : [QuickSlotButtonModel] +} + // Model struct QuickSlotButtonModel: Codable { - let id: UUID + let scriptUuid: UUID var title: String var path: String? + var type: String? - init(id: UUID = UUID(), title: String = "Default", path: String? = nil) { - self.id = id + init(id: UUID = UUID(), title: String = "Default", path: String? = "none", type: String = "sh") { + self.scriptUuid = id self.title = title self.path = path + self.type = type } } diff --git a/Box42/QuickSlot/Model/QuickSlotUI.swift b/Box42/QuickSlot/Model/QuickSlotUI.swift index 096bcbc..f026d00 100644 --- a/Box42/QuickSlot/Model/QuickSlotUI.swift +++ b/Box42/QuickSlot/Model/QuickSlotUI.swift @@ -23,7 +23,7 @@ enum QuickSlotUI { } enum title { - static let clean = "Clean" + static let clean = "CleanCache" static let preferences = "Preferences" static let scripts = "Scripts" static let user = "User" diff --git a/Box42/QuickSlot/View/QuickSlotGroupView.swift b/Box42/QuickSlot/View/QuickSlotGroupView.swift index 228c26b..e436c86 100644 --- a/Box42/QuickSlot/View/QuickSlotGroupView.swift +++ b/Box42/QuickSlot/View/QuickSlotGroupView.swift @@ -33,7 +33,6 @@ class QuickSlotGroupView: NSView { self.addSubview(divider) self.addSubview(headerView) self.addSubview(buttonCollectionView.view) - } private func setupConstraints() { diff --git a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift index 568168d..ca43f68 100644 --- a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift +++ b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift @@ -13,30 +13,57 @@ class QuickSlotViewModel { @Published var buttons: [QuickSlotButtonModel] = [] private init() { - let button1 = QuickSlotButtonModel(id: UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!, + let button1 = QuickSlotButtonModel(id: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, title: QuickSlotUI.title.clean, - path: Bundle.main.path(forResource: "cleanCache", ofType: "sh")) - let button2 = QuickSlotButtonModel(title: QuickSlotUI.title.preferences) + path: Bundle.main.path(forResource: "cleanCache", ofType: "sh"), + type: "sh" + ) + let button2 = QuickSlotButtonModel(title: QuickSlotUI.title.preferences, type: "pref") let button3 = QuickSlotButtonModel(title: QuickSlotUI.title.scripts) - let button4 = QuickSlotButtonModel(title: QuickSlotUI.title.user) + let button4 = QuickSlotButtonModel(title: QuickSlotUI.title.user, type: "pref") buttons = [button1, button2, button3, button4] } + func setUpQuickSlot() { + if let newButtons = UserManager.shared.userProfile?.quickSlotList { + replaceQuickSlot(with: newButtons) + } + } + + // 새로운 스크립트 배열로 교체하는 메소드 + func replaceQuickSlot(with newQuickSlot: [QuickSlotButtonModel]) { + self.buttons = newQuickSlot + } + // 퀵슬롯 안에 해당 버튼이 없으면 추가 func addButton(_ button: QuickSlotButtonModel) { - if !buttons.contains(where: { $0.id == button.id }) { + if !buttons.contains(where: { $0.scriptUuid == button.scriptUuid }) { buttons.append(button) + updateMeQuickSlot() } } func removeButton(_ id: UUID) { - buttons.removeAll { $0.id == id } + buttons.removeAll { $0.scriptUuid == id } + updateMeQuickSlot() } func updateButton(id: UUID, newTitle: String) { - if let index = buttons.firstIndex(where: { $0.id == id }) { + if let index = buttons.firstIndex(where: { $0.scriptUuid == id }) { buttons[index].title = newTitle } } + + func updateMeQuickSlot() { + let body = QuickSlotModels(quickSlotList: buttons) + API.putUserMeQuickSlot(quickSlots: body) { result in + switch result { + case .success(_): + print("Successfully updated the scripts.") // 혹은 사용자에게 보여줄 알림 추가 + case .failure(let error): + print("Failed to update scripts: \(error.localizedDescription)") // 혹은 사용자에게 보여줄 알림 추가 + } + } + } } diff --git a/Box42/Resources/AppDelegate.swift b/Box42/Resources/AppDelegate.swift index 7d2ae45..bc8ce90 100644 --- a/Box42/Resources/AppDelegate.swift +++ b/Box42/Resources/AppDelegate.swift @@ -22,22 +22,22 @@ class AppDelegate: NSObject, NSApplicationDelegate { iconController = IconController() // alertAccessibility() // hotkey() + self.eventMonitoring() // storage.storageTimerEvent() _ = UserManager.shared - _ = ScriptsLogicController.shared + _ = QuickSlotScriptsLogicController.shared // MARK: - 유저데이터 동기화 WebViewManager.shared.getCookie() API.getUserProfile(WebViewManager.shared.getCookieWebKit) - _ = QuickSlotViewModel.shared - _ = ScriptViewModel.shared // 초기화와 동시에 + _ = ScriptViewModel.shared } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } - + func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } diff --git a/Box42/Shared/API/API.swift b/Box42/Shared/API/API.swift index 5d709ef..c4fbde4 100644 --- a/Box42/Shared/API/API.swift +++ b/Box42/Shared/API/API.swift @@ -67,4 +67,28 @@ class API { task.resume() } + // PUT + static func putDataFromAPI(withURL urlString: String, completion: @escaping (Result) -> Void) { + + let url = URL(string: urlString)! + var request = URLRequest(url: url) + request.httpMethod = "PUT" + request.httpShouldHandleCookies = true + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { + completion(.failure(NSError(domain: "InvalidStatusCode", code: httpResponse.statusCode, userInfo: nil))) + return + } + completion(.success(data)) + } + task.resume() + } + + } diff --git a/Box42/Shared/API/GetUserMeQuickSlot.swift b/Box42/Shared/API/GetUserMeQuickSlot.swift new file mode 100644 index 0000000..c4481bc --- /dev/null +++ b/Box42/Shared/API/GetUserMeQuickSlot.swift @@ -0,0 +1,8 @@ +// +// GetUserMeQuickSlot.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import Foundation diff --git a/Box42/Shared/API/GetUserProfile.swift b/Box42/Shared/API/GetUserProfile.swift index 533a695..a631e2d 100644 --- a/Box42/Shared/API/GetUserProfile.swift +++ b/Box42/Shared/API/GetUserProfile.swift @@ -23,6 +23,7 @@ extension API { case .success(let userProfile): print(">> User MacOS Get :", userProfile) UserManager.shared.updateUserProfile(newProfile: userProfile) + QuickSlotViewModel.shared.setUpQuickSlot() case .failure(let error): print("Error: \(error)") } diff --git a/Box42/Shared/API/PutUserMeQuickSlot.swift b/Box42/Shared/API/PutUserMeQuickSlot.swift new file mode 100644 index 0000000..f672f42 --- /dev/null +++ b/Box42/Shared/API/PutUserMeQuickSlot.swift @@ -0,0 +1,55 @@ +// +// PutUserMeQuickSlot.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import WebKit + +extension API { + // MARK: - Scripts PUT: https://api.42box.kr/user-service/users/me/quick-slot + // TODO: refactoring 필수 + static func putUserMeQuickSlot(quickSlots: QuickSlotModels, completion: @escaping (Result) -> Void) { + + WebViewManager.shared.hostingWebView?.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in + let cookieStorage = HTTPCookieStorage.shared + for cookie in cookies { + cookieStorage.setCookie(cookie) + } + + let url = "https://api.42box.kr/user-service/users/me/quick-slot" + + var request = URLRequest(url: URL(string: url)!) + request.httpMethod = "PUT" + request.httpShouldHandleCookies = true + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + // Scripts 객체를 JSON 데이터로 인코딩 + print(quickSlots) + do { + let jsonData = try JSONEncoder().encode(quickSlots) + request.httpBody = jsonData + print(request.httpBody!) + } catch { + print("Failed to encode scripts: \(error)") + completion(.failure(error)) + return + } + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { + completion(.failure(NSError(domain: "InvalidStatusCode", code: httpResponse.statusCode, userInfo: nil))) + return + } + completion(.success(())) + } + task.resume() + } + } +} diff --git a/Box42/Shared/User/UserManager.swift b/Box42/Shared/User/UserManager.swift index ec61323..18e4677 100644 --- a/Box42/Shared/User/UserManager.swift +++ b/Box42/Shared/User/UserManager.swift @@ -9,7 +9,7 @@ import Foundation class UserManager { static let shared = UserManager() - private var userProfile: UserProfile? { + var userProfile: UserProfile? { didSet { NotificationCenter.default.post(name: .didUpdateUserProfile, object: nil) } diff --git a/Box42/Shared/User/UserProfile.swift b/Box42/Shared/User/UserProfile.swift index ce4e8a8..fdeafcf 100644 --- a/Box42/Shared/User/UserProfile.swift +++ b/Box42/Shared/User/UserProfile.swift @@ -46,7 +46,7 @@ extension UserProfile { statusMessage: "hello 42Box!", profileImageUrl: "https://42box.kr/user_profile_image/a52671f9-fca9-43ad-b0c0-1c5360831cf2.png", profileImagePath: "user_profile_image/a52671f9-fca9-43ad-b0c0-1c5360831cf2.png", - quickSlotList: [ QuickSlotButtonModel(id: UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!, title: "cleanCache", path: Bundle.main.path(forResource: "cleanCache", ofType: "sh")), + quickSlotList: [ QuickSlotButtonModel(id: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, title: "cleanCache", path: Bundle.main.path(forResource: "cleanCache", ofType: "sh")), ] ) } From 75f6c64c24e88a94f91ee6e324dd4dc544db9cfb Mon Sep 17 00:00:00 2001 From: chanhihi Date: Sun, 3 Sep 2023 05:43:08 +0900 Subject: [PATCH 08/17] =?UTF-8?q?feat:=20quick=20slot=20=ED=82=A4=20?= =?UTF-8?q?=EB=8B=A8=EC=B6=95=ED=82=A4=EB=A5=BC=20=EC=A7=80=EC=A0=95?= =?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42/Extensions/keyDown+Appdelegate.swift | 105 ++++++++++++++++++ .../Main/BoxBaseContainerViewController.swift | 4 + ...yDown+BoxBaseContainerViewController.swift | 37 ------ .../View/Funtion/ShortcutSettingView.swift | 18 +-- .../QuickSlotScriptsLogicController.swift | 34 ++---- ...ckSlotButtonCollectionViewController.swift | 29 +++-- .../QuickSlot/View/QuickSlotHeaderView.swift | 4 +- Box42/Resources/Info.plist | 4 +- 8 files changed, 149 insertions(+), 86 deletions(-) create mode 100644 Box42/Extensions/keyDown+Appdelegate.swift delete mode 100644 Box42/Main/keyDown+BoxBaseContainerViewController.swift diff --git a/Box42/Extensions/keyDown+Appdelegate.swift b/Box42/Extensions/keyDown+Appdelegate.swift new file mode 100644 index 0000000..75bf2e4 --- /dev/null +++ b/Box42/Extensions/keyDown+Appdelegate.swift @@ -0,0 +1,105 @@ +// +// keyDown+AppDelegate.swift +// Box42 +// +// Created by Chanhee Kim on 8/21/23. +// + +import AppKit + +extension AppDelegate { + func eventMonitoring() { + NSEvent.addLocalMonitorForEvents(matching: .keyUp) { (event) -> NSEvent? in + print("Key Up: \(event.keyCode)") + + if event.modifierFlags.contains(.control) { + switch event.keyCode { + case 12: // 'Q' 키의 keyCode + print("Control + Q pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(0) + case 13: // 'W' 키의 keyCode + print("Control + W pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(2) + case 14: // 'E' 키의 keyCode + print("Control + E pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(4) + case 15: // 'R' 키의 keyCode + print("Control + R pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(6) + case 0: // 'A' 키의 keyCode + print("Control + A pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(1) + case 1: // 'S' 키의 keyCode + print("Control + S pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(3) + case 2: // 'D' 키의 keyCode + print("Control + D pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(5) + case 3: // 'F' 키의 keyCode + print("Control + F pressed") + QuickSlotScriptsLogicController.shared.shortCutKeyUP(7) + case 6: // 'Z' 키의 keyCode + print("Control + Z pressed") + StateManager.shared.togglePin() // pin 처리 + case 7: // 'X' 키의 keyCode + print("Control + X pressed") + default: + break + } + } else if event.modifierFlags.contains(.command) { + switch event.keyCode { + case 12: // 'Q' 키의 keyCode + print("command + Q pressed") + case 13: // 'W' 키의 keyCode + print("command + W pressed") + case 14: // 'E' 키의 keyCode + print("command + E pressed") + case 15: // 'R' 키의 keyCode + print("command + R pressed") + WebViewManager.shared.hostingWebView?.reload() + case 0: // 'A' 키의 keyCode + print("command + A pressed") + case 1: // 'S' 키의 keyCode + print("command + S pressed") + case 2: // 'D' 키의 keyCode + print("command + D pressed") + case 3: // 'F' 키의 keyCode + print("command + F pressed") + default: + break + } + } else { + switch event.keyCode { + case 12: // 'Q' 키의 keyCode + print("Q pressed") + case 13: // 'W' 키의 keyCode + print("W pressed") + case 14: // 'E' 키의 keyCode + print("E pressed") + case 15: // 'R' 키의 keyCode + print("R pressed") + case 0: // 'A' 키의 keyCode + print("A pressed") + case 1: // 'S' 키의 keyCode + print("S pressed") + StorageConfig.shared.setThreshold(.percentage50) + StorageConfig.shared.setPeriod(.period10s) + case 2: // 'D' 키의 keyCode + print("D pressed") + DispatchQueue.main.async { + StorageConfig.shared.setThreshold(.percentage30) + } + StorageConfig.shared.setPeriod(.period1s) + case 3: // 'F' 키의 keyCode + print("F pressed") + case 53: // Escape 키 + print("escape") +// menubarVCDelegate?.toggleWindow(sender: nil) + default: + break + } + } + return event + } + } +} diff --git a/Box42/Main/BoxBaseContainerViewController.swift b/Box42/Main/BoxBaseContainerViewController.swift index 49bdf4c..e217f74 100644 --- a/Box42/Main/BoxBaseContainerViewController.swift +++ b/Box42/Main/BoxBaseContainerViewController.swift @@ -25,6 +25,10 @@ class BoxBaseContainerViewController: NSViewController { weak var menubarVCDelegate: MenubarViewControllerDelegate? // extension + + var quickSlotButtonCollectionVC: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() + + override func loadView() { self.view = NSView() self.view.addSubview(splitView) diff --git a/Box42/Main/keyDown+BoxBaseContainerViewController.swift b/Box42/Main/keyDown+BoxBaseContainerViewController.swift deleted file mode 100644 index f73306a..0000000 --- a/Box42/Main/keyDown+BoxBaseContainerViewController.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// keyDown+BoxViewController.swift -// Box42 -// -// Created by Chanhee Kim on 8/21/23. -// - -import AppKit - -extension BoxBaseContainerViewController { - override func keyDown(with event: NSEvent) { - print(event.keyCode) - if event.keyCode == 1 { - StorageConfig.shared.setThreshold(.percentage50) - StorageConfig.shared.setPeriod(.period10s) - } - if event.keyCode == 2 { - // SdtorageConfig.shared.setThreshold(.percentage30) - DispatchQueue.main.async { - StorageConfig.shared.setThreshold(.percentage30) - } - StorageConfig.shared.setPeriod(.period1s) - } - - if event.modifierFlags.contains(.command) && event.keyCode == 15 { - print("Cmd + R pressed, reloading...") - WebViewManager.shared.hostingWebView?.reload() - } - - if event.keyCode == 53 { // Escape 키의 keyCode는 53입니다. - print("escape") - menubarVCDelegate?.toggleWindow(sender: nil) - } else { - super.keyDown(with: event) // 기타 키를 처리하기 위해 상위 클래스에게 전달 - } - } -} diff --git a/Box42/Preferences/View/Funtion/ShortcutSettingView.swift b/Box42/Preferences/View/Funtion/ShortcutSettingView.swift index b5e215d..f626c65 100644 --- a/Box42/Preferences/View/Funtion/ShortcutSettingView.swift +++ b/Box42/Preferences/View/Funtion/ShortcutSettingView.swift @@ -22,15 +22,15 @@ class ShortcutSettingView: NSView { // Create an array of labels and text fields for various shortcut settings let shortcutSettings: [(label: String, defaultKey: String)] = [ ("앱 Show 단축키 설정", "Middle Mouse"), - ("Pin Box 단축키 설정", "P"), - ("퀵슬롯 1 설정", "Q"), - ("퀵슬롯 2 설정", "W"), - ("퀵슬롯 3 설정", "E"), - ("퀵슬롯 4 설정", "R"), - ("퀵슬롯 5 설정", "A"), - ("퀵슬롯 6 설정", "S"), - ("퀵슬롯 7 설정", "D"), - ("퀵슬롯 8 설정", "F") + ("Pin Box 단축키 설정", "^B"), + ("퀵슬롯 1 설정", "^Q"), + ("퀵슬롯 2 설정", "^W"), + ("퀵슬롯 3 설정", "^E"), + ("퀵슬롯 4 설정", "^R"), + ("퀵슬롯 5 설정", "^A"), + ("퀵슬롯 6 설정", "^S"), + ("퀵슬롯 7 설정", "^D"), + ("퀵슬롯 8 설정", "^F") ] lazy var stackView: NSStackView = { diff --git a/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift b/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift index d9b168d..0d24603 100644 --- a/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift +++ b/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift @@ -7,9 +7,8 @@ import AppKit -class ScriptsLogicController { - - static let shared = ScriptsLogicController() +class QuickSlotScriptsLogicController { + static let shared = QuickSlotScriptsLogicController() private init() { NotificationCenter.default.addObserver(self, selector: #selector(handleButtonTapped), name: .collectionButtonTapped, object: nil) @@ -17,7 +16,6 @@ class ScriptsLogicController { @objc func handleButtonTapped(notification: NSNotification) { if let button = notification.object as? NSButton { -// SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: button.associatedString ?? "") if let associatedString = button.associatedString { if let lastThreeCharacters = button.associatedString?.suffix(3) { if lastThreeCharacters == ".sh" { @@ -28,28 +26,12 @@ class ScriptsLogicController { } } - private func executeCleanScript() { - if let scriptPath = Bundle.main.path(forResource: "cleanCache", ofType: "sh") { - let task = Process() - task.launchPath = "/bin/sh" - task.arguments = [scriptPath] - - let outputPipe = Pipe() - task.standardOutput = outputPipe - task.standardError = outputPipe - - task.launch() - task.waitUntilExit() - - let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: outputData, encoding: .utf8) ?? "" - - DispatchQueue.main.async { - print("Output: \(output)") - } - } else { - DispatchQueue.main.async { - print("Script not found") + func shortCutKeyUP(_ keyCode: Int) { + if QuickSlotViewModel.shared.buttons.count > keyCode { + if let lastThreeCharacters = QuickSlotViewModel.shared.buttons[keyCode].path?.suffix(3) { + if lastThreeCharacters == ".sh" { + ScriptsFileManager.downloadFile(from: "https://42box.kr/" + QuickSlotViewModel.shared.buttons[keyCode].path!) + } } } } diff --git a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift index b1b31be..5e1c9f1 100644 --- a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift +++ b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift @@ -90,16 +90,25 @@ extension QuickSlotButtonCollectionViewController: NSCollectionViewDelegate, NSC let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "QuickSlotButtonViewItem"), for: indexPath) if let customItem = item as? QuickSlotButtonViewItem { - let buttonModel = viewModel.buttons[indexPath.item] - let btn = NSButton() - btn.title = buttonModel.title - btn.action = #selector(collectionButtonTapped) - btn.target = self - btn.wantsLayer = true - btn.layer?.backgroundColor = NSColor.red.cgColor - customItem.view.addSubview(btn) - btn.frame = CGRect(x: 0, y: 0, width: QuickSlotUI.size.button, height: QuickSlotUI.size.button) - btn.associatedString = buttonModel.path + if customItem.view.subviews.isEmpty { + let btn = QuickSlotItemButton(indexPath.item) + btn.action = #selector(collectionButtonTapped) + btn.target = self + + let label = QuickSlotItemLabel(indexPath.item) + + customItem.view.addSubview(btn) + customItem.view.addSubview(label) + + btn.snp.makeConstraints { make in + make.left.top.right.equalToSuperview() + } + + label.snp.makeConstraints { make in + make.top.equalTo(btn.snp.bottom) + make.left.right.bottom.equalToSuperview() + } + } } return item } diff --git a/Box42/QuickSlot/View/QuickSlotHeaderView.swift b/Box42/QuickSlot/View/QuickSlotHeaderView.swift index 443ed69..3638225 100644 --- a/Box42/QuickSlot/View/QuickSlotHeaderView.swift +++ b/Box42/QuickSlot/View/QuickSlotHeaderView.swift @@ -17,7 +17,7 @@ class QuickSlotHeaderView: NSView { init(image: NSImage, completion: @escaping () -> Void) { super.init(frame: .zero) - QuickSlotHeaderButton = NSButton(image: image, target: self, action: #selector(pin)) + QuickSlotHeaderButton = NSButton(image: image, target: self, action: #selector(btnAction)) QuickSlotHeaderButton.isBordered = false QuickSlotHeaderButton.wantsLayer = true QuickSlotHeaderButton.layer?.backgroundColor = NSColor.clear.cgColor @@ -49,7 +49,7 @@ class QuickSlotHeaderView: NSView { fatalError("init(coder:) has not been implemented") } - @objc func pin() { + @objc func btnAction() { callback?() } } diff --git a/Box42/Resources/Info.plist b/Box42/Resources/Info.plist index 4187051..e470c4d 100644 --- a/Box42/Resources/Info.plist +++ b/Box42/Resources/Info.plist @@ -2,8 +2,8 @@ - NSAppleEventsUsageDescription - This app needs access to control System Preferences. + NSAppleEventsUsageDescription + This app needs access to control System Preferences. NSDocumentsFolderUsageDescription 원활한 앱 구동을 위해 유저 디렉토리의 권한을 요청합니다. NSAppTransportSecurity From bdc630ae9f7633bed318f2e9409b980cece0f2da Mon Sep 17 00:00:00 2001 From: chanhihi Date: Mon, 4 Sep 2023 14:10:49 +0900 Subject: [PATCH 09/17] =?UTF-8?q?refactor:=20QuickSlot=EC=9D=84=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42.xcodeproj/project.pbxproj | 32 ++++- .../Main/BoxBaseContainerViewController.swift | 16 ++- .../QuickSlotManagerViewController.swift | 37 ++++++ .../Controller/QuickSlotViewController.swift | 2 +- .../Model/QuickSlotButtonModel.swift | 4 +- ...ckSlotButtonCollectionViewController.swift | 8 +- .../Vertical Item/QuickSlotItemButton.swift | 4 + Box42/QuickSlot/View/QuickSlotGroupView.swift | 2 +- .../QuickSlot/View/QuickSlotHeaderView.swift | 48 +++++--- .../QuickSlot/View/Table/QuickSlotCell.swift | 101 ++++++++++++++++ .../Table/QuickSlotCellDeleteButton.swift | 76 ++++++++++++ .../View/Table/QuickSlotCellManager.swift | 109 ++++++++++++++++++ .../View/Table/QuickSlotTableView.swift | 78 +++++++++++++ .../ViewModel/QuickSlotViewModel.swift | 48 +++++++- .../uibuttons/add.imageset/Contents.json | 21 ++++ .../uibuttons/add.imageset/add.png | Bin 0 -> 265 bytes .../Assets.xcassets/uibuttons/add.png | Bin 0 -> 265 bytes Box42/Shared/User/UserProfile.swift | 2 +- 18 files changed, 554 insertions(+), 34 deletions(-) create mode 100644 Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift create mode 100644 Box42/QuickSlot/View/Table/QuickSlotCell.swift create mode 100644 Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift create mode 100644 Box42/QuickSlot/View/Table/QuickSlotCellManager.swift create mode 100644 Box42/QuickSlot/View/Table/QuickSlotTableView.swift create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/add.imageset/Contents.json create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/add.imageset/add.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/add.png diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index e36dc27..a5f301d 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - D65C5C7C2AA195AB00BD291A /* prefsHelper.app in Resources */ = {isa = PBXBuildFile; fileRef = D65C5C7B2AA195AB00BD291A /* prefsHelper.app */; }; DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */; }; DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB62A5099F900FF0AA3 /* Box42.xcdatamodeld */; }; DE018BDD2A509AEB00FF0AA3 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BDC2A509AEB00FF0AA3 /* EventMonitor.swift */; }; @@ -125,11 +124,15 @@ DEF0762A2AA3B955005700E5 /* PutUserMeQuickSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF076292AA3B955005700E5 /* PutUserMeQuickSlot.swift */; }; DEF0762D2AA3C34C005700E5 /* GetUserMeQuickSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0762C2AA3C34C005700E5 /* GetUserMeQuickSlot.swift */; }; DEF076302AA3CF8A005700E5 /* QuickSlotItemLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0762F2AA3CF8A005700E5 /* QuickSlotItemLabel.swift */; }; + DEF076362AA47FDB005700E5 /* QuickSlotManagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF076352AA47FDB005700E5 /* QuickSlotManagerViewController.swift */; }; + DEF0763A2AA480C6005700E5 /* QuickSlotTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF076392AA480C6005700E5 /* QuickSlotTableView.swift */; }; + DEF0763D2AA48125005700E5 /* QuickSlotCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0763C2AA48125005700E5 /* QuickSlotCell.swift */; }; + DEF076402AA4845E005700E5 /* QuickSlotCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF0763F2AA4845E005700E5 /* QuickSlotCellManager.swift */; }; + DEF076432AA48AF0005700E5 /* QuickSlotCellDeleteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF076422AA48AF0005700E5 /* QuickSlotCellDeleteButton.swift */; }; DEF749322A85657600D987C8 /* NSScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF749312A85657600D987C8 /* NSScreen.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - D65C5C7B2AA195AB00BD291A /* prefsHelper.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; name = prefsHelper.app; path = ../../Downloads/prefsHelper.app; sourceTree = ""; }; DE018BAF2A5099F900FF0AA3 /* Box42.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Box42.app; sourceTree = BUILT_PRODUCTS_DIR; }; DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DE018BB72A5099F900FF0AA3 /* Box42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Box42.xcdatamodel; sourceTree = ""; }; @@ -249,6 +252,11 @@ DEF076292AA3B955005700E5 /* PutUserMeQuickSlot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutUserMeQuickSlot.swift; sourceTree = ""; }; DEF0762C2AA3C34C005700E5 /* GetUserMeQuickSlot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserMeQuickSlot.swift; sourceTree = ""; }; DEF0762F2AA3CF8A005700E5 /* QuickSlotItemLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotItemLabel.swift; sourceTree = ""; }; + DEF076352AA47FDB005700E5 /* QuickSlotManagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotManagerViewController.swift; sourceTree = ""; }; + DEF076392AA480C6005700E5 /* QuickSlotTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotTableView.swift; sourceTree = ""; }; + DEF0763C2AA48125005700E5 /* QuickSlotCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotCell.swift; sourceTree = ""; }; + DEF0763F2AA4845E005700E5 /* QuickSlotCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotCellManager.swift; sourceTree = ""; }; + DEF076422AA48AF0005700E5 /* QuickSlotCellDeleteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotCellDeleteButton.swift; sourceTree = ""; }; DEF7492E2A85603700D987C8 /* nodeInstall.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = nodeInstall.sh; sourceTree = ""; }; DEF749312A85657600D987C8 /* NSScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSScreen.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -268,7 +276,6 @@ DE018BA62A5099F900FF0AA3 = { isa = PBXGroup; children = ( - D65C5C7B2AA195AB00BD291A /* prefsHelper.app */, DE018BB12A5099F900FF0AA3 /* Box42 */, DE018BB02A5099F900FF0AA3 /* Products */, DE17AF722A834A1600325BF4 /* Frameworks */, @@ -660,6 +667,7 @@ DE98E8412A98DDEB00F8744A /* View */ = { isa = PBXGroup; children = ( + DEF076382AA480B7005700E5 /* Table */, DE44081C2A928F760091937A /* Divider.swift */, DE97CA782A9A6F6A001073DE /* QuickSlotHeaderView.swift */, DE97CA7B2A9A7199001073DE /* QuickSlotGroupView.swift */, @@ -741,6 +749,7 @@ children = ( DE98E8422A98DDFD00F8744A /* QuickSlotViewController.swift */, DE6332F12A9BCA2C00DCFAF6 /* QuickSlotScriptsLogicController.swift */, + DEF076352AA47FDB005700E5 /* QuickSlotManagerViewController.swift */, ); path = Controller; sourceTree = ""; @@ -753,6 +762,17 @@ path = Model; sourceTree = ""; }; + DEF076382AA480B7005700E5 /* Table */ = { + isa = PBXGroup; + children = ( + DEF076392AA480C6005700E5 /* QuickSlotTableView.swift */, + DEF0763C2AA48125005700E5 /* QuickSlotCell.swift */, + DEF076422AA48AF0005700E5 /* QuickSlotCellDeleteButton.swift */, + DEF0763F2AA4845E005700E5 /* QuickSlotCellManager.swift */, + ); + path = Table; + sourceTree = ""; + }; DEF749302A85655E00D987C8 /* Extensions */ = { isa = PBXGroup; children = ( @@ -840,7 +860,6 @@ DE7F9D462A9B7A4700F8ACAE /* QuickSlotButtonViewItem.xib in Resources */, DEB862D92A852C4500278FCD /* brewInGoinfre.sh in Resources */, DE62BE5A2A9BA31700D97E06 /* QuickSlotButtonCollectionViewController.xib in Resources */, - D65C5C7C2AA195AB00BD291A /* prefsHelper.app in Resources */, DE018BE02A509B0600FF0AA3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -862,6 +881,7 @@ DE874F542A591F1400FC3B77 /* PreferencesView.swift in Sources */, DE98E83B2A98DB6000F8744A /* RotateImage+NSImage.swift in Sources */, DE0A91982A8F977F00D1D6F1 /* ToolbarViewController.swift in Sources */, + DEF076432AA48AF0005700E5 /* QuickSlotCellDeleteButton.swift in Sources */, DE9DA8142A97F20E001C0D3B /* ButtonGroupViewController.swift in Sources */, DE4408082A9240300091937A /* BoxFunctionButtonView.swift in Sources */, DE3FF3742A978AB8009C88EF /* WindowMaximizeButton.swift in Sources */, @@ -883,6 +903,8 @@ DE77BBF02A9E38C6006CC98B /* GetUserProfile.swift in Sources */, DEF749322A85657600D987C8 /* NSScreen.swift in Sources */, DE018BF02A509B2F00FF0AA3 /* MenubarViewController.swift in Sources */, + DEF0763A2AA480C6005700E5 /* QuickSlotTableView.swift in Sources */, + DEF076362AA47FDB005700E5 /* QuickSlotManagerViewController.swift in Sources */, DE94571C2A9EFB7800B0B768 /* Associated+NSButton.swift in Sources */, DE6332E42A9BB8F800DCFAF6 /* QuickSlotButtonCollectionViewController.swift in Sources */, DE9457062A9E69C100B0B768 /* ScriptNameLabel.swift in Sources */, @@ -929,6 +951,7 @@ DE9457572AA0C5C600B0B768 /* IconSettingView.swift in Sources */, DE0A91672A8E6CA700D1D6F1 /* WebViewManager.swift in Sources */, DE77BBF32A9E38DC006CC98B /* UserManager.swift in Sources */, + DEF076402AA4845E005700E5 /* QuickSlotCellManager.swift in Sources */, DE94570C2A9E69EB00B0B768 /* ScriptExcuteButton.swift in Sources */, DE9457162A9E6D3000B0B768 /* ScriptQuickSlotButton.swift in Sources */, DE97CA7F2A9A73A9001073DE /* QuickSlotUI.swift in Sources */, @@ -936,6 +959,7 @@ DE018BED2A509B2600FF0AA3 /* URLModel.swift in Sources */, DE97CA692A9A6364001073DE /* PixelConversion+CGFloat.swift in Sources */, DE7886282A9D186700FE21DD /* ScriptsViewController.swift in Sources */, + DEF0763D2AA48125005700E5 /* QuickSlotCell.swift in Sources */, DE4408052A923EC00091937A /* QuitButtonView.swift in Sources */, DE0A918A2A8F88A900D1D6F1 /* GoForwardInToolbar.swift in Sources */, DE1F1A1E2A8B50C500A88DD8 /* BoxButtonViewGroup.swift in Sources */, diff --git a/Box42/Main/BoxBaseContainerViewController.swift b/Box42/Main/BoxBaseContainerViewController.swift index e217f74..b2cc181 100644 --- a/Box42/Main/BoxBaseContainerViewController.swift +++ b/Box42/Main/BoxBaseContainerViewController.swift @@ -25,7 +25,7 @@ class BoxBaseContainerViewController: NSViewController { weak var menubarVCDelegate: MenubarViewControllerDelegate? // extension - + var quickSlotManagerVC: QuickSlotManagerViewController = QuickSlotManagerViewController() var quickSlotButtonCollectionVC: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() @@ -47,6 +47,8 @@ class BoxBaseContainerViewController: NSViewController { self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor NotificationCenter.default.addObserver(self, selector: #selector(handleButtonTapped), name: .collectionButtonTapped, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(headerTappedQuickSlotManagerHandle), name: .collectionHeaderTapped, object: nil) } func BoxButtonViewGroupInit() -> BoxButtonViewGroup { @@ -210,3 +212,15 @@ extension BoxBaseContainerViewController { } } } + +extension BoxBaseContainerViewController { + @objc func headerTappedQuickSlotManagerHandle(notification: NSNotification) { + print("QuickSlotManager") + contentGroup.removeAllSubviews() + print(quickSlotManagerVC) + contentGroup.addSubview(quickSlotManagerVC.view) + quickSlotManagerVC.view.snp.makeConstraints { make in + make.top.bottom.left.right.equalToSuperview() + } + } +} diff --git a/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift b/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift new file mode 100644 index 0000000..18a9f47 --- /dev/null +++ b/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift @@ -0,0 +1,37 @@ +// +// QuickSlotManagerViewController.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit + +class QuickSlotManagerViewController: NSViewController { + var quickSlotManagerTableView: QuickSlotTableView? + var viewModel: QuickSlotViewModel? = QuickSlotViewModel.shared { + didSet { + quickSlotManagerTableView?.viewModel = viewModel + } + } + + override func loadView() { + self.view = NSView() + + quickSlotManagerTableView = QuickSlotTableView(frame: .zero) + quickSlotManagerTableView?.setup() + quickSlotManagerTableView?.viewModel = viewModel + + let scrollView = NSScrollView() + scrollView.documentView = quickSlotManagerTableView + self.view.addSubview(scrollView) + + scrollView.snp.makeConstraints({ make in + make.edges.equalToSuperview() + }) + + quickSlotManagerTableView?.snp.makeConstraints({ make in + make.edges.equalToSuperview() + }) + } +} diff --git a/Box42/QuickSlot/Controller/QuickSlotViewController.swift b/Box42/QuickSlot/Controller/QuickSlotViewController.swift index e60f49a..5a659d9 100644 --- a/Box42/QuickSlot/Controller/QuickSlotViewController.swift +++ b/Box42/QuickSlot/Controller/QuickSlotViewController.swift @@ -21,7 +21,7 @@ class QuickSlotViewController: NSViewController { } func headerAction() { - print("quick slot header") + NotificationCenter.default.post(name: .collectionHeaderTapped, object: nil) } @objc func handleButtonTapped(notification: NSNotification) { diff --git a/Box42/QuickSlot/Model/QuickSlotButtonModel.swift b/Box42/QuickSlot/Model/QuickSlotButtonModel.swift index 2665c32..0f3b50f 100644 --- a/Box42/QuickSlot/Model/QuickSlotButtonModel.swift +++ b/Box42/QuickSlot/Model/QuickSlotButtonModel.swift @@ -18,8 +18,8 @@ struct QuickSlotButtonModel: Codable { var path: String? var type: String? - init(id: UUID = UUID(), title: String = "Default", path: String? = "none", type: String = "sh") { - self.scriptUuid = id + init(scriptUuid: UUID? = UUID(), title: String = "Default", path: String? = "none", type: String = "sh") { + self.scriptUuid = scriptUuid ?? UUID() self.title = title self.path = path self.type = type diff --git a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift index 5e1c9f1..9b5ebfc 100644 --- a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift +++ b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift @@ -88,8 +88,13 @@ extension QuickSlotButtonCollectionViewController: NSCollectionViewDelegate, NSC func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "QuickSlotButtonViewItem"), for: indexPath) - + print("current item index>", indexPath.item) if let customItem = item as? QuickSlotButtonViewItem { + + for subview in customItem.view.subviews { + subview.removeFromSuperview() + } + if customItem.view.subviews.isEmpty { let btn = QuickSlotItemButton(indexPath.item) btn.action = #selector(collectionButtonTapped) @@ -123,4 +128,5 @@ extension QuickSlotButtonCollectionViewController { // MARK: - Notification Name collectionButtonTapped extension Notification.Name { static let collectionButtonTapped = Notification.Name("collectionButtonTapped") + static let collectionHeaderTapped = Notification.Name("collectionHeaderTapped") } diff --git a/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift index 6d0f349..8fdaf8c 100644 --- a/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift +++ b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift @@ -22,6 +22,10 @@ class QuickSlotItemButton: NSButton { self.image = NSImage(imageLiteralResourceName: "document-text") } else if buttonModel.type == "pref" { self.image = NSImage(imageLiteralResourceName: "setting") + } else if buttonModel.type == "default-sh" { + self.image = NSImage(imageLiteralResourceName: "document-text") + } else if buttonModel.type == "default-pref" { + self.image = NSImage(imageLiteralResourceName: "setting") } self.isBordered = false self.wantsLayer = true diff --git a/Box42/QuickSlot/View/QuickSlotGroupView.swift b/Box42/QuickSlot/View/QuickSlotGroupView.swift index d6238eb..e436c86 100644 --- a/Box42/QuickSlot/View/QuickSlotGroupView.swift +++ b/Box42/QuickSlot/View/QuickSlotGroupView.swift @@ -11,7 +11,7 @@ import SnapKit class QuickSlotGroupView: NSView { lazy var divider: NSBox = Divider(completion: { [weak self] in self?.dividerAction?() }) - lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "Star"), completion: { [weak self] in self?.headerAction?() }) + lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "star"), completion: { [weak self] in self?.headerAction?() }) lazy var buttonCollectionView: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() var dividerAction: (() -> Void)? diff --git a/Box42/QuickSlot/View/QuickSlotHeaderView.swift b/Box42/QuickSlot/View/QuickSlotHeaderView.swift index 3638225..341bc48 100644 --- a/Box42/QuickSlot/View/QuickSlotHeaderView.swift +++ b/Box42/QuickSlot/View/QuickSlotHeaderView.swift @@ -11,37 +11,51 @@ import SnapKit class QuickSlotHeaderView: NSView { private var callback: (() -> Void)? - private var QuickSlotHeaderButton: NSButton! - private var QuickSlotHeaderLabel: NSTextField! + private var quickSlotHeaderButton: NSButton! + private var quickSlotHeaderLabel: NSTextField! + private var quickSlotManageButton: NSButton! init(image: NSImage, completion: @escaping () -> Void) { super.init(frame: .zero) - QuickSlotHeaderButton = NSButton(image: image, target: self, action: #selector(btnAction)) - QuickSlotHeaderButton.isBordered = false - QuickSlotHeaderButton.wantsLayer = true - QuickSlotHeaderButton.layer?.backgroundColor = NSColor.clear.cgColor + quickSlotHeaderButton = NSButton(image: image, target: self, action: #selector(btnAction)) + quickSlotHeaderButton.isBordered = false + quickSlotHeaderButton.wantsLayer = true + quickSlotHeaderButton.layer?.backgroundColor = NSColor.clear.cgColor - self.addSubview(QuickSlotHeaderButton) + self.addSubview(quickSlotHeaderButton) - QuickSlotHeaderLabel = NSTextField(labelWithString: "Quick Slot") - QuickSlotHeaderLabel.font = NSFont(name: "Inter", size: QuickSlotUI.size.font) - QuickSlotHeaderLabel.textColor = NSColor(hex: "#696969") - QuickSlotHeaderLabel.backgroundColor = NSColor.clear - QuickSlotHeaderLabel.isBordered = false + quickSlotHeaderLabel = NSTextField(labelWithString: "Quick Slot") + quickSlotHeaderLabel.font = NSFont(name: "Inter", size: QuickSlotUI.size.font) + quickSlotHeaderLabel.textColor = NSColor(hex: "#696969") + quickSlotHeaderLabel.backgroundColor = NSColor.clear + quickSlotHeaderLabel.isBordered = false - self.addSubview(QuickSlotHeaderLabel) + self.addSubview(quickSlotHeaderLabel) - QuickSlotHeaderButton.snp.makeConstraints { make in + quickSlotManageButton = NSButton(image: NSImage(imageLiteralResourceName: "add"), target: self, action: #selector(btnAction)) + quickSlotManageButton.isBordered = false + quickSlotManageButton.wantsLayer = true + quickSlotManageButton.layer?.backgroundColor = NSColor.clear.cgColor + + self.addSubview(quickSlotManageButton) + + quickSlotHeaderButton.snp.makeConstraints { make in make.left.equalToSuperview() make.top.equalToSuperview().offset(13) } - QuickSlotHeaderLabel.snp.makeConstraints { make in - make.left.equalTo(QuickSlotHeaderButton.snp.right).offset(4) - make.bottom.equalTo(QuickSlotHeaderButton.snp.bottom) + quickSlotHeaderLabel.snp.makeConstraints { make in + make.left.equalTo(quickSlotHeaderButton.snp.right).offset(4) + make.bottom.equalTo(quickSlotHeaderButton.snp.bottom) + } + + quickSlotManageButton.snp.makeConstraints { make in + make.right.equalToSuperview() + make.bottom.equalTo(quickSlotHeaderLabel.snp.bottom) } + self.callback = completion } diff --git a/Box42/QuickSlot/View/Table/QuickSlotCell.swift b/Box42/QuickSlot/View/Table/QuickSlotCell.swift new file mode 100644 index 0000000..89e6deb --- /dev/null +++ b/Box42/QuickSlot/View/Table/QuickSlotCell.swift @@ -0,0 +1,101 @@ +// +// QuickSlotCell.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit +import SnapKit + +class QuickSlotCell: NSTableCellView { + var imageButton: NSButton = NSButton() + var titleLabel: NSTextField = NSTextField() + var pathLabel: NSTextField = NSTextField() + var typeLabel: NSTextField = NSTextField() + var deleteButton: QuickSlotCellDeleteButton = QuickSlotCellDeleteButton() + + var viewModel: QuickSlotViewModel? + var qsButton: QuickSlotButtonModel? + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + addSubview(imageButton) + addSubview(titleLabel) + addSubview(pathLabel) + addSubview(typeLabel) + addSubview(deleteButton) + + imageButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(8) + make.top.equalToSuperview().offset(8) + make.bottom.equalToSuperview().offset(-8) + make.width.equalTo(imageButton.snp.height).multipliedBy(1) + } + + deleteButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-8) + make.centerY.equalToSuperview() + make.height.equalToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.left.equalTo(imageButton.snp.right).offset(8) + make.right.equalTo(deleteButton.snp.left).offset(-8) + make.top.equalTo(imageButton) + } + + pathLabel.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.right.equalTo(deleteButton.snp.left).offset(-8) + make.top.equalTo(titleLabel.snp.bottom).offset(4) + } + + typeLabel.snp.makeConstraints { make in + make.left.equalTo(pathLabel) + make.right.equalTo(deleteButton.snp.left).offset(-8) + make.top.equalTo(pathLabel.snp.bottom).offset(4) + } + } + + func configure(with qsItem: QuickSlotButtonModel, viewModel: QuickSlotViewModel?) { + self.qsButton = qsItem + self.viewModel = viewModel + titleLabel.stringValue = qsItem.title + pathLabel.stringValue = qsItem.path ?? "none" + + if qsItem.title == "CleanCache" { + imageButton.image = NSImage(imageLiteralResourceName: "trash") + } else if qsItem.type == "sh" { + imageButton.image = NSImage(imageLiteralResourceName: "document-text") + } else if qsItem.type == "pref" { + imageButton.image = NSImage(imageLiteralResourceName: "setting") + } else if qsItem.type == "default-sh" { + imageButton.image = NSImage(imageLiteralResourceName: "document-text") + } else if qsItem.type == "default-pref" { + imageButton.image = NSImage(imageLiteralResourceName: "setting") + } + imageButton.isBordered = false + + + deleteButton.target = self + deleteButton.action = #selector(deleteButtonClicked) + } + + @objc func deleteButtonClicked() { + // if let id = qsButton?.scriptUuid { + // viewModel?.removeButton(id) + // } + if let path = qsButton?.path { + viewModel?.removeButton(path) + } + } +} diff --git a/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift b/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift new file mode 100644 index 0000000..9db5098 --- /dev/null +++ b/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift @@ -0,0 +1,76 @@ +// +// QuickSlotCellDeleteButton.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit + +class QuickSlotCellDeleteButton: NSButton { + + init() { + super.init(frame: NSRect(x: 0, y: 0, width: 53, height: 100)) + + self.title = "퀵슬롯에서\n제거" + self.isBordered = false + self.wantsLayer = true + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + } + + override func layout() { + super.layout() + + for trackingArea in self.trackingAreas { + self.removeTrackingArea(trackingArea) + } + + let trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.toValue = WindowButtonUI.color.close + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.close + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius / 2 + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.close + bgColorAnimation.toValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + } +} diff --git a/Box42/QuickSlot/View/Table/QuickSlotCellManager.swift b/Box42/QuickSlot/View/Table/QuickSlotCellManager.swift new file mode 100644 index 0000000..29215c7 --- /dev/null +++ b/Box42/QuickSlot/View/Table/QuickSlotCellManager.swift @@ -0,0 +1,109 @@ +// +// QuickSlotCellManager.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit +import SnapKit + +// MARK: - 다음 버전에 추가 예정 +class QuickSlotCellManager: NSTableCellView { + var nameLabel: NSTextField = NSTextField() + var descriptionLabel: NSTextField = NSTextField() + var excuteButton: ScriptExcuteButton = ScriptExcuteButton() + var deleteButton: ScriptDeleteButton = ScriptDeleteButton() + var quickSlotButton: ScriptQuickSlotButton = ScriptQuickSlotButton() + + var viewModel: ScriptViewModel? + var script: Script? + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + addSubview(nameLabel) + addSubview(descriptionLabel) + addSubview(quickSlotButton) + addSubview(excuteButton) + addSubview(deleteButton) + + nameLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + make.width.lessThanOrEqualTo(200).priority(.high) // 최대 너비와 우선순위 설정 + } + + deleteButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + make.width.equalTo(53) + make.height.equalTo(40) + } + + excuteButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalTo(deleteButton.snp.left).offset(-8) + make.width.equalTo(70) + make.height.equalTo(40) + } + + quickSlotButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalTo(excuteButton.snp.left).offset(-8) + make.width.equalTo(53) + make.height.equalTo(40) + } + + descriptionLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(nameLabel.snp.right).offset(8) + make.right.lessThanOrEqualTo(quickSlotButton.snp.left).offset(-8) + make.width.greaterThanOrEqualTo(100).priority(.low) // 최소 너비와 낮은 우선순위 설정 + } + } + + + + func configure(with script: Script, viewModel: ScriptViewModel?) { + self.script = script + self.viewModel = viewModel + nameLabel.stringValue = script.name + descriptionLabel.stringValue = script.description ?? "description" + + deleteButton.target = self + deleteButton.action = #selector(deleteButtonClicked) + + excuteButton.target = self + excuteButton.action = #selector(excuteButtonClicked) + + quickSlotButton.target = self + quickSlotButton.action = #selector(quickSlotButtonclicked) + + } + + @objc func deleteButtonClicked() { + if let id = script?.scriptUuid { + viewModel?.deleteScript(id: id) + } + } + + @objc func excuteButtonClicked() { + if let path = script?.path { + viewModel?.excuteScript(path: path) + } + } + + @objc func quickSlotButtonclicked() { + if let id = script?.scriptUuid { + viewModel?.quickSlotScript(id: id) + } + } +} diff --git a/Box42/QuickSlot/View/Table/QuickSlotTableView.swift b/Box42/QuickSlot/View/Table/QuickSlotTableView.swift new file mode 100644 index 0000000..7fbf7b5 --- /dev/null +++ b/Box42/QuickSlot/View/Table/QuickSlotTableView.swift @@ -0,0 +1,78 @@ +// +// QuickSlotTableView.swift +// Box42 +// +// Created by Chanhee Kim on 9/3/23. +// + +import AppKit +import SnapKit +import Combine + +class QuickSlotTableView: NSTableView { + var viewModel: QuickSlotViewModel? { + didSet { + print("ViewModel has been set.") + setupBindings() + } + } + + var cancellables: Set = [] + + private func setupBindings() { + print("Setting up QuickSlotTableView...") // 디버깅 로그 + viewModel?.$buttons.sink(receiveValue: { [weak self] newScripts in + print("Received new QuickSlotTableViewModel: \(newScripts)") // 디버깅 로그 + DispatchQueue.main.async { + self?.reloadData() + } + }).store(in: &cancellables) + } + + func setup() { + self.delegate = self + self.dataSource = self + + let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("QuickSlots")) + column1.width = 100.0 + column1.title = "QuickSlot" + self.addTableColumn(column1) + } +} + +extension QuickSlotTableView: NSTableViewDelegate, NSTableViewDataSource { + func getCellForRow(at row: Int) -> NSView { + guard let viewModel = viewModel else { + return NSView() + } + + if row < viewModel.buttons.count { + return getQuickSlotCell(for: viewModel.buttons[row], viewModel: viewModel) + } else { + return getQuickSlotCellManager() + } + } + + private func getQuickSlotCell(for quickSlotItem: QuickSlotButtonModel, viewModel: QuickSlotViewModel) -> QuickSlotCell { + let cell = QuickSlotCell(frame: .zero) + cell.configure(with: quickSlotItem, viewModel: viewModel) + return cell + } + + private func getQuickSlotCellManager() -> QuickSlotCellManager { + let quickSlotCellManger = QuickSlotCellManager(frame: .zero) + return quickSlotCellManger + } + + func numberOfRows(in tableView: NSTableView) -> Int { + return (viewModel?.buttons.count ?? 0 ) // + 1 + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + getCellForRow(at: row) + } + + func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { + return 200 + } +} diff --git a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift index ca43f68..351a562 100644 --- a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift +++ b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift @@ -13,14 +13,20 @@ class QuickSlotViewModel { @Published var buttons: [QuickSlotButtonModel] = [] private init() { - let button1 = QuickSlotButtonModel(id: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, + let button1 = QuickSlotButtonModel(scriptUuid: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, title: QuickSlotUI.title.clean, path: Bundle.main.path(forResource: "cleanCache", ofType: "sh"), type: "sh" ) - let button2 = QuickSlotButtonModel(title: QuickSlotUI.title.preferences, type: "pref") - let button3 = QuickSlotButtonModel(title: QuickSlotUI.title.scripts) - let button4 = QuickSlotButtonModel(title: QuickSlotUI.title.user, type: "pref") + let button2 = QuickSlotButtonModel(title: QuickSlotUI.title.preferences, + path: "preferences", + type: "default-pref") + let button3 = QuickSlotButtonModel(title: QuickSlotUI.title.scripts, + path: "scripts", + type: "default-sh") + let button4 = QuickSlotButtonModel(title: QuickSlotUI.title.user, + path: "user", + type: "default-pref") buttons = [button1, button2, button3, button4] } @@ -45,10 +51,39 @@ class QuickSlotViewModel { } func removeButton(_ id: UUID) { - buttons.removeAll { $0.scriptUuid == id } - updateMeQuickSlot() + if let index = buttons.firstIndex(where: { $0.scriptUuid == id }) { + let buttonToRemove = buttons[index] + if let type = buttonToRemove.type, !type.hasPrefix("default") { + buttons.remove(at: index) + updateMeQuickSlot() + } + } } + func removeButton(_ path: String) { + print("1. Attempting to remove button with path: \(path)") + print("2. Current buttons: \(buttons)") + if let index = buttons.firstIndex(where: { $0.path == path }) { + print("3. Found button at index: \(index)") + let buttonToRemove = buttons[index] + print("4. Button to remove: \(buttonToRemove)") + if let type = buttonToRemove.type { + print("5. Button type: \(type)") + if !type.hasPrefix("default") { + print("6. Removing button...") + buttons.remove(at: index) + updateMeQuickSlot() + } else { + print("Button type starts with 'default'. Skipping removal.") + } + } else { + print("Button type is nil. Skipping removal.") + } + } else { + print("Button not found.") + } + } + func updateButton(id: UUID, newTitle: String) { if let index = buttons.firstIndex(where: { $0.scriptUuid == id }) { buttons[index].title = newTitle @@ -67,3 +102,4 @@ class QuickSlotViewModel { } } } + diff --git a/Box42/Resources/Assets.xcassets/uibuttons/add.imageset/Contents.json b/Box42/Resources/Assets.xcassets/uibuttons/add.imageset/Contents.json new file mode 100644 index 0000000..52e243f --- /dev/null +++ b/Box42/Resources/Assets.xcassets/uibuttons/add.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "add.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Box42/Resources/Assets.xcassets/uibuttons/add.imageset/add.png b/Box42/Resources/Assets.xcassets/uibuttons/add.imageset/add.png new file mode 100644 index 0000000000000000000000000000000000000000..12a1110ba4ffadb19bec1665137e64006dc072e3 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=QDq5jpAb51}9S6$aB~=&Waxs_zB5l)=;0&t;uc GLK6VCwqi&C literal 0 HcmV?d00001 diff --git a/Box42/Resources/Assets.xcassets/uibuttons/add.png b/Box42/Resources/Assets.xcassets/uibuttons/add.png new file mode 100644 index 0000000000000000000000000000000000000000..12a1110ba4ffadb19bec1665137e64006dc072e3 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=QDq5jpAb51}9S6$aB~=&Waxs_zB5l)=;0&t;uc GLK6VCwqi&C literal 0 HcmV?d00001 diff --git a/Box42/Shared/User/UserProfile.swift b/Box42/Shared/User/UserProfile.swift index fdeafcf..b786d4d 100644 --- a/Box42/Shared/User/UserProfile.swift +++ b/Box42/Shared/User/UserProfile.swift @@ -46,7 +46,7 @@ extension UserProfile { statusMessage: "hello 42Box!", profileImageUrl: "https://42box.kr/user_profile_image/a52671f9-fca9-43ad-b0c0-1c5360831cf2.png", profileImagePath: "user_profile_image/a52671f9-fca9-43ad-b0c0-1c5360831cf2.png", - quickSlotList: [ QuickSlotButtonModel(id: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, title: "cleanCache", path: Bundle.main.path(forResource: "cleanCache", ofType: "sh")), + quickSlotList: [ QuickSlotButtonModel(scriptUuid: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, title: "cleanCache", path: Bundle.main.path(forResource: "cleanCache", ofType: "sh")), ] ) } From efbfece094a6d40465d4aaca75d22961b25b0940 Mon Sep 17 00:00:00 2001 From: chanhihi Date: Mon, 4 Sep 2023 14:12:14 +0900 Subject: [PATCH 10/17] =?UTF-8?q?fix:=20configuration=EC=9D=84=20=EB=BA=8F?= =?UTF-8?q?=EC=96=B4=EA=B0=80=EB=8D=98=20webview=20=ED=95=98=EB=82=98?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42/View/BoxContentsViewGroup.swift | 137 ++++++++++---------------- Box42/WebView/WebView.swift | 59 +++++++++-- Box42/WebView/WebViewController.swift | 7 -- 3 files changed, 104 insertions(+), 99 deletions(-) diff --git a/Box42/View/BoxContentsViewGroup.swift b/Box42/View/BoxContentsViewGroup.swift index bb48f46..df71ba7 100644 --- a/Box42/View/BoxContentsViewGroup.swift +++ b/Box42/View/BoxContentsViewGroup.swift @@ -8,106 +8,75 @@ import WebKit import SnapKit -class BoxContentsViewGroup: NSView, WKUIDelegate { - // var webVC: WebViewController? +class BoxContentsViewGroup: NSView { + var webVC: WebViewController? var preferencesVC = PreferencesViewController() var scriptsVC = ScriptsViewController() - var webView: WKWebView! +// var webView: WKWebView! static let shared = BoxContentsViewGroup() init() { super.init(frame: .zero) - // webVC = WebViewController(nibName: nil, bundle: nil) + webVC = WebViewController(nibName: nil, bundle: nil) self.wantsLayer = true self.layer?.cornerRadius = 20 self.layer?.masksToBounds = true - // self.addSubview(webVC!.view) - // webVC?.view.snp.makeConstraints { make in - // make.edges.equalTo(self) - // } - - let webConfiguration = WKWebViewConfiguration() - webView = WKWebView(frame: .zero, configuration: webConfiguration) - webView.uiDelegate = self - WebViewManager.shared.hostingWebView = webView - self.addSubview(webView) - webView.snp.makeConstraints { make in + self.addSubview(webVC!.view) + webVC?.view.snp.makeConstraints { make in make.edges.equalTo(self) } - if let url = URL(string: "https://www.42box.kr") { - let request = URLRequest(url: url) - webView.load(request) - } - - NotificationCenter.default.addObserver(self, selector: - #selector(goBack), name: - Notification.Name("goBack"), object:nil) - - NotificationCenter.default.addObserver(self, selector: - #selector(goForward), name: - Notification.Name("goForward"), object:nil) - - NotificationCenter.default.addObserver(self, selector: - #selector(reload), name: - Notification.Name("reload"), object:nil) - } - - deinit { - // view controller가 해제될 때 observer도 제거합니다. - NotificationCenter.default.removeObserver(self) - } - - @objc func goBack() { - if webView.canGoBack { - webView.goBack() - } - } - - @objc func goForward() { - if webView.canGoForward { - webView.goForward() - } - } - - @objc func reload() { - webView.reload() - } - - func webView(_ webView: WKWebView, - createWebViewWith configuration: WKWebViewConfiguration, - for navigationAction: WKNavigationAction, - windowFeatures: WKWindowFeatures) -> WKWebView? { - - if navigationAction.targetFrame == nil { - if let url = navigationAction.request.url { - if navigationAction.navigationType == .linkActivated { - webView.load(URLRequest(url: url)) - return nil - } - } - } - return nil - } - - func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { - let openPanel = NSOpenPanel() - openPanel.canChooseFiles = true - openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection - openPanel.level = .popUpMenu - - openPanel.begin { (result) in - if result == .OK { - completionHandler(openPanel.urls) - } else { - completionHandler(nil) - } - } +// let webConfiguration = WKWebViewConfiguration() +// webView = WKWebView(frame: .zero, configuration: webConfiguration) +// +// webView.uiDelegate = self +// WebViewManager.shared.hostingWebView = webView +// self.addSubview(webView) +// webView.snp.makeConstraints { make in +// make.edges.equalTo(self) +// } +// +// if let url = URL(string: "https://www.42box.kr") { +// let request = URLRequest(url: url) +// webView.load(request) +// } +// +// NotificationCenter.default.addObserver(self, selector: +// #selector(goBack), name: +// Notification.Name("goBack"), object:nil) +// +// NotificationCenter.default.addObserver(self, selector: +// #selector(goForward), name: +// Notification.Name("goForward"), object:nil) +// +// NotificationCenter.default.addObserver(self, selector: +// #selector(reload), name: +// Notification.Name("reload"), object:nil) } +// +// deinit { +// // view controller가 해제될 때 observer도 제거합니다. +// NotificationCenter.default.removeObserver(self) +// } +// +// @objc func goBack() { +// if webView.canGoBack { +// webView.goBack() +// } +// } +// +// @objc func goForward() { +// if webView.canGoForward { +// webView.goForward() +// } +// } +// +// @objc func reload() { +// webView.reload() +// } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Box42/WebView/WebView.swift b/Box42/WebView/WebView.swift index ac76554..9117d5d 100644 --- a/Box42/WebView/WebView.swift +++ b/Box42/WebView/WebView.swift @@ -7,7 +7,7 @@ import WebKit -class WebView: WKWebView, WKScriptMessageHandler { +class WebView: WKWebView, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate { var icon = MenubarViewController() init() { @@ -32,6 +32,9 @@ class WebView: WKWebView, WKScriptMessageHandler { self.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") self.becomeFirstResponder() + + self.navigationDelegate = self + self.uiDelegate = self } required init?(coder: NSCoder) { @@ -39,6 +42,41 @@ class WebView: WKWebView, WKScriptMessageHandler { } } +extension WebView { + // front openpanel + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + let openPanel = NSOpenPanel() + openPanel.canChooseFiles = true + openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection + openPanel.level = .popUpMenu + + openPanel.begin { (result) in + if result == .OK { + completionHandler(openPanel.urls) + } else { + completionHandler(nil) + } + } + } + + // front new tap navigation + func webView(_ webView: WKWebView, + createWebViewWith configuration: WKWebViewConfiguration, + for navigationAction: WKNavigationAction, + windowFeatures: WKWindowFeatures) -> WKWebView? { + + if navigationAction.targetFrame == nil { + if let url = navigationAction.request.url { + if navigationAction.navigationType == .linkActivated { + webView.load(URLRequest(url: url)) + return nil + } + } + } + return nil + } +} + // MARK: - Front Client 통신 extension WebView { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { @@ -73,7 +111,12 @@ extension WebView { let decoder = JSONDecoder() let downloadString = try decoder.decode(Script.self, from: scriptJson!) - ScriptViewModel.shared.addScript(id: UUID(), name: downloadString.name, description: downloadString.description ?? "description", path: downloadString.path, savedId: Int(downloadString.savedId ?? 0), userUuid: downloadString.userUuid!) + ScriptViewModel.shared.addScript(scriptUuid: downloadString.scriptUuid, + name: downloadString.name, + description: downloadString.description, + path: downloadString.path, + savedId: downloadString.savedId, + userUuid: downloadString.userUuid) print(downloadString) @@ -92,9 +135,9 @@ extension WebView { let executeScript = try decoder.decode(Script.self, from: scriptJson!) print(String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") - print(executeScript) - - ScriptsFileManager.downloadFile(from: "https://42box.kr/" + executeScript.path) + DispatchQueue.global().async { + ScriptsFileManager.downloadFile(from: "https://42box.kr/" + executeScript.path) + } } catch { print("JSON decoding failed: \(error)") } @@ -110,9 +153,9 @@ extension WebView { let deleteScript = try decoder.decode(Script.self, from: scriptJson!) print(String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") - print(deleteScript) - - ScriptViewModel.shared.deleteScript(id: deleteScript.scriptUuid!) + DispatchQueue.global().async { + ScriptViewModel.shared.deleteScript(id: deleteScript.scriptUuid) + } } catch { print("JSON decoding failed: \(error)") } diff --git a/Box42/WebView/WebViewController.swift b/Box42/WebView/WebViewController.swift index cb4b4c9..3acc72d 100644 --- a/Box42/WebView/WebViewController.swift +++ b/Box42/WebView/WebViewController.swift @@ -61,11 +61,4 @@ class WebViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() } - - func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { - if let url = navigationAction.request.url { - webView.load(URLRequest(url: url)) - } - return nil - } } From f29a519678b623bf1a6a3704d4e8f8af7998f30c Mon Sep 17 00:00:00 2001 From: chanhihi Date: Mon, 4 Sep 2023 14:13:02 +0900 Subject: [PATCH 11/17] =?UTF-8?q?refactor:=20script=20uuid=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=ED=95=A9=EB=8B=88=EB=8B=A4?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Box42/Scripts/Model/Scripts.swift | 4 +- Box42/Scripts/View/Table/ScriptCell.swift | 13 +++-- .../View/Table/ScriptCellManager.swift | 7 ++- .../Scripts/ViewModel/ScriptsViewModel.swift | 55 +++++++++++++++---- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/Box42/Scripts/Model/Scripts.swift b/Box42/Scripts/Model/Scripts.swift index d22fc03..313511f 100644 --- a/Box42/Scripts/Model/Scripts.swift +++ b/Box42/Scripts/Model/Scripts.swift @@ -19,8 +19,8 @@ struct Script: Codable { var savedId: Int? var userUuid: String? - init(id: UUID?, name: String, description: String?, path: String, savedId: Int?, userUuid: String?) { - self.scriptUuid = id + init(scriptUuid: UUID?, name: String, description: String?, path: String, savedId: Int?, userUuid: String?) { + self.scriptUuid = scriptUuid self.name = name self.description = description self.path = path diff --git a/Box42/Scripts/View/Table/ScriptCell.swift b/Box42/Scripts/View/Table/ScriptCell.swift index c8be376..251e194 100644 --- a/Box42/Scripts/View/Table/ScriptCell.swift +++ b/Box42/Scripts/View/Table/ScriptCell.swift @@ -95,27 +95,28 @@ class ScriptCell: NSTableCellView { } @objc func excuteButtonClicked() { - if let id = script?.scriptUuid { - viewModel?.excuteScript(id: id) + if let path = script?.path { + viewModel?.excuteScript(path: path) } } @objc func quickSlotButtonclicked() { - guard let id = script?.scriptUuid else { + guard let path = script?.path else { return } - let alreadyExists = QuickSlotViewModel.shared.buttons.contains { $0.scriptUuid == id } + let alreadyExists = QuickSlotViewModel.shared.buttons.contains { $0.path == path } if alreadyExists { - QuickSlotViewModel.shared.removeButton(id) + QuickSlotViewModel.shared.removeButton(path) quickSlotButton.title = "퀵슬롯" } else { if QuickSlotViewModel.shared.buttons.count > 7 { return } else { quickSlotButton.title = "저장됨" - viewModel?.quickSlotScript(id: id) +// viewModel?.quickSlotScript(id: id) + viewModel?.quickSlotScript(path: path) } } } diff --git a/Box42/Scripts/View/Table/ScriptCellManager.swift b/Box42/Scripts/View/Table/ScriptCellManager.swift index a7567ae..1dfc952 100644 --- a/Box42/Scripts/View/Table/ScriptCellManager.swift +++ b/Box42/Scripts/View/Table/ScriptCellManager.swift @@ -96,8 +96,11 @@ class ScriptCellManager: NSTableCellView { } @objc func excuteButtonClicked() { - if let id = script?.scriptUuid { - viewModel?.excuteScript(id: id) + // if let id = script?.scriptUuid { + // viewModel?.excuteScript(id: id) + // } + if let path = script?.path { + viewModel?.excuteScript(path: path) } } diff --git a/Box42/Scripts/ViewModel/ScriptsViewModel.swift b/Box42/Scripts/ViewModel/ScriptsViewModel.swift index 9abc40e..a2f2a51 100644 --- a/Box42/Scripts/ViewModel/ScriptsViewModel.swift +++ b/Box42/Scripts/ViewModel/ScriptsViewModel.swift @@ -15,7 +15,7 @@ class ScriptViewModel: NSObject { private override init() { self.scripts = [ - Script(id: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3"), + Script(scriptUuid: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3"), name: "CleanCache", description: "Cleaning cache", path: Bundle.main.path(forResource: "cleanCache", ofType: "sh") ?? "", savedId: -1 , userUuid: nil), @@ -39,19 +39,24 @@ class ScriptViewModel: NSObject { } // Create - func addScript(id: UUID = UUID(), name: String, description: String, path: String, savedId: Int, userUuid: String) { - let newScript = Script(id: UUID(), name: name, description: description, path: path, savedId: savedId, userUuid: userUuid) + func addScript(scriptUuid: UUID?, name: String, description: String?, path: String, savedId: Int?, userUuid: String?) { + let newScript = Script(scriptUuid: scriptUuid, name: name, description: description, path: path, savedId: savedId, userUuid: userUuid) scripts.append(newScript) } + // // Read + // func excuteScript(id: UUID) { + // if let index = scripts.firstIndex(where: { $0.scriptUuid == id }) { + // // MARK: - 파일스크립트 매니저에서 권한을 얻은 실행으로 실행합니다. + // ScriptsFileManager.downloadFile(from: "https://42box.kr/" + scripts[index].path) + // } + // } + // Read - func excuteScript(id: UUID) { - if let index = scripts.firstIndex(where: { $0.scriptUuid == id }) { - // ExecuteScripts.executeShellScript(path: scripts[index].name) + func excuteScript(path: String) { + if scripts.firstIndex(where: { $0.path == path }) != nil { // MARK: - 파일스크립트 매니저에서 권한을 얻은 실행으로 실행합니다. - ScriptsFileManager.downloadFile(from: "https://42box.kr/" + scripts[index].path) - - // SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: scripts[index].path) + ScriptsFileManager.downloadFile(from: "https://42box.kr/" + path) } } @@ -64,13 +69,31 @@ class ScriptViewModel: NSObject { } // Delete - func deleteScript(id: UUID) { + func deleteScript(id: UUID?) { if let script = scripts.first(where: { $0.scriptUuid == id }) { API.deleteUserMeScripts(WebViewManager.shared.getCookieWebKit, savedId: script.savedId!) { result in switch result { case .success(_): self.scripts.removeAll(where: { $0.scriptUuid == id }) - QuickSlotViewModel.shared.removeButton(id) + if let scriptUuid = id { + QuickSlotViewModel.shared.removeButton(scriptUuid) + } + + case .failure(let error): + print("Failed to delete script: \(error)") + } + } + } + } + + // Delete + func deleteScript(path: String) { + if let script = scripts.first(where: { $0.path == path }) { + API.deleteUserMeScripts(WebViewManager.shared.getCookieWebKit, savedId: script.savedId!) { result in + switch result { + case .success(_): + self.scripts.removeAll(where: { $0.path == path }) + QuickSlotViewModel.shared.removeButton(path) case .failure(let error): print("Failed to delete script: \(error)") @@ -92,7 +115,15 @@ class ScriptViewModel: NSObject { // 스크립트안에서 해당하는 스크립트를 찾아서 quickslotVM에 추가 func quickSlotScript(id: UUID) { if let index = scripts.firstIndex(where: { $0.scriptUuid == id }) { - let button = QuickSlotButtonModel(id: id, title: scripts[index].name, path: scripts[index].path) + let button = QuickSlotButtonModel(scriptUuid: id, title: scripts[index].name, path: scripts[index].path) + QuickSlotViewModel.shared.addButton(button) + } + } + + // 스크립트안에서 해당하는 스크립트를 찾아서 quickslotVM에 추가 + func quickSlotScript(path: String) { + if let index = scripts.firstIndex(where: { $0.path == path }) { + let button = QuickSlotButtonModel(scriptUuid: scripts[index].scriptUuid, title: scripts[index].name, path: scripts[index].path) QuickSlotViewModel.shared.addButton(button) } } From 371274ad242a322fd440b2317077c19ac3919e11 Mon Sep 17 00:00:00 2001 From: DaSol Kim Date: Mon, 4 Sep 2023 16:09:37 +0900 Subject: [PATCH 12/17] refactor: refactoring the main view. --- Box42.xcodeproj/project.pbxproj | 4 + .../Main/BoxBaseContainerViewController.swift | 657 +++++++++++++++--- Box42/QuickSlot/View/QuickSlotGroupView.swift | 2 +- .../42Box icon 1.png | Bin 0 -> 624 bytes .../42Box icon 2.png | Bin 0 -> 624 bytes .../bookmark-default.imageset/42Box icon.png | Bin 0 -> 624 bytes .../bookmark-default.imageset/Contents.json | 23 + Box42/View/BoxContentsViewGroup.swift | 137 ++-- 8 files changed, 663 insertions(+), 160 deletions(-) create mode 100644 Box42/Resources/Assets.xcassets/Icons/bookmark-default.imageset/42Box icon 1.png create mode 100644 Box42/Resources/Assets.xcassets/Icons/bookmark-default.imageset/42Box icon 2.png create mode 100644 Box42/Resources/Assets.xcassets/Icons/bookmark-default.imageset/42Box icon.png create mode 100644 Box42/Resources/Assets.xcassets/Icons/bookmark-default.imageset/Contents.json diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index a5f301d..3a787bd 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7ECC67EA2AA5B74600265D54 /* BookmarkEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECC67E92AA5B74600265D54 /* BookmarkEditorView.swift */; }; DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */; }; DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB62A5099F900FF0AA3 /* Box42.xcdatamodeld */; }; DE018BDD2A509AEB00FF0AA3 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BDC2A509AEB00FF0AA3 /* EventMonitor.swift */; }; @@ -133,6 +134,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 7ECC67E92AA5B74600265D54 /* BookmarkEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BookmarkEditorView.swift; path = ../../../42box4/Box42/View/BookmarkEditorView.swift; sourceTree = ""; }; DE018BAF2A5099F900FF0AA3 /* Box42.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Box42.app; sourceTree = BUILT_PRODUCTS_DIR; }; DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DE018BB72A5099F900FF0AA3 /* Box42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Box42.xcdatamodel; sourceTree = ""; }; @@ -445,6 +447,7 @@ DE4408202A9297EE0091937A /* View */ = { isa = PBXGroup; children = ( + 7ECC67E92AA5B74600265D54 /* BookmarkEditorView.swift */, DE1F1A1A2A8B50C500A88DD8 /* BoxContentsViewGroup.swift */, DE24E6372A8FE10300E29F5D /* BoxBaseSplitView.swift */, ); @@ -876,6 +879,7 @@ DE0A917B2A8F0CA800D1D6F1 /* AppleScripts+ShowMessage.swift in Sources */, DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */, DEF0761B2AA33671005700E5 /* DeleteUserMeScript.swift in Sources */, + 7ECC67EA2AA5B74600265D54 /* BookmarkEditorView.swift in Sources */, DE62BE672A9BA92E00D97E06 /* QuickSlotButtonViewItem.swift in Sources */, DEF076302AA3CF8A005700E5 /* QuickSlotItemLabel.swift in Sources */, DE874F542A591F1400FC3B77 /* PreferencesView.swift in Sources */, diff --git a/Box42/Main/BoxBaseContainerViewController.swift b/Box42/Main/BoxBaseContainerViewController.swift index b2cc181..861a902 100644 --- a/Box42/Main/BoxBaseContainerViewController.swift +++ b/Box42/Main/BoxBaseContainerViewController.swift @@ -8,16 +8,31 @@ import Cocoa import SnapKit +let bookMarkList = [ + ("home", "https://42box.kr/"), + ("23Coaltheme", "https://42box.github.io/front-end/"), + ("Box 42", "https://42box.github.io/front-end/#/box"), + ("Intra 42", "https://intra.42.fr"), + ("Jiphyeonjeon", "https://42library.kr"), + ("42STAT", "https://stat.42seoul.kr/home"), + ("24Hane", "https://24hoursarenotenough.42seoul.kr"), + ("80kCoding", "https://80000coding.oopy.io"), + ("where42", "https://www.where42.kr"), + ("cabi", "https://cabi.42seoul.io/"), + ("42gg", "https://42gg.kr/"), + ("textart", "https://textart.sh/"), +] + class BoxBaseContainerViewController: NSViewController { // MARK: - LeftContainer - var splitView: BoxBaseSplitView = BoxBaseSplitView() +// var splitView: BoxBaseSplitView = BoxBaseSplitView() var contentGroup: BoxContentsViewGroup = BoxContentsViewGroup() var toolbarGroupVC: ToolbarViewController = ToolbarViewController() var quickSlotGroupVC: QuickSlotViewController = QuickSlotViewController() var functionGroupVC: BoxFunctionViewController = BoxFunctionViewController() let windowViewGroupVC: WindowButtonViewController = WindowButtonViewController() - var leftContainer: MovableContainerView = MovableContainerView() - var buttonGroupVC: ButtonGroupViewController = ButtonGroupViewController() +// var leftContainer: MovableContainerView = MovableContainerView() +// var buttonGroupVC: ButtonGroupViewController = ButtonGroupViewController() // MARK: - QuickSlot var preferenceVC: PreferencesViewController = PreferencesViewController() @@ -28,129 +43,296 @@ class BoxBaseContainerViewController: NSViewController { var quickSlotManagerVC: QuickSlotManagerViewController = QuickSlotManagerViewController() var quickSlotButtonCollectionVC: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() + private let splitView: NSSplitView = { + let splitView = NSSplitView() + splitView.isVertical = true + splitView.dividerStyle = .thick + return splitView + }() + + private let leftView: NSView = { + let view = NSView() + view.frame.size.width = 302 - 12 + view.frame.size.height = 1200 + return view + }() + + private let bookMarkView: NSView = { + let view = NSView() + return view + }() + + let tableView: NSTableView = { + let tableView = NSTableView() + tableView.autoresizingMask = [.width, .height] + tableView.headerView = nil + return tableView + }() + + var buttonTitleArray = bookMarkList.map { $0.0 } + var urlArray = bookMarkList.map { $0.1 } + var selectedRow: Int? + var selectedButton: DraggableButton? override func loadView() { self.view = NSView() - self.view.addSubview(splitView) - splitView.delegate = self - -// buttonGroup = BoxButtonViewGroupInit() - - leftContainerInit() - viewInit() - } - - override func viewDidLoad() { - self.view.wantsLayer = true - -// self.view.layer?.backgroundColor = NSColor(hex: "#FF9548").cgColor + self.view.wantsLayer=true self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor + self.view.snp.makeConstraints { make in + make.width.equalTo(BoxSizeManager.shared.size.width) + make.height.equalTo(BoxSizeManager.shared.size.height) + } - NotificationCenter.default.addObserver(self, selector: #selector(handleButtonTapped), name: .collectionButtonTapped, object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(headerTappedQuickSlotManagerHandle), name: .collectionHeaderTapped, object: nil) - } - - func BoxButtonViewGroupInit() -> BoxButtonViewGroup { + splitView.addArrangedSubview(leftView) + splitView.addArrangedSubview(contentGroup) + self.view.addSubview(splitView) - let buttonGroup = BoxButtonViewGroup { sender in - self.clickBtn(sender: sender) - } - - return buttonGroup - } - - func clickBtn(sender: Any?) { - if let button = sender as? NSButton { - guard let clickCount = NSApp.currentEvent?.clickCount else { return } - if clickCount == 2 { - WebViewManager.shared.list[button.title]!.reload() - print("Dobule Click") - } else if clickCount > 2 { - if let currentURL = WebViewManager.shared.hostingWebView?.url { - NSWorkspace.shared.open(currentURL) - } - print("Triple Click") - } else if clickCount < 2 { - contentGroup.removeAllSubviews() - contentGroup.showWebviews(button) - } - } else { - if let str = sender as? String { - if str == "box" { - contentGroup.removeAllSubviews() - print("box inside") - } - } + splitView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(10) } - } - - private func leftContainerInit() { - leftContainer.frame.size.width = BoxSizeManager.shared.windowButtonGroupSize.width - leftContainer.frame.size.height = BoxSizeManager.shared.windowButtonGroupSize.height - leftContainer.addSubview(windowViewGroupVC.view) - leftContainer.addSubview(buttonGroupVC.view) - leftContainer.addSubview(toolbarGroupVC.view) - leftContainer.addSubview(quickSlotGroupVC.view) - leftContainer.addSubview(functionGroupVC.view) - - leftContainerAutolayout() - } - - private func leftContainerAutolayout() { + + leftView.addSubview(windowViewGroupVC.view) + leftView.addSubview(bookMarkView) + leftView.addSubview(toolbarGroupVC.view) + leftView.addSubview(quickSlotGroupVC.view) + leftView.addSubview(functionGroupVC.view) + windowViewGroupVC.view.snp.makeConstraints { make in - make.top.equalTo(leftContainer) - make.left.equalTo(leftContainer).offset(3) + make.top.equalTo(leftView) + make.left.equalTo(leftView).offset(3) make.width.equalTo(77) make.height.equalTo(21) } - toolbarGroupVC.view.snp.makeConstraints { make in make.top.equalTo(windowViewGroupVC.view.snp.bottom).offset(31) - make.right.equalTo(leftContainer) - make.left.equalTo(leftContainer) + make.right.equalTo(leftView) + make.left.equalTo(leftView) make.height.equalTo(44 + 14 + 24) } - - buttonGroupVC.view.snp.makeConstraints { make in + bookMarkView.snp.makeConstraints { make in make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) - make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) - make.left.equalTo(leftContainer) + make.leading.trailing.equalToSuperview() make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) } + quickSlotGroupVC.view.snp.makeConstraints { make in + make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) + make.right.equalTo(leftView).offset(-Constants.UI.groupAutolayout) + make.left.equalTo(leftView) + make.height.equalTo(178) + } + functionGroupVC.view.snp.makeConstraints { make in + make.right.equalTo(leftView).offset(-Constants.UI.groupAutolayout) + make.left.bottom.equalTo(leftView) + } - quickSlotGroupVC.view.snp.makeConstraints { make in - make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) - make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) - make.left.equalTo(leftContainer) - make.height.equalTo(178) - } + splitView.delegate = self - functionGroupVC.view.snp.makeConstraints { make in - make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) - make.left.bottom.equalTo(leftContainer) + let stackView = NSStackView() + stackView.orientation = .horizontal + stackView.spacing = 6 + stackView.alignment = .centerY + + let imageView = NSImageView() + imageView.image = NSImage(named: NSImage.Name("bookmark")) + + let label = NSTextField(labelWithString: "북마크") + label.textColor = NSColor.black + label.font = NSFont.boldSystemFont(ofSize: 16) + + let buttonImage = NSImage(named: NSImage.Name("add"))! + buttonImage.size = NSSize(width: 24, height: 24) + let button = NSButton(image: buttonImage, target: self, action: #selector(addBookMarkButtonClicked(_:))) + button.setButtonType(.momentaryChange) + button.bezelStyle = .texturedRounded + button.isBordered = false + button.wantsLayer = true + button.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor + let spacerView = NSView() + spacerView.snp.makeConstraints { make in + make.width.equalTo(173) + } + + stackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(label) + stackView.addArrangedSubview(spacerView) + stackView.addArrangedSubview(button) + + tableView.wantsLayer = true + tableView.backgroundColor = NSColor(hex: "#E7E7E7") + tableView.focusRingType = .none + tableView.headerView = nil + tableView.autoresizingMask = [.width, .height] + tableView.selectionHighlightStyle = .none + tableView.intercellSpacing = NSSize(width: 0, height: 0) + tableView.setDraggingSourceOperationMask(.move, forLocal: true) + + let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("column1")) + column.title = "" + // column.width = 100 + column.resizingMask = .autoresizingMask + + tableView.addTableColumn(column) + + + let scrollView = NSScrollView() + scrollView.hasVerticalScroller = true + scrollView.documentView = tableView + + bookMarkView.addSubview(stackView) + bookMarkView.addSubview(scrollView) + // bookMarkView.addSubview(tableView) + + stackView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(18) + make.leading.trailing.equalToSuperview().offset(0) + make.height.equalTo(24) } + + // tableView.snp.makeConstraints { make in + // make.top.equalTo(stackView.snp.bottom).offset(0) + // make.leading.equalToSuperview() + // } + + scrollView.snp.makeConstraints { make in + make.top.equalTo(stackView.snp.bottom).offset(0) + make.leading.trailing.equalToSuperview().offset(0) + make.bottom.equalToSuperview() + } + + tableView.dataSource = self + tableView.delegate = self + tableView.registerForDraggedTypes([NSPasteboard.PasteboardType.string]) } - func viewInit() { - self.boxViewSizeInit() + @objc func addBookMarkButtonClicked(_ sender: NSButton) { + splitView.removeArrangedSubview(contentGroup) + contentGroup.removeFromSuperview() - splitView.addArrangedSubview(leftContainer) - splitView.addArrangedSubview(contentGroup) - self.view.addSubview(splitView) + let newView = BookmarkEditorView(bookMarkList: bookMarkList) + newView.wantsLayer = true + newView.layer?.backgroundColor = NSColor.black.cgColor + newView.layer?.cornerRadius = 20 + newView.frame.size = contentGroup.frame.size - splitView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(Constants.UI.groupAutolayout) - make.left.equalToSuperview().offset(Constants.UI.groupAutolayout) - make.right.equalToSuperview().offset(-Constants.UI.groupAutolayout) - make.bottom.equalToSuperview().offset(-Constants.UI.groupAutolayout) + contentGroup.addSubview(newView) + newView.snp.makeConstraints { make in + make.edges.equalToSuperview() } + + splitView.addArrangedSubview(contentGroup) } - func boxViewSizeInit() { - self.view.frame.size.width = BoxSizeManager.shared.size.width - self.view.frame.size.height = BoxSizeManager.shared.size.height + override func viewDidLoad() { +// self.view.wantsLayer = true +// +//// self.view.layer?.backgroundColor = NSColor(hex: "#FF9548").cgColor +// self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor + + NotificationCenter.default.addObserver(self, selector: #selector(handleButtonTapped), name: .collectionButtonTapped, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(headerTappedQuickSlotManagerHandle), name: .collectionHeaderTapped, object: nil) } + +// func BoxButtonViewGroupInit() -> BoxButtonViewGroup { +// +// let buttonGroup = BoxButtonViewGroup { sender in +// self.clickBtn(sender: sender) +// } +// +// return buttonGroup +// } + +// func clickBtn(sender: Any?) { +// if let button = sender as? NSButton { +// guard let clickCount = NSApp.currentEvent?.clickCount else { return } +// if clickCount == 2 { +// WebViewManager.shared.list[button.title]!.reload() +// print("Dobule Click") +// } else if clickCount > 2 { +// if let currentURL = WebViewManager.shared.hostingWebView?.url { +// NSWorkspace.shared.open(currentURL) +// } +// print("Triple Click") +// } else if clickCount < 2 { +// contentGroup.removeAllSubviews() +// contentGroup.showWebviews(button) +// } +// } else { +// if let str = sender as? String { +// if str == "box" { +// contentGroup.removeAllSubviews() +// print("box inside") +// } +// } +// } +// } +// +// private func leftContainerInit() { +// leftContainer.frame.size.width = BoxSizeManager.shared.windowButtonGroupSize.width +// leftContainer.frame.size.height = BoxSizeManager.shared.windowButtonGroupSize.height +// leftContainer.addSubview(windowViewGroupVC.view) +// leftContainer.addSubview(buttonGroupVC.view) +// leftContainer.addSubview(toolbarGroupVC.view) +// leftContainer.addSubview(quickSlotGroupVC.view) +// leftContainer.addSubview(functionGroupVC.view) +// +// leftContainerAutolayout() +// } +// +// private func leftContainerAutolayout() { +// windowViewGroupVC.view.snp.makeConstraints { make in +// make.top.equalTo(leftContainer) +// make.left.equalTo(leftContainer).offset(3) +// make.width.equalTo(77) +// make.height.equalTo(21) +// } +// +// toolbarGroupVC.view.snp.makeConstraints { make in +// make.top.equalTo(windowViewGroupVC.view.snp.bottom).offset(31) +// make.right.equalTo(leftContainer) +// make.left.equalTo(leftContainer) +// make.height.equalTo(44 + 14 + 24) +// } +// +// buttonGroupVC.view.snp.makeConstraints { make in +// make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) +// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) +// make.left.equalTo(leftContainer) +// make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) +// } +// +// quickSlotGroupVC.view.snp.makeConstraints { make in +// make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) +// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) +// make.left.equalTo(leftContainer) +// make.height.equalTo(178) +// } +// +// functionGroupVC.view.snp.makeConstraints { make in +// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) +// make.left.bottom.equalTo(leftContainer) +// } +// } +// +// func viewInit() { +// self.boxViewSizeInit() +// +// splitView.addArrangedSubview(leftContainer) +// splitView.addArrangedSubview(contentGroup) +// self.view.addSubview(splitView) +// +// splitView.snp.makeConstraints { make in +// make.top.equalToSuperview().offset(Constants.UI.groupAutolayout) +// make.left.equalToSuperview().offset(Constants.UI.groupAutolayout) +// make.right.equalToSuperview().offset(-Constants.UI.groupAutolayout) +// make.bottom.equalToSuperview().offset(-Constants.UI.groupAutolayout) +// } +// } +// +// func boxViewSizeInit() { +// self.view.frame.size.width = BoxSizeManager.shared.size.width +// self.view.frame.size.height = BoxSizeManager.shared.size.height +// } } extension BoxBaseContainerViewController: NSSplitViewDelegate { @@ -161,10 +343,10 @@ extension BoxBaseContainerViewController: NSSplitViewDelegate { } return proposedMinimumPosition } - + func splitView(_ splitView: NSSplitView, constrainMaxCoordinate proposedMaximumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat { if dividerIndex == 0 { - return CGFloat(312).pointsToPixels() + return CGFloat(302).pointsToPixels() } return proposedMaximumPosition } @@ -172,21 +354,284 @@ extension BoxBaseContainerViewController: NSSplitViewDelegate { func splitView(_ splitView: NSSplitView, resizeSubviewsWithOldSize oldSize: NSSize) { let dividerThickness = splitView.dividerThickness let newWidth = splitView.frame.width - dividerThickness - - let leftWidth = leftContainer.frame.width + + let leftWidth = leftView.frame.width let contentWidth = newWidth - leftWidth - - leftContainer.frame = NSRect(x: 0, y: 0, width: leftWidth, height: splitView.bounds.height) + + leftView.frame = NSRect(x: 0, y: 0, width: leftWidth, height: splitView.bounds.height) contentGroup.frame = NSRect(x: leftWidth + dividerThickness, y: 0, width: contentWidth, height: splitView.bounds.height) } } -extension BoxBaseContainerViewController: BoxFunctionViewControllerDelegate { - func didTapBoxButton() { - clickBtn(sender: "box") +extension BoxBaseContainerViewController: NSTableViewDelegate { + func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? { + let pasteboardItem = NSPasteboardItem() + pasteboardItem.setString(String(row), forType: .string) + return pasteboardItem + } + + func sendUpdatedDataToServer() { + let urlList = zip(buttonTitleArray, urlArray).map { ["name": $0.0, "url": $0.1] } + let jsonData = try? JSONSerialization.data(withJSONObject: ["urlList": urlList]) + + var request = URLRequest(url: URL(string:"https://api.42box.kr/user-service/users/me/url-list")!) + request.httpMethod = "POST" + request.httpBody = jsonData + + URLSession.shared.dataTask(with:request) { (data, response, error) in + if error != nil{ + print(error!.localizedDescription) + } + else{ + print("Data posted successfully") + } + }.resume() + } + +} + +class ButtonTableCellView: NSTableCellView { + var button: NSButton! + var deleteButton: NSButton! + var rowIndex: Int! + + override func viewWillDraw() { + super.viewWillDraw() + self.frame.size.width = 268.0 + self.frame = NSRect(x: self.frame.origin.x - 3, y: self.frame.origin.y, + width: self.frame.size.width, height: self.frame.size.height) + } +} + + +extension BoxBaseContainerViewController: NSTableViewDataSource { + func numberOfRows(in tableView: NSTableView) -> Int { + return buttonTitleArray.count + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + let cellView = ButtonTableCellView() + cellView.rowIndex = row + + let button = DraggableButton(frame: NSRect(x: 0, y: 0, width: 300, height: 44)) + button.tag = row + button.bezelStyle = .inline + button.isBordered = false + button.title = "" + button.registerForDraggedTypes([NSPasteboard.PasteboardType.string]) + button.target = self + button.action = #selector(buttonClicked(_:)) + button.delegate = self + + let label = NSTextField(frame: NSRect(x: 26 + 21 + 8, y: 25 / 2, width: button.bounds.width, height: button.bounds.height)) + + label.stringValue = buttonTitleArray[row] + label.backgroundColor = .clear + label.isBordered = false + label.isEditable = false + + let attributes : [NSAttributedString.Key : Any] = + [ + NSAttributedString.Key.font : NSFont.systemFont(ofSize:18.0, weight: .light), + NSAttributedString.Key.foregroundColor : NSColor.black, + ] + let attributedStringTitle = NSAttributedString(string: label.stringValue , attributes: + attributes) + label.attributedStringValue=attributedStringTitle + button.addSubview(label) + + + // let image = NSImage(named: NSImage.Name("bookmark-default")) + // image?.size = NSSize(width: 21, height: 21) + // button.image = image + // button.imagePosition = .imageLeading + // button.image?.alignmentRect = NSRect(x: 0, y: 0, width: 21, height: 21) + + let imageView = NSImageView(frame: NSRect(x: 26, y: 25 / 2, width: 21, height: 21)) + imageView.image = NSImage(named: NSImage.Name("bookmark-default")) + imageView.imageScaling = .scaleProportionallyUpOrDown + imageView.imageAlignment = .alignCenter + button.addSubview(imageView) + + + + cellView.addSubview(button) + + button.snp.makeConstraints { make in + make.top.equalToSuperview().offset(2) + make.leading.equalToSuperview() + make.trailing.equalToSuperview() + // make.width.equalTo(268) + make.width.lessThanOrEqualTo(268) + make.height.equalTo(44) + } + + tableView.rowHeight = 50 + + if row == selectedRow { + button.wantsLayer = true + button.layer?.cornerRadius = 12 + button.layer?.backgroundColor = NSColor.white.cgColor + } else { + button.wantsLayer = true + button.layer?.cornerRadius = 12 + button.layer?.backgroundColor = NSColor.clear.cgColor + } + + return cellView + } + + @objc func buttonClicked(_ sender: DraggableButton) { + selectedButton?.layer?.backgroundColor = NSColor.clear.cgColor + + // Update the reference to the currently selected button and change its background color. + selectedButton = sender + sender.layer?.backgroundColor = NSColor.white.cgColor + + if sender.tag < urlArray.count { + if let url = URL(string:urlArray[sender.tag]) { + print(url) + contentGroup.webView.load(URLRequest(url: url)) + } + } + } + + func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { + if dropOperation == .above { + return .move + } else { + return [] + } + } + + func tableView(_ aTableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { + guard let str = info.draggingPasteboard.string(forType: .string), let from = Int(str) else { + return false + } + + let to = (from < row) ? row - 1 : row + let item = buttonTitleArray[from] + buttonTitleArray.remove(at: from) + buttonTitleArray.insert(item, at: to) + tableView.reloadData() + + for (_, subview) in tableView.subviews.enumerated() { + guard let cellView = subview as? CustomTableCellView else { + continue + } + + cellView.button.title = buttonTitleArray[cellView.rowIndex] + } + + return true + } +} + +class DraggableButton: NSButton, NSDraggingSource { + weak var delegate: BoxBaseContainerViewController? + var mouseDownEvent: NSEvent? + + func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { + return .move + } + + override func mouseUp(with event: NSEvent) { + print("mouseUp") + if let down = self.mouseDownEvent { + if event.locationInWindow == down.locationInWindow { + self.target?.perform(self.action, with: self) + } + + super.mouseUp(with:event) + self.mouseDownEvent = nil + } + } + + override func mouseDown(with event: NSEvent) { + print("mouseDown") + self.mouseDownEvent = event + if event.clickCount > 1 { + self.target?.perform(self.action, with: self) + } + } + + override func mouseDragged(with event: NSEvent) { + guard let down = self.mouseDownEvent else { return } + + let distance = hypot( + down.locationInWindow.x - event.locationInWindow.x, + down.locationInWindow.y - event.locationInWindow.y) + + if distance > 3 { // Adjust this as needed + let pasteboardItem = NSPasteboardItem() + pasteboardItem.setString("\(self.tag)", forType: .string) + + let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) + + // Create a snapshot of the button + let snapshot = self.snapshot() + + // Set the dragging frame and contents + draggingItem.setDraggingFrame(self.bounds, contents:snapshot) + + beginDraggingSession(with: [draggingItem], event:self.mouseDownEvent!, source:self) + + self.mouseDownEvent = nil + } + } + + func snapshot() -> NSImage? { + guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { return nil } + cacheDisplay(in: bounds, to: bitmapRep) + let image = NSImage(size: bounds.size) + image.addRepresentation(bitmapRep) + return image + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + + let trackingArea = NSTrackingArea( + rect: bounds, + options: [.mouseEnteredAndExited, .activeAlways], + owner: self, + userInfo: nil + ) + + addTrackingArea(trackingArea) + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + if self != delegate?.selectedButton { + wantsLayer = true + layer?.frame.size = CGSize(width: 268.0, height: 44.0) + layer?.cornerRadius = 12 + layer?.backgroundColor = NSColor(red: 0.848, green: 0.848, blue: 0.848, alpha: 1).cgColor + } + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + if self != delegate?.selectedButton { + wantsLayer = true + layer?.frame.size = CGSize(width: 268.0, height: 44.0) + layer?.cornerRadius = 12 + layer?.backgroundColor = NSColor.clear.cgColor + } } } + + +//extension BoxBaseContainerViewController: BoxFunctionViewControllerDelegate { +// func didTapBoxButton() { +// clickBtn(sender: "box") +// } +//} + extension BoxBaseContainerViewController { @objc func handleButtonTapped(notification: NSNotification) { if let button = notification.object as? NSButton { diff --git a/Box42/QuickSlot/View/QuickSlotGroupView.swift b/Box42/QuickSlot/View/QuickSlotGroupView.swift index e436c86..d6238eb 100644 --- a/Box42/QuickSlot/View/QuickSlotGroupView.swift +++ b/Box42/QuickSlot/View/QuickSlotGroupView.swift @@ -11,7 +11,7 @@ import SnapKit class QuickSlotGroupView: NSView { lazy var divider: NSBox = Divider(completion: { [weak self] in self?.dividerAction?() }) - lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "star"), completion: { [weak self] in self?.headerAction?() }) + lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "Star"), completion: { [weak self] in self?.headerAction?() }) lazy var buttonCollectionView: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() var dividerAction: (() -> Void)? diff --git a/Box42/Resources/Assets.xcassets/Icons/bookmark-default.imageset/42Box icon 1.png b/Box42/Resources/Assets.xcassets/Icons/bookmark-default.imageset/42Box icon 1.png new file mode 100644 index 0000000000000000000000000000000000000000..b5c6cf7ee2883d97a1c1166059c89836606cfe09 GIT binary patch literal 624 zcmV-$0+0QPP)xXjNlR2;IhHp2JHsz2F*=Cn1C_@6NC-u26O|ufw93LK(U=Vj==c;q^DM{ zU(%PjvHhzc2!bF8f*?H8810M(qemnCg?e<~V{4>XmyEnRZK3f+1XSAB&v*)^TuNJ%f-mp*!t7Ck(WVgAE$J68A zW!Vx~>+!XOOJu#rr|@sPJ7&<09Pokol^#4_W*hNsJ-&yC&)$f!3)h8ADZlwUqhqAc z%}ARa;(f%|yMCYfAMu?$=M0h({&qs2yOFj7(>Nr0ryl=}l<-ZaKId@C)aL^!;V%;V zOf8Iz^=paqmorHTzaoBTcOz}q6MNuo7(aGw8dPMe*1o61Kh-IZCsgB~Gu8QO@ePSn zUFsso4oy3qy)JTI+g5~!cjJ6}J}z?XkX;kTjvBidvQPe_Yy0Ecb3bFA=~%Y3ytKMd zIN8|LiiEEf312G`zE&iBt;E6~DVwtk9Pd$-wU+-Z#Y_f37pQwO&;q(Zt;s|W=zxXjNlR2;IhHp2JHsz2F*=Cn1C_@6NC-u26O|ufw93LK(U=Vj==c;q^DM{ zU(%PjvHhzc2!bF8f*?H8810M(qemnCg?e<~V{4>XmyEnRZK3f+1XSAB&v*)^TuNJ%f-mp*!t7Ck(WVgAE$J68A zW!Vx~>+!XOOJu#rr|@sPJ7&<09Pokol^#4_W*hNsJ-&yC&)$f!3)h8ADZlwUqhqAc z%}ARa;(f%|yMCYfAMu?$=M0h({&qs2yOFj7(>Nr0ryl=}l<-ZaKId@C)aL^!;V%;V zOf8Iz^=paqmorHTzaoBTcOz}q6MNuo7(aGw8dPMe*1o61Kh-IZCsgB~Gu8QO@ePSn zUFsso4oy3qy)JTI+g5~!cjJ6}J}z?XkX;kTjvBidvQPe_Yy0Ecb3bFA=~%Y3ytKMd zIN8|LiiEEf312G`zE&iBt;E6~DVwtk9Pd$-wU+-Z#Y_f37pQwO&;q(Zt;s|W=zxXjNlR2;IhHp2JHsz2F*=Cn1C_@6NC-u26O|ufw93LK(U=Vj==c;q^DM{ zU(%PjvHhzc2!bF8f*?H8810M(qemnCg?e<~V{4>XmyEnRZK3f+1XSAB&v*)^TuNJ%f-mp*!t7Ck(WVgAE$J68A zW!Vx~>+!XOOJu#rr|@sPJ7&<09Pokol^#4_W*hNsJ-&yC&)$f!3)h8ADZlwUqhqAc z%}ARa;(f%|yMCYfAMu?$=M0h({&qs2yOFj7(>Nr0ryl=}l<-ZaKId@C)aL^!;V%;V zOf8Iz^=paqmorHTzaoBTcOz}q6MNuo7(aGw8dPMe*1o61Kh-IZCsgB~Gu8QO@ePSn zUFsso4oy3qy)JTI+g5~!cjJ6}J}z?XkX;kTjvBidvQPe_Yy0Ecb3bFA=~%Y3ytKMd zIN8|LiiEEf312G`zE&iBt;E6~DVwtk9Pd$-wU+-Z#Y_f37pQwO&;q(Zt;s|W=z WKWebView? { + + if navigationAction.targetFrame == nil { + if let url = navigationAction.request.url { + if navigationAction.navigationType == .linkActivated { + webView.load(URLRequest(url: url)) + return nil + } + } + } + return nil + } + + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + let openPanel = NSOpenPanel() + openPanel.canChooseFiles = true + openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection + openPanel.level = .popUpMenu -// let webConfiguration = WKWebViewConfiguration() -// webView = WKWebView(frame: .zero, configuration: webConfiguration) -// -// webView.uiDelegate = self -// WebViewManager.shared.hostingWebView = webView -// self.addSubview(webView) -// webView.snp.makeConstraints { make in -// make.edges.equalTo(self) -// } -// -// if let url = URL(string: "https://www.42box.kr") { -// let request = URLRequest(url: url) -// webView.load(request) -// } -// -// NotificationCenter.default.addObserver(self, selector: -// #selector(goBack), name: -// Notification.Name("goBack"), object:nil) -// -// NotificationCenter.default.addObserver(self, selector: -// #selector(goForward), name: -// Notification.Name("goForward"), object:nil) -// -// NotificationCenter.default.addObserver(self, selector: -// #selector(reload), name: -// Notification.Name("reload"), object:nil) + openPanel.begin { (result) in + if result == .OK { + completionHandler(openPanel.urls) + } else { + completionHandler(nil) + } + } } -// -// deinit { -// // view controller가 해제될 때 observer도 제거합니다. -// NotificationCenter.default.removeObserver(self) -// } -// -// @objc func goBack() { -// if webView.canGoBack { -// webView.goBack() -// } -// } -// -// @objc func goForward() { -// if webView.canGoForward { -// webView.goForward() -// } -// } -// -// @objc func reload() { -// webView.reload() -// } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") From cc85bd7b6891163ea9374c27e635100a440ad9c9 Mon Sep 17 00:00:00 2001 From: DaSol Kim Date: Mon, 4 Sep 2023 17:07:04 +0900 Subject: [PATCH 13/17] fix: issue xcode issue --- Box42.xcodeproj/project.pbxproj | 12 +- .../xcshareddata/swiftpm/Package.resolved | 24 ++-- Box42/{ => Main}/BoxWindowController.swift | 0 Box42/{ => Main}/UI/HoverButton.swift | 0 .../{ => Main}/UI/MovableContainerView.swift | 0 Box42/Main/View/BookmarkEditorView.swift | 104 ++++++++++++++++++ Box42/{ => Main}/View/BoxBaseSplitView.swift | 0 .../View/BoxContentsViewGroup.swift | 0 8 files changed, 121 insertions(+), 19 deletions(-) rename Box42/{ => Main}/BoxWindowController.swift (100%) rename Box42/{ => Main}/UI/HoverButton.swift (100%) rename Box42/{ => Main}/UI/MovableContainerView.swift (100%) create mode 100644 Box42/Main/View/BookmarkEditorView.swift rename Box42/{ => Main}/View/BoxBaseSplitView.swift (100%) rename Box42/{ => Main}/View/BoxContentsViewGroup.swift (100%) diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index 3a787bd..77df456 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 7ECC67EA2AA5B74600265D54 /* BookmarkEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECC67E92AA5B74600265D54 /* BookmarkEditorView.swift */; }; + 7E9B46922AA5C564009EB900 /* BookmarkEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9B46912AA5C564009EB900 /* BookmarkEditorView.swift */; }; DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */; }; DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB62A5099F900FF0AA3 /* Box42.xcdatamodeld */; }; DE018BDD2A509AEB00FF0AA3 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BDC2A509AEB00FF0AA3 /* EventMonitor.swift */; }; @@ -134,7 +134,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 7ECC67E92AA5B74600265D54 /* BookmarkEditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BookmarkEditorView.swift; path = ../../../42box4/Box42/View/BookmarkEditorView.swift; sourceTree = ""; }; + 7E9B46912AA5C564009EB900 /* BookmarkEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkEditorView.swift; sourceTree = ""; }; DE018BAF2A5099F900FF0AA3 /* Box42.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Box42.app; sourceTree = BUILT_PRODUCTS_DIR; }; DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DE018BB72A5099F900FF0AA3 /* Box42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Box42.xcdatamodel; sourceTree = ""; }; @@ -165,7 +165,7 @@ DE1F1A112A8B506600A88DD8 /* importMacOSInfo.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = importMacOSInfo.sh; sourceTree = ""; }; DE1F1A122A8B506600A88DD8 /* exportMacOSInfo.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = exportMacOSInfo.sh; sourceTree = ""; }; DE1F1A132A8B506600A88DD8 /* keyMapping.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = keyMapping.sh; sourceTree = ""; }; - DE1F1A192A8B50C500A88DD8 /* BoxBaseContainerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoxBaseContainerViewController.swift; path = Main/BoxBaseContainerViewController.swift; sourceTree = ""; }; + DE1F1A192A8B50C500A88DD8 /* BoxBaseContainerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoxBaseContainerViewController.swift; sourceTree = ""; }; DE1F1A1A2A8B50C500A88DD8 /* BoxContentsViewGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoxContentsViewGroup.swift; sourceTree = ""; }; DE1F1A1B2A8B50C500A88DD8 /* BoxButtonViewGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoxButtonViewGroup.swift; sourceTree = ""; }; DE1F1A282A8B50E200A88DD8 /* BoxSizeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoxSizeManager.swift; sourceTree = ""; }; @@ -408,7 +408,7 @@ DE1F1A192A8B50C500A88DD8 /* BoxBaseContainerViewController.swift */, DEB862E92A853F7F00278FCD /* BoxWindowController.swift */, ); - name = Main; + path = Main; sourceTree = ""; }; DE3FF36F2A978A6E009C88EF /* View */ = { @@ -447,7 +447,7 @@ DE4408202A9297EE0091937A /* View */ = { isa = PBXGroup; children = ( - 7ECC67E92AA5B74600265D54 /* BookmarkEditorView.swift */, + 7E9B46912AA5C564009EB900 /* BookmarkEditorView.swift */, DE1F1A1A2A8B50C500A88DD8 /* BoxContentsViewGroup.swift */, DE24E6372A8FE10300E29F5D /* BoxBaseSplitView.swift */, ); @@ -879,7 +879,6 @@ DE0A917B2A8F0CA800D1D6F1 /* AppleScripts+ShowMessage.swift in Sources */, DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */, DEF0761B2AA33671005700E5 /* DeleteUserMeScript.swift in Sources */, - 7ECC67EA2AA5B74600265D54 /* BookmarkEditorView.swift in Sources */, DE62BE672A9BA92E00D97E06 /* QuickSlotButtonViewItem.swift in Sources */, DEF076302AA3CF8A005700E5 /* QuickSlotItemLabel.swift in Sources */, DE874F542A591F1400FC3B77 /* PreferencesView.swift in Sources */, @@ -929,6 +928,7 @@ DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */, DE78860C2A9C770300FE21DD /* ScriptsViewModel.swift in Sources */, DE0A91632A8E6A5400D1D6F1 /* Constants.swift in Sources */, + 7E9B46922AA5C564009EB900 /* BookmarkEditorView.swift in Sources */, DE7886172A9CCB3B00FE21DD /* UserProfile.swift in Sources */, DE0A91902A8F88CA00D1D6F1 /* DisplayURLInToolbar.swift in Sources */, DE77BBCD2A9E0568006CC98B /* ExecuteScripts.swift in Sources */, diff --git a/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 19d4e8c..009c162 100644 --- a/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,16 +1,14 @@ { - "object": { - "pins": [ - { - "package": "SnapKit", - "repositoryURL": "https://github.com/SnapKit/SnapKit.git", - "state": { - "branch": null, - "revision": "f222cbdf325885926566172f6f5f06af95473158", - "version": "5.6.0" - } + "pins" : [ + { + "identity" : "snapkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SnapKit/SnapKit.git", + "state" : { + "revision" : "f222cbdf325885926566172f6f5f06af95473158", + "version" : "5.6.0" } - ] - }, - "version": 1 + } + ], + "version" : 2 } diff --git a/Box42/BoxWindowController.swift b/Box42/Main/BoxWindowController.swift similarity index 100% rename from Box42/BoxWindowController.swift rename to Box42/Main/BoxWindowController.swift diff --git a/Box42/UI/HoverButton.swift b/Box42/Main/UI/HoverButton.swift similarity index 100% rename from Box42/UI/HoverButton.swift rename to Box42/Main/UI/HoverButton.swift diff --git a/Box42/UI/MovableContainerView.swift b/Box42/Main/UI/MovableContainerView.swift similarity index 100% rename from Box42/UI/MovableContainerView.swift rename to Box42/Main/UI/MovableContainerView.swift diff --git a/Box42/Main/View/BookmarkEditorView.swift b/Box42/Main/View/BookmarkEditorView.swift new file mode 100644 index 0000000..fc0088f --- /dev/null +++ b/Box42/Main/View/BookmarkEditorView.swift @@ -0,0 +1,104 @@ +// +// BookmarkEditorView.swift +// Box42 +// +// Created by Dasol on 2023/09/04. +// + +import Cocoa + +class BookmarkRowView: NSView { + let nameField = NSTextField() + let urlField = NSTextField() + + init(bookmark: (String, String)) { + super.init(frame: .zero) + + nameField.stringValue = bookmark.0 + urlField.stringValue = bookmark.1 + + addSubview(nameField) + addSubview(urlField) + + nameField.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.leading.equalToSuperview() + make.width.equalToSuperview().multipliedBy(0.5) + } + + urlField.snp.makeConstraints { make in + make.top.bottom.trailing.equalToSuperview() + make.leading.equalTo(nameField.snp.trailing) + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class BookmarkEditorView : NSView { + + var bookMarkList : [(String,String)] + var changeButton=NSButton(title:"Change",target:nil,action:nil) + var closeButton=NSButton(title:"Close",target:nil,action:nil) + + init(bookMarkList : [(String,String)]){ + + self.bookMarkList=bookMarkList + + super.init(frame:.zero) + + for (index, bookmark) in bookMarkList.enumerated(){ + let row=BookmarkRowView(bookmark: bookmark) + self.addSubview(row) + + row.snp.makeConstraints { make in + if index == 0 { + // First row should be at the top of the editor. + make.top.equalToSuperview() + } else { + // Other rows should be below the previous one. + make.top.equalTo(self.subviews[index - 1].snp.bottom) + } + + // All rows have the same height and span the entire width of the editor. + make.height.equalTo(44) // Or any other height you want. + make.left.right.equalToSuperview() + } + } + + self.addSubview(changeButton) + + changeButton.snp.makeConstraints {make in + if let lastSubview=self.subviews.last{ + // Place it under last subview + make.top.equalTo(lastSubview.snp.bottom).offset(10) + }else{ + make.top.equalToSuperview() + } + + make.centerX.equalToSuperview() + } + + self.addSubview(closeButton) + closeButton.target = self + closeButton.action = #selector(closeButtonClicked(_:)) + closeButton.snp.makeConstraints{maker in + maker.centerX.equalToSuperview() + maker.top.equalTo(changeButton.snp.bottom).offset(10) + maker.bottom.equalToSuperview().offset(-10) + } + } + + required init?(coder aDecoder:NSCoder){ + fatalError("init(coder:) has not been implemented") + } + + @objc func closeButtonClicked(_ sender:NSButton) { + removeFromSuperview() + } +} + + + diff --git a/Box42/View/BoxBaseSplitView.swift b/Box42/Main/View/BoxBaseSplitView.swift similarity index 100% rename from Box42/View/BoxBaseSplitView.swift rename to Box42/Main/View/BoxBaseSplitView.swift diff --git a/Box42/View/BoxContentsViewGroup.swift b/Box42/Main/View/BoxContentsViewGroup.swift similarity index 100% rename from Box42/View/BoxContentsViewGroup.swift rename to Box42/Main/View/BoxContentsViewGroup.swift From 4ca843dbe0eb54936bac7a81b01e4c5e7211bd18 Mon Sep 17 00:00:00 2001 From: DaSol Kim Date: Tue, 5 Sep 2023 17:26:55 +0900 Subject: [PATCH 14/17] feat: design quickslot and editor view (#132) * refactor: design UI * refactor: design UI --- Box42/FunctionButton/View/PinButtonView.swift | 43 +- .../Main/BoxBaseContainerViewController.swift | 380 +++++++++--------- Box42/Preferences/View/Funtion/CPUView.swift | 8 +- .../View/Funtion/IconSettingView.swift | 11 +- .../Funtion/NotificationSettingView.swift | 9 +- .../View/Funtion/RequestAccessView.swift | 56 ++- .../View/Funtion/ShortcutSettingView.swift | 84 ++-- .../View/Funtion/StorageView.swift | 28 +- .../View/Table/PreferencesTableView.swift | 13 +- .../QuickSlotManagerViewController.swift | 1 + ...ckSlotButtonCollectionViewController.swift | 17 + .../Vertical Item/QuickSlotItemButton.swift | 30 +- Box42/QuickSlot/View/QuickSlotGroupView.swift | 12 +- .../QuickSlot/View/QuickSlotHeaderView.swift | 40 +- .../QuickSlot/View/Table/QuickSlotCell.swift | 17 +- .../Table/QuickSlotCellDeleteButton.swift | 48 +-- .../View/Table/QuickSlotTableView.swift | 19 +- ...{Bookmark icon.png => Bookmark icon 1.png} | Bin .../Bookmark.imageset/Bookmark icon 2.png | Bin 0 -> 539 bytes .../Bookmark.imageset/Bookmark icon 3.png | Bin 0 -> 539 bytes .../uibuttons/Bookmark.imageset/Contents.json | 4 +- .../View/Button/ScriptDeleteButton.swift | 44 +- .../View/Button/ScriptExcuteButton.swift | 42 +- .../View/Button/ScriptQuickSlotButton.swift | 42 +- Box42/Scripts/View/Table/ScriptCell.swift | 29 +- .../Scripts/View/Table/ScriptsTableView.swift | 7 +- .../Controller/ToolbarViewController.swift | 63 ++- Box42/Toolbar/View/BoxToolbarViewGroup.swift | 3 + Box42/Toolbar/View/SideBarLeading.swift | 2 +- 29 files changed, 567 insertions(+), 485 deletions(-) rename Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/{Bookmark icon.png => Bookmark icon 1.png} (100%) create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon 2.png create mode 100644 Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon 3.png diff --git a/Box42/FunctionButton/View/PinButtonView.swift b/Box42/FunctionButton/View/PinButtonView.swift index 9c9db8b..31cf50e 100644 --- a/Box42/FunctionButton/View/PinButtonView.swift +++ b/Box42/FunctionButton/View/PinButtonView.swift @@ -11,35 +11,44 @@ import SnapKit class PinButtonView: NSView { private var callback: (() -> Void)? - private var pinBoxButton: NSButton! - private var pinBoxLabel: NSTextField! + private let pinBoxButton: NSButton = { + let button = NSButton() + return button + }() init(image: NSImage, completion: @escaping () -> Void) { super.init(frame: .zero) - pinBoxButton = NSButton(image: image, target: self, action: #selector(pin)) + pinBoxButton.image = image + pinBoxButton.imagePosition = .imageLeading + pinBoxButton.image?.alignmentRect = NSRect(x: 0, y: 7, width: 22, height: 22) + + pinBoxButton.target = self + pinBoxButton.action = #selector(pin) + pinBoxButton.isBordered = false pinBoxButton.wantsLayer = true pinBoxButton.layer?.backgroundColor = NSColor.clear.cgColor + pinBoxButton.bezelStyle = .inline + let pinBoxTitle = "Pin Box" + + let attributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 14.0, weight: .semibold), // 원하는 폰트 및 무게로 설정 + NSAttributedString.Key.foregroundColor: NSColor(red: 0.41, green: 0.41, blue: 0.41, alpha: 1), + ] + + let attributedTitle = NSAttributedString(string: pinBoxTitle, attributes: attributes) + + pinBoxButton.attributedTitle = attributedTitle + pinBoxButton.attributedAlternateTitle = attributedTitle + self.addSubview(pinBoxButton) - - pinBoxLabel = NSTextField(labelWithString: "Pin Box") - pinBoxLabel.font = NSFont(name: "Inter", size: 14) - pinBoxLabel.textColor = NSColor(hex: "#696969") - pinBoxLabel.backgroundColor = NSColor.clear - pinBoxLabel.isBordered = false - - self.addSubview(pinBoxLabel) - pinBoxButton.snp.makeConstraints { make in make.left.equalToSuperview() make.centerY.equalToSuperview() - } - - pinBoxLabel.snp.makeConstraints { make in - make.left.equalTo(pinBoxButton.snp.right).offset(2) - make.centerY.equalToSuperview() + make.width.equalTo(89) + make.height.equalTo(27) } self.callback = completion diff --git a/Box42/Main/BoxBaseContainerViewController.swift b/Box42/Main/BoxBaseContainerViewController.swift index 861a902..4c1c4b0 100644 --- a/Box42/Main/BoxBaseContainerViewController.swift +++ b/Box42/Main/BoxBaseContainerViewController.swift @@ -25,32 +25,32 @@ let bookMarkList = [ class BoxBaseContainerViewController: NSViewController { // MARK: - LeftContainer -// var splitView: BoxBaseSplitView = BoxBaseSplitView() + // var splitView: BoxBaseSplitView = BoxBaseSplitView() var contentGroup: BoxContentsViewGroup = BoxContentsViewGroup() var toolbarGroupVC: ToolbarViewController = ToolbarViewController() var quickSlotGroupVC: QuickSlotViewController = QuickSlotViewController() var functionGroupVC: BoxFunctionViewController = BoxFunctionViewController() let windowViewGroupVC: WindowButtonViewController = WindowButtonViewController() -// var leftContainer: MovableContainerView = MovableContainerView() -// var buttonGroupVC: ButtonGroupViewController = ButtonGroupViewController() + // var leftContainer: MovableContainerView = MovableContainerView() + // var buttonGroupVC: ButtonGroupViewController = ButtonGroupViewController() // MARK: - QuickSlot var preferenceVC: PreferencesViewController = PreferencesViewController() var scriptsVC: ScriptsViewController = ScriptsViewController() - + weak var menubarVCDelegate: MenubarViewControllerDelegate? // extension var quickSlotManagerVC: QuickSlotManagerViewController = QuickSlotManagerViewController() var quickSlotButtonCollectionVC: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() - - private let splitView: NSSplitView = { - let splitView = NSSplitView() - splitView.isVertical = true - splitView.dividerStyle = .thick - return splitView - }() - private let leftView: NSView = { + // private let splitView: NSSplitView = { + // let splitView = NSSplitView() + // splitView.isVertical = true + // splitView.dividerStyle = .thick + // return splitView + // }() + + public let leftView: NSView = { let view = NSView() view.frame.size.width = 302 - 12 view.frame.size.height = 1200 @@ -75,6 +75,8 @@ class BoxBaseContainerViewController: NSViewController { var selectedButton: DraggableButton? override func loadView() { + toolbarGroupVC.baseContainerVC = self + self.view = NSView() self.view.wantsLayer=true self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor @@ -83,17 +85,37 @@ class BoxBaseContainerViewController: NSViewController { make.height.equalTo(BoxSizeManager.shared.size.height) } - splitView.addArrangedSubview(leftView) - splitView.addArrangedSubview(contentGroup) - self.view.addSubview(splitView) + // splitView.adjustSubviews() + // splitView.setNeedsDisplay(splitView.bounds) + // splitView.addArrangedSubview(leftView) + // splitView.addArrangedSubview(contentGroup) + // self.view.addSubview(splitView) + // + // splitView.snp.makeConstraints { make in + // make.edges.equalToSuperview().inset(10) + // } + self.view.addSubview(leftView) + self.view.addSubview(contentGroup) + + leftView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview().inset(12) + make.leading.equalToSuperview().offset(16) + make.trailing.equalTo(contentGroup.snp.leading).offset(-18) + make.width.equalTo((268 + 16 + 18)) + } - splitView.snp.makeConstraints { make in - make.edges.equalToSuperview().inset(10) + contentGroup.snp.makeConstraints { make in + make.top.bottom.trailing.equalToSuperview().inset(12) + make.leading.equalTo(leftView.snp.trailing) } + let borderView = NSView() + borderView.wantsLayer = true + borderView.layer?.backgroundColor = NSColor(red: 0.773, green: 0.773, blue: 0.773, alpha: 1).cgColor leftView.addSubview(windowViewGroupVC.view) leftView.addSubview(bookMarkView) leftView.addSubview(toolbarGroupVC.view) + leftView.addSubview(borderView) leftView.addSubview(quickSlotGroupVC.view) leftView.addSubview(functionGroupVC.view) @@ -114,9 +136,15 @@ class BoxBaseContainerViewController: NSViewController { make.leading.trailing.equalToSuperview() make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) } + borderView.snp.makeConstraints { make in + make.top.equalTo(bookMarkView.snp.bottom).offset(15) + make.leading.trailing.equalToSuperview() + make.height.equalTo(1) + } quickSlotGroupVC.view.snp.makeConstraints { make in + make.top.equalTo(borderView.snp.bottom).offset(0) make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) - make.right.equalTo(leftView).offset(-Constants.UI.groupAutolayout) + make.right.equalTo(leftView).offset(0) make.left.equalTo(leftView) make.height.equalTo(178) } @@ -125,19 +153,17 @@ class BoxBaseContainerViewController: NSViewController { make.left.bottom.equalTo(leftView) } - splitView.delegate = self - - let stackView = NSStackView() - stackView.orientation = .horizontal - stackView.spacing = 6 - stackView.alignment = .centerY + let superView = NSView() let imageView = NSImageView() imageView.image = NSImage(named: NSImage.Name("bookmark")) + imageView.image?.size = NSSize(width: 18, height: 18) + superView.addSubview(imageView) let label = NSTextField(labelWithString: "북마크") label.textColor = NSColor.black label.font = NSFont.boldSystemFont(ofSize: 16) + superView.addSubview(label) let buttonImage = NSImage(named: NSImage.Name("add"))! buttonImage.size = NSSize(width: 24, height: 24) @@ -146,16 +172,23 @@ class BoxBaseContainerViewController: NSViewController { button.bezelStyle = .texturedRounded button.isBordered = false button.wantsLayer = true - button.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor - let spacerView = NSView() - spacerView.snp.makeConstraints { make in - make.width.equalTo(173) + button.layer?.backgroundColor = NSColor(hex:"#E7E7E7").cgColor + superView.addSubview(button) + + imageView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerY.equalToSuperview() } - stackView.addArrangedSubview(imageView) - stackView.addArrangedSubview(label) - stackView.addArrangedSubview(spacerView) - stackView.addArrangedSubview(button) + label.snp.makeConstraints { make in + make.left.equalTo(imageView.snp.right).offset(8) + make.centerY.equalToSuperview() + } + + button.snp.makeConstraints { make in + make.right.equalToSuperview() + make.centerY.equalToSuperview() + } tableView.wantsLayer = true tableView.backgroundColor = NSColor(hex: "#E7E7E7") @@ -178,11 +211,11 @@ class BoxBaseContainerViewController: NSViewController { scrollView.hasVerticalScroller = true scrollView.documentView = tableView - bookMarkView.addSubview(stackView) + bookMarkView.addSubview(superView) bookMarkView.addSubview(scrollView) // bookMarkView.addSubview(tableView) - stackView.snp.makeConstraints { make in + superView.snp.makeConstraints { make in make.top.equalToSuperview().offset(18) make.leading.trailing.equalToSuperview().offset(0) make.height.equalTo(24) @@ -194,7 +227,7 @@ class BoxBaseContainerViewController: NSViewController { // } scrollView.snp.makeConstraints { make in - make.top.equalTo(stackView.snp.bottom).offset(0) + make.top.equalTo(superView.snp.bottom).offset(0) make.leading.trailing.equalToSuperview().offset(0) make.bottom.equalToSuperview() } @@ -205,162 +238,141 @@ class BoxBaseContainerViewController: NSViewController { } @objc func addBookMarkButtonClicked(_ sender: NSButton) { - splitView.removeArrangedSubview(contentGroup) - contentGroup.removeFromSuperview() - - let newView = BookmarkEditorView(bookMarkList: bookMarkList) - newView.wantsLayer = true - newView.layer?.backgroundColor = NSColor.black.cgColor - newView.layer?.cornerRadius = 20 - newView.frame.size = contentGroup.frame.size - - contentGroup.addSubview(newView) - newView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - splitView.addArrangedSubview(contentGroup) + // splitView.removeArrangedSubview(contentGroup) + // contentGroup.removeFromSuperview() + // + // let newView = BookmarkEditorView(bookMarkList: bookMarkList) + // newView.wantsLayer = true + // newView.layer?.backgroundColor = NSColor.black.cgColor + // newView.layer?.cornerRadius = 20 + // newView.frame.size = contentGroup.frame.size + // + // contentGroup.addSubview(newView) + // newView.snp.makeConstraints { make in + // make.edges.equalToSuperview() + // } + // + // splitView.addArrangedSubview(contentGroup) } override func viewDidLoad() { -// self.view.wantsLayer = true -// -//// self.view.layer?.backgroundColor = NSColor(hex: "#FF9548").cgColor -// self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor + // self.view.wantsLayer = true + // + //// self.view.layer?.backgroundColor = NSColor(hex: "#FF9548").cgColor + // self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor NotificationCenter.default.addObserver(self, selector: #selector(handleButtonTapped), name: .collectionButtonTapped, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(headerTappedQuickSlotManagerHandle), name: .collectionHeaderTapped, object: nil) } -// func BoxButtonViewGroupInit() -> BoxButtonViewGroup { -// -// let buttonGroup = BoxButtonViewGroup { sender in -// self.clickBtn(sender: sender) -// } -// -// return buttonGroup -// } + // func BoxButtonViewGroupInit() -> BoxButtonViewGroup { + // + // let buttonGroup = BoxButtonViewGroup { sender in + // self.clickBtn(sender: sender) + // } + // + // return buttonGroup + // } -// func clickBtn(sender: Any?) { -// if let button = sender as? NSButton { -// guard let clickCount = NSApp.currentEvent?.clickCount else { return } -// if clickCount == 2 { -// WebViewManager.shared.list[button.title]!.reload() -// print("Dobule Click") -// } else if clickCount > 2 { -// if let currentURL = WebViewManager.shared.hostingWebView?.url { -// NSWorkspace.shared.open(currentURL) -// } -// print("Triple Click") -// } else if clickCount < 2 { -// contentGroup.removeAllSubviews() -// contentGroup.showWebviews(button) -// } -// } else { -// if let str = sender as? String { -// if str == "box" { -// contentGroup.removeAllSubviews() -// print("box inside") -// } -// } -// } -// } -// -// private func leftContainerInit() { -// leftContainer.frame.size.width = BoxSizeManager.shared.windowButtonGroupSize.width -// leftContainer.frame.size.height = BoxSizeManager.shared.windowButtonGroupSize.height -// leftContainer.addSubview(windowViewGroupVC.view) -// leftContainer.addSubview(buttonGroupVC.view) -// leftContainer.addSubview(toolbarGroupVC.view) -// leftContainer.addSubview(quickSlotGroupVC.view) -// leftContainer.addSubview(functionGroupVC.view) -// -// leftContainerAutolayout() -// } -// -// private func leftContainerAutolayout() { -// windowViewGroupVC.view.snp.makeConstraints { make in -// make.top.equalTo(leftContainer) -// make.left.equalTo(leftContainer).offset(3) -// make.width.equalTo(77) -// make.height.equalTo(21) -// } -// -// toolbarGroupVC.view.snp.makeConstraints { make in -// make.top.equalTo(windowViewGroupVC.view.snp.bottom).offset(31) -// make.right.equalTo(leftContainer) -// make.left.equalTo(leftContainer) -// make.height.equalTo(44 + 14 + 24) -// } -// -// buttonGroupVC.view.snp.makeConstraints { make in -// make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) -// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) -// make.left.equalTo(leftContainer) -// make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) -// } -// -// quickSlotGroupVC.view.snp.makeConstraints { make in -// make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) -// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) -// make.left.equalTo(leftContainer) -// make.height.equalTo(178) -// } -// -// functionGroupVC.view.snp.makeConstraints { make in -// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) -// make.left.bottom.equalTo(leftContainer) -// } -// } -// -// func viewInit() { -// self.boxViewSizeInit() -// -// splitView.addArrangedSubview(leftContainer) -// splitView.addArrangedSubview(contentGroup) -// self.view.addSubview(splitView) -// -// splitView.snp.makeConstraints { make in -// make.top.equalToSuperview().offset(Constants.UI.groupAutolayout) -// make.left.equalToSuperview().offset(Constants.UI.groupAutolayout) -// make.right.equalToSuperview().offset(-Constants.UI.groupAutolayout) -// make.bottom.equalToSuperview().offset(-Constants.UI.groupAutolayout) -// } -// } -// -// func boxViewSizeInit() { -// self.view.frame.size.width = BoxSizeManager.shared.size.width -// self.view.frame.size.height = BoxSizeManager.shared.size.height -// } + // func clickBtn(sender: Any?) { + // if let button = sender as? NSButton { + // guard let clickCount = NSApp.currentEvent?.clickCount else { return } + // if clickCount == 2 { + // WebViewManager.shared.list[button.title]!.reload() + // print("Dobule Click") + // } else if clickCount > 2 { + // if let currentURL = WebViewManager.shared.hostingWebView?.url { + // NSWorkspace.shared.open(currentURL) + // } + // print("Triple Click") + // } else if clickCount < 2 { + // contentGroup.removeAllSubviews() + // contentGroup.showWebviews(button) + // } + // } else { + // if let str = sender as? String { + // if str == "box" { + // contentGroup.removeAllSubviews() + // print("box inside") + // } + // } + // } + // } + // + // private func leftContainerInit() { + // leftContainer.frame.size.width = BoxSizeManager.shared.windowButtonGroupSize.width + // leftContainer.frame.size.height = BoxSizeManager.shared.windowButtonGroupSize.height + // leftContainer.addSubview(windowViewGroupVC.view) + // leftContainer.addSubview(buttonGroupVC.view) + // leftContainer.addSubview(toolbarGroupVC.view) + // leftContainer.addSubview(quickSlotGroupVC.view) + // leftContainer.addSubview(functionGroupVC.view) + // + // leftContainerAutolayout() + // } + // + // private func leftContainerAutolayout() { + // windowViewGroupVC.view.snp.makeConstraints { make in + // make.top.equalTo(leftContainer) + // make.left.equalTo(leftContainer).offset(3) + // make.width.equalTo(77) + // make.height.equalTo(21) + // } + // + // toolbarGroupVC.view.snp.makeConstraints { make in + // make.top.equalTo(windowViewGroupVC.view.snp.bottom).offset(31) + // make.right.equalTo(leftContainer) + // make.left.equalTo(leftContainer) + // make.height.equalTo(44 + 14 + 24) + // } + // + // buttonGroupVC.view.snp.makeConstraints { make in + // make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) + // make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) + // make.left.equalTo(leftContainer) + // make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) + // } + // + // quickSlotGroupVC.view.snp.makeConstraints { make in + // make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) + // make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) + // make.left.equalTo(leftContainer) + // make.height.equalTo(178) + // } + // + // functionGroupVC.view.snp.makeConstraints { make in + // make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) + // make.left.bottom.equalTo(leftContainer) + // } + // } + // + // func viewInit() { + // self.boxViewSizeInit() + // + // splitView.addArrangedSubview(leftContainer) + // splitView.addArrangedSubview(contentGroup) + // self.view.addSubview(splitView) + // + // splitView.snp.makeConstraints { make in + // make.top.equalToSuperview().offset(Constants.UI.groupAutolayout) + // make.left.equalToSuperview().offset(Constants.UI.groupAutolayout) + // make.right.equalToSuperview().offset(-Constants.UI.groupAutolayout) + // make.bottom.equalToSuperview().offset(-Constants.UI.groupAutolayout) + // } + // } + // + // func boxViewSizeInit() { + // self.view.frame.size.width = BoxSizeManager.shared.size.width + // self.view.frame.size.height = BoxSizeManager.shared.size.height + // } } extension BoxBaseContainerViewController: NSSplitViewDelegate { - func splitView(_ splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat { - - if dividerIndex == 0 { - return CGFloat(132).pointsToPixels() - } - return proposedMinimumPosition - } - - func splitView(_ splitView: NSSplitView, constrainMaxCoordinate proposedMaximumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat { - if dividerIndex == 0 { - return CGFloat(302).pointsToPixels() - } - return proposedMaximumPosition + func splitView(_ splitView: NSSplitView, shouldAdjustSizeOfSubview view: NSView) -> Bool { + return false } - func splitView(_ splitView: NSSplitView, resizeSubviewsWithOldSize oldSize: NSSize) { - let dividerThickness = splitView.dividerThickness - let newWidth = splitView.frame.width - dividerThickness - - let leftWidth = leftView.frame.width - let contentWidth = newWidth - leftWidth - - leftView.frame = NSRect(x: 0, y: 0, width: leftWidth, height: splitView.bounds.height) - contentGroup.frame = NSRect(x: leftWidth + dividerThickness, y: 0, width: contentWidth, height: splitView.bounds.height) - } } extension BoxBaseContainerViewController: NSTableViewDelegate { @@ -397,9 +409,6 @@ class ButtonTableCellView: NSTableCellView { override func viewWillDraw() { super.viewWillDraw() - self.frame.size.width = 268.0 - self.frame = NSRect(x: self.frame.origin.x - 3, y: self.frame.origin.y, - width: self.frame.size.width, height: self.frame.size.height) } } @@ -413,7 +422,7 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { let cellView = ButtonTableCellView() cellView.rowIndex = row - let button = DraggableButton(frame: NSRect(x: 0, y: 0, width: 300, height: 44)) + let button = DraggableButton(frame: NSRect(x: 0, y: 0, width: 268, height: 44)) button.tag = row button.bezelStyle = .inline button.isBordered = false @@ -424,7 +433,6 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { button.delegate = self let label = NSTextField(frame: NSRect(x: 26 + 21 + 8, y: 25 / 2, width: button.bounds.width, height: button.bounds.height)) - label.stringValue = buttonTitleArray[row] label.backgroundColor = .clear label.isBordered = false @@ -440,7 +448,6 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { label.attributedStringValue=attributedStringTitle button.addSubview(label) - // let image = NSImage(named: NSImage.Name("bookmark-default")) // image?.size = NSSize(width: 21, height: 21) // button.image = image @@ -453,16 +460,14 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { imageView.imageAlignment = .alignCenter button.addSubview(imageView) - - cellView.addSubview(button) button.snp.makeConstraints { make in make.top.equalToSuperview().offset(2) make.leading.equalToSuperview() make.trailing.equalToSuperview() - // make.width.equalTo(268) - make.width.lessThanOrEqualTo(268) + make.width.equalTo(268) + // make.width.lessThanOrEqualTo(268) make.height.equalTo(44) } @@ -484,7 +489,6 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { @objc func buttonClicked(_ sender: DraggableButton) { selectedButton?.layer?.backgroundColor = NSColor.clear.cgColor - // Update the reference to the currently selected button and change its background color. selectedButton = sender sender.layer?.backgroundColor = NSColor.white.cgColor @@ -519,7 +523,6 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { guard let cellView = subview as? CustomTableCellView else { continue } - cellView.button.title = buttonTitleArray[cellView.rowIndex] } @@ -541,7 +544,6 @@ class DraggableButton: NSButton, NSDraggingSource { if event.locationInWindow == down.locationInWindow { self.target?.perform(self.action, with: self) } - super.mouseUp(with:event) self.mouseDownEvent = nil } diff --git a/Box42/Preferences/View/Funtion/CPUView.swift b/Box42/Preferences/View/Funtion/CPUView.swift index 74a296b..651afa6 100644 --- a/Box42/Preferences/View/Funtion/CPUView.swift +++ b/Box42/Preferences/View/Funtion/CPUView.swift @@ -13,25 +13,25 @@ class CPUView: NSView { // UI Elements private let usageLabel: NSTextField = { let label = NSTextField(labelWithString: "Usage: ") - label.font = NSFont.systemFont(ofSize: 16) + label.font = NSFont.systemFont(ofSize: 16, weight: .semibold) return label }() private let systemLabel: NSTextField = { let label = NSTextField(labelWithString: "System: ") - label.font = NSFont.systemFont(ofSize: 16) + label.font = NSFont.systemFont(ofSize: 16, weight: .semibold) return label }() private let userLabel: NSTextField = { let label = NSTextField(labelWithString: "User: ") - label.font = NSFont.systemFont(ofSize: 16) + label.font = NSFont.systemFont(ofSize: 16, weight: .semibold) return label }() private let idleLabel: NSTextField = { let label = NSTextField(labelWithString: "Idle: ") - label.font = NSFont.systemFont(ofSize: 16) + label.font = NSFont.systemFont(ofSize: 16, weight: .semibold) return label }() diff --git a/Box42/Preferences/View/Funtion/IconSettingView.swift b/Box42/Preferences/View/Funtion/IconSettingView.swift index 4993aa1..bac701a 100644 --- a/Box42/Preferences/View/Funtion/IconSettingView.swift +++ b/Box42/Preferences/View/Funtion/IconSettingView.swift @@ -38,7 +38,7 @@ class CustomSwitch: NSButton { class IconSettingView: NSView { private let iconSettingLabel: NSTextField = { let label = NSTextField(labelWithString: "아이콘 설정") - label.font = NSFont.systemFont(ofSize: 20) + label.font = NSFont.systemFont(ofSize: 20, weight: .semibold) label.isEditable = false label.isSelectable = false return label @@ -46,6 +46,7 @@ class IconSettingView: NSView { private let flipLabel: NSTextField = { let label = NSTextField(labelWithString: "아이콘 좌우반전") + label.font = NSFont.systemFont(ofSize: 14, weight: .medium) return label }() @@ -85,14 +86,14 @@ class IconSettingView: NSView { self.addSubview(speedUpSwitch) iconSettingLabel.snp.makeConstraints { (make) in - make.top.equalToSuperview().offset(10) - make.left.equalToSuperview().offset(10) - make.right.equalToSuperview().offset(-10) + make.top.equalToSuperview().offset(20) + make.leading.equalToSuperview().offset(12) + make.trailing.equalToSuperview().offset(-12) } flipLabel.snp.makeConstraints { (make) in make.top.equalTo(iconSettingLabel.snp.bottom).offset(20) - make.left.equalToSuperview().offset(10) + make.leading.equalToSuperview().offset(22) } flipSwitch.snp.makeConstraints { (make) in diff --git a/Box42/Preferences/View/Funtion/NotificationSettingView.swift b/Box42/Preferences/View/Funtion/NotificationSettingView.swift index 978df63..f7384c9 100644 --- a/Box42/Preferences/View/Funtion/NotificationSettingView.swift +++ b/Box42/Preferences/View/Funtion/NotificationSettingView.swift @@ -13,7 +13,7 @@ class NotificationSettingView: NSView { // Create a label for the title let titleLabel: NSTextField = { let label = NSTextField(labelWithString: "Box 알림 설정") - label.font = NSFont.systemFont(ofSize: 20) + label.font = NSFont.systemFont(ofSize: 20, weight: .semibold) label.isEditable = false label.isSelectable = false return label @@ -22,6 +22,9 @@ class NotificationSettingView: NSView { // Create a switch button let toggleButton: NSButton = { let toggle = NSButton(checkboxWithTitle: "알림 활성화", target: nil, action: #selector(toggleChanged)) + let titleFont = NSFont.systemFont(ofSize: 14, weight: .medium) + toggle.attributedTitle = NSAttributedString(string: toggle.title, attributes: [.font: titleFont]) + return toggle }() @@ -40,14 +43,14 @@ class NotificationSettingView: NSView { addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(self).offset(20) - make.centerX.equalTo(self) + make.leading.equalToSuperview().offset(12) } // Add toggleButton to the view addSubview(toggleButton) toggleButton.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(20) - make.centerX.equalTo(self) + make.leading.equalToSuperview().offset(22) } toggleButton.target = self diff --git a/Box42/Preferences/View/Funtion/RequestAccessView.swift b/Box42/Preferences/View/Funtion/RequestAccessView.swift index b95e905..22b4612 100644 --- a/Box42/Preferences/View/Funtion/RequestAccessView.swift +++ b/Box42/Preferences/View/Funtion/RequestAccessView.swift @@ -11,7 +11,7 @@ import SnapKit class RequestAccessView: NSView { private let requestAccessLabel: NSTextField = { let label = NSTextField(labelWithString: "Request Access") - label.font = NSFont.systemFont(ofSize: 20) + label.font = NSFont.systemFont(ofSize: 20, weight: .semibold) label.isEditable = false label.isSelectable = false return label @@ -24,7 +24,7 @@ class RequestAccessView: NSView { override init(frame frameRect: NSRect) { super.init(frame: .zero) self.wantsLayer = true - self.layer?.backgroundColor = NSColor(hex: "#7FFFFFFF").cgColor +// self.layer?.backgroundColor = NSColor(hex: "#7FFFFFFF").cgColor self.layer?.cornerRadius = 13 // Add subviews @@ -51,7 +51,7 @@ class RequestAccessView: NSView { func textfieldInit() { requestAccessTextField.stringValue = "Script 및 기능들을 실행하기 위해서 루트 디렉토리의 권한이 필요합니다." - requestAccessTextField.font = NSFont.systemFont(ofSize: 15) + requestAccessTextField.font = NSFont.systemFont(ofSize: 14, weight: .medium) requestAccessTextField.isEditable = false requestAccessTextField.isBordered = false requestAccessTextField.backgroundColor = NSColor.clear @@ -61,17 +61,23 @@ class RequestAccessView: NSView { func buttonInit() { grantAccessButton.title = "권한 부여" + grantAccessButton.bezelStyle = .inline + grantAccessButton.wantsLayer = true + grantAccessButton.layer?.cornerRadius = 15 grantAccessButton.target = self grantAccessButton.action = #selector(requestFolderAccess(_:)) revokeAccessButton.title = "권한 취소" + revokeAccessButton.bezelStyle = .inline + revokeAccessButton.wantsLayer = true + revokeAccessButton.layer?.cornerRadius = 15 revokeAccessButton.target = self revokeAccessButton.action = #selector(revokeFolderAccess(_:)) } func directoryNameTextFieldInit() { + directoryNameTextField.font = NSFont.systemFont(ofSize: 12, weight: .light) directoryNameTextField.stringValue = "선택된 디렉터리: 없음" - directoryNameTextField.font = NSFont.systemFont(ofSize: 15) directoryNameTextField.isEditable = false directoryNameTextField.isBordered = false directoryNameTextField.backgroundColor = NSColor.clear @@ -80,35 +86,38 @@ class RequestAccessView: NSView { func directoryNameTextFieldConstraints() { directoryNameTextField.snp.makeConstraints { make in - make.top.equalTo(revokeAccessButton.snp.bottom).offset(10) - make.leading.equalToSuperview().offset(17) - make.trailing.equalToSuperview().offset(-17) + make.top.equalTo(revokeAccessButton.snp.centerY) + make.leading.equalToSuperview().offset(30) } } func textfieldConstraints() { requestAccessLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(10) - make.leading.equalToSuperview().offset(17) - make.trailing.equalToSuperview().offset(-17) + make.top.equalToSuperview().offset(20) + make.leading.equalToSuperview().offset(12) + make.trailing.equalToSuperview().offset(-12) } requestAccessTextField.snp.makeConstraints { make in - make.top.equalTo(requestAccessLabel.snp.bottom).offset(10) - make.leading.equalToSuperview().offset(17) - make.trailing.equalToSuperview().offset(-17) + make.top.equalTo(requestAccessLabel.snp.bottom).offset(20) + make.leading.equalToSuperview().offset(22) + make.trailing.equalToSuperview().offset(-22) } } func buttonConstraints() { grantAccessButton.snp.makeConstraints { make in - make.top.equalTo(requestAccessTextField.snp.bottom).offset(10) - make.leading.equalToSuperview().offset(17) + make.top.equalTo(requestAccessTextField.snp.bottom).offset(20) + make.trailing.equalTo(revokeAccessButton.snp.leading).offset(-10) + make.width.equalTo(70) + make.height.equalTo(30) } revokeAccessButton.snp.makeConstraints { make in - make.top.equalTo(requestAccessTextField.snp.bottom).offset(10) - make.leading.equalTo(grantAccessButton.snp.trailing).offset(10) + make.top.equalTo(requestAccessTextField.snp.bottom).offset(20) + make.trailing.equalTo(requestAccessLabel.snp.trailing).offset(0) + make.width.equalTo(70) + make.height.equalTo(30) } } @@ -121,13 +130,22 @@ class RequestAccessView: NSView { openPanel.canCreateDirectories = true openPanel.allowsMultipleSelection = false openPanel.canChooseFiles = false +// openPanel.level = .popUpMenu +// openPanel.begin { (result) in +// if result == .OK { +//// completionHandler(openPanel.urls) +// } else { +//// completionHandler(nil) +// } +// } if openPanel.runModal() == NSApplication.ModalResponse.OK { let result = openPanel.url - + if let result = result { print("Selected folder is \(result.path)") - + +// directoryNameTextField.font = NSFont.systemFont(ofSize: 12, weight: .light) directoryNameTextField.stringValue = "선택된 디렉터리: \(result.path)" do { diff --git a/Box42/Preferences/View/Funtion/ShortcutSettingView.swift b/Box42/Preferences/View/Funtion/ShortcutSettingView.swift index f626c65..d635ee7 100644 --- a/Box42/Preferences/View/Funtion/ShortcutSettingView.swift +++ b/Box42/Preferences/View/Funtion/ShortcutSettingView.swift @@ -13,7 +13,7 @@ class ShortcutSettingView: NSView { // Create a label for the title let titleLabel: NSTextField = { let label = NSTextField(labelWithString: "앱 내부 단축키 설정") - label.font = NSFont.systemFont(ofSize: 20) + label.font = NSFont.systemFont(ofSize: 20, weight: .semibold) label.isEditable = false label.isSelectable = false return label @@ -34,45 +34,49 @@ class ShortcutSettingView: NSView { ] lazy var stackView: NSStackView = { - let stackView = NSStackView() - stackView.orientation = .vertical - stackView.distribution = .fillEqually - stackView.spacing = 20 - - var subStackView: NSStackView? + let stackView = NSStackView() + stackView.orientation = .vertical + stackView.distribution = .fillEqually + stackView.spacing = 20 + + var subStackView: NSStackView? + + for (index, (labelText, defaultKey)) in shortcutSettings.enumerated() { + let label = NSTextField(labelWithString: labelText) + label.font = NSFont.systemFont(ofSize: 14, weight: .medium) + label.isEditable = false + label.isSelectable = false + let textField = NSTextField() + textField.placeholderString = defaultKey + textField.snp.makeConstraints { make in + make.width.equalTo(50) + } - for (index, (labelText, defaultKey)) in shortcutSettings.enumerated() { - let label = NSTextField(labelWithString: labelText) - label.isEditable = false - label.isSelectable = false - let textField = NSTextField() - textField.placeholderString = defaultKey + if index == 0 || index == 1 { + let innerStackView = NSStackView(views: [label, textField]) + innerStackView.distribution = .fillProportionally + stackView.addArrangedSubview(innerStackView) + } else { + if index == 2 || index == 6 { + subStackView = NSStackView() + subStackView?.orientation = .horizontal + subStackView?.distribution = .fillEqually + subStackView?.spacing = 20 + } + + let innerStackView = NSStackView(views: [label, textField]) + innerStackView.distribution = .fillProportionally - if index == 0 || index == 1 { - let innerStackView = NSStackView(views: [label, textField]) - innerStackView.distribution = .fillProportionally - stackView.addArrangedSubview(innerStackView) - } else { - if index == 2 || index == 6 { - subStackView = NSStackView() - subStackView?.orientation = .horizontal - subStackView?.distribution = .fillEqually - subStackView?.spacing = 20 - } - - let innerStackView = NSStackView(views: [label, textField]) - innerStackView.distribution = .fillProportionally - - subStackView?.addArrangedSubview(innerStackView) - - if index == 4 || index == 8 { - stackView.addArrangedSubview(subStackView!) - } + subStackView?.addArrangedSubview(innerStackView) + + if index == 4 || index == 8 { + stackView.addArrangedSubview(subStackView!) } } - - return stackView - }() + } + + return stackView + }() override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -89,17 +93,15 @@ class ShortcutSettingView: NSView { addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(self).offset(20) - make.centerX.equalTo(self) + make.leading.equalToSuperview().offset(12) } // Add stackView to the view addSubview(stackView) stackView.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(20) - make.left.equalTo(self).offset(20) - make.right.equalTo(self).offset(-20) + make.leading.equalToSuperview().offset(22) + make.trailing.equalToSuperview().offset(-22) } } - - // Further actions like saving the shortcuts, adding validations can be implemented } diff --git a/Box42/Preferences/View/Funtion/StorageView.swift b/Box42/Preferences/View/Funtion/StorageView.swift index 9a72a30..953fe24 100644 --- a/Box42/Preferences/View/Funtion/StorageView.swift +++ b/Box42/Preferences/View/Funtion/StorageView.swift @@ -42,24 +42,28 @@ class StorageView: NSView { func initTextFields() { // Initialize textfields - currentStorageTextField.stringValue = "Current Storage: ?? GB" - remainingStorageTextField.stringValue = "Remaining Storage: ?? GB" - totalStorageTextField.stringValue = "Total Storage: ?? GB" + let labels = [currentStorageTextField, remainingStorageTextField, totalStorageTextField, thresholdTextField, intervalTextField] + for label in labels { + label.font = NSFont.systemFont(ofSize: 14, weight: .semibold) + label.isEditable = false + label.isBordered = false + label.backgroundColor = NSColor.clear + } + currentStorageTextField.stringValue = "Current Storage : ?? GB" + remainingStorageTextField.stringValue = "Remaining Storage : ?? GB" + totalStorageTextField.stringValue = "Total Storage : ?? GB" thresholdTextField.placeholderString = "Enter threshold (%)" intervalTextField.placeholderString = "Enter interval (seconds)" - - [currentStorageTextField, remainingStorageTextField, totalStorageTextField, thresholdTextField, intervalTextField].forEach { textField in - textField.isEditable = false - textField.isBordered = false - textField.backgroundColor = NSColor.clear - } - + intervalTextField.isEditable = true thresholdTextField.isEditable = true } func initButton() { executeScriptButton.title = "Run Script" + executeScriptButton.bezelStyle = .inline + executeScriptButton.wantsLayer = true + executeScriptButton.layer?.cornerRadius = 15 executeScriptButton.target = self executeScriptButton.action = #selector(runScript(_:)) } @@ -95,8 +99,10 @@ class StorageView: NSView { } executeScriptButton.snp.makeConstraints { make in - make.top.equalTo(intervalTextField.snp.bottom).offset(20) + make.top.equalTo(intervalTextField.snp.bottom).offset(0) // 여기에 20을 추가합니다. make.leading.equalToSuperview().offset(20) + make.width.equalTo(70) + make.height.equalTo(30) } } diff --git a/Box42/Preferences/View/Table/PreferencesTableView.swift b/Box42/Preferences/View/Table/PreferencesTableView.swift index 9c8119f..a0427b4 100644 --- a/Box42/Preferences/View/Table/PreferencesTableView.swift +++ b/Box42/Preferences/View/Table/PreferencesTableView.swift @@ -58,14 +58,17 @@ class PreferencesTableView: NSTableView { self.delegate = self self.dataSource = self + self.headerView = nil + self.selectionHighlightStyle = .none + + self.backgroundColor = .white + if #available(macOS 10.14, *) { + self.appearance = NSAppearance(named: .aqua) + } + let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Preferences")) - column1.width = 100.0 - column1.title = "Preferences" self.addTableColumn(column1) - -// self.selectionHighlightStyle = .none } - } extension PreferencesTableView: NSTableViewDelegate, NSTableViewDataSource { diff --git a/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift b/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift index 18a9f47..84363fc 100644 --- a/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift +++ b/Box42/QuickSlot/Controller/QuickSlotManagerViewController.swift @@ -21,6 +21,7 @@ class QuickSlotManagerViewController: NSViewController { quickSlotManagerTableView = QuickSlotTableView(frame: .zero) quickSlotManagerTableView?.setup() quickSlotManagerTableView?.viewModel = viewModel + quickSlotManagerTableView?.backgroundColor = .white let scrollView = NSScrollView() scrollView.documentView = quickSlotManagerTableView diff --git a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift index 9b5ebfc..4c9b310 100644 --- a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift +++ b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.swift @@ -107,6 +107,8 @@ extension QuickSlotButtonCollectionViewController: NSCollectionViewDelegate, NSC btn.snp.makeConstraints { make in make.left.top.right.equalToSuperview() +// make.width.equalTo(58) + make.height.equalTo(58) } label.snp.makeConstraints { make in @@ -117,6 +119,21 @@ extension QuickSlotButtonCollectionViewController: NSCollectionViewDelegate, NSC } return item } +// func collectionView(_ collectionView: NSCollectionView, mouseEnteredAt indexPath: IndexPath) { +// // 호버 효과를 적용하려는 코드 +// if let item = collectionView.item(at: indexPath) as? QuickSlotButtonViewItem, +// let button = item.view.subviews.first as? QuickSlotItemButton { +// button.mouseEntered(with: NSEvent()) // 호버 효과 메서드 호출 +// } +// } +// +// func collectionView(_ collectionView: NSCollectionView, mouseExitedAt indexPath: IndexPath) { +// // 호버 효과를 해제하려는 코드 +// if let item = collectionView.item(at: indexPath) as? QuickSlotButtonViewItem, +// let button = item.view.subviews.first as? QuickSlotItemButton { +// button.mouseExited(with: NSEvent()) // 호버 효과 해제 메서드 호출 +// } +// } } extension QuickSlotButtonCollectionViewController { diff --git a/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift index 8fdaf8c..d225eb7 100644 --- a/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift +++ b/Box42/QuickSlot/View/ButtonCollectionView/Vertical Item/QuickSlotItemButton.swift @@ -27,13 +27,41 @@ class QuickSlotItemButton: NSButton { } else if buttonModel.type == "default-pref" { self.image = NSImage(imageLiteralResourceName: "setting") } + self.target = self + self.action = #selector(mouseEntered) self.isBordered = false self.wantsLayer = true - self.layer?.backgroundColor = NSColor.clear.cgColor + self.layer?.backgroundColor = NSColor.white.cgColor // 흰색 배경 색상 + self.layer?.cornerRadius = 8.0 + self.layer?.zPosition = 1 + let trackingArea = NSTrackingArea( + rect: self.bounds, // 뷰의 경계를 기준으로 할 경우 + options: [.mouseEnteredAndExited, .activeAlways], + owner: self, + userInfo: nil + ) + self.addTrackingArea(trackingArea) + self.associatedString = buttonModel.path } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + self.wantsLayer = true +// self.layer?.frame.size = CGSize(width: 58.0, height: 58.0) + self.layer?.backgroundColor = NSColor(red: 0.848, green: 0.848, blue: 0.848, alpha: 1).cgColor + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + wantsLayer = true +// layer?.frame.size = CGSize(width: 58.0, height: 58.0) + layer?.backgroundColor = NSColor.white.cgColor + } } diff --git a/Box42/QuickSlot/View/QuickSlotGroupView.swift b/Box42/QuickSlot/View/QuickSlotGroupView.swift index d6238eb..785a196 100644 --- a/Box42/QuickSlot/View/QuickSlotGroupView.swift +++ b/Box42/QuickSlot/View/QuickSlotGroupView.swift @@ -10,7 +10,7 @@ import SnapKit class QuickSlotGroupView: NSView { - lazy var divider: NSBox = Divider(completion: { [weak self] in self?.dividerAction?() }) +// lazy var divider: NSBox = Divider(completion: { [weak self] in self?.dividerAction?() }) lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "Star"), completion: { [weak self] in self?.headerAction?() }) lazy var buttonCollectionView: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() @@ -30,16 +30,16 @@ class QuickSlotGroupView: NSView { } private func setupViews() { - self.addSubview(divider) +// self.addSubview(divider) self.addSubview(headerView) self.addSubview(buttonCollectionView.view) } private func setupConstraints() { - divider.snp.makeConstraints { make in - make.top.equalToSuperview() - make.left.right.equalToSuperview() - } +// divider.snp.makeConstraints { make in +// make.top.equalToSuperview() +// make.left.right.equalToSuperview() +// } headerView.snp.makeConstraints { make in make.top.equalToSuperview() diff --git a/Box42/QuickSlot/View/QuickSlotHeaderView.swift b/Box42/QuickSlot/View/QuickSlotHeaderView.swift index 341bc48..da5e6ae 100644 --- a/Box42/QuickSlot/View/QuickSlotHeaderView.swift +++ b/Box42/QuickSlot/View/QuickSlotHeaderView.swift @@ -11,51 +11,51 @@ import SnapKit class QuickSlotHeaderView: NSView { private var callback: (() -> Void)? - private var quickSlotHeaderButton: NSButton! + // private var quickSlotHeaderButton: NSButton! + private var quickSlotIcon: NSImageView! private var quickSlotHeaderLabel: NSTextField! private var quickSlotManageButton: NSButton! init(image: NSImage, completion: @escaping () -> Void) { super.init(frame: .zero) - quickSlotHeaderButton = NSButton(image: image, target: self, action: #selector(btnAction)) - quickSlotHeaderButton.isBordered = false - quickSlotHeaderButton.wantsLayer = true - quickSlotHeaderButton.layer?.backgroundColor = NSColor.clear.cgColor + quickSlotIcon = NSImageView() + quickSlotIcon.image = image + quickSlotIcon.image?.size = NSSize(width: 18, height: 18) + self.addSubview(quickSlotIcon) - self.addSubview(quickSlotHeaderButton) - - quickSlotHeaderLabel = NSTextField(labelWithString: "Quick Slot") - quickSlotHeaderLabel.font = NSFont(name: "Inter", size: QuickSlotUI.size.font) - quickSlotHeaderLabel.textColor = NSColor(hex: "#696969") + quickSlotHeaderLabel = NSTextField(labelWithString: "퀵슬롯") + quickSlotHeaderLabel.font = NSFont.boldSystemFont(ofSize: 16) + quickSlotHeaderLabel.textColor = NSColor.black quickSlotHeaderLabel.backgroundColor = NSColor.clear quickSlotHeaderLabel.isBordered = false - self.addSubview(quickSlotHeaderLabel) - quickSlotManageButton = NSButton(image: NSImage(imageLiteralResourceName: "add"), target: self, action: #selector(btnAction)) + let buttonImage = NSImage(named: NSImage.Name("add"))! + buttonImage.size = NSSize(width: 24, height: 24) + quickSlotManageButton = NSButton(image: buttonImage, target: self, action: #selector(btnAction)) + quickSlotManageButton.setButtonType(.momentaryChange) + quickSlotManageButton.bezelStyle = .texturedRounded quickSlotManageButton.isBordered = false quickSlotManageButton.wantsLayer = true quickSlotManageButton.layer?.backgroundColor = NSColor.clear.cgColor - self.addSubview(quickSlotManageButton) - - quickSlotHeaderButton.snp.makeConstraints { make in + + quickSlotIcon.snp.makeConstraints { make in make.left.equalToSuperview() - make.top.equalToSuperview().offset(13) + make.centerY.equalToSuperview() } quickSlotHeaderLabel.snp.makeConstraints { make in - make.left.equalTo(quickSlotHeaderButton.snp.right).offset(4) - make.bottom.equalTo(quickSlotHeaderButton.snp.bottom) + make.left.equalTo(quickSlotIcon.snp.right).offset(8) + make.centerY.equalToSuperview() } quickSlotManageButton.snp.makeConstraints { make in make.right.equalToSuperview() - make.bottom.equalTo(quickSlotHeaderLabel.snp.bottom) + make.centerY.equalToSuperview() } - self.callback = completion } diff --git a/Box42/QuickSlot/View/Table/QuickSlotCell.swift b/Box42/QuickSlot/View/Table/QuickSlotCell.swift index 89e6deb..dcaaf24 100644 --- a/Box42/QuickSlot/View/Table/QuickSlotCell.swift +++ b/Box42/QuickSlot/View/Table/QuickSlotCell.swift @@ -28,6 +28,17 @@ class QuickSlotCell: NSTableCellView { } private func setupUI() { + let labels = [titleLabel, pathLabel, typeLabel] + for label in labels { + label.wantsLayer = true + label.layer?.cornerRadius = 15 + label.layer?.borderColor = NSColor(red: 0.781, green: 0.781, blue: 0.781, alpha: 1).cgColor + label.layer?.borderWidth = 1 + + label.font = NSFont.systemFont(ofSize: 16, weight: .medium) + label.textColor = NSColor.black + } + addSubview(imageButton) addSubview(titleLabel) addSubview(pathLabel) @@ -44,25 +55,29 @@ class QuickSlotCell: NSTableCellView { deleteButton.snp.makeConstraints { make in make.right.equalToSuperview().offset(-8) make.centerY.equalToSuperview() - make.height.equalToSuperview() + make.width.equalTo(100) + make.height.equalTo(50) } titleLabel.snp.makeConstraints { make in make.left.equalTo(imageButton.snp.right).offset(8) make.right.equalTo(deleteButton.snp.left).offset(-8) make.top.equalTo(imageButton) + make.height.equalTo(30) } pathLabel.snp.makeConstraints { make in make.left.equalTo(titleLabel) make.right.equalTo(deleteButton.snp.left).offset(-8) make.top.equalTo(titleLabel.snp.bottom).offset(4) + make.height.equalTo(30) } typeLabel.snp.makeConstraints { make in make.left.equalTo(pathLabel) make.right.equalTo(deleteButton.snp.left).offset(-8) make.top.equalTo(pathLabel.snp.bottom).offset(4) + make.height.equalTo(30) } } diff --git a/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift b/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift index 9db5098..20d53a9 100644 --- a/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift +++ b/Box42/QuickSlot/View/Table/QuickSlotCellDeleteButton.swift @@ -8,15 +8,19 @@ import AppKit class QuickSlotCellDeleteButton: NSButton { - init() { - super.init(frame: NSRect(x: 0, y: 0, width: 53, height: 100)) - - self.title = "퀵슬롯에서\n제거" + super.init(frame: NSRect(x: 0, y: 0, width: 0, height: 0)) + self.title = "삭제" + let attributes: [NSAttributedString.Key: Any] = [ + .font: NSFont.systemFont(ofSize: 14, weight: .medium), + .foregroundColor: NSColor.white + ] + let attributedTitle = NSAttributedString(string: title, attributes: attributes) + self.attributedTitle = attributedTitle self.isBordered = false self.wantsLayer = true - self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius - self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + self.layer?.cornerRadius = 15 + self.layer?.backgroundColor = NSColor.red.cgColor } override func layout() { @@ -37,40 +41,12 @@ class QuickSlotCellDeleteButton: NSButton { override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) - let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") - bgColorAnimation.fromValue = WindowButtonUI.color.opacityWhite - bgColorAnimation.toValue = WindowButtonUI.color.close - bgColorAnimation.duration = WindowButtonUI.animation.duration - - let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") - cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius - cornerAnimation.toValue = WindowButtonUI.size.cornerRadius / 2 - cornerAnimation.duration = WindowButtonUI.animation.duration - - self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") - self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") - - self.layer?.backgroundColor = WindowButtonUI.color.close - self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius / 2 + self.layer?.backgroundColor = NSColor(red: 1, green: 0, blue: 0, alpha: 0.5).cgColor } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) - let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") - bgColorAnimation.fromValue = WindowButtonUI.color.close - bgColorAnimation.toValue = WindowButtonUI.color.opacityWhite - bgColorAnimation.duration = WindowButtonUI.animation.duration - - let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") - cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius / 2 - cornerAnimation.toValue = WindowButtonUI.size.cornerRadius - cornerAnimation.duration = WindowButtonUI.animation.duration - - self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") - self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") - - self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite - self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + self.layer?.backgroundColor = NSColor.red.cgColor } } diff --git a/Box42/QuickSlot/View/Table/QuickSlotTableView.swift b/Box42/QuickSlot/View/Table/QuickSlotTableView.swift index 7fbf7b5..bf9f054 100644 --- a/Box42/QuickSlot/View/Table/QuickSlotTableView.swift +++ b/Box42/QuickSlot/View/Table/QuickSlotTableView.swift @@ -32,10 +32,11 @@ class QuickSlotTableView: NSTableView { func setup() { self.delegate = self self.dataSource = self + + self.headerView = nil + self.selectionHighlightStyle = .none let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("QuickSlots")) - column1.width = 100.0 - column1.title = "QuickSlot" self.addTableColumn(column1) } } @@ -69,10 +70,20 @@ extension QuickSlotTableView: NSTableViewDelegate, NSTableViewDataSource { } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - getCellForRow(at: row) +// getCellForRow(at: row) + let cellView = getCellForRow(at: row) + + // Remove the top border line for the first row (index 0) + if row == 0 { + let topBorder = CALayer() + topBorder.backgroundColor = NSColor.clear.cgColor + cellView.layer?.addSublayer(topBorder) + } + + return cellView } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - return 200 + return 120 } } diff --git a/Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon.png b/Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon 1.png similarity index 100% rename from Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon.png rename to Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon 1.png diff --git a/Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon 2.png b/Box42/Resources/Assets.xcassets/uibuttons/Bookmark.imageset/Bookmark icon 2.png new file mode 100644 index 0000000000000000000000000000000000000000..fa00ba016b23664c6fe74784f4a56141f1adb01c GIT binary patch literal 539 zcmV+$0_6RPP)2-YtFwB*U3-F;|5*4ETV`qO-iQm_FnLf)^XM zLn)SX7R54~nSvLL*Dw3D#j?#(g$p5-rA{(AEsFd`97-evuR{5Ydz@Z9+fvII@EHS~ z`wJ||OmYs#JBtX10`LmG(ALr{uAkm<^YRf!dowEi^Cmg>uhAiwio$OWH{oBdu(fYuP)!QbrXN~S^(}PHCVzK}k zFeOW4bS+bESaW&OJVp|79>7GZcTF0iUbV*(LvP_Lb;c|)nj1T~M7G>;E!K}!o$LFn zCfj;bi3IH`OyhZQ8bcpyXRcGFX)Qgs(p{}bM#mVve27?yv!3fk9?2-YtFwB*U3-F;|5*4ETV`qO-iQm_FnLf)^XM zLn)SX7R54~nSvLL*Dw3D#j?#(g$p5-rA{(AEsFd`97-evuR{5Ydz@Z9+fvII@EHS~ z`wJ||OmYs#JBtX10`LmG(ALr{uAkm<^YRf!dowEi^Cmg>uhAiwio$OWH{oBdu(fYuP)!QbrXN~S^(}PHCVzK}k zFeOW4bS+bESaW&OJVp|79>7GZcTF0iUbV*(LvP_Lb;c|)nj1T~M7G>;E!K}!o$LFn zCfj;bi3IH`OyhZQ8bcpyXRcGFX)Qgs(p{}bM#mVve27?yv!3fk9? Void)? var sidebar: (() -> Void)? + var toolbarGroupVC: ToolbarViewController = ToolbarViewController() override init(frame: NSRect) { super.init(frame: frame) setupViews() setupConstraints() + + toolbarGroupVC.toolbarViewGroup = self } required init?(coder: NSCoder) { diff --git a/Box42/Toolbar/View/SideBarLeading.swift b/Box42/Toolbar/View/SideBarLeading.swift index c177e12..11b81eb 100644 --- a/Box42/Toolbar/View/SideBarLeading.swift +++ b/Box42/Toolbar/View/SideBarLeading.swift @@ -63,7 +63,7 @@ class SideBarLeading: NSButton { @objc func sideBarLeading() { - runPrefsHelperApplication() +// runPrefsHelperApplication() callback?() } } From 8e05f27a0197cc89be2f7720214ce0f0f36aa344 Mon Sep 17 00:00:00 2001 From: KIM CHAN HEE <85754295+chanhihi@users.noreply.github.com> Date: Tue, 5 Sep 2023 18:19:20 +0900 Subject: [PATCH 15/17] feat: Bookmark table view model view viewmodel (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip: MacBook to iMac * feat: mvvm 구조로 변경합니다. --- Box42.xcodeproj/project.pbxproj | 100 +++- .../xcshareddata/swiftpm/Package.resolved | 24 +- Box42/Bookmark/Model/BookmarkModel.swift | 21 + Box42/Bookmark/View/BookmarkTableView.swift | 196 ++++++++ .../View/Button/BookmarkCreateButton.swift | 68 +++ .../View/Button/BookmarkDeleteButton.swift | 68 +++ .../View/Button/BookmarkUpdateButton.swift | 94 ++++ Box42/Bookmark/View/ButtonTableCellView.swift | 21 + Box42/Bookmark/View/DraggableButton.swift | 105 +++++ Box42/Bookmark/View/Editor/BookmarkCell.swift | 96 ++++ .../View/Editor/BookmarkCellManager.swift | 67 +++ .../View/Editor/BookmarkEditorTableView.swift | 75 +++ .../ViewModel/BookmarkViewModel.swift | 105 +++++ .../Main/BoxBaseContainerViewController.swift | 443 +++++++----------- Box42/Main/UI/MovableContainerView.swift | 2 + Box42/Main/View/BookmarkEditorView.swift | 104 ---- Box42/Main/View/BoxContentsViewGroup.swift | 102 +--- ...uickSlotButtonCollectionViewController.xib | 4 +- Box42/QuickSlot/View/QuickSlotGroupView.swift | 6 +- .../ViewModel/QuickSlotViewModel.swift | 8 +- Box42/Resources/AppDelegate.swift | 1 + .../View/Table/ScriptCellManager.swift | 65 ++- .../Scripts/View/Table/ScriptsTableView.swift | 6 - Box42/Shared/API/GetUserProfile.swift | 1 + Box42/Shared/API/PutUserMeQuickSlot.swift | 61 ++- Box42/Shared/API/PutUserMeUrlList.swift | 50 ++ Box42/Shared/User/UserProfile.swift | 18 +- .../Controller/ToolbarViewController.swift | 2 + Box42/WebView/Model/WebViewUI.swift | 1 + Box42/WebView/WebView.swift | 21 + Box42/WebView/WebViewController.swift | 2 + Box42/WebView/WebViewManager.swift | 11 +- 32 files changed, 1355 insertions(+), 593 deletions(-) create mode 100644 Box42/Bookmark/Model/BookmarkModel.swift create mode 100644 Box42/Bookmark/View/BookmarkTableView.swift create mode 100644 Box42/Bookmark/View/Button/BookmarkCreateButton.swift create mode 100644 Box42/Bookmark/View/Button/BookmarkDeleteButton.swift create mode 100644 Box42/Bookmark/View/Button/BookmarkUpdateButton.swift create mode 100644 Box42/Bookmark/View/ButtonTableCellView.swift create mode 100644 Box42/Bookmark/View/DraggableButton.swift create mode 100644 Box42/Bookmark/View/Editor/BookmarkCell.swift create mode 100644 Box42/Bookmark/View/Editor/BookmarkCellManager.swift create mode 100644 Box42/Bookmark/View/Editor/BookmarkEditorTableView.swift create mode 100644 Box42/Bookmark/ViewModel/BookmarkViewModel.swift delete mode 100644 Box42/Main/View/BookmarkEditorView.swift create mode 100644 Box42/Shared/API/PutUserMeUrlList.swift diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index 77df456..24e1b4d 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -7,7 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 7E9B46922AA5C564009EB900 /* BookmarkEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9B46912AA5C564009EB900 /* BookmarkEditorView.swift */; }; + 64D699FE2AA5F69900EEF7BC /* BookmarkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D699FD2AA5F69900EEF7BC /* BookmarkModel.swift */; }; + 64D69A012AA5F97B00EEF7BC /* BookmarkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D699F72AA5F5C100EEF7BC /* BookmarkViewModel.swift */; }; + 7E9B46922AA5C564009EB900 /* BookmarkEditorTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9B46912AA5C564009EB900 /* BookmarkEditorTableView.swift */; }; DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */; }; DE018BB82A5099F900FF0AA3 /* Box42.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DE018BB62A5099F900FF0AA3 /* Box42.xcdatamodeld */; }; DE018BDD2A509AEB00FF0AA3 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BDC2A509AEB00FF0AA3 /* EventMonitor.swift */; }; @@ -115,6 +117,15 @@ DE98E83B2A98DB6000F8744A /* RotateImage+NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE98E83A2A98DB6000F8744A /* RotateImage+NSImage.swift */; }; DE98E8432A98DDFD00F8744A /* QuickSlotViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE98E8422A98DDFD00F8744A /* QuickSlotViewController.swift */; }; DE98E8552A98EA7900F8744A /* WindowButtonUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE98E8542A98EA7900F8744A /* WindowButtonUI.swift */; }; + DE9B57562AA62723007B796C /* BookmarkTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B57552AA62723007B796C /* BookmarkTableView.swift */; }; + DE9B575A2AA629E4007B796C /* DraggableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B57592AA629E4007B796C /* DraggableButton.swift */; }; + DE9B575E2AA62A54007B796C /* ButtonTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B575D2AA62A54007B796C /* ButtonTableCellView.swift */; }; + DE9B57672AA65B87007B796C /* PutUserMeUrlList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B57662AA65B87007B796C /* PutUserMeUrlList.swift */; }; + DE9B576C2AA6647F007B796C /* BookmarkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B576B2AA6647F007B796C /* BookmarkCell.swift */; }; + DE9B576F2AA664D1007B796C /* BookmarkCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B576E2AA664D1007B796C /* BookmarkCellManager.swift */; }; + DE9B57782AA66DCD007B796C /* BookmarkUpdateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B57772AA66DCD007B796C /* BookmarkUpdateButton.swift */; }; + DE9B577C2AA66DEA007B796C /* BookmarkDeleteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B577B2AA66DEA007B796C /* BookmarkDeleteButton.swift */; }; + DE9B577F2AA66DF9007B796C /* BookmarkCreateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9B577E2AA66DF9007B796C /* BookmarkCreateButton.swift */; }; DE9DA8142A97F20E001C0D3B /* ButtonGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE9DA8132A97F20E001C0D3B /* ButtonGroupViewController.swift */; }; DEB862D42A85124500278FCD /* cleanCache.sh in Resources */ = {isa = PBXBuildFile; fileRef = DEB862D32A85124500278FCD /* cleanCache.sh */; }; DEB862D92A852C4500278FCD /* brewInGoinfre.sh in Resources */ = {isa = PBXBuildFile; fileRef = DEB862D82A852C4500278FCD /* brewInGoinfre.sh */; }; @@ -134,7 +145,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 7E9B46912AA5C564009EB900 /* BookmarkEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkEditorView.swift; sourceTree = ""; }; + 64D699F72AA5F5C100EEF7BC /* BookmarkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = ""; }; + 64D699FD2AA5F69900EEF7BC /* BookmarkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkModel.swift; sourceTree = ""; }; + 7E9B46912AA5C564009EB900 /* BookmarkEditorTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkEditorTableView.swift; sourceTree = ""; }; DE018BAF2A5099F900FF0AA3 /* Box42.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Box42.app; sourceTree = BUILT_PRODUCTS_DIR; }; DE018BB22A5099F900FF0AA3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DE018BB72A5099F900FF0AA3 /* Box42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Box42.xcdatamodel; sourceTree = ""; }; @@ -244,6 +257,15 @@ DE98E83A2A98DB6000F8744A /* RotateImage+NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RotateImage+NSImage.swift"; sourceTree = ""; }; DE98E8422A98DDFD00F8744A /* QuickSlotViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSlotViewController.swift; sourceTree = ""; }; DE98E8542A98EA7900F8744A /* WindowButtonUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowButtonUI.swift; sourceTree = ""; }; + DE9B57552AA62723007B796C /* BookmarkTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkTableView.swift; sourceTree = ""; }; + DE9B57592AA629E4007B796C /* DraggableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableButton.swift; sourceTree = ""; }; + DE9B575D2AA62A54007B796C /* ButtonTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTableCellView.swift; sourceTree = ""; }; + DE9B57662AA65B87007B796C /* PutUserMeUrlList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PutUserMeUrlList.swift; sourceTree = ""; }; + DE9B576B2AA6647F007B796C /* BookmarkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkCell.swift; sourceTree = ""; }; + DE9B576E2AA664D1007B796C /* BookmarkCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkCellManager.swift; sourceTree = ""; }; + DE9B57772AA66DCD007B796C /* BookmarkUpdateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkUpdateButton.swift; sourceTree = ""; }; + DE9B577B2AA66DEA007B796C /* BookmarkDeleteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDeleteButton.swift; sourceTree = ""; }; + DE9B577E2AA66DF9007B796C /* BookmarkCreateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkCreateButton.swift; sourceTree = ""; }; DE9DA8132A97F20E001C0D3B /* ButtonGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupViewController.swift; sourceTree = ""; }; DEB862D32A85124500278FCD /* cleanCache.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = cleanCache.sh; sourceTree = ""; }; DEB862D82A852C4500278FCD /* brewInGoinfre.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = brewInGoinfre.sh; sourceTree = ""; }; @@ -275,6 +297,32 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 64D699F62AA5F5AA00EEF7BC /* Bookmark */ = { + isa = PBXGroup; + children = ( + 64D699FF2AA5F69D00EEF7BC /* Model */, + 64D69A002AA5F6A100EEF7BC /* ViewModel */, + DE9B57542AA62704007B796C /* View */, + ); + path = Bookmark; + sourceTree = ""; + }; + 64D699FF2AA5F69D00EEF7BC /* Model */ = { + isa = PBXGroup; + children = ( + 64D699FD2AA5F69900EEF7BC /* BookmarkModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 64D69A002AA5F6A100EEF7BC /* ViewModel */ = { + isa = PBXGroup; + children = ( + 64D699F72AA5F5C100EEF7BC /* BookmarkViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; DE018BA62A5099F900FF0AA3 = { isa = PBXGroup; children = ( @@ -301,6 +349,7 @@ DE018C0E2A509C0C00FF0AA3 /* Menubar */, DE9DA8122A97F1E2001C0D3B /* ButtonGroup */, DE1F1A202A8B50CA00A88DD8 /* Main */, + 64D699F62AA5F5AA00EEF7BC /* Bookmark */, DE874F512A591EC600FC3B77 /* Preferences */, DEB862D22A8511D600278FCD /* Scripts */, DE018C0C2A509BDF00FF0AA3 /* Resources */, @@ -447,7 +496,6 @@ DE4408202A9297EE0091937A /* View */ = { isa = PBXGroup; children = ( - 7E9B46912AA5C564009EB900 /* BookmarkEditorView.swift */, DE1F1A1A2A8B50C500A88DD8 /* BoxContentsViewGroup.swift */, DE24E6372A8FE10300E29F5D /* BoxBaseSplitView.swift */, ); @@ -486,6 +534,7 @@ DEF0761A2AA33671005700E5 /* DeleteUserMeScript.swift */, DEF076292AA3B955005700E5 /* PutUserMeQuickSlot.swift */, DEF0762C2AA3C34C005700E5 /* GetUserMeQuickSlot.swift */, + DE9B57662AA65B87007B796C /* PutUserMeUrlList.swift */, ); path = API; sourceTree = ""; @@ -704,6 +753,38 @@ path = Model; sourceTree = ""; }; + DE9B57542AA62704007B796C /* View */ = { + isa = PBXGroup; + children = ( + DE9B57732AA666D3007B796C /* Button */, + DE9B57742AA666DD007B796C /* Editor */, + DE9B57552AA62723007B796C /* BookmarkTableView.swift */, + DE9B57592AA629E4007B796C /* DraggableButton.swift */, + DE9B575D2AA62A54007B796C /* ButtonTableCellView.swift */, + ); + path = View; + sourceTree = ""; + }; + DE9B57732AA666D3007B796C /* Button */ = { + isa = PBXGroup; + children = ( + DE9B57772AA66DCD007B796C /* BookmarkUpdateButton.swift */, + DE9B577B2AA66DEA007B796C /* BookmarkDeleteButton.swift */, + DE9B577E2AA66DF9007B796C /* BookmarkCreateButton.swift */, + ); + path = Button; + sourceTree = ""; + }; + DE9B57742AA666DD007B796C /* Editor */ = { + isa = PBXGroup; + children = ( + 7E9B46912AA5C564009EB900 /* BookmarkEditorTableView.swift */, + DE9B576B2AA6647F007B796C /* BookmarkCell.swift */, + DE9B576E2AA664D1007B796C /* BookmarkCellManager.swift */, + ); + path = Editor; + sourceTree = ""; + }; DE9DA8122A97F1E2001C0D3B /* ButtonGroup */ = { isa = PBXGroup; children = ( @@ -883,6 +964,7 @@ DEF076302AA3CF8A005700E5 /* QuickSlotItemLabel.swift in Sources */, DE874F542A591F1400FC3B77 /* PreferencesView.swift in Sources */, DE98E83B2A98DB6000F8744A /* RotateImage+NSImage.swift in Sources */, + DE9B575E2AA62A54007B796C /* ButtonTableCellView.swift in Sources */, DE0A91982A8F977F00D1D6F1 /* ToolbarViewController.swift in Sources */, DEF076432AA48AF0005700E5 /* QuickSlotCellDeleteButton.swift in Sources */, DE9DA8142A97F20E001C0D3B /* ButtonGroupViewController.swift in Sources */, @@ -912,6 +994,7 @@ DE6332E42A9BB8F800DCFAF6 /* QuickSlotButtonCollectionViewController.swift in Sources */, DE9457062A9E69C100B0B768 /* ScriptNameLabel.swift in Sources */, DE77BA512A82580400713683 /* MenubarViewModel.swift in Sources */, + DE9B577F2AA66DF9007B796C /* BookmarkCreateButton.swift in Sources */, DE44080C2A924B520091937A /* BoxFunctionViewGroup.swift in Sources */, DE97CA7C2A9A7199001073DE /* QuickSlotGroupView.swift in Sources */, DEF0762A2AA3B955005700E5 /* PutUserMeQuickSlot.swift in Sources */, @@ -919,8 +1002,11 @@ DE77BBA22A9DDC40006CC98B /* ScriptsFileManager.swift in Sources */, DE874F5F2A5935CC00FC3B77 /* String.swift in Sources */, DE0A91862A8F889F00D1D6F1 /* RefreshPageViaToolbar.swift in Sources */, + DE9B577C2AA66DEA007B796C /* BookmarkDeleteButton.swift in Sources */, + DE9B575A2AA629E4007B796C /* DraggableButton.swift in Sources */, DE874F4E2A591DEA00FC3B77 /* Hotkey.swift in Sources */, DE77BBA62A9DDF2B006CC98B /* WebView.swift in Sources */, + 64D69A012AA5F97B00EEF7BC /* BookmarkViewModel.swift in Sources */, DE0A91832A8F889000D1D6F1 /* GoHomePageViaToolbar().swift in Sources */, DE6332F22A9BCA2C00DCFAF6 /* QuickSlotScriptsLogicController.swift in Sources */, DE9457512AA0BE0F00B0B768 /* NotificationSettingView.swift in Sources */, @@ -928,8 +1014,9 @@ DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */, DE78860C2A9C770300FE21DD /* ScriptsViewModel.swift in Sources */, DE0A91632A8E6A5400D1D6F1 /* Constants.swift in Sources */, - 7E9B46922AA5C564009EB900 /* BookmarkEditorView.swift in Sources */, + 7E9B46922AA5C564009EB900 /* BookmarkEditorTableView.swift in Sources */, DE7886172A9CCB3B00FE21DD /* UserProfile.swift in Sources */, + DE9B57782AA66DCD007B796C /* BookmarkUpdateButton.swift in Sources */, DE0A91902A8F88CA00D1D6F1 /* DisplayURLInToolbar.swift in Sources */, DE77BBCD2A9E0568006CC98B /* ExecuteScripts.swift in Sources */, DE9456F82A9E44FD00B0B768 /* IconController.swift in Sources */, @@ -950,8 +1037,10 @@ DE3FF3772A978AB8009C88EF /* WindowMinimizeButton.swift in Sources */, DE9457542AA0BF5200B0B768 /* ShortcutSettingView.swift in Sources */, DE4408022A923EB60091937A /* PinButtonView.swift in Sources */, + DE9B57672AA65B87007B796C /* PutUserMeUrlList.swift in Sources */, DE77BBE22A9E0F70006CC98B /* Scripts.swift in Sources */, DE78862D2A9D1ADE00FE21DD /* PreferencesCell.swift in Sources */, + DE9B576C2AA6647F007B796C /* BookmarkCell.swift in Sources */, DE9457572AA0C5C600B0B768 /* IconSettingView.swift in Sources */, DE0A91672A8E6CA700D1D6F1 /* WebViewManager.swift in Sources */, DE77BBF32A9E38DC006CC98B /* UserManager.swift in Sources */, @@ -964,10 +1053,12 @@ DE97CA692A9A6364001073DE /* PixelConversion+CGFloat.swift in Sources */, DE7886282A9D186700FE21DD /* ScriptsViewController.swift in Sources */, DEF0763D2AA48125005700E5 /* QuickSlotCell.swift in Sources */, + DE9B57562AA62723007B796C /* BookmarkTableView.swift in Sources */, DE4408052A923EC00091937A /* QuitButtonView.swift in Sources */, DE0A918A2A8F88A900D1D6F1 /* GoForwardInToolbar.swift in Sources */, DE1F1A1E2A8B50C500A88DD8 /* BoxButtonViewGroup.swift in Sources */, DEB862EB2A853F7F00278FCD /* BoxWindowController.swift in Sources */, + 64D699FE2AA5F69900EEF7BC /* BookmarkModel.swift in Sources */, DE0A918D2A8F88BC00D1D6F1 /* GoBackInToolbar.swift in Sources */, DE94574E2AA0B56200B0B768 /* CPUView.swift in Sources */, DE018BDD2A509AEB00FF0AA3 /* EventMonitor.swift in Sources */, @@ -982,6 +1073,7 @@ DE98E8432A98DDFD00F8744A /* QuickSlotViewController.swift in Sources */, DEF0762D2AA3C34C005700E5 /* GetUserMeQuickSlot.swift in Sources */, DE94574B2AA0A70500B0B768 /* NetworkView.swift in Sources */, + DE9B576F2AA664D1007B796C /* BookmarkCellManager.swift in Sources */, DE1F1A2E2A8BCC9800A88DD8 /* Storage.swift in Sources */, DE3FF36B2A978A57009C88EF /* WindowButtonViewController.swift in Sources */, DE1F1A312A8BD68F00A88DD8 /* Double.swift in Sources */, diff --git a/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 009c162..19d4e8c 100644 --- a/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,14 +1,16 @@ { - "pins" : [ - { - "identity" : "snapkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SnapKit/SnapKit.git", - "state" : { - "revision" : "f222cbdf325885926566172f6f5f06af95473158", - "version" : "5.6.0" + "object": { + "pins": [ + { + "package": "SnapKit", + "repositoryURL": "https://github.com/SnapKit/SnapKit.git", + "state": { + "branch": null, + "revision": "f222cbdf325885926566172f6f5f06af95473158", + "version": "5.6.0" + } } - } - ], - "version" : 2 + ] + }, + "version": 1 } diff --git a/Box42/Bookmark/Model/BookmarkModel.swift b/Box42/Bookmark/Model/BookmarkModel.swift new file mode 100644 index 0000000..0f8bdd5 --- /dev/null +++ b/Box42/Bookmark/Model/BookmarkModel.swift @@ -0,0 +1,21 @@ +// +// BookmarkModel.swift +// Box42 +// +// Created by Chan on 2023/09/04. +// + +struct URLList: Codable { + let urlList: [URLItem] +} + +struct URLItem: Codable { + let name: String + let url: String +} + +extension URLItem: Equatable { + static func ==(lhs: URLItem, rhs: URLItem) -> Bool { + return lhs.name == rhs.name && lhs.url == rhs.url + } +} diff --git a/Box42/Bookmark/View/BookmarkTableView.swift b/Box42/Bookmark/View/BookmarkTableView.swift new file mode 100644 index 0000000..7cdad6a --- /dev/null +++ b/Box42/Bookmark/View/BookmarkTableView.swift @@ -0,0 +1,196 @@ +//// +//// BookmarkTableView.swift +//// Box42 +//// +//// Created by Chanhee Kim on 9/4/23. +//// +// +//import AppKit +//import SnapKit +//import Combine +// +//class BookmarkTableView: NSTableView { +// var onButtonClicked: ((DraggableButton) -> Void)? +// +// var buttonTitleArray: [String] { +// return BookmarkViewModel.shared.bookMarkList.map { $0.name } +// } +// +// var urlArray: [String] { +// return BookmarkViewModel.shared.bookMarkList.map { $0.url } +// } +// +// var viewModel: BookmarkViewModel? { +// didSet { +// print("ViewModel has been set.") +// setupBindings() +// } +// } +// +// var cancellables: Set = [] +// +// private func setupBindings() { +// print("Setting up bindings...") // 디버깅 로그 +// viewModel?.$bookMarkList.sink(receiveValue: { [weak self] newScripts in +// print("Received new scripts: \(newScripts)") // 디버깅 로그 +// DispatchQueue.main.async { +// self?.reloadData() +// } +// }).store(in: &cancellables) +// } +// +// func setup() { +// self.delegate = self +// self.dataSource = self +// self.registerForDraggedTypes([NSPasteboard.PasteboardType.string]) +// +// self.wantsLayer = true +// self.backgroundColor = NSColor(hex: "#E7E7E7") +// self.focusRingType = .none +// self.headerView = nil +// self.autoresizingMask = [.width, .height] +// self.selectionHighlightStyle = .none +// self.intercellSpacing = NSSize(width: 0, height: 0) +// self.setDraggingSourceOperationMask(.move, forLocal: true) +// +// let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Bookmark")) +// column.title = "" +// // column.width = 100 +// column.resizingMask = .autoresizingMask +// +// self.addTableColumn(column) +// } +//} +// +//extension BookmarkTableView: NSTableViewDelegate, NSTableViewDataSource { +// +// func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { +// if dropOperation == .above { +// return .move +// } else { +// return [] +// } +// } +// +// func tableView(_ aTableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { +// guard let str = info.draggingPasteboard.string(forType: .string), let from = Int(str) else { +// return false +// } +// +// let to = (from < row) ? row - 1 : row +// let item = BookmarkViewModel.shared.bookMarkList[from] +// BookmarkViewModel.shared.bookMarkList.remove(at: from) +// BookmarkViewModel.shared.bookMarkList.insert(item, at: to) +// self.reloadData() +// +// for (_, subview) in self.subviews.enumerated() { +// guard let cellView = subview as? CustomTableCellView else { +// continue +// } +// +// cellView.button.title = buttonTitleArray[cellView.rowIndex] +// } +// +// return true +// } +// +// func numberOfRows(in tableView: NSTableView) -> Int { +// return BookmarkViewModel.shared.bookMarkList.count +// } +// +// func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { +// let cellView = ButtonTableCellView() +// cellView.rowIndex = row +// +// let button = DraggableButton(frame: NSRect(x: 0, y: 0, width: 300, height: 44)) +// button.tag = row +// button.bezelStyle = .inline +// button.isBordered = false +// button.title = "" +// button.registerForDraggedTypes([NSPasteboard.PasteboardType.string]) +// button.target = self +// button.action = #selector(buttonClicked(_:)) +// button.delegate = self +// +// let label = NSTextField(frame: NSRect(x: 26 + 21 + 8, y: 25 / 2, width: button.bounds.width, height: button.bounds.height)) +// +// label.stringValue = buttonTitleArray[row] +// label.backgroundColor = .clear +// label.isBordered = false +// label.isEditable = false +// +// let attributes : [NSAttributedString.Key : Any] = +// [ +// NSAttributedString.Key.font : NSFont.systemFont(ofSize:18.0, weight: .light), +// NSAttributedString.Key.foregroundColor : NSColor.black, +// ] +// let attributedStringTitle = NSAttributedString(string: label.stringValue , attributes: +// attributes) +// label.attributedStringValue=attributedStringTitle +// button.addSubview(label) +// +// +// // let image = NSImage(named: NSImage.Name("bookmark-default")) +// // image?.size = NSSize(width: 21, height: 21) +// // button.image = image +// // button.imagePosition = .imageLeading +// // button.image?.alignmentRect = NSRect(x: 0, y: 0, width: 21, height: 21) +// +// let imageView = NSImageView(frame: NSRect(x: 26, y: 25 / 2, width: 21, height: 21)) +// imageView.image = NSImage(named: NSImage.Name("bookmark-default")) +// imageView.imageScaling = .scaleProportionallyUpOrDown +// imageView.imageAlignment = .alignCenter +// button.addSubview(imageView) +// +// +// +// cellView.addSubview(button) +// +// button.snp.makeConstraints { make in +// make.top.equalToSuperview().offset(2) +// make.leading.equalToSuperview() +// make.trailing.equalToSuperview() +// // make.width.equalTo(268) +// make.width.lessThanOrEqualTo(268) +// make.height.equalTo(44) +// } +// +// tableView.rowHeight = 50 +// +// if row == selectedRow { +// button.wantsLayer = true +// button.layer?.cornerRadius = 12 +// button.layer?.backgroundColor = NSColor.white.cgColor +// } else { +// button.wantsLayer = true +// button.layer?.cornerRadius = 12 +// button.layer?.backgroundColor = NSColor.clear.cgColor +// } +// +// return cellView +// } +// +// func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? { +// let pasteboardItem = NSPasteboardItem() +// pasteboardItem.setString(String(row), forType: .string) +// return pasteboardItem +// } +// +// func sendUpdatedDataToServer() { +// let urlList = zip(buttonTitleArray, urlArray).map { ["name": $0.0, "url": $0.1] } +// let jsonData = try? JSONSerialization.data(withJSONObject: ["urlList": urlList]) +// +// var request = URLRequest(url: URL(string:"https://api.42box.kr/user-service/users/me/url-list")!) +// request.httpMethod = "POST" +// request.httpBody = jsonData +// +// URLSession.shared.dataTask(with:request) { (data, response, error) in +// if error != nil{ +// print(error!.localizedDescription) +// } +// else{ +// print("Data posted successfully") +// } +// }.resume() +// } +//} diff --git a/Box42/Bookmark/View/Button/BookmarkCreateButton.swift b/Box42/Bookmark/View/Button/BookmarkCreateButton.swift new file mode 100644 index 0000000..2c246de --- /dev/null +++ b/Box42/Bookmark/View/Button/BookmarkCreateButton.swift @@ -0,0 +1,68 @@ +// +// BookmarkCreateButton.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import AppKit + +class BookmarkCreateButton: NSButton { + + init() { + super.init(frame: NSRect(x: 0, y: 0, width: 70, height: 40)) + + self.title = "북마크 추가" + self.isBordered = false + self.wantsLayer = true + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + + let trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.toValue = WindowButtonUI.color.maximize + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.maximize + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius / 2 + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.maximize + bgColorAnimation.toValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + } +} diff --git a/Box42/Bookmark/View/Button/BookmarkDeleteButton.swift b/Box42/Bookmark/View/Button/BookmarkDeleteButton.swift new file mode 100644 index 0000000..9d94da9 --- /dev/null +++ b/Box42/Bookmark/View/Button/BookmarkDeleteButton.swift @@ -0,0 +1,68 @@ +// +// BookmarkDeleteButton.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import AppKit + +class BookmarkDeleteButton: NSButton { + + init() { + super.init(frame: NSRect(x: 0, y: 0, width: 53, height: 40)) + + self.title = "삭제" + self.isBordered = false + self.wantsLayer = true + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + + let trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.toValue = WindowButtonUI.color.close + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.close + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius / 2 + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.close + bgColorAnimation.toValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + } +} diff --git a/Box42/Bookmark/View/Button/BookmarkUpdateButton.swift b/Box42/Bookmark/View/Button/BookmarkUpdateButton.swift new file mode 100644 index 0000000..f621e96 --- /dev/null +++ b/Box42/Bookmark/View/Button/BookmarkUpdateButton.swift @@ -0,0 +1,94 @@ +// +// BookmarkUpdateButton.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import AppKit + +class BookmarkUpdateButton: NSButton { + + init() { + super.init(frame: NSRect(x: 0, y: 0, width: 53, height: 40)) + + self.title = "변경하기" + self.isBordered = false + self.wantsLayer = true + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + + let trackingArea = NSTrackingArea(rect: self.bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.toValue = WindowButtonUI.color.minimize + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.minimize + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius / 2 + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + let bgColorAnimation = CABasicAnimation(keyPath: "backgroundColor") + bgColorAnimation.fromValue = WindowButtonUI.color.minimize + bgColorAnimation.toValue = WindowButtonUI.color.opacityWhite + bgColorAnimation.duration = WindowButtonUI.animation.duration + + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.fromValue = WindowButtonUI.size.cornerRadius / 2 + cornerAnimation.toValue = WindowButtonUI.size.cornerRadius + cornerAnimation.duration = WindowButtonUI.animation.duration + + self.layer?.add(bgColorAnimation, forKey: "backgroundColorAnimation") + self.layer?.add(cornerAnimation, forKey: "cornerRadiusAnimation") + + self.layer?.backgroundColor = WindowButtonUI.color.opacityWhite + self.layer?.cornerRadius = WindowButtonUI.size.cornerRadius + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + + // 윈도우의 크기와 위치 정보를 가져옴 + guard self.window != nil else { + return + } + if self.title == "퀵슬롯" { return } + // 현재 버튼의 위치를 윈도우 기준으로 변환 + let initialLocation = self.frame.origin + + // 윈도우의 왼쪽 아래 모서리를 최종 목표 위치로 설정 + let finalLocation = NSPoint(x: 0, y: 0) + + // 애니메이션 블록 + NSAnimationContext.runAnimationGroup({ context in + context.duration = 1 // 애니메이션 지속 시간 + + // 애니메이션 적용 + self.animator().setFrameOrigin(finalLocation) + }, completionHandler: { + // 애니메이션 완료 후, 버튼을 원래 위치로 되돌림 + self.setFrameOrigin(initialLocation) + }) + } +} diff --git a/Box42/Bookmark/View/ButtonTableCellView.swift b/Box42/Bookmark/View/ButtonTableCellView.swift new file mode 100644 index 0000000..1a4b2e1 --- /dev/null +++ b/Box42/Bookmark/View/ButtonTableCellView.swift @@ -0,0 +1,21 @@ +//// +//// ButtonTableCellView.swift +//// Box42 +//// +//// Created by Chanhee Kim on 9/5/23. +//// +// +//import AppKit +// +//class ButtonTableCellView: NSTableCellView { +// var button: NSButton! +// var deleteButton: NSButton! +// var rowIndex: Int! +// +// override func viewWillDraw() { +// super.viewWillDraw() +// self.frame.size.width = 268.0 +// self.frame = NSRect(x: self.frame.origin.x - 3, y: self.frame.origin.y, +// width: self.frame.size.width, height: self.frame.size.height) +// } +//} diff --git a/Box42/Bookmark/View/DraggableButton.swift b/Box42/Bookmark/View/DraggableButton.swift new file mode 100644 index 0000000..cf9b0ec --- /dev/null +++ b/Box42/Bookmark/View/DraggableButton.swift @@ -0,0 +1,105 @@ +// +// DraggableButton.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import AppKit + +class DraggableButton: NSButton, NSDraggingSource { + weak var delegate: BoxBaseContainerViewController? + var mouseDownEvent: NSEvent? + + func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { + return .move + } + var initialMouseDownPoint: CGPoint? + + override func mouseDown(with event: NSEvent) { + print("mouseDown") + self.initialMouseDownPoint = event.locationInWindow + } + + override func mouseDragged(with event: NSEvent) { + guard let initialPoint = self.initialMouseDownPoint else { return } + + let distance = hypot( + initialPoint.x - event.locationInWindow.x, + initialPoint.y - event.locationInWindow.y) + + if distance > 3 { // 드래그로 판단하는 최소 거리. 필요에 따라 조절 가능합니다. + let pasteboardItem = NSPasteboardItem() + pasteboardItem.setString("\(self.tag)", forType: .string) + + let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) + + // Create a snapshot of the button + let snapshot = self.snapshot() + + // Set the dragging frame and contents + draggingItem.setDraggingFrame(self.bounds, contents: snapshot) + + beginDraggingSession(with: [draggingItem], event: event, source: self) + + self.initialMouseDownPoint = nil + } + } + + override func mouseUp(with event: NSEvent) { + guard let initialPoint = self.initialMouseDownPoint else { return } + + let distance = hypot( + initialPoint.x - event.locationInWindow.x, + initialPoint.y - event.locationInWindow.y) + + if distance < 3 { // 클릭으로 판단하는 최대 거리. 필요에 따라 조절 가능합니다. + self.target?.perform(self.action, with: self) + } + + self.initialMouseDownPoint = nil + } + + func snapshot() -> NSImage? { + guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { return nil } + cacheDisplay(in: bounds, to: bitmapRep) + let image = NSImage(size: bounds.size) + image.addRepresentation(bitmapRep) + return image + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + + let trackingArea = NSTrackingArea( + rect: bounds, + options: [.mouseEnteredAndExited, .activeAlways], + owner: self, + userInfo: nil + ) + + addTrackingArea(trackingArea) + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + + if self != delegate?.selectedButton { + wantsLayer = true + layer?.frame.size = CGSize(width: 268.0, height: 44.0) + layer?.cornerRadius = 12 + layer?.backgroundColor = NSColor(red: 0.848, green: 0.848, blue: 0.848, alpha: 1).cgColor + } + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + + if self != delegate?.selectedButton { + wantsLayer = true + layer?.frame.size = CGSize(width: 268.0, height: 44.0) + layer?.cornerRadius = 12 + layer?.backgroundColor = NSColor.clear.cgColor + } + } +} diff --git a/Box42/Bookmark/View/Editor/BookmarkCell.swift b/Box42/Bookmark/View/Editor/BookmarkCell.swift new file mode 100644 index 0000000..7e0fc27 --- /dev/null +++ b/Box42/Bookmark/View/Editor/BookmarkCell.swift @@ -0,0 +1,96 @@ +// +// BookmarkCell.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import AppKit +import SnapKit + +class BookmarkCell: NSTableCellView { + var nameLabel: NSTextField = NSTextField() + var descriptionLabel: NSTextField = NSTextField() + var deleteButton: BookmarkDeleteButton = BookmarkDeleteButton() + var quickSlotButton: BookmarkUpdateButton = BookmarkUpdateButton() + + var viewModel: BookmarkViewModel? + var urlitem: URLItem? + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + addSubview(nameLabel) + addSubview(descriptionLabel) + addSubview(quickSlotButton) + addSubview(deleteButton) + + nameLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + make.width.lessThanOrEqualTo(200).priority(.high) + } + + deleteButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + make.width.equalTo(53) + make.height.equalTo(40) + } + + quickSlotButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalTo(deleteButton.snp.left).offset(-8) + make.width.equalTo(53) + make.height.equalTo(40) + } + + descriptionLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(nameLabel.snp.right).offset(8) + make.right.lessThanOrEqualTo(quickSlotButton.snp.left).offset(-8) + make.width.greaterThanOrEqualTo(100).priority(.low) // 최소 너비와 낮은 우선순위 설정 + } + } + + + + func configure(with urlitem: URLItem, viewModel: BookmarkViewModel?) { + self.urlitem = urlitem + self.viewModel = viewModel + nameLabel.stringValue = urlitem.name + descriptionLabel.stringValue = urlitem.url + + deleteButton.target = self + deleteButton.action = #selector(deleteButtonClicked) + + quickSlotButton.target = self + quickSlotButton.action = #selector(quickSlotButtonclicked) + + } + + @objc func deleteButtonClicked() { + if let deleteItem = urlitem { + BookmarkViewModel.shared.deleteBookmark(item: deleteItem) + } + } + + @objc func quickSlotButtonclicked() { + if let tableView = self.superview as? NSTableView { + let rowIndex = tableView.row(for: self) + print("현재 셀의 index: \(rowIndex)") + + if let updateItem = urlitem { + print(rowIndex, updateItem) +// BookmarkViewModel.shared.updateBookmark(rowIndex, item: updateItem) + } + } + } +} diff --git a/Box42/Bookmark/View/Editor/BookmarkCellManager.swift b/Box42/Bookmark/View/Editor/BookmarkCellManager.swift new file mode 100644 index 0000000..a312395 --- /dev/null +++ b/Box42/Bookmark/View/Editor/BookmarkCellManager.swift @@ -0,0 +1,67 @@ +// +// BookmarkCellManager.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import AppKit +import SnapKit + +class BookmarkCellManager: NSTableCellView { + var nameLabel: NSTextField = NSTextField() + var descriptionLabel: NSTextField = NSTextField() + var excuteButton: BookmarkCreateButton = BookmarkCreateButton() + + var viewModel: BookmarkViewModel? + var urlitem: URLItem? + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + addSubview(nameLabel) + addSubview(descriptionLabel) + addSubview(excuteButton) + + nameLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(16) + make.width.lessThanOrEqualTo(200).priority(.high) // 최대 너비와 우선순위 설정 + } + + excuteButton.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-16) + make.width.equalTo(150) + make.height.equalTo(40) + } + + descriptionLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(nameLabel.snp.right).offset(8) + make.right.lessThanOrEqualTo(excuteButton.snp.left).offset(-8) + make.width.greaterThanOrEqualTo(100).priority(.low) // 최소 너비와 낮은 우선순위 설정 + } + } + + func configure(with urlitem: URLItem, viewModel: BookmarkViewModel?) { + self.urlitem = urlitem + self.viewModel = viewModel + nameLabel.stringValue = urlitem.name + descriptionLabel.stringValue = urlitem.url + + excuteButton.target = self + excuteButton.action = #selector(excuteButtonClicked) + } + + @objc func excuteButtonClicked() { + BookmarkViewModel.shared.addBookmark(item: URLItem(name: nameLabel.stringValue, url: descriptionLabel.stringValue)) + } +} diff --git a/Box42/Bookmark/View/Editor/BookmarkEditorTableView.swift b/Box42/Bookmark/View/Editor/BookmarkEditorTableView.swift new file mode 100644 index 0000000..e0db761 --- /dev/null +++ b/Box42/Bookmark/View/Editor/BookmarkEditorTableView.swift @@ -0,0 +1,75 @@ +// +// BookmarkEditorTableView.swift +// Box42 +// +// Created by Dasol on 2023/09/04. +// + +import AppKit +import SnapKit +import Combine + +class BookmarkEditorTableView: NSTableView { + var viewModel: BookmarkViewModel? = BookmarkViewModel.shared + var cancellables: Set = [] + + func setup() { + self.delegate = self + self.dataSource = self + + let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("BookmarkEditorTableView")) + column1.width = 100.0 + column1.title = "BookmarkEditorTableView" + self.addTableColumn(column1) + + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.lightGray.cgColor + self.layer?.cornerRadius = 20 + } + + func setupBindings() { + viewModel?.$bookMarkList + .sink { [weak self] _ in + self?.reloadData() + } + .store(in: &cancellables) + } +} + +extension BookmarkEditorTableView: NSTableViewDelegate, NSTableViewDataSource { + func getCellForRow(at row: Int) -> NSView { + guard let viewModel = viewModel else { + return NSView() + } + + if row < viewModel.bookMarkList.count { + return getBookmarkCell(for: viewModel.bookMarkList[row], viewModel: viewModel) + } else { + // MARK: - 다음 버전에 추가 예정 + return getBookmarkCellManager() + } + } + + private func getBookmarkCell(for urlitem: URLItem, viewModel: BookmarkViewModel) -> BookmarkCell { + let cell = BookmarkCell(frame: .zero) + cell.configure(with: urlitem, viewModel: viewModel) + return cell + } + + private func getBookmarkCellManager() -> BookmarkCellManager { + let scriptCellManger = BookmarkCellManager(frame: .zero) + return scriptCellManger + } + + func numberOfRows(in tableView: NSTableView) -> Int { + return (viewModel?.bookMarkList.count ?? 0) + 1 + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + getCellForRow(at: row) + } + + func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { + return 44.0 + } +} diff --git a/Box42/Bookmark/ViewModel/BookmarkViewModel.swift b/Box42/Bookmark/ViewModel/BookmarkViewModel.swift new file mode 100644 index 0000000..b2c452e --- /dev/null +++ b/Box42/Bookmark/ViewModel/BookmarkViewModel.swift @@ -0,0 +1,105 @@ +// +// BookmarkViewModel.swift +// Box42 +// +// Created by Chan on 2023/09/04. +// + +import AppKit +import Combine + +class BookmarkViewModel: NSObject { + static let shared = BookmarkViewModel() + + @Published var bookMarkList: [URLItem] = [] + + private override init() { + self.bookMarkList = [URLItem(name: "42Box", url: "https://42box.kr/"), + URLItem(name: "23Coaltheme", url: "https://42box.github.io/front-end/"), + URLItem(name: "loopback", url: "http://127.0.0.1:3000/"), + URLItem(name: "Box 42", url: "https://42box.github.io/front-end/#/box"), + URLItem(name: "Intra 42", url: "https://intra.42.fr"), + URLItem(name: "Jiphyeonjeon", url: "https://42library.kr"), + URLItem(name: "42STAT", url: "https://stat.42seoul.kr/home"), + URLItem(name: "24Hane", url: "https://24hoursarenotenough.42seoul.kr"), + URLItem(name: "80kCoding", url: "https://80000coding.oopy.io"), + URLItem(name: "where42", url: "https://www.where42.kr"), + URLItem(name: "cabi", url: "https://cabi.42seoul.io/"), + URLItem(name: "42gg", url: "https://42gg.kr/")] + } + + // Create + func addBookmarkByFront(item: URLItem) { + bookMarkList.append(item) + loadWebView(item.name, item.url) + } + + // Create + func addBookmark(item: URLItem) { + bookMarkList.append(item) + loadWebView(item.name, item.url) + + let body = URLList(urlList: bookMarkList) + API.putUserMeUrlList(urlList: body) { result in + switch result { + case .success(_): + print("Successfully updated the scripts.") // 혹은 사용자에게 보여줄 알림 추가 + case .failure(let error): + print("Failed to update scripts: \(error.localizedDescription)") // 혹은 사용자에게 보여줄 알림 추가 + } + } + } + + func updateBookmark(index: Int, item: URLItem) { + WebViewManager.shared.list[item.name]?.navigationDelegate = nil + WebViewManager.shared.list[item.name]?.stopLoading() + WebViewManager.shared.list[item.name] = nil + bookMarkList[index] = item + loadWebView(item.name, item.url) + } + + // Delete + func deleteBookmark(item: URLItem) { + WebViewManager.shared.list[item.name]?.navigationDelegate = nil + WebViewManager.shared.list[item.name]?.stopLoading() + WebViewManager.shared.list[item.name] = nil + self.bookMarkList.removeAll(where: { $0 == item }) + } + + // 새로운 북마크 배열로 교체하는 메소드 + func replaceBookMarkList(with newBookMarkList: [URLItem]) { + DispatchQueue.main.async { + WebViewManager.shared.list.forEach { (key, webView) in + webView.navigationDelegate = nil + webView.stopLoading() + WebViewManager.shared.list[key] = nil + } + } + + newBookMarkList.forEach { (URLItem) in + loadWebView(URLItem.name, URLItem.url) + } + self.bookMarkList = newBookMarkList + } + + + func loadWebView(_ name: String, _ url: String) { + var url = url + if !url.hasPrefix("https://") && !url.hasPrefix("http://") { + url = "https://" + url + print(url) + } + if let loadURL = URL(string: url) { + DispatchQueue.main.async { + let wkWebView = WebView() + let request = URLRequest(url: loadURL) + + print(request) + WebViewManager.shared.list[name] = wkWebView + DispatchQueue.main.async { + wkWebView.load(request) + } + } + } + } +} diff --git a/Box42/Main/BoxBaseContainerViewController.swift b/Box42/Main/BoxBaseContainerViewController.swift index 4c1c4b0..abd8ce9 100644 --- a/Box42/Main/BoxBaseContainerViewController.swift +++ b/Box42/Main/BoxBaseContainerViewController.swift @@ -7,32 +7,18 @@ import Cocoa import SnapKit - -let bookMarkList = [ - ("home", "https://42box.kr/"), - ("23Coaltheme", "https://42box.github.io/front-end/"), - ("Box 42", "https://42box.github.io/front-end/#/box"), - ("Intra 42", "https://intra.42.fr"), - ("Jiphyeonjeon", "https://42library.kr"), - ("42STAT", "https://stat.42seoul.kr/home"), - ("24Hane", "https://24hoursarenotenough.42seoul.kr"), - ("80kCoding", "https://80000coding.oopy.io"), - ("where42", "https://www.where42.kr"), - ("cabi", "https://cabi.42seoul.io/"), - ("42gg", "https://42gg.kr/"), - ("textart", "https://textart.sh/"), -] +import Combine class BoxBaseContainerViewController: NSViewController { // MARK: - LeftContainer - // var splitView: BoxBaseSplitView = BoxBaseSplitView() + var splitView: BoxBaseSplitView = BoxBaseSplitView() var contentGroup: BoxContentsViewGroup = BoxContentsViewGroup() var toolbarGroupVC: ToolbarViewController = ToolbarViewController() var quickSlotGroupVC: QuickSlotViewController = QuickSlotViewController() var functionGroupVC: BoxFunctionViewController = BoxFunctionViewController() let windowViewGroupVC: WindowButtonViewController = WindowButtonViewController() - // var leftContainer: MovableContainerView = MovableContainerView() - // var buttonGroupVC: ButtonGroupViewController = ButtonGroupViewController() + var leftView: MovableContainerView = MovableContainerView() +// var buttonGroupVC: ButtonGroupViewController = ButtonGroupViewController() // MARK: - QuickSlot var preferenceVC: PreferencesViewController = PreferencesViewController() @@ -42,21 +28,12 @@ class BoxBaseContainerViewController: NSViewController { var quickSlotManagerVC: QuickSlotManagerViewController = QuickSlotManagerViewController() var quickSlotButtonCollectionVC: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() + + // MARK: - table View + var viewModel: BookmarkViewModel? = BookmarkViewModel.shared - // private let splitView: NSSplitView = { - // let splitView = NSSplitView() - // splitView.isVertical = true - // splitView.dividerStyle = .thick - // return splitView - // }() - - public let leftView: NSView = { - let view = NSView() - view.frame.size.width = 302 - 12 - view.frame.size.height = 1200 - return view - }() - + var cancellables: Set = [] + private let bookMarkView: NSView = { let view = NSView() return view @@ -68,9 +45,20 @@ class BoxBaseContainerViewController: NSViewController { tableView.headerView = nil return tableView }() + + + // MARK: - table View End - var buttonTitleArray = bookMarkList.map { $0.0 } - var urlArray = bookMarkList.map { $0.1 } + private func setupBindings() { + print("Setting up bindings...") // 디버깅 로그 + viewModel?.$bookMarkList.sink(receiveValue: { [weak self] newScripts in + print("Received new scripts: \(newScripts)") // 디버깅 로그 + DispatchQueue.main.async { + self?.tableView.reloadData() + } + }).store(in: &cancellables) + } + var selectedRow: Int? var selectedButton: DraggableButton? @@ -134,6 +122,7 @@ class BoxBaseContainerViewController: NSViewController { bookMarkView.snp.makeConstraints { make in make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) make.leading.trailing.equalToSuperview() + make.width.equalToSuperview() make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) } borderView.snp.makeConstraints { make in @@ -206,9 +195,7 @@ class BoxBaseContainerViewController: NSViewController { tableView.addTableColumn(column) - let scrollView = NSScrollView() - scrollView.hasVerticalScroller = true scrollView.documentView = tableView bookMarkView.addSubview(superView) @@ -229,6 +216,7 @@ class BoxBaseContainerViewController: NSViewController { scrollView.snp.makeConstraints { make in make.top.equalTo(superView.snp.bottom).offset(0) make.leading.trailing.equalToSuperview().offset(0) + make.width.equalToSuperview() make.bottom.equalToSuperview() } @@ -238,134 +226,113 @@ class BoxBaseContainerViewController: NSViewController { } @objc func addBookMarkButtonClicked(_ sender: NSButton) { - // splitView.removeArrangedSubview(contentGroup) - // contentGroup.removeFromSuperview() - // - // let newView = BookmarkEditorView(bookMarkList: bookMarkList) - // newView.wantsLayer = true - // newView.layer?.backgroundColor = NSColor.black.cgColor - // newView.layer?.cornerRadius = 20 - // newView.frame.size = contentGroup.frame.size - // - // contentGroup.addSubview(newView) - // newView.snp.makeConstraints { make in - // make.edges.equalToSuperview() - // } - // - // splitView.addArrangedSubview(contentGroup) + contentGroup.removeAllSubviews() + + let bookmarkTableView = BookmarkEditorTableView() + bookmarkTableView.setup() + bookmarkTableView.setupBindings() + + let scrollView = NSScrollView() + contentGroup.addSubview(scrollView) + scrollView.documentView = bookmarkTableView + + scrollView.snp.makeConstraints({ make in + make.edges.equalToSuperview() + }) + + bookmarkTableView.snp.makeConstraints({ make in + make.edges.equalToSuperview() + }) } override func viewDidLoad() { - // self.view.wantsLayer = true - // - //// self.view.layer?.backgroundColor = NSColor(hex: "#FF9548").cgColor - // self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor +// self.view.wantsLayer = true + super.viewDidLoad() + setupBindings() +// +//// self.view.layer?.backgroundColor = NSColor(hex: "#FF9548").cgColor +// self.view.layer?.backgroundColor = NSColor(hex: "#E7E7E7").cgColor NotificationCenter.default.addObserver(self, selector: #selector(handleButtonTapped), name: .collectionButtonTapped, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(headerTappedQuickSlotManagerHandle), name: .collectionHeaderTapped, object: nil) } - // func BoxButtonViewGroupInit() -> BoxButtonViewGroup { - // - // let buttonGroup = BoxButtonViewGroup { sender in - // self.clickBtn(sender: sender) - // } - // - // return buttonGroup - // } - - // func clickBtn(sender: Any?) { - // if let button = sender as? NSButton { - // guard let clickCount = NSApp.currentEvent?.clickCount else { return } - // if clickCount == 2 { - // WebViewManager.shared.list[button.title]!.reload() - // print("Dobule Click") - // } else if clickCount > 2 { - // if let currentURL = WebViewManager.shared.hostingWebView?.url { - // NSWorkspace.shared.open(currentURL) - // } - // print("Triple Click") - // } else if clickCount < 2 { - // contentGroup.removeAllSubviews() - // contentGroup.showWebviews(button) - // } - // } else { - // if let str = sender as? String { - // if str == "box" { - // contentGroup.removeAllSubviews() - // print("box inside") - // } - // } - // } - // } - // - // private func leftContainerInit() { - // leftContainer.frame.size.width = BoxSizeManager.shared.windowButtonGroupSize.width - // leftContainer.frame.size.height = BoxSizeManager.shared.windowButtonGroupSize.height - // leftContainer.addSubview(windowViewGroupVC.view) - // leftContainer.addSubview(buttonGroupVC.view) - // leftContainer.addSubview(toolbarGroupVC.view) - // leftContainer.addSubview(quickSlotGroupVC.view) - // leftContainer.addSubview(functionGroupVC.view) - // - // leftContainerAutolayout() - // } - // - // private func leftContainerAutolayout() { - // windowViewGroupVC.view.snp.makeConstraints { make in - // make.top.equalTo(leftContainer) - // make.left.equalTo(leftContainer).offset(3) - // make.width.equalTo(77) - // make.height.equalTo(21) - // } - // - // toolbarGroupVC.view.snp.makeConstraints { make in - // make.top.equalTo(windowViewGroupVC.view.snp.bottom).offset(31) - // make.right.equalTo(leftContainer) - // make.left.equalTo(leftContainer) - // make.height.equalTo(44 + 14 + 24) - // } - // - // buttonGroupVC.view.snp.makeConstraints { make in - // make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) - // make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) - // make.left.equalTo(leftContainer) - // make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) - // } - // - // quickSlotGroupVC.view.snp.makeConstraints { make in - // make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) - // make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) - // make.left.equalTo(leftContainer) - // make.height.equalTo(178) - // } - // - // functionGroupVC.view.snp.makeConstraints { make in - // make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) - // make.left.bottom.equalTo(leftContainer) - // } - // } - // - // func viewInit() { - // self.boxViewSizeInit() - // - // splitView.addArrangedSubview(leftContainer) - // splitView.addArrangedSubview(contentGroup) - // self.view.addSubview(splitView) - // - // splitView.snp.makeConstraints { make in - // make.top.equalToSuperview().offset(Constants.UI.groupAutolayout) - // make.left.equalToSuperview().offset(Constants.UI.groupAutolayout) - // make.right.equalToSuperview().offset(-Constants.UI.groupAutolayout) - // make.bottom.equalToSuperview().offset(-Constants.UI.groupAutolayout) - // } - // } - // - // func boxViewSizeInit() { - // self.view.frame.size.width = BoxSizeManager.shared.size.width - // self.view.frame.size.height = BoxSizeManager.shared.size.height - // } +// func BoxButtonViewGroupInit() -> BoxButtonViewGroup { +// +// let buttonGroup = BoxButtonViewGroup { sender in +// self.clickBtn(sender: sender) +// } +// +// return buttonGroup +// } +// +// private func leftContainerInit() { +// leftContainer.frame.size.width = BoxSizeManager.shared.windowButtonGroupSize.width +// leftContainer.frame.size.height = BoxSizeManager.shared.windowButtonGroupSize.height +// leftContainer.addSubview(windowViewGroupVC.view) +// leftContainer.addSubview(buttonGroupVC.view) +// leftContainer.addSubview(toolbarGroupVC.view) +// leftContainer.addSubview(quickSlotGroupVC.view) +// leftContainer.addSubview(functionGroupVC.view) +// +// leftContainerAutolayout() +// } +// +// private func leftContainerAutolayout() { +// windowViewGroupVC.view.snp.makeConstraints { make in +// make.top.equalTo(leftContainer) +// make.left.equalTo(leftContainer).offset(3) +// make.width.equalTo(77) +// make.height.equalTo(21) +// } +// +// toolbarGroupVC.view.snp.makeConstraints { make in +// make.top.equalTo(windowViewGroupVC.view.snp.bottom).offset(31) +// make.right.equalTo(leftContainer) +// make.left.equalTo(leftContainer) +// make.height.equalTo(44 + 14 + 24) +// } +// +// buttonGroupVC.view.snp.makeConstraints { make in +// make.top.equalTo(toolbarGroupVC.view.snp.bottom).offset(Constants.UI.groupAutolayout) +// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) +// make.left.equalTo(leftContainer) +// make.bottom.equalTo(quickSlotGroupVC.view.snp.top).offset(-Constants.UI.groupAutolayout) +// } +// +// quickSlotGroupVC.view.snp.makeConstraints { make in +// make.bottom.equalTo(functionGroupVC.view.snp.top).offset(-27) +// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) +// make.left.equalTo(leftContainer) +// make.height.equalTo(178) +// } +// +// functionGroupVC.view.snp.makeConstraints { make in +// make.right.equalTo(leftContainer).offset(-Constants.UI.groupAutolayout) +// make.left.bottom.equalTo(leftContainer) +// } +// } +// +// func viewInit() { +// self.boxViewSizeInit() +// +// splitView.addArrangedSubview(leftContainer) +// splitView.addArrangedSubview(contentGroup) +// self.view.addSubview(splitView) +// +// splitView.snp.makeConstraints { make in +// make.top.equalToSuperview().offset(Constants.UI.groupAutolayout) +// make.left.equalToSuperview().offset(Constants.UI.groupAutolayout) +// make.right.equalToSuperview().offset(-Constants.UI.groupAutolayout) +// make.bottom.equalToSuperview().offset(-Constants.UI.groupAutolayout) +// } +// } +// +// func boxViewSizeInit() { +// self.view.frame.size.width = BoxSizeManager.shared.size.width +// self.view.frame.size.height = BoxSizeManager.shared.size.height +// } } extension BoxBaseContainerViewController: NSSplitViewDelegate { @@ -381,25 +348,6 @@ extension BoxBaseContainerViewController: NSTableViewDelegate { pasteboardItem.setString(String(row), forType: .string) return pasteboardItem } - - func sendUpdatedDataToServer() { - let urlList = zip(buttonTitleArray, urlArray).map { ["name": $0.0, "url": $0.1] } - let jsonData = try? JSONSerialization.data(withJSONObject: ["urlList": urlList]) - - var request = URLRequest(url: URL(string:"https://api.42box.kr/user-service/users/me/url-list")!) - request.httpMethod = "POST" - request.httpBody = jsonData - - URLSession.shared.dataTask(with:request) { (data, response, error) in - if error != nil{ - print(error!.localizedDescription) - } - else{ - print("Data posted successfully") - } - }.resume() - } - } class ButtonTableCellView: NSTableCellView { @@ -415,7 +363,7 @@ class ButtonTableCellView: NSTableCellView { extension BoxBaseContainerViewController: NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { - return buttonTitleArray.count + return BookmarkViewModel.shared.bookMarkList.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { @@ -427,13 +375,15 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { button.bezelStyle = .inline button.isBordered = false button.title = "" + button.associatedString = BookmarkViewModel.shared.bookMarkList[row].name button.registerForDraggedTypes([NSPasteboard.PasteboardType.string]) button.target = self button.action = #selector(buttonClicked(_:)) button.delegate = self let label = NSTextField(frame: NSRect(x: 26 + 21 + 8, y: 25 / 2, width: button.bounds.width, height: button.bounds.height)) - label.stringValue = buttonTitleArray[row] + + label.stringValue = BookmarkViewModel.shared.bookMarkList[row].name label.backgroundColor = .clear label.isBordered = false label.isEditable = false @@ -466,8 +416,7 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { make.top.equalToSuperview().offset(2) make.leading.equalToSuperview() make.trailing.equalToSuperview() - make.width.equalTo(268) - // make.width.lessThanOrEqualTo(268) + make.width.equalToSuperview() make.height.equalTo(44) } @@ -492,10 +441,35 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { selectedButton = sender sender.layer?.backgroundColor = NSColor.white.cgColor - if sender.tag < urlArray.count { - if let url = URL(string:urlArray[sender.tag]) { + if sender.tag < BookmarkViewModel.shared.bookMarkList.count { + if let url = URL(string: BookmarkViewModel.shared.bookMarkList[sender.tag].url) { print(url) - contentGroup.webView.load(URLRequest(url: url)) + clickBtn(sender: sender) + } + } + } + + func clickBtn(sender: Any?) { + if let button = sender as? NSButton { + guard let clickCount = NSApp.currentEvent?.clickCount else { return } + if clickCount == 2 { + WebViewManager.shared.list[button.title]?.reload() + print("Dobule Click") + } else if clickCount > 2 { + if let currentURL = WebViewManager.shared.hostingWebView?.url { + NSWorkspace.shared.open(currentURL) + } + print("Triple Click") + } else if clickCount < 2 { + contentGroup.removeAllSubviews() + contentGroup.showWebviews(button) // sender.tag + } + } else { + if let str = sender as? String { + if str == "box" { + contentGroup.removeAllSubviews() + print("box inside") + } } } } @@ -514,120 +488,21 @@ extension BoxBaseContainerViewController: NSTableViewDataSource { } let to = (from < row) ? row - 1 : row - let item = buttonTitleArray[from] - buttonTitleArray.remove(at: from) - buttonTitleArray.insert(item, at: to) - tableView.reloadData() + let item = BookmarkViewModel.shared.bookMarkList[from] + BookmarkViewModel.shared.bookMarkList.remove(at: from) + BookmarkViewModel.shared.bookMarkList.insert(item, at: to) for (_, subview) in tableView.subviews.enumerated() { guard let cellView = subview as? CustomTableCellView else { continue } - cellView.button.title = buttonTitleArray[cellView.rowIndex] + cellView.button.title = BookmarkViewModel.shared.bookMarkList[cellView.rowIndex].url } return true } } -class DraggableButton: NSButton, NSDraggingSource { - weak var delegate: BoxBaseContainerViewController? - var mouseDownEvent: NSEvent? - - func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { - return .move - } - - override func mouseUp(with event: NSEvent) { - print("mouseUp") - if let down = self.mouseDownEvent { - if event.locationInWindow == down.locationInWindow { - self.target?.perform(self.action, with: self) - } - super.mouseUp(with:event) - self.mouseDownEvent = nil - } - } - - override func mouseDown(with event: NSEvent) { - print("mouseDown") - self.mouseDownEvent = event - if event.clickCount > 1 { - self.target?.perform(self.action, with: self) - } - } - - override func mouseDragged(with event: NSEvent) { - guard let down = self.mouseDownEvent else { return } - - let distance = hypot( - down.locationInWindow.x - event.locationInWindow.x, - down.locationInWindow.y - event.locationInWindow.y) - - if distance > 3 { // Adjust this as needed - let pasteboardItem = NSPasteboardItem() - pasteboardItem.setString("\(self.tag)", forType: .string) - - let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) - - // Create a snapshot of the button - let snapshot = self.snapshot() - - // Set the dragging frame and contents - draggingItem.setDraggingFrame(self.bounds, contents:snapshot) - - beginDraggingSession(with: [draggingItem], event:self.mouseDownEvent!, source:self) - - self.mouseDownEvent = nil - } - } - - func snapshot() -> NSImage? { - guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { return nil } - cacheDisplay(in: bounds, to: bitmapRep) - let image = NSImage(size: bounds.size) - image.addRepresentation(bitmapRep) - return image - } - - override func viewDidMoveToWindow() { - super.viewDidMoveToWindow() - - let trackingArea = NSTrackingArea( - rect: bounds, - options: [.mouseEnteredAndExited, .activeAlways], - owner: self, - userInfo: nil - ) - - addTrackingArea(trackingArea) - } - - override func mouseEntered(with event: NSEvent) { - super.mouseEntered(with: event) - - if self != delegate?.selectedButton { - wantsLayer = true - layer?.frame.size = CGSize(width: 268.0, height: 44.0) - layer?.cornerRadius = 12 - layer?.backgroundColor = NSColor(red: 0.848, green: 0.848, blue: 0.848, alpha: 1).cgColor - } - } - - override func mouseExited(with event: NSEvent) { - super.mouseExited(with: event) - - if self != delegate?.selectedButton { - wantsLayer = true - layer?.frame.size = CGSize(width: 268.0, height: 44.0) - layer?.cornerRadius = 12 - layer?.backgroundColor = NSColor.clear.cgColor - } - } -} - - - //extension BoxBaseContainerViewController: BoxFunctionViewControllerDelegate { // func didTapBoxButton() { // clickBtn(sender: "box") diff --git a/Box42/Main/UI/MovableContainerView.swift b/Box42/Main/UI/MovableContainerView.swift index bd8dba6..88a5d15 100644 --- a/Box42/Main/UI/MovableContainerView.swift +++ b/Box42/Main/UI/MovableContainerView.swift @@ -10,6 +10,8 @@ import AppKit class MovableContainerView: NSView { init() { super.init(frame: .zero) + self.frame.size.width = 302 - 12 + self.frame.size.height = 1200 } required init?(coder: NSCoder) { diff --git a/Box42/Main/View/BookmarkEditorView.swift b/Box42/Main/View/BookmarkEditorView.swift deleted file mode 100644 index fc0088f..0000000 --- a/Box42/Main/View/BookmarkEditorView.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// BookmarkEditorView.swift -// Box42 -// -// Created by Dasol on 2023/09/04. -// - -import Cocoa - -class BookmarkRowView: NSView { - let nameField = NSTextField() - let urlField = NSTextField() - - init(bookmark: (String, String)) { - super.init(frame: .zero) - - nameField.stringValue = bookmark.0 - urlField.stringValue = bookmark.1 - - addSubview(nameField) - addSubview(urlField) - - nameField.snp.makeConstraints { make in - make.top.bottom.equalToSuperview() - make.leading.equalToSuperview() - make.width.equalToSuperview().multipliedBy(0.5) - } - - urlField.snp.makeConstraints { make in - make.top.bottom.trailing.equalToSuperview() - make.leading.equalTo(nameField.snp.trailing) - } - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class BookmarkEditorView : NSView { - - var bookMarkList : [(String,String)] - var changeButton=NSButton(title:"Change",target:nil,action:nil) - var closeButton=NSButton(title:"Close",target:nil,action:nil) - - init(bookMarkList : [(String,String)]){ - - self.bookMarkList=bookMarkList - - super.init(frame:.zero) - - for (index, bookmark) in bookMarkList.enumerated(){ - let row=BookmarkRowView(bookmark: bookmark) - self.addSubview(row) - - row.snp.makeConstraints { make in - if index == 0 { - // First row should be at the top of the editor. - make.top.equalToSuperview() - } else { - // Other rows should be below the previous one. - make.top.equalTo(self.subviews[index - 1].snp.bottom) - } - - // All rows have the same height and span the entire width of the editor. - make.height.equalTo(44) // Or any other height you want. - make.left.right.equalToSuperview() - } - } - - self.addSubview(changeButton) - - changeButton.snp.makeConstraints {make in - if let lastSubview=self.subviews.last{ - // Place it under last subview - make.top.equalTo(lastSubview.snp.bottom).offset(10) - }else{ - make.top.equalToSuperview() - } - - make.centerX.equalToSuperview() - } - - self.addSubview(closeButton) - closeButton.target = self - closeButton.action = #selector(closeButtonClicked(_:)) - closeButton.snp.makeConstraints{maker in - maker.centerX.equalToSuperview() - maker.top.equalTo(changeButton.snp.bottom).offset(10) - maker.bottom.equalToSuperview().offset(-10) - } - } - - required init?(coder aDecoder:NSCoder){ - fatalError("init(coder:) has not been implemented") - } - - @objc func closeButtonClicked(_ sender:NSButton) { - removeFromSuperview() - } -} - - - diff --git a/Box42/Main/View/BoxContentsViewGroup.swift b/Box42/Main/View/BoxContentsViewGroup.swift index 0848b59..8c57685 100644 --- a/Box42/Main/View/BoxContentsViewGroup.swift +++ b/Box42/Main/View/BoxContentsViewGroup.swift @@ -9,109 +9,31 @@ import WebKit import SnapKit class BoxContentsViewGroup: NSView, WKUIDelegate { -// var webVC: WebViewController? + var webVC: WebViewController? var preferencesVC = PreferencesViewController() var scriptsVC = ScriptsViewController() - - var webView: WKWebView! - static let shared = BoxContentsViewGroup() + +// var webView: WKWebView! +// static let shared = BoxContentsViewGroup() init() { super.init(frame: .zero) -// webVC = WebViewController(nibName: nil, bundle: nil) + webVC = WebViewController(nibName: nil, bundle: nil) + self.wantsLayer = true self.layer?.cornerRadius = 20 self.layer?.masksToBounds = true -// self.addSubview(webVC!.view) -// webVC?.view.snp.makeConstraints { make in -// make.edges.equalTo(self) -// } - - let webConfiguration = WKWebViewConfiguration() - webView = WKWebView(frame: .zero, configuration: webConfiguration) - - webView.uiDelegate = self - WebViewManager.shared.hostingWebView = webView - self.addSubview(webView) - webView.snp.makeConstraints { make in + self.addSubview(webVC!.view) + webVC?.view.snp.makeConstraints { make in make.edges.equalTo(self) } - - if let url = URL(string: "https://www.42box.kr") { - let request = URLRequest(url: url) - webView.load(request) - } - - NotificationCenter.default.addObserver(self, selector: - #selector(goBack), name: - Notification.Name("goBack"), object:nil) - - NotificationCenter.default.addObserver(self, selector: - #selector(goForward), name: - Notification.Name("goForward"), object:nil) - - NotificationCenter.default.addObserver(self, selector: - #selector(reload), name: - Notification.Name("reload"), object:nil) - } - - deinit { - // view controller가 해제될 때 observer도 제거합니다. - NotificationCenter.default.removeObserver(self) - } - - @objc func goBack() { - if webView.canGoBack { - webView.goBack() - } - } - - @objc func goForward() { - if webView.canGoForward { - webView.goForward() - } - } - - @objc func reload() { - webView.reload() - } - - func webView(_ webView: WKWebView, - createWebViewWith configuration: WKWebViewConfiguration, - for navigationAction: WKNavigationAction, - windowFeatures: WKWindowFeatures) -> WKWebView? { - - if navigationAction.targetFrame == nil { - if let url = navigationAction.request.url { - if navigationAction.navigationType == .linkActivated { - webView.load(URLRequest(url: url)) - return nil - } - } - } - return nil } - func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { - let openPanel = NSOpenPanel() - openPanel.canChooseFiles = true - openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection - openPanel.level = .popUpMenu - - openPanel.begin { (result) in - if result == .OK { - completionHandler(openPanel.urls) - } else { - completionHandler(nil) - } - } - } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func removeAllSubviews() { for subview in self.subviews { subview.removeFromSuperview() @@ -135,11 +57,11 @@ class BoxContentsViewGroup: NSView, WKUIDelegate { } func showWebviews(_ sender: NSButton) { - guard let currentWebview = WebViewManager.shared.list[sender.title] else { - print("No WebView found for title: \(sender.title)") + guard let currentWebview = WebViewManager.shared.list[sender.associatedString ?? "42Box"] else { + print("No WebView found for title: \(sender.associatedString)") return } - + WebViewManager.shared.hostingname = sender.title WebViewManager.shared.hostingWebView = currentWebview diff --git a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.xib b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.xib index 0fda14b..a516887 100644 --- a/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.xib +++ b/Box42/QuickSlot/View/ButtonCollectionView/QuickSlotButtonCollectionViewController.xib @@ -1,8 +1,8 @@ - + - + diff --git a/Box42/QuickSlot/View/QuickSlotGroupView.swift b/Box42/QuickSlot/View/QuickSlotGroupView.swift index 785a196..6352cbd 100644 --- a/Box42/QuickSlot/View/QuickSlotGroupView.swift +++ b/Box42/QuickSlot/View/QuickSlotGroupView.swift @@ -9,9 +9,9 @@ import AppKit import SnapKit class QuickSlotGroupView: NSView { - -// lazy var divider: NSBox = Divider(completion: { [weak self] in self?.dividerAction?() }) - lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "Star"), completion: { [weak self] in self?.headerAction?() }) + + lazy var divider: NSBox = Divider(completion: { [weak self] in self?.dividerAction?() }) + lazy var headerView: QuickSlotHeaderView = QuickSlotHeaderView(image: NSImage(imageLiteralResourceName: "star"), completion: { [weak self] in self?.headerAction?() }) lazy var buttonCollectionView: QuickSlotButtonCollectionViewController = QuickSlotButtonCollectionViewController() var dividerAction: (() -> Void)? diff --git a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift index 351a562..c0daf9b 100644 --- a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift +++ b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift @@ -13,19 +13,19 @@ class QuickSlotViewModel { @Published var buttons: [QuickSlotButtonModel] = [] private init() { - let button1 = QuickSlotButtonModel(scriptUuid: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3")!, + let button1 = QuickSlotButtonModel(scriptUuid: UUID(uuidString: "37a56076-e72c-4efe-ba7f-de0effe7f4c3f"), title: QuickSlotUI.title.clean, path: Bundle.main.path(forResource: "cleanCache", ofType: "sh"), type: "sh" ) let button2 = QuickSlotButtonModel(title: QuickSlotUI.title.preferences, - path: "preferences", + path: "default-preferences", type: "default-pref") let button3 = QuickSlotButtonModel(title: QuickSlotUI.title.scripts, - path: "scripts", + path: "default-scripts", type: "default-sh") let button4 = QuickSlotButtonModel(title: QuickSlotUI.title.user, - path: "user", + path: "default-user", type: "default-pref") buttons = [button1, button2, button3, button4] diff --git a/Box42/Resources/AppDelegate.swift b/Box42/Resources/AppDelegate.swift index bc8ce90..1f52499 100644 --- a/Box42/Resources/AppDelegate.swift +++ b/Box42/Resources/AppDelegate.swift @@ -32,6 +32,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { WebViewManager.shared.getCookie() API.getUserProfile(WebViewManager.shared.getCookieWebKit) _ = ScriptViewModel.shared + _ = BookmarkViewModel.shared } func applicationWillTerminate(_ aNotification: Notification) { diff --git a/Box42/Scripts/View/Table/ScriptCellManager.swift b/Box42/Scripts/View/Table/ScriptCellManager.swift index 1dfc952..c3aa555 100644 --- a/Box42/Scripts/View/Table/ScriptCellManager.swift +++ b/Box42/Scripts/View/Table/ScriptCellManager.swift @@ -8,16 +8,14 @@ import AppKit import SnapKit -// MARK: - 다음 버전에 추가 예정 class ScriptCellManager: NSTableCellView { var nameLabel: NSTextField = NSTextField() var descriptionLabel: NSTextField = NSTextField() - var excuteButton: ScriptExcuteButton = ScriptExcuteButton() var deleteButton: ScriptDeleteButton = ScriptDeleteButton() var quickSlotButton: ScriptQuickSlotButton = ScriptQuickSlotButton() - var viewModel: ScriptViewModel? - var script: Script? + var viewModel: BookmarkViewModel? + var urlitem: URLItem? override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -32,13 +30,12 @@ class ScriptCellManager: NSTableCellView { addSubview(nameLabel) addSubview(descriptionLabel) addSubview(quickSlotButton) - addSubview(excuteButton) addSubview(deleteButton) nameLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.left.equalToSuperview().offset(16) - make.width.lessThanOrEqualTo(200).priority(.high) // 최대 너비와 우선순위 설정 + make.width.lessThanOrEqualTo(200).priority(.high) } deleteButton.snp.makeConstraints { make in @@ -48,16 +45,9 @@ class ScriptCellManager: NSTableCellView { make.height.equalTo(40) } - excuteButton.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.right.equalTo(deleteButton.snp.left).offset(-8) - make.width.equalTo(70) - make.height.equalTo(40) - } - quickSlotButton.snp.makeConstraints { make in make.centerY.equalToSuperview() - make.right.equalTo(excuteButton.snp.left).offset(-8) + make.right.equalTo(deleteButton.snp.left).offset(-8) make.width.equalTo(53) make.height.equalTo(40) } @@ -72,41 +62,50 @@ class ScriptCellManager: NSTableCellView { - func configure(with script: Script, viewModel: ScriptViewModel?) { - self.script = script + func configure(with urlitem: URLItem, viewModel: BookmarkViewModel?) { + self.urlitem = urlitem self.viewModel = viewModel - nameLabel.stringValue = script.name - descriptionLabel.stringValue = script.description ?? "description" + nameLabel.stringValue = urlitem.name + descriptionLabel.stringValue = urlitem.url deleteButton.target = self deleteButton.action = #selector(deleteButtonClicked) - excuteButton.target = self - excuteButton.action = #selector(excuteButtonClicked) - quickSlotButton.target = self quickSlotButton.action = #selector(quickSlotButtonclicked) } @objc func deleteButtonClicked() { - if let id = script?.scriptUuid { - viewModel?.deleteScript(id: id) - } +// if let id = script?.scriptUuid { +// viewModel?.deleteScript(id: id) +// } } @objc func excuteButtonClicked() { - // if let id = script?.scriptUuid { - // viewModel?.excuteScript(id: id) - // } - if let path = script?.path { - viewModel?.excuteScript(path: path) - } +// if let path = script?.path { +// viewModel?.excuteScript(path: path) +// } } @objc func quickSlotButtonclicked() { - if let id = script?.scriptUuid { - viewModel?.quickSlotScript(id: id) - } +// guard let path = script?.path else { +// return +// } +// +// let alreadyExists = QuickSlotViewModel.shared.buttons.contains { $0.path == path } +// +// if alreadyExists { +// QuickSlotViewModel.shared.removeButton(path) +// quickSlotButton.title = "퀵슬롯" +// } else { +// if QuickSlotViewModel.shared.buttons.count > 7 { +// return +// } else { +// quickSlotButton.title = "저장됨" +//// viewModel?.quickSlotScript(id: id) +// viewModel?.quickSlotScript(path: path) +// } +// } } } diff --git a/Box42/Scripts/View/Table/ScriptsTableView.swift b/Box42/Scripts/View/Table/ScriptsTableView.swift index b312a51..54a8d38 100644 --- a/Box42/Scripts/View/Table/ScriptsTableView.swift +++ b/Box42/Scripts/View/Table/ScriptsTableView.swift @@ -32,12 +32,6 @@ class ScriptsTableView: NSTableView { func setup() { self.delegate = self self.dataSource = self - - self.headerView = nil - self.selectionHighlightStyle = .none - - self.backgroundColor = .white - let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Scripts")) self.addTableColumn(column1) } diff --git a/Box42/Shared/API/GetUserProfile.swift b/Box42/Shared/API/GetUserProfile.swift index a631e2d..5188273 100644 --- a/Box42/Shared/API/GetUserProfile.swift +++ b/Box42/Shared/API/GetUserProfile.swift @@ -24,6 +24,7 @@ extension API { print(">> User MacOS Get :", userProfile) UserManager.shared.updateUserProfile(newProfile: userProfile) QuickSlotViewModel.shared.setUpQuickSlot() + BookmarkViewModel.shared.replaceBookMarkList(with: userProfile.urlList) case .failure(let error): print("Error: \(error)") } diff --git a/Box42/Shared/API/PutUserMeQuickSlot.swift b/Box42/Shared/API/PutUserMeQuickSlot.swift index f672f42..918561f 100644 --- a/Box42/Shared/API/PutUserMeQuickSlot.swift +++ b/Box42/Shared/API/PutUserMeQuickSlot.swift @@ -11,45 +11,40 @@ extension API { // MARK: - Scripts PUT: https://api.42box.kr/user-service/users/me/quick-slot // TODO: refactoring 필수 static func putUserMeQuickSlot(quickSlots: QuickSlotModels, completion: @escaping (Result) -> Void) { + WebViewManager.shared.storageSetCookie() - WebViewManager.shared.hostingWebView?.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in - let cookieStorage = HTTPCookieStorage.shared - for cookie in cookies { - cookieStorage.setCookie(cookie) - } - - let url = "https://api.42box.kr/user-service/users/me/quick-slot" - - var request = URLRequest(url: URL(string: url)!) - request.httpMethod = "PUT" - request.httpShouldHandleCookies = true - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - - // Scripts 객체를 JSON 데이터로 인코딩 - print(quickSlots) - do { - let jsonData = try JSONEncoder().encode(quickSlots) - request.httpBody = jsonData - print(request.httpBody!) - } catch { - print("Failed to encode scripts: \(error)") + let url = "https://api.42box.kr/user-service/users/me/quick-slot" + + var request = URLRequest(url: URL(string: url)!) + request.httpMethod = "PUT" + request.httpShouldHandleCookies = true + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + // Scripts 객체를 JSON 데이터로 인코딩 + print(quickSlots) + do { + let jsonData = try JSONEncoder().encode(quickSlots) + request.httpBody = jsonData + print(request.httpBody!) + } catch { + print("Failed to encode scripts: \(error)") + completion(.failure(error)) + return + } + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { completion(.failure(error)) return } - let task = URLSession.shared.dataTask(with: request) { (data, response, error) in - if let error = error { - completion(.failure(error)) - return - } - - if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { - completion(.failure(NSError(domain: "InvalidStatusCode", code: httpResponse.statusCode, userInfo: nil))) - return - } - completion(.success(())) + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { + completion(.failure(NSError(domain: "InvalidStatusCode", code: httpResponse.statusCode, userInfo: nil))) + return } - task.resume() + completion(.success(())) } + task.resume() } } + diff --git a/Box42/Shared/API/PutUserMeUrlList.swift b/Box42/Shared/API/PutUserMeUrlList.swift new file mode 100644 index 0000000..695fe4a --- /dev/null +++ b/Box42/Shared/API/PutUserMeUrlList.swift @@ -0,0 +1,50 @@ +// +// PutUserMeUrlList.swift +// Box42 +// +// Created by Chanhee Kim on 9/5/23. +// + +import WebKit + +extension API { + // MARK: - Scripts PUT: https://api.42box.kr/user-service/users/me/url-list + // TODO: refactoring 필수 + static func putUserMeUrlList(urlList: URLList, completion: @escaping (Result) -> Void) { + + WebViewManager.shared.storageSetCookie() + + let url = "https://api.42box.kr/user-service/users/me/url-list" + + var request = URLRequest(url: URL(string: url)!) + request.httpMethod = "PUT" + request.httpShouldHandleCookies = true + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + + // Scripts 객체를 JSON 데이터로 인코딩 + print(urlList) + do { + let jsonData = try JSONEncoder().encode(urlList) + request.httpBody = jsonData + print(request.httpBody!) + } catch { + print("Failed to encode scripts: \(error)") + completion(.failure(error)) + return + } + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + if let error = error { + completion(.failure(error)) + return + } + + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { + completion(.failure(NSError(domain: "InvalidStatusCode", code: httpResponse.statusCode, userInfo: nil))) + return + } + completion(.success(())) + } + task.resume() + } +} diff --git a/Box42/Shared/User/UserProfile.swift b/Box42/Shared/User/UserProfile.swift index b786d4d..40c984d 100644 --- a/Box42/Shared/User/UserProfile.swift +++ b/Box42/Shared/User/UserProfile.swift @@ -19,11 +19,6 @@ struct UserProfile: Codable { let quickSlotList: [QuickSlotButtonModel] } -struct URLItem: Codable { - let name: String - let url: String -} - extension UserProfile { static func defaultProfile() -> UserProfile { return UserProfile( @@ -31,18 +26,7 @@ extension UserProfile { nickname: "fox", theme: 0, icon: "fox", - urlList: [URLItem(name: "home", url: "https://42box.kr/"), - URLItem(name: "23Coaltheme", url: "https://42box.github.io/front-end/"), - URLItem(name: "loopback", url: "http://127.0.0.1:3000/"), - URLItem(name: "Box 42", url: "https://42box.github.io/front-end/#/box"), - URLItem(name: "Intra 42", url: "https://intra.42.fr"), - URLItem(name: "Jiphyeonjeon", url: "https://42library.kr"), - URLItem(name: "42STAT", url: "https://stat.42seoul.kr/home"), - URLItem(name: "24Hane", url: "https://24hoursarenotenough.42seoul.kr"), - URLItem(name: "80kCoding", url: "https://80000coding.oopy.io"), - URLItem(name: "where42", url: "https://www.where42.kr"), - URLItem(name: "cabi", url: "https://cabi.42seoul.io/"), - URLItem(name: "42gg", url: "https://42gg.kr/")], + urlList: BookmarkViewModel.shared.bookMarkList, statusMessage: "hello 42Box!", profileImageUrl: "https://42box.kr/user_profile_image/a52671f9-fca9-43ad-b0c0-1c5360831cf2.png", profileImagePath: "user_profile_image/a52671f9-fca9-43ad-b0c0-1c5360831cf2.png", diff --git a/Box42/Toolbar/Controller/ToolbarViewController.swift b/Box42/Toolbar/Controller/ToolbarViewController.swift index 50a77ae..3bcaf21 100644 --- a/Box42/Toolbar/Controller/ToolbarViewController.swift +++ b/Box42/Toolbar/Controller/ToolbarViewController.swift @@ -45,6 +45,8 @@ class ToolbarViewController: NSViewController { lazy var sidebarLeading: SideBarLeading = SideBarLeading(image: NSImage(imageLiteralResourceName: "toggle-on"), completion: { [weak self] in self?.sidebar() }) func sidebar() { +// print("sidebar") +// BookmarkViewModel.shared.addBookmark(item: URLItem(name: "chan", url: "https://42box.kr/")) toolbarViewGroup = BoxToolbarViewGroup() if let baseContainerVC = baseContainerVC { baseContainerVC.leftView.isHidden.toggle() diff --git a/Box42/WebView/Model/WebViewUI.swift b/Box42/WebView/Model/WebViewUI.swift index d5e4a63..fd5376c 100644 --- a/Box42/WebView/Model/WebViewUI.swift +++ b/Box42/WebView/Model/WebViewUI.swift @@ -14,5 +14,6 @@ enum WebViewUI { static let deleteScript = "deleteScript" static let icon = "icon" static let userProfile = "userProfile" + static let saveURL = "saveURL" } } diff --git a/Box42/WebView/WebView.swift b/Box42/WebView/WebView.swift index 9117d5d..2990542 100644 --- a/Box42/WebView/WebView.swift +++ b/Box42/WebView/WebView.swift @@ -26,6 +26,7 @@ class WebView: WKWebView, WKScriptMessageHandler, WKUIDelegate, WKNavigationDele contentController.add(self, name: WebViewUI.transfer.downloadScript) contentController.add(self, name: WebViewUI.transfer.icon) contentController.add(self, name: WebViewUI.transfer.userProfile) + contentController.add(self, name: WebViewUI.transfer.saveURL) self.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true self.configuration.preferences.javaScriptEnabled = true @@ -160,5 +161,25 @@ extension WebView { print("JSON decoding failed: \(error)") } } + + // URL 저장 + if message.name == WebViewUI.transfer.saveURL, let saveURL = message.body as? String { + let scriptJson = saveURL.data(using: .utf8) + print("saveURL : ", String(data: scriptJson!, encoding: .utf8) ?? "Invalid JSON data") + + do { + let decoder = JSONDecoder() + let saveURL = try decoder.decode(URLItem.self, from: scriptJson!) + + BookmarkViewModel.shared.addBookmarkByFront(item: URLItem(name: saveURL.name, url: saveURL.url)) + + print(saveURL) + + } catch { + print("JSON decoding failed: \(error)") + } + } + + } } diff --git a/Box42/WebView/WebViewController.swift b/Box42/WebView/WebViewController.swift index 3acc72d..8c7314c 100644 --- a/Box42/WebView/WebViewController.swift +++ b/Box42/WebView/WebViewController.swift @@ -10,6 +10,8 @@ import WebKit import Combine class WebViewController: NSViewController { + var viewModel = BookmarkViewModel.shared + var URLVM = WebViewModel() var webView: WKWebView! diff --git a/Box42/WebView/WebViewManager.swift b/Box42/WebView/WebViewManager.swift index 088c310..4d222ec 100644 --- a/Box42/WebView/WebViewManager.swift +++ b/Box42/WebView/WebViewManager.swift @@ -12,8 +12,6 @@ typealias WebViewMapping = [String : WKWebView] class WebViewManager: NSObject { static let shared = WebViewManager() - var icon = MenubarViewController() - var getCookieWebKit: WKWebView { didSet { getCookieWebKit.navigationDelegate = self @@ -45,6 +43,15 @@ class WebViewManager: NSObject { getCookieWebKit.load(request) } } + + func storageSetCookie() { + getCookieWebKit.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in + let cookieStorage = HTTPCookieStorage.shared + for cookie in cookies { + cookieStorage.setCookie(cookie) + } + } + } } extension WebViewManager: WKNavigationDelegate { From 2bc233d9ff1db8012dff17b392e518fb571aaa5f Mon Sep 17 00:00:00 2001 From: DaSol Kim Date: Tue, 5 Sep 2023 21:27:13 +0900 Subject: [PATCH 16/17] feat: add pin button and automation setting button. (#135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add pin button and automation setting button. * chore: Package.resolved * chore: 코드외 필요없는 부분 삭제 --------- Co-authored-by: KIM CHAN HEE <85754295+chanhihi@users.noreply.github.com> --- .../xcshareddata/swiftpm/Package.resolved | 16 ---------------- Box42/Main/BoxBaseContainerViewController.swift | 14 ++++++++------ 2 files changed, 8 insertions(+), 22 deletions(-) delete mode 100644 Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 19d4e8c..0000000 --- a/Box42.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "SnapKit", - "repositoryURL": "https://github.com/SnapKit/SnapKit.git", - "state": { - "branch": null, - "revision": "f222cbdf325885926566172f6f5f06af95473158", - "version": "5.6.0" - } - } - ] - }, - "version": 1 -} diff --git a/Box42/Main/BoxBaseContainerViewController.swift b/Box42/Main/BoxBaseContainerViewController.swift index abd8ce9..dda231d 100644 --- a/Box42/Main/BoxBaseContainerViewController.swift +++ b/Box42/Main/BoxBaseContainerViewController.swift @@ -523,12 +523,14 @@ extension BoxBaseContainerViewController { } if button.title == QuickSlotUI.title.user { - print("Button with title \(button.title) was tapped in BaseVC") - contentGroup.removeAllSubviews() - print(WebViewManager.shared.hostingWebView!) - contentGroup.addSubview(WebViewManager.shared.hostingWebView!) - WebViewManager.shared.hostingWebView!.snp.makeConstraints { make in - make.top.bottom.left.right.equalToSuperview() + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.keyboard") { + NSWorkspace.shared.open(url) + + DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.general") { + NSWorkspace.shared.open(url) + } + } } } } From cd0b8dc3cf48894af54785e8c2d40fedf66b4fd0 Mon Sep 17 00:00:00 2001 From: DaSol Kim Date: Tue, 5 Sep 2023 22:56:32 +0900 Subject: [PATCH 17/17] feat: add pin button and automation setting button. (#138) * feat: add pin button and automation setting button. * chore: delete * chore: QuickSlotGroupView.swift --------- Co-authored-by: KIM CHAN HEE <85754295+chanhihi@users.noreply.github.com> --- .../View/BoxFunctionViewGroup.swift | 2 +- Box42/FunctionButton/View/PinButtonView.swift | 4 +-- .../Scripts/View/Table/ScriptsTableView.swift | 2 ++ .../Controller/ToolbarViewController.swift | 28 +++++++++++++--- Box42/Toolbar/View/SideBarLeading.swift | 32 +++++++++++++------ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Box42/FunctionButton/View/BoxFunctionViewGroup.swift b/Box42/FunctionButton/View/BoxFunctionViewGroup.swift index 2e961a0..6510582 100644 --- a/Box42/FunctionButton/View/BoxFunctionViewGroup.swift +++ b/Box42/FunctionButton/View/BoxFunctionViewGroup.swift @@ -10,7 +10,7 @@ import SnapKit class BoxFunctionViewGroup: NSView { lazy var preferenceButton: PreferenceButtonView = PreferenceButtonView(image: NSImage(imageLiteralResourceName: "plus"), completion: { self.preferenceAction?() }) - lazy var pinButton: PinButtonView = PinButtonView(image: NSImage(imageLiteralResourceName: "pin-box"), completion: { self.pinAction?() }) + lazy var pinButton: PinButtonView = PinButtonView(title: "Pin Box", image: NSImage(imageLiteralResourceName: "pin-box"), completion: { self.pinAction?() }) lazy var quitButton: QuitButtonView = QuitButtonView(image: NSImage(imageLiteralResourceName: "figure.snowboarding"), completion: { self.quitAction?() }) lazy var boxButton: BoxFunctionButtonView = BoxFunctionButtonView(image: NSImage(imageLiteralResourceName: "shippingbox"), completion: { self.boxAction?() }) diff --git a/Box42/FunctionButton/View/PinButtonView.swift b/Box42/FunctionButton/View/PinButtonView.swift index 31cf50e..158fbf3 100644 --- a/Box42/FunctionButton/View/PinButtonView.swift +++ b/Box42/FunctionButton/View/PinButtonView.swift @@ -16,7 +16,7 @@ class PinButtonView: NSView { return button }() - init(image: NSImage, completion: @escaping () -> Void) { + init(title: String, image: NSImage, completion: @escaping () -> Void) { super.init(frame: .zero) pinBoxButton.image = image @@ -31,7 +31,7 @@ class PinButtonView: NSView { pinBoxButton.layer?.backgroundColor = NSColor.clear.cgColor pinBoxButton.bezelStyle = .inline - let pinBoxTitle = "Pin Box" + let pinBoxTitle = title let attributes: [NSAttributedString.Key: Any] = [ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 14.0, weight: .semibold), // 원하는 폰트 및 무게로 설정 diff --git a/Box42/Scripts/View/Table/ScriptsTableView.swift b/Box42/Scripts/View/Table/ScriptsTableView.swift index 54a8d38..6d1f3ab 100644 --- a/Box42/Scripts/View/Table/ScriptsTableView.swift +++ b/Box42/Scripts/View/Table/ScriptsTableView.swift @@ -32,6 +32,8 @@ class ScriptsTableView: NSTableView { func setup() { self.delegate = self self.dataSource = self + self.headerView = nil + self.backgroundColor = .white let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Scripts")) self.addTableColumn(column1) } diff --git a/Box42/Toolbar/Controller/ToolbarViewController.swift b/Box42/Toolbar/Controller/ToolbarViewController.swift index 3bcaf21..18f0ddc 100644 --- a/Box42/Toolbar/Controller/ToolbarViewController.swift +++ b/Box42/Toolbar/Controller/ToolbarViewController.swift @@ -26,6 +26,7 @@ class ToolbarViewController: NSViewController { toolbarViewGroup = BoxToolbarViewGroup() toolbarViewGroup?.sidebar = sidebar + } // func runPrefsHelperApplication() { @@ -41,12 +42,22 @@ class ToolbarViewController: NSViewController { // } // } - lazy var sidebarLeading: SideBarLeading = SideBarLeading(image: NSImage(imageLiteralResourceName: "toggle-on"), completion: { [weak self] in self?.sidebar() }) + lazy var pinButton: PinButtonView = PinButtonView(title: "", image: NSImage(imageLiteralResourceName: "pin-box"), completion: { StateManager.shared.togglePin() + + let newImage: NSImage + if StateManager.shared.pin { + newImage = NSImage(imageLiteralResourceName: "pin-box-ver") + } else { + newImage = NSImage(imageLiteralResourceName: "pin-box") + } + + self.pinButton.changePinImage(to: newImage) + }) func sidebar() { -// print("sidebar") -// BookmarkViewModel.shared.addBookmark(item: URLItem(name: "chan", url: "https://42box.kr/")) + // print("sidebar") + // BookmarkViewModel.shared.addBookmark(item: URLItem(name: "chan", url: "https://42box.kr/")) toolbarViewGroup = BoxToolbarViewGroup() if let baseContainerVC = baseContainerVC { baseContainerVC.leftView.isHidden.toggle() @@ -56,7 +67,7 @@ class ToolbarViewController: NSViewController { make.top.bottom.trailing.equalToSuperview().inset(12) make.leading.equalToSuperview().offset(24 + 24) } - + baseContainerVC.view.addSubview(sidebarLeading) sidebarLeading.snp.makeConstraints { make in make.top.equalToSuperview().inset(63) @@ -64,12 +75,21 @@ class ToolbarViewController: NSViewController { make.width.equalTo(24) make.height.equalTo(24) } + + baseContainerVC.view.addSubview(pinButton) + pinButton.snp.makeConstraints { make in + make.leading.equalToSuperview().inset(10) + make.left.bottom.equalTo(baseContainerVC.leftView) + make.width.equalTo(FunctionButtonUI.size.pinWidth) + make.height.equalTo(FunctionButtonUI.size.pinHeight) + } } else { baseContainerVC.contentGroup.snp.remakeConstraints { make in make.top.bottom.trailing.equalToSuperview().inset(12) make.leading.equalTo(baseContainerVC.leftView.snp.trailing) } sidebarLeading.removeFromSuperview() + pinButton.removeFromSuperview() } // 제약 조건을 다시 설정 diff --git a/Box42/Toolbar/View/SideBarLeading.swift b/Box42/Toolbar/View/SideBarLeading.swift index 11b81eb..c76be41 100644 --- a/Box42/Toolbar/View/SideBarLeading.swift +++ b/Box42/Toolbar/View/SideBarLeading.swift @@ -48,18 +48,30 @@ class SideBarLeading: NSButton { } } - func runPrefsHelperApplication() { - if let appURL = Bundle.main.url(forResource: "prefsHelper", withExtension: "app") { - let workspace = NSWorkspace.shared - do { - try workspace.open([appURL], withAppBundleIdentifier: nil, options: [], additionalEventParamDescriptor: nil, launchIdentifiers: nil) - } catch { - print("Error opening app: \(error)") + func runPrefsHelperApplication() { + if let appURL = Bundle.main.url(forResource: "prefsHelper", withExtension: "app") { + let workspace = NSWorkspace.shared + do { + try workspace.open([appURL], withAppBundleIdentifier: nil, options: [], additionalEventParamDescriptor: nil, launchIdentifiers: nil) + } catch { + print("Error opening app: \(error)") + } + } else { + print("App not found") } - } else { - print("App not found") } - } +// func runPrefsHelperApplication() { +// let prefsHelperAppPath = "/Users/solda/Downloads/prefsHelper.app" // prefsHelper.app의 경로 +// +// let appURL = URL(fileURLWithPath: prefsHelperAppPath) +// +// let workspace = NSWorkspace.shared +// do { +// try workspace.open([appURL], withAppBundleIdentifier: nil, options: [], additionalEventParamDescriptor: nil, launchIdentifiers: nil) +// } catch { +// print("Error opening app: \(error)") +// } +// } @objc func sideBarLeading() {