Integrate Additional RNTester Refactoring (9/2 Nightly RN Build) (#6356)

* Integrate Additional RNTester Refactoring (9/2 Nightly RN Build)

Commits: 5bc67b658...f0e80ae22

The most interesting bits here are addiontal RNTester changes, such as the removal of the back button. This forces some more E2ETest updates that were merged in a previous PR.

Nested Images inside of Text are rendered on start now, which is problematic for NetUI. ACoates forked the component for XAML RNTester, and this change forks for NetUI as well, which intention to upstream tracked by #6341. We similarly need to re-patch out AsyncStorage usage on NetUI after the code changed for it.

The fbjs dependency was removed, whichgets closer to letting us remove a node-fetch resolution (though the CLI pulls in an older Metro package which doesn't let us). Android added some extra Platform constants around manufacturer/Brand (likely related to the Xiamoi crash workaround), which we don't implement here, since we only really care about the union of Android + iOS, along with anything we think would be useful specifically for Windows.

* Work around RNW display: none issue

* Change files
This commit is contained in:
Nick Gerleman 2020-10-28 20:53:00 -07:00 коммит произвёл GitHub
Родитель fdf09a5447
Коммит 34d003379d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
56 изменённых файлов: 1813 добавлений и 2720 удалений

Просмотреть файл

@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Integrate Additional RNTester Refactoring (9/2 Nightly RN Build)",
"packageName": "@office-iss/react-native-win32",
"email": "ngerlem@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-10-28T22:55:24.887Z"
}

Просмотреть файл

@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Integrate Additional RNTester Refactoring (9/2 Nightly RN Build)",
"packageName": "react-native-windows",
"email": "ngerlem@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-10-28T22:55:28.008Z"
}

Просмотреть файл

@ -143,7 +143,6 @@ Pay attention to the last line of the LoginPage, we always export a new instance
```
// login.spec.ts
before(() => {
HomePage.backToHomePage();
HomePage.clickAndGotoLoginPage();
});

Просмотреть файл

@ -5,13 +5,20 @@
"excludePatterns": [
"src/js/examples-win/**"
],
"baseVersion": "0.0.0-5bc67b658",
"baseVersion": "0.0.0-f0e80ae22",
"overrides": [
{
"type": "patch",
"file": "src/js/components/RNTesterEmptyBookmarksState.windows.js",
"baseFile": "packages/rn-tester/js/components/RNTesterEmptyBookmarksState.js",
"baseHash": "312a8f4a51f942df975f865638570a5349c0cd3f",
"issue": 6341
},
{
"type": "patch",
"file": "src/js/components/RNTesterExampleList.windows.js",
"baseFile": "packages/rn-tester/js/components/RNTesterExampleList.js",
"baseHash": "6292c4c6f4133f6355b8759ed5bfdefc8bcfcd44",
"baseHash": "9e2583dfe4f3ce7119052e879a2a29ec6996b77f",
"issue": 5834
},
{
@ -84,12 +91,19 @@
"baseHash": "24a37c3bb52acc5a29cf1259756490321fdf1475"
},
{
"type": "derived",
"type": "copy",
"file": "src/js/RNTesterApp.windows.js",
"baseFile": "packages/rn-tester/js/RNTesterApp.ios.js",
"baseHash": "b86a1fd9363b807c340c58135e610bdae8ee2fae",
"baseFile": "packages/rn-tester/js/RNTesterApp.android.js",
"baseHash": "30f3ef9bdc0e2c337309079831664c4a596c404f",
"issue": 4586
},
{
"type": "patch",
"file": "src/js/RNTesterAppShared.windows.js",
"baseFile": "packages/rn-tester/js/RNTesterAppShared.js",
"baseHash": "9584b88105275c66c755247acd363ad33dffcc47",
"issue": 6355
},
{
"type": "derived",
"file": "src/js/utils/RNTesterList.windows.js",

Просмотреть файл

@ -14,7 +14,7 @@
"@react-native/tester": "0.0.1"
},
"peerDependencies": {
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-windows": "^0.0.0-canary.190"
},
"devDependencies": {
@ -22,7 +22,7 @@
"@rnw-scripts/ts-config": "0.1.0",
"eslint": "6.8.0",
"just-scripts": "^0.44.7",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-platform-override": "^0.4.0",
"react-native-windows": "^0.0.0-canary.190",
"typescript": "^3.8.3"

Просмотреть файл

@ -10,363 +10,10 @@
'use strict';
const RNTesterActions = require('./utils/RNTesterActions');
const RNTesterExampleContainer = require('./components/RNTesterExampleContainer');
const RNTesterExampleList = require('./components/RNTesterExampleList');
const RNTesterList = require('./utils/RNTesterList'); // [Windows] Remove .ios
const RNTesterNavigationReducer = require('./utils/RNTesterNavigationReducer');
const React = require('react');
// const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios'); [Windows]
const RNTesterNavbar = require('./components/RNTesterNavbar');
import {AppRegistry} from 'react-native';
const {
AppRegistry,
AsyncStorage,
BackHandler,
Button,
Platform,
SafeAreaView,
StyleSheet,
Text,
useColorScheme,
View,
LogBox,
} = require('react-native');
import type {RNTesterExample} from './types/RNTesterTypes';
import type {
RNTesterAction,
RNTesterExampleAction,
} from './utils/RNTesterActions';
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import RNTesterDocumentationURL from './components/RNTesterDocumentationURL';
import type {ColorSchemeName} from '../../../Libraries/Utilities/NativeAppearance';
import {
RNTesterBookmarkContext,
bookmarks,
} from './components/RNTesterBookmark';
import type {RNTesterBookmark} from './components/RNTesterBookmark';
type Props = {exampleFromAppetizeParams?: ?string, ...};
import {
initializeAsyncStore,
addApi,
addComponent,
removeApi,
removeComponent,
checkBookmarks,
updateRecentlyViewedList,
} from './utils/RNTesterAsyncStorageAbstraction';
const APP_STATE_KEY = 'RNTesterAppState.v2';
const Header = ({
onBack,
title,
documentationURL,
}: {
onBack?: () => mixed,
title: string,
documentationURL?: string,
...
}) => (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<SafeAreaView
style={[
styles.headerContainer,
{
borderBottomColor: theme.SeparatorColor,
backgroundColor: theme.TertiarySystemBackgroundColor,
},
]}>
<View style={styles.header}>
<View style={styles.headerCenter}>
<Text style={[styles.title, {color: theme.LabelColor}]}>
{title}
</Text>
{documentationURL && (
<RNTesterDocumentationURL documentationURL={documentationURL} />
)}
</View>
{onBack && (
<View>
<Button
testID="BackButton"
title="Back"
onPress={onBack}
color={Platform.select({
ios: theme.LinkColor,
default: undefined,
})}
/>
</View>
)}
</View>
</SafeAreaView>
);
}}
</RNTesterThemeContext.Consumer>
);
const RNTesterExampleContainerViaHook = ({
onBack,
title,
module,
}: {
onBack?: () => mixed,
title: string,
module: RNTesterExample,
...
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header
title={title}
onBack={onBack}
documentationURL={module.documentationURL}
/>
<RNTesterExampleContainer module={module} />
</View>
</RNTesterThemeContext.Provider>
);
};
const RNTesterExampleListViaHook = ({
onNavigate,
UpdateRecentlyViewedList,
recentComponents,
recentApis,
bookmark,
list,
screen,
}: {
onNavigate?: (item: RNTesterExampleAction, key: string) => mixed,
UpdateRecentlyViewedList?: (item: RNTesterExample, key: string) => mixed,
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
bookmark: RNTesterBookmark,
screen: string,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
...
},
...
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
const exampleTitle =
screen === 'component'
? 'Component Store'
: screen === 'api'
? 'API Store'
: 'Bookmarks';
return (
<RNTesterThemeContext.Provider value={theme}>
<RNTesterBookmarkContext.Provider value={bookmark}>
<View style={styles.exampleContainer}>
<Header title={exampleTitle} />
<RNTesterExampleList
onNavigate={onNavigate}
recentComponents={recentComponents}
recentApis={recentApis}
updateRecentlyViewedList={UpdateRecentlyViewedList}
list={list}
screen={screen}
/>
</View>
</RNTesterBookmarkContext.Provider>
</RNTesterThemeContext.Provider>
);
};
class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
_mounted: boolean;
constructor() {
super();
// RNTester App currently uses Async Storage from react-native for storing navigation state
// and bookmark items.
// TODO: Add Native Async Storage Module in RNTester
LogBox.ignoreLogs([new RegExp('has been extracted from react-native')]);
this.state = {
openExample: null,
screen: 'component',
Components: bookmarks.Components,
Api: bookmarks.Api,
recentComponents: [],
recentApis: [],
AddApi: (apiName, api) => addApi(apiName, api, this),
AddComponent: (componentName, component) =>
addComponent(componentName, component, this),
RemoveApi: apiName => removeApi(apiName, this),
RemoveComponent: componentName => removeComponent(componentName, this),
checkBookmark: (title, key) => checkBookmarks(title, key, this),
updateRecentlyViewedList: (item, key) =>
updateRecentlyViewedList(item, key, this),
};
}
UNSAFE_componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this._handleBack);
}
componentDidMount() {
initializeAsyncStore(this);
}
_handleBack = () => {
this._handleAction(RNTesterActions.Back(this.state.screen));
};
_handleAction = (action: ?RNTesterAction) => {
if (!action) {
return;
}
const newState = RNTesterNavigationReducer(this.state, action);
if (this.state !== newState) {
// syncing the app screens over async storage
this.setState(newState, () =>
AsyncStorage.setItem(APP_STATE_KEY, JSON.stringify(this.state)),
);
}
};
render(): React.Node | null {
const bookmark = {
Components: this.state.Components,
Api: this.state.Api,
AddApi: this.state.AddApi,
AddComponent: this.state.AddComponent,
RemoveApi: this.state.RemoveApi,
RemoveComponent: this.state.RemoveComponent,
checkBookmark: this.state.checkBookmark,
};
if (!this.state) {
return null;
}
if (this.state.openExample) {
const Component = RNTesterList.Modules[this.state.openExample];
if (Component && Component.external) {
return <Component onExampleExit={this._handleBack} />;
} else {
return (
<>
<RNTesterExampleContainerViaHook
onBack={this._handleBack}
title={Component.title}
module={Component}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavbar onNavigate={this._handleAction} />
</View>
</>
);
}
}
return (
<>
<RNTesterExampleListViaHook
key={this.state.screen}
title={'RNTester'}
onNavigate={this._handleAction}
UpdateRecentlyViewedList={this.state.updateRecentlyViewedList}
recentComponents={this.state.recentComponents}
recentApis={this.state.recentApis}
bookmark={bookmark}
list={RNTesterList}
screen={this.state.screen}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavbar
screen={this.state.screen}
onNavigate={this._handleAction}
/>
</View>
</>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
headerContainer: {
borderBottomWidth: StyleSheet.hairlineWidth,
},
header: {
height: 40,
flexDirection: 'row',
},
headerCenter: {
flex: 1,
position: 'absolute',
top: 7,
left: 0,
right: 0,
alignItems: 'center',
},
title: {
fontSize: 19,
fontWeight: '600',
textAlign: 'center',
},
exampleContainer: {
flex: 1,
},
bottomNavbar: {
bottom: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
},
});
// [Windows
// AppRegistry.registerComponent('SetPropertiesExampleApp', () =>
// require('./examples/SetPropertiesExample/SetPropertiesExampleApp'),
// );
// AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () =>
// require('./examples/RootViewSizeFlexibilityExample/RootViewSizeFlexibilityExampleApp'),
// );
// Windows]
import RNTesterApp from './RNTesterAppShared';
AppRegistry.registerComponent('RNTesterApp', () => RNTesterApp);
// [Windows
// // Register suitable examples for snapshot tests
// RNTesterList.ComponentExamples.concat(RNTesterList.APIExamples).forEach(
// (Example: RNTesterExample) => {
// const ExampleModule = Example.module;
// if (ExampleModule.displayName) {
// class Snapshotter extends React.Component<{...}> {
// render() {
// return (
// <SnapshotViewIOS>
// <RNTesterExampleContainer module={ExampleModule} />
// </SnapshotViewIOS>
// );
// }
// }
// AppRegistry.registerComponent(
// ExampleModule.displayName,
// () => Snapshotter,
// );
// }
// },
// );
// Windows]
module.exports = RNTesterApp;

Просмотреть файл

@ -0,0 +1,251 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import {
BackHandler,
StyleSheet,
useColorScheme,
View,
LogBox,
} from 'react-native';
import * as React from 'react';
import RNTesterExampleContainer from './components/RNTesterExampleContainer';
import RNTesterExampleList from './components/RNTesterExampleList';
import RNTesterNavBar from './components/RNTesterNavbar';
import RNTesterList from './utils/RNTesterList';
import {
Screens,
initialState,
getExamplesListWithBookmarksAndRecentlyUsed,
getInitialStateFromAsyncStorage,
} from './utils/testerStateUtils';
import {useAsyncStorageReducer} from './utils/useAsyncStorageReducer';
import {RNTesterReducer, RNTesterActionsType} from './utils/RNTesterReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import {Header} from './components/RNTesterHeader';
import {RNTesterEmptyBookmarksState} from './components/RNTesterEmptyBookmarksState';
import type {RNTesterTheme} from './components/RNTesterTheme';
import type {ExamplesList} from './types/RNTesterTypes';
const APP_STATE_KEY = 'RNTesterAppState.v3';
// RNTester App currently uses AsyncStorage from react-native for storing navigation state
// and bookmark items.
// TODO: Vendor AsyncStorage or create our own.
LogBox.ignoreLogs([/AsyncStorage has been extracted from react-native/]);
// [Windows #6355 The existing code used isVisible to control style and render
// components with display: none. Windows will currently still propagate an
// insvisible tree to UIA, leading to multiple components in the tree with the
// same testId. Avoid rendering invisible trees instead.
const DisplayIfVisible = ({isVisible, children}) => {
if (isVisible) {
return <View style={[styles.container]}>{children}</View>;
} else {
return <></>;
}
};
// Windows]
type ExampleListsContainerProps = $ReadOnly<{|
theme: RNTesterTheme,
screen: string,
title: string,
examplesList: ExamplesList,
toggleBookmark: (args: {exampleType: string, key: string}) => mixed,
handleExampleCardPress: (args: {exampleType: string, key: string}) => mixed,
isVisible: boolean,
|}>;
const ExampleListsContainer = ({
theme,
screen,
title,
examplesList,
toggleBookmark,
handleExampleCardPress,
isVisible,
}: ExampleListsContainerProps) => {
const isBookmarkEmpty = examplesList.bookmarks.length === 0;
return (
<DisplayIfVisible isVisible={isVisible}>
<Header title={title} theme={theme} />
<DisplayIfVisible isVisible={screen === Screens.COMPONENTS}>
<RNTesterExampleList
sections={examplesList.components}
toggleBookmark={toggleBookmark}
handleExampleCardPress={handleExampleCardPress}
/>
</DisplayIfVisible>
<DisplayIfVisible isVisible={screen === Screens.APIS}>
<RNTesterExampleList
sections={examplesList.apis}
toggleBookmark={toggleBookmark}
handleExampleCardPress={handleExampleCardPress}
/>
</DisplayIfVisible>
<DisplayIfVisible isVisible={screen === Screens.BOOKMARKS}>
{isBookmarkEmpty ? (
<RNTesterEmptyBookmarksState />
) : (
<RNTesterExampleList
sections={examplesList.bookmarks}
toggleBookmark={toggleBookmark}
handleExampleCardPress={handleExampleCardPress}
/>
)}
</DisplayIfVisible>
</DisplayIfVisible>
);
};
const RNTesterApp = (): React.Node => {
const [state, dispatch] = useAsyncStorageReducer(
RNTesterReducer,
initialState,
APP_STATE_KEY,
);
const colorScheme = useColorScheme();
const {openExample, screen, bookmarks, recentlyUsed} = state;
React.useEffect(() => {
getInitialStateFromAsyncStorage(APP_STATE_KEY).then(
initialStateFromStorage => {
dispatch({
type: RNTesterActionsType.INIT_FROM_STORAGE,
data: initialStateFromStorage,
});
},
);
}, [dispatch]);
const examplesList = React.useMemo(
() =>
getExamplesListWithBookmarksAndRecentlyUsed({bookmarks, recentlyUsed}),
[bookmarks, recentlyUsed],
);
const handleBackPress = React.useCallback(() => {
if (openExample) {
dispatch({type: RNTesterActionsType.BACK_BUTTON_PRESS});
}
}, [dispatch, openExample]);
// Setup hardware back button press listener
React.useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', () => {
if (openExample) {
handleBackPress();
return true;
}
return false;
});
}, [openExample, handleBackPress]);
const handleExampleCardPress = React.useCallback(
({exampleType, key}) => {
dispatch({
type: RNTesterActionsType.EXAMPLE_CARD_PRESS,
data: {exampleType, key},
});
},
[dispatch],
);
const toggleBookmark = React.useCallback(
({exampleType, key}) => {
dispatch({
type: RNTesterActionsType.BOOKMARK_PRESS,
data: {exampleType, key},
});
},
[dispatch],
);
const handleNavBarPress = React.useCallback(
args => {
dispatch({
type: RNTesterActionsType.NAVBAR_PRESS,
data: {screen: args.screen},
});
},
[dispatch],
);
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
if (examplesList === null) {
return null;
}
const ExampleModule = openExample && RNTesterList.Modules[openExample];
const title = Screens.COMPONENTS
? 'Components'
: Screens.APIS
? 'APIs'
: 'Bookmarks';
return (
<RNTesterThemeContext.Provider value={theme}>
{ExampleModule && (
<View style={styles.container}>
<Header
onBack={handleBackPress}
title={title}
theme={theme}
documentationURL={ExampleModule.documentationURL}
/>
<RNTesterExampleContainer module={ExampleModule} />
</View>
)}
<ExampleListsContainer
isVisible={!ExampleModule}
screen={screen || Screens.COMPONENTS}
title={title}
theme={theme}
examplesList={examplesList}
handleExampleCardPress={handleExampleCardPress}
toggleBookmark={toggleBookmark}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavBar
screen={screen || Screens.COMPONENTS}
isExamplePageOpen={!!ExampleModule}
handleNavBarPress={handleNavBarPress}
/>
</View>
</RNTesterThemeContext.Provider>
);
};
export default RNTesterApp;
const styles = StyleSheet.create({
container: {
flex: 1,
},
bottomNavbar: {
bottom: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
},
hidden: {
display: 'none',
},
});

Просмотреть файл

@ -0,0 +1,64 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import * as React from 'react';
import {View, Image, Text, StyleSheet} from 'react-native';
export const RNTesterEmptyBookmarksState = (): React.Node => (
<View style={styles.emptyContainer}>
<View style={styles.emptyContainerInner}>
<Image
source={require('../assets/empty.png')}
resizeMode="contain"
style={styles.emptyImage}
/>
<View>
<Text style={styles.heading}>Bookmarks are empty</Text>
<Text style={styles.subheading}>
Please tap the{' '}
{/* [Win32] remove the image since nested non-Text in text is unsupported in NetUI*/}
icon to bookmark examples.
</Text>
</View>
</View>
</View>
);
const styles = StyleSheet.create({
emptyContainer: {
flex: 1,
paddingHorizontal: 40,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
emptyContainerInner: {
marginTop: -150,
},
emptyImage: {
maxWidth: '100%',
height: 300,
},
heading: {
fontSize: 24,
textAlign: 'center',
},
subheading: {
fontSize: 16,
textAlign: 'center',
},
bookmarkIcon: {
width: 24,
height: 24,
transform: [{translateY: 4}],
},
});

Просмотреть файл

@ -9,9 +9,7 @@
*/
'use strict';
import type {RNTesterBookmark} from './RNTesterBookmark.js';
const RNTesterActions = require('../utils/RNTesterActions');
const RNTesterExampleFilter = require('./RNTesterExampleFilter');
const RNTesterComponentTitle = require('./RNTesterComponentTitle');
const React = require('react');
@ -26,173 +24,83 @@ const {
View,
} = require('react-native');
import type {ViewStyleProp} from '../../../Libraries/StyleSheet/StyleSheet';
import type {RNTesterExample} from '../types/RNTesterTypes';
import {RNTesterThemeContext} from './RNTesterTheme';
import {RNTesterBookmarkContext} from './RNTesterBookmark';
type Props = {
screen: string,
onNavigate: Function,
updateRecentlyViewedList: Function,
recentApis: Array<RNTesterExample>,
recentComponents: Array<RNTesterExample>,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
...
},
style?: ?ViewStyleProp,
...
};
type State = {
components: Array<RNTesterExample>,
api: Array<RNTesterExample>,
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
updateRecentlyViewedList: Function,
};
type ButtonState = {active: boolean, key: string, ...};
type ButtonProps = {
item: Object,
section: Object,
active: boolean,
onNavigate: Function,
onPress?: Function,
onShowUnderlay?: Function,
onHideUnderlay?: Function,
updateRecentlyViewedList: Function,
...
};
class RowComponent extends React.PureComponent<ButtonProps, ButtonState> {
static contextType = RNTesterBookmarkContext;
constructor(props: ButtonProps) {
super(props);
this.state = {
active: props.active,
title: props.item.module.title,
key: props.section.key,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.active !== prevState.active) {
return {active: nextProps.active};
}
return null;
}
onButtonPress = () => {
let bookmark = this.context;
if (!this.state.active) {
if (this.state.key === 'APIS' || this.state.key === 'RECENT_APIS') {
bookmark.AddApi(this.props.item.module.title, this.props.item);
} else {
bookmark.AddComponent(this.props.item.module.title, this.props.item);
}
} else {
if (this.state.key === 'APIS' || this.state.key === 'RECENT_APIS') {
bookmark.RemoveApi(this.props.item.module.title);
} else {
bookmark.RemoveComponent(this.props.item.module.title);
}
}
this.setState({
active: !this.state.active,
});
};
_onPress = () => {
this.props.updateRecentlyViewedList();
if (this.props.onPress) {
this.props.onPress();
return;
}
this.props.onNavigate(RNTesterActions.ExampleAction(this.props.item.key));
};
render() {
const {item} = this.props;
const platform = item.module.platform;
const onIos = !platform || platform === 'ios';
const onAndroid = !platform || platform === 'android';
return (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<TouchableHighlight
// [Windows Add testID for e2etest
testID={item.module.title}
// Windows]
onShowUnderlay={this.props.onShowUnderlay}
onHideUnderlay={this.props.onHideUnderlay}
accessibilityLabel={
item.module.title + ' ' + item.module.description
const ExampleCard = ({
onShowUnderlay,
onHideUnderlay,
item,
toggleBookmark,
handlePress,
}) => {
const theme = React.useContext(RNTesterThemeContext);
const platform = item.module.platform;
const onIos = !platform || platform === 'ios';
const onAndroid = !platform || platform === 'android';
return (
<TouchableHighlight
// [Windows Add testID for e2etest
testID={item.module.title}
// Windows]
onShowUnderlay={onShowUnderlay}
onHideUnderlay={onHideUnderlay}
accessibilityLabel={item.module.title + ' ' + item.module.description}
style={styles.listItem}
underlayColor={'rgb(242,242,242)'}
onPress={() =>
handlePress({exampleType: item.exampleType, key: item.key})
}>
<View
style={[styles.row, {backgroundColor: theme.SystemBackgroundColor}]}>
<View style={styles.topRowStyle}>
<RNTesterComponentTitle>{item.module.title}</RNTesterComponentTitle>
<TouchableHighlight
style={styles.imageViewStyle}
onPress={() =>
toggleBookmark({exampleType: item.exampleType, key: item.key})
}>
<Image
style={styles.imageStyle}
source={
item.isBookmarked
? require('../assets/bookmark-outline-blue.png')
: require('../assets/bookmark-outline-gray.png')
}
style={styles.listItem}
underlayColor={'rgb(242,242,242)'}
onPress={this._onPress}>
<View
style={[
styles.row,
{backgroundColor: theme.SystemBackgroundColor},
]}>
<View style={styles.topRowStyle}>
<RNTesterComponentTitle>
{item.module.title}
</RNTesterComponentTitle>
<TouchableHighlight
style={styles.imageViewStyle}
onPress={() => this.onButtonPress()}>
<Image
style={styles.imageStyle}
source={
this.state.active
? require('../assets/bookmark-outline-blue.png')
: require('../assets/bookmark-outline-gray.png')
}
/>
</TouchableHighlight>
</View>
<Text
style={[
styles.rowDetailText,
{color: theme.SecondaryLabelColor, marginBottom: 5},
]}>
{item.module.description}
</Text>
<View style={styles.bottomRowStyle}>
<Text style={{color: theme.SecondaryLabelColor, width: 65}}>
{item.module.category || 'Other'}
</Text>
<View style={styles.platformLabelStyle}>
<Text
style={{
color: onIos ? '#787878' : theme.SeparatorColor,
fontWeight: onIos ? '500' : '300',
}}>
iOS
</Text>
<Text
style={{
color: onAndroid ? '#787878' : theme.SeparatorColor,
fontWeight: onAndroid ? '500' : '300',
}}>
Android
</Text>
</View>
</View>
</View>
</TouchableHighlight>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
/>
</TouchableHighlight>
</View>
<Text
style={[
styles.rowDetailText,
{color: theme.SecondaryLabelColor, marginBottom: 5},
]}>
{item.module.description}
</Text>
<View style={styles.bottomRowStyle}>
<Text style={{color: theme.SecondaryLabelColor, width: 65}}>
{item.module.category || 'Other'}
</Text>
<View style={styles.platformLabelStyle}>
<Text
style={{
color: onIos ? '#787878' : theme.SeparatorColor,
fontWeight: onIos ? '500' : '300',
}}>
iOS
</Text>
<Text
style={{
color: onAndroid ? '#787878' : theme.SeparatorColor,
fontWeight: onAndroid ? '500' : '300',
}}>
Android
</Text>
</View>
</View>
</View>
</TouchableHighlight>
);
};
const renderSectionHeader = ({section}) => (
<RNTesterThemeContext.Consumer>
@ -213,176 +121,57 @@ const renderSectionHeader = ({section}) => (
</RNTesterThemeContext.Consumer>
);
class RNTesterExampleList extends React.Component<Props, State> {
static contextType: React.Context<RNTesterBookmark> = RNTesterBookmarkContext;
const RNTesterExampleList: React$AbstractComponent<any, void> = React.memo(
({sections, toggleBookmark, handleExampleCardPress}) => {
const theme = React.useContext(RNTesterThemeContext);
constructor(props: Props) {
super(props);
this.state = {
components: props.list.ComponentExamples,
api: props.list.APIExamples,
recentComponents: props.recentComponents,
recentApis: props.recentApis,
updateRecentlyViewedList: (item, key) =>
props.updateRecentlyViewedList(item, key),
};
}
static getDerivedStateFromProps(nextProps: Props, prevState: State): State {
if (
nextProps.recentComponents.every(
(component, index) => component !== prevState.recentComponents[index],
) &&
nextProps.recentApis.every(
(api, index) => api !== prevState.recentApis[index],
)
) {
return {
...prevState,
recentComponents: nextProps.recentComponents,
recentApis: nextProps.recentApis,
};
}
return prevState;
}
render(): React.Node {
const bookmark = this.context;
const filter = ({example, filterRegex, category}) =>
filterRegex.test(example.module.title) &&
(!category || example.module.category === category) &&
(!category || example.category === category) &&
(!Platform.isTV || example.supportsTVOS);
const {screen} = this.props;
let sections = [];
if (screen === 'component') {
if (this.state.recentComponents.length > 0) {
sections = [
{
data: this.state.recentComponents,
key: 'RECENT_COMPONENTS',
title: 'Recently viewed',
},
{
data: this.state.components,
key: 'COMPONENTS',
title: 'Components',
},
];
} else {
sections = [
{
data: this.state.components,
key: 'COMPONENTS',
title: 'Components',
},
];
}
} else if (screen === 'api') {
if (this.state.recentApis.length > 0) {
sections = [
{
data: this.state.recentApis,
key: 'RECENT_APIS',
title: 'Recently viewed',
},
{
data: this.state.api,
key: 'APIS',
title: 'APIS',
},
];
} else {
sections = [
{
data: this.state.api,
key: 'APIS',
title: 'APIS',
},
];
}
} else if (screen === 'bookmark') {
sections = [
{
data: Object.values(bookmark.Components),
title: 'COMPONENTS',
key: 'COMPONENTS',
},
{
data: Object.values(bookmark.Api),
title: 'APIS',
key: 'APIS',
},
];
} else {
sections = [];
}
const isEmpty = sections.filter(s => s.data.length).length === 0;
if (isEmpty) {
return <EmptyState />;
}
const renderListItem = ({item, section, separators}) => {
return (
<ExampleCard
item={item}
section={section}
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
toggleBookmark={toggleBookmark}
handlePress={handleExampleCardPress}
/>
);
};
return (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.listContainer,
this.props.style,
{backgroundColor: theme.SecondaryGroupedBackgroundColor},
]}>
<RNTesterExampleFilter
testID="explorer_search"
page="components_page"
// $FlowFixMe
sections={sections}
filter={filter}
render={({filteredSections}) => (
<SectionList
sections={filteredSections}
extraData={filteredSections}
renderItem={this._renderItem}
ItemSeparatorComponent={ItemSeparator}
keyboardShouldPersistTaps="handled"
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
renderSectionHeader={renderSectionHeader}
ListFooterComponent={() => <View style={{height: 200}} />}
/>
)}
/>
</View>
);
}}
</RNTesterThemeContext.Consumer>
<View
style={[
styles.listContainer,
{backgroundColor: theme.SecondaryGroupedBackgroundColor},
]}>
<RNTesterExampleFilter
testID="explorer_search"
page="components_page"
sections={sections}
filter={filter}
render={({filteredSections}) => (
<SectionList
sections={filteredSections}
extraData={filteredSections}
renderItem={renderListItem}
ItemSeparatorComponent={ItemSeparator}
keyboardShouldPersistTaps="handled"
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
renderSectionHeader={renderSectionHeader}
ListFooterComponent={() => <View style={{height: 80}} />}
/>
)}
/>
</View>
);
}
_renderItem = ({item, section, separators, index}) => {
let bookmark = this.context;
return (
<RowComponent
item={item}
section={section}
active={!bookmark.checkBookmark(item.module.title, section.key)}
onNavigate={this.props.onNavigate}
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
updateRecentlyViewedList={() =>
this.state.updateRecentlyViewedList(item, section.key)
}
/>
);
};
_handleRowPress(exampleKey: string): void {
this.props.onNavigate(RNTesterActions.ExampleAction(exampleKey));
}
}
},
);
const ItemSeparator = ({highlighted}) => (
<RNTesterThemeContext.Consumer>
@ -403,26 +192,6 @@ const ItemSeparator = ({highlighted}) => (
</RNTesterThemeContext.Consumer>
);
const EmptyState = () => (
<View style={styles.emptyContainer}>
<View style={styles.emptyContainerInner}>
<Image
source={require('../assets/empty.png')}
resizeMode="contain"
style={styles.emptyImage}
/>
<View>
<Text style={styles.heading}>Bookmarks are empty</Text>
<Text style={styles.subheading}>
{/* [Windows replace Bookmark inline image with text Bookmark */}
Please tap the Bookmark icon to bookmark examples.
{/* Windows] */}
</Text>
</View>
</View>
</View>
);
const styles = StyleSheet.create({
listContainer: {
flex: 1,
@ -482,33 +251,6 @@ const styles = StyleSheet.create({
width: 100,
justifyContent: 'space-between',
},
emptyContainer: {
flex: 1,
paddingHorizontal: 40,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
emptyContainerInner: {
marginTop: -150,
},
emptyImage: {
maxWidth: '100%',
height: 300,
},
heading: {
fontSize: 24,
textAlign: 'center',
},
subheading: {
fontSize: 16,
textAlign: 'center',
},
bookmarkIcon: {
width: 24,
height: 24,
transform: [{translateY: 4}],
},
});
module.exports = RNTesterExampleList;

Просмотреть файл

@ -1,5 +1,5 @@
{
"baseVersion": "0.0.0-5bc67b658",
"baseVersion": "0.0.0-f0e80ae22",
"overrides": [
{
"type": "copy",

Просмотреть файл

@ -10,331 +10,9 @@
'use strict';
const RNTesterActions = require('./utils/RNTesterActions');
const RNTesterExampleContainer = require('./components/RNTesterExampleContainer');
const RNTesterExampleList = require('./components/RNTesterExampleList');
const RNTesterList = require('./utils/RNTesterList');
const RNTesterNavigationReducer = require('./utils/RNTesterNavigationReducer');
const React = require('react');
const RNTesterNavBar = require('./components/RNTesterNavbar');
import {AppRegistry} from 'react-native';
const {
AppRegistry,
AsyncStorage,
BackHandler,
StyleSheet,
Text,
UIManager,
useColorScheme,
View,
LogBox,
} = require('react-native');
import type {RNTesterExample} from './types/RNTesterTypes';
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import RNTesterDocumentationURL from './components/RNTesterDocumentationURL';
import {
RNTesterBookmarkContext,
bookmarks,
} from './components/RNTesterBookmark';
import type {RNTesterBookmark} from './components/RNTesterBookmark';
import {
initializeAsyncStore,
addApi,
addComponent,
removeApi,
removeComponent,
checkBookmarks,
updateRecentlyViewedList,
} from './utils/RNTesterAsyncStorageAbstraction';
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
type Props = {exampleFromAppetizeParams?: ?string, ...};
const APP_STATE_KEY = 'RNTesterAppState.v2';
const Header = ({
title,
documentationURL,
}: {
title: string,
documentationURL?: string,
...
}) => (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View style={[styles.toolbar, {backgroundColor: '#F3F8FF'}]}>
<View style={styles.toolbarCenter}>
<Text style={[styles.title, {color: theme.LabelColor}]}>
{title}
</Text>
{documentationURL && (
<RNTesterDocumentationURL documentationURL={documentationURL} />
)}
</View>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
const RNTesterExampleContainerViaHook = ({
title,
module,
exampleRef,
}: {
title: string,
module: RNTesterExample,
exampleRef: () => void,
...
}) => {
const colorScheme = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.container}>
<Header title={title} documentationURL={module.documentationURL} />
<RNTesterExampleContainer module={module} ref={exampleRef} />
</View>
</RNTesterThemeContext.Provider>
);
};
const RNTesterExampleListViaHook = ({
title,
onNavigate,
UpdateRecentlyViewedList,
recentComponents,
recentApis,
bookmark,
list,
screen,
}: {
title: string,
screen: string,
onNavigate?: () => mixed,
UpdateRecentlyViewedList?: (item: RNTesterExample, key: string) => mixed,
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
...
},
bookmark: RNTesterBookmark,
...
}) => {
const colorScheme = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
const exampleTitle =
screen === 'component'
? 'Component Store'
: screen === 'api'
? 'API Store'
: 'Bookmarks';
return (
<RNTesterThemeContext.Provider value={theme}>
<RNTesterBookmarkContext.Provider value={bookmark}>
<View style={styles.container}>
<Header title={exampleTitle} />
<RNTesterExampleList
onNavigate={onNavigate}
recentComponents={recentComponents}
recentApis={recentApis}
updateRecentlyViewedList={UpdateRecentlyViewedList}
list={list}
screen={screen}
/>
</View>
</RNTesterBookmarkContext.Provider>
</RNTesterThemeContext.Provider>
);
};
class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
constructor() {
super();
// RNTester App currently uses Async Storage from react-native for storing navigation state
// and bookmark items.
// TODO: Add Native Async Storage Module in RNTester
LogBox.ignoreLogs([new RegExp('has been extracted from react-native')]);
this.state = {
openExample: null,
Components: bookmarks.Components,
Api: bookmarks.Api,
recentComponents: [],
recentApis: [],
screen: 'component',
AddApi: (apiName, api) => addApi(apiName, api, this),
AddComponent: (componentName, component) =>
addComponent(componentName, component, this),
RemoveApi: apiName => removeApi(apiName, this),
RemoveComponent: componentName => removeComponent(componentName, this),
checkBookmark: (title, key) => checkBookmarks(title, key, this),
updateRecentlyViewedList: (item, key) =>
updateRecentlyViewedList(item, key, this),
};
}
UNSAFE_componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', () =>
this._handleBackButtonPress(this.state.screen),
);
}
componentDidMount() {
initializeAsyncStore(this);
}
render(): React.Node {
if (!this.state) {
return null;
}
return (
<View style={styles.container}>
{this._renderApp({
Components: this.state.Components,
Api: this.state.Api,
AddApi: this.state.AddApi,
AddComponent: this.state.AddComponent,
RemoveApi: this.state.RemoveApi,
RemoveComponent: this.state.RemoveComponent,
checkBookmark: this.state.checkBookmark,
})}
<View style={styles.bottomNavbar}>
<RNTesterNavBar
screen={this.state.screen}
onNavigate={this._handleAction}
/>
</View>
</View>
);
}
_renderApp(bookmark) {
const {openExample, screen} = this.state;
if (openExample) {
const ExampleModule = RNTesterList.Modules[openExample];
if (ExampleModule.external) {
return (
<ExampleModule
onExampleExit={() => {
this._handleAction(RNTesterActions.Back(screen));
}}
ref={example => {
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue
* was found when making Flow check .android.js files. */
this._exampleRef = example;
}}
/>
);
} else if (ExampleModule) {
return (
<>
<RNTesterExampleContainerViaHook
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
title={ExampleModule.title}
module={ExampleModule}
exampleRef={example => {
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue
* was found when making Flow check .android.js files. */
this._exampleRef = example;
}}
/>
</>
);
}
}
return (
<RNTesterExampleListViaHook
key={screen}
title={'RNTester'}
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
onNavigate={this._handleAction}
UpdateRecentlyViewedList={this.state.updateRecentlyViewedList}
recentComponents={this.state.recentComponents}
recentApis={this.state.recentApis}
bookmark={bookmark}
list={RNTesterList}
screen={screen}
/>
);
}
_handleAction = (action: Object): boolean => {
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
const newState = RNTesterNavigationReducer(this.state, action);
if (this.state !== newState) {
this.setState(newState, () =>
AsyncStorage.setItem(APP_STATE_KEY, JSON.stringify(this.state)),
);
return true;
}
return false;
};
_handleBackButtonPress = screen => {
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
if (
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
this._exampleRef &&
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
this._exampleRef.handleBackAction &&
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
this._exampleRef.handleBackAction()
) {
return true;
}
return this._handleAction(RNTesterActions.Back(screen));
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
toolbar: {
height: 56,
},
toolbarLeft: {
marginTop: 2,
},
toolbarCenter: {
flex: 1,
position: 'absolute',
top: 12,
left: 0,
right: 0,
alignItems: 'center',
},
title: {
fontSize: 19,
fontWeight: '600',
textAlign: 'center',
},
bottomNavbar: {
bottom: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
},
});
import RNTesterApp from './RNTesterAppShared';
AppRegistry.registerComponent('RNTesterApp', () => RNTesterApp);

Просмотреть файл

@ -10,328 +10,14 @@
'use strict';
const RNTesterActions = require('./utils/RNTesterActions');
const RNTesterExampleContainer = require('./components/RNTesterExampleContainer');
const RNTesterExampleList = require('./components/RNTesterExampleList');
const RNTesterList = require('./utils/RNTesterList.ios');
const RNTesterNavigationReducer = require('./utils/RNTesterNavigationReducer');
const React = require('react');
const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios');
const RNTesterNavbar = require('./components/RNTesterNavbar');
const {
AppRegistry,
AsyncStorage,
BackHandler,
Button,
Platform,
SafeAreaView,
StyleSheet,
Text,
useColorScheme,
View,
LogBox,
} = require('react-native');
import {AppRegistry} from 'react-native';
import React from 'react';
import SnapshotViewIOS from './examples/Snapshot/SnapshotViewIOS.ios';
import RNTesterExampleContainer from './components/RNTesterExampleContainer';
import RNTesterList from './utils/RNTesterList';
import RNTesterApp from './RNTesterAppShared';
import type {RNTesterExample} from './types/RNTesterTypes';
import type {
RNTesterAction,
RNTesterExampleAction,
} from './utils/RNTesterActions';
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import RNTesterDocumentationURL from './components/RNTesterDocumentationURL';
import type {ColorSchemeName} from '../../../Libraries/Utilities/NativeAppearance';
import {
RNTesterBookmarkContext,
bookmarks,
} from './components/RNTesterBookmark';
import type {RNTesterBookmark} from './components/RNTesterBookmark';
type Props = {exampleFromAppetizeParams?: ?string, ...};
import {
initializeAsyncStore,
addApi,
addComponent,
removeApi,
removeComponent,
checkBookmarks,
updateRecentlyViewedList,
} from './utils/RNTesterAsyncStorageAbstraction';
const APP_STATE_KEY = 'RNTesterAppState.v2';
const Header = ({
onBack,
title,
documentationURL,
}: {
onBack?: () => mixed,
title: string,
documentationURL?: string,
...
}) => (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<SafeAreaView
style={[
styles.headerContainer,
{
borderBottomColor: theme.SeparatorColor,
backgroundColor: theme.TertiarySystemBackgroundColor,
},
]}>
<View style={styles.header}>
<View style={styles.headerCenter}>
<Text style={[styles.title, {color: theme.LabelColor}]}>
{title}
</Text>
{documentationURL && (
<RNTesterDocumentationURL documentationURL={documentationURL} />
)}
</View>
{onBack && (
<View>
<Button
title="Back"
onPress={onBack}
color={Platform.select({
ios: theme.LinkColor,
default: undefined,
})}
/>
</View>
)}
</View>
</SafeAreaView>
);
}}
</RNTesterThemeContext.Consumer>
);
const RNTesterExampleContainerViaHook = ({
onBack,
title,
module,
}: {
onBack?: () => mixed,
title: string,
module: RNTesterExample,
...
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header
title={title}
onBack={onBack}
documentationURL={module.documentationURL}
/>
<RNTesterExampleContainer module={module} />
</View>
</RNTesterThemeContext.Provider>
);
};
const RNTesterExampleListViaHook = ({
onNavigate,
UpdateRecentlyViewedList,
recentComponents,
recentApis,
bookmark,
list,
screen,
}: {
onNavigate?: (item: RNTesterExampleAction, key: string) => mixed,
UpdateRecentlyViewedList?: (item: RNTesterExample, key: string) => mixed,
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
bookmark: RNTesterBookmark,
screen: string,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
...
},
...
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
const exampleTitle =
screen === 'component'
? 'Component Store'
: screen === 'api'
? 'API Store'
: 'Bookmarks';
return (
<RNTesterThemeContext.Provider value={theme}>
<RNTesterBookmarkContext.Provider value={bookmark}>
<View style={styles.exampleContainer}>
<Header title={exampleTitle} />
<RNTesterExampleList
onNavigate={onNavigate}
recentComponents={recentComponents}
recentApis={recentApis}
updateRecentlyViewedList={UpdateRecentlyViewedList}
list={list}
screen={screen}
/>
</View>
</RNTesterBookmarkContext.Provider>
</RNTesterThemeContext.Provider>
);
};
class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
_mounted: boolean;
constructor() {
super();
// RNTester App currently uses Async Storage from react-native for storing navigation state
// and bookmark items.
// TODO: Add Native Async Storage Module in RNTester
LogBox.ignoreLogs([new RegExp('has been extracted from react-native')]);
this.state = {
openExample: null,
screen: 'component',
Components: bookmarks.Components,
Api: bookmarks.Api,
recentComponents: [],
recentApis: [],
AddApi: (apiName, api) => addApi(apiName, api, this),
AddComponent: (componentName, component) =>
addComponent(componentName, component, this),
RemoveApi: apiName => removeApi(apiName, this),
RemoveComponent: componentName => removeComponent(componentName, this),
checkBookmark: (title, key) => checkBookmarks(title, key, this),
updateRecentlyViewedList: (item, key) =>
updateRecentlyViewedList(item, key, this),
};
}
UNSAFE_componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this._handleBack);
}
componentDidMount() {
initializeAsyncStore(this);
}
_handleBack = () => {
this._handleAction(RNTesterActions.Back(this.state.screen));
};
_handleAction = (action: ?RNTesterAction) => {
if (!action) {
return;
}
const newState = RNTesterNavigationReducer(this.state, action);
if (this.state !== newState) {
// syncing the app screens over async storage
this.setState(newState, () =>
AsyncStorage.setItem(APP_STATE_KEY, JSON.stringify(this.state)),
);
}
};
render(): React.Node | null {
const bookmark = {
Components: this.state.Components,
Api: this.state.Api,
AddApi: this.state.AddApi,
AddComponent: this.state.AddComponent,
RemoveApi: this.state.RemoveApi,
RemoveComponent: this.state.RemoveComponent,
checkBookmark: this.state.checkBookmark,
};
if (!this.state) {
return null;
}
if (this.state.openExample) {
const Component = RNTesterList.Modules[this.state.openExample];
if (Component && Component.external) {
return <Component onExampleExit={this._handleBack} />;
} else {
return (
<>
<RNTesterExampleContainerViaHook
onBack={this._handleBack}
title={Component.title}
module={Component}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavbar onNavigate={this._handleAction} />
</View>
</>
);
}
}
return (
<>
<RNTesterExampleListViaHook
key={this.state.screen}
title={'RNTester'}
onNavigate={this._handleAction}
UpdateRecentlyViewedList={this.state.updateRecentlyViewedList}
recentComponents={this.state.recentComponents}
recentApis={this.state.recentApis}
bookmark={bookmark}
list={RNTesterList}
screen={this.state.screen}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavbar
screen={this.state.screen}
onNavigate={this._handleAction}
/>
</View>
</>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
headerContainer: {
borderBottomWidth: StyleSheet.hairlineWidth,
},
header: {
height: 40,
flexDirection: 'row',
},
headerCenter: {
flex: 1,
position: 'absolute',
top: 7,
left: 0,
right: 0,
alignItems: 'center',
},
title: {
fontSize: 19,
fontWeight: '600',
textAlign: 'center',
},
exampleContainer: {
flex: 1,
},
bottomNavbar: {
bottom: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
},
});
AppRegistry.registerComponent('SetPropertiesExampleApp', () =>
require('./examples/SetPropertiesExample/SetPropertiesExampleApp'),

Просмотреть файл

@ -0,0 +1,244 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import {
BackHandler,
StyleSheet,
useColorScheme,
View,
LogBox,
} from 'react-native';
import * as React from 'react';
import RNTesterExampleContainer from './components/RNTesterExampleContainer';
import RNTesterExampleList from './components/RNTesterExampleList';
import RNTesterNavBar from './components/RNTesterNavbar';
import RNTesterList from './utils/RNTesterList';
import {
Screens,
initialState,
getExamplesListWithBookmarksAndRecentlyUsed,
getInitialStateFromAsyncStorage,
} from './utils/testerStateUtils';
import {useAsyncStorageReducer} from './utils/useAsyncStorageReducer';
import {RNTesterReducer, RNTesterActionsType} from './utils/RNTesterReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import {Header} from './components/RNTesterHeader';
import {RNTesterEmptyBookmarksState} from './components/RNTesterEmptyBookmarksState';
import type {RNTesterTheme} from './components/RNTesterTheme';
import type {ExamplesList} from './types/RNTesterTypes';
const APP_STATE_KEY = 'RNTesterAppState.v3';
// RNTester App currently uses AsyncStorage from react-native for storing navigation state
// and bookmark items.
// TODO: Vendor AsyncStorage or create our own.
LogBox.ignoreLogs([/AsyncStorage has been extracted from react-native/]);
const DisplayIfVisible = ({isVisible, children}) => (
<View style={[styles.container, !isVisible && styles.hidden]}>
{children}
</View>
);
type ExampleListsContainerProps = $ReadOnly<{|
theme: RNTesterTheme,
screen: string,
title: string,
examplesList: ExamplesList,
toggleBookmark: (args: {exampleType: string, key: string}) => mixed,
handleExampleCardPress: (args: {exampleType: string, key: string}) => mixed,
isVisible: boolean,
|}>;
const ExampleListsContainer = ({
theme,
screen,
title,
examplesList,
toggleBookmark,
handleExampleCardPress,
isVisible,
}: ExampleListsContainerProps) => {
const isBookmarkEmpty = examplesList.bookmarks.length === 0;
return (
<DisplayIfVisible isVisible={isVisible}>
<Header title={title} theme={theme} />
<DisplayIfVisible isVisible={screen === Screens.COMPONENTS}>
<RNTesterExampleList
sections={examplesList.components}
toggleBookmark={toggleBookmark}
handleExampleCardPress={handleExampleCardPress}
/>
</DisplayIfVisible>
<DisplayIfVisible isVisible={screen === Screens.APIS}>
<RNTesterExampleList
sections={examplesList.apis}
toggleBookmark={toggleBookmark}
handleExampleCardPress={handleExampleCardPress}
/>
</DisplayIfVisible>
<DisplayIfVisible isVisible={screen === Screens.BOOKMARKS}>
{isBookmarkEmpty ? (
<RNTesterEmptyBookmarksState />
) : (
<RNTesterExampleList
sections={examplesList.bookmarks}
toggleBookmark={toggleBookmark}
handleExampleCardPress={handleExampleCardPress}
/>
)}
</DisplayIfVisible>
</DisplayIfVisible>
);
};
const RNTesterApp = (): React.Node => {
const [state, dispatch] = useAsyncStorageReducer(
RNTesterReducer,
initialState,
APP_STATE_KEY,
);
const colorScheme = useColorScheme();
const {openExample, screen, bookmarks, recentlyUsed} = state;
React.useEffect(() => {
getInitialStateFromAsyncStorage(APP_STATE_KEY).then(
initialStateFromStorage => {
dispatch({
type: RNTesterActionsType.INIT_FROM_STORAGE,
data: initialStateFromStorage,
});
},
);
}, [dispatch]);
const examplesList = React.useMemo(
() =>
getExamplesListWithBookmarksAndRecentlyUsed({bookmarks, recentlyUsed}),
[bookmarks, recentlyUsed],
);
const handleBackPress = React.useCallback(() => {
if (openExample) {
dispatch({type: RNTesterActionsType.BACK_BUTTON_PRESS});
}
}, [dispatch, openExample]);
// Setup hardware back button press listener
React.useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', () => {
if (openExample) {
handleBackPress();
return true;
}
return false;
});
}, [openExample, handleBackPress]);
const handleExampleCardPress = React.useCallback(
({exampleType, key}) => {
dispatch({
type: RNTesterActionsType.EXAMPLE_CARD_PRESS,
data: {exampleType, key},
});
},
[dispatch],
);
const toggleBookmark = React.useCallback(
({exampleType, key}) => {
dispatch({
type: RNTesterActionsType.BOOKMARK_PRESS,
data: {exampleType, key},
});
},
[dispatch],
);
const handleNavBarPress = React.useCallback(
args => {
dispatch({
type: RNTesterActionsType.NAVBAR_PRESS,
data: {screen: args.screen},
});
},
[dispatch],
);
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
if (examplesList === null) {
return null;
}
const ExampleModule = openExample && RNTesterList.Modules[openExample];
const title = Screens.COMPONENTS
? 'Components'
: Screens.APIS
? 'APIs'
: 'Bookmarks';
return (
<RNTesterThemeContext.Provider value={theme}>
{ExampleModule && (
<View style={styles.container}>
<Header
onBack={handleBackPress}
title={title}
theme={theme}
documentationURL={ExampleModule.documentationURL}
/>
<RNTesterExampleContainer module={ExampleModule} />
</View>
)}
<ExampleListsContainer
isVisible={!ExampleModule}
screen={screen || Screens.COMPONENTS}
title={title}
theme={theme}
examplesList={examplesList}
handleExampleCardPress={handleExampleCardPress}
toggleBookmark={toggleBookmark}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavBar
screen={screen || Screens.COMPONENTS}
isExamplePageOpen={!!ExampleModule}
handleNavBarPress={handleNavBarPress}
/>
</View>
</RNTesterThemeContext.Provider>
);
};
export default RNTesterApp;
const styles = StyleSheet.create({
container: {
flex: 1,
},
bottomNavbar: {
bottom: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
},
hidden: {
display: 'none',
},
});

Просмотреть файл

@ -36,7 +36,7 @@ export default function ExamplePage(props: Props): React.Node {
return (
<>
<View style={styles.titleView}>
<Text style={{marginVertical: 8, fontSize: 16}}>{description}</Text>
<Text style={styles.description}>{description}</Text>
<View style={styles.rowStyle}>
<Text style={{color: theme.SecondaryLabelColor, width: 65}}>
{category || 'Other'}
@ -80,9 +80,8 @@ const styles = StyleSheet.create({
flexGrow: 1,
},
description: {
paddingVertical: 5,
flexDirection: 'row',
justifyContent: 'space-between',
marginVertical: 8,
fontSize: 16,
},
docsContainer: {
alignContent: 'center',

Просмотреть файл

@ -20,9 +20,7 @@ type Props = $ReadOnly<{|
description?: ?string,
|}>;
/** functional component for generating example blocks */
const RNTesterBlock = (props: Props): React.Node => {
const {description, title, children} = props;
const RNTesterBlock = ({description, title, children}: Props): React.Node => {
const theme = React.useContext(RNTesterThemeContext);
return (
<View style={[[styles.container], {borderColor: theme.SeparatorColor}]}>
@ -60,7 +58,7 @@ const styles = StyleSheet.create({
color: 'black',
},
children: {
paddingVertical: 10,
paddingTop: 10,
paddingHorizontal: 10,
margin: 10,
},

Просмотреть файл

@ -0,0 +1,68 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import * as React from 'react';
import {View, Image, Text, StyleSheet} from 'react-native';
export const RNTesterEmptyBookmarksState = (): React.Node => (
<View style={styles.emptyContainer}>
<View style={styles.emptyContainerInner}>
<Image
source={require('../assets/empty.png')}
resizeMode="contain"
style={styles.emptyImage}
/>
<View>
<Text style={styles.heading}>Bookmarks are empty</Text>
<Text style={styles.subheading}>
Please tap the{' '}
<Image
source={require('../assets/bookmark-outline-gray.png')}
resizeMode="contain"
style={styles.bookmarkIcon}
/>{' '}
icon to bookmark examples.
</Text>
</View>
</View>
</View>
);
const styles = StyleSheet.create({
emptyContainer: {
flex: 1,
paddingHorizontal: 40,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
emptyContainerInner: {
marginTop: -150,
},
emptyImage: {
maxWidth: '100%',
height: 300,
},
heading: {
fontSize: 24,
textAlign: 'center',
},
subheading: {
fontSize: 16,
textAlign: 'center',
},
bookmarkIcon: {
width: 24,
height: 24,
transform: [{translateY: 4}],
},
});

Просмотреть файл

@ -22,18 +22,16 @@ const {
import {RNTesterThemeContext} from './RNTesterTheme';
import type {RNTesterExample} from '../types/RNTesterTypes';
import type {SectionData} from '../types/RNTesterTypes';
type Props = {
filter: Function,
render: Function,
disableSearch?: boolean,
testID?: string,
hideFilterPills?: boolean,
page: string, // possible values -> examples_page, components_page, bookmarks_page
sections: Array<{
data: Array<RNTesterExample>,
title: string,
key: string,
}>,
page: 'examples_page' | 'components_page' | 'bookmarks_page',
sections: SectionData[],
...
};
@ -71,7 +69,7 @@ class RNTesterExampleFilter extends React.Component<Props, State> {
if (this.state.filter.trim() !== '' || this.state.category.trim() !== '') {
filteredSections = filteredSections.filter(
section => section.title !== 'Recently viewed',
section => section.title !== 'Recently Viewed',
);
}

Просмотреть файл

@ -9,9 +9,7 @@
*/
'use strict';
import type {RNTesterBookmark} from './RNTesterBookmark.js';
const RNTesterActions = require('../utils/RNTesterActions');
const RNTesterExampleFilter = require('./RNTesterExampleFilter');
const RNTesterComponentTitle = require('./RNTesterComponentTitle');
const React = require('react');
@ -26,170 +24,80 @@ const {
View,
} = require('react-native');
import type {ViewStyleProp} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {RNTesterExample} from '../types/RNTesterTypes';
import {RNTesterThemeContext} from './RNTesterTheme';
import {RNTesterBookmarkContext} from './RNTesterBookmark';
type Props = {
screen: string,
onNavigate: Function,
updateRecentlyViewedList: Function,
recentApis: Array<RNTesterExample>,
recentComponents: Array<RNTesterExample>,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
...
},
style?: ?ViewStyleProp,
...
};
type State = {
components: Array<RNTesterExample>,
api: Array<RNTesterExample>,
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
updateRecentlyViewedList: Function,
};
type ButtonState = {active: boolean, key: string, ...};
type ButtonProps = {
item: Object,
section: Object,
active: boolean,
onNavigate: Function,
onPress?: Function,
onShowUnderlay?: Function,
onHideUnderlay?: Function,
updateRecentlyViewedList: Function,
...
};
class RowComponent extends React.PureComponent<ButtonProps, ButtonState> {
static contextType = RNTesterBookmarkContext;
constructor(props: ButtonProps) {
super(props);
this.state = {
active: props.active,
title: props.item.module.title,
key: props.section.key,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.active !== prevState.active) {
return {active: nextProps.active};
}
return null;
}
onButtonPress = () => {
let bookmark = this.context;
if (!this.state.active) {
if (this.state.key === 'APIS' || this.state.key === 'RECENT_APIS') {
bookmark.AddApi(this.props.item.module.title, this.props.item);
} else {
bookmark.AddComponent(this.props.item.module.title, this.props.item);
}
} else {
if (this.state.key === 'APIS' || this.state.key === 'RECENT_APIS') {
bookmark.RemoveApi(this.props.item.module.title);
} else {
bookmark.RemoveComponent(this.props.item.module.title);
}
}
this.setState({
active: !this.state.active,
});
};
_onPress = () => {
this.props.updateRecentlyViewedList();
if (this.props.onPress) {
this.props.onPress();
return;
}
this.props.onNavigate(RNTesterActions.ExampleAction(this.props.item.key));
};
render() {
const {item} = this.props;
const platform = item.module.platform;
const onIos = !platform || platform === 'ios';
const onAndroid = !platform || platform === 'android';
return (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<TouchableHighlight
onShowUnderlay={this.props.onShowUnderlay}
onHideUnderlay={this.props.onHideUnderlay}
accessibilityLabel={
item.module.title + ' ' + item.module.description
const ExampleCard = ({
onShowUnderlay,
onHideUnderlay,
item,
toggleBookmark,
handlePress,
}) => {
const theme = React.useContext(RNTesterThemeContext);
const platform = item.module.platform;
const onIos = !platform || platform === 'ios';
const onAndroid = !platform || platform === 'android';
return (
<TouchableHighlight
onShowUnderlay={onShowUnderlay}
onHideUnderlay={onHideUnderlay}
accessibilityLabel={item.module.title + ' ' + item.module.description}
style={styles.listItem}
underlayColor={'rgb(242,242,242)'}
onPress={() =>
handlePress({exampleType: item.exampleType, key: item.key})
}>
<View
style={[styles.row, {backgroundColor: theme.SystemBackgroundColor}]}>
<View style={styles.topRowStyle}>
<RNTesterComponentTitle>{item.module.title}</RNTesterComponentTitle>
<TouchableHighlight
style={styles.imageViewStyle}
onPress={() =>
toggleBookmark({exampleType: item.exampleType, key: item.key})
}>
<Image
style={styles.imageStyle}
source={
item.isBookmarked
? require('../assets/bookmark-outline-blue.png')
: require('../assets/bookmark-outline-gray.png')
}
style={styles.listItem}
underlayColor={'rgb(242,242,242)'}
onPress={this._onPress}>
<View
style={[
styles.row,
{backgroundColor: theme.SystemBackgroundColor},
]}>
<View style={styles.topRowStyle}>
<RNTesterComponentTitle>
{item.module.title}
</RNTesterComponentTitle>
<TouchableHighlight
style={styles.imageViewStyle}
onPress={() => this.onButtonPress()}>
<Image
style={styles.imageStyle}
source={
this.state.active
? require('../assets/bookmark-outline-blue.png')
: require('../assets/bookmark-outline-gray.png')
}
/>
</TouchableHighlight>
</View>
<Text
style={[
styles.rowDetailText,
{color: theme.SecondaryLabelColor, marginBottom: 5},
]}>
{item.module.description}
</Text>
<View style={styles.bottomRowStyle}>
<Text style={{color: theme.SecondaryLabelColor, width: 65}}>
{item.module.category || 'Other'}
</Text>
<View style={styles.platformLabelStyle}>
<Text
style={{
color: onIos ? '#787878' : theme.SeparatorColor,
fontWeight: onIos ? '500' : '300',
}}>
iOS
</Text>
<Text
style={{
color: onAndroid ? '#787878' : theme.SeparatorColor,
fontWeight: onAndroid ? '500' : '300',
}}>
Android
</Text>
</View>
</View>
</View>
</TouchableHighlight>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
/>
</TouchableHighlight>
</View>
<Text
style={[
styles.rowDetailText,
{color: theme.SecondaryLabelColor, marginBottom: 5},
]}>
{item.module.description}
</Text>
<View style={styles.bottomRowStyle}>
<Text style={{color: theme.SecondaryLabelColor, width: 65}}>
{item.module.category || 'Other'}
</Text>
<View style={styles.platformLabelStyle}>
<Text
style={{
color: onIos ? '#787878' : theme.SeparatorColor,
fontWeight: onIos ? '500' : '300',
}}>
iOS
</Text>
<Text
style={{
color: onAndroid ? '#787878' : theme.SeparatorColor,
fontWeight: onAndroid ? '500' : '300',
}}>
Android
</Text>
</View>
</View>
</View>
</TouchableHighlight>
);
};
const renderSectionHeader = ({section}) => (
<RNTesterThemeContext.Consumer>
@ -210,176 +118,57 @@ const renderSectionHeader = ({section}) => (
</RNTesterThemeContext.Consumer>
);
class RNTesterExampleList extends React.Component<Props, State> {
static contextType: React.Context<RNTesterBookmark> = RNTesterBookmarkContext;
const RNTesterExampleList: React$AbstractComponent<any, void> = React.memo(
({sections, toggleBookmark, handleExampleCardPress}) => {
const theme = React.useContext(RNTesterThemeContext);
constructor(props: Props) {
super(props);
this.state = {
components: props.list.ComponentExamples,
api: props.list.APIExamples,
recentComponents: props.recentComponents,
recentApis: props.recentApis,
updateRecentlyViewedList: (item, key) =>
props.updateRecentlyViewedList(item, key),
};
}
static getDerivedStateFromProps(nextProps: Props, prevState: State): State {
if (
nextProps.recentComponents.every(
(component, index) => component !== prevState.recentComponents[index],
) &&
nextProps.recentApis.every(
(api, index) => api !== prevState.recentApis[index],
)
) {
return {
...prevState,
recentComponents: nextProps.recentComponents,
recentApis: nextProps.recentApis,
};
}
return prevState;
}
render(): React.Node {
const bookmark = this.context;
const filter = ({example, filterRegex, category}) =>
filterRegex.test(example.module.title) &&
(!category || example.module.category === category) &&
(!category || example.category === category) &&
(!Platform.isTV || example.supportsTVOS);
const {screen} = this.props;
let sections = [];
if (screen === 'component') {
if (this.state.recentComponents.length > 0) {
sections = [
{
data: this.state.recentComponents,
key: 'RECENT_COMPONENTS',
title: 'Recently viewed',
},
{
data: this.state.components,
key: 'COMPONENTS',
title: 'Components',
},
];
} else {
sections = [
{
data: this.state.components,
key: 'COMPONENTS',
title: 'Components',
},
];
}
} else if (screen === 'api') {
if (this.state.recentApis.length > 0) {
sections = [
{
data: this.state.recentApis,
key: 'RECENT_APIS',
title: 'Recently viewed',
},
{
data: this.state.api,
key: 'APIS',
title: 'APIS',
},
];
} else {
sections = [
{
data: this.state.api,
key: 'APIS',
title: 'APIS',
},
];
}
} else if (screen === 'bookmark') {
sections = [
{
data: Object.values(bookmark.Components),
title: 'COMPONENTS',
key: 'COMPONENTS',
},
{
data: Object.values(bookmark.Api),
title: 'APIS',
key: 'APIS',
},
];
} else {
sections = [];
}
const isEmpty = sections.filter(s => s.data.length).length === 0;
if (isEmpty) {
return <EmptyState />;
}
const renderListItem = ({item, section, separators}) => {
return (
<ExampleCard
item={item}
section={section}
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
toggleBookmark={toggleBookmark}
handlePress={handleExampleCardPress}
/>
);
};
return (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.listContainer,
this.props.style,
{backgroundColor: theme.SecondaryGroupedBackgroundColor},
]}>
<RNTesterExampleFilter
testID="explorer_search"
page="components_page"
// $FlowFixMe
sections={sections}
filter={filter}
render={({filteredSections}) => (
<SectionList
sections={filteredSections}
extraData={filteredSections}
renderItem={this._renderItem}
ItemSeparatorComponent={ItemSeparator}
keyboardShouldPersistTaps="handled"
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
renderSectionHeader={renderSectionHeader}
ListFooterComponent={() => <View style={{height: 200}} />}
/>
)}
/>
</View>
);
}}
</RNTesterThemeContext.Consumer>
<View
style={[
styles.listContainer,
{backgroundColor: theme.SecondaryGroupedBackgroundColor},
]}>
<RNTesterExampleFilter
testID="explorer_search"
page="components_page"
sections={sections}
filter={filter}
render={({filteredSections}) => (
<SectionList
sections={filteredSections}
extraData={filteredSections}
renderItem={renderListItem}
ItemSeparatorComponent={ItemSeparator}
keyboardShouldPersistTaps="handled"
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
renderSectionHeader={renderSectionHeader}
ListFooterComponent={() => <View style={{height: 80}} />}
/>
)}
/>
</View>
);
}
_renderItem = ({item, section, separators, index}) => {
let bookmark = this.context;
return (
<RowComponent
item={item}
section={section}
active={!bookmark.checkBookmark(item.module.title, section.key)}
onNavigate={this.props.onNavigate}
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
updateRecentlyViewedList={() =>
this.state.updateRecentlyViewedList(item, section.key)
}
/>
);
};
_handleRowPress(exampleKey: string): void {
this.props.onNavigate(RNTesterActions.ExampleAction(exampleKey));
}
}
},
);
const ItemSeparator = ({highlighted}) => (
<RNTesterThemeContext.Consumer>
@ -400,30 +189,6 @@ const ItemSeparator = ({highlighted}) => (
</RNTesterThemeContext.Consumer>
);
const EmptyState = () => (
<View style={styles.emptyContainer}>
<View style={styles.emptyContainerInner}>
<Image
source={require('../assets/empty.png')}
resizeMode="contain"
style={styles.emptyImage}
/>
<View>
<Text style={styles.heading}>Bookmarks are empty</Text>
<Text style={styles.subheading}>
Please tap the{' '}
<Image
source={require('../assets/bookmark-outline-gray.png')}
resizeMode="contain"
style={styles.bookmarkIcon}
/>{' '}
icon to bookmark examples.
</Text>
</View>
</View>
</View>
);
const styles = StyleSheet.create({
listContainer: {
flex: 1,
@ -483,33 +248,6 @@ const styles = StyleSheet.create({
width: 100,
justifyContent: 'space-between',
},
emptyContainer: {
flex: 1,
paddingHorizontal: 40,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
emptyContainerInner: {
marginTop: -150,
},
emptyImage: {
maxWidth: '100%',
height: 300,
},
heading: {
fontSize: 24,
textAlign: 'center',
},
subheading: {
fontSize: 16,
textAlign: 'center',
},
bookmarkIcon: {
width: 24,
height: 24,
transform: [{translateY: 4}],
},
});
module.exports = RNTesterExampleList;

Просмотреть файл

@ -0,0 +1,144 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import {
Text,
View,
SafeAreaView,
Button,
Platform,
StyleSheet,
} from 'react-native';
import * as React from 'react';
import RNTesterDocumentationURL from './RNTesterDocumentationURL';
import {RNTesterThemeContext} from './RNTesterTheme';
const HeaderIOS = ({
onBack,
title,
documentationURL,
}: {
onBack?: () => mixed,
title: string,
documentationURL?: string,
}) => {
const theme = React.useContext(RNTesterThemeContext);
return (
<SafeAreaView
style={[
styles.headerContainer,
{
borderBottomColor: theme.SeparatorColor,
backgroundColor: theme.TertiarySystemBackgroundColor,
},
]}>
<View style={styles.header}>
<View style={styles.headerCenter}>
<Text style={{...styles.title, ...{color: theme.LabelColor}}}>
{title}
</Text>
{documentationURL && (
<RNTesterDocumentationURL documentationURL={documentationURL} />
)}
</View>
{onBack && (
<View>
<Button
title="Back"
onPress={onBack}
color={Platform.select({
ios: theme.LinkColor,
default: undefined,
})}
/>
</View>
)}
</View>
</SafeAreaView>
);
};
const HeaderAndroid = ({
title,
documentationURL,
}: {
title: string,
documentationURL?: string,
}) => {
const theme = React.useContext(RNTesterThemeContext);
return (
<SafeAreaView>
<View style={[styles.toolbar, {backgroundColor: '#F3F8FF'}]}>
<View style={styles.toolbarCenter}>
<Text style={[styles.title, {color: theme.LabelColor}]}>{title}</Text>
{documentationURL && (
<RNTesterDocumentationURL documentationURL={documentationURL} />
)}
</View>
</View>
</SafeAreaView>
);
};
export const Header = ({
onBack,
title,
documentationURL,
}: {
onBack?: () => mixed,
title: string,
documentationURL?: string,
...
}): React.Node =>
Platform.OS === 'ios' ? (
<HeaderIOS
documentationURL={documentationURL}
title={title}
onBack={onBack}
/>
) : (
<HeaderAndroid documentationURL={documentationURL} title={title} />
);
const styles = StyleSheet.create({
headerContainer: {
borderBottomWidth: StyleSheet.hairlineWidth,
},
header: {
height: 40,
flexDirection: 'row',
},
headerCenter: {
flex: 1,
position: 'absolute',
top: 7,
left: 0,
right: 0,
alignItems: 'center',
},
title: {
fontSize: 19,
fontWeight: '600',
textAlign: 'center',
},
toolbar: {
height: 56,
},
toolbarCenter: {
flex: 1,
position: 'absolute',
top: 12,
left: 0,
right: 0,
alignItems: 'center',
},
});

Просмотреть файл

@ -5,31 +5,37 @@
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import React from 'react';
import * as React from 'react';
import {Text, View, StyleSheet, Image, Pressable} from 'react-native';
import {RNTesterThemeContext} from './RNTesterTheme';
const RNTesterActions = require('../utils/RNTesterActions');
const RNTesterNavbar = ({onNavigate, screen}) => {
type Props = $ReadOnly<{|
handleNavBarPress: (data: {screen: string}) => void,
screen: string,
isExamplePageOpen: boolean,
|}>;
const RNTesterNavbar = ({
handleNavBarPress,
screen,
isExamplePageOpen,
}: Props): React.Node => {
const theme = React.useContext(RNTesterThemeContext);
/** to be attached to navigation framework */
const isAPIActive = screen === 'api';
const isComponentActive = screen === 'component';
const isBookmarkActive = screen === 'bookmark';
const isAPIActive = screen === 'apis' && !isExamplePageOpen;
const isComponentActive = screen === 'components' && !isExamplePageOpen;
const isBookmarkActive = screen === 'bookmarks' && !isExamplePageOpen;
return (
<View>
{/** Bottom Navbar code */}
{/** component and APIs tab */}
<View style={styles.buttonContainer}>
{/** left tab with Components */}
<Pressable
testID="components-tab"
onPress={() => onNavigate(RNTesterActions.OpenList('component'))}
onPress={() => handleNavBarPress({screen: 'components'})}
style={[styles.navButton, {backgroundColor: theme.BackgroundColor}]}>
<View
style={[
@ -54,20 +60,16 @@ const RNTesterNavbar = ({onNavigate, screen}) => {
</View>
</Pressable>
{/** central tab with bookmark icon */}
<View style={styles.centerBox}>
<Image
style={styles.centralBoxCutout}
source={require('./../assets/bottom-nav-center-box.png')}
/>
{/** floating button in center */}
<View style={styles.floatContainer}>
<Pressable
testID="bookmarks-tab"
onPress={() => {
onNavigate(RNTesterActions.OpenList('bookmark'));
}}>
onPress={() => handleNavBarPress({screen: 'bookmarks'})}>
<View
style={[
styles.floatingButton,
@ -86,12 +88,9 @@ const RNTesterNavbar = ({onNavigate, screen}) => {
</View>
</View>
{/** right tab with Components */}
<Pressable
testID="apis-tab"
onPress={() => {
onNavigate(RNTesterActions.OpenList('api'));
}}
onPress={() => handleNavBarPress({screen: 'apis'})}
style={[styles.navButton, {backgroundColor: theme.BackgroundColor}]}>
<View
style={[
@ -163,6 +162,10 @@ const styles = StyleSheet.create({
inactiveText: {
color: '#B1B4BA',
},
activeBar: {
borderTopWidth: 2,
borderColor: '#005DFF',
},
centralBoxCutout: {
height: '100%',
width: '100%',
@ -185,10 +188,6 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
},
activeBar: {
borderTopWidth: 2,
borderColor: '#005DFF',
},
});
module.exports = RNTesterNavbar;

Просмотреть файл

@ -10,20 +10,8 @@
'use strict';
import type {ComponentType} from 'react';
import * as React from 'react';
export type RNTesterProps = $ReadOnly<{|
navigator?: ?$ReadOnlyArray<
$ReadOnly<{|
title: string,
component: ComponentType<any>,
backButtonTitle: string,
passProps: any,
|}>,
>,
|}>;
export type RNTesterExampleModuleItem = $ReadOnly<{|
title: string,
platform?: string,
@ -40,6 +28,8 @@ export type RNTesterExampleModule = $ReadOnly<{|
framework?: string,
examples: Array<RNTesterExampleModuleItem>,
simpleExampleContainer?: ?boolean,
category?: string,
documentationURL?: string,
|}>;
export type RNTesterExample = $ReadOnly<{|
@ -48,4 +38,29 @@ export type RNTesterExample = $ReadOnly<{|
category?: string,
supportsTVOS?: boolean,
documentationURL?: string,
isBookmarked?: boolean,
exampleType?: 'components' | 'apis',
|}>;
export type SectionData = {
key: string,
title: string,
data: Array<RNTesterExample>,
};
export type ExamplesList = $ReadOnly<{|
components: SectionData[],
apis: SectionData[],
bookmarks: SectionData[],
|}>;
export type ScreenTypes = 'components' | 'apis' | 'bookmarks' | null;
export type ComponentList = null | {components: string[], apis: string[]};
export type RNTesterState = {
openExample: null | string,
screen: ScreenTypes,
bookmarks: ComponentList,
recentlyUsed: ComponentList,
};

Просмотреть файл

@ -1,55 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
export type RNTesterBackAction = {type: 'RNTesterBackAction', ...};
export type RNTesterListAction = {type: 'RNTesterListAction', ...};
export type RNTesterExampleAction = {
type: 'RNTesterExampleAction',
openExample: string,
...
};
export type RNTesterAction =
| RNTesterBackAction
| RNTesterListAction
| RNTesterExampleAction;
function Back(screen: string): RNTesterBackAction {
return {
type: 'RNTesterBackAction',
screen,
};
}
function OpenList(screen: string): RNTesterListAction {
return {
type: 'RNTesterListAction',
screen,
};
}
function ExampleAction(openExample: string): RNTesterExampleAction {
return {
type: 'RNTesterExampleAction',
openExample,
};
}
const RNTesterActions = {
Back,
OpenList,
ExampleAction,
};
module.exports = RNTesterActions;

Просмотреть файл

@ -1,190 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import {Platform, Linking} from 'react-native';
import {AsyncStorage} from 'react-native';
const RNTesterNavigationReducer = require('./RNTesterNavigationReducer');
const URIActionMap = require('./URIActionMap');
import type {RNTesterExample} from '../types/RNTesterTypes';
const APP_STATE_KEY = 'RNTesterAppState.v2';
type Context = $FlowFixMe;
export const initializeAsyncStore = (context: Context) => {
Linking.getInitialURL().then(url => {
AsyncStorage.getItem(APP_STATE_KEY, (err, storedString) => {
const exampleAction = URIActionMap(
context.props.exampleFromAppetizeParams,
);
const urlAction = URIActionMap(url);
const launchAction = exampleAction || urlAction;
if (err || !storedString) {
const initialAction = launchAction || {type: 'RNTesterListAction'};
context.setState(
RNTesterNavigationReducer(context.state, initialAction),
);
return;
}
const storedState = JSON.parse(storedString);
if (launchAction) {
context.setState(RNTesterNavigationReducer(storedState, launchAction));
return;
}
context.setState({
openExample: storedState.openExample,
});
});
});
if (Platform.OS === 'ios') {
Linking.addEventListener('url', url => {
context._handleAction(URIActionMap(url));
});
}
AsyncStorage.getItem('Components', (err, storedString) => {
if (err || !storedString) {
return;
}
const components = JSON.parse(storedString);
context.setState({
Components: components,
});
});
AsyncStorage.getItem('Api', (err, storedString) => {
if (err || !storedString) {
return;
}
const api = JSON.parse(storedString);
context.setState({
Api: api,
});
});
AsyncStorage.getItem('RecentComponents', (err, storedString) => {
if (err || !storedString) {
return;
}
const recentComponents = JSON.parse(storedString);
context.setState({
recentComponents: recentComponents,
});
});
AsyncStorage.getItem('RecentApi', (err, storedString) => {
if (err || !storedString) {
return;
}
const recentApis = JSON.parse(storedString);
context.setState({
recentApis: recentApis,
});
});
};
export const addApi = (
apiName: string,
api: RNTesterExample,
context: $FlowFixMe,
) => {
const stateApi = Object.assign({}, context.state.Api);
stateApi[apiName] = api;
context.setState({
Api: stateApi,
});
// Syncing the bookmarks over async storage
AsyncStorage.setItem('Api', JSON.stringify(stateApi));
};
export const addComponent = (
componentName: string,
component: RNTesterExample,
context: Context,
) => {
const stateComponent = Object.assign({}, context.state.Components);
stateComponent[componentName] = component;
context.setState({
Components: stateComponent,
});
// Syncing the bookmarks over async storage
AsyncStorage.setItem('Components', JSON.stringify(stateComponent));
};
export const removeApi = (apiName: string, context: Context) => {
const stateApi = Object.assign({}, context.state.Api);
delete stateApi[apiName];
context.setState({
Api: stateApi,
});
AsyncStorage.setItem('Api', JSON.stringify(stateApi));
};
export const removeComponent = (componentName: string, context: Context) => {
const stateComponent = Object.assign({}, context.state.Components);
delete stateComponent[componentName];
context.setState({
Components: stateComponent,
});
AsyncStorage.setItem('Components', JSON.stringify(stateComponent));
};
export const checkBookmarks = (
title: string,
key: string,
context: Context,
): boolean => {
if (key === 'APIS' || key === 'RECENT_APIS') {
return context.state.Api[title] === undefined;
}
return context.state.Components[title] === undefined;
};
export const updateRecentlyViewedList = (
item: RNTesterExample,
key: string,
context: Context,
) => {
const openedItem = item;
if (key === 'COMPONENTS' || key === 'RECENT_COMPONENTS') {
let componentsCopy = [...context.state.recentComponents];
const ind = componentsCopy.findIndex(
component => component.key === openedItem.key,
);
if (ind !== -1) {
componentsCopy.splice(ind, 1);
}
if (context.state.recentComponents.length >= 5) {
componentsCopy.pop();
}
componentsCopy.unshift(openedItem);
context.setState({
recentComponents: componentsCopy,
});
// Syncing the recently viewed components over async storage
AsyncStorage.setItem('RecentComponents', JSON.stringify(componentsCopy));
} else {
let apisCopy = [...context.state.recentApis];
const ind = apisCopy.findIndex(api => api.key === openedItem.key);
if (ind !== -1) {
apisCopy.splice(ind, 1);
}
if (context.state.recentApis.length >= 5) {
apisCopy.pop();
}
apisCopy.unshift(openedItem);
context.setState({
recentApis: apisCopy,
});
// Syncing the recently viewed apis over async storage
AsyncStorage.setItem('RecentApi', JSON.stringify(apisCopy));
}
};

Просмотреть файл

@ -1,74 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const RNTesterList = require('./RNTesterList');
import type {RNTesterExample} from '../types/RNTesterTypes';
export type RNTesterNavigationState = {
openExample: ?string,
screen: string,
Components: {...},
Api: {...},
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
AddApi: (apiName: string, api: RNTesterExample) => mixed,
AddComponent: (componentName: string, component: RNTesterExample) => mixed,
RemoveApi: (apiName: string) => mixed,
RemoveComponent: (componentName: string) => mixed,
checkBookmark: (title: string, key: string) => mixed,
updateRecentlyViewedList: (item: RNTesterExample, key: string) => mixed,
...
};
function RNTesterNavigationReducer(
state: RNTesterNavigationState,
action: any,
): RNTesterNavigationState {
if (
// Default value is to see example list
!state ||
// Handle the explicit list action
action.type === 'RNTesterListAction' ||
// Handle requests to go back to the list when an example is open
(state.openExample && action.type === 'RNTesterBackAction')
) {
return {
...state,
screen: action.screen ?? 'component',
// A null openExample will cause the views to display the RNTester example list
openExample: null,
};
}
if (action.screen === 'bookmark' && action.type === 'RNTesterBackAction') {
return {
...state,
screen: 'component',
openExample: null,
};
}
if (action.type === 'RNTesterExampleAction') {
// Make sure we see the module before returning the new state
const ExampleModule = RNTesterList.Modules[action.openExample];
if (ExampleModule) {
return {
...state,
openExample: action.openExample,
};
}
}
return state;
}
module.exports = RNTesterNavigationReducer;

Просмотреть файл

@ -0,0 +1,111 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import type {RNTesterState, ComponentList} from '../types/RNTesterTypes';
export const RNTesterActionsType = {
INIT_FROM_STORAGE: 'INIT_FROM_STORAGE',
NAVBAR_PRESS: 'NAVBAR_PRESS',
EXAMPLE_CARD_PRESS: 'EXAMPLE_CARD_PRESS',
BOOKMARK_PRESS: 'BOOKMARK_PRESS',
BACK_BUTTON_PRESS: 'BACK_BUTTON_PRESS',
};
const getUpdatedBookmarks = ({
exampleType,
key,
bookmarks,
}: {
exampleType: string,
key: string,
bookmarks: ComponentList,
}) => {
const updatedBookmarks = bookmarks
? {...bookmarks}
: {components: [], apis: []};
if (updatedBookmarks[exampleType].includes(key)) {
updatedBookmarks[exampleType] = updatedBookmarks[exampleType].filter(
k => k !== key,
);
} else {
updatedBookmarks[exampleType].push(key);
}
return updatedBookmarks;
};
const getUpdatedRecentlyUsed = ({
exampleType,
key,
recentlyUsed,
}: {
exampleType: string,
key: string,
recentlyUsed: ComponentList,
}) => {
const updatedRecentlyUsed = recentlyUsed
? {...recentlyUsed}
: {components: [], apis: []};
let existingKeys = updatedRecentlyUsed[exampleType];
if (existingKeys.includes(key)) {
existingKeys = existingKeys.filter(k => k !== key);
}
existingKeys.unshift(key);
updatedRecentlyUsed[exampleType] = existingKeys.slice(0, 5);
return updatedRecentlyUsed;
};
export const RNTesterReducer = (
state: RNTesterState,
action: {type: string, data: any},
): RNTesterState => {
switch (action.type) {
case RNTesterActionsType.INIT_FROM_STORAGE:
return action.data;
case RNTesterActionsType.NAVBAR_PRESS:
return {
...state,
openExample: null,
screen: action.data.screen,
};
case RNTesterActionsType.EXAMPLE_CARD_PRESS:
return {
...state,
openExample: action.data.key,
recentlyUsed: getUpdatedRecentlyUsed({
exampleType: action.data.exampleType,
key: action.data.key,
recentlyUsed: state.recentlyUsed,
}),
};
case RNTesterActionsType.BOOKMARK_PRESS:
return {
...state,
bookmarks: getUpdatedBookmarks({
exampleType: action.data.exampleType,
key: action.data.key,
bookmarks: state.bookmarks,
}),
};
case RNTesterActionsType.BACK_BUTTON_PRESS:
return {
...state,
openExample: null,
};
default:
throw new Error(`Invalid action type ${action.type}`);
}
};

Просмотреть файл

@ -1,48 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const ReactNative = require('react-native');
const RNTesterActions = require('./RNTesterActions');
const RNTesterList = require('./RNTesterList');
const {Alert} = ReactNative;
import type {RNTesterAction} from './RNTesterActions';
function PathActionMap(path: string): ?RNTesterAction {
// Warning! Hacky parsing for example code. Use a library for this!
const exampleParts = path.split('/example/');
const exampleKey = exampleParts[1];
if (exampleKey) {
if (!RNTesterList.Modules[exampleKey]) {
Alert.alert(`${exampleKey} example could not be found!`);
return null;
}
return RNTesterActions.ExampleAction(exampleKey);
}
return null;
}
function URIActionMap(uri: ?string): ?RNTesterAction {
if (!uri) {
return null;
}
// Warning! Hacky parsing for example code. Use a library for this!
const parts = uri.split('rntester:/');
if (!parts[1]) {
return null;
}
const path = parts[1];
return PathActionMap(path);
}
module.exports = URIActionMap;

Просмотреть файл

@ -0,0 +1,142 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import {AsyncStorage} from 'react-native';
import RNTesterList from './RNTesterList';
import type {
ExamplesList,
RNTesterState,
ComponentList,
} from '../types/RNTesterTypes';
export const Screens = {
COMPONENTS: 'components',
APIS: 'apis',
BOOKMARKS: 'bookmarks',
};
export const initialState: RNTesterState = {
openExample: null,
screen: null,
bookmarks: null,
recentlyUsed: null,
};
const filterEmptySections = (examplesList: ExamplesList): any => {
const filteredSections = {};
const sectionKeys = Object.keys(examplesList);
sectionKeys.forEach(key => {
filteredSections[key] = examplesList[key].filter(
section => section.data.length > 0,
);
});
return filteredSections;
};
export const getExamplesListWithBookmarksAndRecentlyUsed = ({
bookmarks,
recentlyUsed,
}: {
bookmarks: ComponentList,
recentlyUsed: ComponentList,
}): ExamplesList | null => {
// Return early if state has not been initialized from storage
if (!bookmarks || !recentlyUsed) {
return null;
}
const components = RNTesterList.ComponentExamples.map(c => ({
...c,
isBookmarked: bookmarks.components.includes(c.key),
exampleType: Screens.COMPONENTS,
}));
const recentlyUsedComponents = recentlyUsed.components
.map(k => components.find(c => c.key === k))
.filter(Boolean);
const bookmarkedComponents = components.filter(c => c.isBookmarked);
const apis = RNTesterList.APIExamples.map(c => ({
...c,
isBookmarked: bookmarks.apis.includes(c.key),
exampleType: Screens.APIS,
}));
const recentlyUsedAPIs = recentlyUsed.apis
.map(k => apis.find(c => c.key === k))
.filter(Boolean);
const bookmarkedAPIs = apis.filter(c => c.isBookmarked);
const examplesList: ExamplesList = {
[Screens.COMPONENTS]: [
{
key: 'RECENT_COMPONENTS',
data: recentlyUsedComponents,
title: 'Recently Viewed',
},
{
key: 'COMPONENTS',
data: components,
title: 'Components',
},
],
[Screens.APIS]: [
{
key: 'RECENT_APIS',
data: recentlyUsedAPIs,
title: 'Recently viewed',
},
{
key: 'APIS',
data: apis,
title: 'APIs',
},
],
[Screens.BOOKMARKS]: [
{
key: 'COMPONENTS',
data: bookmarkedComponents,
title: 'Components',
},
{
key: 'APIS',
data: bookmarkedAPIs,
title: 'APIs',
},
],
};
return filterEmptySections(examplesList);
};
export const getInitialStateFromAsyncStorage = async (
storageKey: string,
): Promise<RNTesterState> => {
const initialStateString = await AsyncStorage.getItem(storageKey);
if (!initialStateString) {
return {
openExample: null,
screen: Screens.COMPONENTS,
bookmarks: {components: [], apis: []},
recentlyUsed: {components: [], apis: []},
};
} else {
return JSON.parse(initialStateString);
}
};

Просмотреть файл

@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import * as React from 'react';
import {AsyncStorage} from 'react-native';
import type {RNTesterState} from '../types/RNTesterTypes';
export const useAsyncStorageReducer = (
reducer: Function,
initialState: RNTesterState,
storageKey: string,
): [RNTesterState, Function] => {
const [state, dispatch] = React.useReducer<Function, Object>(
reducer,
initialState,
);
React.useEffect(() => {
if (state !== initialState) {
AsyncStorage.setItem(storageKey, JSON.stringify(state));
}
}, [state, storageKey, initialState]);
return [state, dispatch];
};

Просмотреть файл

@ -1,11 +1,11 @@
{
"baseVersion": "0.0.0-5bc67b658",
"baseVersion": "0.0.0-f0e80ae22",
"overrides": [
{
"type": "copy",
"directory": "js",
"baseDirectory": "packages/rn-tester/js",
"baseHash": "c758fbc9a4b64d2bd5a049179fb7d27af2efd5c3",
"baseHash": "0a19766b09b7fc4d3ccb7744b8a5ef0347c8a733",
"issue": 4054
},
{

Просмотреть файл

@ -15,7 +15,7 @@
},
"peerDependencies": {
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658"
"react-native": "0.0.0-f0e80ae22"
},
"devDependencies": {
"react-native-platform-override": "^0.4.0"

Просмотреть файл

@ -24,7 +24,7 @@
"@react-native-windows/tester": "0.0.1",
"prompt-sync": "^4.2.0",
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-windows": "0.0.0-canary.190"
},
"devDependencies": {

Просмотреть файл

@ -1,6 +1,8 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*
* @format
*/
import { TREE_DUMP_RESULT } from '@react-native-windows/tester/js/examples-win/LegacyTests/Consts';

Просмотреть файл

@ -1,6 +1,8 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*
* @format
*/
import { BasePage, By } from './BasePage';

Просмотреть файл

@ -12,7 +12,7 @@
"dependencies": {
"chai": "^4.2.0",
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-windows": "^0.0.0-canary.190"
},
"devDependencies": {

Просмотреть файл

@ -12,7 +12,7 @@
},
"dependencies": {
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-windows": "0.0.0-canary.190"
},
"devDependencies": {

Просмотреть файл

@ -12,7 +12,7 @@
"dependencies": {
"@react-native-windows/tester": "0.0.1",
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-windows": "0.0.0-canary.190"
},
"devDependencies": {

Просмотреть файл

@ -5,13 +5,13 @@
"excludePatterns": [
"src/js/examples-win32/**"
],
"baseVersion": "0.0.0-5bc67b658",
"baseVersion": "0.0.0-f0e80ae22",
"overrides": [
{
"type": "patch",
"file": "src/js/components/ExamplePage.win32.js",
"baseFile": "packages/rn-tester/js/components/ExamplePage.js",
"baseHash": "7d97d2dd62d2055c1e2e7bd69ce93e1b6ec1c2c0",
"baseHash": "929a58e006b074189564a856cfc5dd7a132205a6",
"issue": 4050
},
{
@ -20,11 +20,18 @@
"baseFile": "packages/rn-tester/js/components/ListExampleShared.js",
"baseHash": "d7be07e3fd8d9c1df8e23d5674ce3eb067d00865"
},
{
"type": "patch",
"file": "src/js/components/RNTesterEmptyBookmarksState.win32.js",
"baseFile": "packages/rn-tester/js/components/RNTesterEmptyBookmarksState.js",
"baseHash": "312a8f4a51f942df975f865638570a5349c0cd3f",
"issue": 6341
},
{
"type": "patch",
"file": "src/js/components/RNTesterExampleFilter.win32.js",
"baseFile": "packages/rn-tester/js/components/RNTesterExampleFilter.js",
"baseHash": "7f3f22f1c2643b730bbd1e3d633c263dcd6a0a6f"
"baseHash": "20e35122c8529a5bf848f91d9ea9b85be3c23882"
},
{
"type": "patch",
@ -41,24 +48,38 @@
"issue": 6210
},
{
"type": "derived",
"type": "copy",
"file": "src/js/RNTesterApp.win32.js",
"baseFile": "packages/rn-tester/js/RNTesterApp.ios.js",
"baseHash": "b86a1fd9363b807c340c58135e610bdae8ee2fae",
"baseFile": "packages/rn-tester/js/RNTesterApp.android.js",
"baseHash": "30f3ef9bdc0e2c337309079831664c4a596c404f",
"issue": 4586
},
{
"type": "derived",
"file": "src/js/utils/RNTesterAsyncStorageAbstraction.win32.js",
"baseFile": "packages/rn-tester/js/utils/RNTesterAsyncStorageAbstraction.js",
"baseHash": "a00e706027e10d9c6d98ab3febd6a6f1e947ae5e",
"issue": 6316
},
{
"type": "derived",
"file": "src/js/utils/RNTesterList.win32.js",
"baseFile": "packages/rn-tester/js/utils/RNTesterList.android.js",
"baseHash": "6e0d7dd2c48c2a46cb746cfbd56cdafd141ef3b5"
},
{
"type": "patch",
"file": "src/js/utils/RNTesterStatePersister.win32.js",
"baseFile": "packages/rn-tester/js/utils/RNTesterStatePersister.js",
"baseHash": "b15ee55c3730ae01faa39fad54158596061a1a49",
"issue": 6316
},
{
"type": "patch",
"file": "src/js/utils/testerStateUtils.win32.js",
"baseFile": "packages/rn-tester/js/utils/testerStateUtils.js",
"baseHash": "dd1a238d51ec4926a3cb82a92994cfc3507e16f7",
"issue": 6316
},
{
"type": "patch",
"file": "src/js/utils/useAsyncStorageReducer.win32.js",
"baseFile": "packages/rn-tester/js/utils/useAsyncStorageReducer.js",
"baseHash": "dd12805229a025d0c45f676d6c535f2f19386025",
"issue": 6316
}
]
}

Просмотреть файл

@ -15,7 +15,7 @@
},
"peerDependencies": {
"@office-iss/react-native-win32": "^0.0.0-canary.59",
"react-native": "0.0.0-5bc67b658"
"react-native": "0.0.0-f0e80ae22"
},
"devDependencies": {
"@office-iss/react-native-win32": "^0.0.0-canary.59",
@ -23,7 +23,7 @@
"@rnw-scripts/ts-config": "0.1.0",
"eslint": "6.8.0",
"just-scripts": "^0.44.7",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-platform-override": "^0.4.0",
"typescript": "^3.8.3"
}

Просмотреть файл

@ -10,361 +10,10 @@
'use strict';
const RNTesterActions = require('./utils/RNTesterActions');
const RNTesterExampleContainer = require('./components/RNTesterExampleContainer');
const RNTesterExampleList = require('./components/RNTesterExampleList');
const RNTesterList = require('./utils/RNTesterList'); // [Win32] Remove .ios
const RNTesterNavigationReducer = require('./utils/RNTesterNavigationReducer');
const React = require('react');
// const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios'); [Win32]
const RNTesterNavbar = require('./components/RNTesterNavbar');
import {AppRegistry} from 'react-native';
const {
AppRegistry,
// AsyncStorage, [Win32]
// BackHandler, [Win32]
Button,
Platform,
SafeAreaView,
StyleSheet,
Text,
useColorScheme,
View,
LogBox,
} = require('react-native');
import type {RNTesterExample} from './types/RNTesterTypes';
import type {
RNTesterAction,
RNTesterExampleAction,
} from './utils/RNTesterActions';
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
import RNTesterDocumentationURL from './components/RNTesterDocumentationURL';
import type {ColorSchemeName} from '../../../Libraries/Utilities/NativeAppearance';
import {
RNTesterBookmarkContext,
bookmarks,
} from './components/RNTesterBookmark';
import type {RNTesterBookmark} from './components/RNTesterBookmark';
type Props = {exampleFromAppetizeParams?: ?string, ...};
import {
initializeAsyncStore,
addApi,
addComponent,
removeApi,
removeComponent,
checkBookmarks,
updateRecentlyViewedList,
} from './utils/RNTesterAsyncStorageAbstraction';
// const APP_STATE_KEY = 'RNTesterAppState.v2'; [Win32]
const Header = ({
onBack,
title,
documentationURL,
}: {
onBack?: () => mixed,
title: string,
documentationURL?: string,
...
}) => (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<SafeAreaView
style={[
styles.headerContainer,
{
borderBottomColor: theme.SeparatorColor,
backgroundColor: theme.TertiarySystemBackgroundColor,
},
]}>
<View style={styles.header}>
<View style={styles.headerCenter}>
<Text style={[styles.title, {color: theme.LabelColor}]}>
{title}
</Text>
{documentationURL && (
<RNTesterDocumentationURL documentationURL={documentationURL} />
)}
</View>
{onBack && (
<View>
<Button
title="Back"
onPress={onBack}
color={Platform.select({
ios: theme.LinkColor,
default: undefined,
})}
/>
</View>
)}
</View>
</SafeAreaView>
);
}}
</RNTesterThemeContext.Consumer>
);
const RNTesterExampleContainerViaHook = ({
onBack,
title,
module,
}: {
onBack?: () => mixed,
title: string,
module: RNTesterExample,
...
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
return (
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header
title={title}
onBack={onBack}
documentationURL={module.documentationURL}
/>
<RNTesterExampleContainer module={module} />
</View>
</RNTesterThemeContext.Provider>
);
};
const RNTesterExampleListViaHook = ({
onNavigate,
UpdateRecentlyViewedList,
recentComponents,
recentApis,
bookmark,
list,
screen,
}: {
onNavigate?: (item: RNTesterExampleAction, key: string) => mixed,
UpdateRecentlyViewedList?: (item: RNTesterExample, key: string) => mixed,
recentComponents: Array<RNTesterExample>,
recentApis: Array<RNTesterExample>,
bookmark: RNTesterBookmark,
screen: string,
list: {
ComponentExamples: Array<RNTesterExample>,
APIExamples: Array<RNTesterExample>,
...
},
...
}) => {
const colorScheme: ?ColorSchemeName = useColorScheme();
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
const exampleTitle =
screen === 'component'
? 'Component Store'
: screen === 'api'
? 'API Store'
: 'Bookmarks';
return (
<RNTesterThemeContext.Provider value={theme}>
<RNTesterBookmarkContext.Provider value={bookmark}>
<View style={styles.exampleContainer}>
<Header title={exampleTitle} />
<RNTesterExampleList
onNavigate={onNavigate}
recentComponents={recentComponents}
recentApis={recentApis}
updateRecentlyViewedList={UpdateRecentlyViewedList}
list={list}
screen={screen}
/>
</View>
</RNTesterBookmarkContext.Provider>
</RNTesterThemeContext.Provider>
);
};
class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
_mounted: boolean;
constructor() {
super();
// RNTester App currently uses Async Storage from react-native for storing navigation state
// and bookmark items.
// TODO: Add Native Async Storage Module in RNTester
LogBox.ignoreLogs([new RegExp('has been extracted from react-native')]);
this.state = {
openExample: null,
screen: 'component',
Components: bookmarks.Components,
Api: bookmarks.Api,
recentComponents: [],
recentApis: [],
AddApi: (apiName, api) => addApi(apiName, api, this),
AddComponent: (componentName, component) =>
addComponent(componentName, component, this),
RemoveApi: apiName => removeApi(apiName, this),
RemoveComponent: componentName => removeComponent(componentName, this),
checkBookmark: (title, key) => checkBookmarks(title, key, this),
updateRecentlyViewedList: (item, key) =>
updateRecentlyViewedList(item, key, this),
};
}
UNSAFE_componentWillMount() {
// [Win32 BackHandler not implemented
// BackHandler.addEventListener('hardwareBackPress', this._handleBack);
// Win32]
}
componentDidMount() {
initializeAsyncStore(this);
}
_handleBack = () => {
this._handleAction(RNTesterActions.Back(this.state.screen));
};
_handleAction = (action: ?RNTesterAction) => {
if (!action) {
return;
}
const newState = RNTesterNavigationReducer(this.state, action);
if (this.state !== newState) {
this.setState(newState); // [Win32] Remove AsyncStorage usage
}
};
render(): React.Node | null {
const bookmark = {
Components: this.state.Components,
Api: this.state.Api,
AddApi: this.state.AddApi,
AddComponent: this.state.AddComponent,
RemoveApi: this.state.RemoveApi,
RemoveComponent: this.state.RemoveComponent,
checkBookmark: this.state.checkBookmark,
};
if (!this.state) {
return null;
}
if (this.state.openExample) {
const Component = RNTesterList.Modules[this.state.openExample];
if (Component && Component.external) {
return <Component onExampleExit={this._handleBack} />;
} else {
return (
<>
<RNTesterExampleContainerViaHook
onBack={this._handleBack}
title={Component.title}
module={Component}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavbar onNavigate={this._handleAction} />
</View>
</>
);
}
}
return (
<>
<RNTesterExampleListViaHook
key={this.state.screen}
title={'RNTester'}
onNavigate={this._handleAction}
UpdateRecentlyViewedList={this.state.updateRecentlyViewedList}
recentComponents={this.state.recentComponents}
recentApis={this.state.recentApis}
bookmark={bookmark}
list={RNTesterList}
screen={this.state.screen}
/>
<View style={styles.bottomNavbar}>
<RNTesterNavbar
screen={this.state.screen}
onNavigate={this._handleAction}
/>
</View>
</>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
headerContainer: {
borderBottomWidth: StyleSheet.hairlineWidth,
},
header: {
height: 40,
flexDirection: 'row',
},
headerCenter: {
flex: 1,
position: 'absolute',
top: 7,
left: 0,
right: 0,
alignItems: 'center',
},
title: {
fontSize: 19,
fontWeight: '600',
textAlign: 'center',
},
exampleContainer: {
flex: 1,
},
bottomNavbar: {
bottom: 0,
width: '100%',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
},
});
// [Win32
// AppRegistry.registerComponent('SetPropertiesExampleApp', () =>
// require('./examples/SetPropertiesExample/SetPropertiesExampleApp'),
// );
// AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () =>
// require('./examples/RootViewSizeFlexibilityExample/RootViewSizeFlexibilityExampleApp'),
// );
// Win32]
import RNTesterApp from './RNTesterAppShared';
AppRegistry.registerComponent('RNTesterApp', () => RNTesterApp);
// [Win32
// // Register suitable examples for snapshot tests
// RNTesterList.ComponentExamples.concat(RNTesterList.APIExamples).forEach(
// (Example: RNTesterExample) => {
// const ExampleModule = Example.module;
// if (ExampleModule.displayName) {
// class Snapshotter extends React.Component<{...}> {
// render() {
// return (
// <SnapshotViewIOS>
// <RNTesterExampleContainer module={ExampleModule} />
// </SnapshotViewIOS>
// );
// }
// }
// AppRegistry.registerComponent(
// ExampleModule.displayName,
// () => Snapshotter,
// );
// }
// },
// );
// Win32]
module.exports = RNTesterApp;

Просмотреть файл

@ -36,7 +36,7 @@ export default function ExamplePage(props: Props): React.Node {
return (
<>
<View style={styles.titleView}>
<Text style={{marginVertical: 8, fontSize: 16}}>{description}</Text>
<Text style={styles.description}>{description}</Text>
<View style={styles.rowStyle}>
<Text style={{color: theme.SecondaryLabelColor, width: 65}}>
{category || 'Other'}
@ -80,9 +80,8 @@ const styles = StyleSheet.create({
flexGrow: 1,
},
description: {
paddingVertical: 5,
flexDirection: 'row',
justifyContent: 'space-between',
marginVertical: 8,
fontSize: 16,
},
docsContainer: {
alignContent: 'center',

Просмотреть файл

@ -0,0 +1,64 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import * as React from 'react';
import {View, Image, Text, StyleSheet} from 'react-native';
export const RNTesterEmptyBookmarksState = (): React.Node => (
<View style={styles.emptyContainer}>
<View style={styles.emptyContainerInner}>
<Image
source={require('../assets/empty.png')}
resizeMode="contain"
style={styles.emptyImage}
/>
<View>
<Text style={styles.heading}>Bookmarks are empty</Text>
<Text style={styles.subheading}>
Please tap the{' '}
{/* [Win32] remove the image since nested non-Text in text is unsupported in NetUI*/}
icon to bookmark examples.
</Text>
</View>
</View>
</View>
);
const styles = StyleSheet.create({
emptyContainer: {
flex: 1,
paddingHorizontal: 40,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
emptyContainerInner: {
marginTop: -150,
},
emptyImage: {
maxWidth: '100%',
height: 300,
},
heading: {
fontSize: 24,
textAlign: 'center',
},
subheading: {
fontSize: 16,
textAlign: 'center',
},
bookmarkIcon: {
width: 24,
height: 24,
transform: [{translateY: 4}],
},
});

Просмотреть файл

@ -22,18 +22,16 @@ const {
import {RNTesterThemeContext} from './RNTesterTheme';
import type {RNTesterExample} from '../types/RNTesterTypes';
import type {SectionData} from '../types/RNTesterTypes';
type Props = {
filter: Function,
render: Function,
disableSearch?: boolean,
testID?: string,
hideFilterPills?: boolean,
page: string, // possible values -> examples_page, components_page, bookmarks_page
sections: Array<{
data: Array<RNTesterExample>,
title: string,
key: string,
}>,
page: 'examples_page' | 'components_page' | 'bookmarks_page',
sections: SectionData[],
...
};
@ -71,7 +69,7 @@ class RNTesterExampleFilter extends React.Component<Props, State> {
if (this.state.filter.trim() !== '' || this.state.category.trim() !== '') {
filteredSections = filteredSections.filter(
section => section.title !== 'Recently viewed',
section => section.title !== 'Recently Viewed',
);
}

Просмотреть файл

@ -1,115 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import type {RNTesterExample} from '../types/RNTesterTypes';
type Context = $FlowFixMe;
export const initializeAsyncStore = (_context: Context) => {
// Stubbed on Win32 due to lack of linking + AsyncStorage in Rex
};
export const addApi = (
apiName: string,
api: RNTesterExample,
context: $FlowFixMe,
) => {
const stateApi = Object.assign({}, context.state.Api);
stateApi[apiName] = api;
context.setState({
Api: stateApi,
});
};
export const addComponent = (
componentName: string,
component: RNTesterExample,
context: Context,
) => {
const stateComponent = Object.assign({}, context.state.Components);
stateComponent[componentName] = component;
context.setState({
Components: stateComponent,
});
// Syncing the bookmarks over async storage
// AsyncStorage.setItem('Components', JSON.stringify(stateComponent)); [Win32]
};
export const removeApi = (apiName: string, context: Context) => {
const stateApi = Object.assign({}, context.state.Api);
delete stateApi[apiName];
context.setState({
Api: stateApi,
});
// AsyncStorage.setItem('Api', JSON.stringify(stateApi)); [Win32]
};
export const removeComponent = (componentName: string, context: Context) => {
const stateComponent = Object.assign({}, context.state.Components);
delete stateComponent[componentName];
context.setState({
Components: stateComponent,
});
// AsyncStorage.setItem('Components', JSON.stringify(stateComponent)); [Win32]
};
export const checkBookmarks = (
title: string,
key: string,
context: Context,
): boolean => {
if (key === 'APIS' || key === 'RECENT_APIS') {
return context.state.Api[title] === undefined;
}
return context.state.Components[title] === undefined;
};
export const updateRecentlyViewedList = (
item: RNTesterExample,
key: string,
context: Context,
) => {
const openedItem = item;
if (key === 'COMPONENTS' || key === 'RECENT_COMPONENTS') {
let componentsCopy = [...context.state.recentComponents];
const ind = componentsCopy.findIndex(
component => component.key === openedItem.key,
);
if (ind !== -1) {
componentsCopy.splice(ind, 1);
}
if (context.state.recentComponents.length >= 5) {
componentsCopy.pop();
}
componentsCopy.unshift(openedItem);
context.setState({
recentComponents: componentsCopy,
});
// Syncing the recently viewed components over async storage
// AsyncStorage.setItem('RecentComponents', JSON.stringify(componentsCopy)); [Win32]
} else {
let apisCopy = [...context.state.recentApis];
const ind = apisCopy.findIndex(api => api.key === openedItem.key);
if (ind !== -1) {
apisCopy.splice(ind, 1);
}
if (context.state.recentApis.length >= 5) {
apisCopy.pop();
}
apisCopy.unshift(openedItem);
context.setState({
recentApis: apisCopy,
});
// Syncing the recently viewed apis over async storage
// AsyncStorage.setItem('RecentApi', JSON.stringify(apisCopy)); [Win32]
}
};

Просмотреть файл

@ -0,0 +1,86 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const React = require('react');
// import {AsyncStorage} from 'react-native'; [Win32] #6316
export type PassProps<State> = {
state: State,
setState: (stateLamda: (state: State) => State) => void,
...
};
/**
* A simple container for persisting some state and passing it into the wrapped component as
* `props.persister.state`. Update it with `props.persister.setState`. The component is initially
* rendered using `getInitialState` in the spec and is then re-rendered with the persisted data
* once it's fetched.
*
* This is currently tied to RNTester because it's generally not good to use AsyncStorage like
* this in real apps with user data, but we could maybe pull it out for other internal settings-type
* usage.
*/
function createContainer<Props: Object, State>(
Component: React.ComponentType<Props & {persister: PassProps<State>, ...}>,
spec: {
cacheKeySuffix: (props: Props) => string,
getInitialState: (props: Props) => State,
version?: string,
...
},
): React.ComponentType<Props> {
return class ComponentWithPersistedState extends React.Component<
Props,
$FlowFixMeState,
> {
static displayName = `RNTesterStatePersister(${String(
Component.displayName ?? Component.name,
)})`;
state = {value: spec.getInitialState(this.props)};
_cacheKey = `RNTester:${spec.version || 'v1'}:${spec.cacheKeySuffix(
this.props,
)}`;
componentDidMount() {
/* [Win32 #6316
AsyncStorage.getItem(this._cacheKey, (err, value) => {
if (!err && value) {
this.setState({value: JSON.parse(value)});
}
});
Win32] */
}
_passSetState = (stateLamda: (state: State) => State): void => {
this.setState(state => {
const value = stateLamda(state.value);
// AsyncStorage.setItem(this._cacheKey, JSON.stringify(value)); [Win32] #6316
return {value};
});
};
render(): React.Node {
return (
<Component
{...this.props}
persister={{
state: this.state.value,
setState: this._passSetState,
}}
/>
);
}
};
}
const RNTesterStatePersister = {
createContainer,
};
module.exports = RNTesterStatePersister;

Просмотреть файл

@ -0,0 +1,144 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
// import {AsyncStorage} from 'react-native'; [Win32] #6316
import RNTesterList from './RNTesterList';
import type {
ExamplesList,
RNTesterState,
ComponentList,
} from '../types/RNTesterTypes';
export const Screens = {
COMPONENTS: 'components',
APIS: 'apis',
BOOKMARKS: 'bookmarks',
};
export const initialState: RNTesterState = {
openExample: null,
screen: null,
bookmarks: null,
recentlyUsed: null,
};
const filterEmptySections = (examplesList: ExamplesList): any => {
const filteredSections = {};
const sectionKeys = Object.keys(examplesList);
sectionKeys.forEach(key => {
filteredSections[key] = examplesList[key].filter(
section => section.data.length > 0,
);
});
return filteredSections;
};
export const getExamplesListWithBookmarksAndRecentlyUsed = ({
bookmarks,
recentlyUsed,
}: {
bookmarks: ComponentList,
recentlyUsed: ComponentList,
}): ExamplesList | null => {
// Return early if state has not been initialized from storage
if (!bookmarks || !recentlyUsed) {
return null;
}
const components = RNTesterList.ComponentExamples.map(c => ({
...c,
isBookmarked: bookmarks.components.includes(c.key),
exampleType: Screens.COMPONENTS,
}));
const recentlyUsedComponents = recentlyUsed.components
.map(k => components.find(c => c.key === k))
.filter(Boolean);
const bookmarkedComponents = components.filter(c => c.isBookmarked);
const apis = RNTesterList.APIExamples.map(c => ({
...c,
isBookmarked: bookmarks.apis.includes(c.key),
exampleType: Screens.APIS,
}));
const recentlyUsedAPIs = recentlyUsed.apis
.map(k => apis.find(c => c.key === k))
.filter(Boolean);
const bookmarkedAPIs = apis.filter(c => c.isBookmarked);
const examplesList: ExamplesList = {
[Screens.COMPONENTS]: [
{
key: 'RECENT_COMPONENTS',
data: recentlyUsedComponents,
title: 'Recently Viewed',
},
{
key: 'COMPONENTS',
data: components,
title: 'Components',
},
],
[Screens.APIS]: [
{
key: 'RECENT_APIS',
data: recentlyUsedAPIs,
title: 'Recently viewed',
},
{
key: 'APIS',
data: apis,
title: 'APIs',
},
],
[Screens.BOOKMARKS]: [
{
key: 'COMPONENTS',
data: bookmarkedComponents,
title: 'Components',
},
{
key: 'APIS',
data: bookmarkedAPIs,
title: 'APIs',
},
],
};
return filterEmptySections(examplesList);
};
export const getInitialStateFromAsyncStorage = async (
storageKey: string,
): Promise<RNTesterState> => {
// [Win32 #6316
const initialStateString = null;
// Win32]
if (!initialStateString) {
return {
openExample: null,
screen: Screens.COMPONENTS,
bookmarks: {components: [], apis: []},
recentlyUsed: {components: [], apis: []},
};
} else {
return JSON.parse(initialStateString);
}
};

Просмотреть файл

@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import * as React from 'react';
// import {AsyncStorage} from 'react-native'; [Win32] #6316
import type {RNTesterState} from '../types/RNTesterTypes';
export const useAsyncStorageReducer = (
reducer: Function,
initialState: RNTesterState,
storageKey: string,
): [RNTesterState, Function] => {
const [state, dispatch] = React.useReducer<Function, Object>(
reducer,
initialState,
);
React.useEffect(() => {
if (state !== initialState) {
// AsyncStorage.setItem(storageKey, JSON.stringify(state)); [Win32] #6316
}
}, [state, storageKey, initialState]);
return [state, dispatch];
};

Просмотреть файл

@ -50,14 +50,6 @@
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
.*/node_modules/warning/.*
; Ignore the src folder - flow files are combined with ones from react-native into the root Libraries folder
.*/react-native-win32/src/.*

Просмотреть файл

@ -4,13 +4,13 @@
"flow-typed",
"src/**"
],
"baseVersion": "0.0.0-5bc67b658",
"baseVersion": "0.0.0-f0e80ae22",
"overrides": [
{
"type": "derived",
"file": ".flowconfig",
"baseFile": ".flowconfig",
"baseHash": "a02ec80bfa99acea12365f29e51b898a074e362a"
"baseHash": "86d79e561c8ce6b2dd89ba20ec7d42246cb2a50f"
},
{
"type": "copy",
@ -23,7 +23,7 @@
"type": "derived",
"file": "src/index.win32.js",
"baseFile": "index.js",
"baseHash": "717b73b2397607b3d0a74534cc3360a1be627906"
"baseHash": "a26839159af016e9fb20dc4c18b3c744cde55cc1"
},
{
"type": "patch",
@ -154,7 +154,7 @@
"type": "derived",
"file": "src/Libraries/Components/TextInput/TextInput.win32.tsx",
"baseFile": "Libraries/Components/TextInput/TextInput.js",
"baseHash": "05c8c1e9142210b472c9c1ce891314b786ecc50f"
"baseHash": "b49b732f6a88bdc4e5783b5ac2339ee6105523fd"
},
{
"type": "patch",
@ -383,7 +383,7 @@
"type": "derived",
"file": "src/Libraries/Utilities/Platform.win32.js",
"baseFile": "Libraries/Utilities/Platform.android.js",
"baseHash": "750d5a0afc192d9ec98c27d28402285c9e3155a0"
"baseHash": "1e689ecb36aaa20e7b60b5f6e7620785e3826e0a"
},
{
"type": "platform",

Просмотреть файл

@ -32,7 +32,6 @@
"art": "^0.10.0",
"base64-js": "^1.1.2",
"event-target-shim": "^5.0.1",
"fbjs": "^1.0.0",
"fbjs-scripts": "^1.1.0",
"hermes-engine": "~0.6.0",
"invariant": "^2.2.4",
@ -71,14 +70,14 @@
"just-scripts": "^0.44.7",
"prettier": "1.19.1",
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-platform-override": "^0.4.0",
"react-shallow-renderer": "^16.13.1",
"typescript": "^3.8.3"
},
"peerDependencies": {
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658"
"react-native": "0.0.0-f0e80ae22"
},
"beachball": {
"defaultNpmTag": "canary",

Просмотреть файл

@ -374,7 +374,7 @@ module.exports = {
.default;
},
get NativeEventEmitter(): NativeEventEmitter {
return require('./Libraries/EventEmitter/NativeEventEmitter');
return require('./Libraries/EventEmitter/NativeEventEmitter').default;
},
get Networking(): Networking {
return require('./Libraries/Network/RCTNetworking');
@ -455,7 +455,7 @@ module.exports = {
// Plugins
get DeviceEventEmitter(): RCTDeviceEventEmitter {
return require('./Libraries/EventEmitter/RCTDeviceEventEmitter');
return require('./Libraries/EventEmitter/RCTDeviceEventEmitter').default;
},
get NativeAppEventEmitter(): RCTNativeAppEventEmitter {
return require('./Libraries/EventEmitter/RCTNativeAppEventEmitter');

Просмотреть файл

@ -57,14 +57,6 @@
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
.*/node_modules/warning/.*
; Ignore the src folder - flow files are combined with ones from react-native into the root Libraries folder
.*/vnext/src/.*

Просмотреть файл

@ -12,13 +12,13 @@
"src/tsconfig.json",
"**/*.windesktop.js"
],
"baseVersion": "0.0.0-5bc67b658",
"baseVersion": "0.0.0-f0e80ae22",
"overrides": [
{
"type": "derived",
"file": ".flowconfig",
"baseFile": ".flowconfig",
"baseHash": "a02ec80bfa99acea12365f29e51b898a074e362a"
"baseHash": "86d79e561c8ce6b2dd89ba20ec7d42246cb2a50f"
},
{
"type": "patch",
@ -66,7 +66,7 @@
"type": "derived",
"file": "src/index.windows.js",
"baseFile": "index.js",
"baseHash": "717b73b2397607b3d0a74534cc3360a1be627906"
"baseHash": "a26839159af016e9fb20dc4c18b3c744cde55cc1"
},
{
"type": "platform",
@ -249,7 +249,7 @@
"type": "patch",
"file": "src/Libraries/Components/TextInput/TextInput.windows.js",
"baseFile": "Libraries/Components/TextInput/TextInput.js",
"baseHash": "05c8c1e9142210b472c9c1ce891314b786ecc50f"
"baseHash": "b49b732f6a88bdc4e5783b5ac2339ee6105523fd"
},
{
"type": "patch",
@ -405,7 +405,7 @@
"type": "derived",
"file": "src/Libraries/Utilities/Platform.windows.js",
"baseFile": "Libraries/Utilities/Platform.android.js",
"baseHash": "750d5a0afc192d9ec98c27d28402285c9e3155a0"
"baseHash": "1e689ecb36aaa20e7b60b5f6e7620785e3826e0a"
},
{
"type": "platform",

Просмотреть файл

@ -33,7 +33,6 @@
"anser": "^1.4.9",
"base64-js": "^1.1.2",
"event-target-shim": "^5.0.1",
"fbjs": "^1.0.0",
"fbjs-scripts": "^1.1.0",
"hermes-engine": "~0.6.0",
"invariant": "^2.2.4",
@ -69,7 +68,7 @@
"just-scripts": "^0.44.7",
"prettier": "1.19.1",
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658",
"react-native": "0.0.0-f0e80ae22",
"react-native-platform-override": "^0.4.0",
"react-native-windows-codegen": "0.1.7",
"react-refresh": "^0.4.0",
@ -78,7 +77,7 @@
},
"peerDependencies": {
"react": "16.13.1",
"react-native": "0.0.0-5bc67b658"
"react-native": "0.0.0-f0e80ae22"
},
"beachball": {
"defaultNpmTag": "canary",

Просмотреть файл

@ -496,6 +496,7 @@ export type Props = $ReadOnly<{|
* The following values work on Android only:
*
* - `visible-password`
*
*/
keyboardType?: ?KeyboardType,
@ -692,7 +693,12 @@ export type Props = $ReadOnly<{|
/**
* If `true`, caret is hidden. The default value is `false`.
* This property is supported only for single-line TextInput component on iOS.
*
* On Android devices manufactured by Xiaomi with Android Q,
* when keyboardType equals 'email-address'this will be set
* in native to 'true' to prevent a system related crash. This
* will cause cursor to be diabled as a side-effect.
*
*/
caretHidden?: ?boolean,

Просмотреть файл

@ -369,7 +369,7 @@ module.exports = {
.default;
},
get NativeEventEmitter(): NativeEventEmitter {
return require('./Libraries/EventEmitter/NativeEventEmitter');
return require('./Libraries/EventEmitter/NativeEventEmitter').default;
},
// $FlowFixMe[value-as-type]
get Networking(): Networking {
@ -451,7 +451,7 @@ module.exports = {
// Plugins
get DeviceEventEmitter(): RCTDeviceEventEmitter {
return require('./Libraries/EventEmitter/RCTDeviceEventEmitter');
return require('./Libraries/EventEmitter/RCTDeviceEventEmitter').default;
},
get NativeAppEventEmitter(): RCTNativeAppEventEmitter {
return require('./Libraries/EventEmitter/RCTNativeAppEventEmitter');

Просмотреть файл

@ -12074,10 +12074,10 @@ react-native-tscodegen@0.66.1:
nullthrows "1.1.1"
typescript "^3.5.3"
react-native@0.0.0-5bc67b658:
version "0.0.0-5bc67b658"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.0.0-5bc67b658.tgz#926ce3dfa5683f9de303a33ae0c767b440df88ec"
integrity sha512-ozYlcZKJ0YZNg1Q876YlKhcOykAaZrTNh7W7K3k415ySDAGfGr7RmhI6cbot7/2bQbMnFhnVR3sc/fh3589WxQ==
react-native@0.0.0-f0e80ae22:
version "0.0.0-f0e80ae22"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.0.0-f0e80ae22.tgz#f83cf1c102a24e2840da2400ee5eabbca0f9a4c8"
integrity sha512-ojn3pfFo9MpX2pjUipmVBaC+6w6niNnoZYZCyAbOqlg6I7aLAU7jnq3Za7izsDeFbViJ3ezXflIvzjQ3zcntVA==
dependencies:
"@react-native-community/cli" "^4.10.0"
"@react-native-community/cli-platform-android" "^4.10.0"
@ -12089,7 +12089,6 @@ react-native@0.0.0-5bc67b658:
anser "^1.4.9"
base64-js "^1.1.2"
event-target-shim "^5.0.1"
fbjs "^1.0.0"
fbjs-scripts "^1.1.0"
hermes-engine "~0.6.0"
invariant "^2.2.4"