diff --git a/.gitignore b/.gitignore index 7c11bae..94dd65c 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,7 @@ msbuild.binlog .claude/* -# Windows reserved device names -nul \ No newline at end of file +# Windows +nul +ReactNativeWindows.dsc.yaml +WindowsDevTools.dsc.yaml diff --git a/README.md b/README.md index c08aff0..3a4b572 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,15 @@ npm run macos-release #### System Requirements -First, install the system requirements for React Native Windows: https://microsoft.github.io/react-native-windows/docs/rnw-dependencies +First, install the system requirements for React Native Windows by running the following in an elevated Powershell session: + +```powershell +.\bin\windows-setup.ps1 +``` + +This should install everything for you. + +**Alternative**: Follow the official installation steps (results vary): https://microsoft.github.io/react-native-windows/docs/rnw-dependencies **Alternative**: If you experience issues with the official `rnw-dependencies.ps1` script, consider using Josh Yoes' improved setup process: https://github.com/joshuayoes/ReactNativeWindowsSandbox @@ -60,6 +68,26 @@ npm run windows npm run windows-release ``` +**Troubleshooting**: If you run into a build error with ctype.h or ROSLYNCODETASKFACTORYCSHARPCOMPILER, try making a symlink like this (it's weird, I know): + +```powershell +mklink /D "C:\Program Files\Windows Kits\10\lib" "C:\Program Files (x86)\Windows Kits\10\lib" +``` + +Somehow, `C:\Program Files\` instead of `C:\Program Files (x86)\` is being used, which breaks things. This makes a symlink to fix that. + +You may also need to add this path to the `reactotron.vcxproj`: + +``` + + + + $(ProjectDir)..\..\app;%(AdditionalIncludeDirectories);C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt + + + +``` + ### Cross-Platform Native Development Both platforms use unified commands for native module development: diff --git a/app/app.tsx b/app/app.tsx index c8cae8d..d6d6846 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -1,25 +1,17 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * - * @format - */ -import { DevSettings, NativeModules, StatusBar, View, type ViewStyle } from "react-native" +import { StatusBar, View, type ViewStyle } from "react-native" import { connectToServer } from "./state/connectToServer" import { useTheme, themed } from "./theme/theme" -import { useEffect, useMemo, useState } from "react" +import { useEffect } from "react" import { TimelineScreen } from "./screens/TimelineScreen" -import { useMenuItem } from "./utils/useMenuItem" import { Titlebar } from "./components/Titlebar/Titlebar" import { Sidebar } from "./components/Sidebar/Sidebar" -import { useSidebar } from "./state/useSidebar" -import { useGlobal, withGlobal } from "./state/useGlobal" +import { useGlobal } from "./state/useGlobal" import { MenuItemId } from "./components/Sidebar/SidebarMenu" import { HelpScreen } from "./screens/HelpScreen" -import { TimelineItem } from "./types" import { PortalHost } from "./components/Portal" import { StateScreen } from "./screens/StateScreen" -import { AboutModal } from "./components/AboutModal" +import { ShortcutsProvider } from "./contexts/ShortcutsContext" +import { SystemMenu } from "./components/SystemMenu" import { CustomCommandsScreen } from "./screens/CustomCommandsScreen" if (__DEV__) { @@ -30,96 +22,7 @@ if (__DEV__) { function App(): React.JSX.Element { const { colors } = useTheme() - const { toggleSidebar } = useSidebar() - const [activeItem, setActiveItem] = useGlobal("sidebar-active-item", "logs") - const [, setTimelineItems] = withGlobal("timelineItems", []) - const [aboutVisible, setAboutVisible] = useState(false) - - const menuConfig = useMemo( - () => ({ - remove: ["File", "Edit", "Format", "Reactotron > About Reactotron"], - items: { - Reactotron: [ - { - label: "About Reactotron", - position: 0, - action: () => setAboutVisible(true), - }, - ], - View: [ - { - label: "Toggle Sidebar", - shortcut: "cmd+b", - action: toggleSidebar, - }, - { - label: "Logs Tab", - shortcut: "cmd+1", - action: () => setActiveItem("logs"), - }, - { - label: "Network Tab", - shortcut: "cmd+2", - action: () => setActiveItem("network"), - }, - { - label: "Performance Tab", - shortcut: "cmd+3", - action: () => setActiveItem("performance"), - }, - { - label: "Plugins Tab", - shortcut: "cmd+4", - action: () => setActiveItem("plugins"), - }, - { - label: "Custom Commands Tab", - shortcut: "cmd+5", - action: () => setActiveItem("customCommands"), - }, - { - label: "Help Tab", - shortcut: "cmd+6", - action: () => setActiveItem("help"), - }, - ...(__DEV__ - ? [ - { - label: "Toggle Dev Menu", - shortcut: "cmd+shift+d", - action: () => NativeModules.DevMenu.show(), - }, - ] - : []), - ], - Window: [ - { - label: "Reload", - shortcut: "cmd+shift+r", - action: () => DevSettings.reload(), - }, - ], - Tools: [ - { - label: "Clear Timeline Items", - shortcut: "cmd+k", - action: () => setTimelineItems([]), - }, - ], - }, - }), - [toggleSidebar, setActiveItem], - ) - - useMenuItem(menuConfig) - - setTimeout(() => { - fetch("https://www.google.com") - .then((res) => res.text()) - .then((text) => { - console.tron.log("text", text) - }) - }, 1000) + const [activeItem] = useGlobal("sidebar-active-item", "logs") // Connect to the server when the app mounts. // This will update global state with the server's state @@ -140,16 +43,19 @@ function App(): React.JSX.Element { } return ( - - - - - - {renderActiveItem()} - - - setAboutVisible(false)} /> - + + + + + + + + {renderActiveItem()} + + + + + ) } diff --git a/app/components/Menu/MenuDropdown.tsx b/app/components/Menu/MenuDropdown.tsx new file mode 100644 index 0000000..0f1a9d0 --- /dev/null +++ b/app/components/Menu/MenuDropdown.tsx @@ -0,0 +1,102 @@ +import { View, type ViewStyle } from "react-native" +import { useRef, useMemo, memo } from "react" +import { themed } from "../../theme/theme" +import { Portal } from "../Portal" +import { MenuDropdownItem } from "./MenuDropdownItem" +import { useSubmenuState } from "./useSubmenuState" +import { menuSettings } from "./menuSettings" +import { type Position, type DropdownMenuItem, type MenuItem, MENU_SEPARATOR } from "./types" +import { getUUID } from "../../utils/random/getUUID" +import { Separator } from "../Separator" + +interface MenuDropdownProps { + items: (DropdownMenuItem | typeof MENU_SEPARATOR)[] + position: Position + onItemPress: (item: MenuItem) => void + isSubmenu?: boolean +} + +const MenuDropdownComponent = ({ items, position, onItemPress, isSubmenu }: MenuDropdownProps) => { + const portalName = useRef(`${isSubmenu ? "submenu" : "dropdown"}-${getUUID()}`).current + const { openSubmenu, submenuPosition, handleItemHover } = useSubmenuState(position) + + const isSeparator = (item: MenuItem | typeof MENU_SEPARATOR): item is typeof MENU_SEPARATOR => { + return item === MENU_SEPARATOR + } + + // Find the submenu item if one is open + const submenuItem = openSubmenu + ? (items.find((item) => !isSeparator(item) && item.label === openSubmenu) as + | DropdownMenuItem + | undefined) + : undefined + + const dropdownContent = useMemo( + () => ( + + {items.map((item, index) => { + if (isSeparator(item)) return + + return ( + + ) + })} + + ), + [items, isSubmenu, position.x, position.y, onItemPress, handleItemHover], + ) + + return ( + <> + {dropdownContent} + {/* Render submenu */} + {submenuItem?.submenu && ( + + )} + + ) +} + +export const MenuDropdown = memo(MenuDropdownComponent) + +const $dropdown = themed(({ colors, spacing }) => ({ + position: "absolute", + backgroundColor: colors.cardBackground, + borderColor: colors.keyline, + borderWidth: 1, + borderRadius: 4, + minWidth: menuSettings.dropdownMinWidth, + paddingVertical: spacing.xs, + zIndex: menuSettings.zIndex.dropdown, +})) + +const $submenuDropdown = themed(({ colors, spacing }) => ({ + position: "absolute", + backgroundColor: colors.cardBackground, + borderColor: colors.keyline, + borderWidth: 1, + borderRadius: 4, + minWidth: menuSettings.submenuMinWidth, + paddingVertical: spacing.xs, + zIndex: menuSettings.zIndex.submenu, +})) + +const $menuPosition = (position: Position, isSubmenu: boolean | undefined) => ({ + left: position.x, + top: position.y, + zIndex: isSubmenu ? 10001 : 10000, +}) diff --git a/app/components/Menu/MenuDropdownItem.tsx b/app/components/Menu/MenuDropdownItem.tsx new file mode 100644 index 0000000..e804b5b --- /dev/null +++ b/app/components/Menu/MenuDropdownItem.tsx @@ -0,0 +1,131 @@ +import { Pressable, Text, View, type ViewStyle, type TextStyle } from "react-native" +import { useState, useRef, memo, useCallback } from "react" +import { themed } from "../../theme/theme" +import { menuSettings } from "./menuSettings" +import type { MenuItem } from "./types" + +interface MenuDropdownItemProps { + item: MenuItem + index: number + onItemPress: (item: MenuItem) => void + onItemHover: (itemLabel: string, index: number, hasSubmenu: boolean) => void +} + +const MenuDropdownItemComponent = ({ + item, + index, + onItemPress, + onItemHover, +}: MenuDropdownItemProps) => { + const [hoveredItem, setHoveredItem] = useState(null) + const hoverTimeoutRef = useRef(null) + const enabled = item.enabled !== false + + const handleHoverIn = useCallback(() => { + // Clear any pending hover clear + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current) + hoverTimeoutRef.current = null + } + setHoveredItem(item.label) + const hasSubmenu = !!item.submenu + onItemHover(item.label, index, hasSubmenu) + }, [item.label, item.submenu, index, onItemHover]) + + const handleHoverOut = useCallback(() => { + // Use a small timeout to prevent flickering between items + hoverTimeoutRef.current = setTimeout(() => { + setHoveredItem((current) => (current === item.label ? null : current)) + }, 10) + }, [item.label]) + + const handlePress = useCallback(() => { + if (!item.action || !enabled) return + item.action() + onItemPress(item) + }, [item, onItemPress]) + + return ( + [ + $dropdownItem(), + (pressed || hoveredItem === item.label) && enabled && $dropdownItemHovered(), + !enabled && $dropdownItemDisabled, + ]} + > + + {item.label} + + + {item.shortcut && ( + + {formatShortcut(item.shortcut.windows || "")} + + )} + {item.submenu && ( + + )} + + + ) +} + +export const MenuDropdownItem = memo(MenuDropdownItemComponent) + +function formatShortcut(shortcut: string): string { + if (!shortcut) return "" + return shortcut + .replace(/cmd/gi, "Ctrl") + .replace(/shift/gi, "Shift") + .split("+") + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join("+") +} + +const $dropdownItem = themed(({ spacing }) => ({ + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: spacing.sm, + paddingVertical: spacing.xs, + borderRadius: 4, + minHeight: menuSettings.itemMinHeight, +})) + +const $dropdownItemHovered = themed(({ colors }) => ({ + backgroundColor: colors.neutralVery, +})) + +const $dropdownItemDisabled = { + opacity: 0.5, +} + +const $dropdownItemText = themed(({ colors, typography }) => ({ + color: colors.mainText, + fontSize: typography.caption, +})) + +const $dropdownItemTextDisabled = themed((theme) => ({ + color: theme.colors.neutral, +})) + +const $shortcut = themed(({ colors, typography, spacing }) => ({ + color: colors.neutral, + fontSize: typography.small, + marginLeft: spacing.md, +})) + +const $submenuArrow = themed(({ colors, typography, spacing }) => ({ + color: colors.neutral, + fontSize: typography.small, + marginLeft: spacing.sm, +})) + +const $rightContent: ViewStyle = { + flexDirection: "row", + alignItems: "center", +} diff --git a/app/components/Menu/MenuOverlay.tsx b/app/components/Menu/MenuOverlay.tsx new file mode 100644 index 0000000..cf2b967 --- /dev/null +++ b/app/components/Menu/MenuOverlay.tsx @@ -0,0 +1,46 @@ +import { Pressable, type ViewStyle } from "react-native" +import { Portal } from "../Portal" +import { menuSettings } from "./menuSettings" + +interface MenuOverlayProps { + onPress: () => void + portalName?: string + style?: ViewStyle + excludeArea?: { + top?: number + left?: number + right?: number + bottom?: number + } +} + +export const MenuOverlay = ({ + onPress, + portalName = "menu-overlay", + style, + excludeArea, +}: MenuOverlayProps) => { + return ( + + + + ) +} + +interface OverlayStyleArgs { + excludeArea?: { top?: number; left?: number; right?: number; bottom?: number } + style?: ViewStyle +} + +const overlayStyle: (args: OverlayStyleArgs) => ViewStyle = ({ + excludeArea, + style, +}: OverlayStyleArgs) => ({ + position: "absolute", + top: excludeArea?.top ?? 0, + left: excludeArea?.left ?? 0, + right: excludeArea?.right ?? 0, + bottom: excludeArea?.bottom ?? 0, + zIndex: menuSettings.zIndex.menuOverlay, + ...style, +}) diff --git a/app/components/Menu/menuSettings.ts b/app/components/Menu/menuSettings.ts new file mode 100644 index 0000000..abe0a90 --- /dev/null +++ b/app/components/Menu/menuSettings.ts @@ -0,0 +1,13 @@ +export const menuSettings = { + dropdownMinWidth: 200, + submenuMinWidth: 150, + itemMinHeight: 28, + itemHeight: 32, + submenuOffsetX: 200, + submenuOffsetY: -5, + zIndex: { + menuOverlay: 9999, + dropdown: 10000, + submenu: 10001, + }, +} as const diff --git a/app/components/Menu/types.ts b/app/components/Menu/types.ts new file mode 100644 index 0000000..0b02bed --- /dev/null +++ b/app/components/Menu/types.ts @@ -0,0 +1,21 @@ +import { PlatformShortcut } from "app/utils/useSystemMenu/types" + +export interface Position { + x: number + y: number +} + +// Generic menu item interface for UI components +export interface MenuItem { + label: string + shortcut?: PlatformShortcut + enabled?: boolean + action?: () => void + submenu?: (MenuItem | typeof MENU_SEPARATOR)[] +} + +// Type alias for dropdown menu items (same as MenuItem) +export type DropdownMenuItem = MenuItem + +// Menu separator constant +export const MENU_SEPARATOR = "menu-item-separator" as const diff --git a/app/components/Menu/useMenuPositioning.ts b/app/components/Menu/useMenuPositioning.ts new file mode 100644 index 0000000..150c5ff --- /dev/null +++ b/app/components/Menu/useMenuPositioning.ts @@ -0,0 +1,54 @@ +import { useCallback } from "react" +import { menuSettings } from "./menuSettings" +import type { Position } from "./types" + +export interface PositioningStrategy { + calculateSubmenuPosition: ( + basePosition: Position, + itemIndex: number, + parentWidth?: number, + ) => Position + calculateContextMenuPosition?: ( + clickPosition: Position, + menuSize?: { width: number; height: number }, + screenSize?: { width: number; height: number }, + ) => Position +} + +const defaultStrategy: PositioningStrategy = { + calculateSubmenuPosition: ( + basePosition, + itemIndex, + parentWidth = menuSettings.submenuOffsetX, + ) => ({ + x: basePosition.x + parentWidth, + y: basePosition.y + itemIndex * menuSettings.itemHeight + menuSettings.submenuOffsetY, + }), + + calculateContextMenuPosition: (clickPosition: Position) => { + // Basic positioning - can be enhanced for screen edge detection + return { + x: clickPosition.x, + y: clickPosition.y, + } + }, +} + +export const useMenuPositioning = (strategy: PositioningStrategy = defaultStrategy) => { + const calculateSubmenuPosition = useCallback( + (basePosition: Position, itemIndex: number, parentWidth?: number) => + strategy.calculateSubmenuPosition(basePosition, itemIndex, parentWidth), + [strategy], + ) + + const calculateContextMenuPosition = useCallback( + (clickPosition: Position) => + strategy.calculateContextMenuPosition?.(clickPosition) ?? clickPosition, + [strategy], + ) + + return { + calculateSubmenuPosition, + calculateContextMenuPosition, + } +} diff --git a/app/components/Menu/useSubmenuState.ts b/app/components/Menu/useSubmenuState.ts new file mode 100644 index 0000000..4cb5778 --- /dev/null +++ b/app/components/Menu/useSubmenuState.ts @@ -0,0 +1,43 @@ +import { useState, useCallback } from "react" +import { menuSettings } from "./menuSettings" +import { type Position } from "./types" + +export const useSubmenuState = (basePosition: Position) => { + const [openSubmenu, setOpenSubmenu] = useState(null) + const [submenuPosition, setSubmenuPosition] = useState({ x: 0, y: 0 }) + + const openSubmenuAt = useCallback( + (itemLabel: string, index: number) => { + setOpenSubmenu(itemLabel) + setSubmenuPosition({ + x: basePosition.x + menuSettings.submenuOffsetX, + y: basePosition.y + index * menuSettings.itemHeight + menuSettings.submenuOffsetY, + }) + }, + [basePosition.x, basePosition.y], + ) + + const closeSubmenu = useCallback(() => { + setOpenSubmenu(null) + }, []) + + const handleItemHover = useCallback( + (itemLabel: string, index: number, hasSubmenu: boolean) => { + if (hasSubmenu) { + openSubmenuAt(itemLabel, index) + } else { + if (openSubmenu) { + closeSubmenu() + } + } + }, + [openSubmenu, openSubmenuAt, closeSubmenu], + ) + + return { + openSubmenu, + submenuPosition, + handleItemHover, + closeSubmenu, + } +} diff --git a/app/components/SystemMenu.tsx b/app/components/SystemMenu.tsx new file mode 100644 index 0000000..b2b0bae --- /dev/null +++ b/app/components/SystemMenu.tsx @@ -0,0 +1,105 @@ +import { useMemo, useState } from "react" +import { DevSettings, NativeModules } from "react-native" +import { useSidebar } from "../state/useSidebar" +import { useGlobal, withGlobal } from "../state/useGlobal" +import { useSystemMenu } from "../utils/useSystemMenu/useSystemMenu" +import { TimelineItem } from "app/types" +import { MenuItemId } from "./Sidebar/SidebarMenu" +import { AboutModal } from "./AboutModal" + +export function SystemMenu({ children }: { children: React.ReactNode }) { + const { toggleSidebar } = useSidebar() + const [_, setActiveItem] = useGlobal("sidebar-active-item", "logs", { + persist: true, + }) + const [__, setTimelineItems] = withGlobal("timelineItems", [], { + persist: true, + }) + const [aboutVisible, setAboutVisible] = useState(false) + + const menuConfig = useMemo( + () => ({ + remove: ["File", "Edit", "Format", "Reactotron > About Reactotron"], + items: { + Reactotron: [ + { + label: "About Reactotron", + position: 0, + action: () => setAboutVisible(true), + }, + ], + View: [ + { + label: "Toggle Sidebar", + shortcut: { macos: "cmd+b", windows: "ctrl+b" }, + action: toggleSidebar, + }, + { + label: "Logs Tab", + shortcut: { macos: "cmd+1", windows: "ctrl+1" }, + action: () => setActiveItem("logs"), + }, + { + label: "Network Tab", + shortcut: { macos: "cmd+2", windows: "ctrl+2" }, + action: () => setActiveItem("network"), + }, + { + label: "Performance Tab", + shortcut: { macos: "cmd+3", windows: "ctrl+3" }, + action: () => setActiveItem("performance"), + }, + { + label: "Plugins Tab", + shortcut: { macos: "cmd+4", windows: "ctrl+4" }, + action: () => setActiveItem("plugins"), + }, + { + label: "Custom Commands Tab", + shortcut: { macos: "cmd+5", windows: "ctrl+5" }, + action: () => setActiveItem("customCommands"), + }, + { + label: "Help Tab", + shortcut: { macos: "cmd+6", windows: "ctrl+6" }, + action: () => setActiveItem("help"), + }, + ...(__DEV__ + ? [ + { + label: "Toggle Dev Menu", + shortcut: { macos: "cmd+shift+d", windows: "ctrl+shift+d" }, + action: () => NativeModules.DevMenu.show(), + }, + ] + : []), + ], + Window: [ + { + label: "Reload", + shortcut: { macos: "cmd+shift+r", windows: "ctrl+shift+r" }, + action: () => DevSettings.reload(), + }, + ], + Tools: [ + { + label: "Clear Timeline Items", + shortcut: { macos: "cmd+k", windows: "ctrl+k" }, + action: () => setTimelineItems([]), + }, + ], + }, + }), + [toggleSidebar, setActiveItem], + ) + + useSystemMenu(menuConfig) + + return ( + <> + {children} + + setAboutVisible(false)} /> + + ) +} diff --git a/app/components/Titlebar/Titlebar.tsx b/app/components/Titlebar/Titlebar.tsx index 97c473b..b44b671 100644 --- a/app/components/Titlebar/Titlebar.tsx +++ b/app/components/Titlebar/Titlebar.tsx @@ -4,6 +4,7 @@ import { Icon } from "../Icon" import ActionButton from "../ActionButton" import { useSidebar } from "../../state/useSidebar" import { PassthroughView } from "./PassthroughView" +import { TitlebarMenu } from "./TitlebarMenu" import { useGlobal } from "../../state/useGlobal" import { ClientTab } from "../ClientTab" @@ -16,6 +17,11 @@ export const Titlebar = () => { + {Platform.OS === "windows" && ( + + + + )} ( diff --git a/app/components/Titlebar/TitlebarMenu.tsx b/app/components/Titlebar/TitlebarMenu.tsx new file mode 100644 index 0000000..6b729a2 --- /dev/null +++ b/app/components/Titlebar/TitlebarMenu.tsx @@ -0,0 +1,78 @@ +import { View, ViewStyle } from "react-native" +import { useState, useCallback, useRef } from "react" +import { themed } from "../../theme/theme" +import { TitlebarMenuItem } from "./TitlebarMenuItem" +import { MenuDropdown } from "../Menu/MenuDropdown" +import { MenuOverlay } from "../Menu/MenuOverlay" +import type { DropdownMenuItem, Position } from "../Menu/types" +import { PassthroughView } from "./PassthroughView" +import { useSystemMenu } from "../../utils/useSystemMenu/useSystemMenu" + +export const TitlebarMenu = () => { + const { menuStructure, menuItems, handleMenuItemPressed } = useSystemMenu() + const [openMenu, setOpenMenu] = useState(null) + const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0 }) + const menuRefs = useRef>(new Map()) + + const handleMenuClick = useCallback((menuName: string) => { + const menuRef = menuRefs.current.get(menuName) + if (!menuRef) return + menuRef.measureInWindow((x, y, _, height) => { + setDropdownPosition({ x, y: y + height }) + setOpenMenu((prev) => (prev === menuName ? null : menuName)) + }) + }, []) + + const handleMenuHover = useCallback( + (menuName: string) => { + if (openMenu === null || openMenu === menuName) return + handleMenuClick(menuName) + }, + [openMenu, handleMenuClick], + ) + + const handleClose = () => setOpenMenu(null) + + // TODO: Add hotkey handling + + return ( + + {menuStructure.map((menu) => ( + { + if (ref) menuRefs.current.set(menu.title, ref) + }} + > + handleMenuClick(menu.title)} + onHoverIn={() => handleMenuHover(menu.title)} + /> + + ))} + {openMenu && menuItems[openMenu] && ( + <> + {/* Single overlay for all menu interactions */} + + { + handleMenuItemPressed({ menuPath: [openMenu, item.label] }) + handleClose() + }} + /> + + )} + + ) +} + +const $menuBar = themed(() => ({ + flexDirection: "row", + alignItems: "center", + height: "100%", + paddingHorizontal: 4, +})) diff --git a/app/components/Titlebar/TitlebarMenuItem.tsx b/app/components/Titlebar/TitlebarMenuItem.tsx new file mode 100644 index 0000000..b8d21e6 --- /dev/null +++ b/app/components/Titlebar/TitlebarMenuItem.tsx @@ -0,0 +1,66 @@ +import { Pressable, Text, type TextStyle, type ViewStyle } from "react-native" +import { useCallback, useState } from "react" +import { themed } from "../../theme/theme" + +interface TitlebarMenuItemProps { + title: string + isOpen?: boolean + onPress: () => void + onHoverIn?: () => void + onHoverOut?: () => void +} + +export const TitlebarMenuItem = ({ + title, + isOpen, + onPress, + onHoverIn, + onHoverOut, +}: TitlebarMenuItemProps) => { + const [isHovered, setIsHovered] = useState(false) + + const handleHover = useCallback( + (isHovered: boolean) => { + setIsHovered(isHovered) + if (isHovered) { + onHoverIn?.() + } else { + onHoverOut?.() + } + }, + [onHoverIn, onHoverOut], + ) + + return ( + { + handleHover(true) + }} + onHoverOut={() => { + handleHover(false) + }} + style={({ pressed }) => [$menuItem(), (pressed || isOpen || isHovered) && $menuItemHovered()]} + > + {title} + + ) +} + +const $menuItem = themed(({ spacing }) => ({ + paddingHorizontal: spacing.sm, + paddingVertical: spacing.xs, + borderRadius: 4, + justifyContent: "center", +})) + +const $menuItemHovered = themed(({ colors }) => ({ + backgroundColor: colors.neutralVery, + opacity: 0.8, +})) + +const $menuItemText = themed(({ colors, typography }) => ({ + color: colors.mainText, + fontSize: typography.caption, + fontWeight: "400", +})) diff --git a/app/contexts/ShortcutsContext.tsx b/app/contexts/ShortcutsContext.tsx new file mode 100644 index 0000000..c69cd2e --- /dev/null +++ b/app/contexts/ShortcutsContext.tsx @@ -0,0 +1,96 @@ +import { createContext, useContext, useCallback } from "react" +import { useGlobal } from "../state/useGlobal" +import { useKeyboardEvents } from "../utils/system" +import { parseShortcut, matchesKeyCombo, type KeyCombination } from "../utils/useSystemMenu/utils" + +// Global state for shortcuts - shared across all instances +type ShortcutRegistry = Record void> +type ShortcutCombinations = Record + +interface ShortcutsContextType { + registerShortcut: (shortcut: string, action: () => void) => void + unregisterShortcut: (shortcut: string) => void + clearAllShortcuts: () => void +} + +const ShortcutsContext = createContext(null) + +export function ShortcutsProvider({ children }: { children: React.ReactNode }) { + const [shortcuts, setShortcuts] = useGlobal("global-shortcuts", {}) + const [combinations, setCombinations] = useGlobal( + "global-shortcut-combinations", + {}, + ) + + const registerShortcut = useCallback( + (shortcut: string, action: () => void) => { + if (!shortcut || !action) return + + const combination = parseShortcut(shortcut) + if (!combination) { + console.warn(`Invalid shortcut format: ${shortcut}`) + return + } + + // Register globally (will overwrite if already exists - automatic deduplication!) + setShortcuts((prev) => ({ ...prev, [shortcut]: action })) + setCombinations((prev) => ({ ...prev, [shortcut]: combination })) + }, + [setShortcuts, setCombinations], + ) + + const unregisterShortcut = useCallback( + (shortcut: string) => { + setShortcuts((prev) => { + const { [shortcut]: _, ...rest } = prev + return rest + }) + setCombinations((prev) => { + const { [shortcut]: _, ...rest } = prev + return rest + }) + }, + [setShortcuts, setCombinations], + ) + + const clearAllShortcuts = useCallback(() => { + setShortcuts({}) + setCombinations({}) + }, [setShortcuts, setCombinations]) + + const handleKeyboardEvent = useCallback( + (event: any) => { + // Only handle keydown events + if (event.type !== "keydown") return + + // Check all registered shortcuts for a match + for (const [shortcut, combination] of Object.entries(combinations)) { + if (matchesKeyCombo(event, combination)) { + const action = shortcuts[shortcut] + if (action) { + action() + return // Stop after first match + } + } + } + }, + [shortcuts, combinations], + ) + + // Set up the global keyboard listener + useKeyboardEvents(handleKeyboardEvent, [handleKeyboardEvent]) + + return ( + + {children} + + ) +} + +export function useShortcuts() { + const context = useContext(ShortcutsContext) + if (!context) { + throw new Error("useShortcuts must be used within a ShortcutsProvider") + } + return context +} diff --git a/app/native/IRKeyboard/IRKeyboard.windows.cpp b/app/native/IRKeyboard/IRKeyboard.windows.cpp index 386b6b3..7cb3e6c 100644 --- a/app/native/IRKeyboard/IRKeyboard.windows.cpp +++ b/app/native/IRKeyboard/IRKeyboard.windows.cpp @@ -1,33 +1,168 @@ #include "pch.h" #include "IRKeyboard.windows.h" + #include +#include +#include +#include +#include + +namespace { + // Bit tested by GetKeyState for "currently down". + constexpr auto KEYBOARD_STATE_PRESSED = 0x8000; + + // Global WinAppSDK input state. Keep lifetimes/threading aligned with RNW's UI thread. + winrt::reactotron::implementation::IRKeyboard* g_keyboardInstance = nullptr; + winrt::Microsoft::UI::Input::InputKeyboardSource g_keyboardSource{ nullptr }; + winrt::Microsoft::UI::Content::ContentIsland g_contentIsland{ nullptr }; + winrt::event_token g_keyDownToken{}; + winrt::event_token g_keyUpToken{}; + + inline bool IsKeyPressed(int vk) noexcept { return (GetKeyState(vk) & KEYBOARD_STATE_PRESSED) != 0; } + + // Virtual-key → label. These labels are consumed cross-platform; changing them can break shortcuts. + const std::unordered_map VirtualKeyNames = { + // Navigation / editing + {VK_BACK,"Backspace"},{VK_TAB,"Tab"},{VK_RETURN,"\r"},{VK_ESCAPE,"\u001b"}, + {VK_SPACE,"Space"},{VK_DELETE,"Delete"},{VK_HOME,"Home"},{VK_END,"End"}, + {VK_PRIOR,"PageUp"},{VK_NEXT,"PageDown"},{VK_INSERT,"Insert"}, + + // Arrows + {VK_LEFT,"ArrowLeft"},{VK_UP,"ArrowUp"},{VK_RIGHT,"ArrowRight"},{VK_DOWN,"ArrowDown"}, + + // Function keys + {VK_F1,"F1"},{VK_F2,"F2"},{VK_F3,"F3"},{VK_F4,"F4"},{VK_F5,"F5"},{VK_F6,"F6"}, + {VK_F7,"F7"},{VK_F8,"F8"},{VK_F9,"F9"},{VK_F10,"F10"},{VK_F11,"F11"},{VK_F12,"F12"}, + + // Numpad digits + {VK_NUMPAD0,"0"},{VK_NUMPAD1,"1"},{VK_NUMPAD2,"2"},{VK_NUMPAD3,"3"},{VK_NUMPAD4,"4"}, + {VK_NUMPAD5,"5"},{VK_NUMPAD6,"6"},{VK_NUMPAD7,"7"},{VK_NUMPAD8,"8"},{VK_NUMPAD9,"9"}, + + // Modifiers normalized to single labels + {VK_CONTROL,"Control"},{VK_LCONTROL,"Control"},{VK_RCONTROL,"Control"}, + {VK_MENU,"Alt"},{VK_LMENU,"Alt"},{VK_RMENU,"Alt"}, + {VK_SHIFT,"Shift"},{VK_LSHIFT,"Shift"},{VK_RSHIFT,"Shift"}, + {VK_LWIN,"Meta"},{VK_RWIN,"Meta"}, + }; + + // VK → human label or character(s). Order matters: table → ASCII A-Z/0-9 → ToUnicode → "Unknown". + std::string TranslateVirtualKeyToString(DWORD vk) noexcept { + if (const auto it = VirtualKeyNames.find(vk); it != VirtualKeyNames.end()) return it->second; + + if ((vk >= 'A' && vk <= 'Z') || (vk >= '0' && vk <= '9')) return std::string(1, static_cast(vk)); + + BYTE keyboardState[256]; + GetKeyboardState(keyboardState); + + WCHAR unicodeBuffer[8] = {}; + const UINT scanCode = MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC, GetKeyboardLayout(0)); + const int result = ToUnicode(vk, scanCode, keyboardState, unicodeBuffer, 8, 0); + + if (result > 0) { + char utf8[16] = {}; + WideCharToMultiByte(CP_UTF8, 0, unicodeBuffer, result, utf8, 16, nullptr, nullptr); + return std::string(utf8); + } + return "Unknown"; + } -using namespace winrt::reactotron::implementation; + // KeyDown only: consumers derive state from live modifiers, not event-latched ones. + void OnKeyDown(winrt::Microsoft::UI::Input::InputKeyboardSource const&, + winrt::Microsoft::UI::Input::KeyEventArgs const& args) noexcept { + if (!g_keyboardInstance || !g_keyboardInstance->m_isListening) return; -namespace -{ - inline bool IsKeyDown(int vk) noexcept { return (GetKeyState(vk) & 0x8000) != 0; } + try { + const auto vk = static_cast(args.VirtualKey()); + reactotronCodegen::IRKeyboardSpec_KeyboardEvent e{}; + e.type = "keydown"; + e.key = TranslateVirtualKeyToString(vk); + e.characters = e.key; + e.keyCode = static_cast(vk); + + // Modifier policy: report actual keys pressed + e.modifiers = reactotronCodegen::IRKeyboardSpec_KeyboardEvent_modifiers{ + IsKeyPressed(VK_CONTROL), // ctrl + IsKeyPressed(VK_MENU), // alt + IsKeyPressed(VK_SHIFT), // shift + IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN) // cmd (Windows key) + }; + + if (g_keyboardInstance->onKeyboardEvent) g_keyboardInstance->onKeyboardEvent(std::move(e)); + } catch (...) { + #ifdef _DEBUG + OutputDebugStringA("[IRKeyboard] Exception in OnKeyDown"); + #endif + } + } + + // Present for completeness and future use. Currently a no-op; subscription remains intentional. + void OnKeyUp(winrt::Microsoft::UI::Input::InputKeyboardSource const&, + winrt::Microsoft::UI::Input::KeyEventArgs const&) noexcept {} + + // Binds to the current island and wires events. Call on the island's UI thread. + void InitializeKeyboardCapture(winrt::reactotron::implementation::IRKeyboard* instance) noexcept { + if (!instance || !instance->m_isListening) return; + + try { + const auto islands = winrt::Microsoft::UI::Content::ContentIsland::FindAllForCurrentThread(); + if (islands.size() == 0) return; + + g_contentIsland = islands[0]; + g_keyboardSource = winrt::Microsoft::UI::Input::InputKeyboardSource::GetForIsland(g_contentIsland); + if (!g_keyboardSource) return; + + g_keyboardInstance = instance; + g_keyDownToken = g_keyboardSource.KeyDown({ OnKeyDown }); + g_keyUpToken = g_keyboardSource.KeyUp({ OnKeyUp }); + + // Encourage focus so keystrokes route to the foreground window (best-effort, not guaranteed). + if (HWND hwnd = GetActiveWindow()) { + SetForegroundWindow(hwnd); + SetFocus(hwnd); + SendMessage(hwnd, WM_SETFOCUS, 0, 0); + } + } catch (...) { + // Non-fatal: input can be retried on the next startListening(). + } + } +} // namespace + +void winrt::reactotron::implementation::IRKeyboard::Initialize( + winrt::Microsoft::ReactNative::ReactContext const& reactContext) noexcept { + // ReactContext is used only to marshal to the owning UI dispatcher. + m_reactContext = reactContext; } -bool IRKeyboard::ctrl() noexcept { return IsKeyDown(VK_CONTROL); } -bool IRKeyboard::alt() noexcept { return IsKeyDown(VK_MENU); } -bool IRKeyboard::shift() noexcept { return IsKeyDown(VK_SHIFT); } -bool IRKeyboard::cmd() noexcept { return IsKeyDown(VK_LWIN) || IsKeyDown(VK_RWIN); } - -void IRKeyboard::startListening() noexcept -{ - m_isListening = true; - - // Emit a synthetic event to verify wiring - reactotronCodegen::IRKeyboardSpec_KeyboardEvent evt{}; - evt.type = "keydown"; - evt.key = "A"; - evt.characters = "a"; - evt.keyCode = 65; - evt.modifiers = reactotronCodegen::IRKeyboardSpec_KeyboardEvent_modifiers{ctrl(), alt(), shift(), cmd()}; - - if (onKeyboardEvent) - onKeyboardEvent(std::move(evt)); +// These helpers intentionally query live (GetKeyState) modifier state. +bool winrt::reactotron::implementation::IRKeyboard::ctrl() noexcept { return IsKeyPressed(VK_CONTROL); } +bool winrt::reactotron::implementation::IRKeyboard::alt() noexcept { return IsKeyPressed(VK_MENU); } +bool winrt::reactotron::implementation::IRKeyboard::shift() noexcept { return IsKeyPressed(VK_SHIFT); } +bool winrt::reactotron::implementation::IRKeyboard::cmd() noexcept { return IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN); } + +void winrt::reactotron::implementation::IRKeyboard::startListening() noexcept { + if (m_isListening || !m_reactContext) return; + + m_isListening = true; // matches original timing; some consumers check this before the dispatch posts + m_reactContext.UIDispatcher().Post([this]() { + InitializeKeyboardCapture(this); + }); } -void IRKeyboard::stopListening() noexcept { m_isListening = false; } +void winrt::reactotron::implementation::IRKeyboard::stopListening() noexcept { + if (!m_isListening) return; + m_isListening = false; + + // Remove handlers if present; tokens must be cleared even on exceptions. + if (g_keyboardSource) { + try { + if (g_keyDownToken.value != 0) { g_keyboardSource.KeyDown(g_keyDownToken); g_keyDownToken = {}; } + if (g_keyUpToken.value != 0) { g_keyboardSource.KeyUp(g_keyUpToken); g_keyUpToken = {}; } + } catch (...) { + // Non-fatal cleanup failure; state below still resets. + } + g_keyboardSource = nullptr; + } + + if (g_keyboardInstance == this) g_keyboardInstance = nullptr; + g_contentIsland = nullptr; +} diff --git a/app/native/IRKeyboard/IRKeyboard.windows.h b/app/native/IRKeyboard/IRKeyboard.windows.h index 537af25..6761821 100644 --- a/app/native/IRKeyboard/IRKeyboard.windows.h +++ b/app/native/IRKeyboard/IRKeyboard.windows.h @@ -1,34 +1,37 @@ #pragma once -#include "NativeModules.h" #include "..\..\..\windows\reactotron\codegen\NativeIRKeyboardDataTypes.g.h" #include "..\..\..\windows\reactotron\codegen\NativeIRKeyboardSpec.g.h" +#include "NativeModules.h" + +namespace winrt::reactotron::implementation { +REACT_TURBO_MODULE(IRKeyboard) +struct IRKeyboard : reactotronCodegen::IRKeyboardSpec { + IRKeyboard() noexcept = default; -namespace winrt::reactotron::implementation -{ - REACT_MODULE(IRKeyboard, L"IRKeyboard") - struct IRKeyboard - { - IRKeyboard() noexcept = default; + REACT_INIT(Initialize) + void Initialize( + winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept; - REACT_SYNC_METHOD(ctrl) - bool ctrl() noexcept; - REACT_SYNC_METHOD(alt) - bool alt() noexcept; - REACT_SYNC_METHOD(shift) - bool shift() noexcept; - REACT_SYNC_METHOD(cmd) - bool cmd() noexcept; + REACT_SYNC_METHOD(ctrl) + bool ctrl() noexcept; + REACT_SYNC_METHOD(alt) + bool alt() noexcept; + REACT_SYNC_METHOD(shift) + bool shift() noexcept; + REACT_SYNC_METHOD(cmd) + bool cmd() noexcept; - REACT_METHOD(startListening) - void startListening() noexcept; - REACT_METHOD(stopListening) - void stopListening() noexcept; + REACT_METHOD(startListening) + void startListening() noexcept; + REACT_METHOD(stopListening) + void stopListening() noexcept; - REACT_EVENT(onKeyboardEvent) - std::function onKeyboardEvent; + REACT_EVENT(onKeyboardEvent) + std::function + onKeyboardEvent; - private: - bool m_isListening = false; - HHOOK m_keyboardHook = nullptr; - }; -} + // Public members needed for event-driven keyboard capture + bool m_isListening = false; + winrt::Microsoft::ReactNative::ReactContext m_reactContext{nullptr}; +}; +} // namespace winrt::reactotron::implementation diff --git a/app/native/IRMenuItemManager/IRMenuItemManager.h b/app/native/IRMenuItemManager/IRMenuItemManager.h deleted file mode 100644 index c042f99..0000000 --- a/app/native/IRMenuItemManager/IRMenuItemManager.h +++ /dev/null @@ -1,8 +0,0 @@ -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface IRMenuItemManager : NativeIRMenuItemManagerSpecBase -@end - -NS_ASSUME_NONNULL_END diff --git a/app/native/IRSystemMenuManager/IRSystemMenuManager.h b/app/native/IRSystemMenuManager/IRSystemMenuManager.h new file mode 100644 index 0000000..6c379d1 --- /dev/null +++ b/app/native/IRSystemMenuManager/IRSystemMenuManager.h @@ -0,0 +1,8 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IRSystemMenuManager : NativeIRSystemMenuManagerSpecBase +@end + +NS_ASSUME_NONNULL_END diff --git a/app/native/IRMenuItemManager/IRMenuItemManager.mm b/app/native/IRSystemMenuManager/IRSystemMenuManager.mm similarity index 98% rename from app/native/IRMenuItemManager/IRMenuItemManager.mm rename to app/native/IRSystemMenuManager/IRSystemMenuManager.mm index 82df0bd..e2610e6 100644 --- a/app/native/IRMenuItemManager/IRMenuItemManager.mm +++ b/app/native/IRSystemMenuManager/IRSystemMenuManager.mm @@ -1,16 +1,16 @@ -#import "IRMenuItemManager.h" +#import "IRSystemMenuManager.h" #import #import static NSString * const separatorString = @"menu-item-separator"; -@implementation IRMenuItemManager { +@implementation IRSystemMenuManager { } RCT_EXPORT_MODULE() - (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { - return std::make_shared(params); + return std::make_shared(params); } #pragma mark - API diff --git a/app/native/IRMenuItemManager/IRMenuItemManager.windows.cpp b/app/native/IRSystemMenuManager/IRSystemMenuManager.windows.cpp similarity index 78% rename from app/native/IRMenuItemManager/IRMenuItemManager.windows.cpp rename to app/native/IRSystemMenuManager/IRSystemMenuManager.windows.cpp index 2df05f9..85220ee 100644 --- a/app/native/IRMenuItemManager/IRMenuItemManager.windows.cpp +++ b/app/native/IRSystemMenuManager/IRSystemMenuManager.windows.cpp @@ -1,18 +1,18 @@ // -// IRMenuItemManager.cpp +// IRSystemMenuManager.cpp // Reactotron-Windows // // Windows TurboModule implementation of menu item management // #include "pch.h" -#include "IRMenuItemManager.windows.h" +#include "IRSystemMenuManager.windows.h" -using winrt::reactotron::implementation::IRMenuItemManager; +using winrt::reactotron::implementation::IRSystemMenuManager; namespace winrt::reactotron::implementation { - void IRMenuItemManager::createMenu(std::string menuName, + void IRSystemMenuManager::createMenu(std::string menuName, ::React::ReactPromise &&result) noexcept { // THE PROBLEM: onMenuItemPressed is nullptr/undefined at runtime diff --git a/app/native/IRMenuItemManager/IRMenuItemManager.windows.h b/app/native/IRSystemMenuManager/IRSystemMenuManager.windows.h similarity index 57% rename from app/native/IRMenuItemManager/IRMenuItemManager.windows.h rename to app/native/IRSystemMenuManager/IRSystemMenuManager.windows.h index e15daf9..32edc65 100644 --- a/app/native/IRMenuItemManager/IRMenuItemManager.windows.h +++ b/app/native/IRSystemMenuManager/IRSystemMenuManager.windows.h @@ -4,17 +4,17 @@ #include // Generated (DataTypes before Spec) -#include "..\..\..\windows\reactotron\codegen\NativeIRMenuItemManagerDataTypes.g.h" -#include "..\..\..\windows\reactotron\codegen\NativeIRMenuItemManagerSpec.g.h" +#include "..\..\..\windows\reactotron\codegen\NativeIRSystemMenuManagerDataTypes.g.h" +#include "..\..\..\windows\reactotron\codegen\NativeIRSystemMenuManagerSpec.g.h" namespace winrt::reactotron::implementation { - REACT_TURBO_MODULE(IRMenuItemManager) - struct IRMenuItemManager : reactotronCodegen::IRMenuItemManagerSpec + REACT_TURBO_MODULE(IRSystemMenuManager) + struct IRSystemMenuManager : reactotronCodegen::IRSystemMenuManagerSpec { // Only the essential types needed for the event - using PressEvent = reactotronCodegen::IRMenuItemManagerSpec_MenuItemPressedEvent; - using CreateRet = reactotronCodegen::IRMenuItemManagerSpec_createMenu_returnType; + using PressEvent = reactotronCodegen::IRSystemMenuManagerSpec_SystemMenuItemPressedEvent; + using CreateRet = reactotronCodegen::IRSystemMenuManagerSpec_createMenu_returnType; // One simple method to test event emission REACT_METHOD(createMenu) diff --git a/app/native/IRMenuItemManager/NativeIRMenuItemManager.ts b/app/native/IRSystemMenuManager/NativeIRSystemMenuManager.ts similarity index 71% rename from app/native/IRMenuItemManager/NativeIRMenuItemManager.ts rename to app/native/IRSystemMenuManager/NativeIRSystemMenuManager.ts index fa17dfe..af59d58 100644 --- a/app/native/IRMenuItemManager/NativeIRMenuItemManager.ts +++ b/app/native/IRSystemMenuManager/NativeIRSystemMenuManager.ts @@ -6,41 +6,41 @@ import { TurboModuleRegistry } from "react-native" export const SEPARATOR = "menu-item-separator" as const // Path shape: ["View", "Zen Mode"] -export interface MenuItemPressedEvent { +export interface SystemMenuItemPressedEvent { menuPath: string[] } // Native -> JS: Tree node describing a menu item returned by getMenuStructure() -export interface MenuNode { +export interface SystemMenuNode { title: string enabled: boolean path: string[] // TODO: This creates an infinite loop when building for windows - // children?: MenuNode[] + // children?: SystemMenuNode[] children?: any } // Native -> JS: Top-level entry from getMenuStructure() -export interface MenuEntry { +export interface SystemMenuEntry { title: string - items: MenuNode[] + items: SystemMenuNode[] } -export type MenuStructure = MenuEntry[] +export type SystemMenuStructure = SystemMenuEntry[] -// JS -> Native: For building menu -export interface MenuItem { +// JS -> Native: For building menu (legacy - use SystemMenuItem instead) +export interface SystemNativeMenuItem { label: string shortcut?: string enabled?: boolean action: () => void } -export type MenuListEntry = MenuItem | typeof SEPARATOR +export type SystemMenuListEntry = SystemNativeMenuItem | typeof SEPARATOR export interface Spec extends TurboModule { getAvailableMenus(): string[] - getMenuStructure(): MenuStructure + getMenuStructure(): SystemMenuStructure createMenu(menuName: string): Promise<{ success: boolean; existed: boolean; menuName: string }> addMenuItemAtPath( parentPath: string[], @@ -60,7 +60,7 @@ export interface Spec extends TurboModule { path: string[], enabled: boolean, ): Promise<{ success: boolean; error?: string }> - readonly onMenuItemPressed: EventEmitter + readonly onMenuItemPressed: EventEmitter } -export default TurboModuleRegistry.getEnforcing("IRMenuItemManager") +export default TurboModuleRegistry.getEnforcing("IRSystemMenuManager") diff --git a/app/state/useGlobal.ts b/app/state/useGlobal.ts index c2b20e4..1badd6a 100644 --- a/app/state/useGlobal.ts +++ b/app/state/useGlobal.ts @@ -113,7 +113,7 @@ function buildSetValue(id: string, persist: boolean) { } export function deleteGlobal(id: string): void { - delete globals[id] + delete _globals[id] } /** diff --git a/app/state/useGlobal.windows.ts b/app/state/useGlobal.windows.ts index b13f1ac..c4edad5 100644 --- a/app/state/useGlobal.windows.ts +++ b/app/state/useGlobal.windows.ts @@ -1,87 +1,271 @@ -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react" +/** + * Global State Management System (React Native Windows) + * + * A lightweight global state solution built on React's useSyncExternalStore. + * Provides both hook-based and imperative access to shared state values. + * + * Features: + * - Type-safe global state with "initialize on first read" semantics + * - Efficient subscription system with automatic cleanup + * - Batched updates to minimize re-renders + * - Support for both React components (useGlobal) and external code (withGlobal) + * - Memory leak prevention via Set cleanup + * - No-op persistence stubs (API-stable; can be wired later) + * + * Usage: + * const [count, setCount] = useGlobal('counter', 0); + * const [data, setData] = withGlobal('userData', {}); + * const [persistedData, setPersisted] = useGlobal('key', {}, { persist: true }); // persist currently no-op + * + * Notes: + * - `initialValue` is used as the value if the key is missing. For components, + * we DO NOT write during render; we write once post-mount if still missing. + * - If different `initialValue`s are passed for the same `id`, the first + * established value "wins" (subsequent differing defaults are ignored). + */ + +import { useSyncExternalStore, useEffect, useCallback } from "react" +import { unstable_batchedUpdates } from "react-native" + +type Id = string +type Listener = () => void +type SetValue = T | ((prev: T) => T) +type UseGlobalOptions = { persist?: boolean } + +/* ----------------------------------------------------------------------------- + * Internal Stores + * -------------------------------------------------------------------------- */ +// Central storage for all global state values, keyed by unique identifiers +const globals = new Map() +// Subscription system: maps each global ID to a set of listener functions +const listeners = new Map>() + +/* ----------------------------------------------------------------------------- + * Persistence Stubs (no-op) + * RN Web doesn't have react-native-mmkv support out of the box; plan to wire later + * -------------------------------------------------------------------------- */ +function loadPersistedGlobals(): void { + // No-op: persistence not implemented +} + +function debouncePersist(_delay: number = 300): void { + // No-op: persistence not implemented +} + +/* ----------------------------------------------------------------------------- + * Helpers + * -------------------------------------------------------------------------- */ + +/** + * Read a snapshot for an id, returning `initialValue` when missing. + * Pure read: NEVER writes during render. + */ +function getSnapshotWithDefault(id: Id, initialValue: T): T { + return globals.has(id) ? (globals.get(id) as T) : initialValue +} -type UseGlobalOptions = Record +/** + * Read a snapshot without default (used by imperative API and setters). + */ +function getSnapshot(id: Id): T { + return globals.get(id) as T +} + +/** + * Subscribe a component to changes for a specific global ID. + * Returns an unsubscribe function that cleans up both the listener and empty sets. + */ +function subscribe(id: Id, cb: Listener): () => void { + let set = listeners.get(id) + if (!set) listeners.set(id, (set = new Set())) + set.add(cb) + + // Return cleanup function that prevents memory leaks + return () => { + const s = listeners.get(id) + if (!s) return + s.delete(cb) + // Clean up empty listener sets to prevent memory leaks + if (s.size === 0) listeners.delete(id) + } +} -const globals: Record = {} -const components_to_rerender: Record>[]> = {} +/** + * Notify all subscribers of a global value change. + * Uses batched updates to prevent excessive re-renders when multiple globals change. + * Iterates over a copy to be resilient to listeners mutating subscriptions. + */ +function notify(id: Id) { + const s = listeners.get(id) + if (!s || s.size === 0) return -type SetValueFn = (prev: T) => T -type SetValue = T | SetValueFn + unstable_batchedUpdates(() => { + for (const l of Array.from(s)) l() + }) +} /** - * Trying for the simplest possible global state management. - * Use anywhere and it'll share the same state globally, and rerender any component that uses it. + * Create a setter function that handles state updates (persistence is no-op). + * - Supports functional updates like React.useState + * - Skips notifications if value is Object.is-equal (no-op update) + * - Accepts `null` to reset/delete the value (used by imperative API) + */ +function buildSetValue(id: Id, persist: boolean, initialValue?: T) { + return (value: SetValue | null) => { + const prev = globals.get(id) as T | undefined + + // Handle null value as reset (imperative API) + if (value === null) { + if (!globals.has(id)) return // nothing to reset + globals.delete(id) + // persistence cleanup would go here (no-op for now) + notify(id) + return + } + + // Resolve functional updater - use prev if exists, otherwise use initialValue as fallback + const current = prev !== undefined ? prev : initialValue + const next = typeof value === "function" ? (value as (prev: T) => T)(current as T) : value + + // Avoid unnecessary notifications/re-renders on no-op updates + // BUT: prev might be undefined while next is defined (first set) + if (prev !== undefined && Object.is(prev, next)) return + + globals.set(id, next) + + // Would save to persistent storage if implemented (no-op for now) + if (persist) debouncePersist() + + // Notify all subscribers for re-renders + notify(id) + } +} + +/* ----------------------------------------------------------------------------- + * Public API + * -------------------------------------------------------------------------- */ + +/** + * React hook for accessing and updating global state (component-friendly API) * - * const [value, setValue] = useGlobal("my-state", "initial-value") + * RN-only: No SSR getServerSnapshot is provided. * + * @param id - Unique identifier for the global value + * @param initialValue - Default value to use if the global doesn't exist yet + * @param options - Configuration options including persistence (NOTE: persist is no-op) + * @returns Tuple of [currentValue, setter] similar to useState */ -export function useGlobal( - id: string, +export function useGlobal( + id: Id, initialValue: T, - options: UseGlobalOptions = {}, -): [T, (value: SetValue) => void] { - // This is a dummy state to rerender any component that uses this global. - const [_v, setRender] = useState([]) + { persist = false }: UseGlobalOptions = {}, +): [T, (v: SetValue) => void] { + // Read via useSyncExternalStore; ensure the snapshot read is PURE (no writes) + const value = useSyncExternalStore( + (cb) => subscribe(id, cb), // subscribe + () => getSnapshotWithDefault(id, initialValue), // getSnapshot (client) + ) - // Subscribe & unsubscribe from state changes for this ID. + /** + * Post-mount initialization: + * If the key is still missing, establish it with `initialValue`. + * This avoids writes during render while ensuring the key exists thereafter. + * No notify needed: subscribers already read `initialValue` on first render. + */ useEffect(() => { - components_to_rerender[id] ||= [] - components_to_rerender[id].push(setRender) - return () => { - if (!components_to_rerender[id]) return - components_to_rerender[id] = components_to_rerender[id].filter( - (listener) => listener !== setRender, - ) + if (!globals.has(id)) { + globals.set(id, initialValue) } }, [id]) - // We use the withGlobal hook to do the actual work. - const [value] = withGlobal(id, initialValue, options) + // Memoize the setter; enforce non-null signature for hook users + const setAny = useCallback(buildSetValue(id, persist, initialValue), [ + id, + persist, + initialValue, + ]) + const set = useCallback<(v: SetValue) => void>((v) => setAny(v), [setAny]) - // We use a callback to ensure that the setValue function is stable. - const setValue = useCallback(buildSetValue(id), [id]) - - return [value, setValue] + return [value, set] } /** - * For global state used outside of a component. Can be used in a component with - * the same id string, using useGlobal. + * Imperative access to global state (for use outside React components) + * + * Useful for accessing global state in utility functions, event handlers, + * or other non-React code that needs to read/write global values. + * + * @param id - Unique identifier for the global value + * @param initialValue - Default value to use if the global doesn't exist yet + * @param options - Configuration options including persistence (NOTE: persist is no-op) + * @returns Tuple of [currentValue, setter] where setter accepts null to reset */ export function withGlobal( - id: string, + id: Id, initialValue: T, - _: UseGlobalOptions = {}, -): [T, (value: SetValue | null) => void] { - // Initialize this global if it doesn't exist. - if (globals[id] === undefined) globals[id] = initialValue - - return [globals[id] as T, buildSetValue(id)] + { persist = false }: UseGlobalOptions = {}, +): [T, (v: SetValue | null) => void] { + // Imperative path can initialize synchronously without render concerns + if (!globals.has(id)) globals.set(id, initialValue) + return [getSnapshot(id), buildSetValue(id, persist, initialValue)] } -function buildSetValue(id: string) { - return (value: SetValue | null) => { - // Call the setter function if it's a function. - if (typeof value === "function") value = (value as SetValueFn)(globals[id] as T) - if (value === null) { - delete globals[id] - } else { - globals[id] = value - } - components_to_rerender[id] ||= [] - components_to_rerender[id].forEach((rerender) => rerender([])) - } +/** + * Reset a specific global value back to its initial state (delete the key). + * + * @param id - The global identifier to reset + * @param rerender - Whether to trigger re-renders (default: true) + */ +export function resetGlobal(id: Id, rerender = true) { + if (!globals.has(id)) return + globals.delete(id) + // Note: No persistence cleanup needed since persistence is not implemented + if (rerender) notify(id) } /** - * Clear all globals and reset the storage entirely. - * Optionally rerender all components that use useGlobal. + * Clear all global state values. + * + * Useful for testing or app-wide state resets. Only notifies globals + * that have active listeners to avoid unnecessary work. + * + * @param rerender - Whether to trigger re-renders (default: true) */ -export function clearGlobals(rerender: boolean = true): void { - Object.keys(globals).forEach((key) => delete globals[key]) - if (rerender) { - Object.keys(components_to_rerender).forEach((key) => { - components_to_rerender[key].forEach((rerender) => rerender([])) - }) +export function clearGlobals(rerender = true) { + // Clear in-memory state + const hadAny = globals.size > 0 + globals.clear() + // Note: No persistent storage to clear since persistence is not implemented + + if (rerender && hadAny) { + // Only notify ids that currently have listeners + for (const id of listeners.keys()) notify(id) } } + +/* ----------------------------------------------------------------------------- + * Optional Developer Ergonomics (handy for tests & utilities) + * -------------------------------------------------------------------------- */ + +/** + * Read a global value without subscribing. Returns undefined if missing. + */ +export const getGlobal = (id: Id): T | undefined => globals.get(id) as T | undefined + +/** + * Set a global value without subscribing. (Non-null only.) + */ +export const setGlobal = (id: Id, v: SetValue): void => + buildSetValue(id, false, undefined)(v) + +/** + * Check whether a global key exists. + */ +export const hasGlobal = (id: Id): boolean => globals.has(id) + +/* ----------------------------------------------------------------------------- + * Module Initialization + * -------------------------------------------------------------------------- */ + +// Load persisted globals on module initialization (no-op for now) +loadPersistedGlobals() diff --git a/app/utils/random/IRRandom.windows.cpp b/app/utils/random/IRRandom.windows.cpp index 88f1a74..ddb7090 100644 --- a/app/utils/random/IRRandom.windows.cpp +++ b/app/utils/random/IRRandom.windows.cpp @@ -7,6 +7,9 @@ #include "pch.h" #include "IRRandom.windows.h" +#include +#include +#include namespace winrt::reactotron::implementation { @@ -17,7 +20,28 @@ namespace winrt::reactotron::implementation std::string IRRandom::getUUID() noexcept { - // TODO: Generate UUID on Windows using CoCreateGuid or similar - return "00000000-0000-0000-0000-000000000000"; + GUID guid; + HRESULT result = CoCreateGuid(&guid); + + if (FAILED(result)) + { + return "00000000-0000-0000-0000-000000000000"; + } + + std::ostringstream stream; + stream << std::hex << std::uppercase << std::setfill('0') + << std::setw(8) << guid.Data1 << "-" + << std::setw(4) << guid.Data2 << "-" + << std::setw(4) << guid.Data3 << "-" + << std::setw(2) << static_cast(guid.Data4[0]) + << std::setw(2) << static_cast(guid.Data4[1]) << "-" + << std::setw(2) << static_cast(guid.Data4[2]) + << std::setw(2) << static_cast(guid.Data4[3]) + << std::setw(2) << static_cast(guid.Data4[4]) + << std::setw(2) << static_cast(guid.Data4[5]) + << std::setw(2) << static_cast(guid.Data4[6]) + << std::setw(2) << static_cast(guid.Data4[7]); + + return stream.str(); } } \ No newline at end of file diff --git a/app/utils/system.ts b/app/utils/system.ts index dcc66be..d054f8f 100644 --- a/app/utils/system.ts +++ b/app/utils/system.ts @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react" import IRRunShellCommand from "../native/IRRunShellCommand/NativeIRRunShellCommand" import IRSystemInfo, { SystemInfo } from "../native/IRSystemInfo/NativeIRSystemInfo" import IRKeyboard, { KeyboardEvent } from "../native/IRKeyboard/NativeIRKeyboard" -import { Platform, type EventSubscription } from "react-native" +import { type EventSubscription } from "react-native" /** * Get the current memory usage of the app in MB via a shell command. @@ -47,7 +47,6 @@ export function useKeyboardEvents(onKeyboardEvent: (event: KeyboardEvent) => voi const keyboardSubscription = useRef(null) useEffect(() => { - if (Platform.OS === "windows") return _keyboardSubscribers++ if (_keyboardSubscribers === 1) IRKeyboard.startListening() keyboardSubscription.current = IRKeyboard.onKeyboardEvent(onKeyboardEvent) diff --git a/app/utils/useSystemMenu/types.ts b/app/utils/useSystemMenu/types.ts new file mode 100644 index 0000000..b2941d8 --- /dev/null +++ b/app/utils/useSystemMenu/types.ts @@ -0,0 +1,26 @@ +import { MENU_SEPARATOR } from "../../components/Menu/types" +export type { + SystemMenuItemPressedEvent, + SystemMenuStructure, +} from "../../native/IRSystemMenuManager/NativeIRSystemMenuManager" + +export type PlatformShortcut = { + windows?: string + macos?: string +} + +export interface SystemMenuItem { + label: string + shortcut?: string | PlatformShortcut + enabled?: boolean + position?: number + action?: () => void + submenu?: SystemMenuListEntry[] +} + +export type SystemMenuListEntry = SystemMenuItem | typeof MENU_SEPARATOR + +export interface SystemMenuConfig { + items?: Record + remove?: string[] +} diff --git a/app/utils/useMenuItem.tsx b/app/utils/useSystemMenu/useSystemMenu.ts similarity index 68% rename from app/utils/useMenuItem.tsx rename to app/utils/useSystemMenu/useSystemMenu.ts index dc094ec..ae40f84 100644 --- a/app/utils/useMenuItem.tsx +++ b/app/utils/useSystemMenu/useSystemMenu.ts @@ -42,7 +42,7 @@ * removeMenuItemByName, * setMenuItemEnabled, * getAllMenuPaths - * } = useMenuItem() + * } = useSystemMenu() * * useEffect(() => { * removeMenuItemByName("Format") @@ -51,48 +51,24 @@ */ import { useEffect, useRef, useCallback, useState } from "react" -import NativeIRMenuItemManager, { - type MenuItemPressedEvent, - type MenuStructure, - type MenuListEntry, - SEPARATOR, -} from "../native/IRMenuItemManager/NativeIRMenuItemManager" - -// Only thing to configure here is the path separator. -const PATH_SEPARATOR = " > " - -export { SEPARATOR } // Menu separator - -export interface MenuItem { - label: string - shortcut?: string - enabled?: boolean - position?: number - action: () => void -} - -export interface MenuItemConfig { - items?: Record - remove?: string[] -} - -const parsePathKey = (key: string): string[] => - key - .split(PATH_SEPARATOR) - .map((s) => s.trim()) - .filter(Boolean) - -const joinPath = (p: string[]) => p.join(PATH_SEPARATOR) - -const isSeparator = (e: MenuListEntry): e is typeof SEPARATOR => e === SEPARATOR - -export function useMenuItem(config?: MenuItemConfig) { +import NativeIRSystemMenuManager from "../../native/IRSystemMenuManager/NativeIRSystemMenuManager" +import { + type SystemMenuItem, + type SystemMenuConfig, + type SystemMenuListEntry, + type SystemMenuItemPressedEvent, + type SystemMenuStructure, +} from "./types" +import { parsePathKey, joinPath, isSeparator } from "./utils" +import { MENU_SEPARATOR } from "../../components/Menu/types" + +export function useSystemMenu(config?: SystemMenuConfig) { const actionsRef = useRef void>>(new Map()) - const previousConfigRef = useRef(null) + const previousConfigRef = useRef(null) const [availableMenus, setAvailableMenus] = useState([]) - const [menuStructure, setMenuStructure] = useState([]) + const [menuStructure, setMenuStructure] = useState([]) - const handleMenuItemPressed = useCallback((event: MenuItemPressedEvent) => { + const handleMenuItemPressed = useCallback((event: SystemMenuItemPressedEvent) => { const key = joinPath(event.menuPath) const action = actionsRef.current.get(key) if (action) action() @@ -100,8 +76,8 @@ export function useMenuItem(config?: MenuItemConfig) { const discoverMenus = useCallback(async () => { try { - const menus = NativeIRMenuItemManager.getAvailableMenus() - const structure = NativeIRMenuItemManager.getMenuStructure() + const menus = NativeIRSystemMenuManager.getAvailableMenus() + const structure = NativeIRSystemMenuManager.getMenuStructure() setAvailableMenus(menus) setMenuStructure(structure) return menus @@ -111,11 +87,12 @@ export function useMenuItem(config?: MenuItemConfig) { } }, []) - const addEntries = useCallback(async (parentKey: string, entries: MenuListEntry[]) => { + const addEntries = useCallback(async (parentKey: string, entries: SystemMenuListEntry[]) => { const parentPath = parsePathKey(parentKey) + // Clear any existing separators before adding new ones to avoid duplication try { - await NativeIRMenuItemManager.removeMenuItemAtPath([...parentPath, SEPARATOR]) + await NativeIRSystemMenuManager.removeMenuItemAtPath([...parentPath, MENU_SEPARATOR]) } catch (e) { console.warn(`Failed to clear separators for "${parentKey}":`, e) } @@ -123,36 +100,41 @@ export function useMenuItem(config?: MenuItemConfig) { for (const entry of entries) { if (isSeparator(entry)) { try { - await NativeIRMenuItemManager.addMenuItemAtPath(parentPath, SEPARATOR, "") + await NativeIRSystemMenuManager.addMenuItemAtPath(parentPath, MENU_SEPARATOR, "") } catch (e) { console.error(`Failed to add separator under "${parentKey}":`, e) } continue } - const item = entry as MenuItem + const item = entry as SystemMenuItem const leafPath = [...parentPath, item.label] const actionKey = joinPath(leafPath) - actionsRef.current.set(actionKey, item.action) + + if (item.action) actionsRef.current.set(actionKey, item.action) + + // Resolve platform-specific shortcut for macOS + const resolvedShortcut = + typeof item.shortcut === "object" ? item.shortcut.macos ?? "" : item.shortcut ?? "" try { if (typeof item.position === "number") { - await NativeIRMenuItemManager.insertMenuItemAtPath( + await NativeIRSystemMenuManager.insertMenuItemAtPath( parentPath, item.label, item.position, - item.shortcut ?? "", + resolvedShortcut, ) } else { - await NativeIRMenuItemManager.addMenuItemAtPath( + await NativeIRSystemMenuManager.addMenuItemAtPath( parentPath, item.label, - item.shortcut ?? "", + resolvedShortcut, ) } if (item.enabled !== undefined) { - await NativeIRMenuItemManager.setMenuItemEnabledAtPath(leafPath, item.enabled) + await NativeIRSystemMenuManager.setMenuItemEnabledAtPath(leafPath, item.enabled) } } catch (error) { console.error(`Failed to add "${item.label}" under "${parentKey}":`, error) @@ -160,12 +142,12 @@ export function useMenuItem(config?: MenuItemConfig) { } }, []) - const removeMenuItems = useCallback(async (parentKey: string, items: MenuItem[]) => { + const removeMenuItems = useCallback(async (parentKey: string, items: SystemMenuItem[]) => { const parentPath = parsePathKey(parentKey) for (const item of items) { const leafPath = [...parentPath, item.label] try { - await NativeIRMenuItemManager.removeMenuItemAtPath(leafPath) + await NativeIRSystemMenuManager.removeMenuItemAtPath(leafPath) } catch (error) { console.error(`Failed to remove menu item ${joinPath(leafPath)}:`, error) } finally { @@ -177,7 +159,7 @@ export function useMenuItem(config?: MenuItemConfig) { const removeMenuItemByName = useCallback(async (nameOrPath: string) => { const path = parsePathKey(nameOrPath) try { - await NativeIRMenuItemManager.removeMenuItemAtPath(path) + await NativeIRSystemMenuManager.removeMenuItemAtPath(path) actionsRef.current.delete(joinPath(path)) } catch (error) { console.error(`Failed to remove menu item/menu ${nameOrPath}:`, error) @@ -187,7 +169,7 @@ export function useMenuItem(config?: MenuItemConfig) { const setMenuItemEnabled = useCallback(async (pathOrKey: string | string[], enabled: boolean) => { const path = Array.isArray(pathOrKey) ? pathOrKey : parsePathKey(pathOrKey) try { - await NativeIRMenuItemManager.setMenuItemEnabledAtPath(path, enabled) + await NativeIRSystemMenuManager.setMenuItemEnabledAtPath(path, enabled) } catch (error) { console.error(`Failed to set enabled for ${joinPath(path)}:`, error) } @@ -195,8 +177,9 @@ export function useMenuItem(config?: MenuItemConfig) { const getAllMenuPaths = useCallback(async (): Promise => { try { - const structure = NativeIRMenuItemManager.getMenuStructure() + const structure = NativeIRSystemMenuManager.getMenuStructure() const out: string[] = [] + // Recursively walk the menu tree structure const walk = (nodes?: any[]) => { if (!nodes) return for (const n of nodes) { @@ -213,16 +196,16 @@ export function useMenuItem(config?: MenuItemConfig) { }, []) const getItemDifference = useCallback( - (oldEntries: MenuListEntry[] = [], newEntries: MenuListEntry[] = []) => { - const oldItems = oldEntries.filter((e): e is MenuItem => !isSeparator(e)) - const newItems = newEntries.filter((e): e is MenuItem => !isSeparator(e)) + (oldEntries: SystemMenuListEntry[] = [], newEntries: SystemMenuListEntry[] = []) => { + const oldItems = oldEntries.filter((e): e is SystemMenuItem => !isSeparator(e)) + const newItems = newEntries.filter((e): e is SystemMenuItem => !isSeparator(e)) - const byLabel = (xs: MenuItem[]) => new Map(xs.map((x) => [x.label, x])) + const byLabel = (xs: SystemMenuItem[]) => new Map(xs.map((x) => [x.label, x])) const oldMap = byLabel(oldItems) const newMap = byLabel(newItems) - const toRemove: MenuItem[] = [] - const toUpdate: MenuItem[] = [] + const toRemove: SystemMenuItem[] = [] + const toUpdate: SystemMenuItem[] = [] for (const [label, item] of newMap) { if (oldMap.has(label)) { @@ -267,10 +250,12 @@ export function useMenuItem(config?: MenuItemConfig) { for (const item of toUpdate) { const leafPath = [...parsePathKey(parentKey), item.label] - actionsRef.current.set(joinPath(leafPath), item.action) + if (item.action) { + actionsRef.current.set(joinPath(leafPath), item.action) + } if (item.enabled !== undefined) { try { - await NativeIRMenuItemManager.setMenuItemEnabledAtPath(leafPath, item.enabled) + await NativeIRSystemMenuManager.setMenuItemEnabledAtPath(leafPath, item.enabled) } catch (e) { console.error(`Failed to update ${joinPath(leafPath)}:`, e) } @@ -284,17 +269,17 @@ export function useMenuItem(config?: MenuItemConfig) { } updateMenus() - }, [config, addEntries, removeMenuItems, getItemDifference]) + }, [config, addEntries, removeMenuItems, getItemDifference, removeMenuItemByName, discoverMenus]) useEffect(() => { - const subscription = NativeIRMenuItemManager.onMenuItemPressed(handleMenuItemPressed) + const subscription = NativeIRSystemMenuManager.onMenuItemPressed(handleMenuItemPressed) discoverMenus() return () => { subscription.remove() } }, [handleMenuItemPressed, discoverMenus]) - // Clean up old menu items + // Clean up old menu items when component unmounts useEffect(() => { return () => { if (!previousConfigRef.current || !config || !config.items) { @@ -303,22 +288,24 @@ export function useMenuItem(config?: MenuItemConfig) { const pairs = Object.entries(previousConfigRef.current.items ?? config.items) const cleanup = async () => { for (const [parentKey, entries] of pairs) { - const itemsOnly = entries.filter((e): e is MenuItem => !isSeparator(e)) + const itemsOnly = entries.filter((e): e is SystemMenuItem => !isSeparator(e)) await removeMenuItems(parentKey, itemsOnly) - await NativeIRMenuItemManager.removeMenuItemAtPath([ + // Remove any remaining separators + await NativeIRSystemMenuManager.removeMenuItemAtPath([ ...parsePathKey(parentKey), - SEPARATOR, + MENU_SEPARATOR, ]) const parentPath = parsePathKey(parentKey) + // If this was a top-level menu we created and it's now empty, remove it entirely if (parentPath.length === 1) { const top = parentPath[0] - const structure = NativeIRMenuItemManager.getMenuStructure() + const structure = NativeIRSystemMenuManager.getMenuStructure() const entry = structure.find( (e) => e.title.localeCompare(top, undefined, { sensitivity: "accent" }) === 0, ) if (!entry || !entry.items || entry.items.length === 0) { try { - await NativeIRMenuItemManager.removeMenuItemAtPath([top]) + await NativeIRSystemMenuManager.removeMenuItemAtPath([top]) } catch (e) { console.warn(`Couldn't remove top-level menu "${top}":`, e) } @@ -328,10 +315,10 @@ export function useMenuItem(config?: MenuItemConfig) { } cleanup() } - }, []) + }, [removeMenuItems]) const addMenuItem = useCallback( - async (parentKey: string, item: MenuItem) => { + async (parentKey: string, item: SystemMenuItem) => { await addEntries(parentKey, [item]) }, [addEntries], @@ -340,10 +327,12 @@ export function useMenuItem(config?: MenuItemConfig) { return { availableMenus, menuStructure, + menuItems: {} as Record, discoverMenus, addMenuItem, removeMenuItemByName, setMenuItemEnabled, getAllMenuPaths, + handleMenuItemPressed, } } diff --git a/app/utils/useSystemMenu/useSystemMenu.windows.ts b/app/utils/useSystemMenu/useSystemMenu.windows.ts new file mode 100644 index 0000000..d23de6e --- /dev/null +++ b/app/utils/useSystemMenu/useSystemMenu.windows.ts @@ -0,0 +1,228 @@ +/* + * Windows Menu Management (Global State Facade) + * + * Add, delete, and update Windows menu items using global state persistence. + * This implementation provides a facade over global state since Windows doesn't + * have native menu management APIs like macOS. + * + * ────────────────────────────── + * Declarative Usage (via config) + * ────────────────────────────── + * + * const menuConfig = { + * items: { + * "File": [ + * { + * label: "New Project", + * shortcut: "ctrl+n", + * action: () => console.log("New project created"), + * }, + * SEPARATOR, + * { + * label: "Save", + * enabled: false, + * action: () => console.log("Save action"), + * }, + * ], + * "View": [ + * { + * label: "Toggle Sidebar", + * shortcut: "ctrl+b", + * action: () => console.log("Sidebar toggled"), + * }, + * ], + * }, + * remove: ["Help", "Format"], + * } + * + * ─────────────────────────── + * Imperative Usage (via hook) + * ─────────────────────────── + * + * const { + * addMenuItem, + * removeMenuItemByName, + * setMenuItemEnabled, + * getAllMenuPaths, + * menuItems, + * menuStructure + * } = useSystemMenu() + * + * useEffect(() => { + * addMenuItem("Tools", { + * label: "Clear Cache", + * action: () => console.log("Cache cleared") + * }) + * getAllMenuPaths().then(paths => console.log({ paths })) + * }, [addMenuItem, getAllMenuPaths]) + * + * // Note: Windows implementation stores menu state globally for persistence + * // across component unmounts. Actions are stored in actionsRef for execution. + */ + +import { useEffect, useRef, useCallback } from "react" +import { useGlobal } from "../../state/useGlobal" +import { useShortcuts } from "../../contexts/ShortcutsContext" +import { + type SystemMenuItem, + type SystemMenuConfig, + type SystemMenuItemPressedEvent, + type SystemMenuStructure, +} from "./types" +import { parsePathKey, joinPath, isSeparator } from "./utils" + +export function useSystemMenu(config?: SystemMenuConfig) { + const actionsRef = useRef void>>(new Map()) + const { registerShortcut, clearAllShortcuts } = useShortcuts() + + const [globalMenuConfig, setGlobalMenuConfig] = useGlobal( + "windows-menu-config", + null, + ) + const [globalMenuStructure, setGlobalMenuStructure] = useGlobal( + "windows-menu-structure", + [], + ) + const [globalMenuItems, setGlobalMenuItems] = useGlobal>( + "windows-menu-items", + {}, + ) + + const handleMenuItemPressed = useCallback((event: SystemMenuItemPressedEvent) => { + const action = actionsRef.current.get(joinPath(event.menuPath)) + if (action) action() + }, []) + + const discoverMenus = useCallback(async () => { + if (!config?.items || config === globalMenuConfig) return [] + + const menuStructure: SystemMenuStructure = Object.keys(config.items).map((title) => ({ + title, + enabled: true, + path: [title], + items: [], + children: [], + })) + + setGlobalMenuConfig(config) + setGlobalMenuStructure(menuStructure) + setGlobalMenuItems(config.items as Record) + + return [] + }, [config, globalMenuConfig, setGlobalMenuConfig, setGlobalMenuStructure, setGlobalMenuItems]) + + const addMenuItem = useCallback( + async (parentKey: string, item: SystemMenuItem) => { + const actionKey = joinPath([parentKey, item.label]) + + if (item.action) { + actionsRef.current.set(actionKey, item.action) + } + + setGlobalMenuItems((prev) => ({ + ...prev, + [parentKey]: [...(prev[parentKey] || []), item], + })) + }, + [setGlobalMenuItems], + ) + + const removeMenuItemByName = useCallback( + async (nameOrPath: string) => { + const path = parsePathKey(nameOrPath) + actionsRef.current.delete(joinPath(path)) + + if (path.length === 1) { + // Remove entire top-level menu + setGlobalMenuItems((prev) => { + const { [path[0]]: _, ...rest } = prev + return rest + }) + } else if (path.length === 2) { + const [parentKey, itemLabel] = path + setGlobalMenuItems((prev) => ({ + ...prev, + [parentKey]: (prev[parentKey] || []).filter((item) => item.label !== itemLabel), + })) + } + }, + [setGlobalMenuItems, globalMenuItems], + ) + + const setMenuItemEnabled = useCallback( + async (pathOrKey: string | string[], enabled: boolean) => { + const path = Array.isArray(pathOrKey) ? pathOrKey : parsePathKey(pathOrKey) + + if (path.length >= 2) { + const [parentKey, itemLabel] = path + setGlobalMenuItems((prev) => ({ + ...prev, + [parentKey]: (prev[parentKey] || []).map((item) => + item.label === itemLabel ? { ...item, enabled } : item, + ), + })) + } + }, + [setGlobalMenuItems], + ) + + const getAllMenuPaths = useCallback(async (): Promise => { + return Object.entries(globalMenuItems).flatMap(([parentKey, entries]) => + entries + .filter((entry) => !isSeparator(entry)) + .map((entry) => joinPath([parentKey, entry.label])), + ) + }, [globalMenuItems]) + + useEffect(() => { + if (!config?.items) return + + // Clear all existing actions and shortcuts first (only on initial mount) + actionsRef.current.clear() + clearAllShortcuts() + + Object.entries(config.items).forEach(([parentKey, entries]) => { + entries.forEach((entry) => { + if (!isSeparator(entry)) { + const item = entry as SystemMenuItem + if (item.action) { + actionsRef.current.set(joinPath([parentKey, item.label]), item.action) + // Register shortcut if present + if (item.shortcut) { + const resolvedShortcut = + typeof item.shortcut === "object" ? item.shortcut.windows : item.shortcut + if (resolvedShortcut) { + registerShortcut(resolvedShortcut, item.action) + } + } + } + } + }) + }) + + // Update global state directly without calling discoverMenus to avoid redundancy + const menuStructure: SystemMenuStructure = Object.keys(config.items).map((title) => ({ + title, + enabled: true, + path: [title], + items: [], + children: [], + })) + + setGlobalMenuConfig(config) + setGlobalMenuStructure(menuStructure) + setGlobalMenuItems(config.items as Record) + }, []) + + return { + availableMenus: [], + menuStructure: globalMenuStructure, + menuItems: globalMenuItems, + discoverMenus, + addMenuItem, + removeMenuItemByName, + setMenuItemEnabled, + getAllMenuPaths, + handleMenuItemPressed, + } +} diff --git a/app/utils/useSystemMenu/utils.ts b/app/utils/useSystemMenu/utils.ts new file mode 100644 index 0000000..9a3cc35 --- /dev/null +++ b/app/utils/useSystemMenu/utils.ts @@ -0,0 +1,87 @@ +import { type SystemMenuListEntry } from "./types" +import { MENU_SEPARATOR } from "../../components/Menu/types" + +export const PATH_SEPARATOR = " > " + +export const parsePathKey = (key: string): string[] => + key + .split(PATH_SEPARATOR) + .map((s) => s.trim()) + .filter(Boolean) + +export const joinPath = (path: string[]): string => path.join(PATH_SEPARATOR) + +export const isSeparator = (entry: SystemMenuListEntry): entry is typeof MENU_SEPARATOR => + entry === MENU_SEPARATOR + +export interface KeyCombination { + ctrl: boolean + alt: boolean + shift: boolean + cmd: boolean + key: string +} + +/** + * Parse a shortcut string like "ctrl+shift+n" into a key combination object + * @param shortcut - Shortcut string (e.g., "ctrl+n", "ctrl+shift+f", "alt+f4") + * @returns KeyCombination object for matching against keyboard events + */ +export function parseShortcut(shortcut: string): KeyCombination | null { + if (!shortcut?.trim()) return null + + const parts = shortcut + .toLowerCase() + .split("+") + .map((s) => s.trim()) + .filter(Boolean) + if (parts.length === 0) return null + + const combination: KeyCombination = { ctrl: false, alt: false, shift: false, cmd: false, key: "" } + + for (const part of parts) { + switch (part) { + case "ctrl": + case "control": + combination.ctrl = true + break + case "alt": + case "option": + combination.alt = true + break + case "shift": + combination.shift = true + break + case "cmd": + case "command": + case "win": + case "windows": + combination.cmd = true + break + default: + if (!combination.key) combination.key = part.toUpperCase() + break + } + } + + return combination.key ? combination : null +} + +/** + * Check if a keyboard event matches a key combination + * @param event - Keyboard event from native keyboard hook + * @param combination - Parsed key combination to match against + * @returns true if the event matches the combination + */ +export function matchesKeyCombo(event: any, combination: KeyCombination): boolean { + if (!event?.modifiers || !combination) return false + + const { modifiers } = event + return ( + modifiers.ctrl === combination.ctrl && + modifiers.alt === combination.alt && + modifiers.shift === combination.shift && + modifiers.cmd === combination.cmd && + event.key?.toUpperCase() === combination.key.toUpperCase() + ) +} diff --git a/bin/setup-windows.ps1 b/bin/setup-windows.ps1 new file mode 100644 index 0000000..dc1d487 --- /dev/null +++ b/bin/setup-windows.ps1 @@ -0,0 +1,11 @@ +echo "Downloading the RN windows dependencies manifest..." +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/joshuayoes/ReactNativeWindowsSandbox/main/ReactNativeWindows.dsc.yaml" -OutFile "ReactNativeWindows.dsc.yaml" +echo "Installing RN windows deps..." +winget configure ReactNativeWindows.dsc.yaml +echo "Downloading the windows dev tools manifest..." +Invoke-WebRequest -Uri "https://raw.githubusercontent.com/joshuayoes/ReactNativeWindowsSandbox/main/WindowsDevTools.dsc.yaml" -OutFile "WindowsDevTools.dsc.yaml" +echo "Installing windows dev tools..." +winget configure WindowsDevTools.dsc.yaml +echo "Installing react-native-windows dependencies..." +.\node_modules\react-native-windows\scripts\rnw-dependencies.ps1 +echo "Windows setup complete!" diff --git a/package-lock.json b/package-lock.json index 253ae42..166128f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "@expo-google-fonts/space-grotesk": "^0.3.0", "@legendapp/list": "^1.1.4", "react": "19.0.0", - "react-native-macos": "^0.78.2", + "react-native-macos": "0.78.2", "react-native-mmkv": "^3.3.0", - "react-native-windows": "^0.78.5", + "react-native-windows": "0.78.5", "reactotron-core-contract": "^0.3.2" }, "devDependencies": { @@ -60,6 +60,54 @@ "node": ">=6.0.0" } }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -2711,12 +2759,11 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.9.tgz", - "integrity": "sha512-T8s5qROH7caBNiFrUpN8vgC6wg7QysVPryZKprgl3kLQQPpoMFM6ffIYvUWD74KM9fWWLU7vzFFNBWDBsrTyWg==", - "license": "MIT", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.10.tgz", + "integrity": "sha512-5fSZmkGwWkH+mrIA5M1GYPZdPM+SjXwCCl2Am7VhFoVwOBJNhRnwvIpAdzw6sFjiebN/rz+/YH0NdxztGZSa9Q==", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.9", + "@microsoft/applicationinsights-core-js": "3.3.10", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/ts-async": ">= 0.5.4 < 2.x", @@ -2724,12 +2771,11 @@ } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.9.tgz", - "integrity": "sha512-BvxI4CW8Ws+gfXKy+Y/9pmEXp88iU1GYVjkUfqXP7La59VHARTumlG5iIgMVvaifOrvSW7G6knvQM++0tEfMBQ==", - "license": "MIT", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.10.tgz", + "integrity": "sha512-VSLjc9cT+Y+eTiSfYltJHJCejn8oYr0E6Pq2BMhOEO7F6IyLGYIxzKKvo78ze9x+iHX7KPTATcZ+PFgjGXuNqg==", "dependencies": { - "@microsoft/1ds-core-js": "4.3.9", + "@microsoft/1ds-core-js": "4.3.10", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", "@nevware21/ts-async": ">= 0.5.4 < 2.x", @@ -2737,10 +2783,9 @@ } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.9.tgz", - "integrity": "sha512-xliiE9H09xCycndlua4QjajN8q5k/ET6VCv+e0Jjodxr9+cmoOP/6QY9dun9ptokuwR8TK0qOaIJ8z4fgslVSA==", - "license": "MIT", + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.10.tgz", + "integrity": "sha512-5yKeyassZTq2l+SAO4npu6LPnbS++UD+M+Ghjm9uRzoBwD8tumFx0/F8AkSVqbniSREd+ztH/2q2foewa2RZyg==", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", @@ -2755,7 +2800,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", - "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } @@ -2764,7 +2808,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", - "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } @@ -2773,7 +2816,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.4.tgz", "integrity": "sha512-IBTyj29GwGlxfzXw2NPnzty+w0Adx61Eze1/lknH/XIVdxtF9UnOpk76tnrHXWa6j84a1RR9hsOcHQPFv9qJjA==", - "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.11.6 < 2.x" } @@ -2781,8 +2823,7 @@ "node_modules/@nevware21/ts-utils": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.12.5.tgz", - "integrity": "sha512-JPQZWPKQJjj7kAftdEZL0XDFfbMgXCGiUAZe0d7EhLC3QlXTlZdSckGqqRIQ2QNl0VTEZyZUvRBw6Ednw089Fw==", - "license": "MIT" + "integrity": "sha512-JPQZWPKQJjj7kAftdEZL0XDFfbMgXCGiUAZe0d7EhLC3QlXTlZdSckGqqRIQ2QNl0VTEZyZUvRBw6Ednw089Fw==" }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", @@ -3146,30 +3187,6 @@ "resolved": "https://registry.npmjs.org/@react-native-mac/virtualized-lists/-/virtualized-lists-0.78.2.tgz", "integrity": "sha512-BtT/qFrSbAyts9Ugnk1lLlqqTphDA/VumRsDRA7rz7GWaKtBJhGiH4/Bd0n94BeoUE+sQ0tZxD4YiTKPVqxZmA==", "license": "MIT", - "peer": true, - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": "^19.0.0", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@react-native-macos/virtualized-lists": { - "version": "0.78.6", - "resolved": "https://registry.npmjs.org/@react-native-macos/virtualized-lists/-/virtualized-lists-0.78.6.tgz", - "integrity": "sha512-As43Mg/QWVUXKCmNlZhCfcBNplKxcIuY7jrjE0aN6yNBE8mV66MnWjfneTFHqZ0Z3ax34shtPan13/94IZW3pA==", - "license": "MIT", "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -3189,20 +3206,18 @@ } }, "node_modules/@react-native-windows/cli": { - "version": "0.78.9", - "resolved": "https://registry.npmjs.org/@react-native-windows/cli/-/cli-0.78.9.tgz", - "integrity": "sha512-lpG5tHeEvTbr/6OGaCRdrU3hHXe+EnVM7euCEsyIInoo1QA5qx8Hz33D+kO0t4ewWTI/Xr3iZIqdXeujHDAUTg==", - "license": "MIT", + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/@react-native-windows/cli/-/cli-0.78.2.tgz", + "integrity": "sha512-F5l0P7j1LpsWWkenIa118GSQhBZXLWU2V30E+xePh52Yp2H4WmGnvG41Ie8LiTLXs5BRwmzuGbOIgjYbHhK2pw==", "dependencies": { - "@react-native-windows/codegen": "0.78.3", - "@react-native-windows/fs": "0.78.1", - "@react-native-windows/package-utils": "0.78.1", - "@react-native-windows/telemetry": "0.78.2", + "@react-native-windows/codegen": "0.78.1", + "@react-native-windows/fs": "0.78.0", + "@react-native-windows/package-utils": "0.78.0", + "@react-native-windows/telemetry": "0.78.0", "@xmldom/xmldom": "^0.7.7", "chalk": "^4.1.0", "cli-spinners": "^2.2.0", "envinfo": "^7.5.0", - "execa": "^5.0.0", "find-up": "^4.1.0", "glob": "^7.1.1", "lodash": "^4.17.15", @@ -3227,7 +3242,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "license": "MIT", "engines": { "node": ">=6" } @@ -3236,7 +3250,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -3248,7 +3261,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "license": "MIT", "dependencies": { "restore-cursor": "^2.0.0" }, @@ -3260,7 +3272,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -3268,14 +3279,12 @@ "node_modules/@react-native-windows/cli/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/@react-native-windows/cli/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -3284,7 +3293,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3297,7 +3305,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", "engines": { "node": ">=4" } @@ -3306,7 +3313,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -3318,7 +3324,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "license": "MIT", "dependencies": { "chalk": "^2.0.1" }, @@ -3330,7 +3335,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3344,7 +3348,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "license": "MIT", "engines": { "node": ">=4" } @@ -3353,7 +3356,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "license": "MIT", "dependencies": { "mimic-fn": "^1.0.0" }, @@ -3365,7 +3367,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", - "license": "MIT", "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", @@ -3382,7 +3383,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3396,7 +3396,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -3411,7 +3410,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -3423,7 +3421,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "license": "MIT", "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -3433,10 +3430,9 @@ } }, "node_modules/@react-native-windows/cli/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "bin": { "semver": "bin/semver.js" }, @@ -3448,7 +3444,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "license": "MIT", "dependencies": { "ansi-regex": "^4.1.0" }, @@ -3460,7 +3455,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -3469,12 +3463,11 @@ } }, "node_modules/@react-native-windows/codegen": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native-windows/codegen/-/codegen-0.78.3.tgz", - "integrity": "sha512-SANoBXhFEcpwJuLrTwJPXzdchvni6Me1kFg6/KWX/QSWTRPy7tyHUKTN4X1QqwxHCLEOtwsgbrfq15Y49pfzeA==", - "license": "MIT", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native-windows/codegen/-/codegen-0.78.1.tgz", + "integrity": "sha512-qZZMrgTCkjVK93GQ39hrbEPxnbHV0F+v7gVf9xlndqYcgyL5MqlEP29Nx9TEQsi/bZ9MqJpQJqz1ydtJIl3jYA==", "dependencies": { - "@react-native-windows/fs": "0.78.1", + "@react-native-windows/fs": "0.78.0", "chalk": "^4.1.0", "globby": "^11.1.0", "mustache": "^4.0.1", @@ -3495,7 +3488,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -3506,7 +3498,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -3516,7 +3507,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "license": "MIT", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -3534,18 +3524,16 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/@react-native-windows/find-repo-root": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@react-native-windows/find-repo-root/-/find-repo-root-0.78.1.tgz", - "integrity": "sha512-2O0yMNcERh5D3bq8jWo6BCAckZEEbB+EDOqtLm8AQb9VVVvI1CNyA7RSk6rzQFVEPvLDSp4rxsI4FlbS7LNqhg==", - "license": "MIT", + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native-windows/find-repo-root/-/find-repo-root-0.78.0.tgz", + "integrity": "sha512-ts3zioNoo8M0Fct1m1w657Bn7KZ5ILGaprW0HBUUWNmc3NPBg1qfKMq0dZ1ivwpBTwKEcqyCrfJ7MMLFMhH7qQ==", "dependencies": { - "@react-native-windows/fs": "0.78.1", + "@react-native-windows/fs": "0.78.0", "find-up": "^4.1.0" }, "engines": { @@ -3556,7 +3544,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3569,7 +3556,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -3581,7 +3567,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -3596,7 +3581,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -3605,22 +3589,20 @@ } }, "node_modules/@react-native-windows/fs": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@react-native-windows/fs/-/fs-0.78.1.tgz", - "integrity": "sha512-GYIElnKFv2TUhbYvduNrXclHNqWFZxelttslv2f14jocF7gwjHYJg3tjQJtf5lk1KRlMYddl9EO+VR+tCgOr7A==", - "license": "MIT", + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native-windows/fs/-/fs-0.78.0.tgz", + "integrity": "sha512-7RasiN+xySObTujqkpujTsY2ZMv95JVvvY5CytoJuy1NFoKRyXyxBsJtAVWDNx3tGpl1E2xE7H8ObCiT/mnVuQ==", "dependencies": { "graceful-fs": "^4.2.8" } }, "node_modules/@react-native-windows/package-utils": { - "version": "0.78.1", - "resolved": "https://registry.npmjs.org/@react-native-windows/package-utils/-/package-utils-0.78.1.tgz", - "integrity": "sha512-dyAmdJ4iNJcbq+SZfqzaZPglLEvs9/KsRN9J37QrK6gx/r0xEt3dAJVmDEMnZ4oW9znTfA+lku6i8SQN1XOU9w==", - "license": "MIT", + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native-windows/package-utils/-/package-utils-0.78.0.tgz", + "integrity": "sha512-6KaHvQRUxZsm52HgLhdzU/ySoNroJH+m/trSRxsIDx1x1VKUhr6SzwNYSfx5ec7TaGt6gYtQwYLYVj7L+hwdIw==", "dependencies": { - "@react-native-windows/find-repo-root": "0.78.1", - "@react-native-windows/fs": "0.78.1", + "@react-native-windows/find-repo-root": "0.78.0", + "@react-native-windows/fs": "0.78.0", "get-monorepo-packages": "^1.2.0", "lodash": "^4.17.15" }, @@ -3629,14 +3611,14 @@ } }, "node_modules/@react-native-windows/telemetry": { - "version": "0.78.2", - "resolved": "https://registry.npmjs.org/@react-native-windows/telemetry/-/telemetry-0.78.2.tgz", - "integrity": "sha512-IkgZBNp/Nsf7lb2Cs1+TJrwUFR2x7sITkX4IrDcDvMvLVw0QQGPIDwE7c4hm/DKDXQa4/Dz3SWwCH76qLScKKg==", - "license": "MIT", + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native-windows/telemetry/-/telemetry-0.78.0.tgz", + "integrity": "sha512-q7Y9gZUFuLHi4GOMmNkgVO4UWdy7lQEh9wcN8GEHiW0HDyMM7vzeo8EYPr1iUcM//xuCayj3FuYwhP+W17C+4g==", "dependencies": { + "@azure/core-auth": "1.5.0", "@microsoft/1ds-core-js": "^4.3.0", "@microsoft/1ds-post-js": "^4.3.0", - "@react-native-windows/fs": "0.78.1", + "@react-native-windows/fs": "0.78.0", "@xmldom/xmldom": "^0.7.7", "ci-info": "^3.2.0", "envinfo": "^7.8.1", @@ -3656,6 +3638,7 @@ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.2.tgz", "integrity": "sha512-VHqQqjj1rnh2KQeS3yx4IfFSxIIIDi1jR4yUeC438Q6srwxDohR4W0UkXuSIz0imhlems5eS7yZTjdgSpWHRUQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3758,6 +3741,7 @@ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.2.tgz", "integrity": "sha512-xqEnpqxvBlm02mRY58L0NBjF25MTHmbaeA2qBx5VtheH/pXL6MHUbtwB1Q2dJrg9XcK0Np1i9h7N5h9gFwA2Mg==", "license": "MIT", + "peer": true, "dependencies": { "@react-native/dev-middleware": "0.78.2", "@react-native/metro-babel-transformer": "0.78.2", @@ -3787,6 +3771,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -3795,13 +3780,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/community-cli-plugin/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -3814,6 +3801,7 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.2.tgz", "integrity": "sha512-qNJT679OU/cdAKmZxfBFjqTG+ZC5i/4sLyvbcQjFFypunGSOaWl3mMQFQQdCBIQN+DFDPVSUXTPZQK1uI2j/ow==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=18" } @@ -3823,6 +3811,7 @@ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.2.tgz", "integrity": "sha512-/u0pGiWVgvx09cYNO4/Okj8v1ZNt4K941pQJPhdwg5AHYuggVHNJjROukXJzZiElYFcJhMfOuxwksiIyx/GAkA==", "license": "MIT", + "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.78.2", @@ -3846,6 +3835,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -3855,6 +3845,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -3866,13 +3857,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/dev-middleware/node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -3940,6 +3933,7 @@ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.2.tgz", "integrity": "sha512-LHgmdrbyK9fcBDdxtn2GLOoDAE+aFHtDHgu6vUZ5CSCi9CMd5Krq8IWAmWjeq+BQr+D1rwSXDAHtOrfJ6qOolA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3991,7 +3985,8 @@ "version": "0.78.2", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.2.tgz", "integrity": "sha512-CA/3ynRO6/g1LDbqU8ewrv0js/1lU4+j04L7qz6btXbLTDk1UkF+AfpGRJGbIVY9UmFBJ7l1AOmzwutrWb3Txw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/typescript-config": { "version": "0.78.2", @@ -4001,10 +3996,9 @@ "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.78.2", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.78.2.tgz", - "integrity": "sha512-y/wVRUz1ImR2hKKUXFroTdSBiL0Dd+oudzqcGKp/M8Ybrw9MQ0m2QCXxtyONtDn8qkEGceqllwTCKq5WQwJcew==", - "license": "MIT", + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.78.0.tgz", + "integrity": "sha512-ibETs3AwpkkRcORRANvZeEFjzvN41W02X882sBzoxC5XdHiZ2DucXo4fjKF7i86MhYCFLfNSIYbwupx1D1iFmg==", "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -4490,6 +4484,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.1.tgz", + "integrity": "sha512-SnbaqayTVFEA6/tYumdF0UmybY0KHyKwGPBXnyckFlrrKdhWFrL3a2HIPXHjht5ZOElKGcXfD2D63P36btb+ww==", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -4509,7 +4516,6 @@ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", "deprecated": "this version is no longer supported, please update to at least 0.8.*", - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -4577,6 +4583,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4763,7 +4777,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6099,7 +6112,6 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -7545,7 +7557,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-monorepo-packages/-/get-monorepo-packages-1.3.0.tgz", "integrity": "sha512-A/s881nNcKhoM7RgkvYFTOtGO+dy4EWbyRaatncPEhhlJAaZRlpfHwuT68p5GJenEt81nnjJOwGg0WKLkR5ZdQ==", - "license": "MIT", "dependencies": { "globby": "^7.1.1", "load-json-file": "^4.0.0" @@ -7555,7 +7566,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "license": "MIT", "dependencies": { "array-uniq": "^1.0.1" }, @@ -7567,7 +7577,6 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "license": "MIT", "dependencies": { "path-type": "^3.0.0" }, @@ -7579,7 +7588,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", - "license": "MIT", "dependencies": { "array-union": "^1.0.1", "dir-glob": "^2.0.0", @@ -7595,14 +7603,12 @@ "node_modules/get-monorepo-packages/node_modules/ignore": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "license": "MIT" + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, "node_modules/get-monorepo-packages/node_modules/path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "license": "MIT", "dependencies": { "pify": "^3.0.0" }, @@ -7614,7 +7620,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7946,6 +7951,30 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -8093,7 +8122,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "license": "MIT", "engines": { "node": ">= 0.10" } @@ -8111,7 +8139,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz", "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==", - "license": "MIT", "engines": { "node": ">=8" }, @@ -9903,7 +9930,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz", "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==", - "license": "MIT", "dependencies": { "invert-kv": "^3.0.0" }, @@ -9969,7 +9995,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -9984,7 +10009,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "license": "MIT", "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -9997,7 +10021,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "license": "MIT", "engines": { "node": ">=4" } @@ -10253,7 +10276,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "license": "MIT", "dependencies": { "p-defer": "^1.0.0" }, @@ -10291,7 +10313,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz", "integrity": "sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==", - "license": "MIT", "dependencies": { "map-age-cleaner": "^0.1.3", "mimic-fn": "^2.1.0", @@ -10855,7 +10876,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "license": "MIT", "bin": { "mustache": "bin/mustache" } @@ -10885,8 +10905,7 @@ "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "license": "MIT" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/nocache": { "version": "3.0.4", @@ -10974,7 +10993,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11180,7 +11198,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-5.0.0.tgz", "integrity": "sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==", - "license": "MIT", "dependencies": { "execa": "^4.0.0", "lcid": "^3.0.0", @@ -11197,7 +11214,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "license": "MIT", "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -11220,7 +11236,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -11235,7 +11250,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "license": "Apache-2.0", "engines": { "node": ">=8.12.0" } @@ -11272,7 +11286,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "license": "MIT", "engines": { "node": ">=4" } @@ -11281,7 +11294,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", "engines": { "node": ">=4" } @@ -11290,7 +11302,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "license": "MIT", "engines": { "node": ">=6" } @@ -11587,7 +11598,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "license": "MIT", "engines": { "node": ">=4" } @@ -11807,7 +11817,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -12029,19 +12038,20 @@ } }, "node_modules/react-native-macos": { - "version": "0.78.6", - "resolved": "https://registry.npmjs.org/react-native-macos/-/react-native-macos-0.78.6.tgz", - "integrity": "sha512-oP6NNr1oAJt4tckOr5nascd5PQxKPQidvrlAQu185NtclMQL1BQhyHaKTvEosgLqKnLeN/iIHmlljuCnoeDAxw==", + "version": "0.78.2", + "resolved": "https://registry.npmjs.org/react-native-macos/-/react-native-macos-0.78.2.tgz", + "integrity": "sha512-q6PsExdr0bVQBqYxLCAoOXQUhoKqqMztyapinh/r0obRhGE7A2StL89Pjaly+fOu0tBj1UtTfM6eTeprT18e0Q==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.6.3", - "@react-native-macos/virtualized-lists": "0.78.6", - "@react-native/assets-registry": "0.78.3", - "@react-native/codegen": "0.78.3", - "@react-native/community-cli-plugin": "0.78.3", - "@react-native/gradle-plugin": "0.78.3", - "@react-native/js-polyfills": "0.78.3", - "@react-native/normalize-colors": "0.78.3", + "@react-native-mac/virtualized-lists": "0.78.2", + "@react-native/assets-registry": "0.78.1", + "@react-native/codegen": "0.78.1", + "@react-native/community-cli-plugin": "0.78.1", + "@react-native/gradle-plugin": "0.78.1", + "@react-native/js-polyfills": "0.78.1", + "@react-native/normalize-colors": "0.78.1", + "@react-native/virtualized-lists": "0.78.1", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -12056,8 +12066,8 @@ "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "memoize-one": "^5.0.0", - "metro-runtime": "^0.81.3", - "metro-source-map": "^0.81.3", + "metro-runtime": "^0.81.0", + "metro-source-map": "^0.81.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", @@ -12079,8 +12089,7 @@ }, "peerDependencies": { "@types/react": "^19.0.0", - "react": "^19.0.0", - "react-native": "0.78.3" + "react": "^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -12089,31 +12098,31 @@ } }, "node_modules/react-native-macos/node_modules/@react-native/assets-registry": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.3.tgz", - "integrity": "sha512-gQGoxEq7CuY/LjnHjORrNnJzUkx0YH7r/U1bvdznaaZ4CLcRFa1nKZEmZMv0h9moVqzr7GUbphJzS+RwqoGYIg==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.1.tgz", + "integrity": "sha512-SegfYQFuut05EQIQIVB/6QMGaxJ29jEtPmzFWJdIp/yc2mmhIq7MfWRjwOe6qbONzIdp6Ca8p835hiGiAGyeKQ==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/react-native-macos/node_modules/@react-native/babel-plugin-codegen": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.3.tgz", - "integrity": "sha512-yKs7KR9CzqGaM8mZi4vdjgaNgqomj094U325h2GWqsdj9+m/lf8e/Crd9sLDFtK0W2UCbcVw2L+M8okqXJ3oHw==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.1.tgz", + "integrity": "sha512-rD0tnct/yPEtoOc8eeFHIf8ZJJJEzLkmqLs8HZWSkt3w9VYWngqLXZxiDGqv0ngXjunAlC/Hpq+ULMVOvOnByw==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.78.3" + "@react-native/codegen": "0.78.1" }, "engines": { "node": ">=18" } }, "node_modules/react-native-macos/node_modules/@react-native/babel-preset": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.3.tgz", - "integrity": "sha512-L1DRY8CYbrnpFoqVgeRW1FO8ZfgagYd3nx0M+9oaqG/VFX5rrfoMt011ZDeoYpmNayZS7klkqCFQLXVWAMPNBA==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.1.tgz", + "integrity": "sha512-yTVcHmEdNQH4Ju7lhvbiQaGxBpMcalgkBy/IvHowXKk/ex3nY1PolF16/mBG1BrefcUA/rtJpqTtk2Ii+7T/Lw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -12157,7 +12166,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.78.3", + "@react-native/babel-plugin-codegen": "0.78.1", "babel-plugin-syntax-hermes-parser": "0.25.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" @@ -12170,9 +12179,9 @@ } }, "node_modules/react-native-macos/node_modules/@react-native/codegen": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.3.tgz", - "integrity": "sha512-p6mbFm6vvDskMj3zBzFIhHc85i2G/f47HwkFLJYSdWUITrPaVlXLSjSoCQPhYSNqrMv2g376OZZ+QXjp50XnTQ==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.1.tgz", + "integrity": "sha512-kGG5qAM9JdFtxzUwe7c6CyJbsU2PnaTrtCHA2dF8VEiNX1K3yd9yKPzfkxA7HPvmHoAn3ga1941O79BStWcM3A==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", @@ -12191,19 +12200,19 @@ } }, "node_modules/react-native-macos/node_modules/@react-native/community-cli-plugin": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.3.tgz", - "integrity": "sha512-Ax4mYFHxWH7xDsfPr7UR+WHBXAv3rXNzROEc7xVNsbNtpNVTHSqawUfDzH8jCO4rJEYQU18RARHwhBIXKwLFew==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.1.tgz", + "integrity": "sha512-S6vF4oWpFqThpt/dBLrqLQw5ED2M1kg5mVtiL6ZqpoYIg+/e0vg7LZ8EXNbcdMDH4obRnm2xbOd+qlC7mOzNBg==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.78.3", - "@react-native/metro-babel-transformer": "0.78.3", + "@react-native/dev-middleware": "0.78.1", + "@react-native/metro-babel-transformer": "0.78.1", "chalk": "^4.0.0", "debug": "^2.2.0", "invariant": "^2.2.4", - "metro": "^0.81.3", - "metro-config": "^0.81.3", - "metro-core": "^0.81.3", + "metro": "^0.81.0", + "metro-config": "^0.81.0", + "metro-core": "^0.81.0", "readline": "^1.3.0", "semver": "^7.1.3" }, @@ -12220,22 +12229,22 @@ } }, "node_modules/react-native-macos/node_modules/@react-native/debugger-frontend": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.3.tgz", - "integrity": "sha512-ImYGtEI9zsF/pietY45M8vd3OVWEkECbOngOhul0GVHECBsSHuOaQ/8PoxWl9Rps+8p1048aIMsPT9QzEtGwtQ==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.1.tgz", + "integrity": "sha512-xev/B++QLxSDpEBWsc74GyCuq9XOHYTBwcGSpsuhOJDUha6WZIbEEvZe3LpVW+OiFso4oGIdnVSQntwippZdWw==", "license": "BSD-3-Clause", "engines": { "node": ">=18" } }, "node_modules/react-native-macos/node_modules/@react-native/dev-middleware": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.3.tgz", - "integrity": "sha512-7upCJUYTFt3AwDQqByWDmTdlHYU93AdU+rsndis2xsJI4h7DrEjKtvvEgFOJG+jGHcyct9vNu1S+Jj2g8DRguQ==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.1.tgz", + "integrity": "sha512-l8p7/dXa1vWPOdj0iuACkex8lgbLpYyPZ3QXGkocMcpl0bQ24K7hf3Bj02tfptP5PAm16b2RuEi04sjIGHUzzg==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.78.3", + "@react-native/debugger-frontend": "0.78.1", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", @@ -12252,31 +12261,31 @@ } }, "node_modules/react-native-macos/node_modules/@react-native/gradle-plugin": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.3.tgz", - "integrity": "sha512-Nrg3TRd/kjE+qOvukqeP5GqD1/oMd25X2yv370lWHBt9d0RJ0d008almkb5fHxQa+vKPeiAEhK726qCX8YXvIQ==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.1.tgz", + "integrity": "sha512-v8GJU+8DzQDWO3iuTFI1nbuQ/kzuqbXv07VVtSIMLbdofHzuuQT14DGBacBkrIDKBDTVaBGAc/baDNsyxCghng==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/react-native-macos/node_modules/@react-native/js-polyfills": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.3.tgz", - "integrity": "sha512-RvWAV2qU+XgMRVF+WIJQIqKdfrth1ghhdzAoKkXpXRKgWPps/6ZSCFgxkSjYaxAwXREOEx8/HunSmXDCsW+0ag==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.1.tgz", + "integrity": "sha512-Ogcv4QOA1o3IyErrf/i4cDnP+nfNcIfGTgw6iNQyAPry1xjPOz4ziajskLpWG/3ADeneIZuyZppKB4A28rZSvg==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/react-native-macos/node_modules/@react-native/metro-babel-transformer": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.3.tgz", - "integrity": "sha512-VSzAJ5G7uD1F5nG6NagHZFq6Q6dpsCU6LH+2j7iTsXZ9QUSds54f+WP5RC0UHZcVkQavSfqzu3+wj4pYGv5Pzg==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.1.tgz", + "integrity": "sha512-jQWf69D+QTMvSZSWLR+cr3VUF16rGB6sbD+bItD8Czdfn3hajzfMoHJTkVFP7991cjK5sIVekNiQIObou8JSQw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.78.3", + "@react-native/babel-preset": "0.78.1", "hermes-parser": "0.25.1", "nullthrows": "^1.1.1" }, @@ -12288,11 +12297,34 @@ } }, "node_modules/react-native-macos/node_modules/@react-native/normalize-colors": { - "version": "0.78.3", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.3.tgz", - "integrity": "sha512-/Nbuhc65xSVE3KFCejQEI9pgF+uwArj6EMHMVCkRtUqkM88Ng+f+8E7PyNN0hDUnj2Vr30FwBczdwm1kQLTWZA==", + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.1.tgz", + "integrity": "sha512-h4wARnY4iBFgigN1NjnaKFtcegWwQyE9+CEBVG4nHmwMtr8lZBmc7ZKIM6hUc6lxqY/ugHg48aSQSynss7mJUg==", "license": "MIT" }, + "node_modules/react-native-macos/node_modules/@react-native/virtualized-lists": { + "version": "0.78.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.78.1.tgz", + "integrity": "sha512-v0jqDNMFXpnRnSlkDVvwNxXgPhifzzTFlxTSnHj9erKJsKpE26gSU5qB4hmJkEsscLG/ygdJ1c88aqinSh/wRA==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^19.0.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-native-macos/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -12378,9 +12410,9 @@ "license": "MIT" }, "node_modules/react-native-macos/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12400,25 +12432,24 @@ } }, "node_modules/react-native-windows": { - "version": "0.78.14", - "resolved": "https://registry.npmjs.org/react-native-windows/-/react-native-windows-0.78.14.tgz", - "integrity": "sha512-Fo5FTf2b1YVhMDK021uYHFiYQTwdQJYgFw60yjnRfC6CMBv5e2CYfh9LaT8cRHSnKqSyJ5nVqeGYHcJHon4xPA==", - "license": "MIT", + "version": "0.78.5", + "resolved": "https://registry.npmjs.org/react-native-windows/-/react-native-windows-0.78.5.tgz", + "integrity": "sha512-ItdDzbifrMxj/6dVkrp2AtzjeAuaqH9cRe92qPXX6BtYfKeL5nw4eKRUXtySMaiyonP3spsWJvgvkC5ktR4Rug==", "dependencies": { "@babel/runtime": "^7.0.0", "@jest/create-cache-key-function": "^29.6.3", "@react-native-community/cli": "^15.0.0", "@react-native-community/cli-platform-android": "^15.0.0", "@react-native-community/cli-platform-ios": "^15.0.0", - "@react-native-windows/cli": "0.78.9", + "@react-native-windows/cli": "0.78.2", "@react-native/assets": "1.0.0", - "@react-native/assets-registry": "0.78.2", - "@react-native/codegen": "0.78.2", - "@react-native/community-cli-plugin": "0.78.2", - "@react-native/gradle-plugin": "0.78.2", - "@react-native/js-polyfills": "0.78.2", - "@react-native/normalize-colors": "0.78.2", - "@react-native/virtualized-lists": "0.78.2", + "@react-native/assets-registry": "0.78.0", + "@react-native/codegen": "0.78.0", + "@react-native/community-cli-plugin": "0.78.0", + "@react-native/gradle-plugin": "0.78.0", + "@react-native/js-polyfills": "0.78.0", + "@react-native/normalize-colors": "0.78.0", + "@react-native/virtualized-lists": "0.78.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -12433,14 +12464,15 @@ "invariant": "^2.2.4", "jest-environment-node": "^29.6.3", "memoize-one": "^5.0.0", - "metro-runtime": "^0.81.3", - "metro-source-map": "^0.81.3", + "metro-runtime": "^0.81.0", + "metro-source-map": "^0.81.0", "mkdirp": "^0.5.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.0.1", "react-refresh": "^0.14.0", + "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.25.0", "semver": "^7.1.3", @@ -12683,6 +12715,215 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/react-native-windows/node_modules/@react-native/assets-registry": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.78.0.tgz", + "integrity": "sha512-PPHlTRuP9litTYkbFNkwveQFto3I94QRWPBBARU0cH/4ks4EkfCfb/Pdb3AHgtJi58QthSHKFvKTQnAWyHPs7w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/babel-plugin-codegen": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.78.0.tgz", + "integrity": "sha512-+Sy9Uine0QAbQRxMl6kBlkzKW0qHQk8hghCoKswRWt1ZfxaMA3rezobD5mtSwt/Yhadds9cGbMFWfFJM3Tynsg==", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.78.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/babel-preset": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.78.0.tgz", + "integrity": "sha512-q44ZbR0JXdPvNrjNw75VmiVXXoJhZIx8dTUBVgnZx/UHBQuhPu0e8pAuo56E2mZVkF7FK0s087/Zji8n5OSxbQ==", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/template": "^7.25.0", + "@react-native/babel-plugin-codegen": "0.78.0", + "babel-plugin-syntax-hermes-parser": "0.25.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/codegen": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.78.0.tgz", + "integrity": "sha512-8iVT2VYhkalLFUWoQRGSluZZHEG93StfwQGwQ+wk1vOUlOfoT/Xqglt6DvGXIyM9gaMCr6fJBFQVrU+FrXEFYA==", + "dependencies": { + "@babel/parser": "^7.25.3", + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "jscodeshift": "^17.0.0", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/community-cli-plugin": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.78.0.tgz", + "integrity": "sha512-LpfEU+F1hZGcxIf07aBrjlImA0hh8v76V4wTJOgxxqGDUjjQ/X6h9V+bMXne60G9gwccTtvs1G0xiKWNUPI0VQ==", + "dependencies": { + "@react-native/dev-middleware": "0.78.0", + "@react-native/metro-babel-transformer": "0.78.0", + "chalk": "^4.0.0", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "metro": "^0.81.0", + "metro-config": "^0.81.0", + "metro-core": "^0.81.0", + "readline": "^1.3.0", + "semver": "^7.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@react-native-community/cli-server-api": "*" + }, + "peerDependenciesMeta": { + "@react-native-community/cli-server-api": { + "optional": true + } + } + }, + "node_modules/react-native-windows/node_modules/@react-native/debugger-frontend": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.78.0.tgz", + "integrity": "sha512-KQYD9QlxES/VdmXh9EEvtZCJK1KAemLlszQq4dpLU1stnue5N8dnCY6A7PpStMf5UtAMk7tiniQhaicw0uVHgQ==", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/dev-middleware": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.78.0.tgz", + "integrity": "sha512-zEafAZdOz4s37Jh5Xcv4hJE5qZ6uNxgrTLcpjDOJnQG6dO34/BoZeXvDrjomQFNn6ogdysR51mKJStaQ3ixp5A==", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.78.0", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.16.2", + "ws": "^6.2.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/gradle-plugin": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.78.0.tgz", + "integrity": "sha512-WvwgfmVs1QfFl1FOL514kz2Fs5Nkg2BGgpE8V0ild8b/UT6jCD8qh2dTI5kL0xdT0d2Xd2BxfuFN0xCLkMC+SA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/js-polyfills": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.78.0.tgz", + "integrity": "sha512-YZ9XtS77s/df7548B6dszX89ReehnA7hiab/axc30j/Mgk7Wv2woOjBKnAA4+rZ0ITLtxNwyJIMaRAc9kGznXw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/metro-babel-transformer": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.78.0.tgz", + "integrity": "sha512-Hy/dl+zytLCRD9dp32ukcRS1Bn0gZH0h0i3AbriS6OGYgUgjAUFhXOKzZ15/G1SEq2sng91MNo/hMvo4uXoc5A==", + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.78.0", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/react-native-windows/node_modules/@react-native/normalize-colors": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.78.0.tgz", + "integrity": "sha512-FkeLvLLaMYlGsSntixTUvlNtc1OHij4TYRtymMNPWqBKFAMXJB/qe45VxXNzWP+jD0Ok6yXineQFtktKcHk9PA==" + }, "node_modules/react-native-windows/node_modules/@types/yargs": { "version": "15.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", @@ -12701,6 +12942,25 @@ "node": ">=18" } }, + "node_modules/react-native-windows/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/react-native-windows/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/react-native-windows/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -12713,6 +12973,11 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/react-native-windows/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/react-native-windows/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -12745,6 +13010,18 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-native-windows/node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-native-windows/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -13542,7 +13819,6 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "license": "BSD-3-Clause", "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -13967,7 +14243,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14481,7 +14756,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/username/-/username-5.1.0.tgz", "integrity": "sha512-PCKbdWw85JsYMvmCv5GH3kXmM66rCd9m1hBEDutPNv94b/pqCMT4NtcKyeWYvLFiE8b+ha1Jdl8XAaUdPn5QTg==", - "license": "MIT", "dependencies": { "execa": "^1.0.0", "mem": "^4.3.0" @@ -14494,7 +14768,6 @@ "version": "6.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -14510,7 +14783,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "license": "MIT", "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -14528,7 +14800,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -14540,7 +14811,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14549,7 +14819,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "license": "MIT", "dependencies": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -14563,7 +14832,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "license": "MIT", "dependencies": { "path-key": "^2.0.0" }, @@ -14575,7 +14843,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", "engines": { "node": ">=4" } @@ -14584,7 +14851,6 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", "bin": { "semver": "bin/semver" } @@ -14593,7 +14859,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", "dependencies": { "shebang-regex": "^1.0.0" }, @@ -14605,7 +14870,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -14614,7 +14878,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -14860,7 +15123,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-2.6.1.tgz", "integrity": "sha512-dOiGwoqm8y22QdTNI7A+N03tyVfBlQ0/oehAzxIZtwnFAHGeSlrfjF73YQvzSsa/Kt6+YZasKsrdu6OIpuBggw==", - "license": "MIT", "dependencies": { "xml-parser-xo": "^3.2.0" }, @@ -14872,7 +15134,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/xml-parser/-/xml-parser-1.2.1.tgz", "integrity": "sha512-lPUzzmS0zdwcNtyNndCl2IwH172ozkUDqmfmH3FcuDzHVl552Kr6oNfsvteHabqTWhsrMgpijqZ/yT7Wo1/Pzw==", - "license": "MIT", "dependencies": { "debug": "^2.2.0" } @@ -14881,7 +15142,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-3.2.0.tgz", "integrity": "sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg==", - "license": "MIT", "engines": { "node": ">= 10" } @@ -14890,7 +15150,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -14898,14 +15157,12 @@ "node_modules/xml-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/xpath": { "version": "0.0.27", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", - "license": "MIT", "engines": { "node": ">=0.6.0" } diff --git a/package.json b/package.json index b36a5eb..8707110 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,15 @@ "ci": "npm run lint", "start": "REACT_NATIVE_PATH=./node_modules/react-native-macos RCT_SCRIPT_RN_DIR=$REACT_NATIVE_PATH RCT_NEW_ARCH_ENABLED=1 ./node_modules/react-native-macos/scripts/packager.sh start", "test": "jest", - "postinstall": "ln -sf $(pwd)/node_modules/react-native-macos $(pwd)/node_modules/react-native && patch-package", + "postinstall": "if [ \"$(uname)\" = \"Darwin\" ]; then ln -sf $(pwd)/node_modules/react-native-macos $(pwd)/node_modules/react-native; fi && patch-package", "node-process": "node -e \"require('./standalone-server').startReactotronServer({ port: 9292 })\"" }, "dependencies": { "@expo-google-fonts/space-grotesk": "^0.3.0", "@legendapp/list": "^1.1.4", "react": "19.0.0", - "react-native-macos": "^0.78.2", - "react-native-windows": "^0.78.5", + "react-native-macos": "0.78.2", + "react-native-windows": "0.78.5", "react-native-mmkv": "^3.3.0", "reactotron-core-contract": "^0.3.2" }, @@ -164,4 +164,4 @@ "template": "cpp-app" } } -} +} \ No newline at end of file diff --git a/windows/reactotron.Package/packages.lock.json b/windows/reactotron.Package/packages.lock.json index 4dde1ed..6921ced 100644 --- a/windows/reactotron.Package/packages.lock.json +++ b/windows/reactotron.Package/packages.lock.json @@ -4,46 +4,18 @@ "UAP,Version=v10.0.17763": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "boost": { - "type": "Transitive", - "resolved": "1.83.0", - "contentHash": "cy53VNMzysEMvhBixDe8ujPk67Fcj3v6FPHQnH91NYJNLHpc6jxa2xq9ruCaaJjE4M3YrGSHDi4uUSTGBWw6EQ==" - }, - "Microsoft.JavaScript.Hermes": { - "type": "Transitive", - "resolved": "0.0.0-2505.2001-0e4bc3b9", - "contentHash": "VNSUBgaGzJ/KkK3Br0b9FORkCgKqke54hi48vG42xRACIlxN+uLFMz0hRo+KHogz+Fsn+ltXicGwQsDVpmaCMg==" - }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.ReactNative.Cxx": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "d1bRbaozh3h0F9+Urg8QMQmlB1OFhOPumDjP2d51KqROeylSbp8Wo5pNRich8VGUKpM6QurVXNsjcydYqi47Dg==", - "dependencies": { - "Microsoft.ReactNative": "0.78.14-Fabric" - } - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" }, "Microsoft.Windows.SDK.BuildTools": { "type": "Transitive", @@ -51,204 +23,126 @@ "contentHash": "7ZL2sFSioYm1Ry067Kw1hg0SCcW5kuVezC2SwjGbcPE61Nn+gTbH86T73G3LcEOVj0S3IZzNuE/29gZvOLS7VA==" }, "reactotron": { - "type": "Project", - "dependencies": { - "Microsoft.JavaScript.Hermes": "[0.0.0-2505.2001-0e4bc3b9, )", - "Microsoft.ReactNative": "[0.78.14-Fabric, )", - "Microsoft.ReactNative.Cxx": "[0.78.14-Fabric, )", - "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", - "Microsoft.WindowsAppSDK": "[1.7.250401001, )", - "boost": "[1.83.0, )" - } + "type": "Project" } }, "UAP,Version=v10.0.17763/win10-arm": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "UAP,Version=v10.0.17763/win10-arm-aot": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "UAP,Version=v10.0.17763/win10-arm64-aot": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "UAP,Version=v10.0.17763/win10-x64": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "UAP,Version=v10.0.17763/win10-x64-aot": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "UAP,Version=v10.0.17763/win10-x86": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "UAP,Version=v10.0.17763/win10-x86-aot": { "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, - "Microsoft.ReactNative": { - "type": "Transitive", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" - }, - "Microsoft.VCRTForwarders.140": { - "type": "Transitive", - "resolved": "1.0.2-rc", - "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ==" - }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } } } diff --git a/windows/reactotron/IRNativeModules.g.h b/windows/reactotron/IRNativeModules.g.h index 867c32b..4c3745f 100644 --- a/windows/reactotron/IRNativeModules.g.h +++ b/windows/reactotron/IRNativeModules.g.h @@ -10,10 +10,10 @@ #include "../../app/native/IRClipboard/IRClipboard.windows.h" #include "../../app/native/IRFontList/IRFontList.windows.h" #include "../../app/native/IRKeyboard/IRKeyboard.windows.h" -#include "../../app/native/IRMenuItemManager/IRMenuItemManager.windows.h" #include "../../app/native/IRPassthroughView/IRPassthroughView.windows.h" #include "../../app/native/IRRunShellCommand/IRRunShellCommand.windows.h" #include "../../app/native/IRSystemInfo/IRSystemInfo.windows.h" +#include "../../app/native/IRSystemMenuManager/IRSystemMenuManager.windows.h" #include "../../app/native/IRTabComponentView/IRTabComponentView.windows.h" #include "../../app/utils/experimental/IRExperimental.windows.h" #include "../../app/utils/random/IRRandom.windows.h" diff --git a/windows/reactotron/packages.lock.json b/windows/reactotron/packages.lock.json index 8cf1d4c..068c537 100644 --- a/windows/reactotron/packages.lock.json +++ b/windows/reactotron/packages.lock.json @@ -10,23 +10,23 @@ }, "Microsoft.JavaScript.Hermes": { "type": "Direct", - "requested": "[0.0.0-2505.2001-0e4bc3b9, )", - "resolved": "0.0.0-2505.2001-0e4bc3b9", - "contentHash": "VNSUBgaGzJ/KkK3Br0b9FORkCgKqke54hi48vG42xRACIlxN+uLFMz0hRo+KHogz+Fsn+ltXicGwQsDVpmaCMg==" + "requested": "[0.1.23, )", + "resolved": "0.1.23", + "contentHash": "cA9t1GjY4Yo0JD1AfA//e1lOwk48hLANfuX6GXrikmEBNZVr2TIX5ONJt5tqCnpZyLz6xGiPDgTfFNKbSfb21g==" }, "Microsoft.ReactNative": { "type": "Direct", - "requested": "[0.78.14-Fabric, )", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" + "requested": "[0.78.5-Fabric, )", + "resolved": "0.78.5-Fabric", + "contentHash": "oN/M8Ob7LVMlCIgyshPWk50dKK8XJuA6pw0IFH8fP6v3+s2OEcvsH2dhxS5GMtJjffeP/y5sBMaY7p+bAKjf7A==" }, "Microsoft.ReactNative.Cxx": { "type": "Direct", - "requested": "[0.78.14-Fabric, )", - "resolved": "0.78.14-Fabric", - "contentHash": "d1bRbaozh3h0F9+Urg8QMQmlB1OFhOPumDjP2d51KqROeylSbp8Wo5pNRich8VGUKpM6QurVXNsjcydYqi47Dg==", + "requested": "[0.78.5-Fabric, )", + "resolved": "0.78.5-Fabric", + "contentHash": "NpMlcXQiSo8wQiTQ34t9v/5fUcxibBSfVMaAEc7G0C72lIetijXjagufbwL8Q+z/1F3zEMH3LdmpUHTFc/+Kfw==", "dependencies": { - "Microsoft.ReactNative": "0.78.14-Fabric" + "Microsoft.ReactNative": "0.78.5-Fabric" } }, "Microsoft.VCRTForwarders.140": { @@ -43,18 +43,18 @@ }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" }, "Microsoft.Windows.SDK.BuildTools": { "type": "Transitive", @@ -65,9 +65,9 @@ "native,Version=v0.0/win": { "Microsoft.ReactNative": { "type": "Direct", - "requested": "[0.78.14-Fabric, )", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" + "requested": "[0.78.5-Fabric, )", + "resolved": "0.78.5-Fabric", + "contentHash": "oN/M8Ob7LVMlCIgyshPWk50dKK8XJuA6pw0IFH8fP6v3+s2OEcvsH2dhxS5GMtJjffeP/y5sBMaY7p+bAKjf7A==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", @@ -77,26 +77,26 @@ }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "native,Version=v0.0/win-arm64": { "Microsoft.ReactNative": { "type": "Direct", - "requested": "[0.78.14-Fabric, )", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" + "requested": "[0.78.5-Fabric, )", + "resolved": "0.78.5-Fabric", + "contentHash": "oN/M8Ob7LVMlCIgyshPWk50dKK8XJuA6pw0IFH8fP6v3+s2OEcvsH2dhxS5GMtJjffeP/y5sBMaY7p+bAKjf7A==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", @@ -106,26 +106,26 @@ }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "native,Version=v0.0/win-x64": { "Microsoft.ReactNative": { "type": "Direct", - "requested": "[0.78.14-Fabric, )", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" + "requested": "[0.78.5-Fabric, )", + "resolved": "0.78.5-Fabric", + "contentHash": "oN/M8Ob7LVMlCIgyshPWk50dKK8XJuA6pw0IFH8fP6v3+s2OEcvsH2dhxS5GMtJjffeP/y5sBMaY7p+bAKjf7A==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", @@ -135,26 +135,26 @@ }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } }, "native,Version=v0.0/win-x86": { "Microsoft.ReactNative": { "type": "Direct", - "requested": "[0.78.14-Fabric, )", - "resolved": "0.78.14-Fabric", - "contentHash": "KcsI2MLHH44fxo3xv8x24Et0SHVGY9qg7QVkLVkQdovPCAkJWnYvSqOl8lxNW1WCQ3zGeRhbs6WgNCAvWhMjVQ==" + "requested": "[0.78.5-Fabric, )", + "resolved": "0.78.5-Fabric", + "contentHash": "oN/M8Ob7LVMlCIgyshPWk50dKK8XJuA6pw0IFH8fP6v3+s2OEcvsH2dhxS5GMtJjffeP/y5sBMaY7p+bAKjf7A==" }, "Microsoft.VCRTForwarders.140": { "type": "Direct", @@ -164,18 +164,18 @@ }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.7.250401001, )", - "resolved": "1.7.250401001", - "contentHash": "kPsJ2LZoo3Xs/6FtIWMZRGnQ2ZMx9zDa0ZpqRGz1qwZr0gwwlXZJTmngaA1Ym2AHmIa05NtX2jEE2He8CzfhTg==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { - "Microsoft.Web.WebView2": "1.0.2903.40", + "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" } }, "Microsoft.Web.WebView2": { "type": "Transitive", - "resolved": "1.0.2903.40", - "contentHash": "THrzYAnJgE3+cNH+9Epr44XjoZoRELdVpXlWGPs6K9C9G6TqyDfVCeVAR/Er8ljLitIUX5gaSkPsy9wRhD1sgQ==" + "resolved": "1.0.2651.64", + "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" } } } diff --git a/windows/reactotron/reactotron.vcxproj b/windows/reactotron/reactotron.vcxproj index 01a66bc..aa3a995 100644 --- a/windows/reactotron/reactotron.vcxproj +++ b/windows/reactotron/reactotron.vcxproj @@ -131,7 +131,7 @@ - $(ProjectDir)..\..\app;%(AdditionalIncludeDirectories) + $(ProjectDir)..\..\app;%(AdditionalIncludeDirectories);C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\ucrt diff --git a/windows/reactotron/reactotron.vcxproj.filters b/windows/reactotron/reactotron.vcxproj.filters index efabed3..5b57ad6 100644 --- a/windows/reactotron/reactotron.vcxproj.filters +++ b/windows/reactotron/reactotron.vcxproj.filters @@ -44,6 +44,12 @@ Source Files + + Source Files + + + Source Files +