From 7d60dfee73cbc5d2f887ef261337d3d76d3249b8 Mon Sep 17 00:00:00 2001 From: Vardan Hakobyan Date: Tue, 21 Jun 2022 15:42:43 +0400 Subject: [PATCH] feat: mobile workspaces (#1093) --- packages/mobile/.eslintrc | 14 ++ packages/mobile/.eslintrc.js | 28 --- packages/mobile/.prettierrc.js | 11 - packages/mobile/e2e/Helpers.ts | 9 +- .../e2e/Screens/Settings/Account.spec.ts | 9 +- .../e2e/Screens/Settings/Options.spec.ts | 1 - packages/mobile/index.js | 12 ++ packages/mobile/package.json | 7 +- packages/mobile/src/App.tsx | 15 +- packages/mobile/src/AppStack.tsx | 6 +- .../mobile/src/ApplicationGroupContext.tsx | 4 + packages/mobile/src/Components/Button.tsx | 2 +- packages/mobile/src/Components/ButtonCell.tsx | 10 +- packages/mobile/src/Components/Circle.ts | 10 +- .../mobile/src/Components/HeaderTitleView.tsx | 10 +- .../mobile/src/Components/SectionHeader.tsx | 24 +-- .../SectionedAccessoryTableCell.tsx | 10 +- .../Components/SectionedOptionsTableCell.tsx | 26 +-- .../src/Components/SectionedTableCell.ts | 20 +- .../mobile/src/Components/TableSection.ts | 2 +- .../mobile/src/Components/ToastWrapper.tsx | 6 +- packages/mobile/src/Hooks/useFiles.ts | 6 +- .../Hooks/useSafeApplicationGroupContext.ts | 8 + packages/mobile/src/Lib/AlertService.ts | 2 +- packages/mobile/src/Lib/ApplicationState.ts | 18 +- packages/mobile/src/Lib/BackupsService.ts | 8 +- packages/mobile/src/Lib/ComponentManager.ts | 4 +- .../mobile/src/Lib/InstallationService.ts | 30 ++- packages/mobile/src/Lib/Interface.ts | 20 +- packages/mobile/src/Lib/SnjsHelperHooks.ts | 14 +- packages/mobile/src/ModalStack.tsx | 30 ++- .../src/Screens/Authenticate/Authenticate.tsx | 20 +- .../src/Screens/Compose/ComponentView.tsx | 2 +- .../mobile/src/Screens/Compose/Compose.tsx | 12 +- .../src/Screens/InputModal/FileInputModal.tsx | 2 +- .../src/Screens/InputModal/TagInputModal.tsx | 4 +- .../InputModal/WorkspaceInputModal.tsx | 61 ++++++ .../Screens/ManageSessions/ManageSessions.tsx | 4 +- .../Screens/ManageSessions/SessionCell.tsx | 8 +- .../src/Screens/NoteHistory/NoteHistory.tsx | 4 +- .../Screens/NoteHistory/NoteHistoryCell.tsx | 8 +- .../NoteHistory/NoteHistoryPreview.tsx | 2 +- .../src/Screens/NoteHistory/RemoteHistory.tsx | 2 +- .../Screens/NoteHistory/SessionHistory.tsx | 2 +- .../src/Screens/Notes/NoteCell.styled.ts | 8 +- .../mobile/src/Screens/Notes/NoteCell.tsx | 10 +- .../src/Screens/Notes/NoteCellFlags.tsx | 2 +- .../src/Screens/Notes/NoteList.styled.ts | 8 +- .../mobile/src/Screens/Notes/NoteList.tsx | 6 +- packages/mobile/src/Screens/Notes/Notes.tsx | 8 +- .../src/Screens/Notes/OfflineBanner.styled.ts | 10 +- packages/mobile/src/Screens/Root.tsx | 6 +- .../Settings/Sections/OptionsSection.tsx | 41 ++-- .../Settings/Sections/PreferencesSection.tsx | 8 +- .../Settings/Sections/WorkspacesSection.tsx | 199 ++++++++++++++++++ .../mobile/src/Screens/Settings/Settings.tsx | 4 +- .../mobile/src/Screens/SideMenu/Files.tsx | 2 +- .../mobile/src/Screens/SideMenu/Listed.tsx | 2 +- .../src/Screens/SideMenu/MainSideMenu.tsx | 10 +- .../src/Screens/SideMenu/NoteSideMenu.tsx | 36 ++-- .../Screens/SideMenu/SideMenuCell.styled.ts | 4 +- .../src/Screens/SideMenu/SideMenuCell.tsx | 2 +- .../src/Screens/SideMenu/SideMenuHero.tsx | 4 +- .../SideMenu/SideMenuSection.styled.ts | 2 +- .../src/Screens/SideMenu/SideMenuSection.tsx | 4 +- .../src/Screens/SideMenu/TagSelectionList.tsx | 6 +- .../UploadedFilesList/UploadedFilesList.tsx | 6 +- packages/mobile/src/Screens/screens.ts | 1 + packages/mobile/src/Style/CssParser.ts | 2 +- .../mobile/src/Style/CustomActionSheet.ts | 6 +- packages/mobile/src/Style/ThemeService.ts | 12 +- 71 files changed, 599 insertions(+), 317 deletions(-) create mode 100644 packages/mobile/.eslintrc delete mode 100644 packages/mobile/.eslintrc.js delete mode 100644 packages/mobile/.prettierrc.js create mode 100644 packages/mobile/src/ApplicationGroupContext.tsx create mode 100644 packages/mobile/src/Hooks/useSafeApplicationGroupContext.ts create mode 100644 packages/mobile/src/Screens/InputModal/WorkspaceInputModal.tsx create mode 100644 packages/mobile/src/Screens/Settings/Sections/WorkspacesSection.tsx diff --git a/packages/mobile/.eslintrc b/packages/mobile/.eslintrc new file mode 100644 index 000000000..f4039e912 --- /dev/null +++ b/packages/mobile/.eslintrc @@ -0,0 +1,14 @@ +{ + "env": { + "node": true, + "commonjs": true + }, + "extends": ["@react-native-community", "plugin:react-hooks/recommended", "../../.eslintrc.json"], + "rules": { + "no-console": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-var-requires": "off", + "eqeqeq": ["off"] + }, + "ignorePatterns": ["metro.config.js"] +} diff --git a/packages/mobile/.eslintrc.js b/packages/mobile/.eslintrc.js deleted file mode 100644 index df9f2a817..000000000 --- a/packages/mobile/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - root: true, - extends: ['@react-native-community', 'prettier', '../../node_modules/@standardnotes/config/src/.eslintrc'], - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: __dirname, - }, - plugins: ['@typescript-eslint', 'prettier'], - ignorePatterns: ['.eslintrc.js', 'metro.config.js'], - overrides: [ - { - files: ['*.ts', '*.tsx'], - rules: { - '@typescript-eslint/no-shadow': ['error'], - 'no-shadow': 'off', - 'no-undef': 'off', - '@typescript-eslint/no-explicit-any': 'warn', - 'no-invalid-this': 'warn', - 'no-console': 'warn', - eqeqeq: ['warn', 'smart'], - 'no-void': 'off', - }, - }, - ], - rules: { - 'prettier/prettier': 'warn', - }, -} diff --git a/packages/mobile/.prettierrc.js b/packages/mobile/.prettierrc.js deleted file mode 100644 index 884ccd5f8..000000000 --- a/packages/mobile/.prettierrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - arrowParens: 'avoid', - bracketSpacing: true, - jsxSingleQuote: false, - quoteProps: 'as-needed', - semi: false, - singleQuote: true, - tabWidth: 2, - printWidth: 120, - trailingComma: 'all', -} diff --git a/packages/mobile/e2e/Helpers.ts b/packages/mobile/e2e/Helpers.ts index 768695a76..05f571a73 100644 --- a/packages/mobile/e2e/Helpers.ts +++ b/packages/mobile/e2e/Helpers.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires const faker = require('faker') import { by, device, element, expect, waitFor } from 'detox' @@ -13,14 +12,10 @@ export const expectToBeVisible = async (testedElement: Detox.IndexableNativeElem const checkAfterReinstall = async () => { if (device.getPlatform() === 'ios') { - const alertElement = element( - by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView')) - ) + const alertElement = element(by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView'))) const alertVisible = await expectToBeVisible(alertElement) if (alertVisible) { - await element( - by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView')) - ).tap() + await element(by.label('Delete Local Data').and(by.type('_UIAlertControllerActionView'))).tap() } } } diff --git a/packages/mobile/e2e/Screens/Settings/Account.spec.ts b/packages/mobile/e2e/Screens/Settings/Account.spec.ts index 04e432115..d9eac0fd3 100644 --- a/packages/mobile/e2e/Screens/Settings/Account.spec.ts +++ b/packages/mobile/e2e/Screens/Settings/Account.spec.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires const helpers = require('../../Helpers') import { by, device, element, expect, waitFor } from 'detox' import { openSettingsScreen } from '../../Helpers' @@ -45,9 +44,7 @@ fdescribe('Account section', () => { await element(by.id('passwordField')).typeText(helpers.randomCredentials.password) await element(by.id('otherOptionsButton')).tap() await element(by.id('syncServerField')).clearText() - await element(by.id('syncServerField')).typeText( - helpers.randomCredentials.syncServerUrl + '\n' - ) + await element(by.id('syncServerField')).typeText(helpers.randomCredentials.syncServerUrl + '\n') // wait for buttons to be visible after closing keyboard on smaller devices await waitFor(element(by.id('registerButton'))) .toBeVisible() @@ -90,9 +87,7 @@ fdescribe('Account section', () => { await element(by.id('passwordField')).typeText(helpers.randomCredentials.password) await element(by.id('otherOptionsButton')).tap() await element(by.id('syncServerField')).clearText() - await element(by.id('syncServerField')).typeText( - helpers.randomCredentials.syncServerUrl + '\n' - ) + await element(by.id('syncServerField')).typeText(helpers.randomCredentials.syncServerUrl + '\n') // wait for button to be visible after keyboard close await waitFor(element(by.id('signInButton'))) .toBeVisible() diff --git a/packages/mobile/e2e/Screens/Settings/Options.spec.ts b/packages/mobile/e2e/Screens/Settings/Options.spec.ts index f4b400c86..5bd47bbc4 100644 --- a/packages/mobile/e2e/Screens/Settings/Options.spec.ts +++ b/packages/mobile/e2e/Screens/Settings/Options.spec.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires const helpers = require('../../Helpers') import { by, device, element, expect } from 'detox' diff --git a/packages/mobile/index.js b/packages/mobile/index.js index 53d362d88..5262bd969 100644 --- a/packages/mobile/index.js +++ b/packages/mobile/index.js @@ -21,6 +21,18 @@ if (__DEV__ === false) { } /* eslint-enable no-console */ +const originalWarn = console.warn + +console.warn = function filterWarnings(msg) { + const supressedWarnings = [ + "[react-native-gesture-handler] Seems like you're using an old API with gesture components", + ] + + if (!supressedWarnings.some(entry => msg.includes(entry))) { + originalWarn.apply(console, arguments) + } +} + enableAndroidFontFix() AppRegistry.registerComponent(appName, () => App) diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 2ef177967..c0903098c 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -11,9 +11,10 @@ "ios-dev": "react-native run-ios --scheme StandardNotesDev", "ios-prod": "react-native run-ios --scheme StandardNotes", "clear-cache": "watchman watch-del-all && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-*", - "lint": "yarn lint:eslint && yarn lint:prettier", - "lint:eslint": "yarn eslint . --ext .ts,.tsx --fix --quiet", - "lint:prettier": "prettier ./src --write", + "lint": "yarn eslint . --ext .ts,.tsx", + "lint:fix": "yarn lint --fix", + "format": "prettier ./src", + "format:fix": "yarn format --write", "build": "yarn android:bundle && yarn install:pods", "tsc": "tsc --noEmit", "start": "react-native start", diff --git a/packages/mobile/src/App.tsx b/packages/mobile/src/App.tsx index 1a3a3bbf6..f6a52073d 100644 --- a/packages/mobile/src/App.tsx +++ b/packages/mobile/src/App.tsx @@ -4,6 +4,7 @@ import { MobileApplication } from '@Lib/Application' import { ApplicationGroup } from '@Lib/ApplicationGroup' import { navigationRef } from '@Lib/NavigationService' import { DefaultTheme, NavigationContainer } from '@react-navigation/native' +import { ApplicationGroupContext } from '@Root/ApplicationGroupContext' import { MobileThemeVariables } from '@Root/Style/Themes/styled-components' import { ApplicationGroupEvent, DeinitMode, DeinitSource } from '@standardnotes/snjs' import { ThemeService, ThemeServiceContext } from '@Style/ThemeService' @@ -68,7 +69,7 @@ const AppComponent: React.FC<{ setThemeServiceRef(themeServiceInstance) await application.prepareForLaunch({ - receiveChallenge: async challenge => { + receiveChallenge: async (challenge) => { application.promptForChallenge(challenge) }, }) @@ -135,7 +136,7 @@ export const App = (props: { env: TEnvironment }) => { const [appGroup, setAppGroup] = useState(() => createNewAppGroup()) useEffect(() => { - const removeAppChangeObserver = appGroup.addEventObserver(event => { + const removeAppChangeObserver = appGroup.addEventObserver((event) => { if (event === ApplicationGroupEvent.PrimaryApplicationSet) { const mobileApplication = appGroup.primaryApplication as MobileApplication setApplication(mobileApplication) @@ -145,15 +146,17 @@ export const App = (props: { env: TEnvironment }) => { } }) return removeAppChangeObserver - }, [appGroup, appGroup.primaryApplication, setAppGroup, createNewAppGroup]) + }, [appGroup, appGroup.primaryApplication, createNewAppGroup]) if (!application) { return null } return ( - - - + + + + + ) } diff --git a/packages/mobile/src/AppStack.tsx b/packages/mobile/src/AppStack.tsx index 71f0929ca..0aa321bf5 100644 --- a/packages/mobile/src/AppStack.tsx +++ b/packages/mobile/src/AppStack.tsx @@ -63,7 +63,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) = const noteDrawerRef = useRef(null) useEffect(() => { - const removeObserver = application?.getAppState().addStateChangeObserver(event => { + const removeObserver = application?.getAppState().addStateChangeObserver((event) => { if (event === AppStateType.EditorClosed) { noteDrawerRef.current?.closeDrawer() if (!isInTabletMode && props.navigation.canGoBack()) { @@ -76,7 +76,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) = }, [application, props.navigation, isInTabletMode]) useEffect(() => { - const removeObserver = application?.getStatusManager().addHeaderStatusObserver(messages => { + const removeObserver = application?.getStatusManager().addHeaderStatusObserver((messages) => { setNotesStatus(messages[SCREEN_NOTES]) setComposeStatus(messages[SCREEN_COMPOSE]) }) @@ -161,7 +161,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) = const screenStatus = isInTabletMode ? composeStatus || notesStatus : notesStatus const title = route.params?.title ?? (children || '') - const subtitle = [screenStatus?.status, route.params?.subTitle].filter(x => !!x).join(' • ') + const subtitle = [screenStatus?.status, route.params?.subTitle].filter((x) => !!x).join(' • ') return }, diff --git a/packages/mobile/src/ApplicationGroupContext.tsx b/packages/mobile/src/ApplicationGroupContext.tsx new file mode 100644 index 000000000..cc15f8dbf --- /dev/null +++ b/packages/mobile/src/ApplicationGroupContext.tsx @@ -0,0 +1,4 @@ +import { ApplicationGroup } from '@Lib/ApplicationGroup' +import React from 'react' + +export const ApplicationGroupContext = React.createContext(undefined) diff --git a/packages/mobile/src/Components/Button.tsx b/packages/mobile/src/Components/Button.tsx index 4c494fa0d..8bab36514 100644 --- a/packages/mobile/src/Components/Button.tsx +++ b/packages/mobile/src/Components/Button.tsx @@ -55,7 +55,7 @@ const ButtonLabel = styled.Text<{ primary?: boolean }>` color: ${({ theme, primary }) => { return primary ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor }}; - font-size: ${props => props.theme.mainTextFontSize}px; + font-size: ${(props) => props.theme.mainTextFontSize}px; ` export const Button: React.FC = ({ onPress, label, primary, fullWidth, last }: Props) => { diff --git a/packages/mobile/src/Components/ButtonCell.tsx b/packages/mobile/src/Components/ButtonCell.tsx index 79b8e09b9..ebe064ac7 100644 --- a/packages/mobile/src/Components/ButtonCell.tsx +++ b/packages/mobile/src/Components/ButtonCell.tsx @@ -17,7 +17,7 @@ type Props = { } type ContainerProps = Pick & TableCellProps -const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ +const Container = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({ underlayColor: props.theme.stylekitBorderColor, }))` padding-top: ${12}px; @@ -32,9 +32,9 @@ const ButtonContainer = styled.View`` type ButtonLabelProps = Pick const ButtonLabel = styled.Text` - text-align: ${props => (props.leftAligned ? 'left' : 'center')}; + text-align: ${(props) => (props.leftAligned ? 'left' : 'center')}; text-align-vertical: center; - color: ${props => { + color: ${(props) => { let color = Platform.OS === 'android' ? props.theme.stylekitForegroundColor : props.theme.stylekitInfoColor if (props.disabled) { color = 'gray' @@ -43,7 +43,7 @@ const ButtonLabel = styled.Text` } return color }}; - font-size: ${props => props.theme.mainTextFontSize}px; + font-size: ${(props) => props.theme.mainTextFontSize}px; ${({ bold }) => bold && css` @@ -56,7 +56,7 @@ const ButtonLabel = styled.Text` `} ` -export const ButtonCell: React.FC = props => ( +export const ButtonCell: React.FC = (props) => ( ` - width: ${props => props.size ?? 12}px; - height: ${props => props.size ?? 12}px; - border-radius: ${props => (props.size ?? 12) / 2}px; - background-color: ${props => props.backgroundColor}; - border-color: ${props => props.borderColor}; + width: ${(props) => props.size ?? 12}px; + height: ${(props) => props.size ?? 12}px; + border-radius: ${(props) => (props.size ?? 12) / 2}px; + background-color: ${(props) => props.backgroundColor}; + border-color: ${(props) => props.borderColor}; border-width: 1px; ` diff --git a/packages/mobile/src/Components/HeaderTitleView.tsx b/packages/mobile/src/Components/HeaderTitleView.tsx index fadc291c6..289294592 100644 --- a/packages/mobile/src/Components/HeaderTitleView.tsx +++ b/packages/mobile/src/Components/HeaderTitleView.tsx @@ -9,14 +9,14 @@ type Props = { } const Container = styled.View` - /* background-color: ${props => props.theme.stylekitContrastBackgroundColor}; */ + /* background-color: ${(props) => props.theme.stylekitContrastBackgroundColor}; */ flex: 1; justify-content: center; ${Platform.OS === 'android' && 'align-items: flex-start; min-width: 100px;'} ` const Title = styled.Text` - color: ${props => props.theme.stylekitForegroundColor}; + color: ${(props) => props.theme.stylekitForegroundColor}; font-weight: bold; font-size: 18px; text-align: center; @@ -25,13 +25,13 @@ const SubTitle = styled.Text.attrs(() => ({ adjustsFontSizeToFit: true, numberOfLines: 1, }))<{ color?: string }>` - color: ${props => props.color ?? props.theme.stylekitForegroundColor}; - opacity: ${props => (props.color ? 1 : 0.6)}; + color: ${(props) => props.color ?? props.theme.stylekitForegroundColor}; + opacity: ${(props) => (props.color ? 1 : 0.6)}; font-size: ${Platform.OS === 'android' ? 13 : 12}px; ${Platform.OS === 'ios' && 'text-align: center'} ` -export const HeaderTitleView: React.FC = props => ( +export const HeaderTitleView: React.FC = (props) => ( {props.title} {props.subtitle && props.subtitle.length > 0 ? ( diff --git a/packages/mobile/src/Components/SectionHeader.tsx b/packages/mobile/src/Components/SectionHeader.tsx index 0b5a8c6b7..32a0aa028 100644 --- a/packages/mobile/src/Components/SectionHeader.tsx +++ b/packages/mobile/src/Components/SectionHeader.tsx @@ -17,19 +17,19 @@ const Container = styled.View>` /* flex-grow: 0; */ justify-content: space-between; flex-direction: row; - padding-right: ${props => props.theme.paddingLeft}px; + padding-right: ${(props) => props.theme.paddingLeft}px; padding-bottom: 10px; padding-top: 10px; - background-color: ${props => props.backgroundColor ?? props.theme.stylekitBackgroundColor}; + background-color: ${(props) => props.backgroundColor ?? props.theme.stylekitBackgroundColor}; ` const TitleContainer = styled.View`` const Title = styled.Text>` - background-color: ${props => props.theme.stylekitBackgroundColor}; - font-size: ${props => { + background-color: ${(props) => props.theme.stylekitBackgroundColor}; + font-size: ${(props) => { return Platform.OS === 'android' ? props.theme.mainTextFontSize - 2 : props.theme.mainTextFontSize - 4 }}px; - padding-left: ${props => props.theme.paddingLeft}px; - color: ${props => { + padding-left: ${(props) => props.theme.paddingLeft}px; + color: ${(props) => { if (props.tinted) { return props.theme.stylekitInfoColor } @@ -39,11 +39,11 @@ const Title = styled.Text>` font-weight: ${Platform.OS === 'android' ? 'bold' : 'normal'}; ` const SubTitle = styled.Text` - background-color: ${props => props.theme.stylekitBackgroundColor}; - font-size: ${props => props.theme.mainTextFontSize - 5}px; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; + font-size: ${(props) => props.theme.mainTextFontSize - 5}px; margin-top: 4px; - padding-left: ${props => props.theme.paddingLeft}px; - color: ${props => props.theme.stylekitNeutralColor}; + padding-left: ${(props) => props.theme.paddingLeft}px; + color: ${(props) => props.theme.stylekitNeutralColor}; ` const ButtonContainer = styled.TouchableOpacity` flex: 1; @@ -51,10 +51,10 @@ const ButtonContainer = styled.TouchableOpacity` justify-content: center; ` const Button = styled.Text` - color: ${props => props.theme.stylekitInfoColor}; + color: ${(props) => props.theme.stylekitInfoColor}; ` -export const SectionHeader: React.FC = props => ( +export const SectionHeader: React.FC = (props) => ( {!!props.title && ( diff --git a/packages/mobile/src/Components/SectionedAccessoryTableCell.tsx b/packages/mobile/src/Components/SectionedAccessoryTableCell.tsx index 710a9b12f..0806de525 100644 --- a/packages/mobile/src/Components/SectionedAccessoryTableCell.tsx +++ b/packages/mobile/src/Components/SectionedAccessoryTableCell.tsx @@ -22,7 +22,7 @@ type Props = { last?: boolean } -const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ +const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({ underlayColor: props.theme.stylekitBorderColor, }))` flex-direction: column; @@ -33,7 +33,7 @@ const TouchableContainer = styled(SectionedTableCellTouchableHighlight).attrs(pr ` const ContentContainer = styled.View>` flex: 1; - justify-content: ${props => { + justify-content: ${(props) => { return props.leftAlignIcon ? 'flex-start' : 'space-between' }}; flex-direction: row; @@ -46,7 +46,7 @@ const IconContainer = styled.View` type LabelProps = Pick const Label = styled.Text` min-width: 80%; - color: ${props => { + color: ${(props) => { let color = props.theme.stylekitForegroundColor if (props.tinted) { color = props.theme.stylekitInfoColor @@ -59,7 +59,7 @@ const Label = styled.Text` } return color }}; - font-size: ${props => props.theme.mainTextFontSize}px; + font-size: ${(props) => props.theme.mainTextFontSize}px; ${({ bold, selected }) => ((selected && selected() === true) || bold) && css` @@ -67,7 +67,7 @@ const Label = styled.Text` `}; ` -export const SectionedAccessoryTableCell: React.FC = props => { +export const SectionedAccessoryTableCell: React.FC = (props) => { const themeContext = useContext(ThemeContext) const onPress = () => { if (props.disabled) { diff --git a/packages/mobile/src/Components/SectionedOptionsTableCell.tsx b/packages/mobile/src/Components/SectionedOptionsTableCell.tsx index e9d25f521..e05636ee8 100644 --- a/packages/mobile/src/Components/SectionedOptionsTableCell.tsx +++ b/packages/mobile/src/Components/SectionedOptionsTableCell.tsx @@ -15,11 +15,11 @@ type Props = { type ContainerProps = Omit export const Container = styled.View` - border-bottom-color: ${props => props.theme.stylekitBorderColor}; + border-bottom-color: ${(props) => props.theme.stylekitBorderColor}; border-bottom-width: 1px; - padding-left: ${props => props.theme.paddingLeft}px; - padding-right: ${props => props.theme.paddingLeft}px; - background-color: ${props => props.theme.stylekitBackgroundColor}; + padding-left: ${(props) => props.theme.paddingLeft}px; + padding-right: ${(props) => props.theme.paddingLeft}px; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; ${({ first, theme }) => first && css` @@ -38,9 +38,9 @@ export const Container = styled.View` ` const Title = styled.Text<{ leftAligned?: boolean }>` - font-size: ${props => props.theme.mainTextFontSize}px; - color: ${props => props.theme.stylekitForegroundColor}; - text-align: ${props => (props.leftAligned ? 'left' : 'center')}; + font-size: ${(props) => props.theme.mainTextFontSize}px; + color: ${(props) => props.theme.stylekitForegroundColor}; + text-align: ${(props) => (props.leftAligned ? 'left' : 'center')}; width: 42%; min-width: 0px; ` @@ -50,13 +50,13 @@ const OptionsContainer = styled.View` flex-direction: row; align-items: center; justify-content: center; - background-color: ${props => props.theme.stylekitBackgroundColor}; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; ` -const ButtonTouchable = styled.TouchableHighlight.attrs(props => ({ +const ButtonTouchable = styled.TouchableHighlight.attrs((props) => ({ underlayColor: props.theme.stylekitBorderColor, }))` - border-left-color: ${props => props.theme.stylekitBorderColor}; + border-left-color: ${(props) => props.theme.stylekitBorderColor}; border-left-width: 1px; flex-grow: 1; padding: 10px; @@ -64,7 +64,7 @@ const ButtonTouchable = styled.TouchableHighlight.attrs(props => ({ ` const ButtonTitle = styled.Text<{ selected: boolean }>` - color: ${props => { + color: ${(props) => { return props.selected ? props.theme.stylekitInfoColor : props.theme.stylekitNeutralColor }}; font-size: 16px; @@ -72,11 +72,11 @@ const ButtonTitle = styled.Text<{ selected: boolean }>` width: 100%; ` -export const SectionedOptionsTableCell: React.FC = props => ( +export const SectionedOptionsTableCell: React.FC = (props) => ( {props.title} - {props.options.map(option => { + {props.options.map((option) => { return ( props.onPress(option)}> {option.title} diff --git a/packages/mobile/src/Components/SectionedTableCell.ts b/packages/mobile/src/Components/SectionedTableCell.ts index fc365ba48..360395195 100644 --- a/packages/mobile/src/Components/SectionedTableCell.ts +++ b/packages/mobile/src/Components/SectionedTableCell.ts @@ -9,12 +9,12 @@ export type Props = { } export const SectionedTableCell = styled.View` - border-bottom-color: ${props => props.theme.stylekitBorderColor}; + border-bottom-color: ${(props) => props.theme.stylekitBorderColor}; border-bottom-width: 1px; - padding-left: ${props => props.theme.paddingLeft}px; - padding-right: ${props => props.theme.paddingLeft}px; - padding-bottom: ${props => (props.textInputCell ? 0 : 12)}px; - background-color: ${props => props.theme.stylekitBackgroundColor}; + padding-left: ${(props) => props.theme.paddingLeft}px; + padding-right: ${(props) => props.theme.paddingLeft}px; + padding-bottom: ${(props) => (props.textInputCell ? 0 : 12)}px; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; ${({ first, theme }) => first && css` @@ -34,12 +34,12 @@ export const SectionedTableCell = styled.View` ` export const SectionedTableCellTouchableHighlight = styled.TouchableHighlight` - border-bottom-color: ${props => props.theme.stylekitBorderColor}; + border-bottom-color: ${(props) => props.theme.stylekitBorderColor}; border-bottom-width: 1px; - padding-left: ${props => props.theme.paddingLeft}px; - padding-right: ${props => props.theme.paddingLeft}px; - padding-bottom: ${props => (props.textInputCell ? 0 : 12)}px; - background-color: ${props => props.theme.stylekitBackgroundColor}; + padding-left: ${(props) => props.theme.paddingLeft}px; + padding-right: ${(props) => props.theme.paddingLeft}px; + padding-bottom: ${(props) => (props.textInputCell ? 0 : 12)}px; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; ${({ first, theme }) => first && css` diff --git a/packages/mobile/src/Components/TableSection.ts b/packages/mobile/src/Components/TableSection.ts index 5df280065..112d12115 100644 --- a/packages/mobile/src/Components/TableSection.ts +++ b/packages/mobile/src/Components/TableSection.ts @@ -3,5 +3,5 @@ import styled from 'styled-components/native' export const TableSection = styled.View` margin-top: 10px; margin-bottom: 10px; - background-color: ${props => props.theme.stylekitBackgroundColor}; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; ` diff --git a/packages/mobile/src/Components/ToastWrapper.tsx b/packages/mobile/src/Components/ToastWrapper.tsx index fc42f7aac..5b7b87af0 100644 --- a/packages/mobile/src/Components/ToastWrapper.tsx +++ b/packages/mobile/src/Components/ToastWrapper.tsx @@ -12,7 +12,7 @@ export const ToastWrapper: FC = () => { const { updateProgressBar, progressBarWidth } = useProgressBar() const toastStyles: ToastConfig = { - info: props => { + info: (props) => { const percentComplete = props.props?.percentComplete || 0 updateProgressBar(percentComplete) @@ -35,13 +35,13 @@ export const ToastWrapper: FC = () => { ) }, - success: props => { + success: (props) => { const percentComplete = props.props?.percentComplete || 0 updateProgressBar(percentComplete) return }, - error: props => { + error: (props) => { const percentComplete = props.props?.percentComplete || 0 updateProgressBar(percentComplete) diff --git a/packages/mobile/src/Hooks/useFiles.ts b/packages/mobile/src/Hooks/useFiles.ts index 90f34b668..be1b6b382 100644 --- a/packages/mobile/src/Hooks/useFiles.ts +++ b/packages/mobile/src/Hooks/useFiles.ts @@ -560,7 +560,7 @@ export const useFiles = ({ note }: Props) => { return } if (shouldAttachToNote(currentTab)) { - uploadedFiles.forEach(file => attachFileToNote(file, false)) + uploadedFiles.forEach((file) => attachFileToNote(file, false)) } }, }, @@ -587,7 +587,7 @@ export const useFiles = ({ note }: Props) => { }, }, ] - const osSpecificOptions = Platform.OS === 'android' ? options.filter(option => option.key !== 'library') : options + const osSpecificOptions = Platform.OS === 'android' ? options.filter((option) => option.key !== 'library') : options showActionSheet({ title: 'Choose action', options: osSpecificOptions, @@ -783,7 +783,7 @@ export const useFiles = ({ note }: Props) => { }, ] const osDependentActions = - Platform.OS === 'ios' ? actions.filter(action => action.text !== 'Download') : [...actions] + Platform.OS === 'ios' ? actions.filter((action) => action.text !== 'Download') : [...actions] showActionSheet({ title: file.name, options: osDependentActions, diff --git a/packages/mobile/src/Hooks/useSafeApplicationGroupContext.ts b/packages/mobile/src/Hooks/useSafeApplicationGroupContext.ts new file mode 100644 index 000000000..7919c55a5 --- /dev/null +++ b/packages/mobile/src/Hooks/useSafeApplicationGroupContext.ts @@ -0,0 +1,8 @@ +import { ApplicationGroup } from '@Lib/ApplicationGroup' +import { ApplicationGroupContext } from '@Root/ApplicationGroupContext' +import { useContext } from 'react' + +export const useSafeApplicationGroupContext = () => { + const applicationGroupContext = useContext(ApplicationGroupContext) as ApplicationGroup + return applicationGroupContext +} diff --git a/packages/mobile/src/Lib/AlertService.ts b/packages/mobile/src/Lib/AlertService.ts index 7eba197e3..589a792dc 100644 --- a/packages/mobile/src/Lib/AlertService.ts +++ b/packages/mobile/src/Lib/AlertService.ts @@ -10,7 +10,7 @@ export class MobileAlertService extends AlertService { return goBack } alert(text: string, title: string, closeButtonText?: string) { - return new Promise(resolve => { + return new Promise((resolve) => { // On iOS, confirm should go first. On Android, cancel should go first. const buttons = [ { diff --git a/packages/mobile/src/Lib/ApplicationState.ts b/packages/mobile/src/Lib/ApplicationState.ts index bd24c2261..50287bab5 100644 --- a/packages/mobile/src/Lib/ApplicationState.ts +++ b/packages/mobile/src/Lib/ApplicationState.ts @@ -155,7 +155,7 @@ export class ApplicationState extends ApplicationService { const savedTag = (this.application.items.findItem(savedTagUuid) as SNTag) || - this.application.items.getSmartViews().find(tag => tag.uuid === savedTagUuid) + this.application.items.getSmartViews().find((tag) => tag.uuid === savedTagUuid) if (savedTag) { this.setSelectedTag(savedTag, false) this.selectedTagRestored = true @@ -288,7 +288,7 @@ export class ApplicationState extends ApplicationService { if (note && note.conflictOf) { void InteractionManager.runAfterInteractions(() => { - void this.application?.mutator.changeAndSaveItem(note, mutator => { + void this.application?.mutator.changeAndSaveItem(note, (mutator) => { mutator.conflictOf = undefined }) }) @@ -328,7 +328,7 @@ export class ApplicationState extends ApplicationService { } } - private keyboardDidShow: KeyboardEventListener = e => { + private keyboardDidShow: KeyboardEventListener = (e) => { this.keyboardHeight = e.endCoordinates.height this.notifyEventObservers(AppStateEventType.KeyboardChangeEvent) } @@ -353,7 +353,7 @@ export class ApplicationState extends ApplicationService { [ContentType.Note, ContentType.Tag], async ({ changed, inserted, removed, source }) => { if (source === PayloadEmitSource.PreSyncSave || source === PayloadEmitSource.RemoteRetrieved) { - const removedNotes = removed.filter(i => i.content_type === ContentType.Note) + const removedNotes = removed.filter((i) => i.content_type === ContentType.Note) for (const removedNote of removedNotes) { const editor = this.editorForNote(removedNote.uuid) if (editor) { @@ -361,7 +361,7 @@ export class ApplicationState extends ApplicationService { } } - const notes = [...changed, ...inserted].filter(candidate => candidate.content_type === ContentType.Note) + const notes = [...changed, ...inserted].filter((candidate) => candidate.content_type === ContentType.Note) const isBrowswingTrashedNotes = this.selectedTag instanceof SmartView && this.selectedTag.uuid === SystemViewId.TrashedNotes @@ -384,7 +384,7 @@ export class ApplicationState extends ApplicationService { } if (this.selectedTag) { - const matchingTag = [...changed, ...inserted].find(candidate => candidate.uuid === this.selectedTag.uuid) + const matchingTag = [...changed, ...inserted].find((candidate) => candidate.uuid === this.selectedTag.uuid) if (matchingTag) { this.selectedTag = matchingTag as SNTag } @@ -397,7 +397,7 @@ export class ApplicationState extends ApplicationService { * Registers for MobileApplication events */ private handleApplicationEvents() { - this.removeAppEventObserver = this.application.addEventObserver(async eventName => { + this.removeAppEventObserver = this.application.addEventObserver(async (eventName) => { switch (eventName) { case ApplicationEvent.LocalDataIncrementalLoad: case ApplicationEvent.LocalDataLoaded: { @@ -441,7 +441,7 @@ export class ApplicationState extends ApplicationService { * @returns tags that are referencing note */ public getNoteTags(note: SNNote) { - return this.application.items.itemsReferencingItem(note).filter(ref => { + return this.application.items.itemsReferencingItem(note).filter((ref) => { return ref.content_type === ContentType.Tag }) as SNTag[] } @@ -453,7 +453,7 @@ export class ApplicationState extends ApplicationService { if (tag instanceof SmartView) { return this.application.items.notesMatchingSmartView(tag) } else { - return this.application.items.referencesForItem(tag).filter(ref => { + return this.application.items.referencesForItem(tag).filter((ref) => { return ref.content_type === ContentType.Note }) as SNNote[] } diff --git a/packages/mobile/src/Lib/BackupsService.ts b/packages/mobile/src/Lib/BackupsService.ts index c3a64e30b..1d7602473 100644 --- a/packages/mobile/src/Lib/BackupsService.ts +++ b/packages/mobile/src/Lib/BackupsService.ts @@ -61,13 +61,13 @@ export class BackupsService extends ApplicationService { } private async exportIOS(filename: string, data: string) { - return new Promise(resolve => { + return new Promise((resolve) => { void (this.application! as MobileApplication).getAppState().performActionWithoutStateChangeImpact(async () => { Share.share({ title: filename, message: data, }) - .then(result => { + .then((result) => { resolve(result.action !== Share.dismissedAction) }) .catch(() => { @@ -98,7 +98,7 @@ export class BackupsService extends ApplicationService { // success return true }) - .catch(error => { + .catch((error) => { console.error('Error opening file', error) return false }) @@ -119,7 +119,7 @@ export class BackupsService extends ApplicationService { } private async exportViaEmailAndroid(data: string, filename: string) { - return new Promise(resolve => { + return new Promise((resolve) => { const fileType = '.json' // Android creates a tmp file and expects dot with extension let resolved = false diff --git a/packages/mobile/src/Lib/ComponentManager.ts b/packages/mobile/src/Lib/ComponentManager.ts index 9201de565..e3538dd3b 100644 --- a/packages/mobile/src/Lib/ComponentManager.ts +++ b/packages/mobile/src/Lib/ComponentManager.ts @@ -18,7 +18,7 @@ import { Base64 } from 'js-base64' import RNFS, { DocumentDirectoryPath } from 'react-native-fs' import StaticServer from 'react-native-static-server' import { unzip } from 'react-native-zip-archive' -import { componentsCdn, version, name } from '../../package.json' +import { componentsCdn, name, version } from '../../package.json' import { MobileThemeContent } from '../Style/MobileTheme' import { IsDev } from './Utils' @@ -356,7 +356,7 @@ export class ComponentManager extends SNComponentManager { } export async function associateComponentWithNote(application: SNApplication, component: SNComponent, note: SNNote) { - return application.mutator.changeItem(component, mutator => { + return application.mutator.changeItem(component, (mutator) => { mutator.removeDisassociatedItemId(note.uuid) mutator.associateWithItem(note.uuid) }) diff --git a/packages/mobile/src/Lib/InstallationService.ts b/packages/mobile/src/Lib/InstallationService.ts index 871bef150..878c3ff7a 100644 --- a/packages/mobile/src/Lib/InstallationService.ts +++ b/packages/mobile/src/Lib/InstallationService.ts @@ -1,5 +1,5 @@ import SNReactNative from '@standardnotes/react-native-utils' -import { ApplicationService, ButtonType, isNullOrUndefined, StorageValueModes } from '@standardnotes/snjs' +import { ApplicationService, ButtonType, StorageValueModes } from '@standardnotes/snjs' import { MobileDeviceInterface } from './Interface' const FIRST_RUN_KEY = 'first_run' @@ -14,7 +14,7 @@ export class InstallationService extends ApplicationService { } async markApplicationAsRan() { - return this.application?.setValue(FIRST_RUN_KEY, false, StorageValueModes.Nonwrapped) + return this.application.deviceInterface.setRawStorageValue(FIRST_RUN_KEY, 'false') } /** @@ -22,24 +22,20 @@ export class InstallationService extends ApplicationService { * AsyncStorage failures, we want to confirm with the user before deleting anything. */ async needsWipe() { - const hasNormalKeys = this.application?.hasAccount() || this.application?.hasPasscode() - const deviceInterface = this.application?.deviceInterface as MobileDeviceInterface - const keychainKey = await deviceInterface?.getRawKeychainValue() - const hasKeychainValue = !( - isNullOrUndefined(keychainKey) || - (typeof keychainKey === 'object' && Object.keys(keychainKey).length === 0) - ) + const hasAccountOrPasscode = this.application.hasAccount() || this.application?.hasPasscode() + const deviceInterface = this.application.deviceInterface as MobileDeviceInterface + const keychainKey = await deviceInterface.getNamespacedKeychainValue(this.application.identifier) - const firstRunKey = await this.application?.getValue(FIRST_RUN_KEY, StorageValueModes.Nonwrapped) - let firstRunKeyMissing = isNullOrUndefined(firstRunKey) - /* - * Because of migration failure first run key might not be in non wrapped storage - */ + const hasKeychainValue = keychainKey != undefined + + const firstRunKey = await this.application.deviceInterface.getRawStorageValue(FIRST_RUN_KEY) + let firstRunKeyMissing = firstRunKey == undefined if (firstRunKeyMissing) { - const fallbackFirstRunValue = await this.application?.deviceInterface?.getRawStorageValue(FIRST_RUN_KEY) - firstRunKeyMissing = isNullOrUndefined(fallbackFirstRunValue) + const fallbackFirstRunValue = await this.application.getValue(FIRST_RUN_KEY, StorageValueModes.Nonwrapped) + firstRunKeyMissing = fallbackFirstRunValue == undefined } - return !hasNormalKeys && hasKeychainValue && firstRunKeyMissing + + return !hasAccountOrPasscode && hasKeychainValue && firstRunKeyMissing } /** diff --git a/packages/mobile/src/Lib/Interface.ts b/packages/mobile/src/Lib/Interface.ts index d8a46ea66..370da7490 100644 --- a/packages/mobile/src/Lib/Interface.ts +++ b/packages/mobile/src/Lib/Interface.ts @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-community/async-storage' +import SNReactNative from '@standardnotes/react-native-utils' import { ApplicationIdentifier, DeviceInterface, @@ -32,7 +33,7 @@ const showLoadFailForItemIds = (failedItemIds: string[]) => { let text = 'The following items could not be loaded. This may happen if you are in low-memory conditions, or if the note is very large in size. We recommend breaking up large notes into smaller chunks using the desktop or web app.\n\nItems:\n' let index = 0 - text += failedItemIds.map(id => { + text += failedItemIds.map((id) => { let result = id if (index !== failedItemIds.length - 1) { result += '\n' @@ -79,8 +80,8 @@ export class MobileDeviceInterface implements DeviceInterface { private async getAllDatabaseKeys(identifier: ApplicationIdentifier) { const keys = await AsyncStorage.getAllKeys() - const filtered = keys.filter(key => { - return key.includes(this.getDatabaseKeyPrefix(identifier)) + const filtered = keys.filter((key) => { + return key.startsWith(this.getDatabaseKeyPrefix(identifier)) }) return filtered } @@ -205,7 +206,7 @@ export class MobileDeviceInterface implements DeviceInterface { return } await Promise.all( - payloads.map(item => { + payloads.map((item) => { return AsyncStorage.setItem(this.keyForPayloadId(item.uuid, identifier), JSON.stringify(item)) }), ) @@ -294,7 +295,7 @@ export class MobileDeviceInterface implements DeviceInterface { } Linking.canOpenURL(url) - .then(supported => { + .then((supported) => { if (!supported) { showAlert() return @@ -306,15 +307,16 @@ export class MobileDeviceInterface implements DeviceInterface { } async clearAllDataFromDevice(_workspaceIdentifiers: string[]): Promise<{ killsApplication: boolean }> { - await this.clearRawKeychainValue() - await this.removeAllRawStorageValues() + await this.clearRawKeychainValue() + return { killsApplication: false } } - // eslint-disable-next-line @typescript-eslint/no-empty-function - performSoftReset() {} + performSoftReset() { + SNReactNative.exitApp() + } // eslint-disable-next-line @typescript-eslint/no-empty-function performHardReset() {} diff --git a/packages/mobile/src/Lib/SnjsHelperHooks.ts b/packages/mobile/src/Lib/SnjsHelperHooks.ts index 5b3aa8576..56ab85184 100644 --- a/packages/mobile/src/Lib/SnjsHelperHooks.ts +++ b/packages/mobile/src/Lib/SnjsHelperHooks.ts @@ -30,7 +30,7 @@ export const useSignedIn = (signedInCallback?: () => void, signedOutCallback?: ( } } void getSignedIn() - const removeSignedInObserver = application.addEventObserver(async event => { + const removeSignedInObserver = application.addEventObserver(async (event) => { if (event === ApplicationEvent.Launched) { void getSignedIn() } @@ -74,7 +74,7 @@ export const useOutOfSync = () => { }, [application]) React.useEffect(() => { - const removeSignedInObserver = application.addEventObserver(async event => { + const removeSignedInObserver = application.addEventObserver(async (event) => { if (event === ApplicationEvent.EnteredOutOfSync) { setOutOfSync(true) } else if (event === ApplicationEvent.ExitedOutOfSync) { @@ -103,7 +103,7 @@ export const useIsLocked = () => { useEffect(() => { let isMounted = true - const removeSignedInObserver = application?.getAppState().addLockStateChangeObserver(event => { + const removeSignedInObserver = application?.getAppState().addLockStateChangeObserver((event) => { if (isMounted) { if (event === LockStateType.Locked) { setIsLocked(true) @@ -131,7 +131,7 @@ export const useHasEditor = () => { const [hasEditor, setHasEditor] = React.useState(false) useEffect(() => { - const removeEditorObserver = application?.editorGroup.addActiveControllerChangeObserver(newEditor => { + const removeEditorObserver = application?.editorGroup.addActiveControllerChangeObserver((newEditor) => { setHasEditor(Boolean(newEditor)) }) return removeEditorObserver @@ -207,7 +207,7 @@ export const useSyncStatus = () => { }, [application, completedInitialSync, setStatus]) useEffect(() => { - const unsubscribeAppEvents = application?.addEventObserver(async eventName => { + const unsubscribeAppEvents = application?.addEventObserver(async (eventName) => { if (eventName === ApplicationEvent.LocalDataIncrementalLoad) { updateLocalDataStatus() } else if (eventName === ApplicationEvent.SyncStatusChanged || eventName === ApplicationEvent.FailedSync) { @@ -340,7 +340,7 @@ export const useProtectionSessionExpiry = () => { const [protectionsDisabledUntil, setProtectionsDisabledUntil] = React.useState(getProtectionsDisabledUntil()) useEffect(() => { - const removeProtectionLengthSubscriber = application?.addEventObserver(async event => { + const removeProtectionLengthSubscriber = application?.addEventObserver(async (event) => { if ([ApplicationEvent.UnprotectedSessionBegan, ApplicationEvent.UnprotectedSessionExpired].includes(event)) { setProtectionsDisabledUntil(getProtectionsDisabledUntil()) } @@ -389,7 +389,7 @@ export const useChangeNote = (note: SNNote | undefined, editor: NoteViewControll if (await canChangeNote()) { await application?.mutator.changeAndSaveItem( note!, - mutator => { + (mutator) => { const noteMutator = mutator as NoteMutator mutate(noteMutator) }, diff --git a/packages/mobile/src/ModalStack.tsx b/packages/mobile/src/ModalStack.tsx index da8557c1b..b9e2e2bd0 100644 --- a/packages/mobile/src/ModalStack.tsx +++ b/packages/mobile/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/packages/mobile/src/Screens/Authenticate/Authenticate.tsx b/packages/mobile/src/Screens/Authenticate/Authenticate.tsx index 838bd8c52..e77f6db6a 100644 --- a/packages/mobile/src/Screens/Authenticate/Authenticate.tsx +++ b/packages/mobile/src/Screens/Authenticate/Authenticate.tsx @@ -71,7 +71,7 @@ export const Authenticate = ({ const [passcodeKeyboardType, setPasscodeKeyboardType] = useState( PasscodeKeyboardType.Default, ) - const [singleValidation] = useState(() => !(challenge.prompts.filter(prompt => prompt.validates).length > 0)) + const [singleValidation] = useState(() => !(challenge.prompts.filter((prompt) => prompt.validates).length > 0)) const [showSwitchKeyboard, setShowSwitchKeyboard] = useState(false) const [{ challengeValues, challengeValueStates }, dispatch] = useReducer( @@ -217,7 +217,7 @@ export const Authenticate = ({ onValueChange(newChallengeValue) return validateChallengeValue(newChallengeValue) }) - .catch(error => { + .catch((error) => { FingerprintScanner.release() if (error.name === 'DeviceLocked') { onValueLocked(challengeValue) @@ -250,7 +250,7 @@ export const Authenticate = ({ onValueChange(newChallengeValue) return validateChallengeValue(newChallengeValue) }) - .catch(error_1 => { + .catch((error_1) => { onValueChange({ ...challengeValue, value: false }) FingerprintScanner.release() if (error_1.name !== 'SystemCancel') { @@ -340,7 +340,7 @@ export const Authenticate = ({ ) useEffect(() => { - const remove = application?.getAppState().addStateChangeObserver(state => { + const remove = application?.getAppState().addStateChangeObserver((state) => { if (state === AppStateType.ResumingFromBackground) { if (!isAuthenticating.current) { beginAuthenticatingForNextChallengeReason() @@ -440,7 +440,7 @@ export const Authenticate = ({ Keyboard.dismiss() const biometricChallengeValue = Object.values(challengeValues).find( - value => value.prompt.validation === ChallengeValidation.Biometric, + (value) => value.prompt.validation === ChallengeValidation.Biometric, ) const state = challengeValueStates[biometricChallengeValue?.prompt.id as number] if (state === AuthenticationValueStateType.Locked || state === AuthenticationValueStateType.Success) { @@ -506,8 +506,8 @@ export const Authenticate = ({ const readyToSubmit = useMemo( () => Object.values(challengeValues) - .map(challengeValue => challengeValue.value) - .filter(value => !value).length === 0, + .map((challengeValue) => challengeValue.value) + .filter((value) => !value).length === 0, [challengeValues], ) @@ -556,7 +556,7 @@ export const Authenticate = ({ key={Platform.OS === 'android' ? keyboardType : undefined} ref={Array.of(firstInputRef, secondInputRef, thirdInputRef, fourthInputRef)[index] as any} placeholder={challengeValue.prompt.placeholder} - onChangeText={text => { + onChangeText={(text) => { onValueChange({ ...challengeValue, value: text }) }} value={(challengeValue.value || '') as string} @@ -621,7 +621,7 @@ export const Authenticate = ({ } const isPending = useMemo( - () => Object.values(challengeValueStates).findIndex(state => state === AuthenticationValueStateType.Pending) >= 0, + () => Object.values(challengeValueStates).findIndex((state) => state === AuthenticationValueStateType.Pending) >= 0, [challengeValueStates], ) @@ -644,7 +644,7 @@ export const Authenticate = ({ return ( - {headerHeight => ( + {(headerHeight) => ( { + const onShouldStartLoadWithRequest: OnShouldStartLoadWithRequest = (request) => { log('Setting last iframe URL to', request.url) /** The first request can typically be 'about:blank', which we want to ignore */ if (!didLoadRootUrl.current) { diff --git a/packages/mobile/src/Screens/Compose/Compose.tsx b/packages/mobile/src/Screens/Compose/Compose.tsx index 2cafe4724..fd898c883 100644 --- a/packages/mobile/src/Screens/Compose/Compose.tsx +++ b/packages/mobile/src/Screens/Compose/Compose.tsx @@ -88,7 +88,7 @@ export class Compose extends React.Component c.item.uuid === noteUuid) as NoteViewController + const editor = this.context.editorGroup.itemControllers.find((c) => c.item.uuid === noteUuid) as NoteViewController if (!editor) { throw 'Unable to to find note controller' } @@ -149,7 +149,7 @@ export class Compose extends React.Component { + this.removeAppEventObserver = this.context?.addEventObserver(async (eventName) => { if (eventName === ApplicationEvent.CompletedFullSync) { /** if we're still dirty, don't change status, a sync is likely upcoming. */ if (!this.note.dirty && this.state.saveError) { @@ -169,7 +169,7 @@ export class Compose extends React.Component { + this.removeStateEventObserver = this.context?.getAppState().addStateEventObserver((state) => { if (state === AppStateEventType.DrawerOpen) { this.dismissKeyboard() /** @@ -352,7 +352,7 @@ export class Compose extends React.Component { + (mutator) => { const noteMutator = mutator as NoteMutator if (newTitle != null) { @@ -483,7 +483,7 @@ export class Compose extends React.Component - {theme => ( + {(theme) => ( <> {this.noteLocked && ( @@ -507,7 +507,7 @@ export class Compose extends React.Component )} - {themeService => ( + {(themeService) => ( <> -export const FileInputModal: FC = props => { +export const FileInputModal: FC = (props) => { const { file, renameFile } = props.route.params const themeService = useContext(ThemeServiceContext) const application = useSafeApplicationContext() diff --git a/packages/mobile/src/Screens/InputModal/TagInputModal.tsx b/packages/mobile/src/Screens/InputModal/TagInputModal.tsx index 57942d3d0..882c4ed5d 100644 --- a/packages/mobile/src/Screens/InputModal/TagInputModal.tsx +++ b/packages/mobile/src/Screens/InputModal/TagInputModal.tsx @@ -41,7 +41,7 @@ export const TagInputModal = (props: Props) => { const onSubmit = useCallback(async () => { if (props.route.params.tagUuid) { const tag = application?.items.findItem(props.route.params.tagUuid) as SNTag - await application?.mutator.changeItem(tag, mutator => { + await application?.mutator.changeItem(tag, (mutator) => { const tagMutator = mutator as TagMutator tagMutator.title = text if (props.route.params.noteUuid) { @@ -54,7 +54,7 @@ export const TagInputModal = (props: Props) => { } else { const tag = await application!.mutator.findOrCreateTag(text) if (props.route.params.noteUuid) { - await application?.mutator.changeItem(tag, mutator => { + await application?.mutator.changeItem(tag, (mutator) => { const tagMutator = mutator as TagMutator const note = application.items.findItem(props.route.params.noteUuid!) if (note) { diff --git a/packages/mobile/src/Screens/InputModal/WorkspaceInputModal.tsx b/packages/mobile/src/Screens/InputModal/WorkspaceInputModal.tsx new file mode 100644 index 000000000..096f2ebb5 --- /dev/null +++ b/packages/mobile/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/packages/mobile/src/Screens/ManageSessions/ManageSessions.tsx b/packages/mobile/src/Screens/ManageSessions/ManageSessions.tsx index b37aeb0ed..d7d4a2903 100644 --- a/packages/mobile/src/Screens/ManageSessions/ManageSessions.tsx +++ b/packages/mobile/src/Screens/ManageSessions/ManageSessions.tsx @@ -64,7 +64,7 @@ const useSessions = (): [ setErrorMessage('An unknown error occurred while revoking the session.') } } else { - setSessions(sessions.filter(session => session.uuid !== uuid)) + setSessions(sessions.filter((session) => session.uuid !== uuid)) } } @@ -136,7 +136,7 @@ export const ManageSessions: React.FC = () => { return ( - keyExtractor={item => item.uuid} + keyExtractor={(item) => item.uuid} contentContainerStyle={{ paddingBottom: insets.bottom }} initialNumToRender={7} windowSize={7} diff --git a/packages/mobile/src/Screens/ManageSessions/SessionCell.tsx b/packages/mobile/src/Screens/ManageSessions/SessionCell.tsx index cb69abba4..c47ff94c6 100644 --- a/packages/mobile/src/Screens/ManageSessions/SessionCell.tsx +++ b/packages/mobile/src/Screens/ManageSessions/SessionCell.tsx @@ -11,7 +11,7 @@ type Props = { currentSession: boolean } -const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ +const Container = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({ underlayColor: props.theme.stylekitBorderColor, }))` padding-top: ${12}px; @@ -21,7 +21,7 @@ const ButtonContainer = styled.View`` type ButtonLabelProps = Pick const ButtonLabel = styled.Text` - color: ${props => { + color: ${(props) => { let color = props.theme.stylekitForegroundColor if (props.disabled) { color = 'gray' @@ -29,7 +29,7 @@ const ButtonLabel = styled.Text` return color }}; font-weight: bold; - font-size: ${props => props.theme.mainTextFontSize}px; + font-size: ${(props) => props.theme.mainTextFontSize}px; ${({ disabled }) => disabled && css` @@ -46,7 +46,7 @@ export const SubTitleText = styled.Text<{ current: boolean }>` line-height: 21px; ` -export const SessionCell: React.FC = props => ( +export const SessionCell: React.FC = (props) => ( {props.title} diff --git a/packages/mobile/src/Screens/NoteHistory/NoteHistory.tsx b/packages/mobile/src/Screens/NoteHistory/NoteHistory.tsx index 4f75d80ef..d44aeedd5 100644 --- a/packages/mobile/src/Screens/NoteHistory/NoteHistory.tsx +++ b/packages/mobile/src/Screens/NoteHistory/NoteHistory.tsx @@ -61,9 +61,9 @@ export const NoteHistory = (props: Props) => { fontStyle={{ color: theme.stylekitForegroundColor, }} - values={routes.map(route => route.title)} + values={routes.map((route) => route.title)} selectedIndex={tabBarProps.navigationState.index} - onChange={event => { + onChange={(event) => { setIndex(event.nativeEvent.selectedSegmentIndex) }} /> diff --git a/packages/mobile/src/Screens/NoteHistory/NoteHistoryCell.tsx b/packages/mobile/src/Screens/NoteHistory/NoteHistoryCell.tsx index 14ce45850..1773f1b86 100644 --- a/packages/mobile/src/Screens/NoteHistory/NoteHistoryCell.tsx +++ b/packages/mobile/src/Screens/NoteHistory/NoteHistoryCell.tsx @@ -12,7 +12,7 @@ type Props = { subTitle?: string } -const Container = styled(SectionedTableCellTouchableHighlight).attrs(props => ({ +const Container = styled(SectionedTableCellTouchableHighlight).attrs((props) => ({ underlayColor: props.theme.stylekitBorderColor, }))` padding-top: ${12}px; @@ -22,7 +22,7 @@ const ButtonContainer = styled.View`` type ButtonLabelProps = Pick const ButtonLabel = styled.Text` - color: ${props => { + color: ${(props) => { let color = props.theme.stylekitForegroundColor if (props.disabled) { color = 'gray' @@ -30,7 +30,7 @@ const ButtonLabel = styled.Text` return color }}; font-weight: bold; - font-size: ${props => props.theme.mainTextFontSize}px; + font-size: ${(props) => props.theme.mainTextFontSize}px; ${({ disabled }) => disabled && css` @@ -45,7 +45,7 @@ export const SubTitleText = styled.Text` line-height: 21px; ` -export const NoteHistoryCell: React.FC = props => ( +export const NoteHistoryCell: React.FC = (props) => ( { + (mutator) => { mutator.setCustomContent(revision.payload.content) }, true, diff --git a/packages/mobile/src/Screens/NoteHistory/RemoteHistory.tsx b/packages/mobile/src/Screens/NoteHistory/RemoteHistory.tsx index 71ac0611e..429bf436a 100644 --- a/packages/mobile/src/Screens/NoteHistory/RemoteHistory.tsx +++ b/packages/mobile/src/Screens/NoteHistory/RemoteHistory.tsx @@ -70,7 +70,7 @@ export const RemoteHistory: React.FC = ({ note, onPress }) => { return ( - keyExtractor={item => item.uuid} + keyExtractor={(item) => item.uuid} contentContainerStyle={{ paddingBottom: insets.bottom }} initialNumToRender={10} windowSize={10} diff --git a/packages/mobile/src/Screens/NoteHistory/SessionHistory.tsx b/packages/mobile/src/Screens/NoteHistory/SessionHistory.tsx index 91c63a11e..9abab51ee 100644 --- a/packages/mobile/src/Screens/NoteHistory/SessionHistory.tsx +++ b/packages/mobile/src/Screens/NoteHistory/SessionHistory.tsx @@ -42,7 +42,7 @@ export const SessionHistory: React.FC = ({ note, onPress }) => { return ( - keyExtractor={item => item.previewTitle()} + keyExtractor={(item) => item.previewTitle()} contentContainerStyle={{ paddingBottom: insets.bottom }} initialNumToRender={10} windowSize={10} diff --git a/packages/mobile/src/Screens/Notes/NoteCell.styled.ts b/packages/mobile/src/Screens/Notes/NoteCell.styled.ts index 99b878345..ff598ce17 100644 --- a/packages/mobile/src/Screens/Notes/NoteCell.styled.ts +++ b/packages/mobile/src/Screens/Notes/NoteCell.styled.ts @@ -8,7 +8,7 @@ export const Container = styled.View<{ selected: boolean; distance: number }>` flex-direction: row; align-items: flex-start; justify-content: space-between; - padding: ${props => props.distance}px 0 0 ${props => props.distance}px; + padding: ${(props) => props.distance}px 0 0 ${(props) => props.distance}px; background-color: ${({ theme, selected }) => { return selected ? theme.stylekitInfoColor : theme.stylekitBackgroundColor }}; @@ -16,10 +16,10 @@ export const Container = styled.View<{ selected: boolean; distance: number }>` export const NoteDataContainer = styled.View<{ distance: number }>` border-bottom-color: ${({ theme }) => hexToRGBA(theme.stylekitBorderColor, 0.75)}; border-bottom-width: 1px; - padding-bottom: ${props => props.distance}px; + padding-bottom: ${(props) => props.distance}px; flex-grow: 1; flex-shrink: 1; - padding-right: ${props => props.distance}px; + padding-right: ${(props) => props.distance}px; ` export const DeletedText = styled.Text` color: ${({ theme }) => theme.stylekitInfoColor}; @@ -54,7 +54,7 @@ export const TagText = styled.Text<{ selected: boolean }>` color: ${({ theme, selected }) => { return selected ? theme.stylekitInfoContrastColor : theme.stylekitForegroundColor }}; - opacity: ${props => (props.selected ? 0.8 : 0.5)}; + opacity: ${(props) => (props.selected ? 0.8 : 0.5)}; ` export const DetailsText = styled(TagText)` margin-right: 0; diff --git a/packages/mobile/src/Screens/Notes/NoteCell.tsx b/packages/mobile/src/Screens/Notes/NoteCell.tsx index 6187ddb0c..6de1e5738 100644 --- a/packages/mobile/src/Screens/Notes/NoteCell.tsx +++ b/packages/mobile/src/Screens/Notes/NoteCell.tsx @@ -62,7 +62,7 @@ export const NoteCell = ({ await application?.mutator.deleteItem(note) }, () => { - void changeNote(mutator => { + void changeNote((mutator) => { mutator.trashed = true }, false) }, @@ -105,7 +105,7 @@ export const NoteCell = ({ text: note.pinned ? 'Unpin' : 'Pin', key: 'pin', callback: () => - changeNote(mutator => { + changeNote((mutator) => { mutator.pinned = !note.pinned }, false), }) @@ -123,7 +123,7 @@ export const NoteCell = ({ return } - void changeNote(mutator => { + void changeNote((mutator) => { mutator.archived = !note.archived }, false) }, @@ -133,7 +133,7 @@ export const NoteCell = ({ text: note.locked ? 'Enable editing' : 'Prevent editing', key: 'lock', callback: () => - changeNote(mutator => { + changeNote((mutator) => { mutator.locked = !note.locked }, false), }) @@ -157,7 +157,7 @@ export const NoteCell = ({ text: 'Restore', key: 'restore-note', callback: () => { - void changeNote(mutator => { + void changeNote((mutator) => { mutator.trashed = false }, false) }, diff --git a/packages/mobile/src/Screens/Notes/NoteCellFlags.tsx b/packages/mobile/src/Screens/Notes/NoteCellFlags.tsx index aae45e574..e69456559 100644 --- a/packages/mobile/src/Screens/Notes/NoteCellFlags.tsx +++ b/packages/mobile/src/Screens/Notes/NoteCellFlags.tsx @@ -44,7 +44,7 @@ export const NoteCellFlags = ({ note, highlight }: { note: SNNote; highlight: bo return flags.length > 0 ? ( - {flags.map(flag => ( + {flags.map((flag) => ( {flag.text} diff --git a/packages/mobile/src/Screens/Notes/NoteList.styled.ts b/packages/mobile/src/Screens/Notes/NoteList.styled.ts index cd70a0235..12efbf3ad 100644 --- a/packages/mobile/src/Screens/Notes/NoteList.styled.ts +++ b/packages/mobile/src/Screens/Notes/NoteList.styled.ts @@ -15,7 +15,7 @@ export const styles = StyleSheet.create({ }) export const Container = styled.View` - background-color: ${props => props.theme.stylekitBackgroundColor}; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; flex: 1; ` @@ -32,8 +32,8 @@ interface LoadingTextProps { export const LoadingText = styled.Text` position: absolute; opacity: 0.5; - color: ${props => props.theme.stylekitForegroundColor}; - text-align: ${props => props.textAlign ?? 'left'}; + color: ${(props) => props.theme.stylekitForegroundColor}; + text-align: ${(props) => props.textAlign ?? 'left'}; ` export const HeaderContainer = styled.View` @@ -43,7 +43,7 @@ export const HeaderContainer = styled.View` ` export const SearchBarContainer = styled.View` - background-color: ${props => props.theme.stylekitBackgroundColor}; + background-color: ${(props) => props.theme.stylekitBackgroundColor}; z-index: 2; ` diff --git a/packages/mobile/src/Screens/Notes/NoteList.tsx b/packages/mobile/src/Screens/Notes/NoteList.tsx index 88d899fae..93e0f6f6c 100644 --- a/packages/mobile/src/Screens/Notes/NoteList.tsx +++ b/packages/mobile/src/Screens/Notes/NoteList.tsx @@ -82,7 +82,7 @@ export const NoteList = (props: Props) => { }) useEffect(() => { - const unsubscribeStateEventObserver = application?.getAppState().addStateEventObserver(state => { + const unsubscribeStateEventObserver = application?.getAppState().addStateEventObserver((state) => { if (state === AppStateEventType.DrawerOpen) { dismissKeyboard() } @@ -99,7 +99,7 @@ export const NoteList = (props: Props) => { }, [noteListScrolled, props.notes]) useEffect(() => { - const unsubscribeTagChangedEventObserver = application?.getAppState().addStateChangeObserver(event => { + const unsubscribeTagChangedEventObserver = application?.getAppState().addStateChangeObserver((event) => { if (event === AppStateType.TagChanged) { scrollListToTop() } @@ -223,7 +223,7 @@ export const NoteList = (props: Props) => { item?.uuid} + keyExtractor={(item) => item?.uuid} contentContainerStyle={[{ paddingBottom: insets.bottom }, props.notes.length > 0 ? {} : { height: '100%' }]} initialNumToRender={6} windowSize={6} diff --git a/packages/mobile/src/Screens/Notes/Notes.tsx b/packages/mobile/src/Screens/Notes/Notes.tsx index e2744e456..e09da0121 100644 --- a/packages/mobile/src/Screens/Notes/Notes.tsx +++ b/packages/mobile/src/Screens/Notes/Notes.tsx @@ -83,7 +83,7 @@ export const Notes = React.memo( title = selectedTag.title if (selectedTag instanceof SNTag && selectedTag.parentId) { const parents = application.items.getTagParentChain(selectedTag) - const hierarchy = parents.map(tag => tag.title).join(' ⫽ ') + const hierarchy = parents.map((tag) => tag.title).join(' ⫽ ') subTitle = hierarchy.length > 0 ? `in ${hierarchy}` : undefined } } @@ -163,7 +163,7 @@ export const Notes = React.memo( useEffect(() => { let mounted = true - const removeEditorObserver = application.editorGroup.addActiveControllerChangeObserver(activeEditor => { + const removeEditorObserver = application.editorGroup.addActiveControllerChangeObserver((activeEditor) => { if (mounted) { setSelectedNoteId(activeEditor?.item?.uuid) } @@ -305,7 +305,7 @@ export const Notes = React.memo( toggleIncludeTrashed, ]) - const getFirstSelectableNote = useCallback((newNotes: SNNote[]) => newNotes.find(note => !note.protected), []) + const getFirstSelectableNote = useCallback((newNotes: SNNote[]) => newNotes.find((note) => !note.protected), []) const selectFirstNote = useCallback( (newNotes: SNNote[]) => { @@ -475,7 +475,7 @@ export const Notes = React.memo( ) useEffect(() => { - const removeAppStateChangeHandler = application.getAppState().addStateChangeObserver(state => { + const removeAppStateChangeHandler = application.getAppState().addStateChangeObserver((state) => { if (state === AppStateType.TagChanged) { reloadNotesDisplayOptions() reloadNotes(true, true) diff --git a/packages/mobile/src/Screens/Notes/OfflineBanner.styled.ts b/packages/mobile/src/Screens/Notes/OfflineBanner.styled.ts index 56185abae..6c7dc921e 100644 --- a/packages/mobile/src/Screens/Notes/OfflineBanner.styled.ts +++ b/packages/mobile/src/Screens/Notes/OfflineBanner.styled.ts @@ -11,17 +11,17 @@ const Container = styled.View` padding: ${PADDING}px; border-width: 1px; border-radius: 4px; - border-color: ${props => props.theme.stylekitBorderColor}; + border-color: ${(props) => props.theme.stylekitBorderColor}; ` const CenterContainer = styled.View` justify-content: center; ` const UserIcon = styled(Icon)` font-size: 24px; - color: ${props => props.theme.stylekitInfoColor}; + color: ${(props) => props.theme.stylekitInfoColor}; ` const ForwardIcon = styled(UserIcon)` - color: ${props => props.theme.stylekitNeutralColor}; + color: ${(props) => props.theme.stylekitNeutralColor}; ` const TextContainer = styled.View` flex: 1; @@ -30,12 +30,12 @@ const TextContainer = styled.View` const BoldText = styled.Text` font-size: 15px; font-weight: 600; - color: ${props => props.theme.stylekitForegroundColor}; + color: ${(props) => props.theme.stylekitForegroundColor}; ` const SubText = styled.Text` margin-top: 2px; font-size: 11px; - color: ${props => props.theme.stylekitNeutralColor}; + color: ${(props) => props.theme.stylekitNeutralColor}; ` export { Touchable, Container, CenterContainer, UserIcon, ForwardIcon, TextContainer, BoldText, SubText } diff --git a/packages/mobile/src/Screens/Root.tsx b/packages/mobile/src/Screens/Root.tsx index 9925d919b..3de8d6298 100644 --- a/packages/mobile/src/Screens/Root.tsx +++ b/packages/mobile/src/Screens/Root.tsx @@ -26,7 +26,7 @@ export const Root = () => { const [keyboardHeight, setKeyboardHeight] = useState(undefined) useEffect(() => { - const removeStateObserver = application?.getAppState().addStateChangeObserver(state => { + const removeStateObserver = application?.getAppState().addStateChangeObserver((state) => { if (state === AppStateType.GainingFocus) { void application.sync.sync() } @@ -50,7 +50,7 @@ export const Root = () => { } } }) - const removeNoteObserver = application?.editorGroup.addActiveControllerChangeObserver(activeController => { + const removeNoteObserver = application?.editorGroup.addActiveControllerChangeObserver((activeController) => { if (activeController instanceof NoteViewController) { setActiveNoteView(activeController) } else { @@ -100,7 +100,7 @@ export const Root = () => { } const toggleNoteList = () => { - setNoteListCollapsed(value => !value) + setNoteListCollapsed((value) => !value) } const collapseIconBottomPosition = (keyboardHeight ?? 0) > (height ?? 0) / 2 ? (keyboardHeight ?? 0) + 40 : '50%' diff --git a/packages/mobile/src/Screens/Settings/Sections/OptionsSection.tsx b/packages/mobile/src/Screens/Settings/Sections/OptionsSection.tsx index a94f5f5b7..4cf618580 100644 --- a/packages/mobile/src/Screens/Settings/Sections/OptionsSection.tsx +++ b/packages/mobile/src/Screens/Settings/Sections/OptionsSection.tsx @@ -1,16 +1,16 @@ import { useSignedIn } from '@Lib/SnjsHelperHooks' import { useNavigation } from '@react-navigation/native' -import { ApplicationContext } from '@Root/ApplicationContext' import { ButtonCell } from '@Root/Components/ButtonCell' import { SectionedAccessoryTableCell } from '@Root/Components/SectionedAccessoryTableCell' import { SectionedOptionsTableCell } from '@Root/Components/SectionedOptionsTableCell' import { SectionHeader } from '@Root/Components/SectionHeader' import { TableSection } from '@Root/Components/TableSection' +import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext' import { ModalStackNavigationProp } from '@Root/ModalStack' import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens' import { ButtonType, PrefKey } from '@standardnotes/snjs' import moment from 'moment' -import React, { useCallback, useContext, useMemo, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { Platform } from 'react-native' import DocumentPicker from 'react-native-document-picker' import RNFS from 'react-native-fs' @@ -22,7 +22,8 @@ type Props = { export const OptionsSection = ({ title, encryptionAvailable }: Props) => { // Context - const application = useContext(ApplicationContext) + const application = useSafeApplicationContext() + const [signedIn] = useSignedIn() const navigation = useNavigation['navigation']>() @@ -57,7 +58,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { const email = useMemo(() => { if (signedIn) { - const user = application?.getUser() + const user = application.getUser() return user?.email } return @@ -76,25 +77,25 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { const destroyLocalData = async () => { if ( - await application?.alertService?.confirm( + await application.alertService.confirm( 'Signing out will remove all data from this device, including notes and tags. Make sure your data is synced before proceeding.', 'Sign Out?', 'Sign Out', ButtonType.Danger, ) ) { - await application!.user.signOut() + await application.user.signOut() } } const exportData = useCallback( async (encrypted: boolean) => { setExporting(true) - const result = await application?.getBackupsService().export(encrypted) + const result = await application.getBackupsService().export(encrypted) if (result) { const exportDate = new Date() setLastExportDate(exportDate) - void application?.getLocalPreferences().setUserPrefValue(PrefKey.MobileLastExportDate, exportDate) + void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileLastExportDate, exportDate) } setExporting(false) }, @@ -103,25 +104,25 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { const readImportFile = async (fileUri: string): Promise => { return RNFS.readFile(fileUri) - .then(result => JSON.parse(result)) + .then((result) => JSON.parse(result)) .catch(() => { - void application!.alertService!.alert('Unable to open file. Ensure it is a proper JSON file and try again.') + void application.alertService.alert('Unable to open file. Ensure it is a proper JSON file and try again.') }) } const performImport = async (data: any) => { - const result = await application!.mutator.importData(data) + const result = await application.mutator.importData(data) if (!result) { return } else if ('error' in result) { - void application!.alertService!.alert(result.error.text) + void application.alertService.alert(result.error.text) } else if (result.errorCount) { - void application!.alertService!.alert( + void application.alertService.alert( `Import complete. ${result.errorCount} items were not imported because ` + 'there was an error decrypting them. Make sure the password is correct and try again.', ) } else { - void application!.alertService!.alert('Your data has been successfully imported.') + void application.alertService.alert('Your data has been successfully imported.') } } @@ -139,10 +140,10 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { setImporting(true) if (data.version || data.auth_params || data.keyParams) { const version = data.version || data.keyParams?.version || data.auth_params?.version - if (application!.protocolService.supportedVersions().includes(version)) { + if (application.protocolService.supportedVersions().includes(version)) { await performImport(data) } else { - void application!.alertService.alert( + void application.alertService.alert( 'This backup file was created using an unsupported version of the application ' + 'and cannot be imported here. Please update your application and try again.', ) @@ -159,7 +160,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { async (option: { key: string }) => { const encrypted = option.key === 'encrypted' if (encrypted && !encryptionAvailable) { - void application?.alertService!.alert( + void application.alertService.alert( 'You must be signed in, or have a local passcode set, to generate an encrypted export file.', 'Not Available', 'OK', @@ -176,12 +177,12 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { }, [navigation]) const showDataBackupAlert = useCallback(() => { - void application?.alertService.alert( + void application.alertService.alert( 'Because you are using the app offline without a sync account, it is your responsibility to keep your data safe and backed up. It is recommended you export a backup of your data at least once a week, or, to sign up for a sync account so that your data is backed up automatically.', 'No Backups Created', 'OK', ) - }, [application?.alertService]) + }, [application.alertService]) return ( @@ -192,7 +193,7 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => { diff --git a/packages/mobile/src/Screens/Settings/Sections/PreferencesSection.tsx b/packages/mobile/src/Screens/Settings/Sections/PreferencesSection.tsx index f235a76ac..a3bf22ea7 100644 --- a/packages/mobile/src/Screens/Settings/Sections/PreferencesSection.tsx +++ b/packages/mobile/src/Screens/Settings/Sections/PreferencesSection.tsx @@ -36,7 +36,7 @@ export const PreferencesSection = () => { const toggleReverseSort = () => { void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileSortNotesReverse, !sortReverse) - setSortReverse(value => !value) + setSortReverse((value) => !value) } const changeSortOption = (key: CollectionSortProperty) => { @@ -45,15 +45,15 @@ export const PreferencesSection = () => { } const toggleNotesPreviewHidden = () => { void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideNotePreview, !hidePreviews) - setHidePreviews(value => !value) + setHidePreviews((value) => !value) } const toggleNotesDateHidden = () => { void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideDate, !hideDates) - setHideDates(value => !value) + setHideDates((value) => !value) } const toggleNotesEditorIconHidden = () => { void application.getLocalPreferences().setUserPrefValue(PrefKey.MobileNotesHideEditorIcon, !hideEditorIcon) - setHideEditorIcon(value => !value) + setHideEditorIcon((value) => !value) } return ( diff --git a/packages/mobile/src/Screens/Settings/Sections/WorkspacesSection.tsx b/packages/mobile/src/Screens/Settings/Sections/WorkspacesSection.tsx new file mode 100644 index 000000000..c97ce20f7 --- /dev/null +++ b/packages/mobile/src/Screens/Settings/Sections/WorkspacesSection.tsx @@ -0,0 +1,199 @@ +import { SectionedAccessoryTableCell } from '@Components/SectionedAccessoryTableCell' +import { SectionHeader } from '@Components/SectionHeader' +import { useNavigation } from '@react-navigation/native' +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_INPUT_MODAL_WORKSPACE_NAME, SCREEN_SETTINGS } from '@Screens/screens' +import { ApplicationDescriptor, ApplicationGroupEvent, ButtonType } from '@standardnotes/snjs' +import { CustomActionSheetOption, useCustomActionSheet } from '@Style/CustomActionSheet' +import React, { useCallback, useEffect, useState } from 'react' + +export const WorkspacesSection = () => { + const application = useSafeApplicationContext() + const appGroup = useSafeApplicationGroupContext() + const navigation = useNavigation['navigation']>() + + const [applicationDescriptors, setApplicationDescriptors] = useState([]) + + enum WorkspaceAction { + AddAnother = 'Add another workspace', + Activate = 'Activate', + Rename = 'Rename', + Remove = 'Remove', + SignOutAll = 'Sign out all workspaces', + } + + useEffect(() => { + let descriptors = appGroup.getDescriptors() + setApplicationDescriptors(descriptors) + + const removeAppGroupObserver = appGroup.addEventObserver((event) => { + if (event === ApplicationGroupEvent.DescriptorsDataChanged) { + descriptors = appGroup.getDescriptors() + setApplicationDescriptors(descriptors) + } + }) + + return () => { + removeAppGroupObserver() + } + }, [appGroup]) + + const { showActionSheet } = useCustomActionSheet() + + const getWorkspaceActionConfirmation = useCallback( + async (action: WorkspaceAction): Promise => { + const { Info, Danger } = ButtonType + const { AddAnother, Activate, Remove, SignOutAll } = WorkspaceAction + let message = '' + let buttonText = '' + let buttonType = Info + + switch (action) { + case Activate: + message = 'Your workspace will be ready for you when you come back.' + buttonText = 'Quit App' + break + case AddAnother: + message = 'Your new workspace will be ready for you when you come back.' + buttonText = 'Quit App' + break + case SignOutAll: + message = + 'Are you sure you want to sign out of all workspaces on this device? This action will restart the application.' + buttonText = 'Sign Out All' + break + case Remove: + message = + 'This action will remove this workspace and its related data from this device. Your synced data will not be affected.' + buttonText = 'Delete Workspace' + buttonType = Danger + break + default: + break + } + return application.alertService.confirm(message, undefined, buttonText, buttonType) + }, + [WorkspaceAction, application.alertService], + ) + + const renameWorkspace = useCallback( + async (descriptor: ApplicationDescriptor, newName: string) => { + appGroup.renameDescriptor(descriptor, newName) + }, + [appGroup], + ) + + const signOutWorkspace = useCallback(async () => { + const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.Remove) + + if (!confirmed) { + return + } + + try { + await application.user.signOut() + } catch (error) { + console.error(error) + } + }, [WorkspaceAction.Remove, application.user, getWorkspaceActionConfirmation]) + + const openWorkspace = useCallback( + async (descriptor: ApplicationDescriptor) => { + const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.Activate) + if (!confirmed) { + return + } + + await appGroup.unloadCurrentAndActivateDescriptor(descriptor) + }, + [WorkspaceAction.Activate, appGroup, getWorkspaceActionConfirmation], + ) + + const getSingleWorkspaceItemOptions = useCallback( + (descriptor: ApplicationDescriptor) => { + const { Activate, Rename, Remove } = WorkspaceAction + const worskspaceItemOptions: CustomActionSheetOption[] = [] + + if (descriptor.primary) { + worskspaceItemOptions.push( + { + text: Rename, + callback: () => { + navigation.navigate(SCREEN_INPUT_MODAL_WORKSPACE_NAME, { + descriptor, + renameWorkspace, + }) + }, + }, + { + text: Remove, + destructive: true, + callback: signOutWorkspace, + }, + ) + } else { + worskspaceItemOptions.push({ + text: Activate, + callback: () => openWorkspace(descriptor), + }) + } + + return worskspaceItemOptions + }, + [WorkspaceAction, navigation, openWorkspace, renameWorkspace, signOutWorkspace], + ) + + const addAnotherWorkspace = useCallback(async () => { + const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.AddAnother) + if (!confirmed) { + return + } + + await appGroup.unloadCurrentAndCreateNewDescriptor() + }, [WorkspaceAction.AddAnother, appGroup, applicationDescriptors, getWorkspaceActionConfirmation]) + + const signOutAllWorkspaces = useCallback(async () => { + try { + const confirmed = await getWorkspaceActionConfirmation(WorkspaceAction.SignOutAll) + if (!confirmed) { + return + } + await appGroup.signOutAllWorkspaces() + } catch (error) { + console.error(error) + } + }, [WorkspaceAction.SignOutAll, appGroup, getWorkspaceActionConfirmation]) + + return ( + + + {applicationDescriptors.map((descriptor, index) => { + return ( + { + const singleItemOptions = getSingleWorkspaceItemOptions(descriptor) + + showActionSheet({ + title: '', + options: singleItemOptions, + }) + }} + key={descriptor.identifier} + text={descriptor.label} + first={index === 0} + selected={() => descriptor.primary} + /> + ) + })} + + + + ) +} diff --git a/packages/mobile/src/Screens/Settings/Settings.tsx b/packages/mobile/src/Screens/Settings/Settings.tsx index f55a218be..15ad7850d 100644 --- a/packages/mobile/src/Screens/Settings/Settings.tsx +++ b/packages/mobile/src/Screens/Settings/Settings.tsx @@ -3,6 +3,7 @@ import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext import { ModalStackNavigationProp } from '@Root/ModalStack' import { SCREEN_SETTINGS } from '@Root/Screens/screens' import { FilesSection } from '@Screens/Settings/Sections/FilesSection' +import { WorkspacesSection } from '@Screens/Settings/Sections/WorkspacesSection' import { FeatureIdentifier } from '@standardnotes/features' import { ApplicationEvent, FeatureStatus } from '@standardnotes/snjs' import React, { useCallback, useEffect, useState } from 'react' @@ -30,7 +31,7 @@ export const Settings = (props: Props) => { }, [application]) useEffect(() => { - const removeApplicationEventSubscriber = application.addEventObserver(async event => { + const removeApplicationEventSubscriber = application.addEventObserver(async (event) => { if (event === ApplicationEvent.KeyStatusChanged) { setHasPasscode(Boolean(application.hasPasscode())) updateProtectionsAvailable() @@ -54,6 +55,7 @@ export const Settings = (props: Props) => { + {application.hasAccount() && isEntitledToFiles && } = ({ note }) => { return ( - {attachedFiles.sort(filesService.sortByName).map(file => { + {attachedFiles.sort(filesService.sortByName).map((file) => { const iconType = application.iconsController.getIconForFileType(file.mimeType) return ( diff --git a/packages/mobile/src/Screens/SideMenu/Listed.tsx b/packages/mobile/src/Screens/SideMenu/Listed.tsx index 6a7e2dfc5..32621282b 100644 --- a/packages/mobile/src/Screens/SideMenu/Listed.tsx +++ b/packages/mobile/src/Screens/SideMenu/Listed.tsx @@ -102,7 +102,7 @@ export const Listed: FC = ({ note }) => { showActionSheet({ title: item.display_name, - options: item.actions.map(action => ({ + options: item.actions.map((action) => ({ text: (action as Action).label, callback: async () => { setIsActionInProgress(true) diff --git a/packages/mobile/src/Screens/SideMenu/MainSideMenu.tsx b/packages/mobile/src/Screens/SideMenu/MainSideMenu.tsx index 03e9e4e49..95c3c9506 100644 --- a/packages/mobile/src/Screens/SideMenu/MainSideMenu.tsx +++ b/packages/mobile/src/Screens/SideMenu/MainSideMenu.tsx @@ -36,7 +36,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => { const styles = useStyles(theme) useEffect(() => { - const removeTagChangeObserver = application!.getAppState().addStateChangeObserver(state => { + const removeTagChangeObserver = application!.getAppState().addStateChangeObserver((state) => { if (state === AppStateType.TagChanged) { setSelectedTag(application!.getAppState().getSelectedTag()) } @@ -160,7 +160,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => { const themeOptions = useMemo(() => { const options: SideMenuOption[] = themeService! .systemThemes() - .map(systemTheme => ({ + .map((systemTheme) => ({ text: systemTheme?.name, key: systemTheme?.uuid, iconDesc: iconDescriptorForTheme(systemTheme), @@ -172,7 +172,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => { .concat( themes .sort((a, b) => a.name.localeCompare(b.name)) - .map(mapTheme => ({ + .map((mapTheme) => ({ text: mapTheme.name, key: mapTheme.uuid, iconDesc: iconDescriptorForTheme(mapTheme), @@ -207,7 +207,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => { const onTagSelect = useCallback( async (tag: SNTag | SmartView) => { if (tag.conflictOf) { - void application!.mutator.changeAndSaveItem(tag, mutator => { + void application!.mutator.changeAndSaveItem(tag, (mutator) => { mutator.conflictOf = undefined }) } @@ -251,7 +251,7 @@ export const MainSideMenu = React.memo(({ drawerRef }: Props) => { ({ + data={['themes-section', 'views-section', 'tags-section'].map((key) => ({ key, themeOptions, onTagSelect, diff --git a/packages/mobile/src/Screens/SideMenu/NoteSideMenu.tsx b/packages/mobile/src/Screens/SideMenu/NoteSideMenu.tsx index 701035723..499f655d9 100644 --- a/packages/mobile/src/Screens/SideMenu/NoteSideMenu.tsx +++ b/packages/mobile/src/Screens/SideMenu/NoteSideMenu.tsx @@ -122,7 +122,7 @@ export const NoteSideMenu = React.memo((props: Props) => { } }, () => { - void changeNote(mutator => { + void changeNote((mutator) => { mutator.trashed = true }, false) props.drawerRef?.closeDrawer() @@ -225,7 +225,7 @@ export const NoteSideMenu = React.memo((props: Props) => { const disassociateComponentWithCurrentNote = useCallback( async (component: SNComponent) => { if (note) { - return application.mutator.changeItem(component, m => { + return application.mutator.changeItem(component, (m) => { const mutator = m as ComponentMutator mutator.removeAssociatedItemId(note.uuid) mutator.disassociateWithItem(note.uuid) @@ -256,7 +256,7 @@ export const NoteSideMenu = React.memo((props: Props) => { if (!note?.prefersPlainEditor) { await application.mutator.changeItem( note, - mutator => { + (mutator) => { const noteMutator = mutator as NoteMutator noteMutator.prefersPlainEditor = true }, @@ -275,7 +275,7 @@ export const NoteSideMenu = React.memo((props: Props) => { if (prefersPlain) { await application.mutator.changeItem( note, - mutator => { + (mutator) => { const noteMutator = mutator as NoteMutator noteMutator.prefersPlainEditor = false }, @@ -294,7 +294,7 @@ export const NoteSideMenu = React.memo((props: Props) => { async (component?: SNComponent) => { const currentDefault = application.componentManager .componentsForArea(ComponentArea.Editor) - .filter(e => e.isMobileDefault)[0] + .filter((e) => e.isMobileDefault)[0] let isDefault = false if (!component) { @@ -314,14 +314,14 @@ export const NoteSideMenu = React.memo((props: Props) => { const setAsDefault = () => { if (currentDefault) { - void application.mutator.changeItem(currentDefault, m => { + void application.mutator.changeItem(currentDefault, (m) => { const mutator = m as ComponentMutator mutator.isMobileDefault = false }) } if (component) { - void application.mutator.changeAndSaveItem(component, m => { + void application.mutator.changeAndSaveItem(component, (m) => { const mutator = m as ComponentMutator mutator.isMobileDefault = true }) @@ -329,7 +329,7 @@ export const NoteSideMenu = React.memo((props: Props) => { } const removeAsDefault = () => { - void application.mutator.changeItem(currentDefault, m => { + void application.mutator.changeItem(currentDefault, (m) => { const mutator = m as ComponentMutator mutator.isMobileDefault = false }) @@ -376,7 +376,7 @@ export const NoteSideMenu = React.memo((props: Props) => { }, }, ] - components.map(component => { + components.map((component) => { options.push({ text: FindNativeFeature(component.identifier)?.name || component.name, subtext: component.isMobileDefault ? 'Mobile Default' : undefined, @@ -435,7 +435,7 @@ export const NoteSideMenu = React.memo((props: Props) => { const pinOption = note.pinned ? 'Unpin' : 'Pin' const pinEvent = () => - changeNote(mutator => { + changeNote((mutator) => { mutator.pinned = !note.pinned }, false) @@ -447,7 +447,7 @@ export const NoteSideMenu = React.memo((props: Props) => { ) return } - void changeNote(mutator => { + void changeNote((mutator) => { mutator.archived = !note.archived }, false) leaveEditor() @@ -455,7 +455,7 @@ export const NoteSideMenu = React.memo((props: Props) => { const lockOption = note.locked ? 'Enable editing' : 'Prevent editing' const lockEvent = () => - changeNote(mutator => { + changeNote((mutator) => { mutator.locked = !note.locked }, false) @@ -506,7 +506,7 @@ export const NoteSideMenu = React.memo((props: Props) => { }) } - let options: SideMenuOption[] = rawOptions.map(rawOption => ({ + let options: SideMenuOption[] = rawOptions.map((rawOption) => ({ text: rawOption.text, key: rawOption.icon, iconDesc: { @@ -523,7 +523,7 @@ export const NoteSideMenu = React.memo((props: Props) => { text: 'Restore', key: 'restore-note', onSelect: () => { - void changeNote(mutator => { + void changeNote((mutator) => { mutator.trashed = false }, false) }, @@ -574,11 +574,11 @@ export const NoteSideMenu = React.memo((props: Props) => { const onTagSelect = useCallback( async (tag: SNTag | SmartView, addTagHierachy: boolean) => { - const isSelected = selectedTags.findIndex(selectedTag => selectedTag.uuid === tag.uuid) > -1 + const isSelected = selectedTags.findIndex((selectedTag) => selectedTag.uuid === tag.uuid) > -1 if (note) { if (isSelected) { - await application.mutator.changeItem(tag, mutator => { + await application.mutator.changeItem(tag, (mutator) => { mutator.removeItemAsRelationship(note) }) } else { @@ -607,7 +607,7 @@ export const NoteSideMenu = React.memo((props: Props) => { ({ + data={Object.values(MenuSections).map((key) => ({ key, noteOptions, editorComponents: editors, @@ -650,7 +650,7 @@ export const NoteSideMenu = React.memo((props: Props) => { item.onTagSelect(tag, shouldAddTagHierarchy)} + onTagSelect={(tag) => item.onTagSelect(tag, shouldAddTagHierarchy)} selectedTags={item.selectedTags} emptyPlaceholder={'Create a new tag using the tag button in the bottom right corner.'} /> diff --git a/packages/mobile/src/Screens/SideMenu/SideMenuCell.styled.ts b/packages/mobile/src/Screens/SideMenu/SideMenuCell.styled.ts index ba37e23e7..d308d1936 100644 --- a/packages/mobile/src/Screens/SideMenu/SideMenuCell.styled.ts +++ b/packages/mobile/src/Screens/SideMenu/SideMenuCell.styled.ts @@ -2,7 +2,7 @@ import { Platform } from 'react-native' import styled, { css } from 'styled-components/native' export const Touchable = styled.TouchableOpacity<{ isSubtext: boolean }>` - min-height: ${props => (props.isSubtext ? 52 : 42)}px; + min-height: ${(props) => (props.isSubtext ? 52 : 42)}px; ` export const CellContent = styled.View<{ iconSide: 'right' | 'left' | null @@ -32,7 +32,7 @@ export const TextContainer = styled.View<{ isSubtext: boolean selected?: boolean }>` - min-height: ${props => (props.isSubtext ? 38 : 24)}px; + min-height: ${(props) => (props.isSubtext ? 38 : 24)}px; margin-left: 6px; flex-shrink: 1; ${({ selected, theme }) => diff --git a/packages/mobile/src/Screens/SideMenu/SideMenuCell.tsx b/packages/mobile/src/Screens/SideMenu/SideMenuCell.tsx index 277e206eb..d047fa0a9 100644 --- a/packages/mobile/src/Screens/SideMenu/SideMenuCell.tsx +++ b/packages/mobile/src/Screens/SideMenu/SideMenuCell.tsx @@ -46,7 +46,7 @@ const renderIcon = (desc: SideMenuOption['iconDesc'], color: string) => { return * } -export const SideMenuCell: React.FC = props => { +export const SideMenuCell: React.FC = (props) => { const theme = useContext(ThemeContext) const colorForTextClass = (textClass: SideMenuOption['textClass']) => { if (!textClass) { diff --git a/packages/mobile/src/Screens/SideMenu/SideMenuHero.tsx b/packages/mobile/src/Screens/SideMenu/SideMenuHero.tsx index 2b68bae29..dea801c00 100644 --- a/packages/mobile/src/Screens/SideMenu/SideMenuHero.tsx +++ b/packages/mobile/src/Screens/SideMenu/SideMenuHero.tsx @@ -13,7 +13,7 @@ type Props = { testID: ViewProps['testID'] } -export const SideMenuHero: React.FC = props => { +export const SideMenuHero: React.FC = (props) => { // Context const application = useContext(ApplicationContext) const theme = useContext(ThemeContext) @@ -26,7 +26,7 @@ export const SideMenuHero: React.FC = props => { useEffect(() => { const observedContentTypes = [ContentType.Note, ContentType.Tag] - const removeStreamItems = application?.streamItems(observedContentTypes, _items => { + const removeStreamItems = application?.streamItems(observedContentTypes, (_items) => { const notesAndTagsCount = application?.items.getItems(observedContentTypes).length ?? 0 if (notesAndTagsCount !== itemsCount) { diff --git a/packages/mobile/src/Screens/SideMenu/SideMenuSection.styled.ts b/packages/mobile/src/Screens/SideMenu/SideMenuSection.styled.ts index 0219a7e08..92fd7c18d 100644 --- a/packages/mobile/src/Screens/SideMenu/SideMenuSection.styled.ts +++ b/packages/mobile/src/Screens/SideMenu/SideMenuSection.styled.ts @@ -4,7 +4,7 @@ export const Root = styled.View` padding-bottom: 6px; ` export const Header = styled.TouchableOpacity<{ collapsed: boolean }>` - height: ${props => (props.collapsed ? 50 : 22)}px; + height: ${(props) => (props.collapsed ? 50 : 22)}px; ` export const Title = styled.Text` color: ${({ theme }) => theme.stylekitInfoColor}; diff --git a/packages/mobile/src/Screens/SideMenu/SideMenuSection.tsx b/packages/mobile/src/Screens/SideMenu/SideMenuSection.tsx index 21df2d6f5..d849171b1 100644 --- a/packages/mobile/src/Screens/SideMenu/SideMenuSection.tsx +++ b/packages/mobile/src/Screens/SideMenu/SideMenuSection.tsx @@ -39,7 +39,7 @@ type Props = { options?: SideMenuOption[] } -export const SideMenuSection: React.FC = React.memo(props => { +export const SideMenuSection: React.FC = React.memo((props) => { const [collapsed, setCollapsed] = useState(Boolean(props.collapsed)) const options = useMemo(() => { return props.options || [] @@ -58,7 +58,7 @@ export const SideMenuSection: React.FC = React.memo(props => { {!collapsed && ( <> - {options.map(option => { + {options.map((option) => { return ( tag.uuid) + const rawChildren = application.items.getTagChildren(item).map((tag) => tag.uuid) children = (tags as SNTag[]).filter((tag: SNTag) => rawChildren.includes(tag.uuid)) } @@ -130,7 +130,7 @@ export const TagSelectionList = React.memo( windowSize={10} maxToRenderPerBatch={10} data={children} - keyExtractor={childTag => childTag.uuid} + keyExtractor={(childTag) => childTag.uuid} renderItem={renderItem} /> )} @@ -147,7 +147,7 @@ export const TagSelectionList = React.memo( windowSize={10} maxToRenderPerBatch={10} data={renderedTags as SNTag[]} - keyExtractor={item => item.uuid} + keyExtractor={(item) => item.uuid} renderItem={renderItem} /> {tags.length === 0 && {emptyPlaceholder}} diff --git a/packages/mobile/src/Screens/UploadedFilesList/UploadedFilesList.tsx b/packages/mobile/src/Screens/UploadedFilesList/UploadedFilesList.tsx index 6d691e9cf..560938595 100644 --- a/packages/mobile/src/Screens/UploadedFilesList/UploadedFilesList.tsx +++ b/packages/mobile/src/Screens/UploadedFilesList/UploadedFilesList.tsx @@ -29,7 +29,7 @@ export enum Tabs { type Props = ModalStackNavigationProp -export const UploadedFilesList: FC = props => { +export const UploadedFilesList: FC = (props) => { const { AttachedFiles, AllFiles } = Tabs const { note } = props.route.params @@ -54,7 +54,7 @@ export const UploadedFilesList: FC = props => { const filteredList = useMemo(() => { return searchString - ? filesList.filter(file => file.name.toLowerCase().includes(searchString.toLowerCase())) + ? filesList.filter((file) => file.name.toLowerCase().includes(searchString.toLowerCase())) : filesList }, [filesList, searchString]) @@ -128,7 +128,7 @@ export const UploadedFilesList: FC = props => { ref={filesListRef} data={filteredList} renderItem={renderItem} - keyExtractor={item => item.uuid} + keyExtractor={(item) => item.uuid} onScroll={onScroll} /> ) : ( diff --git a/packages/mobile/src/Screens/screens.ts b/packages/mobile/src/Screens/screens.ts index 3f74c8ea2..27320e524 100644 --- a/packages/mobile/src/Screens/screens.ts +++ b/packages/mobile/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 diff --git a/packages/mobile/src/Style/CssParser.ts b/packages/mobile/src/Style/CssParser.ts index 744845d53..442bf7595 100644 --- a/packages/mobile/src/Style/CssParser.ts +++ b/packages/mobile/src/Style/CssParser.ts @@ -3,7 +3,7 @@ const PREFIX_STANDARD_NOTES = '--sn-stylekit' const PREFIX_STANDARD_NOTES_BURN = '--sn-' function camelCaseToDashed(camel: string) { - return camel.replace(/[A-Z]/g, m => '-' + m.toLowerCase()) + return camel.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()) } export function objectToCss(object: any) { diff --git a/packages/mobile/src/Style/CustomActionSheet.ts b/packages/mobile/src/Style/CustomActionSheet.ts index 7d9c4e0bf..ee99d0fd4 100644 --- a/packages/mobile/src/Style/CustomActionSheet.ts +++ b/packages/mobile/src/Style/CustomActionSheet.ts @@ -49,12 +49,12 @@ export const useCustomActionSheet = () => { }, ] const tempOptions = options.concat(cancelOption) - const destructiveIndex = tempOptions.findIndex(item => item.destructive) + const destructiveIndex = tempOptions.findIndex((item) => item.destructive) const cancelIndex = tempOptions.length - 1 showActionSheetWithOptions( { - options: tempOptions.map(option => option.text), + options: tempOptions.map((option) => option.text), destructiveButtonIndex: destructiveIndex, cancelButtonIndex: cancelIndex, title, @@ -72,7 +72,7 @@ export const useCustomActionSheet = () => { }, anchor: anchor ? findNodeHandle(anchor) ?? undefined : undefined, }, - buttonIndex => { + (buttonIndex) => { const option = tempOptions[buttonIndex!] option.callback && option.callback(option) }, diff --git a/packages/mobile/src/Style/ThemeService.ts b/packages/mobile/src/Style/ThemeService.ts index 72a84f6ed..685a543e5 100644 --- a/packages/mobile/src/Style/ThemeService.ts +++ b/packages/mobile/src/Style/ThemeService.ts @@ -78,7 +78,7 @@ export class ThemeService { } private registerObservers() { - this.unsubsribeAppEventObserver = this.application?.addEventObserver(async event => { + this.unsubsribeAppEventObserver = this.application?.addEventObserver(async (event) => { /** * If there are any migrations we need to set default theme to start UI */ @@ -259,7 +259,7 @@ export class ThemeService { private async resolveInitialThemeForMode() { try { const savedThemeId = await this.getThemeForMode(this.getColorScheme()) - const matchingThemeId = Object.keys(this.themes).find(themeId => themeId === savedThemeId) + const matchingThemeId = Object.keys(this.themes).find((themeId) => themeId === savedThemeId) if (matchingThemeId) { this.setActiveTheme(matchingThemeId) void this.application?.mobileComponentManager.preloadThirdPartyThemeIndexPath() @@ -278,7 +278,7 @@ export class ThemeService { systemThemes() { return Object.values(this.themes) - .filter(theme => theme.mobileContent.isSystemTheme) + .filter((theme) => theme.mobileContent.isSystemTheme) .sort((a, b) => { if (a.name < b.name) { return -1 @@ -292,7 +292,7 @@ export class ThemeService { nonSystemThemes() { return Object.values(this.themes) - .filter(theme => !theme.mobileContent.isSystemTheme) + .filter((theme) => !theme.mobileContent.isSystemTheme) .sort((a, b) => { if (a.name < b.name) { return -1 @@ -465,7 +465,7 @@ export class ThemeService { private async loadCachedThemes() { const rawValue = (await this.application!.getValue(CACHED_THEMES_KEY, StorageValueModes.Nonwrapped)) || [] - const themes = (rawValue as DecryptedTransferPayload[]).map(rawPayload => { + const themes = (rawValue as DecryptedTransferPayload[]).map((rawPayload) => { const payload = new DecryptedPayload(rawPayload) return new MobileTheme(payload) @@ -480,7 +480,7 @@ export class ThemeService { const themes = this.nonSystemThemes() return this.application!.setValue( CACHED_THEMES_KEY, - themes.map(t => t.payloadRepresentation()), + themes.map((t) => t.payloadRepresentation()), StorageValueModes.Nonwrapped, ) }