From c321b1e628c9ce2aeeb80c45f3423dc4f5829ab5 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 4 Aug 2022 13:23:01 +0200 Subject: [PATCH] fix(macos): fix `mouseDown` always opening React menu React menu popup is for multi-app mode only. --- ios/ReactTestApp/ContentView.swift | 8 +- ios/ReactTestApp/ReactInstance.swift | 86 +++--- ios/ReactTestApp/SceneDelegate.swift | 94 +++---- macos/ReactTestApp/AppDelegate.swift | 342 ++++++++++++------------ macos/ReactTestApp/ViewController.swift | 20 +- package.json | 2 +- 6 files changed, 280 insertions(+), 272 deletions(-) diff --git a/ios/ReactTestApp/ContentView.swift b/ios/ReactTestApp/ContentView.swift index 0f3452270..6a396bf38 100644 --- a/ios/ReactTestApp/ContentView.swift +++ b/ios/ReactTestApp/ContentView.swift @@ -210,9 +210,9 @@ final class ContentViewController: UITableViewController { if sections.isEmpty { #if targetEnvironment(simulator) - let keyboardShortcut = " (⌃⌘Z)" + let keyboardShortcut = " (⌃⌘Z)" #else - let keyboardShortcut = "" + let keyboardShortcut = "" #endif sections.append(SectionData( items: items, @@ -245,9 +245,9 @@ final class ContentViewController: UITableViewController { return "\(major).\(minor).\(patch)" }() #if USE_FABRIC - let fabric = " (Fabric)" + let fabric = " (Fabric)" #else - let fabric = "" + let fabric = "" #endif return "React Native version: \(version)\(fabric)" } diff --git a/ios/ReactTestApp/ReactInstance.swift b/ios/ReactTestApp/ReactInstance.swift index 5a92d773d..e604fd68e 100644 --- a/ios/ReactTestApp/ReactInstance.swift +++ b/ios/ReactTestApp/ReactInstance.swift @@ -19,18 +19,18 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { private var bundleRoot: String? #if USE_TURBOMODULE - private lazy var turboModuleManagerDelegate = RTATurboModuleManagerDelegate(bridgeDelegate: self) + private lazy var turboModuleManagerDelegate = RTATurboModuleManagerDelegate(bridgeDelegate: self) #endif override init() { #if DEBUG - remoteBundleURL = ReactInstance.jsBundleURL() + remoteBundleURL = ReactInstance.jsBundleURL() #endif super.init() #if USE_TURBOMODULE - RCTEnableTurboModule(true) + RCTEnableTurboModule(true) #endif RCTSetFatalHandler { (error: Error?) in @@ -67,32 +67,32 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { ) #if os(iOS) - NotificationCenter.default.addObserver( - self, - selector: #selector(onRemoteBundleURLReceived(_:)), - name: .didReceiveRemoteBundleURL, - object: nil - ) + NotificationCenter.default.addObserver( + self, + selector: #selector(onRemoteBundleURLReceived(_:)), + name: .didReceiveRemoteBundleURL, + object: nil + ) #else - NSAppleEventManager.shared().setEventHandler( - RCTLinkingManager.self, - andSelector: #selector(RCTLinkingManager.getUrlEventHandler(_:withReplyEvent:)), - forEventClass: AEEventClass(kInternetEventClass), - andEventID: AEEventID(kAEGetURL) - ) + NSAppleEventManager.shared().setEventHandler( + RCTLinkingManager.self, + andSelector: #selector(RCTLinkingManager.getUrlEventHandler(_:withReplyEvent:)), + forEventClass: AEEventClass(kInternetEventClass), + andEventID: AEEventID(kAEGetURL) + ) #endif #if USE_FLIPPER - if let flipper = FlipperClient.shared() { - flipper.add(FlipperKitLayoutPlugin( - rootNode: UIApplication.shared, - with: SKDescriptorMapper(defaults: ()) - )) - flipper.add(FKUserDefaultsPlugin(suiteName: nil)) - flipper.add(FlipperKitReactPlugin()) - flipper.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter())) - flipper.start() - } + if let flipper = FlipperClient.shared() { + flipper.add(FlipperKitLayoutPlugin( + rootNode: UIApplication.shared, + with: SKDescriptorMapper(defaults: ()) + )) + flipper.add(FKUserDefaultsPlugin(suiteName: nil)) + flipper.add(FlipperKitReactPlugin()) + flipper.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter())) + flipper.start() + } #endif } @@ -120,15 +120,15 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { ) #if USE_TURBOMODULE - guard let bridge = RCTBridge(delegate: turboModuleManagerDelegate, launchOptions: nil) else { - assertionFailure("Failed to instantiate RCTBridge with TurboModule") - return - } + guard let bridge = RCTBridge(delegate: turboModuleManagerDelegate, launchOptions: nil) else { + assertionFailure("Failed to instantiate RCTBridge with TurboModule") + return + } #else - guard let bridge = RCTBridge(delegate: self, launchOptions: nil) else { - assertionFailure("Failed to instantiate RCTBridge") - return - } + guard let bridge = RCTBridge(delegate: self, launchOptions: nil) else { + assertionFailure("Failed to instantiate RCTBridge") + return + } #endif // USE_TURBOMODULE surfacePresenterBridgeAdapter = RTACreateSurfacePresenterBridgeAdapter(bridge) @@ -169,9 +169,9 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { private func entryFiles() -> [String] { #if os(iOS) - let extensions = [".ios", ".mobile", ".native", ""] + let extensions = [".ios", ".mobile", ".native", ""] #elseif os(macOS) - let extensions = [".macos", ".native", ""] + let extensions = [".macos", ".native", ""] #endif guard let bundleRoot = bundleRoot else { @@ -217,12 +217,12 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { )) #if os(iOS) && !targetEnvironment(simulator) - devMenu.add(RCTDevMenuItem.buttonItem(withTitle: "Scan QR Code") { - NotificationCenter.default.post( - name: ReactInstance.scanForQRCodeNotification, - object: self - ) - }) + devMenu.add(RCTDevMenuItem.buttonItem(withTitle: "Scan QR Code") { + NotificationCenter.default.post( + name: ReactInstance.scanForQRCodeNotification, + object: self + ) + }) #endif } } @@ -252,9 +252,9 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { } #if os(iOS) - typealias RTAView = UIView +typealias RTAView = UIView #else - typealias RTAView = NSView +typealias RTAView = NSView #endif func createReactRootView(_ reactInstance: ReactInstance) -> (RTAView, String)? { diff --git a/ios/ReactTestApp/SceneDelegate.swift b/ios/ReactTestApp/SceneDelegate.swift index ed50e8b90..1c63cc130 100644 --- a/ios/ReactTestApp/SceneDelegate.swift +++ b/ios/ReactTestApp/SceneDelegate.swift @@ -58,36 +58,36 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { #if !ENABLE_SINGLE_APP_MODE - extension SceneDelegate { - var isRunningTests: Bool { - let environment = ProcessInfo.processInfo.environment - return environment["XCInjectBundleInto"] != nil - } +extension SceneDelegate { + var isRunningTests: Bool { + let environment = ProcessInfo.processInfo.environment + return environment["XCInjectBundleInto"] != nil + } - func scene(_ scene: UIScene, - willConnectTo _: UISceneSession, - options _: UIScene.ConnectionOptions) - { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene - // `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see - // `application:configurationForConnectingSceneSession` instead). - - guard !isRunningTests else { - return - } + func scene(_ scene: UIScene, + willConnectTo _: UISceneSession, + options _: UIScene.ConnectionOptions) + { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene + // `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see + // `application:configurationForConnectingSceneSession` instead). + + guard !isRunningTests else { + return + } - if let windowScene = scene as? UIWindowScene { - let window = UIWindow(windowScene: windowScene) - window.rootViewController = UINavigationController( - rootViewController: ContentViewController(reactInstance: reactInstance) - ) - self.window = window - window.makeKeyAndVisible() - } + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UINavigationController( + rootViewController: ContentViewController(reactInstance: reactInstance) + ) + self.window = window + window.makeKeyAndVisible() } } +} #endif // !ENABLE_SINGLE_APP_MODE @@ -95,33 +95,33 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { #if ENABLE_SINGLE_APP_MODE - extension SceneDelegate { - func scene(_ scene: UIScene, - willConnectTo _: UISceneSession, - options _: UIScene.ConnectionOptions) - { - guard let windowScene = scene as? UIWindowScene else { - assertionFailure("Default scene configuration should have been loaded by now") - return - } +extension SceneDelegate { + func scene(_ scene: UIScene, + willConnectTo _: UISceneSession, + options _: UIScene.ConnectionOptions) + { + guard let windowScene = scene as? UIWindowScene else { + assertionFailure("Default scene configuration should have been loaded by now") + return + } - guard let (rootView, _) = createReactRootView(reactInstance) else { - assertionFailure() - return - } + guard let (rootView, _) = createReactRootView(reactInstance) else { + assertionFailure() + return + } - rootView.backgroundColor = UIColor.systemBackground + rootView.backgroundColor = UIColor.systemBackground - let viewController = UIViewController(nibName: nil, bundle: nil) - viewController.view = rootView + let viewController = UIViewController(nibName: nil, bundle: nil) + viewController.view = rootView - let window = UIWindow(windowScene: windowScene) - window.rootViewController = viewController - self.window = window + let window = UIWindow(windowScene: windowScene) + window.rootViewController = viewController + self.window = window - window.makeKeyAndVisible() - } + window.makeKeyAndVisible() } +} #endif // ENABLE_SINGLE_APP_MODE diff --git a/macos/ReactTestApp/AppDelegate.swift b/macos/ReactTestApp/AppDelegate.swift index 0023a81df..f2db30da7 100644 --- a/macos/ReactTestApp/AppDelegate.swift +++ b/macos/ReactTestApp/AppDelegate.swift @@ -71,184 +71,184 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #if !ENABLE_SINGLE_APP_MODE - extension AppDelegate { - private var isPresenting: Bool { - !(mainWindow?.contentViewController is ViewController) +extension AppDelegate { + private var isPresenting: Bool { + !(mainWindow?.contentViewController is ViewController) + } + + func applicationWillFinishLaunching(_: Notification) { + if Session.shouldRememberLastComponent { + rememberLastComponentMenuItem.state = .on } - func applicationWillFinishLaunching(_: Notification) { - if Session.shouldRememberLastComponent { - rememberLastComponentMenuItem.state = .on - } + showReactMenu() + } - showReactMenu() + func applicationDidFinishLaunching(_: Notification) { + defer { + NotificationCenter.default.post( + name: .ReactTestAppDidInitialize, + object: nil + ) } - func applicationDidFinishLaunching(_: Notification) { - defer { - NotificationCenter.default.post( - name: .ReactTestAppDidInitialize, - object: nil - ) - } - - guard let (manifest, checksum) = Manifest.fromFile() else { - let item = reactMenu.addItem( - withTitle: "Could not load 'app.json'", - action: nil, - keyEquivalent: "" - ) - item.isEnabled = false - return - } + guard let (manifest, checksum) = Manifest.fromFile() else { + let item = reactMenu.addItem( + withTitle: "Could not load 'app.json'", + action: nil, + keyEquivalent: "" + ) + item.isEnabled = false + return + } - mainWindow?.title = manifest.displayName - - let components = manifest.components ?? [] - if components.isEmpty { - NotificationCenter.default.addObserver( - forName: .ReactTestAppDidRegisterApps, - object: nil, - queue: .main, - using: { [weak self] note in - guard let strongSelf = self, - let appKeys = note.userInfo?["appKeys"] as? [String] - else { - return - } - - let components = appKeys.map { Component(appKey: $0) } - strongSelf.onComponentsRegistered(components, enable: true) - if components.count == 1, !strongSelf.isPresenting { - strongSelf.present(components[0]) - } + mainWindow?.title = manifest.displayName + + let components = manifest.components ?? [] + if components.isEmpty { + NotificationCenter.default.addObserver( + forName: .ReactTestAppDidRegisterApps, + object: nil, + queue: .main, + using: { [weak self] note in + guard let strongSelf = self, + let appKeys = note.userInfo?["appKeys"] as? [String] + else { + return } - ) - } - onComponentsRegistered(components, enable: false) + let components = appKeys.map { Component(appKey: $0) } + strongSelf.onComponentsRegistered(components, enable: true) + if components.count == 1, !strongSelf.isPresenting { + strongSelf.present(components[0]) + } + } + ) + } - let bundleRoot = manifest.bundleRoot - DispatchQueue.global(qos: .userInitiated).async { [weak self] in - self?.reactInstance.initReact(bundleRoot: bundleRoot) { - DispatchQueue.main.async { [weak self] in - guard let strongSelf = self, !components.isEmpty else { - return - } + onComponentsRegistered(components, enable: false) - if let index = components.count == 1 ? 0 : Session.lastOpenedComponent(checksum) { - strongSelf.present(components[index]) - } + let bundleRoot = manifest.bundleRoot + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.reactInstance.initReact(bundleRoot: bundleRoot) { + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self, !components.isEmpty else { + return + } - strongSelf.reactMenu.items.forEach { $0.isEnabled = true } - strongSelf.rememberLastComponentMenuItem.isEnabled = components.count > 1 + if let index = components.count == 1 ? 0 : Session.lastOpenedComponent(checksum) { + strongSelf.present(components[index]) } + + strongSelf.reactMenu.items.forEach { $0.isEnabled = true } + strongSelf.rememberLastComponentMenuItem.isEnabled = components.count > 1 } } + } - manifestChecksum = checksum + manifestChecksum = checksum + } + + @objc + private func onComponentSelected(menuItem: NSMenuItem) { + guard let component = menuItem.representedObject as? Component else { + return } - @objc - private func onComponentSelected(menuItem: NSMenuItem) { - guard let component = menuItem.representedObject as? Component else { - return - } + present(component) - present(component) + if let checksum = manifestChecksum { + Session.storeComponent(index: menuItem.tag, checksum: checksum) + } + } - if let checksum = manifestChecksum { - Session.storeComponent(index: menuItem.tag, checksum: checksum) - } + private func onComponentsRegistered(_ components: [Component], enable: Bool) { + removeAllComponentsFromMenu() + components.enumerated().forEach { index, component in + let title = component.displayName ?? component.appKey + let item = reactMenu.addItem( + withTitle: title, + action: #selector(onComponentSelected), + keyEquivalent: index < 9 ? String(index + 1) : "" + ) + item.tag = index + item.keyEquivalentModifierMask = [.shift, .command] + item.isEnabled = enable + item.representedObject = component } - private func onComponentsRegistered(_ components: [Component], enable: Bool) { - removeAllComponentsFromMenu() - components.enumerated().forEach { index, component in - let title = component.displayName ?? component.appKey - let item = reactMenu.addItem( - withTitle: title, - action: #selector(onComponentSelected), - keyEquivalent: index < 9 ? String(index + 1) : "" - ) - item.tag = index - item.keyEquivalentModifierMask = [.shift, .command] - item.isEnabled = enable - item.representedObject = component - } + rememberLastComponentMenuItem.isEnabled = components.count > 1 + } - rememberLastComponentMenuItem.isEnabled = components.count > 1 + private func present(_ component: Component) { + guard let window = mainWindow, + let bridge = reactInstance.bridge + else { + return } - private func present(_ component: Component) { - guard let window = mainWindow, - let bridge = reactInstance.bridge - else { - return - } + let title = component.displayName ?? component.appKey - let title = component.displayName ?? component.appKey + let viewController: NSViewController = { + if let viewController = RTAViewControllerFromString(component.appKey, bridge) { + return viewController + } - let viewController: NSViewController = { - if let viewController = RTAViewControllerFromString(component.appKey, bridge) { - return viewController + let viewController = NSViewController(nibName: nil, bundle: nil) + viewController.title = title + viewController.view = RTACreateReactRootView( + bridge, + component.appKey, + component.initialProperties + ) + return viewController + }() + + switch component.presentationStyle { + case "modal": + let rootView = viewController.view + let modalFrame = NSRect(size: WindowSize.modalSize) + rootView.frame = modalFrame + + var token: NSObjectProtocol? + token = NotificationCenter.default.addObserver( + forName: .RCTContentDidAppear, + object: rootView, + queue: nil, + using: { _ in + #if USE_FABRIC + rootView.frame = modalFrame + #else + (rootView as? RCTRootView)?.contentView.frame = modalFrame + #endif + NotificationCenter.default.removeObserver(token!) } + ) - let viewController = NSViewController(nibName: nil, bundle: nil) - viewController.title = title - viewController.view = RTACreateReactRootView( - bridge, - component.appKey, - component.initialProperties - ) - return viewController - }() - - switch component.presentationStyle { - case "modal": - let rootView = viewController.view - let modalFrame = NSRect(size: WindowSize.modalSize) - rootView.frame = modalFrame - - var token: NSObjectProtocol? - token = NotificationCenter.default.addObserver( - forName: .RCTContentDidAppear, - object: rootView, - queue: nil, - using: { _ in - #if USE_FABRIC - rootView.frame = modalFrame - #else - (rootView as? RCTRootView)?.contentView.frame = modalFrame - #endif - NotificationCenter.default.removeObserver(token!) - } - ) - - window.contentViewController?.presentAsModalWindow(viewController) + window.contentViewController?.presentAsModalWindow(viewController) - default: - window.title = title - let frame = window.contentViewController?.view.frame - viewController.view.frame = frame ?? NSRect(size: WindowSize.defaultSize) - window.contentViewController = viewController - } + default: + window.title = title + let frame = window.contentViewController?.view.frame + viewController.view.frame = frame ?? NSRect(size: WindowSize.defaultSize) + window.contentViewController = viewController } + } - private func removeAllComponentsFromMenu() { - let numberOfItems = reactMenu.numberOfItems - for reverseIndex in 1 ... numberOfItems { - let index = numberOfItems - reverseIndex - guard let item = reactMenu.item(at: index) else { - preconditionFailure() - } - if item.isSeparatorItem == true { - break - } - reactMenu.removeItem(at: index) + private func removeAllComponentsFromMenu() { + let numberOfItems = reactMenu.numberOfItems + for reverseIndex in 1 ... numberOfItems { + let index = numberOfItems - reverseIndex + guard let item = reactMenu.item(at: index) else { + preconditionFailure() } + if item.isSeparatorItem == true { + break + } + reactMenu.removeItem(at: index) } } +} #endif // !ENABLE_SINGLE_APP_MODE @@ -256,40 +256,40 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #if ENABLE_SINGLE_APP_MODE - extension AppDelegate { - func applicationWillFinishLaunching(_: Notification) { - guard let window = mainWindow else { - assertionFailure("Main window should have been instantiated by now") - return - } - - guard let (rootView, title) = createReactRootView(reactInstance) else { - assertionFailure() - return - } +extension AppDelegate { + func applicationWillFinishLaunching(_: Notification) { + guard let window = mainWindow else { + assertionFailure("Main window should have been instantiated by now") + return + } - window.title = title + guard let (rootView, title) = createReactRootView(reactInstance) else { + assertionFailure() + return + } - let frame = window.contentViewController?.view.frame - rootView.frame = frame ?? NSRect(size: WindowSize.defaultSize) - window.contentViewController?.view = rootView + window.title = title - #if DEBUG - if Session.shouldRememberLastComponent { - rememberLastComponentMenuItem.state = .on - } + let frame = window.contentViewController?.view.frame + rootView.frame = frame ?? NSRect(size: WindowSize.defaultSize) + window.contentViewController?.view = rootView - showReactMenu() - #endif // DEBUG + #if DEBUG + if Session.shouldRememberLastComponent { + rememberLastComponentMenuItem.state = .on } - func applicationDidFinishLaunching(_: Notification) { - NotificationCenter.default.post( - name: .ReactTestAppDidInitialize, - object: nil - ) - } + showReactMenu() + #endif // DEBUG + } + + func applicationDidFinishLaunching(_: Notification) { + NotificationCenter.default.post( + name: .ReactTestAppDidInitialize, + object: nil + ) } +} #endif // ENABLE_SINGLE_APP_MODE diff --git a/macos/ReactTestApp/ViewController.swift b/macos/ReactTestApp/ViewController.swift index b467ece2e..b4163779c 100644 --- a/macos/ReactTestApp/ViewController.swift +++ b/macos/ReactTestApp/ViewController.swift @@ -1,6 +1,14 @@ import AppKit final class ViewController: NSViewController { + override var representedObject: Any? { + didSet { + // Update the view, if already loaded. + } + } + + #if !ENABLE_SINGLE_APP_MODE + override func viewDidLoad() { super.viewDidLoad() @@ -25,12 +33,6 @@ final class ViewController: NSViewController { ) } - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. - } - } - override func mouseDown(with event: NSEvent) { NSMenu.popUpReactMenu(with: event, for: view) } @@ -38,8 +40,12 @@ final class ViewController: NSViewController { override func rightMouseDown(with event: NSEvent) { NSMenu.popUpReactMenu(with: event, for: view) } + + #endif // !ENABLE_SINGLE_APP_MODE } +#if !ENABLE_SINGLE_APP_MODE + extension NSMenu { static func popUpReactMenu(with event: NSEvent, for view: NSView) { guard let reactMenu = NSApplication.shared.mainMenu?.item(withTitle: "React")?.submenu else { @@ -81,3 +87,5 @@ final class Label: NSTextView { NSMenu.popUpReactMenu(with: event, for: self) } } + +#endif // !ENABLE_SINGLE_APP_MODE diff --git a/package.json b/package.json index dbab5b6ca..25391879b 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "clean": "git clean -dfqx --exclude=.yarn/cache", "format:c": "clang-format -i $(git ls-files '*.cpp' '*.h' '*.m' '*.mm')", "format:js": "prettier --write $(git ls-files '*.js' '*.yml' 'test/**/*.json')", - "format:swift": "swiftformat --swiftversion 5.5 ios macos", + "format:swift": "swiftformat --swiftversion 5.5 --ifdef no-indent ios macos", "generate:code": "node scripts/generate-manifest.mjs", "generate:schema": "node scripts/generate-schema.mjs", "lint:commit": "git log --format='%s' origin/trunk..HEAD | tail -1 | commitlint",