1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-17 18:44:45 +02:00

Chore: Migrate mobile Dropdown, ScreenHeader to TypeScript (#6763)

This commit is contained in:
Henry Heino 2022-08-27 05:36:59 -07:00 committed by GitHub
parent 3ec3a37603
commit 92c24c2129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 229 additions and 103 deletions

View File

@ -842,6 +842,9 @@ packages/app-mobile/components/BackButtonDialogBox.js.map
packages/app-mobile/components/CameraView.d.ts packages/app-mobile/components/CameraView.d.ts
packages/app-mobile/components/CameraView.js packages/app-mobile/components/CameraView.js
packages/app-mobile/components/CameraView.js.map packages/app-mobile/components/CameraView.js.map
packages/app-mobile/components/Dropdown.d.ts
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/Dropdown.js.map
packages/app-mobile/components/CustomButton.d.ts packages/app-mobile/components/CustomButton.d.ts
packages/app-mobile/components/CustomButton.js packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/CustomButton.js.map packages/app-mobile/components/CustomButton.js.map
@ -917,6 +920,9 @@ packages/app-mobile/components/NoteEditor/SelectionFormatting.js.map
packages/app-mobile/components/NoteEditor/types.d.ts packages/app-mobile/components/NoteEditor/types.d.ts
packages/app-mobile/components/NoteEditor/types.js packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteEditor/types.js.map packages/app-mobile/components/NoteEditor/types.js.map
packages/app-mobile/components/ScreenHeader.d.ts
packages/app-mobile/components/ScreenHeader.js
packages/app-mobile/components/ScreenHeader.js.map
packages/app-mobile/components/SelectDateTimeDialog.d.ts packages/app-mobile/components/SelectDateTimeDialog.d.ts
packages/app-mobile/components/SelectDateTimeDialog.js packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SelectDateTimeDialog.js.map packages/app-mobile/components/SelectDateTimeDialog.js.map

6
.gitignore vendored
View File

@ -834,6 +834,9 @@ packages/app-mobile/components/CameraView.js.map
packages/app-mobile/components/CustomButton.d.ts packages/app-mobile/components/CustomButton.d.ts
packages/app-mobile/components/CustomButton.js packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/CustomButton.js.map packages/app-mobile/components/CustomButton.js.map
packages/app-mobile/components/Dropdown.d.ts
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/Dropdown.js.map
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.d.ts packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.d.ts
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js.map packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js.map
@ -906,6 +909,9 @@ packages/app-mobile/components/NoteEditor/SelectionFormatting.js.map
packages/app-mobile/components/NoteEditor/types.d.ts packages/app-mobile/components/NoteEditor/types.d.ts
packages/app-mobile/components/NoteEditor/types.js packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteEditor/types.js.map packages/app-mobile/components/NoteEditor/types.js.map
packages/app-mobile/components/ScreenHeader.d.ts
packages/app-mobile/components/ScreenHeader.js
packages/app-mobile/components/ScreenHeader.js.map
packages/app-mobile/components/SelectDateTimeDialog.d.ts packages/app-mobile/components/SelectDateTimeDialog.d.ts
packages/app-mobile/components/SelectDateTimeDialog.js packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SelectDateTimeDialog.js.map packages/app-mobile/components/SelectDateTimeDialog.js.map

View File

@ -1,31 +1,60 @@
const React = require('react'); const React = require('react');
const { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View } = require('react-native'); import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle } from 'react-native';
import { Component } from 'react';
const { ItemList } = require('./ItemList.js'); const { ItemList } = require('./ItemList.js');
class Dropdown extends React.Component { type ValueType = string;
constructor() { export interface DropdownListItem {
super(); label: string;
value: ValueType;
}
this.headerRef_ = null; export type OnValueChangedListener = (newValue: ValueType)=> void;
}
UNSAFE_componentWillMount() { interface DropdownProps {
this.setState({ listItemStyle?: ViewStyle;
itemListStyle?: ViewStyle;
itemWrapperStyle?: ViewStyle;
headerWrapperStyle?: ViewStyle;
headerStyle?: TextStyle;
itemStyle?: TextStyle;
disabled?: boolean;
labelTransform?: 'trim';
items: DropdownListItem[];
selectedValue: ValueType|null;
onValueChange?: OnValueChangedListener;
}
interface DropdownState {
headerSize: LayoutRectangle;
listVisible: boolean;
}
class Dropdown extends Component<DropdownProps, DropdownState> {
private headerRef: TouchableOpacity;
public constructor(props: DropdownProps) {
super(props);
this.headerRef = null;
this.state = {
headerSize: { x: 0, y: 0, width: 0, height: 0 }, headerSize: { x: 0, y: 0, width: 0, height: 0 },
listVisible: false, listVisible: false,
}); };
} }
updateHeaderCoordinates() { private updateHeaderCoordinates() {
// https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element // https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element
this.headerRef_.measure((fx, fy, width, height, px, py) => { this.headerRef.measure((_fx, _fy, width, height, px, py) => {
this.setState({ this.setState({
headerSize: { x: px, y: py, width: width, height: height }, headerSize: { x: px, y: py, width: width, height: height },
}); });
}); });
} }
render() { public render() {
const items = this.props.items; const items = this.props.items;
const itemHeight = 60; const itemHeight = 60;
const windowHeight = Dimensions.get('window').height - 50; const windowHeight = Dimensions.get('window').height - 50;
@ -84,23 +113,26 @@ class Dropdown extends React.Component {
} }
} }
if (this.props.labelTransform && this.props.labelTransform === 'trim') headerLabel = headerLabel.trim(); if (this.props.labelTransform && this.props.labelTransform === 'trim') {
headerLabel = headerLabel.trim();
}
const closeList = () => { const closeList = () => {
this.setState({ listVisible: false }); this.setState({ listVisible: false });
}; };
const itemRenderer = item => { const itemRenderer = (item: DropdownListItem) => {
const key = item.value.toString();
return ( return (
<TouchableOpacity <TouchableOpacity
style={itemWrapperStyle} style={itemWrapperStyle}
key={item.value} key={key}
onPress={() => { onPress={() => {
closeList(); closeList();
if (this.props.onValueChange) this.props.onValueChange(item.value); if (this.props.onValueChange) this.props.onValueChange(item.value);
}} }}
> >
<Text ellipsizeMode="tail" numberOfLines={1} style={itemStyle} key={item.value}> <Text ellipsizeMode="tail" numberOfLines={1} style={itemStyle} key={key}>
{item.label} {item.label}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -111,7 +143,7 @@ class Dropdown extends React.Component {
<View style={{ flex: 1, flexDirection: 'column' }}> <View style={{ flex: 1, flexDirection: 'column' }}>
<TouchableOpacity <TouchableOpacity
style={headerWrapperStyle} style={headerWrapperStyle}
ref={ref => (this.headerRef_ = ref)} ref={ref => (this.headerRef = ref)}
disabled={this.props.disabled} disabled={this.props.disabled}
onPress={() => { onPress={() => {
this.updateHeaderCoordinates(); this.updateHeaderCoordinates();
@ -141,9 +173,7 @@ class Dropdown extends React.Component {
style={itemListStyle} style={itemListStyle}
items={this.props.items} items={this.props.items}
itemHeight={itemHeight} itemHeight={itemHeight}
itemRenderer={item => { itemRenderer={itemRenderer}
return itemRenderer(item);
}}
/> />
</View> </View>
</View> </View>
@ -154,4 +184,5 @@ class Dropdown extends React.Component {
} }
} }
module.exports = { Dropdown }; export default Dropdown;
export { Dropdown };

View File

@ -1,21 +1,24 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); import { connect } from 'react-redux';
const { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions } = require('react-native'); import { PureComponent, Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native';
const Icon = require('react-native-vector-icons/Ionicons').default; const Icon = require('react-native-vector-icons/Ionicons').default;
const { BackButtonService } = require('../services/back-button.js'); const { BackButtonService } = require('../services/back-button.js');
const NavService = require('@joplin/lib/services/NavService').default; import NavService from '@joplin/lib/services/NavService';
const { Menu, MenuOptions, MenuOption, MenuTrigger } = require('react-native-popup-menu'); import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu';
const { _ } = require('@joplin/lib/locale'); import { _ } from '@joplin/lib/locale';
const Setting = require('@joplin/lib/models/Setting').default; import Setting from '@joplin/lib/models/Setting';
const Note = require('@joplin/lib/models/Note').default; import Note from '@joplin/lib/models/Note';
const Folder = require('@joplin/lib/models/Folder').default; import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
const { themeStyle } = require('./global-style.js'); const { themeStyle } = require('./global-style.js');
const { Dropdown } = require('./Dropdown.js'); import Dropdown, { DropdownListItem, OnValueChangedListener } from './Dropdown';
const { dialogs } = require('../utils/dialogs.js'); const { dialogs } = require('../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;
const { localSyncInfoFromState } = require('@joplin/lib/services/synchronizer/syncInfoUtils'); import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
const { showMissingMasterKeyMessage } = require('@joplin/lib/services/e2ee/utils'); import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { State } from '@joplin/lib/reducer';
import CustomButton from './CustomButton'; import CustomButton from './CustomButton';
Icon.loadFont(); Icon.loadFont();
@ -26,20 +29,78 @@ Icon.loadFont();
// default height. // default height.
const PADDING_V = 10; const PADDING_V = 10;
class ScreenHeaderComponent extends React.PureComponent { type OnSelectCallbackType=()=> void;
constructor() { type OnPressCallback=()=> void;
super(); interface NavButtonPressEvent {
this.styles_ = {}; // Name of the screen to navigate to
screen: string;
}
interface MenuOptionType {
onPress: OnPressCallback;
isDivider?: boolean;
title: string;
}
type DispatchCommandType=(event: { type: string })=> void;
interface ScreenHeaderProps {
selectedNoteIds: string[];
noteSelectionEnabled: boolean;
parentComponent: Component;
showUndoButton: boolean;
undoButtonDisabled?: boolean;
showRedoButton: boolean;
menuOptions: MenuOptionType[];
title?: string|null;
folders: FolderEntity[];
folderPickerOptions?: {
enabled: boolean;
selectedFolderId: string;
onValueChange: OnValueChangedListener;
mustSelect?: boolean;
};
dispatch: DispatchCommandType;
onUndoButtonPress: OnPressCallback;
onRedoButtonPress: OnPressCallback;
onSaveButtonPress: OnPressCallback;
sortButton_press?: OnPressCallback;
showSideMenuButton?: boolean;
showSearchButton?: boolean;
showContextMenuButton?: boolean;
showBackButton?: boolean;
saveButtonDisabled?: boolean;
showSaveButton?: boolean;
historyCanGoBack?: boolean;
showMissingMasterKeyMessage?: boolean;
hasDisabledSyncItems?: boolean;
shouldUpgradeSyncTarget?: boolean;
showShouldUpgradeSyncTargetMessage?: boolean;
}
interface ScreenHeaderState {
}
class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeaderState> {
private cachedStyles: any;
public dialogbox?: typeof DialogBox;
public constructor(props: ScreenHeaderProps) {
super(props);
this.cachedStyles = {};
} }
styles() { private styles() {
const themeId = Setting.value('theme'); const themeId = Setting.value('theme');
if (this.styles_[themeId]) return this.styles_[themeId]; if (this.cachedStyles[themeId]) return this.cachedStyles[themeId];
this.styles_ = {}; this.cachedStyles = {};
const theme = themeStyle(themeId); const theme = themeStyle(themeId);
const styleObject = { const styleObject: any = {
container: { container: {
flexDirection: 'column', flexDirection: 'column',
backgroundColor: theme.backgroundColor2, backgroundColor: theme.backgroundColor2,
@ -148,15 +209,15 @@ class ScreenHeaderComponent extends React.PureComponent {
styleObject.saveButtonDisabled = Object.assign({}, styleObject.saveButton, { opacity: theme.disabledOpacity }); styleObject.saveButtonDisabled = Object.assign({}, styleObject.saveButton, { opacity: theme.disabledOpacity });
styleObject.iconButtonDisabled = Object.assign({}, styleObject.iconButton, { opacity: theme.disabledOpacity }); styleObject.iconButtonDisabled = Object.assign({}, styleObject.iconButton, { opacity: theme.disabledOpacity });
this.styles_[themeId] = StyleSheet.create(styleObject); this.cachedStyles[themeId] = StyleSheet.create(styleObject);
return this.styles_[themeId]; return this.cachedStyles[themeId];
} }
sideMenuButton_press() { private sideMenuButton_press() {
this.props.dispatch({ type: 'SIDE_MENU_TOGGLE' }); this.props.dispatch({ type: 'SIDE_MENU_TOGGLE' });
} }
async backButton_press() { private async backButton_press() {
if (this.props.noteSelectionEnabled) { if (this.props.noteSelectionEnabled) {
this.props.dispatch({ type: 'NOTE_SELECTION_END' }); this.props.dispatch({ type: 'NOTE_SELECTION_END' });
} else { } else {
@ -164,15 +225,15 @@ class ScreenHeaderComponent extends React.PureComponent {
} }
} }
selectAllButton_press() { private selectAllButton_press() {
this.props.dispatch({ type: 'NOTE_SELECT_ALL_TOGGLE' }); this.props.dispatch({ type: 'NOTE_SELECT_ALL_TOGGLE' });
} }
searchButton_press() { private searchButton_press() {
NavService.go('Search'); void NavService.go('Search');
} }
async duplicateButton_press() { private async duplicateButton_press() {
const noteIds = this.props.selectedNoteIds; const noteIds = this.props.selectedNoteIds;
// Duplicate all selected notes. ensureUniqueTitle is set to true to use the // Duplicate all selected notes. ensureUniqueTitle is set to true to use the
@ -182,7 +243,7 @@ class ScreenHeaderComponent extends React.PureComponent {
this.props.dispatch({ type: 'NOTE_SELECTION_END' }); this.props.dispatch({ type: 'NOTE_SELECTION_END' });
} }
async deleteButton_press() { private async deleteButton_press() {
// Dialog needs to be displayed as a child of the parent component, otherwise // Dialog needs to be displayed as a child of the parent component, otherwise
// it won't be visible within the header component. // it won't be visible within the header component.
const noteIds = this.props.selectedNoteIds; const noteIds = this.props.selectedNoteIds;
@ -197,25 +258,17 @@ class ScreenHeaderComponent extends React.PureComponent {
await Note.batchDelete(noteIds); await Note.batchDelete(noteIds);
} }
menu_select(value) { private menu_select(value: OnSelectCallbackType) {
if (typeof value === 'function') { if (typeof value === 'function') {
value(); value();
} }
} }
log_press() { private warningBox_press(event: NavButtonPressEvent) {
NavService.go('Log'); void NavService.go(event.screen);
} }
status_press() { private renderWarningBox(screen: string, message: string) {
NavService.go('Status');
}
warningBox_press(event) {
NavService.go(event.screen);
}
renderWarningBox(screen, message) {
return ( return (
<TouchableOpacity key={screen} style={this.styles().warningBox} onPress={() => this.warningBox_press({ screen: screen })} activeOpacity={0.8}> <TouchableOpacity key={screen} style={this.styles().warningBox} onPress={() => this.warningBox_press({ screen: screen })} activeOpacity={0.8}>
<Text style={{ flex: 1 }}>{message}</Text> <Text style={{ flex: 1 }}>{message}</Text>
@ -223,9 +276,9 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
render() { public render() {
const themeId = Setting.value('theme'); const themeId = Setting.value('theme');
function sideMenuButton(styles, onPress) { function sideMenuButton(styles: any, onPress: OnPressCallback) {
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={onPress} onPress={onPress}
@ -240,7 +293,7 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
function backButton(styles, onPress, disabled) { function backButton(styles: any, onPress: OnPressCallback, disabled: boolean) {
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={onPress} onPress={onPress}
@ -259,7 +312,9 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
function saveButton(styles, onPress, disabled, show) { function saveButton(
styles: any, onPress: OnPressCallback, disabled: boolean, show: boolean
) {
if (!show) return null; if (!show) return null;
const icon = disabled ? <Icon name="md-checkmark" style={styles.savedButtonIcon} /> : <Image style={styles.saveButtonIcon} source={require('./SaveIcon.png')} />; const icon = disabled ? <Icon name="md-checkmark" style={styles.savedButtonIcon} /> : <Image style={styles.saveButtonIcon} source={require('./SaveIcon.png')} />;
@ -278,7 +333,14 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
const renderTopButton = (options) => { interface TopButtonOptions {
visible: boolean;
iconName: string;
disabled?: boolean;
description: string;
onPress: OnPressCallback;
}
const renderTopButton = (options: TopButtonOptions) => {
if (!options.visible) return null; if (!options.visible) return null;
const icon = <Icon name={options.iconName} style={this.styles().topIcon} />; const icon = <Icon name={options.iconName} style={this.styles().topIcon} />;
@ -317,7 +379,7 @@ class ScreenHeaderComponent extends React.PureComponent {
}); });
}; };
function selectAllButton(styles, onPress) { function selectAllButton(styles: any, onPress: OnPressCallback) {
return ( return (
<CustomButton <CustomButton
onPress={onPress} onPress={onPress}
@ -331,7 +393,7 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
function searchButton(styles, onPress) { function searchButton(styles: any, onPress: OnPressCallback) {
return ( return (
<CustomButton <CustomButton
onPress={onPress} onPress={onPress}
@ -345,7 +407,7 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
function deleteButton(styles, onPress, disabled) { function deleteButton(styles: any, onPress: OnPressCallback, disabled: boolean) {
return ( return (
<CustomButton <CustomButton
onPress={onPress} onPress={onPress}
@ -363,7 +425,7 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
function duplicateButton(styles, onPress, disabled) { function duplicateButton(styles: any, onPress: OnPressCallback, disabled: boolean) {
return ( return (
<CustomButton <CustomButton
onPress={onPress} onPress={onPress}
@ -381,7 +443,7 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
function sortButton(styles, onPress) { function sortButton(styles: any, onPress: OnPressCallback) {
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={onPress} onPress={onPress}
@ -430,12 +492,15 @@ class ScreenHeaderComponent extends React.PureComponent {
); );
} }
const createTitleComponent = (disabled) => { const createTitleComponent = (disabled: boolean) => {
const themeId = Setting.value('theme');
const theme = themeStyle(themeId); const theme = themeStyle(themeId);
const folderPickerOptions = this.props.folderPickerOptions; const folderPickerOptions = this.props.folderPickerOptions;
if (folderPickerOptions && folderPickerOptions.enabled) { if (folderPickerOptions && folderPickerOptions.enabled) {
const addFolderChildren = (folders, pickerItems, indent) => { const addFolderChildren = (
folders: FolderEntityWithChildren[], pickerItems: DropdownListItem[], indent: number
) => {
folders.sort((a, b) => { folders.sort((a, b) => {
const aTitle = a && a.title ? a.title : ''; const aTitle = a && a.title ? a.title : '';
const bTitle = b && b.title ? b.title : ''; const bTitle = b && b.title ? b.title : '';
@ -453,7 +518,7 @@ class ScreenHeaderComponent extends React.PureComponent {
return pickerItems; return pickerItems;
}; };
const titlePickerItems = mustSelect => { const titlePickerItems = (mustSelect: boolean) => {
const folders = this.props.folders.filter(f => f.id !== Folder.conflictFolderId()); const folders = this.props.folders.filter(f => f.id !== Folder.conflictFolderId());
let output = []; let output = [];
if (mustSelect) output.push({ label: _('Move to notebook...'), value: null }); if (mustSelect) output.push({ label: _('Move to notebook...'), value: null });
@ -465,7 +530,6 @@ class ScreenHeaderComponent extends React.PureComponent {
return ( return (
<Dropdown <Dropdown
items={titlePickerItems(!!folderPickerOptions.mustSelect)} items={titlePickerItems(!!folderPickerOptions.mustSelect)}
itemHeight={35}
disabled={disabled} disabled={disabled}
labelTransform="trim" labelTransform="trim"
selectedValue={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null} selectedValue={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null}
@ -481,13 +545,13 @@ class ScreenHeaderComponent extends React.PureComponent {
color: theme.color, color: theme.color,
fontSize: theme.fontSize, fontSize: theme.fontSize,
}} }}
onValueChange={async (folderId, itemIndex) => { onValueChange={async (folderId) => {
// If onValueChange is specified, use this as a callback, otherwise do the default // If onValueChange is specified, use this as a callback, otherwise do the default
// which is to take the selectedNoteIds from the state and move them to the // which is to take the selectedNoteIds from the state and move them to the
// chosen folder. // chosen folder.
if (folderPickerOptions.onValueChange) { if (folderPickerOptions.onValueChange) {
folderPickerOptions.onValueChange(folderId, itemIndex); folderPickerOptions.onValueChange(folderId);
return; return;
} }
@ -527,7 +591,7 @@ class ScreenHeaderComponent extends React.PureComponent {
let backButtonDisabled = !this.props.historyCanGoBack; let backButtonDisabled = !this.props.historyCanGoBack;
if (this.props.noteSelectionEnabled) backButtonDisabled = false; if (this.props.noteSelectionEnabled) backButtonDisabled = false;
const headerItemDisabled = !this.props.selectedNoteIds.length > 0; const headerItemDisabled = !(this.props.selectedNoteIds.length > 0);
const titleComp = createTitleComponent(headerItemDisabled); const titleComp = createTitleComponent(headerItemDisabled);
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press()); const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
@ -539,7 +603,10 @@ class ScreenHeaderComponent extends React.PureComponent {
const sortButtonComp = !this.props.noteSelectionEnabled && this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null; const sortButtonComp = !this.props.noteSelectionEnabled && this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
const windowHeight = Dimensions.get('window').height - 50; const windowHeight = Dimensions.get('window').height - 50;
const contextMenuStyle = { paddingTop: PADDING_V, paddingBottom: PADDING_V }; const contextMenuStyle: ViewStyle = {
paddingTop: PADDING_V,
paddingBottom: PADDING_V,
};
// HACK: if this button is removed during selection mode, the header layout is broken, so for now just make it 1 pixel large (normally it should be hidden) // HACK: if this button is removed during selection mode, the header layout is broken, so for now just make it 1 pixel large (normally it should be hidden)
if (this.props.noteSelectionEnabled) contextMenuStyle.width = 1; if (this.props.noteSelectionEnabled) contextMenuStyle.width = 1;
@ -561,8 +628,8 @@ class ScreenHeaderComponent extends React.PureComponent {
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
{sideMenuComp} {sideMenuComp}
{backButtonComp} {backButtonComp}
{renderUndoButton(this.styles())} {renderUndoButton()}
{renderRedoButton(this.styles())} {renderRedoButton()}
{saveButton( {saveButton(
this.styles(), this.styles(),
() => { () => {
@ -581,20 +648,20 @@ class ScreenHeaderComponent extends React.PureComponent {
</View> </View>
{warningComps} {warningComps}
<DialogBox <DialogBox
ref={dialogbox => { ref={(dialogbox: typeof DialogBox) => {
this.dialogbox = dialogbox; this.dialogbox = dialogbox;
}} }}
/> />
</View> </View>
); );
} }
public static defaultProps: Partial<ScreenHeaderProps> ={
menuOptions: [],
};
} }
ScreenHeaderComponent.defaultProps = { const ScreenHeader = connect((state: State) => {
menuOptions: [],
};
const ScreenHeader = connect(state => {
const syncInfo = localSyncInfoFromState(state); const syncInfo = localSyncInfoFromState(state);
return { return {
@ -610,4 +677,5 @@ const ScreenHeader = connect(state => {
}; };
})(ScreenHeaderComponent); })(ScreenHeaderComponent);
module.exports = { ScreenHeader }; export default ScreenHeader;
export { ScreenHeader };

View File

@ -14,7 +14,7 @@ import { reg } from '@joplin/lib/registry';
import { State } from '@joplin/lib/reducer'; import { State } from '@joplin/lib/reducer';
const VersionInfo = require('react-native-version-info').default; const VersionInfo = require('react-native-version-info').default;
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js'); import ScreenHeader from '../ScreenHeader';
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');
const { Dropdown } = require('../Dropdown.js'); const { Dropdown } = require('../Dropdown.js');
@ -461,7 +461,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
color: theme.color, color: theme.color,
fontSize: theme.fontSize, fontSize: theme.fontSize,
}} }}
onValueChange={(itemValue: any) => { onValueChange={(itemValue: string) => {
updateSettingValue(key, itemValue); updateSettingValue(key, itemValue);
}} }}
/> />

View File

@ -26,7 +26,7 @@ import BaseModel from '@joplin/lib/BaseModel';
const { ActionButton } = require('../action-button.js'); const { ActionButton } = require('../action-button.js');
const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils'); const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
const mimeUtils = require('@joplin/lib/mime-utils.js').mime; const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
const { ScreenHeader } = require('../screen-header.js'); import ScreenHeader from '../ScreenHeader';
const NoteTagsDialog = require('./NoteTagsDialog'); const NoteTagsDialog = require('./NoteTagsDialog');
import time from '@joplin/lib/time'; import time from '@joplin/lib/time';
const { Checkbox } = require('../checkbox.js'); const { Checkbox } = require('../checkbox.js');

View File

@ -5,7 +5,7 @@ const { View, Text, ScrollView } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const { ScreenHeader } = require('../screen-header.js'); import ScreenHeader from '../ScreenHeader';
function UpgradeSyncTargetScreen(props: any) { function UpgradeSyncTargetScreen(props: any) {
const upgradeResult = useSyncTargetUpgrade(); const upgradeResult = useSyncTargetUpgrade();

View File

@ -2,7 +2,7 @@ const React = require('react');
const { View, Button, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } = require('react-native'); const { View, Button, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;

View File

@ -1,7 +1,7 @@
const React = require('react'); const React = require('react');
const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native'); const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, ScrollView } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js'); import ScreenHeader from '../ScreenHeader';
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;
const { dialogs } = require('../../utils/dialogs.js'); const { dialogs } = require('../../utils/dialogs.js');

View File

@ -4,7 +4,7 @@ const { View, TextInput, StyleSheet } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const Folder = require('@joplin/lib/models/Folder').default; const Folder = require('@joplin/lib/models/Folder').default;
const BaseModel = require('@joplin/lib/BaseModel').default; const BaseModel = require('@joplin/lib/BaseModel').default;
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');
const { dialogs } = require('../../utils/dialogs.js'); const { dialogs } = require('../../utils/dialogs.js');
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');

View File

@ -3,7 +3,7 @@ const React = require('react');
const { FlatList, View, Text, Button, StyleSheet, Platform } = require('react-native'); const { FlatList, View, Text, Button, StyleSheet, Platform } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('@joplin/lib/registry.js'); const { reg } = require('@joplin/lib/registry.js');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const time = require('@joplin/lib/time').default; const time = require('@joplin/lib/time').default;
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const Logger = require('@joplin/lib/Logger').default; const Logger = require('@joplin/lib/Logger').default;

View File

@ -9,7 +9,7 @@ const Tag = require('@joplin/lib/models/Tag').default;
const Note = require('@joplin/lib/models/Note').default; const Note = require('@joplin/lib/models/Note').default;
const Setting = require('@joplin/lib/models/Setting').default; const Setting = require('@joplin/lib/models/Setting').default;
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { ActionButton } = require('../action-button.js'); const { ActionButton } = require('../action-button.js');
const { dialogs } = require('../../utils/dialogs.js'); const { dialogs } = require('../../utils/dialogs.js');

View File

@ -4,7 +4,7 @@ const { View } = require('react-native');
const { Button } = require('react-native'); const { Button } = require('react-native');
const { WebView } = require('react-native-webview'); const { WebView } = require('react-native-webview');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const { reg } = require('@joplin/lib/registry.js'); const { reg } = require('@joplin/lib/registry.js');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');

View File

@ -2,7 +2,7 @@ const React = require('react');
const { StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require('react-native'); const { StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const Icon = require('react-native-vector-icons/Ionicons').default; const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const Note = require('@joplin/lib/models/Note').default; const Note = require('@joplin/lib/models/Note').default;

View File

@ -3,7 +3,7 @@ const React = require('react');
const { View, Text, Button, FlatList } = require('react-native'); const { View, Text, Button, FlatList } = require('react-native');
const Setting = require('@joplin/lib/models/Setting').default; const Setting = require('@joplin/lib/models/Setting').default;
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const ReportService = require('@joplin/lib/services/ReportService').default; const ReportService = require('@joplin/lib/services/ReportService').default;
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');

View File

@ -4,7 +4,7 @@ const { View, Text, FlatList, StyleSheet, TouchableOpacity } = require('react-na
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const Tag = require('@joplin/lib/models/Tag').default; const Tag = require('@joplin/lib/models/Tag').default;
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const { ScreenHeader } = require('../screen-header.js'); const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');

View File

@ -91,6 +91,7 @@
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
"@types/jest": "^28.1.3", "@types/jest": "^28.1.3",
"@types/react-native": "^0.64.4", "@types/react-native": "^0.64.4",
"@types/react-redux": "^7.1.24",
"babel-plugin-module-resolver": "^4.1.0", "babel-plugin-module-resolver": "^4.1.0",
"execa": "^4.0.0", "execa": "^4.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",

View File

@ -14,7 +14,7 @@ const { substrWithEllipsis } = require('../string-utils.js');
const logger = Logger.create('models/Folder'); const logger = Logger.create('models/Folder');
interface FolderEntityWithChildren extends FolderEntity { export interface FolderEntityWithChildren extends FolderEntity {
children?: FolderEntity[]; children?: FolderEntity[];
} }
@ -609,7 +609,7 @@ export default class Folder extends BaseItem {
return output.join(' / '); return output.join(' / ');
} }
static buildTree(folders: FolderEntity[]) { static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
const idToFolders: Record<string, any> = {}; const idToFolders: Record<string, any> = {};
for (let i = 0; i < folders.length; i++) { for (let i = 0; i < folders.length; i++) {
idToFolders[folders[i].id] = Object.assign({}, folders[i]); idToFolders[folders[i].id] = Object.assign({}, folders[i]);

View File

@ -52,6 +52,7 @@ interface StateResourceFetcher {
export interface State { export interface State {
notes: any[]; notes: any[];
noteSelectionEnabled?: boolean;
notesSource: string; notesSource: string;
notesParentType: string; notesParentType: string;
folders: any[]; folders: any[];

View File

@ -4081,6 +4081,7 @@ __metadata:
"@react-native-community/slider": ^3.0.3 "@react-native-community/slider": ^3.0.3
"@types/jest": ^28.1.3 "@types/jest": ^28.1.3
"@types/react-native": ^0.64.4 "@types/react-native": ^0.64.4
"@types/react-redux": ^7.1.24
assert-browserify: ^2.0.0 assert-browserify: ^2.0.0
babel-plugin-module-resolver: ^4.1.0 babel-plugin-module-resolver: ^4.1.0
buffer: ^5.0.8 buffer: ^5.0.8
@ -6956,6 +6957,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react-redux@npm:^7.1.24":
version: 7.1.24
resolution: "@types/react-redux@npm:7.1.24"
dependencies:
"@types/hoist-non-react-statics": ^3.3.0
"@types/react": "*"
hoist-non-react-statics: ^3.3.0
redux: ^4.0.0
checksum: 6582246581331ac7fbbd44aa1f1c136c8a9c8febbcf462432ac81302263308c21e1a2e7868beb7f73bbcb52a8e67935d133cb37f5bdcb6564eaff3a811805101
languageName: node
linkType: hard
"@types/react-test-renderer@npm:*": "@types/react-test-renderer@npm:*":
version: 17.0.1 version: 17.0.1
resolution: "@types/react-test-renderer@npm:17.0.1" resolution: "@types/react-test-renderer@npm:17.0.1"