2019-07-29 15:43:53 +02:00
const React = require ( 'react' ) ;
2024-08-02 15:51:49 +02:00
import { useMemo , useEffect , useCallback , useContext } from 'react' ;
const { Easing , Animated , TouchableOpacity , Text , StyleSheet , ScrollView , View , Image } = require ( 'react-native' ) ;
2018-03-09 22:59:12 +02:00
const { connect } = require ( 'react-redux' ) ;
const Icon = require ( 'react-native-vector-icons/Ionicons' ) . default ;
2022-10-11 13:31:09 +02:00
import Folder from '@joplin/lib/models/Folder' ;
import Synchronizer from '@joplin/lib/Synchronizer' ;
import NavService from '@joplin/lib/services/NavService' ;
import { _ } from '@joplin/lib/locale' ;
2024-04-11 09:35:20 +02:00
import { ThemeStyle , themeStyle } from './global-style' ;
2024-07-04 14:56:57 +02:00
import { buildFolderTree , isFolderSelected , renderFolders } from '@joplin/lib/components/shared/side-menu-shared' ;
2024-03-02 16:25:27 +02:00
import { FolderEntity , FolderIcon , FolderIconType } from '@joplin/lib/services/database/types' ;
2022-10-11 13:31:09 +02:00
import { AppState } from '../utils/types' ;
2022-10-11 13:46:40 +02:00
import Setting from '@joplin/lib/models/Setting' ;
import { reg } from '@joplin/lib/registry' ;
2023-01-10 14:08:13 +02:00
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types' ;
2024-03-02 16:25:27 +02:00
import { getTrashFolderIcon , getTrashFolderId } from '@joplin/lib/services/trash' ;
import restoreItems from '@joplin/lib/services/trash/restoreItems' ;
2024-03-14 20:42:58 +02:00
import emptyTrash from '@joplin/lib/services/trash/emptyTrash' ;
2024-03-02 16:25:27 +02:00
import { ModelType } from '@joplin/lib/BaseModel' ;
2024-08-02 15:51:49 +02:00
import { DialogContext } from './DialogManager' ;
import AccessibleView from './accessibility/AccessibleView' ;
const { TouchableRipple } = require ( 'react-native-paper' ) ;
2024-03-02 16:25:27 +02:00
const { substrWithEllipsis } = require ( '@joplin/lib/string-utils' ) ;
2017-05-24 21:27:13 +02:00
2022-10-11 13:31:09 +02:00
interface Props {
syncStarted : boolean ;
themeId : number ;
2023-09-11 21:44:15 +02:00
sideMenuVisible : boolean ;
2023-06-30 11:30:29 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
2022-10-11 13:31:09 +02:00
dispatch : Function ;
collapsedFolderIds : string [ ] ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-10-11 13:31:09 +02:00
syncReport : any ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-10-11 13:31:09 +02:00
decryptionWorker : any ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-10-11 13:31:09 +02:00
resourceFetcher : any ;
syncOnlyOverWifi : boolean ;
isOnMobileData : boolean ;
notesParentType : string ;
folders : FolderEntity [ ] ;
opacity : number ;
2023-01-10 14:08:13 +02:00
profileConfig : ProfileConfig ;
2023-07-18 21:15:45 +02:00
inboxJopId : string ;
2024-01-09 18:22:06 +02:00
selectedFolderId : string ;
selectedTagId : string ;
2022-10-11 13:31:09 +02:00
}
2017-08-01 19:59:01 +02:00
2022-10-11 13:31:09 +02:00
const syncIconRotationValue = new Animated . Value ( 0 ) ;
2017-08-01 19:59:01 +02:00
2022-10-11 13:31:09 +02:00
const syncIconRotation = syncIconRotationValue . interpolate ( {
inputRange : [ 0 , 1 ] ,
outputRange : [ '0deg' , '360deg' ] ,
} ) ;
2017-08-01 19:59:01 +02:00
2022-10-12 17:03:51 +02:00
const folderIconRightMargin = 10 ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2022-10-11 13:31:09 +02:00
let syncIconAnimation : any ;
const SideMenuContentComponent = ( props : Props ) = > {
2022-10-11 13:46:40 +02:00
const alwaysShowFolderIcons = useMemo ( ( ) = > Folder . shouldShowFolderIcons ( props . folders ) , [ props . folders ] ) ;
2022-10-11 13:31:09 +02:00
const styles_ = useMemo ( ( ) = > {
const theme = themeStyle ( 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-10-11 13:31:09 +02:00
const styles : any = {
2017-08-01 19:59:01 +02:00
menu : {
flex : 1 ,
2019-07-29 15:43:53 +02:00
backgroundColor : theme.backgroundColor ,
2017-08-01 19:59:01 +02:00
} ,
button : {
flex : 1 ,
2018-03-09 22:59:12 +02:00
flexDirection : 'row' ,
2024-08-02 15:51:49 +02:00
flexBasis : 'auto' ,
2017-08-01 19:59:01 +02:00
height : 36 ,
2018-03-09 22:59:12 +02:00
alignItems : 'center' ,
2017-08-01 19:59:01 +02:00
paddingLeft : theme.marginLeft ,
paddingRight : theme.marginRight ,
} ,
buttonText : {
flex : 1 ,
color : theme.color ,
paddingLeft : 10 ,
fontSize : theme.fontSize ,
} ,
syncStatus : {
paddingLeft : theme.marginLeft ,
paddingRight : theme.marginRight ,
color : theme.colorFaded ,
fontSize : theme.fontSizeSmaller ,
2019-06-26 20:05:37 +02:00
flex : 0 ,
2017-08-01 19:59:01 +02:00
} ,
2019-06-26 02:10:15 +02:00
sidebarIcon : {
fontSize : 22 ,
color : theme.color ,
2024-02-09 14:11:51 +02:00
width : 26 ,
2024-04-25 14:41:28 +02:00
textAlign : 'center' ,
textAlignVertical : 'center' ,
2019-06-26 02:10:15 +02:00
} ,
2017-08-01 19:59:01 +02:00
} ;
2023-06-01 13:02:36 +02:00
styles . folderButton = { . . . styles . button } ;
2018-05-09 13:39:17 +02:00
styles . folderButton . paddingLeft = 0 ;
2023-06-01 13:02:36 +02:00
styles . folderButtonText = { . . . styles . buttonText , paddingLeft : 0 } ;
styles . folderButtonSelected = { . . . styles . folderButton } ;
2017-08-01 19:59:01 +02:00
styles . folderButtonSelected . backgroundColor = theme . selectedColor ;
2023-06-01 13:02:36 +02:00
styles . folderIcon = { . . . theme . icon } ;
2019-10-09 21:35:13 +02:00
styles . folderIcon . color = theme . colorFaded ; // '#0072d5';
2018-05-09 13:39:17 +02:00
styles . folderIcon . paddingTop = 3 ;
2017-08-01 19:59:01 +02:00
2023-06-01 13:02:36 +02:00
styles . sideButton = { . . . styles . button , flex : 0 } ;
styles . sideButtonSelected = { . . . styles . sideButton , backgroundColor : theme.selectedColor } ;
styles . sideButtonText = { . . . styles . buttonText } ;
2017-08-01 19:59:01 +02:00
2024-02-09 14:11:51 +02:00
styles . emptyFolderIcon = { . . . styles . sidebarIcon , marginRight : folderIconRightMargin , width : 26 } ;
2017-07-06 21:48:17 +02:00
2022-10-11 13:31:09 +02:00
return StyleSheet . create ( styles ) ;
} , [ props . themeId ] ) ;
useEffect ( ( ) = > {
if ( props . syncStarted ) {
syncIconAnimation = Animated . loop (
Animated . timing ( syncIconRotationValue , {
toValue : 1 ,
duration : 3000 ,
easing : Easing.linear ,
2023-12-06 12:15:33 +02:00
useNativeDriver : false ,
2023-08-22 12:58:53 +02:00
} ) ,
2022-10-11 13:31:09 +02:00
) ;
syncIconAnimation . start ( ) ;
} else {
if ( syncIconAnimation ) syncIconAnimation . stop ( ) ;
syncIconAnimation = null ;
2019-07-29 15:43:53 +02:00
}
2022-10-11 13:31:09 +02:00
} , [ props . syncStarted ] ) ;
2019-07-12 19:07:47 +02:00
2022-10-11 13:31:09 +02:00
const folder_press = ( folder : FolderEntity ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2019-07-11 19:44:26 +02:00
2022-10-11 13:31:09 +02:00
props . dispatch ( {
2019-07-11 19:44:26 +02:00
type : 'NAV_GO' ,
routeName : 'Notes' ,
folderId : folder.id ,
} ) ;
2022-10-11 13:31:09 +02:00
} ;
2017-05-24 21:27:13 +02:00
2024-08-02 15:51:49 +02:00
const dialogs = useContext ( DialogContext ) ;
2022-11-17 18:34:16 +02:00
const folder_longPress = async ( folderOrAll : FolderEntity | string ) = > {
if ( folderOrAll === 'all' ) return ;
const folder = folderOrAll as FolderEntity ;
2019-06-28 01:51:02 +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
const menuItems : any [ ] = [ ] ;
2024-03-14 20:42:58 +02:00
if ( folder && folder . id === getTrashFolderId ( ) ) {
menuItems . push ( {
text : _ ( 'Empty trash' ) ,
onPress : async ( ) = > {
2024-08-02 15:51:49 +02:00
dialogs . prompt ( '' , _ ( 'This will permanently delete all items in the trash. Continue?' ) , [
2024-03-14 20:42:58 +02:00
{
text : _ ( 'Empty trash' ) ,
onPress : async ( ) = > {
await emptyTrash ( ) ;
} ,
} ,
{
text : _ ( 'Cancel' ) ,
onPress : ( ) = > { } ,
style : 'cancel' ,
} ,
] ) ;
} ,
style : 'destructive' ,
} ) ;
} else if ( folder && ! ! folder . deleted_time ) {
2024-03-02 16:25:27 +02:00
menuItems . push ( {
text : _ ( 'Restore' ) ,
onPress : async ( ) = > {
await restoreItems ( ModelType . Folder , [ folder . id ] ) ;
} ,
style : 'destructive' ,
} ) ;
// Alert.alert(
// '',
// _('Notebook: %s', folder.title),
// [
// {
// text: _('Restore'),
// onPress: async () => {
// await restoreItems(ModelType.Folder, [folder.id]);
// },
// style: 'destructive',
// },
// {
// text: _('Cancel'),
// onPress: () => {},
// style: 'cancel',
// },
// ],
// {
// cancelable: false,
// },
// );
} else {
const generateFolderDeletion = ( ) = > {
const folderDeletion = ( message : string ) = > {
2024-08-02 15:51:49 +02:00
dialogs . prompt ( '' , message , [
2024-03-02 16:25:27 +02:00
{
text : _ ( 'OK' ) ,
onPress : ( ) = > {
2024-03-09 12:33:05 +02:00
void Folder . delete ( folder . id , { toTrash : true , sourceDescription : 'side-menu-content (long-press)' } ) ;
2024-03-02 16:25:27 +02:00
} ,
} ,
{
text : _ ( 'Cancel' ) ,
onPress : ( ) = > { } ,
style : 'cancel' ,
2023-07-18 21:15:45 +02:00
} ,
2024-03-02 16:25:27 +02:00
] ) ;
} ;
if ( folder . id === props . inboxJopId ) {
return folderDeletion (
_ ( 'Delete the Inbox notebook?\n\nIf you delete the inbox notebook, any email that\'s recently been sent to it may be lost.' ) ,
) ;
}
return folderDeletion ( _ ( 'Move notebook "%s" to the trash?\n\nAll notes and sub-notebooks within this notebook will also be moved to the trash.' , substrWithEllipsis ( folder . title , 0 , 32 ) ) ) ;
2023-07-18 21:15:45 +02:00
} ;
2024-03-02 16:25:27 +02:00
menuItems . push ( {
text : _ ( 'Edit' ) ,
onPress : ( ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Folder' ,
folderId : folder.id ,
} ) ;
} ,
} ) ;
menuItems . push ( {
text : _ ( 'Delete' ) ,
onPress : generateFolderDeletion ,
style : 'destructive' ,
} ) ;
}
menuItems . push ( {
text : _ ( 'Cancel' ) ,
onPress : ( ) = > { } ,
style : 'cancel' ,
} ) ;
2023-07-18 21:15:45 +02:00
2024-08-02 15:51:49 +02:00
dialogs . prompt (
2019-06-26 19:28:09 +02:00
'' ,
2019-07-29 15:43:53 +02:00
_ ( 'Notebook: %s' , folder . title ) ,
2024-03-02 16:25:27 +02:00
menuItems ,
2019-06-26 19:28:09 +02:00
) ;
2022-10-11 13:31:09 +02:00
} ;
2019-06-26 19:28:09 +02:00
2022-10-11 13:31:09 +02:00
const folder_togglePress = ( folder : FolderEntity ) = > {
props . dispatch ( {
2018-05-09 13:39:17 +02:00
type : 'FOLDER_TOGGLE' ,
id : folder.id ,
} ) ;
2022-10-11 13:31:09 +02:00
} ;
2018-05-09 13:39:17 +02:00
2022-10-11 13:31:09 +02:00
const tagButton_press = ( ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2017-07-25 20:36:52 +02:00
2022-10-11 13:31:09 +02:00
props . dispatch ( {
2018-03-09 22:59:12 +02:00
type : 'NAV_GO' ,
2019-03-14 00:42:16 +02:00
routeName : 'Tags' ,
2017-07-25 20:36:52 +02:00
} ) ;
2022-10-11 13:31:09 +02:00
} ;
2017-07-25 20:36:52 +02:00
2023-01-10 14:08:13 +02:00
const switchProfileButton_press = ( ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'ProfileSwitcher' ,
} ) ;
} ;
2022-10-11 13:31:09 +02:00
const configButton_press = ( ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
void NavService . go ( 'Config' ) ;
} ;
2019-06-26 20:05:37 +02:00
2022-10-11 13:31:09 +02:00
const allNotesButton_press = ( ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2019-07-11 19:44:26 +02:00
2022-10-11 13:31:09 +02:00
props . dispatch ( {
2019-07-11 19:44:26 +02:00
type : 'NAV_GO' ,
routeName : 'Notes' ,
smartFilterId : 'c3176726992c11e9ac940492261af972' ,
} ) ;
2022-10-11 13:31:09 +02:00
} ;
2019-07-11 19:44:26 +02:00
2022-10-11 13:31:09 +02:00
const newFolderButton_press = ( ) = > {
props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2019-06-26 02:10:15 +02:00
2022-10-11 13:31:09 +02:00
props . dispatch ( {
2019-06-26 02:10:15 +02:00
type : 'NAV_GO' ,
routeName : 'Folder' ,
folderId : null ,
} ) ;
2022-10-11 13:31:09 +02:00
} ;
2019-06-26 02:10:15 +02:00
2022-10-11 13:46:40 +02:00
const performSync = useCallback ( async ( ) = > {
const action = props . syncStarted ? 'cancel' : 'start' ;
if ( ! Setting . value ( 'sync.target' ) ) {
props . dispatch ( {
type : 'SIDE_MENU_CLOSE' ,
} ) ;
props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Config' ,
sectionName : 'sync' ,
} ) ;
return 'init' ;
}
if ( ! ( await reg . syncTarget ( ) . isAuthenticated ( ) ) ) {
if ( reg . syncTarget ( ) . authRouteName ( ) ) {
props . dispatch ( {
type : 'NAV_GO' ,
routeName : reg.syncTarget ( ) . authRouteName ( ) ,
} ) ;
return 'auth' ;
}
reg . logger ( ) . error ( 'Not authenticated with sync target - please check your credentials.' ) ;
return 'error' ;
}
let sync = null ;
try {
sync = await reg . syncTarget ( ) . synchronizer ( ) ;
} catch ( error ) {
reg . logger ( ) . error ( 'Could not initialise synchroniser: ' ) ;
reg . logger ( ) . error ( error ) ;
error . message = ` Could not initialise synchroniser: ${ error . message } ` ;
props . dispatch ( {
type : 'SYNC_REPORT_UPDATE' ,
report : { errors : [ error ] } ,
} ) ;
return 'error' ;
}
if ( action === 'cancel' ) {
void sync . cancel ( ) ;
return 'cancel' ;
} else {
void reg . scheduleSync ( 0 ) ;
return 'sync' ;
}
} , [ props . syncStarted , props . dispatch ] ) ;
const synchronize_press = useCallback ( async ( ) = > {
const actionDone = await performSync ( ) ;
2022-10-11 13:31:09 +02:00
if ( actionDone === 'auth' ) props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2022-10-11 13:46:40 +02:00
} , [ performSync , props . dispatch ] ) ;
2017-07-06 21:48:17 +02:00
2024-04-11 09:35:20 +02:00
const renderFolderIcon = ( folderId : string , theme : ThemeStyle , folderIcon : FolderIcon ) = > {
2022-10-11 13:46:40 +02:00
if ( ! folderIcon ) {
2024-03-21 03:09:01 +02:00
if ( folderId === getTrashFolderId ( ) ) {
2024-03-02 16:25:27 +02:00
folderIcon = getTrashFolderIcon ( FolderIconType . Emoji ) ;
2024-03-21 03:09:01 +02:00
} else if ( alwaysShowFolderIcons ) {
return < Icon name = "folder-outline" style = { styles_ . emptyFolderIcon } / > ;
2022-10-11 13:46:40 +02:00
} else {
return null ;
}
}
2022-02-06 18:42:00 +02:00
if ( folderIcon . type === 1 ) { // FolderIconType.Emoji
2024-02-09 14:11:51 +02:00
return < Text style = { { fontSize : theme.fontSize , marginRight : folderIconRightMargin , width : 27 } } > { folderIcon . emoji } < / Text > ;
2022-02-06 18:42:00 +02:00
} else if ( folderIcon . type === 2 ) { // FolderIconType.DataUrl
2024-02-09 14:11:51 +02:00
return < Image style = { { width : 27 , height : 20 , marginRight : folderIconRightMargin , resizeMode : 'contain' } } source = { { uri : folderIcon.dataUrl } } / > ;
2022-02-06 18:42:00 +02:00
} else {
throw new Error ( ` Unsupported folder icon type: ${ folderIcon . type } ` ) ;
}
2022-10-11 13:31:09 +02:00
} ;
2022-02-06 18:42:00 +02:00
2024-04-25 16:31:18 +02:00
const renderFolderItem = ( folder : FolderEntity , hasChildren : boolean , depth : number ) = > {
2022-10-11 13:31:09 +02:00
const theme = themeStyle ( props . themeId ) ;
2017-07-22 17:55:09 +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-10-11 13:31:09 +02:00
const folderButtonStyle : any = {
2018-05-09 13:39:17 +02:00
flex : 1 ,
flexDirection : 'row' ,
2024-08-02 15:51:49 +02:00
flexBasis : 'auto' ,
2018-05-09 13:39:17 +02:00
height : 36 ,
alignItems : 'center' ,
paddingRight : theme.marginRight ,
2022-02-06 18:42:00 +02:00
paddingLeft : 10 ,
2018-05-09 13:39:17 +02:00
} ;
2024-04-25 16:31:18 +02:00
const selected = isFolderSelected ( folder , { selectedFolderId : props.selectedFolderId , notesParentType : props.notesParentType } ) ;
2018-05-09 13:39:17 +02:00
if ( selected ) folderButtonStyle . backgroundColor = theme . selectedColor ;
2019-07-12 19:07:47 +02:00
folderButtonStyle . paddingLeft = depth * 10 + theme . marginLeft ;
2018-05-09 13:39:17 +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-10-11 13:31:09 +02:00
const iconWrapperStyle : any = { paddingLeft : 10 , paddingRight : 10 } ;
2018-05-09 13:39:17 +02:00
if ( selected ) iconWrapperStyle . backgroundColor = theme . selectedColor ;
2019-06-28 01:51:02 +02:00
let iconWrapper = null ;
2018-05-09 13:39:17 +02:00
2022-10-11 13:31:09 +02:00
const collapsed = props . collapsedFolderIds . indexOf ( folder . id ) >= 0 ;
2022-06-26 19:23:41 +02:00
const iconName = collapsed ? 'chevron-down' : 'chevron-up' ;
2022-10-11 13:31:09 +02:00
const iconComp = < Icon name = { iconName } style = { styles_ . folderIcon } / > ;
2019-06-28 01:51:02 +02:00
2019-07-11 19:44:26 +02:00
iconWrapper = ! hasChildren ? null : (
2019-07-29 15:43:53 +02:00
< TouchableOpacity
style = { iconWrapperStyle }
folderid = { folder . id }
onPress = { ( ) = > {
2022-10-11 13:31:09 +02:00
if ( hasChildren ) folder_togglePress ( folder ) ;
2019-07-29 15:43:53 +02:00
} }
2022-06-26 19:23:41 +02:00
2022-10-30 20:37:58 +02:00
accessibilityLabel = { collapsed ? _ ( 'Expand' ) : _ ( 'Collapse' ) }
2022-06-26 19:23:41 +02:00
accessibilityRole = "togglebutton"
2019-07-29 15:43:53 +02:00
>
{ iconComp }
2019-07-11 19:44:26 +02:00
< / TouchableOpacity >
) ;
2018-05-09 13:39:17 +02:00
2021-11-15 19:19:51 +02:00
const folderIcon = Folder . unserializeIcon ( folder . icon ) ;
2018-05-09 13:39:17 +02:00
return (
2019-07-11 19:44:26 +02:00
< View key = { folder . id } style = { { flex : 1 , flexDirection : 'row' } } >
2024-08-02 15:51:49 +02:00
< TouchableRipple
style = { { flex : 1 , flexBasis : 'auto' } }
2019-07-29 15:43:53 +02:00
onPress = { ( ) = > {
2022-10-11 13:31:09 +02:00
folder_press ( folder ) ;
2019-07-29 15:43:53 +02:00
} }
onLongPress = { ( ) = > {
2022-10-11 13:31:09 +02:00
void folder_longPress ( folder ) ;
2019-07-29 15:43:53 +02:00
} }
2024-08-02 15:51:49 +02:00
onContextMenu = { ( event : Event ) = > { // web only
event . preventDefault ( ) ;
void folder_longPress ( folder ) ;
} }
role = 'button'
2019-07-29 15:43:53 +02:00
>
2018-05-09 13:39:17 +02:00
< View style = { folderButtonStyle } >
2024-03-02 16:25:27 +02:00
{ renderFolderIcon ( folder . id , theme , folderIcon ) }
2022-10-11 13:31:09 +02:00
< Text numberOfLines = { 1 } style = { styles_ . folderButtonText } >
2022-02-06 18:42:00 +02:00
{ Folder . displayTitle ( folder ) }
2019-07-29 15:43:53 +02:00
< / Text >
2018-05-09 13:39:17 +02:00
< / View >
2024-08-02 15:51:49 +02:00
< / TouchableRipple >
2019-07-29 15:43:53 +02:00
{ iconWrapper }
2018-05-09 13:39:17 +02:00
< / View >
) ;
2022-10-11 13:31:09 +02:00
} ;
2017-07-22 17:55:09 +02:00
2023-06-30 11:30:29 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
2022-10-11 13:31:09 +02:00
const renderSidebarButton = ( key : string , title : string , iconName : string , onPressHandler : Function = null , selected = false ) = > {
2024-08-02 15:51:49 +02:00
let icon = < Icon name = { iconName } style = { styles_ . sidebarIcon } aria - hidden = { true } / > ;
2019-07-12 19:07:47 +02:00
if ( key === 'synchronize_button' ) {
2022-10-11 13:31:09 +02:00
icon = < Animated.View style = { { transform : [ { rotate : syncIconRotation } ] } } > { icon } < / Animated.View > ;
2019-07-12 19:07:47 +02:00
}
2019-07-11 19:44:26 +02:00
const content = (
2022-10-11 13:31:09 +02:00
< View key = { key } style = { selected ? styles_.sideButtonSelected : styles_.sideButton } >
2019-07-12 19:07:47 +02:00
{ icon }
2022-10-11 13:31:09 +02:00
< Text style = { styles_ . sideButtonText } > { title } < / Text >
2019-07-11 19:44:26 +02:00
< / View >
) ;
if ( ! onPressHandler ) return content ;
2017-07-22 17:55:09 +02:00
return (
2024-08-02 15:51:49 +02:00
< TouchableOpacity key = { key } onPress = { onPressHandler } role = 'button' >
2019-07-11 19:44:26 +02:00
{ content }
2017-07-22 17:55:09 +02:00
< / TouchableOpacity >
) ;
2022-10-11 13:31:09 +02:00
} ;
2017-07-22 17:55:09 +02:00
2022-10-11 13:31:09 +02:00
const makeDivider = ( key : string ) = > {
const theme = themeStyle ( props . themeId ) ;
2020-06-10 23:08:59 +02:00
return < View style = { { marginTop : 15 , marginBottom : 15 , flex : - 1 , borderBottomWidth : 1 , borderBottomColor : theme.dividerColor } } key = { key } > < / View > ;
2022-10-11 13:31:09 +02:00
} ;
2017-07-25 20:36:52 +02:00
2022-10-11 13:31:09 +02:00
const renderBottomPanel = ( ) = > {
const theme = themeStyle ( props . themeId ) ;
2017-10-30 20:27:51 +02:00
2019-06-26 20:05:37 +02:00
const items = [ ] ;
2017-05-24 21:27:13 +02:00
2022-10-11 13:31:09 +02:00
items . push ( makeDivider ( 'divider_1' ) ) ;
2017-07-28 19:57:01 +02:00
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'newFolder_button' , _ ( 'New Notebook' ) , 'folder-open' , newFolderButton_press ) ) ;
2019-06-26 02:10:15 +02:00
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'tag_button' , _ ( 'Tags' ) , 'pricetag' , tagButton_press ) ) ;
2017-07-06 21:48:17 +02:00
2023-01-10 14:08:13 +02:00
if ( props . profileConfig && props . profileConfig . profiles . length > 1 ) {
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'switchProfile_button' , _ ( 'Switch profile' ) , 'people-circle-outline' , switchProfileButton_press ) ) ;
2023-01-10 14:08:13 +02:00
}
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'config_button' , _ ( 'Configuration' ) , 'settings' , configButton_press ) ) ;
2019-06-26 20:05:37 +02:00
2022-10-11 13:31:09 +02:00
items . push ( makeDivider ( 'divider_2' ) ) ;
2019-06-26 20:05:37 +02:00
2022-10-11 13:31:09 +02:00
const lines = Synchronizer . reportToLines ( props . syncReport ) ;
2019-07-29 15:43:53 +02:00
const syncReportText = lines . join ( '\n' ) ;
2017-07-18 00:43:29 +02:00
2018-06-10 18:43:24 +02:00
let decryptionReportText = '' ;
2022-10-11 13:31:09 +02:00
if ( props . decryptionWorker && props . decryptionWorker . state !== 'idle' && props . decryptionWorker . itemCount ) {
decryptionReportText = _ ( 'Decrypting items: %d/%d' , props . decryptionWorker . itemIndex + 1 , props . decryptionWorker . itemCount ) ;
2018-06-10 18:43:24 +02:00
}
2018-11-14 00:27:58 +02:00
let resourceFetcherText = '' ;
2022-10-11 13:31:09 +02:00
if ( props . resourceFetcher && props . resourceFetcher . toFetchCount ) {
resourceFetcherText = _ ( 'Fetching resources: %d/%d' , props . resourceFetcher . fetchingCount , props . resourceFetcher . toFetchCount ) ;
2018-11-14 00:27:58 +02:00
}
2020-03-14 01:46:14 +02:00
const fullReport = [ ] ;
2018-06-10 18:43:24 +02:00
if ( syncReportText ) fullReport . push ( syncReportText ) ;
2019-05-22 16:56:07 +02:00
if ( resourceFetcherText ) fullReport . push ( resourceFetcherText ) ;
2018-06-10 18:43:24 +02:00
if ( decryptionReportText ) fullReport . push ( decryptionReportText ) ;
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'synchronize_button' , ! props . syncStarted ? _ ( 'Synchronise' ) : _ ( 'Cancel' ) , 'sync' , synchronize_press ) ) ;
2019-06-26 20:05:37 +02:00
2020-03-14 01:57:34 +02:00
if ( fullReport . length ) {
2019-07-29 15:43:53 +02:00
items . push (
2022-10-11 13:31:09 +02:00
< Text key = "sync_report" style = { styles_ . syncStatus } >
2019-07-29 15:43:53 +02:00
{ fullReport . join ( '\n' ) }
2023-08-22 12:58:53 +02:00
< / Text > ,
2019-07-29 15:43:53 +02:00
) ;
2020-03-14 01:57:34 +02:00
}
2019-06-26 20:05:37 +02:00
2022-10-11 13:31:09 +02:00
if ( props . syncOnlyOverWifi && props . isOnMobileData ) {
2021-03-29 10:35:39 +02:00
items . push (
2022-10-11 13:31:09 +02:00
< Text key = "net_info" style = { styles_ . syncStatus } >
2021-03-29 10:35:39 +02:00
{ _ ( 'Mobile data - auto-sync disabled' ) }
2023-08-22 12:58:53 +02:00
< / Text > ,
2021-03-29 10:35:39 +02:00
) ;
}
2024-08-02 15:51:49 +02:00
return < View style = { { flex : 0 , flexDirection : 'column' , flexBasis : 'auto' , paddingBottom : theme.marginBottom } } > { items } < / View > ;
2022-10-11 13:31:09 +02:00
} ;
2019-06-26 20:05:37 +02:00
2022-10-11 13:31:09 +02:00
let items = [ ] ;
2018-06-10 18:43:24 +02:00
2022-10-11 13:31:09 +02:00
const theme = themeStyle ( props . themeId ) ;
2017-07-22 17:55:09 +02:00
2022-10-11 13:31:09 +02:00
// HACK: inner height of ScrollView doesn't appear to be calculated correctly when
// using padding. So instead creating blank elements for padding bottom and top.
items . push ( < View style = { { height : theme.marginTop } } key = "bottom_top_hack" / > ) ;
2019-06-28 01:51:02 +02:00
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'all_notes' , _ ( 'All notes' ) , 'document' , allNotesButton_press , props . notesParentType === 'SmartFilter' ) ) ;
2019-06-28 01:51:02 +02:00
2022-10-11 13:31:09 +02:00
items . push ( makeDivider ( 'divider_all' ) ) ;
2019-07-11 19:44:26 +02:00
2023-10-07 18:25:03 +02:00
items . push ( renderSidebarButton ( 'folder_header' , _ ( 'Notebooks' ) , 'folder' ) ) ;
2017-07-06 21:48:17 +02:00
2024-07-04 14:56:57 +02:00
const folderTree = useMemo ( ( ) = > {
return buildFolderTree ( props . folders ) ;
} , [ props . folders ] ) ;
2022-10-11 13:31:09 +02:00
if ( props . folders . length ) {
2024-07-04 14:56:57 +02:00
const result = renderFolders ( {
folderTree ,
collapsedFolderIds : props.collapsedFolderIds ,
} , renderFolderItem ) ;
2022-10-11 13:31:09 +02:00
const folderItems = result . items ;
items = items . concat ( folderItems ) ;
}
2017-10-30 20:27:51 +02:00
2023-09-11 21:44:15 +02:00
const isHidden = ! props . sideMenuVisible ;
2022-10-11 13:31:09 +02:00
const style = {
flex : 1 ,
borderRightWidth : 1 ,
borderRightColor : theme.dividerColor ,
backgroundColor : theme.backgroundColor ,
2023-09-11 21:44:15 +02:00
// Have the UI reflect whether the View is hidden to the screen reader.
// This way, there will be visual feedback if isHidden is incorrect.
opacity : isHidden ? 0.5 : undefined ,
2022-10-11 13:31:09 +02:00
} ;
return (
2024-08-02 15:51:49 +02:00
< AccessibleView
2023-09-11 21:44:15 +02:00
style = { style }
2024-08-02 15:51:49 +02:00
// Accessibility, keyboard, and touch hidden.
inert = { isHidden }
refocusCounter = { isHidden ? undefined : 1 }
2023-09-11 21:44:15 +02:00
>
2022-10-11 13:31:09 +02:00
< View style = { { flex : 1 , opacity : props.opacity } } >
< ScrollView scrollsToTop = { false } style = { styles_ . menu } >
{ items }
< / ScrollView >
{ renderBottomPanel ( ) }
2017-07-22 17:55:09 +02:00
< / View >
2024-08-02 15:51:49 +02:00
< / AccessibleView >
2022-10-11 13:31:09 +02:00
) ;
} ;
2019-07-29 15:43:53 +02:00
2022-10-11 13:31:09 +02:00
export default connect ( ( state : AppState ) = > {
2019-07-29 15:43:53 +02:00
return {
folders : state.folders ,
syncStarted : state.syncStarted ,
syncReport : state.syncReport ,
selectedFolderId : state.selectedFolderId ,
selectedTagId : state.selectedTagId ,
notesParentType : state.notesParentType ,
locale : state.settings.locale ,
2020-09-15 15:01:07 +02:00
themeId : state.settings.theme ,
2023-09-11 21:44:15 +02:00
sideMenuVisible : state.showSideMenu ,
2019-07-29 15:43:53 +02:00
// Don't do the opacity animation as it means re-rendering the list multiple times
// opacity: state.sideMenuOpenPercent,
collapsedFolderIds : state.collapsedFolderIds ,
decryptionWorker : state.decryptionWorker ,
resourceFetcher : state.resourceFetcher ,
2021-03-29 10:35:39 +02:00
isOnMobileData : state.isOnMobileData ,
syncOnlyOverWifi : state.settings [ 'sync.mobileWifiOnly' ] ,
2023-01-10 14:08:13 +02:00
profileConfig : state.profileConfig ,
2023-07-23 16:57:55 +02:00
inboxJopId : state.settings [ 'sync.10.inboxId' ] ,
2019-07-29 15:43:53 +02:00
} ;
} ) ( SideMenuContentComponent ) ;