2024-04-10 16:31:04 +02:00
import * as React from 'react' ;
2024-03-26 13:35:15 +02:00
import { PureComponent , ReactElement } from 'react' ;
2024-04-10 16:31:04 +02:00
import { connect } from 'react-redux' ;
2024-09-12 10:04:23 +02:00
import { View , Text , StyleSheet , TouchableOpacity , Image , ViewStyle , Platform } from 'react-native' ;
2018-03-09 22:59:12 +02:00
const Icon = require ( 'react-native-vector-icons/Ionicons' ) . default ;
2024-09-21 19:28:41 +02:00
import BackButtonService from '../../services/BackButtonService' ;
2022-08-27 14:36:59 +02:00
import NavService from '@joplin/lib/services/NavService' ;
2023-07-16 18:42:42 +02:00
import { _ , _n } from '@joplin/lib/locale' ;
2022-08-27 14:36:59 +02:00
import Note from '@joplin/lib/models/Note' ;
2023-05-29 12:31:21 +02:00
import Folder from '@joplin/lib/models/Folder' ;
2024-04-10 16:31:04 +02:00
import { themeStyle } from '../global-style' ;
import { OnValueChangedListener } from '../Dropdown' ;
2020-06-13 17:20:59 +02:00
const DialogBox = require ( 'react-native-dialogbox' ) . default ;
2022-08-27 14:36:59 +02:00
import { FolderEntity } from '@joplin/lib/services/database/types' ;
import { State } from '@joplin/lib/reducer' ;
2024-05-25 15:41:27 +02:00
import IconButton from '../IconButton' ;
2024-04-10 16:31:04 +02:00
import FolderPicker from '../FolderPicker' ;
2024-03-20 12:53:36 +02:00
import { itemIsInTrash } from '@joplin/lib/services/trash' ;
2024-03-02 16:25:27 +02:00
import restoreItems from '@joplin/lib/services/trash/restoreItems' ;
import { ModelType } from '@joplin/lib/BaseModel' ;
2024-03-25 13:39:48 +02:00
import { PluginStates } from '@joplin/lib/services/plugins/reducer' ;
import { ContainerType } from '@joplin/lib/services/plugins/WebviewController' ;
2024-03-29 14:40:54 +02:00
import { Dispatch } from 'redux' ;
2024-04-10 16:31:04 +02:00
import WarningBanner from './WarningBanner' ;
2024-08-02 15:51:49 +02:00
import WebBetaButton from './WebBetaButton' ;
2017-05-16 21:57:09 +02:00
2024-09-12 10:04:23 +02:00
import Menu , { MenuOptionType } from './Menu' ;
2024-09-24 16:12:02 +02:00
import shim from '@joplin/lib/shim' ;
2024-09-12 10:04:23 +02:00
export { MenuOptionType } ;
2017-07-31 20:19:58 +02:00
// Rather than applying a padding to the whole bar, it is applied to each
// individual component (button, picker, etc.) so that the touchable areas
// are widder and to give more room to the picker component which has a larger
// default height.
const PADDING_V = 10 ;
2022-08-27 14:36:59 +02:00
type OnPressCallback = ( ) = > void ;
2024-09-21 13:57:38 +02:00
export interface FolderPickerOptions {
enabled : boolean ;
selectedFolderId? : string ;
onValueChange? : OnValueChangedListener ;
mustSelect? : boolean ;
}
2022-08-27 14:36:59 +02:00
interface ScreenHeaderProps {
selectedNoteIds : string [ ] ;
2024-03-02 16:25:27 +02:00
selectedFolderId : string ;
notesParentType : string ;
2022-08-27 14:36:59 +02:00
noteSelectionEnabled : boolean ;
showUndoButton : boolean ;
undoButtonDisabled? : boolean ;
showRedoButton : boolean ;
menuOptions : MenuOptionType [ ] ;
title? : string | null ;
folders : FolderEntity [ ] ;
2024-09-21 13:57:38 +02:00
folderPickerOptions? : FolderPickerOptions ;
2024-03-25 13:39:48 +02:00
plugins : PluginStates ;
2022-08-27 14:36:59 +02:00
2024-03-29 14:40:54 +02:00
dispatch : Dispatch ;
2022-08-27 14:36:59 +02:00
onUndoButtonPress : OnPressCallback ;
onRedoButtonPress : OnPressCallback ;
onSaveButtonPress : OnPressCallback ;
sortButton_press? : OnPressCallback ;
2023-11-16 14:17:03 +02:00
onSearchButtonPress? : OnPressCallback ;
2022-08-27 14:36:59 +02:00
showSideMenuButton? : boolean ;
showSearchButton? : boolean ;
showContextMenuButton? : boolean ;
showBackButton? : boolean ;
saveButtonDisabled? : boolean ;
showSaveButton? : boolean ;
historyCanGoBack? : boolean ;
showShouldUpgradeSyncTargetMessage? : boolean ;
2023-07-18 15:46:11 +02:00
themeId : number ;
2022-08-27 14:36:59 +02:00
}
interface ScreenHeaderState {
}
class ScreenHeaderComponent extends PureComponent < ScreenHeaderProps , ScreenHeaderState > {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
private cachedStyles : any ;
public dialogbox? : typeof DialogBox ;
public constructor ( props : ScreenHeaderProps ) {
super ( props ) ;
this . cachedStyles = { } ;
2017-07-21 23:40:02 +02:00
}
2017-07-16 01:30:54 +02:00
2022-08-27 14:36:59 +02:00
private styles() {
2023-07-18 15:46:11 +02:00
const themeId = this . props . themeId ;
2022-08-27 14:36:59 +02:00
if ( this . cachedStyles [ themeId ] ) return this . cachedStyles [ themeId ] ;
this . cachedStyles = { } ;
2017-08-01 19:59:01 +02:00
const theme = themeStyle ( themeId ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
const styleObject : any = {
2017-08-01 19:59:01 +02:00
container : {
2018-03-09 22:59:12 +02:00
flexDirection : 'column' ,
2020-06-10 23:08:59 +02:00
backgroundColor : theme.backgroundColor2 ,
2018-03-09 22:59:12 +02:00
shadowColor : '#000000' ,
2017-08-01 19:59:01 +02:00
elevation : 5 ,
} ,
sideMenuButton : {
flex : 1 ,
2018-03-09 22:59:12 +02:00
alignItems : 'center' ,
2020-06-10 23:08:59 +02:00
backgroundColor : theme.backgroundColor2 ,
2017-08-01 19:59:01 +02:00
paddingLeft : theme.marginLeft ,
paddingRight : 5 ,
marginRight : 2 ,
paddingTop : PADDING_V ,
paddingBottom : PADDING_V ,
} ,
iconButton : {
flex : 1 ,
2020-06-10 23:08:59 +02:00
backgroundColor : theme.backgroundColor2 ,
2020-06-13 17:20:18 +02:00
paddingLeft : 10 ,
paddingRight : 10 ,
2017-08-01 19:59:01 +02:00
paddingTop : PADDING_V ,
paddingBottom : PADDING_V ,
} ,
saveButton : {
flex : 0 ,
2018-03-09 22:59:12 +02:00
flexDirection : 'row' ,
alignItems : 'center' ,
2017-08-01 19:59:01 +02:00
padding : 10 ,
borderWidth : 1 ,
2020-06-10 23:08:59 +02:00
borderColor : theme.colorBright2 ,
2017-08-01 19:59:01 +02:00
borderRadius : 4 ,
marginRight : 8 ,
} ,
saveButtonText : {
2018-03-09 22:59:12 +02:00
textAlignVertical : 'center' ,
2020-06-10 23:08:59 +02:00
color : theme.colorBright2 ,
2018-03-09 22:59:12 +02:00
fontWeight : 'bold' ,
2017-08-01 19:59:01 +02:00
} ,
savedButtonIcon : {
fontSize : 20 ,
2020-06-10 23:08:59 +02:00
color : theme.colorBright2 ,
2017-08-01 19:59:01 +02:00
width : 18 ,
height : 18 ,
} ,
saveButtonIcon : {
width : 18 ,
height : 18 ,
} ,
contextMenuTrigger : {
2018-05-01 20:30:41 +02:00
fontSize : 30 ,
paddingLeft : 10 ,
2017-08-01 19:59:01 +02:00
paddingRight : theme.marginRight ,
2020-06-10 23:08:59 +02:00
color : theme.color2 ,
2018-03-09 22:59:12 +02:00
fontWeight : 'bold' ,
2017-08-01 19:59:01 +02:00
} ,
titleText : {
flex : 1 ,
2018-03-09 22:59:12 +02:00
textAlignVertical : 'center' ,
2019-06-28 01:51:02 +02:00
marginLeft : 10 ,
2020-06-10 23:08:59 +02:00
color : theme.colorBright2 ,
2018-03-09 22:59:12 +02:00
fontWeight : 'bold' ,
2017-08-01 19:59:01 +02:00
fontSize : theme.fontSize ,
2018-03-16 22:17:52 +02:00
paddingTop : 15 ,
paddingBottom : 15 ,
2017-12-30 21:57:34 +02:00
} ,
2017-08-01 19:59:01 +02:00
} ;
2017-07-22 18:36:55 +02:00
2023-07-16 18:42:42 +02:00
2023-06-01 13:02:36 +02:00
styleObject . topIcon = { . . . theme . icon } ;
2017-08-01 19:59:01 +02:00
styleObject . topIcon . flex = 1 ;
2018-03-09 22:59:12 +02:00
styleObject . topIcon . textAlignVertical = 'center' ;
2020-06-10 23:08:59 +02:00
styleObject . topIcon . color = theme . colorBright2 ;
2017-07-23 00:52:24 +02:00
2023-06-01 13:02:36 +02:00
styleObject . backButton = { . . . styleObject . iconButton } ;
2017-08-01 19:59:01 +02:00
styleObject . backButton . marginRight = 1 ;
2017-07-16 01:30:54 +02:00
2023-06-01 13:02:36 +02:00
styleObject . backButtonDisabled = { . . . styleObject . backButton , opacity : theme.disabledOpacity } ;
styleObject . saveButtonDisabled = { . . . styleObject . saveButton , opacity : theme.disabledOpacity } ;
styleObject . iconButtonDisabled = { . . . styleObject . iconButton , opacity : theme.disabledOpacity } ;
2017-05-16 22:25:19 +02:00
2022-08-27 14:36:59 +02:00
this . cachedStyles [ themeId ] = StyleSheet . create ( styleObject ) ;
return this . cachedStyles [ themeId ] ;
2017-08-01 19:59:01 +02:00
}
2017-05-16 21:57:09 +02:00
2022-08-27 14:36:59 +02:00
private sideMenuButton_press() {
2018-03-09 22:59:12 +02:00
this . props . dispatch ( { type : 'SIDE_MENU_TOGGLE' } ) ;
2017-05-24 21:27:13 +02:00
}
2022-08-27 14:36:59 +02:00
private async backButton_press() {
2019-07-12 20:36:12 +02:00
if ( this . props . noteSelectionEnabled ) {
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2019-07-29 15:43:53 +02:00
} else {
2019-07-12 20:36:12 +02:00
await BackButtonService . back ( ) ;
2019-07-29 15:43:53 +02:00
}
2017-05-16 21:57:09 +02:00
}
2022-08-27 14:36:59 +02:00
private selectAllButton_press() {
2020-03-14 00:41:56 +02:00
this . props . dispatch ( { type : 'NOTE_SELECT_ALL_TOGGLE' } ) ;
}
2022-08-27 14:36:59 +02:00
private searchButton_press() {
2023-11-16 14:17:03 +02:00
if ( this . props . onSearchButtonPress ) {
this . props . onSearchButtonPress ( ) ;
} else {
void NavService . go ( 'Search' ) ;
}
2017-07-23 00:52:24 +02:00
}
2024-03-25 13:39:48 +02:00
private pluginPanelToggleButton_press() {
2024-03-29 14:40:54 +02:00
this . props . dispatch ( { type : 'SET_PLUGIN_PANELS_DIALOG_VISIBLE' , visible : true } ) ;
2024-03-25 13:39:48 +02:00
}
2022-08-27 14:36:59 +02:00
private async duplicateButton_press() {
2019-10-12 00:37:16 +02:00
const noteIds = this . props . selectedNoteIds ;
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2023-07-16 18:42:42 +02:00
try {
// Duplicate all selected notes. ensureUniqueTitle is set to true to use the
// original note's name as a root for the new unique identifier.
await Note . duplicateMultipleNotes ( noteIds , { ensureUniqueTitle : true } ) ;
} catch ( error ) {
alert ( _n ( 'This note could not be duplicated: %s' , 'These notes could not be duplicated: %s' , noteIds . length , error . message ) ) ;
}
2019-10-12 00:37:16 +02:00
}
2022-08-27 14:36:59 +02:00
private async deleteButton_press() {
2017-11-23 20:47:51 +02:00
// Dialog needs to be displayed as a child of the parent component, otherwise
// it won't be visible within the header component.
2021-10-03 19:41:32 +02:00
const noteIds = this . props . selectedNoteIds ;
2024-03-02 16:25:27 +02:00
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2021-10-03 19:41:32 +02:00
2024-03-02 16:25:27 +02:00
try {
2024-03-09 12:33:05 +02:00
await Note . batchDelete ( noteIds , { toTrash : true , sourceDescription : 'Delete selected notes button' } ) ;
2024-03-02 16:25:27 +02:00
} catch ( error ) {
alert ( _n ( 'This note could not be deleted: %s' , 'These notes could not be deleted: %s' , noteIds . length , error . message ) ) ;
}
}
2017-11-23 20:47:51 +02:00
2024-03-02 16:25:27 +02:00
private async restoreButton_press() {
// Dialog needs to be displayed as a child of the parent component, otherwise
// it won't be visible within the header component.
const noteIds = this . props . selectedNoteIds ;
2018-03-09 22:59:12 +02:00
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2023-07-16 18:42:42 +02:00
try {
2024-03-02 16:25:27 +02:00
await restoreItems ( ModelType . Note , noteIds ) ;
2023-07-16 18:42:42 +02:00
} catch ( error ) {
2024-03-02 16:25:27 +02:00
alert ( ` Could not restore note(s): ${ error . message } ` ) ;
2023-07-16 18:42:42 +02:00
}
2017-11-23 20:47:51 +02:00
}
2022-08-27 14:36:59 +02:00
public render() {
2023-07-18 15:46:11 +02:00
const themeId = this . props . themeId ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function sideMenuButton ( styles : any , onPress : OnPressCallback ) {
2017-07-16 01:30:54 +02:00
return (
2022-06-26 19:23:41 +02:00
< TouchableOpacity
onPress = { onPress }
accessibilityLabel = { _ ( 'Sidebar' ) }
accessibilityHint = { _ ( 'Show/hide the sidebar' ) }
accessibilityRole = "button" >
2017-07-16 01:30:54 +02:00
< View style = { styles . sideMenuButton } >
2023-10-07 18:25:03 +02:00
< Icon name = "menu" style = { styles . topIcon } / >
2017-07-16 01:30:54 +02:00
< / View >
< / TouchableOpacity >
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function backButton ( styles : any , onPress : OnPressCallback , disabled : boolean ) {
2017-07-16 01:30:54 +02:00
return (
2022-06-26 19:23:41 +02:00
< TouchableOpacity
onPress = { onPress }
disabled = { disabled }
accessibilityLabel = { _ ( 'Back' ) }
accessibilityRole = "button" >
2017-07-16 01:30:54 +02:00
< View style = { disabled ? styles.backButtonDisabled : styles.backButton } >
2022-06-26 19:23:41 +02:00
< Icon
2023-10-07 18:25:03 +02:00
name = "arrow-back"
2022-06-26 19:23:41 +02:00
style = { styles . topIcon }
/ >
2017-07-16 01:30:54 +02:00
< / View >
< / TouchableOpacity >
) ;
}
2022-08-27 14:36:59 +02:00
function saveButton (
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-08-22 12:58:53 +02:00
styles : any , onPress : OnPressCallback , disabled : boolean , show : boolean ,
2022-08-27 14:36:59 +02:00
) {
2017-07-16 12:17:40 +02:00
if ( ! show ) return null ;
2023-10-07 18:25:03 +02:00
const icon = disabled ? < Icon name = "checkmark" style = { styles . savedButtonIcon } / > : < Image style = { styles . saveButtonIcon } source = { require ( './SaveIcon.png' ) } / > ;
2017-07-30 23:33:54 +02:00
2017-07-16 12:17:40 +02:00
return (
2022-06-26 19:23:41 +02:00
< TouchableOpacity
onPress = { onPress }
disabled = { disabled }
style = { { padding : 0 } }
accessibilityLabel = { _ ( 'Save changes' ) }
accessibilityRole = "button" >
2019-07-29 15:43:53 +02:00
< View style = { disabled ? styles.saveButtonDisabled : styles.saveButton } > { icon } < / View >
2017-07-16 12:17:40 +02:00
< / TouchableOpacity >
) ;
}
2022-08-27 14:36:59 +02:00
interface TopButtonOptions {
visible : boolean ;
iconName : string ;
disabled? : boolean ;
description : string ;
onPress : OnPressCallback ;
}
const renderTopButton = ( options : TopButtonOptions ) = > {
2022-06-21 12:50:10 +02:00
if ( ! options . visible ) return null ;
2020-06-13 17:20:18 +02:00
const viewStyle = options . disabled ? this . styles ( ) . iconButtonDisabled : this.styles ( ) . iconButton ;
return (
2024-05-25 15:41:27 +02:00
< IconButton
2022-06-26 19:23:41 +02:00
onPress = { options . onPress }
2024-05-25 15:41:27 +02:00
containerStyle = { { padding : 0 } }
contentWrapperStyle = { viewStyle }
2022-08-21 23:03:41 +02:00
themeId = { themeId }
2022-06-26 19:23:41 +02:00
disabled = { ! ! options . disabled }
2022-08-21 23:03:41 +02:00
description = { options . description }
2024-05-25 15:41:27 +02:00
iconName = { options . iconName }
iconStyle = { this . styles ( ) . topIcon }
/ >
2020-06-13 17:20:18 +02:00
) ;
} ;
const renderUndoButton = ( ) = > {
return renderTopButton ( {
2024-05-25 15:41:27 +02:00
iconName : 'ionicon arrow-undo-circle-sharp' ,
2022-08-21 23:03:41 +02:00
description : _ ( 'Undo' ) ,
2020-06-13 17:20:18 +02:00
onPress : this.props.onUndoButtonPress ,
visible : this.props.showUndoButton ,
disabled : this.props.undoButtonDisabled ,
} ) ;
} ;
const renderRedoButton = ( ) = > {
return renderTopButton ( {
2024-05-25 15:41:27 +02:00
iconName : 'ionicon arrow-redo-circle-sharp' ,
2022-08-21 23:03:41 +02:00
description : _ ( 'Redo' ) ,
2020-06-13 17:20:18 +02:00
onPress : this.props.onRedoButtonPress ,
visible : this.props.showRedoButton ,
} ) ;
} ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function selectAllButton ( styles : any , onPress : OnPressCallback ) {
2020-03-14 00:41:56 +02:00
return (
2024-05-25 15:41:27 +02:00
< IconButton
2022-06-26 19:23:41 +02:00
onPress = { onPress }
2022-08-21 23:03:41 +02:00
themeId = { themeId }
description = { _ ( 'Select all' ) }
2024-05-25 15:41:27 +02:00
contentWrapperStyle = { styles . iconButton }
iconName = "ionicon checkmark-circle-outline"
iconStyle = { styles . topIcon }
/ >
2020-03-14 00:41:56 +02:00
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function searchButton ( styles : any , onPress : OnPressCallback ) {
2017-07-23 00:52:24 +02:00
return (
2024-05-25 15:41:27 +02:00
< IconButton
2022-06-26 19:23:41 +02:00
onPress = { onPress }
2022-08-21 23:03:41 +02:00
description = { _ ( 'Search' ) }
themeId = { themeId }
2024-05-25 15:41:27 +02:00
contentWrapperStyle = { styles . iconButton }
iconName = 'ionicon search'
iconStyle = { styles . topIcon }
/ >
2017-07-23 00:52:24 +02:00
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-03-25 13:39:48 +02:00
const pluginPanelToggleButton = ( styles : any , onPress : OnPressCallback ) = > {
const allPluginViews = Object . values ( this . props . plugins ) . map ( plugin = > Object . values ( plugin . views ) ) . flat ( ) ;
2024-05-02 15:59:50 +02:00
const allVisiblePanels = allPluginViews . filter (
view = > view . containerType === ContainerType . Panel && view . opened ,
) ;
if ( allVisiblePanels . length === 0 ) return null ;
2024-03-25 13:39:48 +02:00
return (
2024-05-25 15:41:27 +02:00
< IconButton
2024-03-25 13:39:48 +02:00
onPress = { onPress }
description = { _ ( 'Plugin panels' ) }
themeId = { themeId }
2024-05-25 15:41:27 +02:00
contentWrapperStyle = { styles . iconButton }
iconName = "ionicon extension-puzzle"
iconStyle = { styles . topIcon }
/ >
2024-03-25 13:39:48 +02:00
) ;
} ;
2024-08-02 15:51:49 +02:00
const betaIconButton = ( ) = > {
if ( Platform . OS !== 'web' ) return null ;
return (
< WebBetaButton
themeId = { themeId }
wrapperStyle = { this . styles ( ) . iconButton }
iconStyle = { this . styles ( ) . topIcon }
/ >
) ;
} ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function deleteButton ( styles : any , onPress : OnPressCallback , disabled : boolean ) {
2017-11-23 20:47:51 +02:00
return (
2024-05-25 15:41:27 +02:00
< IconButton
2022-06-26 19:23:41 +02:00
onPress = { onPress }
disabled = { disabled }
2022-08-21 23:03:41 +02:00
themeId = { themeId }
description = { _ ( 'Delete' ) }
2022-06-26 19:23:41 +02:00
accessibilityHint = {
disabled ? null : _ ( 'Delete selected notes' )
}
2024-05-25 15:41:27 +02:00
contentWrapperStyle = { disabled ? styles.iconButtonDisabled : styles.iconButton }
iconName = 'ionicon trash'
iconStyle = { styles . topIcon }
/ >
2017-11-23 20:47:51 +02:00
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-03-02 16:25:27 +02:00
function restoreButton ( styles : any , onPress : OnPressCallback , disabled : boolean ) {
return (
2024-05-25 15:41:27 +02:00
< IconButton
2024-03-02 16:25:27 +02:00
onPress = { onPress }
disabled = { disabled }
themeId = { themeId }
description = { _ ( 'Restore' ) }
accessibilityHint = {
disabled ? null : _ ( 'Restore' )
}
2024-05-25 15:41:27 +02:00
contentWrapperStyle = { disabled ? styles.iconButtonDisabled : styles.iconButton }
iconName = 'ionicon reload-circle'
iconStyle = { styles . topIcon }
/ >
2024-03-02 16:25:27 +02:00
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function duplicateButton ( styles : any , onPress : OnPressCallback , disabled : boolean ) {
2019-10-12 00:37:16 +02:00
return (
2024-05-25 15:41:27 +02:00
< IconButton
2022-06-26 19:23:41 +02:00
onPress = { onPress }
disabled = { disabled }
2022-08-21 23:03:41 +02:00
themeId = { themeId }
description = { _ ( 'Duplicate' ) }
2022-06-26 19:23:41 +02:00
accessibilityHint = {
disabled ? null : _ ( 'Duplicate selected notes' )
}
2024-05-25 15:41:27 +02:00
contentWrapperStyle = { disabled ? styles.iconButtonDisabled : styles.iconButton }
iconName = 'ionicon copy'
iconStyle = { styles . topIcon }
/ >
2019-10-12 00:37:16 +02:00
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-08-27 14:36:59 +02:00
function sortButton ( styles : any , onPress : OnPressCallback ) {
2018-02-22 20:58:15 +02:00
return (
2022-06-26 19:23:41 +02:00
< TouchableOpacity
onPress = { onPress }
accessibilityLabel = { _ ( 'Sort notes by' ) }
accessibilityRole = "button" >
2018-02-22 20:58:15 +02:00
< View style = { styles . iconButton } >
2020-10-16 17:26:19 +02:00
< Icon name = "filter-outline" style = { styles . topIcon } / >
2018-02-22 20:58:15 +02:00
< / View >
< / TouchableOpacity >
) ;
}
2024-09-12 10:04:23 +02:00
const menuOptions : MenuOptionType [ ] = [ . . . this . props . menuOptions ] ;
2017-05-16 23:46:21 +02:00
2024-03-02 16:25:27 +02:00
const selectedFolder = this . props . notesParentType === 'Folder' ? Folder . byId ( this . props . folders , this . props . selectedFolderId ) : null ;
const selectedFolderInTrash = itemIsInTrash ( selectedFolder ) ;
2017-11-23 20:47:51 +02:00
if ( ! this . props . noteSelectionEnabled ) {
2024-09-12 10:04:23 +02:00
if ( menuOptions . length ) {
menuOptions . push ( { isDivider : true } ) ;
2017-09-24 16:48:23 +02:00
}
2017-11-23 20:47:51 +02:00
} else {
2024-09-12 10:04:23 +02:00
menuOptions . push ( {
key : 'delete' ,
title : _ ( 'Delete' ) ,
onPress : this.deleteButton_press ,
} ) ;
2019-10-12 00:37:16 +02:00
2024-09-12 10:04:23 +02:00
menuOptions . push ( {
key : 'duplicate' ,
title : _ ( 'Duplicate' ) ,
onPress : this.duplicateButton_press ,
} ) ;
2017-09-24 16:48:23 +02:00
}
2017-07-10 21:16:59 +02:00
2024-03-26 13:35:15 +02:00
const createTitleComponent = ( disabled : boolean , hideableAfterTitleComponents : ReactElement ) = > {
2017-11-23 20:47:51 +02:00
const folderPickerOptions = this . props . folderPickerOptions ;
if ( folderPickerOptions && folderPickerOptions . enabled ) {
2017-07-16 18:06:05 +02:00
return (
2023-05-29 12:31:21 +02:00
< FolderPicker
themeId = { themeId }
2020-05-09 17:01:39 +02:00
disabled = { disabled }
2023-05-29 12:31:21 +02:00
selectedFolderId = { 'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null }
2022-08-27 14:36:59 +02:00
onValueChange = { async ( folderId ) = > {
2017-11-23 20:41:35 +02:00
// 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
// chosen folder.
if ( folderPickerOptions . onValueChange ) {
2022-08-27 14:36:59 +02:00
folderPickerOptions . onValueChange ( folderId ) ;
2017-11-23 20:41:35 +02:00
return ;
}
if ( ! folderId ) return ;
const noteIds = this . props . selectedNoteIds ;
if ( ! noteIds . length ) return ;
const folder = await Folder . load ( folderId ) ;
2024-09-24 16:12:02 +02:00
const ok = noteIds . length > 1 ? await shim . showConfirmationDialog ( _ ( 'Move %d notes to notebook "%s"?' , noteIds . length , folder . title ) ) : true ;
2017-11-23 20:41:35 +02:00
if ( ! ok ) return ;
2018-03-09 22:59:12 +02:00
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2023-07-16 18:42:42 +02:00
try {
for ( let i = 0 ; i < noteIds . length ; i ++ ) {
await Note . moveToFolder ( noteIds [ i ] , folderId ) ;
}
} catch ( error ) {
alert ( _n ( 'This note could not be moved: %s' , 'These notes could not be moved: %s' , noteIds . length , error . message ) ) ;
2017-11-23 20:41:35 +02:00
}
2017-11-23 20:47:51 +02:00
} }
2023-05-29 12:31:21 +02:00
mustSelect = { ! ! folderPickerOptions . mustSelect }
2024-03-20 12:53:36 +02:00
folders = { Folder . getRealFolders ( this . props . folders ) }
2024-03-26 13:35:15 +02:00
coverableChildrenRight = { hideableAfterTitleComponents }
2017-11-19 01:59:07 +02:00
/ >
2017-07-16 18:06:05 +02:00
) ;
} else {
2020-03-14 01:46:14 +02:00
const title = 'title' in this . props && this . props . title !== null ? this . props . title : '' ;
2024-03-26 13:35:15 +02:00
return (
< >
< Text ellipsizeMode = { 'tail' } numberOfLines = { 1 } style = { this . styles ( ) . titleText } > { title } < / Text >
{ hideableAfterTitleComponents }
< / >
) ;
2017-07-16 18:06:05 +02:00
}
2019-07-29 15:43:53 +02:00
} ;
2017-07-16 18:06:05 +02:00
2019-07-12 20:36:12 +02:00
const showSideMenuButton = ! ! this . props . showSideMenuButton && ! this . props . noteSelectionEnabled ;
2020-03-14 00:41:56 +02:00
const showSelectAllButton = this . props . noteSelectionEnabled ;
2019-07-12 20:36:12 +02:00
const showSearchButton = ! ! this . props . showSearchButton && ! this . props . noteSelectionEnabled ;
2018-03-16 22:17:52 +02:00
const showContextMenuButton = this . props . showContextMenuButton !== false ;
2019-07-12 20:36:12 +02:00
const showBackButton = ! ! this . props . noteSelectionEnabled || this . props . showBackButton !== false ;
let backButtonDisabled = ! this . props . historyCanGoBack ;
2019-07-29 15:43:53 +02:00
if ( this . props . noteSelectionEnabled ) backButtonDisabled = false ;
2022-08-27 14:36:59 +02:00
const headerItemDisabled = ! ( this . props . selectedNoteIds . length > 0 ) ;
2018-03-16 22:17:52 +02:00
const sideMenuComp = ! showSideMenuButton ? null : sideMenuButton ( this . styles ( ) , ( ) = > this . sideMenuButton_press ( ) ) ;
2019-07-12 20:36:12 +02:00
const backButtonComp = ! showBackButton ? null : backButton ( this . styles ( ) , ( ) = > this . backButton_press ( ) , backButtonDisabled ) ;
2024-03-26 13:35:15 +02:00
const pluginPanelsComp = pluginPanelToggleButton ( this . styles ( ) , ( ) = > this . pluginPanelToggleButton_press ( ) ) ;
2024-08-02 15:51:49 +02:00
const betaIconComp = betaIconButton ( ) ;
2020-03-14 00:41:56 +02:00
const selectAllButtonComp = ! showSelectAllButton ? null : selectAllButton ( this . styles ( ) , ( ) = > this . selectAllButton_press ( ) ) ;
2018-03-16 22:17:52 +02:00
const searchButtonComp = ! showSearchButton ? null : searchButton ( this . styles ( ) , ( ) = > this . searchButton_press ( ) ) ;
2024-03-02 16:25:27 +02:00
const deleteButtonComp = ! selectedFolderInTrash && this . props . noteSelectionEnabled ? deleteButton ( this . styles ( ) , ( ) = > this . deleteButton_press ( ) , headerItemDisabled ) : null ;
const restoreButtonComp = selectedFolderInTrash && this . props . noteSelectionEnabled ? restoreButton ( this . styles ( ) , ( ) = > this . restoreButton_press ( ) , headerItemDisabled ) : null ;
const duplicateButtonComp = ! selectedFolderInTrash && this . props . noteSelectionEnabled ? duplicateButton ( this . styles ( ) , ( ) = > this . duplicateButton_press ( ) , headerItemDisabled ) : null ;
2019-07-12 20:36:12 +02:00
const sortButtonComp = ! this . props . noteSelectionEnabled && this . props . sortButton_press ? sortButton ( this . styles ( ) , ( ) = > this . props . sortButton_press ( ) ) : null ;
2024-03-26 13:35:15 +02:00
// To allow the notebook dropdown (and perhaps other components) to have sufficient
// space while in use, we allow certain buttons to be hidden.
2024-08-02 15:51:49 +02:00
const hideableRightComponents = < >
{ pluginPanelsComp }
{ betaIconComp }
< / > ;
2024-03-26 13:35:15 +02:00
const titleComp = createTitleComponent ( headerItemDisabled , hideableRightComponents ) ;
2017-11-23 20:47:51 +02:00
2022-08-27 14:36:59 +02:00
const contextMenuStyle : ViewStyle = {
paddingTop : PADDING_V ,
paddingBottom : PADDING_V ,
} ;
2019-07-29 15:43:53 +02:00
2019-07-12 20:36:12 +02:00
// 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)
2019-07-29 15:43:53 +02:00
if ( this . props . noteSelectionEnabled ) contextMenuStyle . width = 1 ;
const menuComp =
2024-09-12 10:04:23 +02:00
! menuOptions . length || ! showContextMenuButton ? null : (
< Menu themeId = { this . props . themeId } options = { menuOptions } >
< View style = { contextMenuStyle } accessibilityLabel = { _ ( 'Actions' ) } >
< Icon name = "ellipsis-vertical" style = { this . styles ( ) . contextMenuTrigger } / >
< / View >
2019-07-29 15:43:53 +02:00
< / Menu >
) ;
2017-05-16 22:25:19 +02:00
2017-05-16 21:57:09 +02:00
return (
2019-07-29 15:43:53 +02:00
< View style = { this . styles ( ) . container } >
< View style = { { flexDirection : 'row' , alignItems : 'center' } } >
{ sideMenuComp }
{ backButtonComp }
2022-08-27 14:36:59 +02:00
{ renderUndoButton ( ) }
{ renderRedoButton ( ) }
2019-07-29 15:43:53 +02:00
{ saveButton (
this . styles ( ) ,
( ) = > {
if ( this . props . onSaveButtonPress ) this . props . onSaveButtonPress ( ) ;
} ,
this . props . saveButtonDisabled === true ,
2023-08-22 12:58:53 +02:00
this . props . showSaveButton === true ,
2019-07-29 15:43:53 +02:00
) }
{ titleComp }
2020-03-14 00:41:56 +02:00
{ selectAllButtonComp }
2019-07-29 15:43:53 +02:00
{ searchButtonComp }
{ deleteButtonComp }
2024-03-02 16:25:27 +02:00
{ restoreButtonComp }
2019-10-12 00:37:16 +02:00
{ duplicateButtonComp }
2019-07-29 15:43:53 +02:00
{ sortButtonComp }
{ menuComp }
2017-12-30 21:57:34 +02:00
< / View >
2024-04-10 16:31:04 +02:00
< WarningBanner
showShouldUpgradeSyncTargetMessage = { this . props . showShouldUpgradeSyncTargetMessage }
/ >
2020-06-13 17:20:59 +02:00
< DialogBox
2022-08-27 14:36:59 +02:00
ref = { ( dialogbox : typeof DialogBox ) = > {
2020-06-13 17:20:59 +02:00
this . dialogbox = dialogbox ;
} }
/ >
2017-05-16 21:57:09 +02:00
< / View >
) ;
}
2022-08-29 15:19:04 +02:00
public static defaultProps : Partial < ScreenHeaderProps > = {
2022-08-27 14:36:59 +02:00
menuOptions : [ ] ,
} ;
}
2017-06-11 23:11:14 +02:00
2022-08-27 14:36:59 +02:00
const ScreenHeader = connect ( ( state : State ) = > {
2019-07-29 15:43:53 +02:00
return {
historyCanGoBack : state.historyCanGoBack ,
locale : state.settings.locale ,
folders : state.folders ,
2020-09-15 15:01:07 +02:00
themeId : state.settings.theme ,
2019-07-29 15:43:53 +02:00
noteSelectionEnabled : state.noteSelectionEnabled ,
selectedNoteIds : state.selectedNoteIds ,
2024-03-02 16:25:27 +02:00
selectedFolderId : state.selectedFolderId ,
notesParentType : state.notesParentType ,
2024-03-25 13:39:48 +02:00
plugins : state.pluginService.plugins ,
2019-07-29 15:43:53 +02:00
} ;
} ) ( ScreenHeaderComponent ) ;
2022-08-27 14:36:59 +02:00
export default ScreenHeader ;
export { ScreenHeader } ;