mirror of
https://github.com/standardnotes/mobile.git
synced 2026-08-01 15:56:52 -04:00
feat: rename workspace
This commit is contained in:
@@ -14,13 +14,15 @@ import {
|
||||
SCREEN_INPUT_MODAL_FILE_NAME,
|
||||
SCREEN_INPUT_MODAL_PASSCODE,
|
||||
SCREEN_INPUT_MODAL_TAG,
|
||||
SCREEN_INPUT_MODAL_WORKSPACE_NAME,
|
||||
SCREEN_MANAGE_SESSIONS,
|
||||
SCREEN_SETTINGS,
|
||||
SCREEN_UPLOADED_FILES_LIST,
|
||||
} from '@Root/Screens/screens'
|
||||
import { Settings } from '@Root/Screens/Settings/Settings'
|
||||
import { UploadedFilesList } from '@Root/Screens/UploadedFilesList/UploadedFilesList'
|
||||
import { Challenge, DeinitMode, DeinitSource, FileItem, SNNote } from '@standardnotes/snjs'
|
||||
import { WorkspaceInputModal } from '@Screens/InputModal/WorkspaceInputModal'
|
||||
import { ApplicationDescriptor, Challenge, DeinitMode, DeinitSource, FileItem, SNNote } from '@standardnotes/snjs'
|
||||
import { ICON_CHECKMARK, ICON_CLOSE } from '@Style/Icons'
|
||||
import { ThemeService } from '@Style/ThemeService'
|
||||
import React, { memo, useContext } from 'react'
|
||||
@@ -48,6 +50,10 @@ export type ModalStackNavigatorParamList = {
|
||||
[SCREEN_UPLOADED_FILES_LIST]: HeaderTitleParams & {
|
||||
note: SNNote
|
||||
}
|
||||
[SCREEN_INPUT_MODAL_WORKSPACE_NAME]: HeaderTitleParams & {
|
||||
descriptor: ApplicationDescriptor
|
||||
renameWorkspace: (descriptor: ApplicationDescriptor, workspaceName: string) => Promise<void>
|
||||
}
|
||||
[SCREEN_INPUT_MODAL_PASSCODE]: undefined
|
||||
[SCREEN_AUTHENTICATE]: {
|
||||
challenge: Challenge
|
||||
@@ -275,6 +281,28 @@ export const MainStackComponent = ({ env }: { env: TEnvironment }) => {
|
||||
})}
|
||||
component={BlockingModal}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name={SCREEN_INPUT_MODAL_WORKSPACE_NAME}
|
||||
options={({ route }) => ({
|
||||
title: 'Workspace',
|
||||
gestureEnabled: false,
|
||||
headerTitle: ({ children }) => {
|
||||
return <HeaderTitleView title={route.params?.title ?? (children || '')} />
|
||||
},
|
||||
headerLeft: ({ disabled, onPress }) => (
|
||||
<HeaderButtons HeaderButtonComponent={IoniconsHeaderButton}>
|
||||
<Item
|
||||
testID="headerButton"
|
||||
disabled={disabled}
|
||||
title={Platform.OS === 'ios' ? 'Cancel' : ''}
|
||||
iconName={Platform.OS === 'ios' ? undefined : ThemeService.nameForIcon(ICON_CLOSE)}
|
||||
onPress={onPress}
|
||||
/>
|
||||
</HeaderButtons>
|
||||
),
|
||||
})}
|
||||
component={WorkspaceInputModal}
|
||||
/>
|
||||
</MainStack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
61
src/Screens/InputModal/WorkspaceInputModal.tsx
Normal file
61
src/Screens/InputModal/WorkspaceInputModal.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||
import { SectionedTableCell } from '@Root/Components/SectionedTableCell'
|
||||
import { TableSection } from '@Root/Components/TableSection'
|
||||
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
|
||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||
import { SCREEN_INPUT_MODAL_WORKSPACE_NAME } from '@Root/Screens/screens'
|
||||
import { ThemeServiceContext } from '@Style/ThemeService'
|
||||
import React, { FC, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { TextInput } from 'react-native'
|
||||
import { Container, Input } from './InputModal.styled'
|
||||
|
||||
type Props = ModalStackNavigationProp<typeof SCREEN_INPUT_MODAL_WORKSPACE_NAME>
|
||||
|
||||
export const WorkspaceInputModal: FC<Props> = props => {
|
||||
const { descriptor, renameWorkspace } = props.route.params
|
||||
const themeService = useContext(ThemeServiceContext)
|
||||
const application = useSafeApplicationContext()
|
||||
|
||||
const workspaceNameInputRef = useRef<TextInput>(null)
|
||||
|
||||
const [workspaceName, setWorkspaceName] = useState(descriptor.label)
|
||||
|
||||
const onSubmit = async () => {
|
||||
const trimmedWorkspaceName = workspaceName.trim()
|
||||
if (trimmedWorkspaceName === '') {
|
||||
setWorkspaceName(descriptor.label)
|
||||
await application?.alertService.alert('Workspace name cannot be empty')
|
||||
workspaceNameInputRef.current?.focus()
|
||||
return
|
||||
}
|
||||
await renameWorkspace(descriptor, trimmedWorkspaceName)
|
||||
void application.sync.sync()
|
||||
props.navigation.goBack()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
workspaceNameInputRef.current?.focus()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<TableSection>
|
||||
<SectionedTableCell textInputCell first={true}>
|
||||
<Input
|
||||
ref={workspaceNameInputRef as any}
|
||||
placeholder={'Workspace name'}
|
||||
onChangeText={setWorkspaceName}
|
||||
value={workspaceName}
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
keyboardAppearance={themeService?.keyboardColorForActiveTheme()}
|
||||
underlineColorAndroid={'transparent'}
|
||||
onSubmitEditing={onSubmit}
|
||||
/>
|
||||
</SectionedTableCell>
|
||||
|
||||
<ButtonCell maxHeight={45} disabled={workspaceName.length === 0} title={'Save'} bold onPress={onSubmit} />
|
||||
</TableSection>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { TableSection } from '@Root/Components/TableSection'
|
||||
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
|
||||
import { useSafeApplicationGroupContext } from '@Root/Hooks/useSafeApplicationGroupContext'
|
||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens'
|
||||
import { SCREEN_INPUT_MODAL_WORKSPACE_NAME, SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens'
|
||||
import SNReactNative from '@standardnotes/react-native-utils'
|
||||
import { ApplicationDescriptor, ApplicationGroupEvent, ButtonType, PrefKey } from '@standardnotes/snjs'
|
||||
import { CustomActionSheetOption, useCustomActionSheet } from '@Style/CustomActionSheet'
|
||||
@@ -241,70 +241,72 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
||||
[AddAnother, Open, Remove, SignOutAll, application.alertService],
|
||||
)
|
||||
|
||||
const renameWorkspace = useCallback(
|
||||
async (descriptor: ApplicationDescriptor, newName: string) => {
|
||||
appGroup.renameDescriptor(descriptor, newName)
|
||||
},
|
||||
[appGroup],
|
||||
)
|
||||
|
||||
const signOutWorkspace = useCallback(async () => {
|
||||
const confirmed = await getWorkspaceActionConfirmation(Remove)
|
||||
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await application.user.signOut() // TODO: do I need to call `SNReactNative.exitApp()` here as well?
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}, [Remove, application.user, getWorkspaceActionConfirmation])
|
||||
|
||||
const openWorkspace = useCallback(
|
||||
async (descriptor: ApplicationDescriptor) => {
|
||||
const confirmed = await getWorkspaceActionConfirmation(Open)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
// await application.getInstallationService().customWipeData()
|
||||
await appGroup.unloadCurrentAndActivateDescriptor(descriptor)
|
||||
// TODO: find a way to check if there are memory leaks *without* the below call.
|
||||
SNReactNative.exitApp()
|
||||
},
|
||||
[Open, appGroup, getWorkspaceActionConfirmation],
|
||||
)
|
||||
|
||||
const getSingleWorkspaceItemOptions = useCallback(
|
||||
(descriptor: ApplicationDescriptor) => {
|
||||
const worskspaceItemOptions: CustomActionSheetOption[] = []
|
||||
if (descriptor.primary) {
|
||||
worskspaceItemOptions.push(
|
||||
{
|
||||
// text: 'Rename',
|
||||
text: Rename,
|
||||
callback: async () => {
|
||||
console.log('implement rename...')
|
||||
// await appGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
callback: () => {
|
||||
navigation.navigate(SCREEN_INPUT_MODAL_WORKSPACE_NAME, {
|
||||
descriptor,
|
||||
renameWorkspace,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
// text: 'Remove',
|
||||
text: Remove,
|
||||
destructive: true,
|
||||
callback: async () => {
|
||||
/*const confirmed = await application.alertService.confirm(
|
||||
'This action will remove this workspace and its related data from this device. Your synced data will not be affected.',
|
||||
undefined,
|
||||
'Quit App',
|
||||
ButtonType.Danger,
|
||||
)*/
|
||||
const confirmed = await getWorkspaceActionConfirmation(Remove)
|
||||
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(
|
||||
'implement remove and destroy the workspace (and probably call `SNReactNative.exitApp()` as well)...',
|
||||
)
|
||||
// application.user.signOut().catch(console.error)
|
||||
try {
|
||||
await application.user.signOut() // TODO: do I need to call `SNReactNative.exitApp()` here as well?
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
// await appGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
},
|
||||
callback: signOutWorkspace,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
worskspaceItemOptions.push({
|
||||
text: Open,
|
||||
callback: async () => {
|
||||
const confirmed = await getWorkspaceActionConfirmation(Open)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
// await application.getInstallationService().customWipeData()
|
||||
await appGroup.unloadCurrentAndActivateDescriptor(descriptor)
|
||||
// TODO: find a way to check if there are memory leaks *without* the below call.
|
||||
SNReactNative.exitApp()
|
||||
},
|
||||
callback: () => openWorkspace(descriptor),
|
||||
})
|
||||
}
|
||||
|
||||
return worskspaceItemOptions
|
||||
},
|
||||
[Open, Remove, Rename, appGroup, application, getWorkspaceActionConfirmation],
|
||||
[Open, Remove, Rename, navigation, openWorkspace, renameWorkspace, signOutWorkspace],
|
||||
)
|
||||
|
||||
const getActiveWorkspaceItems = useCallback(() => {
|
||||
@@ -328,7 +330,18 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
||||
return descriptorItemOptions
|
||||
}, [applicationDescriptors, getSingleWorkspaceItemOptions, showActionSheet])
|
||||
|
||||
/*const customSignOut = useCallback(async () => {
|
||||
const addAnotherWorkspace = useCallback(async () => {
|
||||
const confirmed = await getWorkspaceActionConfirmation(AddAnother)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
// await application.getInstallationService().wipeData()
|
||||
// await application.getInstallationService().customWipeData()
|
||||
await appGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
SNReactNative.exitApp()
|
||||
}, [AddAnother, appGroup, getWorkspaceActionConfirmation])
|
||||
|
||||
const signOutAllWorkspaces = useCallback(async () => {
|
||||
try {
|
||||
const confirmed = await getWorkspaceActionConfirmation(SignOutAll)
|
||||
if (!confirmed) {
|
||||
@@ -341,7 +354,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}, [SignOutAll, appGroup, getWorkspaceActionConfirmation])*/
|
||||
}, [SignOutAll, appGroup, getWorkspaceActionConfirmation])
|
||||
|
||||
const handleSwitchWorkspaceClick = useCallback(() => {
|
||||
const activeDescriptors = getActiveWorkspaceItems()
|
||||
@@ -349,37 +362,15 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
||||
...activeDescriptors,
|
||||
{
|
||||
text: AddAnother,
|
||||
callback: async () => {
|
||||
const confirmed = await getWorkspaceActionConfirmation(AddAnother)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
// await application.getInstallationService().wipeData()
|
||||
// await application.getInstallationService().customWipeData()
|
||||
await appGroup.unloadCurrentAndCreateNewDescriptor()
|
||||
SNReactNative.exitApp()
|
||||
},
|
||||
callback: addAnotherWorkspace,
|
||||
},
|
||||
{
|
||||
text: SignOutAll,
|
||||
callback: async () => {
|
||||
try {
|
||||
const confirmed = await getWorkspaceActionConfirmation(SignOutAll)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
await appGroup.signOutAllWorkspaces()
|
||||
|
||||
// TODO: do we need `exitApp` here as well?
|
||||
SNReactNative.exitApp()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
callback: signOutAllWorkspaces,
|
||||
},
|
||||
]
|
||||
showActionSheet({ title: '', options })
|
||||
}, [AddAnother, SignOutAll, appGroup, getActiveWorkspaceItems, getWorkspaceActionConfirmation, showActionSheet])
|
||||
}, [AddAnother, SignOutAll, addAnotherWorkspace, getActiveWorkspaceItems, showActionSheet, signOutAllWorkspaces])
|
||||
|
||||
const showDataBackupAlert = useCallback(() => {
|
||||
void application.alertService.alert(
|
||||
|
||||
@@ -8,6 +8,7 @@ export const SCREEN_INPUT_MODAL_FILE_NAME = 'InputModalFileName'
|
||||
export const SCREEN_NOTE_HISTORY = 'NoteSessionHistory' as const
|
||||
export const SCREEN_NOTE_HISTORY_PREVIEW = 'NoteSessionHistoryPreview' as const
|
||||
export const SCREEN_UPLOADED_FILES_LIST = 'UploadedFilesList' as const
|
||||
export const SCREEN_INPUT_MODAL_WORKSPACE_NAME = 'InputModalWorkspaceName' as const
|
||||
|
||||
export const SCREEN_SETTINGS = 'Settings'
|
||||
export const SCREEN_MANAGE_SESSIONS = 'ManageSessions' as const
|
||||
|
||||
Reference in New Issue
Block a user