From e8859bdff10bd9ba3206fd02ee4bcd7d9a7ecfd6 Mon Sep 17 00:00:00 2001 From: vardanhakobyan Date: Wed, 8 Jun 2022 16:31:27 +0400 Subject: [PATCH] feat: rename workspace --- src/ModalStack.tsx | 30 +++- .../InputModal/WorkspaceInputModal.tsx | 61 ++++++++ .../Settings/Sections/OptionsSection.tsx | 131 ++++++++---------- src/Screens/screens.ts | 1 + 4 files changed, 152 insertions(+), 71 deletions(-) create mode 100644 src/Screens/InputModal/WorkspaceInputModal.tsx diff --git a/src/ModalStack.tsx b/src/ModalStack.tsx index da8557c1..b9e2e2bd 100644 --- a/src/ModalStack.tsx +++ b/src/ModalStack.tsx @@ -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 + } [SCREEN_INPUT_MODAL_PASSCODE]: undefined [SCREEN_AUTHENTICATE]: { challenge: Challenge @@ -275,6 +281,28 @@ export const MainStackComponent = ({ env }: { env: TEnvironment }) => { })} component={BlockingModal} /> + ({ + title: 'Workspace', + gestureEnabled: false, + headerTitle: ({ children }) => { + return + }, + headerLeft: ({ disabled, onPress }) => ( + + + + ), + })} + component={WorkspaceInputModal} + /> ) } diff --git a/src/Screens/InputModal/WorkspaceInputModal.tsx b/src/Screens/InputModal/WorkspaceInputModal.tsx new file mode 100644 index 00000000..8c4ce466 --- /dev/null +++ b/src/Screens/InputModal/WorkspaceInputModal.tsx @@ -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 + +export const WorkspaceInputModal: FC = props => { + const { descriptor, renameWorkspace } = props.route.params + const themeService = useContext(ThemeServiceContext) + const application = useSafeApplicationContext() + + const workspaceNameInputRef = useRef(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 ( + + + + + + + + + + ) +} diff --git a/src/Screens/Settings/Sections/OptionsSection.tsx b/src/Screens/Settings/Sections/OptionsSection.tsx index 86ea8147..4250ea26 100644 --- a/src/Screens/Settings/Sections/OptionsSection.tsx +++ b/src/Screens/Settings/Sections/OptionsSection.tsx @@ -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( diff --git a/src/Screens/screens.ts b/src/Screens/screens.ts index 3f74c8ea..27320e52 100644 --- a/src/Screens/screens.ts +++ b/src/Screens/screens.ts @@ -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