basalt-core 0.0.0-rc5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cli/basalt.d.ts +3 -0
- package/dist/cli/basalt.d.ts.map +1 -0
- package/dist/cli/basalt.js +47 -0
- package/dist/cli/basalt.js.map +1 -0
- package/dist/cli/bundle-cli.d.ts +12 -0
- package/dist/cli/bundle-cli.d.ts.map +1 -0
- package/dist/cli/bundle-cli.js +124 -0
- package/dist/cli/bundle-cli.js.map +1 -0
- package/dist/cli/bundleWithAssets.d.ts +79 -0
- package/dist/cli/bundleWithAssets.d.ts.map +1 -0
- package/dist/cli/bundleWithAssets.js +198 -0
- package/dist/cli/bundleWithAssets.js.map +1 -0
- package/dist/cli/desktop.d.ts +339 -0
- package/dist/cli/desktop.d.ts.map +1 -0
- package/dist/cli/desktop.js +1029 -0
- package/dist/cli/desktop.js.map +1 -0
- package/dist/cli/doctor.d.ts +31 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +283 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts +77 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +442 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/metro.d.ts +49 -0
- package/dist/cli/metro.d.ts.map +1 -0
- package/dist/cli/metro.js +148 -0
- package/dist/cli/metro.js.map +1 -0
- package/dist/cli/packageApp.d.ts +102 -0
- package/dist/cli/packageApp.d.ts.map +1 -0
- package/dist/cli/packageApp.js +319 -0
- package/dist/cli/packageApp.js.map +1 -0
- package/dist/cli/steps.d.ts +34 -0
- package/dist/cli/steps.d.ts.map +1 -0
- package/dist/cli/steps.js +53 -0
- package/dist/cli/steps.js.map +1 -0
- package/dist/metro-config.d.ts +168 -0
- package/dist/metro-config.d.ts.map +1 -0
- package/dist/metro-config.js +671 -0
- package/dist/metro-config.js.map +1 -0
- package/dist/react-native.config.d.ts +7 -0
- package/dist/react-native.config.d.ts.map +1 -0
- package/dist/react-native.config.js +80 -0
- package/dist/react-native.config.js.map +1 -0
- package/dist/src/DragSource.d.ts +52 -0
- package/dist/src/DragSource.d.ts.map +1 -0
- package/dist/src/DragSource.js +97 -0
- package/dist/src/DragSource.js.map +1 -0
- package/dist/src/DropTarget.d.ts +58 -0
- package/dist/src/DropTarget.d.ts.map +1 -0
- package/dist/src/DropTarget.js +131 -0
- package/dist/src/DropTarget.js.map +1 -0
- package/dist/src/KeyHandler.d.ts +57 -0
- package/dist/src/KeyHandler.d.ts.map +1 -0
- package/dist/src/KeyHandler.js +48 -0
- package/dist/src/KeyHandler.js.map +1 -0
- package/dist/src/Menu.d.ts +72 -0
- package/dist/src/Menu.d.ts.map +1 -0
- package/dist/src/Menu.js +179 -0
- package/dist/src/Menu.js.map +1 -0
- package/dist/src/TitleBar.d.ts +75 -0
- package/dist/src/TitleBar.d.ts.map +1 -0
- package/dist/src/TitleBar.js +236 -0
- package/dist/src/TitleBar.js.map +1 -0
- package/dist/src/Window.d.ts +87 -0
- package/dist/src/Window.d.ts.map +1 -0
- package/dist/src/Window.js +248 -0
- package/dist/src/Window.js.map +1 -0
- package/dist/src/closeRequest.d.ts +55 -0
- package/dist/src/closeRequest.d.ts.map +1 -0
- package/dist/src/closeRequest.js +134 -0
- package/dist/src/closeRequest.js.map +1 -0
- package/dist/src/index.d.ts +45 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +92 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/overrides/Alert.d.ts +69 -0
- package/dist/src/overrides/Alert.d.ts.map +1 -0
- package/dist/src/overrides/Alert.js +112 -0
- package/dist/src/overrides/Alert.js.map +1 -0
- package/dist/src/overrides/BaseViewConfig.d.ts +28 -0
- package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -0
- package/dist/src/overrides/BaseViewConfig.js +72 -0
- package/dist/src/overrides/BaseViewConfig.js.map +1 -0
- package/dist/src/overrides/Platform.linux.d.ts +10 -0
- package/dist/src/overrides/Platform.linux.d.ts.map +1 -0
- package/dist/src/overrides/Platform.linux.js +15 -0
- package/dist/src/overrides/Platform.linux.js.map +1 -0
- package/dist/src/overrides/Platform.macos.d.ts +10 -0
- package/dist/src/overrides/Platform.macos.d.ts.map +1 -0
- package/dist/src/overrides/Platform.macos.js +15 -0
- package/dist/src/overrides/Platform.macos.js.map +1 -0
- package/dist/src/overrides/Platform.windows.d.ts +10 -0
- package/dist/src/overrides/Platform.windows.d.ts.map +1 -0
- package/dist/src/overrides/Platform.windows.js +15 -0
- package/dist/src/overrides/Platform.windows.js.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts +24 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.d.ts.map +1 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js +30 -0
- package/dist/src/overrides/ReactDevToolsSettingsManager.js.map +1 -0
- package/dist/src/overrides/RefreshControl.d.ts +52 -0
- package/dist/src/overrides/RefreshControl.d.ts.map +1 -0
- package/dist/src/overrides/RefreshControl.js +92 -0
- package/dist/src/overrides/RefreshControl.js.map +1 -0
- package/dist/src/overrides/ScrollView.d.ts +39 -0
- package/dist/src/overrides/ScrollView.d.ts.map +1 -0
- package/dist/src/overrides/ScrollView.js +59 -0
- package/dist/src/overrides/ScrollView.js.map +1 -0
- package/dist/src/overrides/Share.d.ts +96 -0
- package/dist/src/overrides/Share.d.ts.map +1 -0
- package/dist/src/overrides/Share.js +97 -0
- package/dist/src/overrides/Share.js.map +1 -0
- package/dist/src/overrides/TextInput.d.ts +81 -0
- package/dist/src/overrides/TextInput.d.ts.map +1 -0
- package/dist/src/overrides/TextInput.js +213 -0
- package/dist/src/overrides/TextInput.js.map +1 -0
- package/dist/src/overrides/View.d.ts +36 -0
- package/dist/src/overrides/View.d.ts.map +1 -0
- package/dist/src/overrides/View.js +99 -0
- package/dist/src/overrides/View.js.map +1 -0
- package/dist/src/overrides/createPlatform.d.ts +32 -0
- package/dist/src/overrides/createPlatform.d.ts.map +1 -0
- package/dist/src/overrides/createPlatform.js +84 -0
- package/dist/src/overrides/createPlatform.js.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts +28 -0
- package/dist/src/overrides/setUpDeveloperTools.d.ts.map +1 -0
- package/dist/src/overrides/setUpDeveloperTools.js +84 -0
- package/dist/src/overrides/setUpDeveloperTools.js.map +1 -0
- package/dist/src/overrides/setUpXHR.d.ts +46 -0
- package/dist/src/overrides/setUpXHR.d.ts.map +1 -0
- package/dist/src/overrides/setUpXHR.js +236 -0
- package/dist/src/overrides/setUpXHR.js.map +1 -0
- package/dist/src/quitRequest.d.ts +55 -0
- package/dist/src/quitRequest.d.ts.map +1 -0
- package/dist/src/quitRequest.js +121 -0
- package/dist/src/quitRequest.js.map +1 -0
- package/dist/src/resourcePath.d.ts +18 -0
- package/dist/src/resourcePath.d.ts.map +1 -0
- package/dist/src/resourcePath.js +31 -0
- package/dist/src/resourcePath.js.map +1 -0
- package/dist/src/responseBody.d.ts +76 -0
- package/dist/src/responseBody.d.ts.map +1 -0
- package/dist/src/responseBody.js +94 -0
- package/dist/src/responseBody.js.map +1 -0
- package/dist/src/responseHeaders.d.ts +40 -0
- package/dist/src/responseHeaders.d.ts.map +1 -0
- package/dist/src/responseHeaders.js +75 -0
- package/dist/src/responseHeaders.js.map +1 -0
- package/dist/src/titleBarState.d.ts +43 -0
- package/dist/src/titleBarState.d.ts.map +1 -0
- package/dist/src/titleBarState.js +77 -0
- package/dist/src/titleBarState.js.map +1 -0
- package/dist/src/useContextMenu.d.ts +113 -0
- package/dist/src/useContextMenu.d.ts.map +1 -0
- package/dist/src/useContextMenu.js +109 -0
- package/dist/src/useContextMenu.js.map +1 -0
- package/dist/src/useDialog.d.ts +98 -0
- package/dist/src/useDialog.d.ts.map +1 -0
- package/dist/src/useDialog.js +112 -0
- package/dist/src/useDialog.js.map +1 -0
- package/dist/src/useDisplays.d.ts +70 -0
- package/dist/src/useDisplays.d.ts.map +1 -0
- package/dist/src/useDisplays.js +118 -0
- package/dist/src/useDisplays.js.map +1 -0
- package/dist/src/useHandledKeys.d.ts +38 -0
- package/dist/src/useHandledKeys.d.ts.map +1 -0
- package/dist/src/useHandledKeys.js +70 -0
- package/dist/src/useHandledKeys.js.map +1 -0
- package/dist/src/useWindow.d.ts +140 -0
- package/dist/src/useWindow.d.ts.map +1 -0
- package/dist/src/useWindow.js +233 -0
- package/dist/src/useWindow.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/native/CMakeLists.txt +355 -0
- package/native/bootstrap.sh +613 -0
- package/native/cmake/CompilerFlags.cmake +49 -0
- package/native/cmake/Expo.cmake +125 -0
- package/native/cmake/ReactNativeCore.cmake +612 -0
- package/native/cmake/Reanimated.cmake +108 -0
- package/native/cmake/Skia.cmake +379 -0
- package/native/cmake/ThirdParty.cmake +290 -0
- package/native/cmake/Worklets.cmake +67 -0
- package/native/cmake/codegen/CMakeLists.txt +32 -0
- package/native/core/AppIdentity.cpp +85 -0
- package/native/core/AppIdentity.h +59 -0
- package/native/core/AppPaths.cpp +96 -0
- package/native/core/AppPaths.h +53 -0
- package/native/core/AppearanceModule.cpp +60 -0
- package/native/core/AppearanceModule.h +40 -0
- package/native/core/Backface.h +59 -0
- package/native/core/BlobModule.cpp +283 -0
- package/native/core/BlobModule.h +76 -0
- package/native/core/BlobRegistry.cpp +75 -0
- package/native/core/BlobRegistry.h +44 -0
- package/native/core/ColorScheme.cpp +105 -0
- package/native/core/ColorScheme.h +59 -0
- package/native/core/ComponentRegistry.h +30 -0
- package/native/core/ControlMetrics.h +37 -0
- package/native/core/CoreModules.cpp +592 -0
- package/native/core/CoreModules.h +308 -0
- package/native/core/DebuggingOverlay.cpp +106 -0
- package/native/core/DebuggingOverlay.h +65 -0
- package/native/core/DesktopControls.cpp +232 -0
- package/native/core/DesktopControls.h +170 -0
- package/native/core/DevBundle.cpp +108 -0
- package/native/core/DevBundle.h +69 -0
- package/native/core/DevMenu.cpp +99 -0
- package/native/core/DevMenu.h +75 -0
- package/native/core/DialogModule.cpp +150 -0
- package/native/core/DialogModule.h +51 -0
- package/native/core/DragAndDrop.cpp +45 -0
- package/native/core/DragAndDrop.h +225 -0
- package/native/core/ExpoImageComponent.cpp +79 -0
- package/native/core/ExpoImageComponent.h +80 -0
- package/native/core/ExpoModules.cpp +415 -0
- package/native/core/ExpoModules.h +115 -0
- package/native/core/ExpoRuntime.cpp +366 -0
- package/native/core/ExpoRuntime.h +42 -0
- package/native/core/FocusRing.h +36 -0
- package/native/core/FontRegistry.h +51 -0
- package/native/core/GestureHandlerModule.cpp +136 -0
- package/native/core/GestureHandlerModule.h +71 -0
- package/native/core/Gestures.cpp +533 -0
- package/native/core/Gestures.h +145 -0
- package/native/core/HoverTracker.cpp +69 -0
- package/native/core/HoverTracker.h +109 -0
- package/native/core/HttpClient.cpp +282 -0
- package/native/core/ImageBytes.cpp +205 -0
- package/native/core/ImageBytes.h +29 -0
- package/native/core/ImageCache.cpp +51 -0
- package/native/core/ImageCache.h +89 -0
- package/native/core/JsRuntimeAccess.cpp +70 -0
- package/native/core/JsRuntimeAccess.h +60 -0
- package/native/core/JsiPromise.h +51 -0
- package/native/core/KeyEvents.cpp +116 -0
- package/native/core/KeyEvents.h +144 -0
- package/native/core/LogBoxSurface.cpp +46 -0
- package/native/core/LogBoxSurface.h +68 -0
- package/native/core/MenuModel.h +117 -0
- package/native/core/MenuModule.cpp +209 -0
- package/native/core/MenuModule.h +66 -0
- package/native/core/MountingWalk.h +524 -0
- package/native/core/PackageModules.cpp.in +25 -0
- package/native/core/PackageModules.h +50 -0
- package/native/core/PlatformConstantsModule.cpp +63 -0
- package/native/core/PlatformConstantsModule.h +64 -0
- package/native/core/PlatformServices.h +213 -0
- package/native/core/PointerButtons.h +63 -0
- package/native/core/PullToRefresh.cpp +57 -0
- package/native/core/PullToRefresh.h +73 -0
- package/native/core/ReanimatedCompat.h +47 -0
- package/native/core/ReanimatedModule.cpp +203 -0
- package/native/core/ReanimatedModule.h +55 -0
- package/native/core/ScrollAnimation.cpp +59 -0
- package/native/core/ScrollAnimation.h +70 -0
- package/native/core/ScrollBounds.cpp +26 -0
- package/native/core/ScrollBounds.h +68 -0
- package/native/core/ScrollIndicator.cpp +50 -0
- package/native/core/ScrollIndicator.h +69 -0
- package/native/core/ScrollMomentum.cpp +71 -0
- package/native/core/ScrollMomentum.h +73 -0
- package/native/core/ScrollSnap.cpp +123 -0
- package/native/core/ScrollSnap.h +80 -0
- package/native/core/ShareFallback.cpp +101 -0
- package/native/core/ShareFallback.h +45 -0
- package/native/core/SkiaPictureViewComponent.cpp +11 -0
- package/native/core/SkiaPictureViewComponent.h +47 -0
- package/native/core/SourceCodeModule.cpp +55 -0
- package/native/core/SourceCodeModule.h +53 -0
- package/native/core/StatusBarModule.cpp +23 -0
- package/native/core/StatusBarModule.h +42 -0
- package/native/core/TestDialog.cpp +148 -0
- package/native/core/TestDialog.h +96 -0
- package/native/core/TestKeys.cpp +78 -0
- package/native/core/TestKeys.h +50 -0
- package/native/core/TestQuitFile.cpp +33 -0
- package/native/core/TestQuitFile.h +60 -0
- package/native/core/TestSettle.h +192 -0
- package/native/core/TitleBarRegions.h +31 -0
- package/native/core/TurboModuleProxy.cpp +51 -0
- package/native/core/TurboModuleProxy.h +46 -0
- package/native/core/UIManagerAccess.cpp +43 -0
- package/native/core/UIManagerAccess.h +43 -0
- package/native/core/WindowBoundsCache.cpp +319 -0
- package/native/core/WindowControl.h +235 -0
- package/native/core/WindowHost.h +158 -0
- package/native/core/WindowMethods.cpp +167 -0
- package/native/core/WindowMethods.h +84 -0
- package/native/core/WindowsModule.cpp +448 -0
- package/native/core/WindowsModule.h +133 -0
- package/native/core/WorkletsModule.cpp +199 -0
- package/native/core/WorkletsModule.h +57 -0
- package/native/core/portability_probe.cpp +210 -0
- package/native/tests/EventRecorder.h +124 -0
- package/native/tests/TestHarness.cpp +109 -0
- package/native/tests/TestHarness.h +89 -0
- package/native/tests/TreeDump.h +49 -0
- package/native/tests/test_app_identity.cpp +114 -0
- package/native/tests/test_app_paths.cpp +48 -0
- package/native/tests/test_appearance.cpp +107 -0
- package/native/tests/test_backface.cpp +79 -0
- package/native/tests/test_blobs.cpp +157 -0
- package/native/tests/test_controls.cpp +185 -0
- package/native/tests/test_devbundle.cpp +103 -0
- package/native/tests/test_drag_and_drop.cpp +143 -0
- package/native/tests/test_gestures.cpp +331 -0
- package/native/tests/test_hover.cpp +92 -0
- package/native/tests/test_image_cache.cpp +109 -0
- package/native/tests/test_js_runtime_access.cpp +137 -0
- package/native/tests/test_key_events.cpp +235 -0
- package/native/tests/test_momentum.cpp +138 -0
- package/native/tests/test_quit_file.cpp +104 -0
- package/native/tests/test_scroll_animation.cpp +120 -0
- package/native/tests/test_scroll_bounds.cpp +66 -0
- package/native/tests/test_scroll_indicator.cpp +149 -0
- package/native/tests/test_scroll_snap.cpp +112 -0
- package/native/tests/test_settle.cpp +115 -0
- package/native/tests/test_share.cpp +75 -0
- package/native/tests/test_sourcecode.cpp +62 -0
- package/native/tests/test_window_close.cpp +119 -0
- package/native/tests/test_window_limits.cpp +123 -0
- package/package.json +43 -0
- package/react-native.config.js +86 -0
- package/supported-versions.json +66 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#include "DesktopControls.h"
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
|
4
|
+
#include <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
|
|
5
|
+
#include <react/renderer/components/FBReactNativeSpec/Props.h>
|
|
6
|
+
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
|
|
7
|
+
#include <react/renderer/components/switch/AppleSwitchShadowNode.h>
|
|
8
|
+
#include <react/renderer/graphics/ColorComponents.h>
|
|
9
|
+
|
|
10
|
+
#include <cstring>
|
|
11
|
+
|
|
12
|
+
namespace facebook::react {
|
|
13
|
+
|
|
14
|
+
// React Native's only <Switch> shadow node is the Apple one, and the two
|
|
15
|
+
// translation units that finish it are both Objective-C. This is the third,
|
|
16
|
+
// and the constants are in DesktopControls.h beside everything else a switch
|
|
17
|
+
// is made of.
|
|
18
|
+
//
|
|
19
|
+
// The component name is not "AppleSwitch" despite the file it comes from:
|
|
20
|
+
// React Native's JavaScript renders `Switch`, and a mismatch here is the kind
|
|
21
|
+
// that renders a blank box rather than failing.
|
|
22
|
+
extern const char AppleSwitchComponentName[] = "Switch";
|
|
23
|
+
|
|
24
|
+
Size SwitchShadowNode::measureContent(const LayoutContext & /*layoutContext*/,
|
|
25
|
+
const LayoutConstraints & /*layoutConstraints*/) const {
|
|
26
|
+
return {.width = basalt::kSwitchWidth, .height = basalt::kSwitchHeight};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
} // namespace facebook::react
|
|
30
|
+
|
|
31
|
+
namespace basalt {
|
|
32
|
+
|
|
33
|
+
using facebook::react::ActivityIndicatorViewProps;
|
|
34
|
+
using facebook::react::ActivityIndicatorViewSize;
|
|
35
|
+
using facebook::react::ModalHostViewShadowNode;
|
|
36
|
+
using facebook::react::PullToRefreshViewProps;
|
|
37
|
+
using facebook::react::ShadowView;
|
|
38
|
+
using facebook::react::SharedColor;
|
|
39
|
+
using facebook::react::Size;
|
|
40
|
+
using facebook::react::SwitchProps;
|
|
41
|
+
|
|
42
|
+
namespace {
|
|
43
|
+
|
|
44
|
+
// A React Native colour into the four floats every view layer here wants.
|
|
45
|
+
// Returns false for an unset colour, which is not the same as transparent: a
|
|
46
|
+
// control with no colour draws in the platform's own, which is the behaviour
|
|
47
|
+
// React Native documents for all three of these props.
|
|
48
|
+
bool readColor(const SharedColor &color, float out[4]) {
|
|
49
|
+
if (!color) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const auto components = colorComponentsFromColor(color);
|
|
53
|
+
out[0] = components.red;
|
|
54
|
+
out[1] = components.green;
|
|
55
|
+
out[2] = components.blue;
|
|
56
|
+
out[3] = components.alpha;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
bool sameColor(const float a[4], const float b[4]) {
|
|
61
|
+
return std::memcmp(a, b, sizeof(float) * 4) == 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} // namespace
|
|
65
|
+
|
|
66
|
+
ControlKind controlKindFor(const char *componentName) {
|
|
67
|
+
if (componentName == nullptr) {
|
|
68
|
+
return ControlKind::None;
|
|
69
|
+
}
|
|
70
|
+
if (std::strcmp(componentName, "ActivityIndicatorView") == 0) {
|
|
71
|
+
return ControlKind::ActivityIndicator;
|
|
72
|
+
}
|
|
73
|
+
if (std::strcmp(componentName, "Switch") == 0) {
|
|
74
|
+
return ControlKind::Switch;
|
|
75
|
+
}
|
|
76
|
+
if (std::strcmp(componentName, "PullToRefreshView") == 0) {
|
|
77
|
+
return ControlKind::PullToRefresh;
|
|
78
|
+
}
|
|
79
|
+
return ControlKind::None;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
bool ControlState::operator==(const ControlState &other) const {
|
|
83
|
+
return kind == other.kind && on == other.on && disabled == other.disabled && large == other.large &&
|
|
84
|
+
hidesWhenStopped == other.hidesWhenStopped && hasForeground == other.hasForeground &&
|
|
85
|
+
sameColor(foreground, other.foreground) && hasTrackOn == other.hasTrackOn &&
|
|
86
|
+
sameColor(trackOn, other.trackOn) && hasTrackOff == other.hasTrackOff &&
|
|
87
|
+
sameColor(trackOff, other.trackOff);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ControlState controlStateOf(const ShadowView &shadowView) {
|
|
91
|
+
ControlState state;
|
|
92
|
+
state.kind = controlKindFor(shadowView.componentName);
|
|
93
|
+
|
|
94
|
+
switch (state.kind) {
|
|
95
|
+
case ControlKind::ActivityIndicator: {
|
|
96
|
+
const auto props = std::dynamic_pointer_cast<const ActivityIndicatorViewProps>(shadowView.props);
|
|
97
|
+
if (props == nullptr) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
state.on = props->animating;
|
|
101
|
+
state.large = props->size == ActivityIndicatorViewSize::Large;
|
|
102
|
+
state.hidesWhenStopped = props->hidesWhenStopped;
|
|
103
|
+
state.hasForeground = readColor(props->color, state.foreground);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case ControlKind::Switch: {
|
|
107
|
+
const auto props = std::dynamic_pointer_cast<const SwitchProps>(shadowView.props);
|
|
108
|
+
if (props == nullptr) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
state.on = props->value;
|
|
112
|
+
state.disabled = props->disabled;
|
|
113
|
+
// `thumbColor` is the modern prop and `thumbTintColor` the deprecated
|
|
114
|
+
// one React Native still parses. Newer first, so an app that sets both
|
|
115
|
+
// gets the one it means.
|
|
116
|
+
state.hasForeground = readColor(props->thumbColor, state.foreground) ||
|
|
117
|
+
readColor(props->thumbTintColor, state.foreground);
|
|
118
|
+
// Likewise: `trackColor={{true, false}}` parses into these two, and the
|
|
119
|
+
// old flat `onTintColor` / `tintColor` are the fallbacks.
|
|
120
|
+
state.hasTrackOn =
|
|
121
|
+
readColor(props->trackColorForTrue, state.trackOn) || readColor(props->onTintColor, state.trackOn);
|
|
122
|
+
state.hasTrackOff =
|
|
123
|
+
readColor(props->trackColorForFalse, state.trackOff) || readColor(props->tintColor, state.trackOff);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case ControlKind::PullToRefresh: {
|
|
127
|
+
const auto props = std::dynamic_pointer_cast<const PullToRefreshViewProps>(shadowView.props);
|
|
128
|
+
if (props == nullptr) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
state.on = props->refreshing;
|
|
132
|
+
state.hasForeground = readColor(props->tintColor, state.foreground);
|
|
133
|
+
// A refresh control that is not refreshing shows nothing, which is the
|
|
134
|
+
// same rule an indicator states as `hidesWhenStopped`.
|
|
135
|
+
state.hidesWhenStopped = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case ControlKind::None:
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return state;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
std::string describeControl(const ControlState &state) {
|
|
146
|
+
switch (state.kind) {
|
|
147
|
+
case ControlKind::None:
|
|
148
|
+
return {};
|
|
149
|
+
case ControlKind::ActivityIndicator: {
|
|
150
|
+
std::string out = state.large ? "spinner-large" : "spinner";
|
|
151
|
+
out += state.on ? ":animating" : ":stopped";
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
case ControlKind::Switch: {
|
|
155
|
+
std::string out = state.on ? "switch:on" : "switch:off";
|
|
156
|
+
if (state.disabled) {
|
|
157
|
+
out += ":disabled";
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
case ControlKind::PullToRefresh:
|
|
162
|
+
return state.on ? "refresh:refreshing" : "refresh:idle";
|
|
163
|
+
}
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void emitSwitchChange(const facebook::react::EventEmitter::Shared &emitter, facebook::react::Tag tag, bool value) {
|
|
168
|
+
const auto typed = std::dynamic_pointer_cast<const facebook::react::SwitchEventEmitter>(emitter);
|
|
169
|
+
if (typed == nullptr) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
typed->onChange({.value = value, .target = static_cast<int>(tag)});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void emitRefresh(const facebook::react::EventEmitter::Shared &emitter) {
|
|
176
|
+
const auto typed = std::dynamic_pointer_cast<const facebook::react::PullToRefreshViewEventEmitter>(emitter);
|
|
177
|
+
if (typed != nullptr) {
|
|
178
|
+
typed->onRefresh({});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
void emitModalShow(const facebook::react::EventEmitter::Shared &emitter) {
|
|
183
|
+
const auto typed = std::dynamic_pointer_cast<const facebook::react::ModalHostViewEventEmitter>(emitter);
|
|
184
|
+
if (typed != nullptr) {
|
|
185
|
+
typed->onShow({});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
void emitModalRequestClose(const facebook::react::EventEmitter::Shared &emitter) {
|
|
190
|
+
const auto typed = std::dynamic_pointer_cast<const facebook::react::ModalHostViewEventEmitter>(emitter);
|
|
191
|
+
if (typed != nullptr) {
|
|
192
|
+
typed->onRequestClose({});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
namespace {
|
|
197
|
+
|
|
198
|
+
// The modal's state, or null for anything that is not a modal -- including a
|
|
199
|
+
// modal whose state has not been created yet, which a Delete mutation's
|
|
200
|
+
// ShadowView looks like.
|
|
201
|
+
std::shared_ptr<const ModalHostViewShadowNode::ConcreteState> modalStateOf(const ShadowView &shadowView) {
|
|
202
|
+
if (shadowView.componentName == nullptr || std::strcmp(shadowView.componentName, "ModalHostView") != 0) {
|
|
203
|
+
return nullptr;
|
|
204
|
+
}
|
|
205
|
+
return std::static_pointer_cast<const ModalHostViewShadowNode::ConcreteState>(shadowView.state);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
} // namespace
|
|
209
|
+
|
|
210
|
+
bool modalScreenSizeNeedsUpdate(const ShadowView &shadowView, float width, float height) {
|
|
211
|
+
const auto state = modalStateOf(shadowView);
|
|
212
|
+
if (state == nullptr) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (width <= 0.0F || height <= 0.0F) {
|
|
216
|
+
// Before the window has a size. Committing zero would be committing the
|
|
217
|
+
// very value that makes a modal invisible.
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const Size &screen = state->getData().screenSize;
|
|
221
|
+
return screen.width != width || screen.height != height;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
void updateModalScreenSize(const ShadowView &shadowView, float width, float height) {
|
|
225
|
+
const auto state = modalStateOf(shadowView);
|
|
226
|
+
if (state == nullptr) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
state->updateState(facebook::react::ModalHostViewState{Size{.width = width, .height = height}});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
} // namespace basalt
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// The four React Native components that are a control rather than a box.
|
|
2
|
+
//
|
|
3
|
+
// <ActivityIndicator>, <Switch>, <RefreshControl> and <Modal> have almost
|
|
4
|
+
// nothing in common on screen and everything in common here: each one is a
|
|
5
|
+
// handful of props that three view layers have to read the same way, and each
|
|
6
|
+
// one would otherwise be read three times, slightly differently. Reading them
|
|
7
|
+
// once is what keeps a switch the same size and the same colour on GTK, AppKit
|
|
8
|
+
// and Win32 -- which `scripts/compare_hosts.sh` checks by diffing the tree
|
|
9
|
+
// dumps line by line, and which a per-host `dynamic_pointer_cast` would have
|
|
10
|
+
// quietly broken the first time one host defaulted a colour.
|
|
11
|
+
//
|
|
12
|
+
// What is deliberately *not* here is any drawing. A GtkSwitch, an NSSwitch and
|
|
13
|
+
// a Direct2D rounded rectangle have no shared vocabulary, so the split is the
|
|
14
|
+
// same one the rest of this project makes: the props are portable, the pixels
|
|
15
|
+
// are not.
|
|
16
|
+
//
|
|
17
|
+
// Two shadow-node obligations also live here, because React Native leaves them
|
|
18
|
+
// to the platform and both platform answers it ships are Objective-C:
|
|
19
|
+
//
|
|
20
|
+
// * `SwitchShadowNode::measureContent`. React Native's only <Switch> shadow
|
|
21
|
+
// node is the Apple one, and it measures by instantiating a UISwitch or an
|
|
22
|
+
// NSSwitch on the main thread. The size below is a constant instead, which
|
|
23
|
+
// is also why a switch is the same size on all three desktops rather than
|
|
24
|
+
// whatever each toolkit's theme happens to want.
|
|
25
|
+
//
|
|
26
|
+
// * The <Modal>'s screen size. `ModalHostViewScreenSize()` is already defined
|
|
27
|
+
// by React Native's own cxx platform file and returns zero, so a modal
|
|
28
|
+
// would lay out 0x0 and render nothing. The host sends the real size
|
|
29
|
+
// through the shadow node's state instead, exactly as the iOS host does;
|
|
30
|
+
// see `modalScreenSizeNeedsUpdate` below.
|
|
31
|
+
|
|
32
|
+
#pragma once
|
|
33
|
+
|
|
34
|
+
#include "ControlMetrics.h"
|
|
35
|
+
|
|
36
|
+
#include <react/renderer/core/EventEmitter.h>
|
|
37
|
+
#include <react/renderer/mounting/ShadowView.h>
|
|
38
|
+
|
|
39
|
+
#include <string>
|
|
40
|
+
|
|
41
|
+
namespace basalt {
|
|
42
|
+
|
|
43
|
+
// --- What kind of control a component name means ----------------------------
|
|
44
|
+
|
|
45
|
+
enum class ControlKind {
|
|
46
|
+
None,
|
|
47
|
+
// <ActivityIndicator>. Spins while `animating`.
|
|
48
|
+
ActivityIndicator,
|
|
49
|
+
// <Switch>. On or off, and possibly disabled.
|
|
50
|
+
Switch,
|
|
51
|
+
// <RefreshControl>. A spinner that is on while `refreshing`, plus the pull
|
|
52
|
+
// that turns it on; see PullToRefresh.h for the pull.
|
|
53
|
+
PullToRefresh,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// The component name React Native mounts each of these under. Free functions
|
|
57
|
+
// rather than a table so a caller can compare without allocating.
|
|
58
|
+
ControlKind controlKindFor(const char *componentName);
|
|
59
|
+
|
|
60
|
+
// --- The props, read once ----------------------------------------------------
|
|
61
|
+
|
|
62
|
+
// Every control's state, flattened. One struct rather than three because the
|
|
63
|
+
// three overlap almost completely and every host stores exactly one of these
|
|
64
|
+
// per view; three would be three unions or three fields.
|
|
65
|
+
struct ControlState {
|
|
66
|
+
ControlKind kind = ControlKind::None;
|
|
67
|
+
|
|
68
|
+
// A switch's `value`; an indicator's `animating`; a refresh control's
|
|
69
|
+
// `refreshing`. The one bit every control has.
|
|
70
|
+
bool on = false;
|
|
71
|
+
|
|
72
|
+
// <Switch> only. A disabled switch is drawn dimmed and does not toggle.
|
|
73
|
+
bool disabled = false;
|
|
74
|
+
|
|
75
|
+
// <ActivityIndicator> only: `size="large"`.
|
|
76
|
+
bool large = false;
|
|
77
|
+
|
|
78
|
+
// <ActivityIndicator> only. When set -- which is React Native's default --
|
|
79
|
+
// a stopped indicator is not drawn at all rather than drawn still.
|
|
80
|
+
bool hidesWhenStopped = true;
|
|
81
|
+
|
|
82
|
+
// The colour of the moving part: an indicator's `color`, a refresh control's
|
|
83
|
+
// `tintColor`, a switch's `thumbColor`. Unset means the platform's own.
|
|
84
|
+
bool hasForeground = false;
|
|
85
|
+
float foreground[4] = {0.0F, 0.0F, 0.0F, 0.0F};
|
|
86
|
+
|
|
87
|
+
// <Switch> only: `trackColor.true` and `trackColor.false`.
|
|
88
|
+
bool hasTrackOn = false;
|
|
89
|
+
float trackOn[4] = {0.0F, 0.0F, 0.0F, 0.0F};
|
|
90
|
+
bool hasTrackOff = false;
|
|
91
|
+
float trackOff[4] = {0.0F, 0.0F, 0.0F, 0.0F};
|
|
92
|
+
|
|
93
|
+
bool operator==(const ControlState &other) const;
|
|
94
|
+
bool operator!=(const ControlState &other) const { return !(*this == other); }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// The control state a mounted view carries, or a `None` state for every
|
|
98
|
+
// component that is not one of the three. Safe to call on any ShadowView.
|
|
99
|
+
ControlState controlStateOf(const facebook::react::ShadowView &shadowView);
|
|
100
|
+
|
|
101
|
+
// The field all three hosts print in `describeTree`, or an empty string for a
|
|
102
|
+
// view that is not a control.
|
|
103
|
+
//
|
|
104
|
+
// Written once because the tree dump is a cross-platform contract: three hosts
|
|
105
|
+
// formatting the same state their own way is a diff on every line, which is a
|
|
106
|
+
// mistake this project has already made twice (phase 53's `role=`, and
|
|
107
|
+
// `focusable` printing in two places on GTK).
|
|
108
|
+
std::string describeControl(const ControlState &state);
|
|
109
|
+
|
|
110
|
+
// --- Metrics -----------------------------------------------------------------
|
|
111
|
+
//
|
|
112
|
+
// kSwitchWidth, kSwitchHeight, kSpinnerSmall and kSpinnerLarge are in
|
|
113
|
+
// ControlMetrics.h next door, because the Windows view layer draws a switch and
|
|
114
|
+
// a spinner and cannot include this file; see its header.
|
|
115
|
+
|
|
116
|
+
// How far a scroll view has to be pulled past its top before a <RefreshControl>
|
|
117
|
+
// fires. React Native's iOS control uses roughly this.
|
|
118
|
+
inline constexpr double kPullToRefreshThreshold = 80.0;
|
|
119
|
+
|
|
120
|
+
// How tall a <RefreshControl> is while it is refreshing. The JavaScript side
|
|
121
|
+
// puts this in the view's style, so Yoga makes room for it; the number is here
|
|
122
|
+
// so the two cannot disagree.
|
|
123
|
+
inline constexpr float kRefreshControlHeight = 40.0F;
|
|
124
|
+
|
|
125
|
+
// --- The events ---------------------------------------------------------------
|
|
126
|
+
//
|
|
127
|
+
// Four one-line calls that exist as functions so that the codegen event
|
|
128
|
+
// emitters stay out of MountingWalk.h and out of three view layers. Each is a
|
|
129
|
+
// no-op for a null emitter, which is what a view between a Remove and its
|
|
130
|
+
// Delete has.
|
|
131
|
+
|
|
132
|
+
// <Switch> was toggled. `tag` is the switch's own, which React Native's payload
|
|
133
|
+
// carries as `target`.
|
|
134
|
+
void emitSwitchChange(const facebook::react::EventEmitter::Shared &emitter, facebook::react::Tag tag, bool value);
|
|
135
|
+
|
|
136
|
+
// <RefreshControl> was pulled far enough. React Native expects this once per
|
|
137
|
+
// pull; core/PullToRefresh.h is what makes that true.
|
|
138
|
+
void emitRefresh(const facebook::react::EventEmitter::Shared &emitter);
|
|
139
|
+
|
|
140
|
+
// <Modal> appeared. React Native's contract is that this fires after the modal
|
|
141
|
+
// is on screen, which here is after the transaction that mounted it.
|
|
142
|
+
void emitModalShow(const facebook::react::EventEmitter::Shared &emitter);
|
|
143
|
+
|
|
144
|
+
// The person asked for the <Modal> to close -- Escape, on a desktop, which is
|
|
145
|
+
// the same role Android's back button plays. React Native does not close the
|
|
146
|
+
// modal itself: the app's handler sets `visible` to false, and not handling it
|
|
147
|
+
// leaves the modal up, which is deliberate.
|
|
148
|
+
void emitModalRequestClose(const facebook::react::EventEmitter::Shared &emitter);
|
|
149
|
+
|
|
150
|
+
// --- <Modal> -----------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
// Whether this view is a <Modal> whose state still says the screen is a
|
|
153
|
+
// different size than it is, and so needs the host to commit a new one.
|
|
154
|
+
//
|
|
155
|
+
// `ModalHostViewComponentDescriptor::adopt` reads the size out of the shadow
|
|
156
|
+
// node's state and applies it as the node's own, which is how a modal comes to
|
|
157
|
+
// cover the window without the app giving it a size. React Native's cxx
|
|
158
|
+
// platform reports that size as zero -- so without this a modal lays out 0x0
|
|
159
|
+
// and the app shows nothing at all, with no error anywhere.
|
|
160
|
+
//
|
|
161
|
+
// Returns false for every component that is not a <Modal>, and for a modal
|
|
162
|
+
// already holding this size: committing state unconditionally would commit on
|
|
163
|
+
// every mount forever.
|
|
164
|
+
bool modalScreenSizeNeedsUpdate(const facebook::react::ShadowView &shadowView, float width, float height);
|
|
165
|
+
|
|
166
|
+
// Commits `width` x `height` as the modal's screen size. Call only when
|
|
167
|
+
// `modalScreenSizeNeedsUpdate` said so.
|
|
168
|
+
void updateModalScreenSize(const facebook::react::ShadowView &shadowView, float width, float height);
|
|
169
|
+
|
|
170
|
+
} // namespace basalt
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#include "DevBundle.h"
|
|
2
|
+
|
|
3
|
+
#include <folly/json.h>
|
|
4
|
+
#include <glog/logging.h>
|
|
5
|
+
|
|
6
|
+
#include <mutex>
|
|
7
|
+
|
|
8
|
+
namespace basalt {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
std::mutex &stateMutex() {
|
|
13
|
+
static std::mutex mutex;
|
|
14
|
+
return mutex;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
std::string &devOrigin() {
|
|
18
|
+
static std::string origin;
|
|
19
|
+
return origin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
std::optional<DevBundleError> &lastError() {
|
|
23
|
+
static std::optional<DevBundleError> error;
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Metro answers an error with JSON: `type`, `message`, and usually a code frame
|
|
28
|
+
// already formatted into `message` with ANSI colour. Anything else -- a proxy's
|
|
29
|
+
// HTML, a plain string -- comes back as itself.
|
|
30
|
+
std::string messageFrom(const std::string &body) {
|
|
31
|
+
try {
|
|
32
|
+
const folly::dynamic parsed = folly::parseJson(body);
|
|
33
|
+
if (parsed.isObject()) {
|
|
34
|
+
if (const auto *message = parsed.get_ptr("message"); message != nullptr && message->isString()) {
|
|
35
|
+
return message->asString();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch (const std::exception &) {
|
|
39
|
+
// Not JSON. Fall through to the body itself.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
constexpr size_t kMaxRawBody = 4000;
|
|
43
|
+
return body.size() > kMaxRawBody ? body.substr(0, kMaxRawBody) + "\n[...truncated]" : body;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} // namespace
|
|
47
|
+
|
|
48
|
+
void setDevServerOrigin(const std::string &host, uint32_t port) {
|
|
49
|
+
const std::lock_guard<std::mutex> lock(stateMutex());
|
|
50
|
+
devOrigin() = "http://" + host + ":" + std::to_string(port) + "/";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
bool isDevBundleRequest(const std::string &method, const std::string &url) {
|
|
54
|
+
if (method != "GET") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
std::string origin;
|
|
59
|
+
{
|
|
60
|
+
const std::lock_guard<std::mutex> lock(stateMutex());
|
|
61
|
+
origin = devOrigin();
|
|
62
|
+
}
|
|
63
|
+
if (origin.empty() || url.compare(0, origin.size(), origin) != 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// `/index.bundle?platform=...`, or `/index.bundle` with no query at all.
|
|
68
|
+
const auto query = url.find('?');
|
|
69
|
+
const std::string path = query == std::string::npos ? url : url.substr(0, query);
|
|
70
|
+
constexpr std::string_view kSuffix = ".bundle";
|
|
71
|
+
return path.size() >= kSuffix.size() &&
|
|
72
|
+
path.compare(path.size() - kSuffix.size(), kSuffix.size(), kSuffix) == 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void recordDevBundleError(long status, const std::string &url, const std::string &body) {
|
|
76
|
+
const std::string message = messageFrom(body);
|
|
77
|
+
|
|
78
|
+
// Logged here rather than left to the host, because this is the moment the
|
|
79
|
+
// developer's actual error is in hand and there is nowhere else it survives.
|
|
80
|
+
LOG(ERROR) << "Metro answered " << status << " for " << url << "\n" << message;
|
|
81
|
+
|
|
82
|
+
const std::lock_guard<std::mutex> lock(stateMutex());
|
|
83
|
+
lastError() = DevBundleError{.status = status, .message = message};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
namespace {
|
|
87
|
+
|
|
88
|
+
std::optional<std::string> &fetched() {
|
|
89
|
+
static std::optional<std::string> url;
|
|
90
|
+
return url;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
} // namespace
|
|
94
|
+
|
|
95
|
+
void recordDevBundleFetched(const std::string &url) {
|
|
96
|
+
fetched() = url;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
std::optional<std::string> devBundleFetched() {
|
|
100
|
+
return fetched();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
std::optional<DevBundleError> devBundleError() {
|
|
104
|
+
const std::lock_guard<std::mutex> lock(stateMutex());
|
|
105
|
+
return lastError();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
} // namespace basalt
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// What happened when this process asked Metro for its bundle.
|
|
2
|
+
//
|
|
3
|
+
// React Native's cxx `DevServerHelper` hands the response body straight to the
|
|
4
|
+
// JS engine without looking at the status code, and `ReactHost` treats any
|
|
5
|
+
// failure as "Metro is not running" and quietly loads the on-disk bundle
|
|
6
|
+
// instead. That is right for one of the two ways the fetch can fail and wrong
|
|
7
|
+
// for the other:
|
|
8
|
+
//
|
|
9
|
+
// - Nothing is listening. curl reports a connection error, no body is
|
|
10
|
+
// produced, and falling back to the prebuilt bundle is exactly what should
|
|
11
|
+
// happen. Verified.
|
|
12
|
+
// - Metro is running and answers 500, which is what it does for any error in
|
|
13
|
+
// your app -- a syntax error, a missing import, a failing transform. The
|
|
14
|
+
// body is Metro's JSON error report, and compiling it as JavaScript
|
|
15
|
+
// produces `Compiling JS failed: 1:8:';' expected`, naming a line in
|
|
16
|
+
// Metro's error rather than a line in your code.
|
|
17
|
+
//
|
|
18
|
+
// This file separates the two. `core/HttpClient.cpp` recognises the bundle
|
|
19
|
+
// request, and on a non-2xx status records the failure here and reports an
|
|
20
|
+
// error instead of a body -- so nothing tries to compile it. The host reads
|
|
21
|
+
// `devBundleError()` after `loadScript` returns and refuses to start on a
|
|
22
|
+
// server error, because the alternative is running yesterday's bundle while the
|
|
23
|
+
// developer stares at code that is not what is executing.
|
|
24
|
+
|
|
25
|
+
#pragma once
|
|
26
|
+
|
|
27
|
+
// For uint32_t, which nothing else here brings in. It compiled on Windows
|
|
28
|
+
// only because windows.h had already been pulled in behind it, and on a Mac for
|
|
29
|
+
// the same kind of reason; Linux said so, which is what the Linux job is for.
|
|
30
|
+
#include <cstdint>
|
|
31
|
+
#include <optional>
|
|
32
|
+
#include <string>
|
|
33
|
+
|
|
34
|
+
namespace basalt {
|
|
35
|
+
|
|
36
|
+
// Called by the host in dev mode, before the bundle is fetched. Without it
|
|
37
|
+
// `isDevBundleRequest` matches nothing and everything below is inert -- which
|
|
38
|
+
// is the correct behaviour in release, where there is no dev server.
|
|
39
|
+
void setDevServerOrigin(const std::string &host, uint32_t port);
|
|
40
|
+
|
|
41
|
+
// True for the one request that fetches the bundle: a GET to the dev server's
|
|
42
|
+
// own origin for a path ending in `.bundle`. An app's own fetch() cannot match
|
|
43
|
+
// unless it deliberately asks Metro for a bundle.
|
|
44
|
+
bool isDevBundleRequest(const std::string &method, const std::string &url);
|
|
45
|
+
|
|
46
|
+
struct DevBundleError {
|
|
47
|
+
long status{0};
|
|
48
|
+
std::string message;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Records a non-2xx response to the bundle request. `body` is Metro's error
|
|
52
|
+
// payload; the message is extracted from it if it is the JSON Metro sends.
|
|
53
|
+
void recordDevBundleError(long status, const std::string &url, const std::string &body);
|
|
54
|
+
|
|
55
|
+
// The last recorded failure, if any. Set on the request thread and read on the
|
|
56
|
+
// main thread after loadScript has joined it.
|
|
57
|
+
std::optional<DevBundleError> devBundleError();
|
|
58
|
+
|
|
59
|
+
// That the bundle came from Metro, with the URL it came from.
|
|
60
|
+
//
|
|
61
|
+
// Recorded for one reason: the host's "loaded script" line named the on-disk
|
|
62
|
+
// path either way, because that is the argument `loadScript` was given -- and
|
|
63
|
+
// `ReactHost` does not say which of the two it used. So a dev run against Metro
|
|
64
|
+
// reported the path of a bundle it had not executed, and a stale file on disk
|
|
65
|
+
// looked like the thing that was running. That cost an afternoon, twice.
|
|
66
|
+
void recordDevBundleFetched(const std::string &url);
|
|
67
|
+
std::optional<std::string> devBundleFetched();
|
|
68
|
+
|
|
69
|
+
} // namespace basalt
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#include "DevMenu.h"
|
|
2
|
+
|
|
3
|
+
#include "PlatformServices.h"
|
|
4
|
+
#include "TestDialog.h"
|
|
5
|
+
|
|
6
|
+
#include <react/runtime/ReactHost.h>
|
|
7
|
+
|
|
8
|
+
#include <folly/dynamic.h>
|
|
9
|
+
|
|
10
|
+
namespace basalt {
|
|
11
|
+
|
|
12
|
+
std::vector<DevMenuItem> devMenuItems(facebook::react::ReactHost *reactHost) {
|
|
13
|
+
std::vector<DevMenuItem> items;
|
|
14
|
+
|
|
15
|
+
items.push_back(DevMenuItem{
|
|
16
|
+
.label = "Reload",
|
|
17
|
+
.shortcut = "Ctrl+R",
|
|
18
|
+
.action =
|
|
19
|
+
[reactHost] {
|
|
20
|
+
if (reactHost == nullptr) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Through JavaScript, because the C++ side has no way in: the
|
|
24
|
+
// reload lives on ReactHost's private `reloadReactInstance`, and
|
|
25
|
+
// the only thing wired to it is the callback React Native's own
|
|
26
|
+
// DevSettings module holds. See the header.
|
|
27
|
+
reactHost->emitDeviceEvent(folly::dynamic::array(kDevMenuEvent, kDevMenuReload));
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
items.push_back(DevMenuItem{
|
|
32
|
+
.label = "Toggle Element Inspector",
|
|
33
|
+
.shortcut = "Ctrl+I",
|
|
34
|
+
.action =
|
|
35
|
+
[reactHost] {
|
|
36
|
+
if (reactHost == nullptr) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// React Native's own event, handled by AppContainer-dev.js. The
|
|
40
|
+
// inspector it renders is ordinary React views, so it needs
|
|
41
|
+
// nothing of this platform beyond mounting them -- which is why
|
|
42
|
+
// this works on all three hosts without a line of platform code.
|
|
43
|
+
reactHost->emitDeviceEvent(folly::dynamic::array("toggleElementInspector"));
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
items.push_back(DevMenuItem{
|
|
48
|
+
.label = "Open Debugger",
|
|
49
|
+
.shortcut = "Ctrl+J",
|
|
50
|
+
.action =
|
|
51
|
+
[reactHost] {
|
|
52
|
+
if (reactHost != nullptr) {
|
|
53
|
+
reactHost->openDebugger();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return items;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void showDevMenu(facebook::react::ReactHost *reactHost, double x, double y) {
|
|
62
|
+
if (reactHost == nullptr) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Built fresh each time rather than kept: an item's label will eventually
|
|
67
|
+
// depend on state -- "Disable Fast Refresh" versus "Enable" -- and a menu
|
|
68
|
+
// built once would be the first thing to go stale.
|
|
69
|
+
auto items = std::make_shared<std::vector<DevMenuItem>>(devMenuItems(reactHost));
|
|
70
|
+
|
|
71
|
+
MenuRequest request;
|
|
72
|
+
request.x = x;
|
|
73
|
+
request.y = y;
|
|
74
|
+
request.entries.reserve(items->size());
|
|
75
|
+
for (const DevMenuItem &item : *items) {
|
|
76
|
+
request.entries.push_back(MenuEntry{
|
|
77
|
+
.label = item.label,
|
|
78
|
+
.enabled = true,
|
|
79
|
+
.shortcut = item.shortcut,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Through presentMenu rather than showMenu, so BASALT_TEST_MENU can answer
|
|
84
|
+
// it: a menu nobody dismisses stops an automated run where it stands. See
|
|
85
|
+
// core/TestDialog.h.
|
|
86
|
+
presentMenu(request, [items](int index) {
|
|
87
|
+
// -1 is a dismissal, which is not a failure and is the common case: a menu
|
|
88
|
+
// opened by a keystroke is closed by another one at least as often as it
|
|
89
|
+
// is chosen from.
|
|
90
|
+
if (index < 0 || static_cast<size_t>(index) >= items->size()) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (const auto &action = (*items)[static_cast<size_t>(index)].action) {
|
|
94
|
+
action();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
} // namespace basalt
|