diff --git a/.gitignore b/.gitignore index f031450..ed7c9da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,101 @@ +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + .DS_Store Box42/.DS_Store -Box42.xcodeproj/xcuserdata/ - .prettierrc .swift-format \ No newline at end of file diff --git a/Box42.xcodeproj/project.pbxproj b/Box42.xcodeproj/project.pbxproj index 5ad7d15..8235f4f 100644 --- a/Box42.xcodeproj/project.pbxproj +++ b/Box42.xcodeproj/project.pbxproj @@ -20,6 +20,11 @@ DE018BF32A509B3300FF0AA3 /* MenubarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BF22A509B3300FF0AA3 /* MenubarModel.swift */; }; DE018BF62A509B3600FF0AA3 /* MenubarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE018BF52A509B3600FF0AA3 /* MenubarView.swift */; }; DE018C032A509B5D00FF0AA3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE018C022A509B5D00FF0AA3 /* Main.storyboard */; }; + DE0A915D2A8E348D00D1D6F1 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = DE0A915C2A8E348D00D1D6F1 /* SnapKit */; }; + DE0A91632A8E6A5400D1D6F1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0A91622A8E6A5400D1D6F1 /* Constants.swift */; }; + DE0A91672A8E6CA700D1D6F1 /* WebViewList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0A91662A8E6CA700D1D6F1 /* WebViewList.swift */; }; + DE0A916D2A8E7DD700D1D6F1 /* HoverButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0A916C2A8E7DD700D1D6F1 /* HoverButton.swift */; }; + DE0A91702A8E8BDE00D1D6F1 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0A916F2A8E8BDE00D1D6F1 /* GradientView.swift */; }; DE1F1A142A8B506600A88DD8 /* importMacOSInfo.sh in Resources */ = {isa = PBXBuildFile; fileRef = DE1F1A112A8B506600A88DD8 /* importMacOSInfo.sh */; }; DE1F1A152A8B506600A88DD8 /* exportMacOSInfo.sh in Resources */ = {isa = PBXBuildFile; fileRef = DE1F1A122A8B506600A88DD8 /* exportMacOSInfo.sh */; }; DE1F1A162A8B506600A88DD8 /* keyMapping.sh in Resources */ = {isa = PBXBuildFile; fileRef = DE1F1A132A8B506600A88DD8 /* keyMapping.sh */; }; @@ -64,6 +69,10 @@ DE018BF52A509B3600FF0AA3 /* MenubarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenubarView.swift; sourceTree = ""; }; DE018C022A509B5D00FF0AA3 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; DE018C192A509DBA00FF0AA3 /* Box42.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Box42.entitlements; sourceTree = ""; }; + DE0A91622A8E6A5400D1D6F1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + DE0A91662A8E6CA700D1D6F1 /* WebViewList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewList.swift; sourceTree = ""; }; + DE0A916C2A8E7DD700D1D6F1 /* HoverButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverButton.swift; sourceTree = ""; }; + DE0A916F2A8E8BDE00D1D6F1 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; 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 = ""; }; @@ -98,7 +107,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D676A64A2A8C5CEA00B5C319 /* SnapKit in Frameworks */, + DE0A915D2A8E348D00D1D6F1 /* SnapKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,8 +134,8 @@ DE018BB12A5099F900FF0AA3 /* Box42 */ = { isa = PBXGroup; children = ( + DE0A916B2A8E7DC700D1D6F1 /* UI */, DE1F1A202A8B50CA00A88DD8 /* Main */, - DE1F1A182A8B50BB00A88DD8 /* Box */, DEF749302A85655E00D987C8 /* Extensions */, DEB862E82A853F6800278FCD /* Window */, DEB862D22A8511D600278FCD /* Scripts */, @@ -135,6 +144,7 @@ DE018C0C2A509BDF00FF0AA3 /* Resources */, DE018C062A509B9000FF0AA3 /* System */, DE018C082A509BB500FF0AA3 /* WebView */, + DE1F1A182A8B50BB00A88DD8 /* Box */, DE018C0E2A509C0C00FF0AA3 /* Menubar */, ); path = Box42; @@ -157,6 +167,7 @@ DE018C0B2A509BC100FF0AA3 /* URL */, DE018BE92A509B2100FF0AA3 /* WebViewModel.swift */, DE018BE62A509B1E00FF0AA3 /* WebViewController.swift */, + DE0A91662A8E6CA700D1D6F1 /* WebViewList.swift */, ); path = WebView; sourceTree = ""; @@ -193,6 +204,15 @@ path = Menubar; sourceTree = ""; }; + DE0A916B2A8E7DC700D1D6F1 /* UI */ = { + isa = PBXGroup; + children = ( + DE0A916C2A8E7DD700D1D6F1 /* HoverButton.swift */, + DE0A916F2A8E8BDE00D1D6F1 /* GradientView.swift */, + ); + path = UI; + sourceTree = ""; + }; DE17AF722A834A1600325BF4 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -225,6 +245,7 @@ children = ( DE1F1A282A8B50E200A88DD8 /* BoxSizeManager.swift */, DE77BA552A82637900713683 /* StateManager.swift */, + DE0A91622A8E6A5400D1D6F1 /* Constants.swift */, ); path = Shared; sourceTree = ""; @@ -298,7 +319,7 @@ ); name = Box42; packageProductDependencies = ( - D676A6492A8C5CEA00B5C319 /* SnapKit */, + DE0A915C2A8E348D00D1D6F1 /* SnapKit */, ); productName = Box42; productReference = DE018BAF2A5099F900FF0AA3 /* Box42.app */; @@ -328,7 +349,7 @@ ); mainGroup = DE018BA62A5099F900FF0AA3; packageReferences = ( - D676A6482A8C5CEA00B5C319 /* XCRemoteSwiftPackageReference "SnapKit" */, + DE0A915B2A8E348D00D1D6F1 /* XCRemoteSwiftPackageReference "SnapKit" */, ); productRefGroup = DE018BB02A5099F900FF0AA3 /* Products */; projectDirPath = ""; @@ -376,9 +397,12 @@ DE874F4E2A591DEA00FC3B77 /* Hotkey.swift in Sources */, DE1F1A252A8B50D500A88DD8 /* BoxViewModel.swift in Sources */, DE018BB32A5099F900FF0AA3 /* AppDelegate.swift in Sources */, + DE0A91632A8E6A5400D1D6F1 /* Constants.swift in Sources */, DE018BF32A509B3300FF0AA3 /* MenubarModel.swift in Sources */, DE7A257A2A6D8CA20043225A /* PreferencesViewController.swift in Sources */, + DE0A916D2A8E7DD700D1D6F1 /* HoverButton.swift in Sources */, DE1F1A242A8B50D500A88DD8 /* BoxButtonHandler.swift in Sources */, + DE0A91672A8E6CA700D1D6F1 /* WebViewList.swift in Sources */, DE018BED2A509B2600FF0AA3 /* URLModel.swift in Sources */, DE1F1A1E2A8B50C500A88DD8 /* BoxButtonViewGroup.swift in Sources */, DEB862EB2A853F7F00278FCD /* BoxWindowController.swift in Sources */, @@ -390,6 +414,7 @@ DE2AD3292A824EEB00002D51 /* Accessibility.swift in Sources */, DE874F572A591F2500FC3B77 /* Icon.swift in Sources */, DE1F1A2E2A8BCC9800A88DD8 /* Storage.swift in Sources */, + DE0A91702A8E8BDE00D1D6F1 /* GradientView.swift in Sources */, DE1F1A312A8BD68F00A88DD8 /* Double.swift in Sources */, DE018BEA2A509B2100FF0AA3 /* WebViewModel.swift in Sources */, ); @@ -449,7 +474,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -504,7 +529,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -577,7 +602,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - D676A6482A8C5CEA00B5C319 /* XCRemoteSwiftPackageReference "SnapKit" */ = { + DE0A915B2A8E348D00D1D6F1 /* XCRemoteSwiftPackageReference "SnapKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SnapKit/SnapKit.git"; requirement = { @@ -588,9 +613,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - D676A6492A8C5CEA00B5C319 /* SnapKit */ = { + DE0A915C2A8E348D00D1D6F1 /* SnapKit */ = { isa = XCSwiftPackageProductDependency; - package = D676A6482A8C5CEA00B5C319 /* XCRemoteSwiftPackageReference "SnapKit" */; + package = DE0A915B2A8E348D00D1D6F1 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Box42.xcodeproj/project.xcworkspace/xcuserdata/chanheki.xcuserdatad/UserInterfaceState.xcuserstate b/Box42.xcodeproj/project.xcworkspace/xcuserdata/chanheki.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 4022600..0000000 Binary files a/Box42.xcodeproj/project.xcworkspace/xcuserdata/chanheki.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/Box42/Box/BoxBaseContainerViewController.swift b/Box42/Box/BoxBaseContainerViewController.swift index ceff194..dd38da3 100644 --- a/Box42/Box/BoxBaseContainerViewController.swift +++ b/Box42/Box/BoxBaseContainerViewController.swift @@ -21,7 +21,6 @@ class BoxBaseContainerViewController: NSViewController { func BoxButtonViewGroupInit() -> BoxButtonViewGroup { let buttonGroup = BoxButtonViewGroup { sender in - // 버튼을 클릭할 때 실행할 코드 self.clickBtn(sender: sender) } view.addSubview(buttonGroup) @@ -31,9 +30,8 @@ class BoxBaseContainerViewController: NSViewController { func clickBtn(sender: NSButton) { guard let clickCount = NSApp.currentEvent?.clickCount else { return } if sender.title == "Preferences" { -// boxView.contentGroup.subviews.removeAll() -// boxView.contentGroup.addSubview(preferencesVC.view) -// preferencesVC.viewDidAppear() + contentGroup.removeAllSubviews() + contentGroup.showPreferences() return } if clickCount == 2 { @@ -44,11 +42,8 @@ class BoxBaseContainerViewController: NSViewController { // WebViewList.shared.list[sender.title]!.load(rqURL) print("Triple Click") } else if clickCount < 2 { -// boxView.contentGroup.subviews.removeAll() -// boxView.contentGroup.addSubview(WebViewList.shared.list[sender.title]!) - WebViewList.shared.list[sender.title]!.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true - WebViewList.shared.list[sender.title]!.configuration.preferences.javaScriptEnabled = true - WebViewList.shared.list[sender.title]?.viewDidMoveToSuperview() + contentGroup.removeAllSubviews() + contentGroup.showWebviews(sender) } } diff --git a/Box42/Box/BoxButtonViewGroup.swift b/Box42/Box/BoxButtonViewGroup.swift index 748e8c8..0160c5b 100644 --- a/Box42/Box/BoxButtonViewGroup.swift +++ b/Box42/Box/BoxButtonViewGroup.swift @@ -14,12 +14,11 @@ class BoxButtonViewGroup: NSView { var pinSwitch : NSSwitch = NSSwitch() var clickAction: ((NSButton) -> Void)? var lastAddedButton: NSView? + var loginInfo: NSView? init(clickAction: @escaping (NSButton) -> Void) { self.clickAction = clickAction super.init(frame: BoxSizeManager.shared.buttonGroupSizeNSRect) - -// self.wantsLayer = true setupButtons() divide() } @@ -33,136 +32,76 @@ class BoxButtonViewGroup: NSView { } private func setupButtons() { - createHomeButton() + for subview in self.subviews { + subview.removeFromSuperview() + } + for (name, _) in boxVM.webViewURL.URLstring { self.createButton(name) } + createLoginInfo() preferencesButton() createQuitButton() createPinButton() } + func createLoginInfo() { + + } + @objc private func clickBtn(sender: NSButton) { - clickAction?(sender) // 클로저 실행 + clickAction?(sender) } private func createButton(_ title: String) { - let button = NSButton() - - button.title = title - button.setButtonType(.momentaryLight) - - button.translatesAutoresizingMaskIntoConstraints = false + let button: NSButton + + if title == "home" { + button = NSButton(title: "home", image: NSImage(imageLiteralResourceName: "42box_logo"), target: self, action: #selector(clickBtn(sender:))) + button.imagePosition = .imageOnly + button.isBordered = false + } else { + button = HoverButton() + button.title = title + + button.wantsLayer = true + button.contentTintColor = NSColor.black + button.layer?.borderColor = NSColor.black.cgColor + button.layer?.borderWidth = 1.0 + button.layer?.cornerRadius = 5.0 + button.layer?.opacity = 0.7 + } + super.addSubview(button) button.target = self button.action = #selector(clickBtn(sender:)) - button.isBordered = true - button.bezelStyle = .roundRect - button.showsBorderOnlyWhileMouseInside = true - - super.addSubview(button) - - NSLayoutConstraint.activate([ - button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20), - // 이전 버튼의 bottom anchor와 연결 - button.topAnchor.constraint(equalTo: lastAddedButton?.bottomAnchor ?? self.topAnchor, constant: 10), - button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), - button.heightAnchor.constraint(equalToConstant: 30) - ]) - - lastAddedButton = button - } - -// func createHomeButton() { -// let button = NSButton(title: "home", image: NSImage(imageLiteralResourceName: "42box_logo"), target: self, action: #selector(clickBtn(sender:))) -// -// button.translatesAutoresizingMaskIntoConstraints = false -// button.isBordered = false -// button.imagePosition = .imageOnly -// -// super.addSubview(button) -// -// NSLayoutConstraint.activate([ -// button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20), -// // 이전 버튼의 bottom anchor와 연결 -// button.topAnchor.constraint(equalTo: lastAddedButton?.bottomAnchor ?? self.topAnchor, constant: 10), -// button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), -// button.heightAnchor.constraint(equalToConstant: 30) -// ]) -// -// lastAddedButton = button -// } -// -// func createHomeButton() { -// let button = NSButton(title: "home", image: NSImage(imageLiteralResourceName: "42box_logo"), target: self, action: #selector(clickBtn(sender:))) -// super.addSubview(button) -// -// button.translatesAutoresizingMaskIntoConstraints = false -// button.isBordered = false -// button.imagePosition = .imageOnly -// -// -//// NSLayoutConstraint.activate([ -//// button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20), -//// button.topAnchor.constraint(equalTo: self.topAnchor, constant: 0), -//// button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), -////// button.heightAnchor.constraint(equalToConstant: 30) -//// ]) -// -// NSLayoutConstraint.activate([ -//// button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20), // 좌측 간격을 100에서 20으로 변경 -//// button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), // 우측 간격을 80에서 20으로 변경 -//// button.heightAnchor.constraint(equalToConstant: 30) -// ]) -// -// lastAddedButton = nil // home 버튼 이후의 버튼들이 상단에 연결되지 않도록 설정 -// } - - func createHomeButton() { - let button = NSButton(title: "home", image: NSImage(imageLiteralResourceName: "42box_logo"), target: self, action: #selector(clickBtn(sender:))) - super.addSubview(button) - + let fontSize: CGFloat = 16.0 + button.font = NSFont.systemFont(ofSize: fontSize) + button.setButtonType(.momentaryLight) button.translatesAutoresizingMaskIntoConstraints = false - button.isBordered = false - button.imagePosition = .imageOnly - + button.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(20) - make.trailing.equalToSuperview().offset(-20) - make.height.equalTo(30) + make.centerX.equalToSuperview() + make.leading.equalToSuperview().offset(10) + make.trailing.equalToSuperview().offset(-10) + + if title == "home" { + make.height.equalTo(50) + } else { + make.height.equalTo(50) + } if let lastButton = lastAddedButton { make.top.equalTo(lastButton.snp.bottom).offset(10) } else { - make.top.equalToSuperview() + make.top.equalToSuperview().offset(10) } } lastAddedButton = button } - - - -// func createQuitButton() { -// let button = NSButton() -// button.title = "Quit Box" -// button.setButtonType(.momentaryLight) -// -// button.translatesAutoresizingMaskIntoConstraints = false -// button.action = #selector(NSApplication.terminate(_:)) -// button.isBordered = true -// button.bezelStyle = .roundRect -// button.showsBorderOnlyWhileMouseInside = true -// -// self.addSubview(button) -// -// button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true -// button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10).isActive = true -// button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true -// } - func createQuitButton() { let button = NSButton() button.title = "Quit Box" @@ -176,16 +115,14 @@ class BoxButtonViewGroup: NSView { self.addSubview(button) - NSLayoutConstraint.activate([ - button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20), - button.topAnchor.constraint(equalTo: lastAddedButton?.bottomAnchor ?? self.topAnchor, constant: 10), // 이 부분 수정 - button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), - button.heightAnchor.constraint(equalToConstant: 30) - ]) + button.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(20) + make.trailing.equalToSuperview().offset(-20) + make.bottom.equalToSuperview() + } lastAddedButton = button // 이 부분 추가 } - func createPinButton() { let button = NSButton() diff --git a/Box42/Box/BoxContentsViewGroup.swift b/Box42/Box/BoxContentsViewGroup.swift index ed20f4a..78c9b96 100644 --- a/Box42/Box/BoxContentsViewGroup.swift +++ b/Box42/Box/BoxContentsViewGroup.swift @@ -11,6 +11,7 @@ import WebKit class BoxContentsViewGroup: NSView { var webVC: WebViewController? var webView: WKWebView! + var preferencesVC = PreferencesViewController() init() { let webVC = WebViewController(nibName: nil, bundle: nil) @@ -28,8 +29,40 @@ class BoxContentsViewGroup: NSView { override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) - // Drawing code here. } + + func removeAllSubviews() { + for subview in self.subviews { + subview.removeFromSuperview() + } + } + + func showPreferences() { + self.addSubview(preferencesVC.view) + preferencesVC.viewDidAppear() + } + + func showWebviews(_ sender: NSButton) { + guard let currentWebview = WebViewList.shared.list[sender.title] else { + print("No WebView found for title: \(sender.title)") + return + } + + currentWebview.frame = self.bounds // WebView의 크기 및 위치 설정 + self.addSubview(currentWebview) + + // WebView 설정 + currentWebview.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true + currentWebview.configuration.preferences.javaScriptEnabled = true + + // WebView 내용 로드 확인 (옵셔널) + if currentWebview.url == nil { + print("WebView for \(sender.title) has no content loaded.") + } + + currentWebview.viewDidMoveToSuperview() + } + } diff --git a/Box42/Menubar/MenubarViewController.swift b/Box42/Menubar/MenubarViewController.swift index df49987..de28917 100644 --- a/Box42/Menubar/MenubarViewController.swift +++ b/Box42/Menubar/MenubarViewController.swift @@ -14,7 +14,7 @@ class MenubarViewController: NSWorkspace { let menuBarView = MenuBarView() lazy var eventMonitor: EventMonitor = self.setupEventMonitor() var boxWindowController: BoxWindowController? - + func menubarViewControllerInit() { self.buttonInit() } @@ -42,11 +42,11 @@ class MenubarViewController: NSWorkspace { statusBarVM.stopRunning() } - func buttonInit() { + func buttonInit() { buttonImageChange("Cat") statusBarVM.statusButtonAppear() - } - + } + func buttonImageChange(_ img: String) { statusBarVM.changeStatusBarIcon(img) } @@ -60,7 +60,7 @@ class MenubarViewController: NSWorkspace { let boxViewController = BoxViewController(nibName: nil, bundle: nil) popover.contentViewController = boxViewController } - + func setupEventMonitor() -> EventMonitor { return EventMonitor(mask: [.leftMouseDown, .rightMouseDown, .otherMouseDown]) { [weak self] event in if let strongSelf = self, strongSelf.popover.isShown { @@ -92,7 +92,7 @@ class MenubarViewController: NSWorkspace { popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) } } - + func closePopover(sender: Any?) { popover.performClose(sender) } @@ -100,33 +100,33 @@ class MenubarViewController: NSWorkspace { extension MenubarViewController: MenubarViewControllerDelegate { func toggleWindow(sender: Any?) { - StateManager.shared.setToggleIsShowWindow(); - if StateManager.shared.getToggleIsShowWindow() == false { - boxWindowController?.close() - print("close") - return - } - boxWindowController = BoxWindowController(window: nil) - - // status bar 버튼의 위치를 얻어옵니다. - if let button = statusBarVM.statusBar.statusItem.button, - let window = boxWindowController?.window { - - let buttonFrame = button.window?.convertToScreen(button.frame) ?? NSZeroRect - - // 버튼 위치 아래에 윈도우를 표시하려면 - let desiredPosition = NSPoint(x: buttonFrame.origin.x, y: buttonFrame.origin.y - window.frame.height) - - // 혹은, 버튼 위치의 중앙에 윈도우를 표시하려면 -// let desiredPosition = NSPoint(x: buttonFrame.midX - window.frame.width / 2, y: buttonFrame.origin.y - window.frame.height) - - // 윈도우의 위치를 설정 - window.setFrameOrigin(desiredPosition) - window.level = .floating + StateManager.shared.setToggleIsShowWindow() + if StateManager.shared.getIsShowWindow() == false { + if let window = boxWindowController?.window { + if window.isVisible { + window.orderOut(sender) + print("hide") + } + } + } else { + if boxWindowController == nil { + boxWindowController = BoxWindowController(window: nil) + } + if let button = statusBarVM.statusBar.statusItem.button, + let window = boxWindowController?.window { + if StateManager.shared.getIsShowFirstWindow() == false { + let buttonFrame = button.window?.convertToScreen(button.frame) ?? NSZeroRect + let desiredPosition = NSPoint(x: buttonFrame.origin.x, y: buttonFrame.origin.y - window.frame.height) + + window.setFrameOrigin(desiredPosition) + StateManager.shared.setToggleIsShowFirstWindow() + } + window.level = .floating + } + boxWindowController?.showWindow(sender) } - - boxWindowController?.showWindow(sender) } + } protocol MenubarViewControllerDelegate: AnyObject { diff --git a/Box42/Resources/AppDelegate.swift b/Box42/Resources/AppDelegate.swift index e3ea76a..26df5dd 100644 --- a/Box42/Resources/AppDelegate.swift +++ b/Box42/Resources/AppDelegate.swift @@ -12,7 +12,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { var menubarController = MenubarViewController() lazy var storage = Storage() - func applicationWillFinishLaunching(_ notification: Notification) { menubarController.menubarViewControllerInit() } diff --git a/Box42/Resources/Info.plist b/Box42/Resources/Info.plist index 6529386..b61be39 100644 --- a/Box42/Resources/Info.plist +++ b/Box42/Resources/Info.plist @@ -2,8 +2,6 @@ - LSUIElement - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -24,6 +22,8 @@ 1 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + NSMainStoryboardFile Main NSPrincipalClass @@ -39,10 +39,10 @@ UISceneClassName - UISceneDelegateClassName - UISceneConfigurationName + UISceneDelegateClassName + UISceneStoryboardFile diff --git a/Box42/Shared/Constants.swift b/Box42/Shared/Constants.swift new file mode 100644 index 0000000..3e4ff73 --- /dev/null +++ b/Box42/Shared/Constants.swift @@ -0,0 +1,13 @@ +// +// Constants.swift +// Box42 +// +// Created by Chanhee Kim on 8/17/23. +// + +struct Constants { + struct url { + static let InitialName = "home" + static let InitialPage = "https://42box.github.io/front-end/" + } +} diff --git a/Box42/Shared/StateManager.swift b/Box42/Shared/StateManager.swift index c5a0fec..0e8ca1d 100644 --- a/Box42/Shared/StateManager.swift +++ b/Box42/Shared/StateManager.swift @@ -11,11 +11,13 @@ class StateManager { private var isPin: Bool! private var isShowCPUUsage: Bool! private var isShowWindow: Bool! + private var isShowFirstWindow: Bool! private init() { isPin = false isShowCPUUsage = false isShowWindow = false + isShowFirstWindow = false } func getIsPin() -> Bool { @@ -34,11 +36,19 @@ class StateManager { isShowCPUUsage.toggle() } - func getToggleIsShowWindow() -> Bool { + func getIsShowWindow() -> Bool { return isShowWindow } func setToggleIsShowWindow() { isShowWindow.toggle() } + + func getIsShowFirstWindow() -> Bool { + return isShowFirstWindow + } + + func setToggleIsShowFirstWindow() { + isShowFirstWindow.toggle() + } } diff --git a/Box42/UI/GradientView.swift b/Box42/UI/GradientView.swift new file mode 100644 index 0000000..7afd87e --- /dev/null +++ b/Box42/UI/GradientView.swift @@ -0,0 +1,37 @@ +// +// GradientView.swift +// Box42 +// +// Created by Chanhee Kim on 8/18/23. +// + +import Cocoa + +//layer caching 기법. +//레이어 캐싱: 복잡한 그래픽 연산이 필요한 뷰의 경우 wantsLayer를 true로 설정하고 shouldRasterize 속성을 true로 설정하여 렌더링 결과를 캐시할 수 있습니다. 하지만 이를 과도하게 사용하면 메모리 사용량이 증가할 수 있으므로 주의가 필요합니다. +class GradientView: NSView { + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.setupLayerCaching() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.setupLayerCaching() + } + + private func setupLayerCaching() { + self.wantsLayer = true + self.layer?.shouldRasterize = true + self.layer?.rasterizationScale = self.window?.backingScaleFactor ?? 1.0 + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + let startingColor = NSColor(red: 1.0, green: 0.804, blue: 0.0, alpha: 0.9) + let endingColor = NSColor(red: 1.0, green: 0.447, blue: 0.0, alpha: 0.7) + let gradient = NSGradient(starting: startingColor, ending: endingColor) + gradient?.draw(in: self.bounds, angle: 90) + } +} diff --git a/Box42/UI/HoverButton.swift b/Box42/UI/HoverButton.swift new file mode 100644 index 0000000..c7bbcfe --- /dev/null +++ b/Box42/UI/HoverButton.swift @@ -0,0 +1,46 @@ +// +// HoverButton.swift +// Box42 +// +// Created by Chanhee Kim on 8/18/23. +// + +import Cocoa + +class HoverButton: NSButton { + + private var trackingArea: NSTrackingArea? + + override func awakeFromNib() { + super.awakeFromNib() + + self.wantsLayer = true + self.layer?.borderColor = NSColor.black.cgColor + self.layer?.borderWidth = 1.0 + self.layer?.cornerRadius = 5.0 + } + + override func updateTrackingAreas() { + super.updateTrackingAreas() + + if let trackingArea = self.trackingArea { + self.removeTrackingArea(trackingArea) + } + + let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways] + trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) + self.addTrackingArea(trackingArea!) + } + + override func mouseEntered(with event: NSEvent) { + // 호버 상태일 때의 스타일을 정의합니다. + self.layer?.backgroundColor = NSColor.gray.cgColor + self.layer?.opacity = 1.0 + } + + override func mouseExited(with event: NSEvent) { + // 호버 상태가 아닐 때의 스타일을 정의합니다. +// self.layer?.backgroundColor = NSColor.white.cgColor + self.layer?.opacity = 0.7 + } +} diff --git a/Box42/WebView/URL/URLModel.swift b/Box42/WebView/URL/URLModel.swift index 9763fce..d27e9e3 100644 --- a/Box42/WebView/URL/URLModel.swift +++ b/Box42/WebView/URL/URLModel.swift @@ -7,6 +7,8 @@ import Foundation +typealias nameUrl = (name: String, url: String) + struct URLModel { var id: UUID var name: String @@ -22,25 +24,19 @@ struct URLModel { struct URLModels { var info: [URLModel] - // Network logic - let URLstring: [(String, String)] = [ + // Network logic api call 날려서 받아올 것. + let URLstring: [nameUrl] = [ ("home", "https://42box.github.io/front-end/"), // ("home", "http://127.0.0.1:3000/"), ("Box 42", "https://42box.github.io/front-end/#/box"), ("Intra 42", "https://intra.42.fr"), ("Jiphyeonjeon", "https://42library.kr"), - ("E-Library", "https://42seoul.dkyobobook.co.kr/main.ink"), + ("42STAT", "https://stat.42seoul.kr/home"), ("24Hane", "https://24hoursarenotenough.42seoul.kr"), - ("80000Coding", "https://80000coding.oopy.io"), + ("80kCoding", "https://80000coding.oopy.io"), ("where42", "https://www.where42.kr"), ("cabi", "https://cabi.42seoul.io/"), ("42gg", "https://42gg.kr/"), - ("textart", "https://textart.sh/") + ("textart", "https://textart.sh/"), ] - - mutating func urlSetup() { - URLstring.forEach { (name, url) in - info.append(URLModel(name: name, url: url)) - } - } } diff --git a/Box42/WebView/WebViewController.swift b/Box42/WebView/WebViewController.swift index 51028b9..55991ed 100644 --- a/Box42/WebView/WebViewController.swift +++ b/Box42/WebView/WebViewController.swift @@ -7,16 +7,21 @@ import Cocoa import WebKit +import Combine class WebViewController: NSViewController, WKScriptMessageHandler, WKUIDelegate, WKNavigationDelegate { var URLVM = WebViewModel() var webView: WKWebView! + // Cancellables array to manage the bindings + var cancellables: Set = [] + override func loadView() { self.webView = addWebView() self.view = webView loadWebViewInit() webViewInit() +// bindViewModel() } func loadWebViewInit() { @@ -24,21 +29,27 @@ class WebViewController: NSViewController, WKScriptMessageHandler, WKUIDelegate, for (key, value) in URLVM.URLdict { let wkWebView = addWebView() WebViewList.shared.list[key] = wkWebView - let rqURL = URLRequest(url:value) DispatchQueue.main.async { - wkWebView.load(rqURL) + wkWebView.load(self.URLVM.requestURL(value)) } } } func webViewInit() { - let request = URLRequest(url: URLVM.URLdict["home"]!) -// let request = URLRequest(url: URL(fileURLWithPath: "https://github.com/CHANhihi")) DispatchQueue.main.async { - self.webView.load(request) + self.webView.load(self.URLVM.requestURL(self.URLVM.safeURL())) } } + func bindViewModel() { + // Whenever URLdict changes, it will call loadWebViewInit + URLVM.$URLdict + .sink { [weak self] _ in + self?.loadWebViewInit() + } + .store(in: &cancellables) + } + func addWebView() -> WKWebView { let preferences = WKPreferences() preferences.javaScriptEnabled = true diff --git a/Box42/WebView/WebViewList.swift b/Box42/WebView/WebViewList.swift new file mode 100644 index 0000000..e4535fc --- /dev/null +++ b/Box42/WebView/WebViewList.swift @@ -0,0 +1,20 @@ +// +// WebViewList.swift +// Box42 +// +// Created by Chanhee Kim on 8/17/23. +// + +import WebKit + +typealias WebViewMapping = [String : WKWebView] + +class WebViewList { + static let shared = WebViewList() + + var list: WebViewMapping! + + private init() { + list = [:] + } +} diff --git a/Box42/WebView/WebViewModel.swift b/Box42/WebView/WebViewModel.swift index b3f64ad..2e091a0 100644 --- a/Box42/WebView/WebViewModel.swift +++ b/Box42/WebView/WebViewModel.swift @@ -5,38 +5,70 @@ // Created by Chan on 2023/03/19. // +import Combine import WebKit -// Singleton -class WebViewList { - static let shared = WebViewList() +typealias URLMapping = [String: URL] - var list: [String : WKWebView]! - - private init() { - list = [:] - } -} - -// CRUD 4가지 형태의 데이터 가공 create, read, update, delete +// WebView 관련 CRUD 4가지 형태의 데이터 가공 create, read, update, delete class WebViewModel: ObservableObject { - var webViewURL: URLModels - @Published var URLdict: [String: URL] - + @Published var webViewURL: URLModels + @Published var URLdict: URLMapping + private var cancellables = Set() + init() { - self.webViewURL = URLModels(info: [URLModel(name: "home", url: "https://42box.github.io/front-end/")]) - self.URLdict = [String: URL]() + self.webViewURL = URLModels(info: [URLModel(name: Constants.url.InitialName, url: Constants.url.InitialPage)]) + self.URLdict = URLMapping() + + $webViewURL.sink { (WVURL) in + self.setUpURLdict() + }.store(in: &cancellables) } func setUpURLdict() { -// for urlModel in webViewURL.info { -// URLdict[urlModel.name] = URL(string: urlModel.url) -// } for urlModel in webViewURL.URLstring { - URLdict[urlModel.0] = URL(string: urlModel.1) + URLdict[urlModel.name] = URL(string: urlModel.url) + } + } + + // Create + func createURL(_ name: String, _ url: String) { + let newURL = URLModel(name: name, url: url) + self.webViewURL.info.append(newURL) + } + + // Read + func readURLString(_ index: Int) -> URLModel { + return webViewURL.info[index] + } + + func readURL(_ index: Int) -> URL { + return URL(string: webViewURL.info[index].url) ?? URL(string: Constants.url.InitialPage)! + } + + func safeURL() -> URL { + return URL(string: webViewURL.info.first?.url ?? Constants.url.InitialPage)! + } + + func requestURL(_ url: URL) -> URLRequest { + return URLRequest(url: url) + } + + // Update + func updateURL(_ id: UUID, _ name: String, _ url: String) { + if let selectedIndex = webViewURL.info.firstIndex(where: { user in user.id == id }) { + webViewURL.info[selectedIndex].name = name + webViewURL.info[selectedIndex].url = url } } + // Delete + func deleteURL(id: UUID) { + if let selectedIndex = webViewURL.info.firstIndex(where: { user in user.id == id }) { + cancellables.removeAll() + webViewURL.info.remove(at: selectedIndex) + } + } } diff --git a/Box42/Window/BoxWindowController.swift b/Box42/Window/BoxWindowController.swift index acae3b9..964e402 100644 --- a/Box42/Window/BoxWindowController.swift +++ b/Box42/Window/BoxWindowController.swift @@ -8,18 +8,26 @@ import Cocoa class BoxWindowController: NSWindowController { + var windowInstance: NSWindow! + var gradientView: NSView! + override init(window: NSWindow?) { let contentRect = BoxSizeManager.shared.boxViewSizeNSRect let styleMask: NSWindow.StyleMask = [.titled, .closable, .resizable, .miniaturizable] - let windowInstance = NSWindow(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) + windowInstance = NSWindow(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false) windowInstance.title = "Box" windowInstance.styleMask.insert(.resizable) - windowInstance.backgroundColor = NSColor.red - + windowInstance.isReleasedWhenClosed = false + windowInstance.isOpaque = false + + super.init(window: windowInstance) + gradientView = GradientView(frame: contentRect) + let boxViewController = BoxViewController(nibName: nil, bundle: nil) windowInstance.contentViewController = boxViewController - - super.init(window: windowInstance) + windowInstance.contentView?.addSubview(gradientView, positioned: .below, relativeTo: nil) + gradientView.translatesAutoresizingMaskIntoConstraints = false + gradientViewAutoLayout() } required init?(coder: NSCoder) { @@ -28,6 +36,16 @@ class BoxWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() - // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. + } + + func gradientViewAutoLayout() { + if let contentView = windowInstance.contentView { + NSLayoutConstraint.activate([ + gradientView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + gradientView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + gradientView.topAnchor.constraint(equalTo: contentView.topAnchor), + gradientView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } } }