diff --git a/packages/app/macos/ReactTestApp.xcodeproj/project.pbxproj b/packages/app/macos/ReactTestApp.xcodeproj/project.pbxproj index 0ec2a5a99..9506a527c 100644 --- a/packages/app/macos/ReactTestApp.xcodeproj/project.pbxproj +++ b/packages/app/macos/ReactTestApp.xcodeproj/project.pbxproj @@ -16,7 +16,7 @@ 193EF098247B130700BE8C79 /* ReactInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 193EF097247B130700BE8C79 /* ReactInstance.swift */; }; 1960F339258C97C400AEC7A2 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1960F338258C97C400AEC7A2 /* Session.swift */; }; 196C22652490CBAB00449D3C /* React+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 196C22632490CBAB00449D3C /* React+Compatibility.m */; }; - 199F3E152FF7AA0000270357 /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199F3E142FF7A9FB00270357 /* WindowController.swift */; }; + 1900C0DE2FF9AA0000000002 /* ReactCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1900C0DE2FF9AA0000000001 /* ReactCommands.swift */; }; 19C4C89327710D8500157870 /* Manifest+Embedded.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C4C89227710D8500157870 /* Manifest+Embedded.g.swift */; }; 19E791C024B08E1400FA6468 /* ReactTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E791BF24B08E1400FA6468 /* ReactTestAppTests.swift */; }; 19E791C324B08E4D00FA6468 /* ReactTestAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E791C224B08E4D00FA6468 /* ReactTestAppUITests.swift */; }; @@ -59,7 +59,7 @@ 1960F338258C97C400AEC7A2 /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Session.swift; path = ../Shared/Session.swift; sourceTree = ""; }; 196C22632490CBAB00449D3C /* React+Compatibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "React+Compatibility.m"; path = "../Shared/React+Compatibility.m"; sourceTree = ""; }; 196C22642490CBAB00449D3C /* React+Compatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "React+Compatibility.h"; path = "../Shared/React+Compatibility.h"; sourceTree = ""; }; - 199F3E142FF7A9FB00270357 /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; + 1900C0DE2FF9AA0000000001 /* ReactCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactCommands.swift; sourceTree = ""; }; 19B368BC24B12C24002CCEFF /* ReactTestApp.common.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReactTestApp.common.xcconfig; sourceTree = ""; }; 19B368BD24B12C24002CCEFF /* ReactTestApp.debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReactTestApp.debug.xcconfig; sourceTree = ""; }; 19B368BE24B12C24002CCEFF /* ReactTestApp.release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReactTestApp.release.xcconfig; sourceTree = ""; }; @@ -117,12 +117,12 @@ isa = PBXGroup; children = ( 193EF062247A736200BE8C79 /* AppDelegate.swift */, + 1900C0DE2FF9AA0000000001 /* ReactCommands.swift */, 193EF08E247A799D00BE8C79 /* Manifest.swift */, 19C4C89227710D8500157870 /* Manifest+Embedded.g.swift */, 193EF097247B130700BE8C79 /* ReactInstance.swift */, 1960F338258C97C400AEC7A2 /* Session.swift */, 193EF064247A736200BE8C79 /* ViewController.swift */, - 199F3E142FF7A9FB00270357 /* WindowController.swift */, 193EF094247A84DA00BE8C79 /* ReactTestApp-Bridging-Header.h */, 196C22642490CBAB00449D3C /* React+Compatibility.h */, 196C22632490CBAB00449D3C /* React+Compatibility.m */, @@ -315,10 +315,10 @@ buildActionMask = 2147483647; files = ( 193EF063247A736200BE8C79 /* AppDelegate.swift in Sources */, + 1900C0DE2FF9AA0000000002 /* ReactCommands.swift in Sources */, 193EF08F247A799D00BE8C79 /* Manifest.swift in Sources */, 19C4C89327710D8500157870 /* Manifest+Embedded.g.swift in Sources */, 196C22652490CBAB00449D3C /* React+Compatibility.m in Sources */, - 199F3E152FF7AA0000270357 /* WindowController.swift in Sources */, 193EF098247B130700BE8C79 /* ReactInstance.swift in Sources */, 1960F339258C97C400AEC7A2 /* Session.swift in Sources */, 193EF093247A830200BE8C79 /* UIViewController+ReactTestApp.m in Sources */, diff --git a/packages/app/macos/ReactTestApp/AppDelegate.swift b/packages/app/macos/ReactTestApp/AppDelegate.swift index 8830425a5..74508595a 100644 --- a/packages/app/macos/ReactTestApp/AppDelegate.swift +++ b/packages/app/macos/ReactTestApp/AppDelegate.swift @@ -1,51 +1,44 @@ import Cocoa import ReactTestApp_DevSupport +import SwiftUI -@main -final class AppDelegate: NSObject, NSApplicationDelegate { - static func main() { - let delegate = AppDelegate() - let app = NSApplication.shared - app.delegate = delegate - app.run() - } +// MARK: - App - var reactMenu: NSMenu! - var rememberLastComponentMenuItem: NSMenuItem! +@main +struct ReactTestAppMain: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate - private(set) lazy var reactInstance = ReactInstance() + var body: some Scene { + Window("ReactTestApp", id: AppModel.mainWindowID) { + RootContentView(model: appDelegate.model) + } + .defaultSize(width: 640, height: 480) + .commands { + #if !ENABLE_SINGLE_APP_MODE || DEBUG + ReactCommands(model: appDelegate.model) + #endif + } + } +} - private lazy var windowController = WindowController() - private lazy var mainWindow = windowController.window +// MARK: - App Delegate - private var contentDidAppearToken: NSObjectProtocol? +final class AppDelegate: NSObject, NSApplicationDelegate { + let model = AppModel() func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { true } - /// Builds the main menu and main window programmatically. This replaces - /// what used to be provided by `Main.storyboard`, and must run before the - /// rest of `applicationWillFinishLaunching(_:)` since it relies on - /// `reactMenu`/`rememberLastComponentMenuItem` and `mainWindow` being set. - private func initWindow() { - guard windowController.window != nil else { - fatalError("Failed to create window") - } - - NSApp.mainMenu = makeMainMenu(title: Manifest.load().displayName) - } - func applicationDidFinishLaunching(_: Notification) { - windowController.showWindow(nil) - NSApp.activate() - NotificationCenter.default.post( name: .ReactAppDidFinishLaunching, object: nil ) - initialize() + model.initialize() + + NSApplication.shared.activate(ignoringOtherApps: true) // applicationDidFinishLaunching(_:) } @@ -73,38 +66,53 @@ final class AppDelegate: NSObject, NSApplicationDelegate { { // application(_:didReceiveRemoteNotification:) } +} - // MARK: User interaction +// MARK: - App Model - @objc - func onLoadEmbeddedBundleSelected(_: NSMenuItem) { - reactInstance.remoteBundleURL = nil - } +final class AppModel: ObservableObject { + static let mainWindowID = "MainWindow" - @objc - func onLoadFromDevServerSelected(_: NSMenuItem) { - reactInstance.remoteBundleURL = ReactInstance.jsBundleURL() + @Published var windowTitle: String + @Published var components: [Component] = [] + @Published var componentsEnabled = false + @Published var contentViewController: NSViewController? + @Published var rememberLastComponent: Bool { + didSet { Session.shouldRememberLastComponent = rememberLastComponent } } - @objc - func onRememberLastComponentSelected(_ menuItem: NSMenuItem) { - onRememberLastComponentSelectedInternal(menuItem) - } + private(set) lazy var reactInstance = ReactInstance() - // MARK: Private + private var registerAppsToken: NSObjectProtocol? + private var contentDidAppearToken: NSObjectProtocol? private enum WindowSize { - static let defaultSize = CGSize(width: 640, height: 480) static let modalSize = CGSize(width: 586, height: 326) } - private func showReactMenu() { - guard let mainMenu = reactMenu.supermenu else { - return + init() { + rememberLastComponent = Session.shouldRememberLastComponent + + #if ENABLE_SINGLE_APP_MODE + let manifest = Manifest.load() + if let slug = manifest.singleApp, + let component = manifest.components?.first(where: { $0.slug == slug }) + { + windowTitle = component.displayName ?? component.appKey + } else { + windowTitle = manifest.displayName } + #else + windowTitle = Manifest.load().displayName + #endif + } - let index = mainMenu.indexOfItem(withSubmenu: reactMenu) - mainMenu.item(at: index)?.isHidden = false + func loadEmbeddedBundle() { + reactInstance.remoteBundleURL = nil + } + + func loadFromDevServer() { + reactInstance.remoteBundleURL = ReactInstance.jsBundleURL() } } @@ -112,18 +120,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate { #if !ENABLE_SINGLE_APP_MODE -extension AppDelegate { - private var isPresenting: Bool { - !(mainWindow?.contentViewController is ViewController) - } - +extension AppModel { func initialize() { let manifest = Manifest.load() - mainWindow?.title = manifest.displayName + windowTitle = manifest.displayName - let components = manifest.components ?? [] - if components.isEmpty { - NotificationCenter.default.addObserver( + let appComponents = manifest.components ?? [] + if appComponents.isEmpty { + registerAppsToken = NotificationCenter.default.addObserver( forName: .ReactAppDidRegisterApps, object: nil, queue: .main, @@ -134,96 +138,49 @@ extension AppDelegate { return } - let components = appKeys.map { Component(appKey: $0) } - strongSelf.onComponentsRegistered(components, enable: true) - if components.count == 1, !strongSelf.isPresenting { - strongSelf.present(components[0]) + let registered = appKeys.map { Component(appKey: $0) } + strongSelf.setComponents(registered, enable: true) + if registered.count == 1, strongSelf.contentViewController == nil { + strongSelf.present(registered[0]) } } ) } - onComponentsRegistered(components, enable: false) + setComponents(appComponents, enable: false) let bundleRoot = manifest.bundleRoot // As of 0.74, we can no longer instantiate on a background thread: - // https://github.com/react/react-native/commit/b7025fe1569349d90d26821b2b8de64a8ec9f352 + // https://github.com/facebook/react-native/commit/b7025fe1569349d90d26821b2b8de64a8ec9f352 DispatchQueue.main.async { [weak self] in self?.reactInstance.initReact(bundleRoot: bundleRoot) { DispatchQueue.main.async { [weak self] in - guard let strongSelf = self, !components.isEmpty else { + guard let strongSelf = self, !appComponents.isEmpty else { return } - if let index = components.count == 1 ? 0 : Session.lastOpenedComponent(Manifest.checksum()) { - strongSelf.present(components[index]) + if let index = appComponents.count == 1 ? 0 : Session.lastOpenedComponent(Manifest.checksum()) { + strongSelf.present(appComponents[index]) } - strongSelf.reactMenu.items.forEach { $0.isEnabled = true } - strongSelf.rememberLastComponentMenuItem.isEnabled = components.count > 1 + strongSelf.componentsEnabled = true } } } } - func applicationWillFinishLaunching(_: Notification) { - initWindow() - - // applicationWillFinishLaunching(_:) [ENABLE_SINGLE_APP_MODE=0] - - if Session.shouldRememberLastComponent { - rememberLastComponentMenuItem.state = .on - } - - showReactMenu() - } - - @objc - private func onComponentSelected(menuItem: NSMenuItem) { - guard let component = menuItem.representedObject as? Component else { - return - } - + func selectComponent(_ component: Component, at index: Int) { present(component) - - Session.storeComponent(index: menuItem.tag, checksum: Manifest.checksum()) - } - - private func onComponentsRegistered(_ components: [Component], enable: Bool) { - removeAllComponentsFromMenu() - for (index, component) in components.enumerated() { - 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 + Session.storeComponent(index: index, checksum: Manifest.checksum()) } - private func onRememberLastComponentSelectedInternal(_ menuItem: NSMenuItem) { - switch menuItem.state { - case .mixed, .on: - Session.shouldRememberLastComponent = false - menuItem.state = .off - case .off: - Session.shouldRememberLastComponent = true - menuItem.state = .on - default: - assertionFailure() - } + private func setComponents(_ newComponents: [Component], enable: Bool) { + components = newComponents + componentsEnabled = enable } private func present(_ component: Component) { - guard let window = mainWindow, - let host = reactInstance.host - else { + guard let host = reactInstance.host else { return } @@ -245,48 +202,35 @@ extension AppDelegate { switch component.presentationStyle { case "modal": - let rootView = viewController.view - let modalFrame = NSRect(size: WindowSize.modalSize) - rootView.frame = modalFrame - - contentDidAppearToken = NotificationCenter.default.addObserver( - forName: .RCTContentDidAppear, - object: rootView, - queue: nil, - using: { [weak self] _ in - #if USE_FABRIC - rootView.frame = modalFrame - #else - (rootView as? RCTRootView)?.contentView.frame = modalFrame - #endif - if let token = self?.contentDidAppearToken { - NotificationCenter.default.removeObserver(token) - } - } - ) - - window.contentViewController?.presentAsModalWindow(viewController) - + presentModal(viewController) default: - window.title = title - let frame = window.contentViewController?.view.frame - viewController.view.frame = frame ?? NSRect(size: WindowSize.defaultSize) - window.contentViewController = viewController + windowTitle = title + 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 + private func presentModal(_ viewController: NSViewController) { + let rootView = viewController.view + let modalFrame = NSRect(size: WindowSize.modalSize) + rootView.frame = modalFrame + + contentDidAppearToken = NotificationCenter.default.addObserver( + forName: .RCTContentDidAppear, + object: rootView, + queue: nil, + using: { [weak self] _ in + #if USE_FABRIC + rootView.frame = modalFrame + #else + (rootView as? RCTRootView)?.contentView.frame = modalFrame + #endif + if let token = self?.contentDidAppearToken { + NotificationCenter.default.removeObserver(token) + } } - reactMenu.removeItem(at: index) - } + ) + + NSApp.keyWindow?.contentViewController?.presentAsModalWindow(viewController) } } @@ -296,36 +240,10 @@ extension AppDelegate { #if ENABLE_SINGLE_APP_MODE -extension AppDelegate { +extension AppModel { func initialize() {} - func applicationWillFinishLaunching(_: Notification) { - initWindow() - - guard let window = mainWindow else { - assertionFailure("Main window should have been instantiated by now") - return - } - - guard let (rootView, title) = createReactRootView(reactInstance) else { - assertionFailure() - return - } - - window.title = title - - let frame = window.contentViewController?.view.frame - rootView.frame = frame ?? NSRect(size: WindowSize.defaultSize) - window.contentViewController?.view = rootView - - // applicationWillFinishLaunching(_:) [ENABLE_SINGLE_APP_MODE=1] - - #if DEBUG - showReactMenu() - #endif // DEBUG - } - - private func onRememberLastComponentSelectedInternal(_: NSMenuItem) {} + func selectComponent(_: Component, at _: Int) {} } #endif // ENABLE_SINGLE_APP_MODE diff --git a/packages/app/macos/ReactTestApp/ReactCommands.swift b/packages/app/macos/ReactTestApp/ReactCommands.swift new file mode 100644 index 000000000..607093d34 --- /dev/null +++ b/packages/app/macos/ReactTestApp/ReactCommands.swift @@ -0,0 +1,41 @@ +import SwiftUI + +struct ReactCommands: Commands { + @ObservedObject var model: AppModel + + var body: some Commands { + CommandMenu("React") { + Button("Load Embedded JS Bundle") { + model.loadEmbeddedBundle() + } + + Button("Load From Dev Server") { + model.loadFromDevServer() + } + + Toggle("Remember Last Opened Component", isOn: $model.rememberLastComponent) + .disabled(model.components.count <= 1) + + Divider() + + ForEach(Array(model.components.enumerated()), id: \.offset) { index, component in + Button(component.displayName ?? component.appKey) { + model.selectComponent(component, at: index) + } + .keyboardShortcut(shortcut(for: index)) + .disabled(!model.componentsEnabled) + } + } + } + + private func shortcut(for index: Int) -> KeyboardShortcut? { + guard index < 9 else { + return nil + } + + return KeyboardShortcut( + KeyEquivalent(Character("\(index + 1)")), + modifiers: [.command, .shift] + ) + } +} diff --git a/packages/app/macos/ReactTestApp/ViewController.swift b/packages/app/macos/ReactTestApp/ViewController.swift index b4163779c..ee734254f 100644 --- a/packages/app/macos/ReactTestApp/ViewController.swift +++ b/packages/app/macos/ReactTestApp/ViewController.swift @@ -1,53 +1,83 @@ -import AppKit +import SwiftUI -final class ViewController: NSViewController { - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. +struct RootContentView: View { + @ObservedObject var model: AppModel + + var body: some View { + content + .navigationTitle(model.windowTitle) + } + + @ViewBuilder + private var content: some View { + #if ENABLE_SINGLE_APP_MODE + SingleAppRootView(reactInstance: model.reactInstance) + #else + if let viewController = model.contentViewController { + HostedViewController(viewController: viewController) + .id(ObjectIdentifier(viewController)) + } else { + ReactMenuPlaceholderView() } + #endif + } +} + +// MARK: - Multi-app content + +#if !ENABLE_SINGLE_APP_MODE + +private struct HostedViewController: NSViewControllerRepresentable { + let viewController: NSViewController + + func makeNSViewController(context: Context) -> NSViewController { + viewController + } + + func updateNSViewController(_: NSViewController, context: Context) {} +} + +private struct ReactMenuPlaceholderView: NSViewRepresentable { + func makeNSView(context: Context) -> NSView { + PlaceholderView(frame: .zero) + } + + func updateNSView(_: NSView, context: Context) {} +} + +private final class PlaceholderView: NSView { + private let label = Label( + text: "Click anywhere to get started or open the React menu in the menu bar" + ) + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + addSubview(label) + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + label.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) } - #if !ENABLE_SINGLE_APP_MODE - - override func viewDidLoad() { - super.viewDidLoad() - - let label = Label(text: "Click anywhere to get started or open the React menu in the menu bar") - view.addSubview(label) - - NSLayoutConstraint.activate( - NSLayoutConstraint.constraints( - withVisualFormat: "V:|-[label]-|", - options: [], - metrics: nil, - views: ["label": label] - ) - ) - NSLayoutConstraint.activate( - NSLayoutConstraint.constraints( - withVisualFormat: "H:|-[label]-|", - options: [], - metrics: nil, - views: ["label": label] - ) - ) + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") } override func mouseDown(with event: NSEvent) { - NSMenu.popUpReactMenu(with: event, for: view) + NSMenu.popUpReactMenu(with: event, for: self) } override func rightMouseDown(with event: NSEvent) { - NSMenu.popUpReactMenu(with: event, for: view) + NSMenu.popUpReactMenu(with: event, for: self) } - - #endif // !ENABLE_SINGLE_APP_MODE } -#if !ENABLE_SINGLE_APP_MODE - extension NSMenu { static func popUpReactMenu(with event: NSEvent, for view: NSView) { + // SwiftUI's "React" `CommandMenu` is backed by a real `NSMenu`. guard let reactMenu = NSApplication.shared.mainMenu?.item(withTitle: "React")?.submenu else { return } @@ -89,3 +119,32 @@ final class Label: NSTextView { } #endif // !ENABLE_SINGLE_APP_MODE + +// MARK: - Single-app content + +#if ENABLE_SINGLE_APP_MODE + +private struct SingleAppRootView: NSViewRepresentable { + let reactInstance: ReactInstance + + func makeNSView(context: Context) -> NSView { + let container = NSView() + guard let (rootView, _) = createReactRootView(reactInstance) else { + return container + } + + rootView.translatesAutoresizingMaskIntoConstraints = false + container.addSubview(rootView) + NSLayoutConstraint.activate([ + rootView.leadingAnchor.constraint(equalTo: container.leadingAnchor), + rootView.trailingAnchor.constraint(equalTo: container.trailingAnchor), + rootView.topAnchor.constraint(equalTo: container.topAnchor), + rootView.bottomAnchor.constraint(equalTo: container.bottomAnchor), + ]) + return container + } + + func updateNSView(_: NSView, context: Context) {} +} + +#endif // ENABLE_SINGLE_APP_MODE diff --git a/packages/app/macos/ReactTestApp/WindowController.swift b/packages/app/macos/ReactTestApp/WindowController.swift deleted file mode 100644 index deb8ff78d..000000000 --- a/packages/app/macos/ReactTestApp/WindowController.swift +++ /dev/null @@ -1,162 +0,0 @@ -import AppKit - -final class WindowController: NSWindowController { - private static let identifier = "MainWindow" - - init() { - let viewController = ViewController() - let window = NSWindow(contentViewController: viewController) - window.identifier = NSUserInterfaceItemIdentifier(WindowController.identifier) - window.setFrameAutosaveName(WindowController.identifier) - - super.init(window: window) - - windowFrameAutosaveName = WindowController.identifier - contentViewController = viewController - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private extension NSMenu { - convenience init(title: String, items: [NSMenuItem]) { - self.init(title: title) - self.items = items - } -} - -private extension NSMenuItem { - convenience init(title: String, - action: Selector? = nil, - target: AnyObject? = nil, - keyEquivalent: String = "", - keyEquivalentModifierMask: NSEvent.ModifierFlags = [.command]) - { - self.init(title: title, action: action, keyEquivalent: keyEquivalent) - self.target = target - if !keyEquivalent.isEmpty { - self.keyEquivalentModifierMask = keyEquivalentModifierMask - } - } - - convenience init(submenu: String, items: [NSMenuItem]) { - self.init(title: submenu) - self.submenu = NSMenu(title: submenu, items: items) - } - - convenience init(submenu: NSMenu) { - self.init(title: submenu.title) - self.submenu = submenu - } -} - -extension AppDelegate { - func makeMainMenu(title: String) -> NSMenu { - NSMenu(title: "Main Menu", items: [ - makeAppMenuItem(title: title), - makeReactMenuItem(), - makeEditMenuItem(), - makeViewMenuItem(), - makeWindowMenuItem(), - makeHelpMenuItem(title: title), - ]) - } - - private func makeAppMenuItem(title: String) -> NSMenuItem { - let servicesMenu = NSMenu(title: "Services") - NSApp.servicesMenu = servicesMenu - - return NSMenuItem(submenu: title, items: [ - NSMenuItem(title: "About \(title)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:))), - .separator(), - NSMenuItem(title: "Preferences…", keyEquivalent: ","), - .separator(), - NSMenuItem(submenu: servicesMenu), - .separator(), - NSMenuItem(title: "Hide \(title)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h"), - NSMenuItem( - title: "Hide Others", - action: #selector(NSApplication.hideOtherApplications(_:)), - keyEquivalent: "h", - keyEquivalentModifierMask: [.option, .command] - ), - NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:))), - .separator(), - NSMenuItem(title: "Quit \(title)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"), - ]) - } - - private func makeEditMenuItem() -> NSMenuItem { - NSMenuItem(submenu: "Edit", items: [ - NSMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z"), - NSMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z"), - .separator(), - NSMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"), - NSMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"), - NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"), - NSMenuItem(title: "Delete", action: #selector(NSText.delete(_:))), - NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"), - ]) - } - - private func makeViewMenuItem() -> NSMenuItem { - NSMenuItem(submenu: "View", items: []) - } - - private func makeReactMenuItem() -> NSMenuItem { - let rememberLastComponentMenuItem = NSMenuItem( - title: "Remember Last Opened Component", - action: #selector(onRememberLastComponentSelected(_:)), - target: self - ) - rememberLastComponentMenuItem.isEnabled = false - self.rememberLastComponentMenuItem = rememberLastComponentMenuItem - - let reactMenu = NSMenu(title: "React", items: [ - NSMenuItem( - title: "Load Embedded JS Bundle", - action: #selector(onLoadEmbeddedBundleSelected(_:)), - target: self - ), - NSMenuItem( - title: "Load From Dev Server", - action: #selector(onLoadFromDevServerSelected(_:)), - target: self - ), - rememberLastComponentMenuItem, - .separator(), - ]) - reactMenu.autoenablesItems = false - self.reactMenu = reactMenu - - let reactMenuItem = NSMenuItem(submenu: reactMenu) - reactMenuItem.isHidden = true - return reactMenuItem - } - - private func makeWindowMenuItem() -> NSMenuItem { - let windowMenu = NSMenu(title: "Window", items: [ - NSMenuItem(title: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m"), - NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:))), - .separator(), - NSMenuItem(title: "Bring All to Front", action: #selector(NSApplication.arrangeInFront(_:))), - ]) - - NSApp.windowsMenu = windowMenu - - return NSMenuItem(submenu: windowMenu) - } - - private func makeHelpMenuItem(title: String) -> NSMenuItem { - let helpMenu = NSMenu(title: "Help", items: [ - NSMenuItem(title: "\(title) Help", action: #selector(NSApplication.showHelp(_:)), keyEquivalent: "?"), - ]) - - NSApp.helpMenu = helpMenu - - return NSMenuItem(submenu: helpMenu) - } -} diff --git a/packages/app/test/pack.test.mts b/packages/app/test/pack.test.mts index 55d01c142..bf648a183 100644 --- a/packages/app/test/pack.test.mts +++ b/packages/app/test/pack.test.mts @@ -163,11 +163,11 @@ describe("npm pack", () => { "macos/ReactTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json", "macos/ReactTestApp/Assets.xcassets/Contents.json", "macos/ReactTestApp/Info.plist", + "macos/ReactTestApp/ReactCommands.swift", "macos/ReactTestApp/ReactTestApp.common.xcconfig", "macos/ReactTestApp/ReactTestApp.debug.xcconfig", "macos/ReactTestApp/ReactTestApp.release.xcconfig", "macos/ReactTestApp/ViewController.swift", - "macos/ReactTestApp/WindowController.swift", "macos/ReactTestAppTests/Info.plist", "macos/ReactTestAppTests/ReactTestAppTests.swift", "macos/ReactTestAppUITests/Info.plist", diff --git a/packages/example-macos/macos/Podfile.lock b/packages/example-macos/macos/Podfile.lock index cfab15c64..b8ac06534 100644 --- a/packages/example-macos/macos/Podfile.lock +++ b/packages/example-macos/macos/Podfile.lock @@ -2292,7 +2292,7 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) - - RNWWebStorage (0.4.5): + - RNWWebStorage (0.4.6): - boost - DoubleConversion - fast_float @@ -2641,10 +2641,10 @@ SPEC CHECKSUMS: ReactNativeHost: 8680b5bbc3afdbfeccc206ed0f3230d7c3df691b ReactTestApp-DevSupport: 4d0b86847c5f7fffa0f09479443be7a2420d19a4 ReactTestApp-Resources: 71a3155bca10819738469de1f161410f43528209 - RNWWebStorage: 8ec03f8288c1e212518f662f44ec223603bbe74c + RNWWebStorage: 8bc155b048a2d1ee1db5f63b1b9c5705434d48db SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 9f9bacf26f441fa32d25c32e5583ff72f6d2806d PODFILE CHECKSUM: 1bbe1c112a99af9746e5ca07f8bc40c7aa7a6eb5 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2