feat: screen presentation and dismiss animations for mobile (#2073)

This commit is contained in:
Mo
2022-11-30 14:37:36 -06:00
committed by GitHub
parent 0e95b451d6
commit 7f2074a6ec
79 changed files with 1338 additions and 878 deletions

View File

@@ -1,36 +1,31 @@
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { getPlatformString } from '@/Utils'
import { ApplicationEvent, Challenge, removeFromArray, WebAppEvent } from '@standardnotes/snjs'
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
import { alertDialog, RouteType } from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application'
import Navigation from '@/Components/Tags/Navigation'
import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView'
import Footer from '@/Components/Footer/Footer'
import SessionsModal from '@/Components/SessionsModal/SessionsModal'
import PreferencesViewWrapper from '@/Components/Preferences/PreferencesViewWrapper'
import ChallengeModal from '@/Components/ChallengeModal/ChallengeModal'
import NotesContextMenu from '@/Components/NotesContextMenu/NotesContextMenu'
import PurchaseFlowWrapper from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
import RevisionHistoryModal from '@/Components/RevisionHistoryModal/RevisionHistoryModal'
import PremiumModalProvider from '@/Hooks/usePremiumModal'
import ConfirmSignoutContainer from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
import { ToastContainer } from '@standardnotes/toast'
import FilePreviewModalWrapper from '@/Components/FilePreview/FilePreviewModal'
import ContentListView from '@/Components/ContentListView/ContentListView'
import FileContextMenuWrapper from '@/Components/FileContextMenu/FileContextMenu'
import PermissionsModalWrapper from '@/Components/PermissionsModal/PermissionsModalWrapper'
import { PanelResizedData } from '@/Types/PanelResizedData'
import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper'
import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvider'
import ResponsivePaneProvider from '../ResponsivePane/ResponsivePaneProvider'
import FileDragNDropProvider from '../FileDragNDropProvider'
import ResponsivePaneProvider from '../Panes/ResponsivePaneProvider'
import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler'
import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal'
import DarkModeHandler from '../DarkModeHandler/DarkModeHandler'
import ApplicationProvider from './ApplicationProvider'
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
import CommandProvider from './CommandProvider'
import ApplicationProvider from '../ApplicationProvider'
import CommandProvider from '../CommandProvider'
import PanesSystemComponent from '../Panes/PanesSystemComponent'
type Props = {
application: WebApplication
@@ -45,8 +40,6 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
const viewControllerManager = application.getViewControllerManager()
const appColumnContainerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const desktopService = application.getDesktopService()
@@ -137,30 +130,8 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
}, [application, onAppLaunch, onAppStart])
useEffect(() => {
const removeObserver = application.addWebEventObserver(async (eventName, data) => {
if (eventName === WebAppEvent.PanelResized) {
if (!appColumnContainerRef.current) {
return
}
const { panel, collapsed } = data as PanelResizedData
if (panel === PANEL_NAME_NOTES) {
if (collapsed) {
appColumnContainerRef.current.classList.add('collapsed-notes')
} else {
appColumnContainerRef.current.classList.remove('collapsed-notes')
}
}
if (panel === PANEL_NAME_NAVIGATION) {
if (collapsed) {
appColumnContainerRef.current.classList.add('collapsed-navigation')
} else {
appColumnContainerRef.current.classList.remove('collapsed-navigation')
}
}
} else if (eventName === WebAppEvent.WindowDidFocus) {
const removeObserver = application.addWebEventObserver(async (eventName) => {
if (eventName === WebAppEvent.WindowDidFocus) {
if (!(await application.isLocked())) {
application.sync.sync().catch(console.error)
}
@@ -206,30 +177,13 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
featuresController={viewControllerManager.featuresController}
>
<div className={platformString + ' main-ui-view sn-component h-full'}>
<div id="app" className="app app-column-container" ref={appColumnContainerRef}>
<FileDragNDropProvider
application={application}
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
>
<Navigation application={application} />
<ContentListView
application={application}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
searchOptionsController={viewControllerManager.searchOptionsController}
linkingController={viewControllerManager.linkingController}
/>
<ErrorBoundary>
<NoteGroupView application={application} />
</ErrorBoundary>
</FileDragNDropProvider>
</div>
<FileDragNDropProvider
application={application}
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
>
<PanesSystemComponent />
</FileDragNDropProvider>
<>
<Footer application={application} applicationGroup={mainApplicationGroup} />

View File

@@ -9,12 +9,11 @@ import {
} from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application'
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
import { FileItem, PrefKey } from '@standardnotes/snjs'
import { FileItem, PrefKey, WebAppEvent } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'
import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react'
import ContentList from '@/Components/ContentListView/ContentList'
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
@@ -24,19 +23,19 @@ import { NotesController } from '@/Controllers/NotesController/NotesController'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { ElementIds } from '@/Constants/ElementIDs'
import ContentListHeader from './Header/ContentListHeader'
import ResponsivePaneContent from '../ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { StreamingFileReader } from '@standardnotes/filepicker'
import SearchBar from '../SearchBar/SearchBar'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
import { classNames } from '@standardnotes/utils'
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
import { useFileDragNDrop } from '../FileDragNDropProvider'
import { LinkingController } from '@/Controllers/LinkingController'
import DailyContentList from './Daily/DailyContentList'
import { ListableContentItem } from './Types/ListableContentItem'
import { FeatureName } from '@/Controllers/FeatureName'
import { PanelResizedData } from '@/Types/PanelResizedData'
import { useForwardedRef } from '@/Hooks/useForwardedRef'
type Props = {
accountMenuController: AccountMenuController
@@ -49,235 +48,252 @@ type Props = {
selectionController: SelectedItemsController
searchOptionsController: SearchOptionsController
linkingController: LinkingController
className?: string
id: string
children?: React.ReactNode
onPanelWidthLoad: (width: number) => void
}
const ContentListView: FunctionComponent<Props> = ({
accountMenuController,
application,
filesController,
itemListController,
navigationController,
noAccountWarningController,
notesController,
selectionController,
searchOptionsController,
linkingController,
}) => {
const { isNotesListVisibleOnTablets, toggleAppPane } = useResponsiveAppPane()
const ContentListView = forwardRef<HTMLDivElement, Props>(
(
{
accountMenuController,
application,
filesController,
itemListController,
navigationController,
noAccountWarningController,
notesController,
selectionController,
searchOptionsController,
linkingController,
className,
id,
children,
onPanelWidthLoad,
},
ref,
) => {
const { toggleAppPane, panes } = useResponsiveAppPane()
const { selectedUuids, selectNextItem, selectPreviousItem } = selectionController
const { selected: selectedTag, selectedAsTag } = navigationController
const {
completedFullSync,
createNewNote,
optionsSubtitle,
paginate,
panelTitle,
renderedItems,
items,
isCurrentNoteTemplate,
} = itemListController
const fileInputRef = useRef<HTMLInputElement>(null)
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
const innerRef = useForwardedRef(ref)
const { addDragTarget, removeDragTarget } = useFileDragNDrop()
const { addDragTarget, removeDragTarget } = useFileDragNDrop()
const fileDropCallback = useCallback(
async (files: FileItem[]) => {
useEffect(() => {
return application.addWebEventObserver((event, data) => {
if (event === WebAppEvent.PanelResized) {
const { panel, width } = data as PanelResizedData
if (panel === PANEL_NAME_NOTES) {
if (selectedAsTag) {
void navigationController.setPanelWidthForTag(selectedAsTag, width)
} else {
void application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
}
}
}
})
}, [application, navigationController, selectedAsTag])
useEffect(() => {
const panelWidth = selectedTag?.preferences?.panelWidth || application.getPreference(PrefKey.NotesPanelWidth)
if (panelWidth) {
onPanelWidthLoad(panelWidth)
}
}, [selectedTag, application, onPanelWidthLoad])
const fileDropCallback = useCallback(
async (files: FileItem[]) => {
const currentTag = navigationController.selected
if (!currentTag) {
return
}
if (navigationController.isInAnySystemView() || navigationController.isInSmartView()) {
console.error('Trying to link uploaded files to smart view')
return
}
files.forEach(async (file) => {
await linkingController.linkItems(file, currentTag)
})
},
[navigationController, linkingController],
)
useEffect(() => {
const target = innerRef.current
const currentTag = navigationController.selected
const shouldAddDropTarget = !navigationController.isInAnySystemView() && !navigationController.isInSmartView()
if (!currentTag) {
return
if (target && shouldAddDropTarget && currentTag) {
addDragTarget(target, {
tooltipText: `Drop your files to upload and link them to tag "${currentTag.title}"`,
callback: fileDropCallback,
})
}
if (navigationController.isInAnySystemView() || navigationController.isInSmartView()) {
console.error('Trying to link uploaded files to smart view')
return
return () => {
if (target) {
removeDragTarget(target)
}
}
files.forEach(async (file) => {
await linkingController.linkItems(file, currentTag)
})
},
[navigationController, linkingController],
)
useEffect(() => {
const target = itemsViewPanelRef.current
const currentTag = navigationController.selected
const shouldAddDropTarget = !navigationController.isInAnySystemView() && !navigationController.isInSmartView()
if (target && shouldAddDropTarget && currentTag) {
addDragTarget(target, {
tooltipText: `Drop your files to upload and link them to tag "${currentTag.title}"`,
callback: fileDropCallback,
})
}
return () => {
if (target) {
removeDragTarget(target)
}
}
}, [addDragTarget, fileDropCallback, navigationController, navigationController.selected, removeDragTarget])
const {
completedFullSync,
createNewNote,
optionsSubtitle,
paginate,
panelTitle,
panelWidth,
renderedItems,
items,
isCurrentNoteTemplate,
} = itemListController
const { selectedUuids, selectNextItem, selectPreviousItem } = selectionController
const { selected: selectedTag, selectedAsTag } = navigationController
const icon = selectedTag?.iconString
const isFilesSmartView = useMemo(() => navigationController.isInFilesView, [navigationController.isInFilesView])
const addNewItem = useCallback(async () => {
if (isFilesSmartView) {
if (!application.entitledToFiles) {
application.showPremiumModal(FeatureName.Files)
return
}
if (StreamingFileReader.available()) {
void filesController.uploadNewFile()
return
}
fileInputRef.current?.click()
} else {
await createNewNote()
toggleAppPane(AppPaneId.Editor)
}
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
useEffect(() => {
const searchBarElement = document.getElementById(ElementIds.SearchBar)
/**
* In the browser we're not allowed to override cmd/ctrl + n, so we have to
* use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent.
*/
return application.keyboardService.addCommandHandlers([
{
command: CREATE_NEW_NOTE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
void addNewItem()
},
},
{
command: NEXT_LIST_ITEM_KEYBOARD_COMMAND,
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
onKeyDown: () => {
if (searchBarElement === document.activeElement) {
searchBarElement?.blur()
}
selectNextItem()
},
},
{
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
element: document.body,
onKeyDown: () => {
selectPreviousItem()
},
},
{
command: SEARCH_KEYBOARD_COMMAND,
onKeyDown: (event) => {
if (searchBarElement) {
event.preventDefault()
searchBarElement.focus()
}
},
},
{
command: CANCEL_SEARCH_COMMAND,
onKeyDown: () => {
if (searchBarElement) {
searchBarElement.blur()
}
},
},
{
command: SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
onKeyDown: (event) => {
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
if (!isTargetInsideContentList) {
return
}
event.preventDefault()
selectionController.selectAll()
},
},
}, [
addDragTarget,
fileDropCallback,
navigationController,
navigationController.selected,
removeDragTarget,
innerRef,
])
}, [addNewItem, application.keyboardService, createNewNote, selectNextItem, selectPreviousItem, selectionController])
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
if (selectedAsTag) {
void navigationController.setPanelWidthForTag(selectedAsTag, width)
const icon = selectedTag?.iconString
const isFilesSmartView = useMemo(() => navigationController.isInFilesView, [navigationController.isInFilesView])
const addNewItem = useCallback(async () => {
if (isFilesSmartView) {
if (!application.entitledToFiles) {
application.showPremiumModal(FeatureName.Files)
return
}
if (StreamingFileReader.available()) {
void filesController.uploadNewFile()
return
}
fileInputRef.current?.click()
} else {
void application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
await createNewNote()
toggleAppPane(AppPaneId.Editor)
}
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed)
},
[application, selectedAsTag, navigationController],
)
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
const shortcutForCreate = useMemo(
() => application.keyboardService.keyboardShortcutForCommand(CREATE_NEW_NOTE_KEYBOARD_COMMAND),
[application],
)
useEffect(() => {
const searchBarElement = document.getElementById(ElementIds.SearchBar)
/**
* In the browser we're not allowed to override cmd/ctrl + n, so we have to
* use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent.
*/
return application.keyboardService.addCommandHandlers([
{
command: CREATE_NEW_NOTE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
void addNewItem()
},
},
{
command: NEXT_LIST_ITEM_KEYBOARD_COMMAND,
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
onKeyDown: () => {
if (searchBarElement === document.activeElement) {
searchBarElement?.blur()
}
selectNextItem()
},
},
{
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
element: document.body,
onKeyDown: () => {
selectPreviousItem()
},
},
{
command: SEARCH_KEYBOARD_COMMAND,
onKeyDown: (event) => {
if (searchBarElement) {
event.preventDefault()
searchBarElement.focus()
}
},
},
{
command: CANCEL_SEARCH_COMMAND,
onKeyDown: () => {
if (searchBarElement) {
searchBarElement.blur()
}
},
},
{
command: SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
onKeyDown: (event) => {
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
const addButtonLabel = useMemo(() => {
return isFilesSmartView
? 'Upload file'
: `Create a new note in the selected tag (${shortcutForCreate && keyboardStringForShortcut(shortcutForCreate)})`
}, [isFilesSmartView, shortcutForCreate])
if (!isTargetInsideContentList) {
return
}
const matchesMediumBreakpoint = useMediaQuery(MediaQueryBreakpoints.md)
const matchesXLBreakpoint = useMediaQuery(MediaQueryBreakpoints.xl)
const isTabletScreenSize = matchesMediumBreakpoint && !matchesXLBreakpoint
event.preventDefault()
selectionController.selectAll()
},
},
])
}, [
addNewItem,
application.keyboardService,
createNewNote,
selectNextItem,
selectPreviousItem,
selectionController,
])
const dailyMode = selectedAsTag?.isDailyEntry
const shortcutForCreate = useMemo(
() => application.keyboardService.keyboardShortcutForCommand(CREATE_NEW_NOTE_KEYBOARD_COMMAND),
[application],
)
const handleDailyListSelection = useCallback(
async (item: ListableContentItem, userTriggered: boolean) => {
await selectionController.selectItemWithScrollHandling(item, {
userTriggered: true,
scrollIntoView: userTriggered === false,
animated: false,
})
},
[selectionController],
)
const addButtonLabel = useMemo(() => {
return isFilesSmartView
? 'Upload file'
: `Create a new note in the selected tag (${shortcutForCreate && keyboardStringForShortcut(shortcutForCreate)})`
}, [isFilesSmartView, shortcutForCreate])
useEffect(() => {
const hasEditorPane = selectedUuids.size > 0 || renderedItems.length === 0 || isCurrentNoteTemplate
if (!hasEditorPane) {
itemsViewPanelRef.current?.style.removeProperty('width')
}
}, [selectedUuids, itemsViewPanelRef, isCurrentNoteTemplate, renderedItems])
const dailyMode = selectedAsTag?.isDailyEntry
const hasEditorPane = selectedUuids.size > 0 || renderedItems.length === 0 || isCurrentNoteTemplate
const handleDailyListSelection = useCallback(
async (item: ListableContentItem, userTriggered: boolean) => {
await selectionController.selectItemWithScrollHandling(item, {
userTriggered: true,
scrollIntoView: userTriggered === false,
animated: false,
})
},
[selectionController],
)
return (
<div
id="items-column"
className={classNames(
'sn-component section app-column flex h-full flex-col overflow-hidden pt-safe-top',
hasEditorPane ? 'xl:w-[24rem] xsm-only:!w-full sm-only:!w-full' : 'w-full md:min-w-[400px]',
hasEditorPane
? isTabletScreenSize && !isNotesListVisibleOnTablets
? 'pointer-coarse:md-only:!w-0 pointer-coarse:lg-only:!w-0'
: 'pointer-coarse:md-only:!w-60 pointer-coarse:lg-only:!w-60'
: '',
)}
aria-label={'Notes & Files'}
ref={itemsViewPanelRef}
>
<ResponsivePaneContent className="overflow-hidden" paneId={AppPaneId.Items}>
useEffect(() => {
const hasEditorPane = panes.includes(AppPaneId.Editor)
if (!hasEditorPane) {
innerRef.current?.style.removeProperty('width')
}
}, [selectedUuids, innerRef, isCurrentNoteTemplate, renderedItems, panes])
return (
<div
id={id}
className={classNames(className, 'sn-component section h-full overflow-hidden pt-safe-top')}
aria-label={'Notes & Files'}
ref={innerRef}
>
<div id="items-title-bar" className="section-title-bar border-b border-solid border-border">
<div id="items-title-bar-container">
<input
@@ -325,14 +341,16 @@ const ContentListView: FunctionComponent<Props> = ({
onSelect={handleDailyListSelection}
/>
)}
{!dailyMode && completedFullSync && !renderedItems.length ? (
<p className="empty-items-list opacity-50">No items.</p>
) : null}
{!dailyMode && !completedFullSync && !renderedItems.length ? (
<p className="empty-items-list opacity-50">Loading...</p>
) : null}
{!dailyMode && renderedItems.length ? (
<>
{completedFullSync && !renderedItems.length ? (
<p className="empty-items-list opacity-50">No items.</p>
) : null}
{!completedFullSync && !renderedItems.length ? (
<p className="empty-items-list opacity-50">Loading...</p>
) : null}
<ContentList
items={renderedItems}
selectedUuids={selectedUuids}
@@ -347,22 +365,10 @@ const ContentListView: FunctionComponent<Props> = ({
</>
) : null}
<div className="absolute bottom-0 h-safe-bottom w-full" />
</ResponsivePaneContent>
{hasEditorPane && itemsViewPanelRef.current && (
<PanelResizer
collapsable={true}
hoverable={true}
defaultWidth={300}
panel={itemsViewPanelRef.current}
side={PanelSide.Right}
type={PanelResizeType.WidthOnly}
resizeFinishCallback={panelResizeFinishCallback}
width={panelWidth}
left={0}
/>
)}
</div>
)
}
{children}
</div>
)
},
)
export default observer(ContentListView)

View File

@@ -2,8 +2,8 @@ import { FunctionComponent, useCallback, useEffect, useLayoutEffect, useMemo, us
import { ListableContentItem } from '../Types/ListableContentItem'
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { useResponsiveAppPane } from '../../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../../Panes/ResponsivePaneProvider'
import { AppPaneId } from '../../Panes/AppPaneMetadata'
import { createDailyItemsWithToday, createItemsByDateMapping, insertBlanks } from './CreateDailySections'
import { DailyItemsDay } from './DailyItemsDaySection'
import { DailyItemCell } from './DailyItemCell'

View File

@@ -6,13 +6,13 @@ import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemTags from './ListItemTags'
import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
import { classNames } from '@standardnotes/utils'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import Icon from '../Icon/Icon'
const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({

View File

@@ -7,8 +7,6 @@ import ListItemFlagIcons from './ListItemFlagIcons'
import ListItemTags from './ListItemTags'
import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
import ListItemNotePreviewText from './ListItemNotePreviewText'
import { ListItemTitle } from './ListItemTitle'
@@ -31,8 +29,6 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
isPreviousItemTiled,
isNextItemTiled,
}) => {
const { toggleAppPane } = useResponsiveAppPane()
const listItemRef = useRef<HTMLDivElement>(null)
const noteType = item.noteType || application.componentManager.editorForNote(item)?.package_info.note_type
@@ -65,11 +61,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
}
const onClick = useCallback(async () => {
const { didSelect } = await onSelect(item, true)
if (didSelect) {
toggleAppPane(AppPaneId.Editor)
}
}, [item, onSelect, toggleAppPane])
await onSelect(item, true)
}, [item, onSelect])
useContextMenuEvent(listItemRef, openContextMenu)

View File

@@ -1,6 +1,6 @@
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import MenuItem from '../Menu/MenuItem'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import { FileBackupRecord, FileItem } from '@standardnotes/snjs'
import { dateToStringStyle1 } from '@/Utils/DateUtils'

View File

@@ -6,8 +6,8 @@ import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import MenuItem from '../Menu/MenuItem'
import { FileContextMenuBackupOption } from './FileContextMenuBackupOption'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'

View File

@@ -7,7 +7,7 @@ import { isHandlingFileDrag } from '@/Utils/DragTypeCheck'
import { StreamingFileReader } from '@standardnotes/filepicker'
import { FileItem } from '@standardnotes/snjs'
import { useMemo, useState, createContext, ReactNode, useRef, useCallback, useEffect, useContext, memo } from 'react'
import Portal from '../Portal/Portal'
import Portal from './Portal/Portal'
type FileDragTargetData = {
tooltipText: string

View File

@@ -3,8 +3,8 @@ import { getBase64FromBlob } from '@/Utils'
import { FileItem } from '@standardnotes/snjs'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'
import Button from '../Button/Button'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { createObjectURLWithRef } from './CreateObjectURLWithRef'
import ImagePreview from './ImagePreview'
import { ImageZoomLevelProps } from './ImageZoomLevelProps'

View File

@@ -9,7 +9,7 @@ import LinkedItemsButton from '../LinkedItems/LinkedItemsButton'
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
import Popover from '../Popover/Popover'
import FilePreviewInfoPanel from '../FilePreview/FilePreviewInfoPanel'
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
import { useFileDragNDrop } from '../FileDragNDropProvider'
import RoundIconButton from '../Button/RoundIconButton'
const SyncTimeoutNoDebounceMs = 100

View File

@@ -19,7 +19,7 @@ import { KeyboardKey } from '@standardnotes/ui-services'
import { ElementIds } from '@/Constants/ElementIDs'
import Menu from '../Menu/Menu'
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
type Props = {
linkingController: LinkingController

View File

@@ -8,7 +8,7 @@ import Icon from '../Icon/Icon'
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
type Props = {

View File

@@ -3,14 +3,14 @@ import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput'
import { LinkingController } from '@/Controllers/LinkingController'
import LinkedItemBubble from './LinkedItemBubble'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { ElementIds } from '@/Constants/ElementIDs'
import { classNames } from '@standardnotes/utils'
import { ContentType } from '@standardnotes/snjs'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
import { useCommandService } from '../ApplicationView/CommandProvider'
import { useCommandService } from '../CommandProvider'
type Props = {
linkingController: LinkingController

View File

@@ -4,7 +4,7 @@ import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag
import { getIconForItem } from '@/Utils/Items/Icons/getIconForItem'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { observer } from 'mobx-react-lite'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import Icon from '../Icon/Icon'
type Props = {

View File

@@ -6,7 +6,7 @@ import { classNames } from '@standardnotes/utils'
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
import { observer } from 'mobx-react-lite'
import { ChangeEventHandler, useEffect, useRef, useState } from 'react'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import ClearInputButton from '../ClearInputButton/ClearInputButton'
import Icon from '../Icon/Icon'
import DecoratedInput from '../Input/DecoratedInput'

View File

@@ -8,7 +8,7 @@ import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { FileItem } from '@standardnotes/snjs'
import { KeyboardKey } from '@standardnotes/ui-services'
import { useRef, useState } from 'react'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import { PopoverFileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
import Icon from '../Icon/Icon'
import MenuItem from '../Menu/MenuItem'

View File

@@ -1,19 +1,19 @@
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
import useIsTabletOrMobileScreen from '@/Hooks/useIsTabletOrMobileScreen'
import { classNames } from '@standardnotes/snjs'
import RoundIconButton from '../Button/RoundIconButton'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
/** This button is displayed in the items list header */
export const NavigationMenuButton = () => {
const { selectedPane, toggleAppPane } = useResponsiveAppPane()
const { setPaneLayout } = useResponsiveAppPane()
const { isTabletOrMobile } = useIsTabletOrMobileScreen()
return (
<RoundIconButton
className="mr-3 md:hidden pointer-coarse:md-only:flex pointer-coarse:lg-only:flex"
className={classNames(isTabletOrMobile ? 'flex' : 'hidden', 'mr-3')}
onClick={() => {
if (selectedPane === AppPaneId.Items || selectedPane === AppPaneId.Editor) {
toggleAppPane(AppPaneId.Navigation)
} else {
toggleAppPane(AppPaneId.Items)
}
setPaneLayout(PaneLayout.TagSelection)
}}
label="Open navigation menu"
icon="menu-variant"

View File

@@ -1,30 +1,33 @@
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { useMediaQuery, MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
import { IconType } from '@standardnotes/snjs'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { classNames, IconType } from '@standardnotes/snjs'
import RoundIconButton from '../Button/RoundIconButton'
import useIsTabletOrMobileScreen from '@/Hooks/useIsTabletOrMobileScreen'
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
const MobileItemsListButton = () => {
const { toggleAppPane, isNotesListVisibleOnTablets, toggleNotesListOnTablets } = useResponsiveAppPane()
const matchesMediumBreakpoint = useMediaQuery(MediaQueryBreakpoints.md)
const matchesXLBreakpoint = useMediaQuery(MediaQueryBreakpoints.xl)
const isTabletScreenSize = matchesMediumBreakpoint && !matchesXLBreakpoint
const { panes, replacePanes, setPaneLayout } = useResponsiveAppPane()
const iconType: IconType = isTabletScreenSize && !isNotesListVisibleOnTablets ? 'chevron-right' : 'chevron-left'
const label = isTabletScreenSize
? isNotesListVisibleOnTablets
? 'Hide items list'
: 'Show items list'
: 'Go to items list'
const { isTablet, isTabletOrMobile, isMobile } = useIsTabletOrMobileScreen()
const itemsShown = panes.includes(AppPaneId.Items)
const iconType: IconType = isTablet && !itemsShown ? 'chevron-right' : 'chevron-left'
const label = isTablet ? (itemsShown ? 'Hide items list' : 'Show items list') : 'Go to items list'
return (
<RoundIconButton
className="mr-3 md:hidden pointer-coarse:md-only:flex pointer-coarse:lg-only:flex"
className={classNames(isTabletOrMobile ? 'flex' : 'hidden', 'mr-3')}
onClick={() => {
if (isTabletScreenSize) {
toggleNotesListOnTablets()
if (isMobile) {
void setPaneLayout(PaneLayout.ItemSelection)
} else {
toggleAppPane(AppPaneId.Items)
if (itemsShown) {
void replacePanes([AppPaneId.Editor])
} else {
void setPaneLayout(PaneLayout.ItemSelection)
}
}
}}
label={label}

View File

@@ -3,9 +3,7 @@ import { AbstractComponent } from '@/Components/Abstract/PureComponent'
import { WebApplication } from '@/Application/Application'
import MultipleSelectedNotes from '@/Components/MultipleSelectedNotes/MultipleSelectedNotes'
import MultipleSelectedFiles from '../MultipleSelectedFiles/MultipleSelectedFiles'
import { ElementIds } from '@/Constants/ElementIDs'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import ResponsivePaneContent from '../ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import FileView from '../FileView/FileView'
import NoteView from '../NoteView/NoteView'
import { NoteViewController } from '../NoteView/Controller/NoteViewController'
@@ -22,6 +20,9 @@ type State = {
type Props = {
application: WebApplication
className?: string
innerRef: (ref: HTMLDivElement) => void
id: string
}
class NoteGroupView extends AbstractComponent<Props, State> {
@@ -97,44 +98,44 @@ class NoteGroupView extends AbstractComponent<Props, State> {
const hasControllers = this.state.controllers.length > 0
const canRenderEditorView = this.state.selectedPane === AppPaneId.Editor || !this.state.isInMobileView
return (
<div id={ElementIds.EditorColumn} className="app-column app-column-third flex h-full flex-col pt-safe-top">
<ResponsivePaneContent paneId={AppPaneId.Editor} className="flex-grow">
{this.state.showMultipleSelectedNotes && (
<MultipleSelectedNotes
application={this.application}
selectionController={this.viewControllerManager.selectionController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
linkingController={this.viewControllerManager.linkingController}
historyModalController={this.viewControllerManager.historyModalController}
/>
)}
{this.state.showMultipleSelectedFiles && (
<MultipleSelectedFiles
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
/>
)}
{shouldNotShowMultipleSelectedItems && hasControllers && canRenderEditorView && (
<>
{this.state.controllers.map((controller) => {
return controller instanceof NoteViewController ? (
<NoteView key={controller.runtimeId} application={this.application} controller={controller} />
) : (
<FileView
key={controller.runtimeId}
application={this.application}
viewControllerManager={this.viewControllerManager}
file={controller.item}
/>
)
})}
</>
)}
</ResponsivePaneContent>
<div
id={this.props.id}
className={`flex h-full flex-grow flex-col pt-safe-top ${this.props.className}`}
ref={this.props.innerRef}
>
{this.state.showMultipleSelectedNotes && (
<MultipleSelectedNotes
application={this.application}
selectionController={this.viewControllerManager.selectionController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
linkingController={this.viewControllerManager.linkingController}
historyModalController={this.viewControllerManager.historyModalController}
/>
)}
{this.state.showMultipleSelectedFiles && (
<MultipleSelectedFiles
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
/>
)}
{shouldNotShowMultipleSelectedItems && hasControllers && (
<>
{this.state.controllers.map((controller) => {
return controller instanceof NoteViewController ? (
<NoteView key={controller.runtimeId} application={this.application} controller={controller} />
) : (
<FileView
key={controller.runtimeId}
application={this.application}
viewControllerManager={this.viewControllerManager}
file={controller.item}
/>
)
})}
</>
)}
</div>
)
}

View File

@@ -4,7 +4,7 @@ import { classNames } from '@standardnotes/utils'
import { ReactNode, useCallback, useState } from 'react'
import { IconType, PrefKey } from '@standardnotes/snjs'
import Icon from '../Icon/Icon'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
export type NoteStatus = {
type: 'saving' | 'saved' | 'error'

View File

@@ -9,7 +9,7 @@ import { ElementIds } from '@/Constants/ElementIDs'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
import { log, LoggingDomain } from '@/Logging'
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils'
import { debounce, isDesktopApplication, isMobileScreen, isTabletOrMobileScreen } from '@/Utils'
import { classNames } from '@standardnotes/utils'
import {
ApplicationEvent,
@@ -672,10 +672,9 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
PrefDefaults[PrefKey.EditorMonospaceEnabled],
)
const marginResizersEnabled = this.application.getPreference(
PrefKey.EditorResizersEnabled,
PrefDefaults[PrefKey.EditorResizersEnabled],
)
const marginResizersEnabled =
!isTabletOrMobileScreen() &&
this.application.getPreference(PrefKey.EditorResizersEnabled, PrefDefaults[PrefKey.EditorResizersEnabled])
const updateSavingIndicator = this.application.getPreference(
PrefKey.UpdateSavingStatusIndicator,
@@ -687,7 +686,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
this.setState({
monospaceFont,
marginResizersEnabled,
updateSavingIndicator,
})
@@ -929,6 +927,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
left={this.state.leftResizerOffset}
width={this.state.leftResizerWidth}
resizeFinishCallback={this.onPanelResizeFinish}
modifyElementWidth={true}
/>
) : null}
@@ -980,6 +979,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
left={this.state.rightResizerOffset}
width={this.state.rightResizerWidth}
resizeFinishCallback={this.onPanelResizeFinish}
modifyElementWidth={true}
/>
) : null}
</div>

View File

@@ -2,7 +2,7 @@ import { FilesController } from '@/Controllers/FilesController'
import { LinkingController } from '@/Controllers/LinkingController'
import { SNNote } from '@standardnotes/snjs'
import { useEffect } from 'react'
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
import { useFileDragNDrop } from '../FileDragNDropProvider'
type Props = {
note: SNNote

View File

@@ -1,7 +1,7 @@
import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ElementFormatType, NodeKey } from 'lexical'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { useApplication } from '@/Components/ApplicationProvider'
import FilePreview from '@/Components/FilePreview/FilePreview'
import { FileItem } from '@standardnotes/snjs'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'

View File

@@ -1,4 +1,4 @@
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { useApplication } from '@/Components/ApplicationProvider'
import { downloadBlobOnAndroid } from '@/NativeMobileWeb/DownloadBlobOnAndroid'
import { shareBlobOnMobile } from '@/NativeMobileWeb/ShareBlobOnMobile'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
@@ -13,7 +13,7 @@ import { useCallback, useEffect } from 'react'
import { $convertToMarkdownString } from '@lexical/markdown'
import { MarkdownTransformers } from '@standardnotes/blocks-editor'
import { $generateHtmlFromNodes } from '@lexical/html'
import { useCommandService } from '@/Components/ApplicationView/CommandProvider'
import { useCommandService } from '@/Components/CommandProvider'
export const ExportPlugin = () => {
const application = useApplication()

View File

@@ -1,10 +1,10 @@
import { useCallback, useMemo } from 'react'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { useApplication } from '@/Components/ApplicationProvider'
import LinkedItemBubble from '@/Components/LinkedItems/LinkedItemBubble'
import { createLinkFromItem } from '@/Utils/Items/Search/createLinkFromItem'
import { useLinkingController } from '@/Controllers/LinkingControllerProvider'
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { useResponsiveAppPane } from '@/Components/ResponsivePane/ResponsivePaneProvider'
import { useResponsiveAppPane } from '@/Components/Panes/ResponsivePaneProvider'
import { LexicalNode } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'

View File

@@ -4,7 +4,7 @@ import { TextNode } from 'lexical'
import { FunctionComponent, useCallback, useMemo, useState } from 'react'
import { ItemSelectionItemComponent } from './ItemSelectionItemComponent'
import { ItemOption } from './ItemOption'
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { useApplication } from '@/Components/ApplicationProvider'
import { ContentType, SNNote } from '@standardnotes/snjs'
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
import Popover from '@/Components/Popover/Popover'

View File

@@ -1,4 +1,4 @@
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { useApplication } from '@/Components/ApplicationProvider'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { SNNote, ContentType } from '@standardnotes/snjs'
import { useState, useEffect } from 'react'

View File

@@ -30,7 +30,7 @@ import {
} from './Plugins/ChangeContentCallback/ChangeContentCallback'
import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { useCommandService } from '@/Components/ApplicationView/CommandProvider'
import { useCommandService } from '@/Components/CommandProvider'
import { SUPER_SHOW_MARKDOWN_PREVIEW } from '@standardnotes/ui-services'
import { SuperNoteMarkdownPreview } from './SuperNoteMarkdownPreview'
import { ExportPlugin } from './Plugins/ExportPlugin/ExportPlugin'

View File

@@ -7,7 +7,7 @@ import { KeyboardKey } from '@standardnotes/ui-services'
import Popover from '../Popover/Popover'
import { IconType } from '@standardnotes/snjs'
import { getTitleForLinkedTag } from '@/Utils/Items/Display/getTitleForLinkedTag'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import MenuItem from '../Menu/MenuItem'
import Menu from '../Menu/Menu'

View File

@@ -15,8 +15,8 @@ import AddTagOption from './AddTagOption'
import { addToast, dismissToast, ToastType } from '@standardnotes/toast'
import { NotesOptionsProps } from './NotesOptionsProps'
import HorizontalSeparator from '../Shared/HorizontalSeparator'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { AppPaneId } from '../Panes/AppPaneMetadata'
import { getNoteBlob, getNoteFileName } from '@/Utils/NoteExportUtils'
import { shareSelectedNotes } from '@/NativeMobileWeb/ShareSelectedNotes'
import { downloadSelectedNotesOnAndroid } from '@/NativeMobileWeb/DownloadSelectedNotesOnAndroid'
@@ -27,7 +27,7 @@ import { NoteAttributes } from './NoteAttributes'
import { SpellcheckOptions } from './SpellcheckOptions'
import { NoteSizeWarning } from './NoteSizeWarning'
import { DeletePermanentlyButton } from './DeletePermanentlyButton'
import { useCommandService } from '../ApplicationView/CommandProvider'
import { useCommandService } from '../CommandProvider'
import { iconClass } from './ClassNames'
import SuperNoteOptions from './SuperNoteOptions'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
@@ -160,6 +160,10 @@ const NotesOptions = ({
return <ProtectedUnauthorizedLabel />
}
if (notes.length === 0) {
return null
}
return (
<>
{notes.length === 1 && (

View File

@@ -6,7 +6,7 @@ import {
SUPER_EXPORT_MARKDOWN,
} from '@standardnotes/ui-services'
import { useRef, useState } from 'react'
import { useCommandService } from '../ApplicationView/CommandProvider'
import { useCommandService } from '../CommandProvider'
import Icon from '../Icon/Icon'
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
import Menu from '../Menu/Menu'

View File

@@ -31,7 +31,8 @@ type Props = {
side: PanelSide
type: PanelResizeType
resizeFinishCallback?: ResizeFinishCallback
widthEventCallback?: () => void
widthEventCallback?: (width: number) => void
modifyElementWidth: boolean
}
type State = {
@@ -82,9 +83,12 @@ class PanelResizer extends Component<Props, State> {
}
override componentDidUpdate(prevProps: Props) {
this.lastWidth = this.props.panel.scrollWidth
if (this.props.width != prevProps.width) {
this.setWidth(this.props.width)
}
if (this.props.left !== prevProps.left) {
this.setLeft(this.props.left)
this.setWidth(this.props.width)
@@ -108,6 +112,10 @@ class PanelResizer extends Component<Props, State> {
}
getParentRect() {
if (!this.props.panel.parentNode) {
return new DOMRect()
}
return (this.props.panel.parentNode as HTMLElement).getBoundingClientRect()
}
@@ -131,7 +139,7 @@ class PanelResizer extends Component<Props, State> {
})
}
setWidth = (width: number, finish = false): void => {
setWidth = (width: number, finish = false): number => {
if (width === 0) {
width = this.computeMaxWidth()
}
@@ -150,22 +158,29 @@ class PanelResizer extends Component<Props, State> {
}
const isFullWidth = Math.round(width + this.lastLeft) === Math.round(parentRect.width)
if (isFullWidth) {
if (this.props.type === PanelResizeType.WidthOnly) {
this.props.panel.style.removeProperty('width')
if (this.props.modifyElementWidth) {
if (isFullWidth) {
if (this.props.type === PanelResizeType.WidthOnly) {
this.props.panel.style.removeProperty('width')
} else {
this.props.panel.style.width = `calc(100% - ${this.lastLeft}px)`
}
} else {
this.props.panel.style.width = `calc(100% - ${this.lastLeft}px)`
this.props.panel.style.width = width + 'px'
}
} else {
this.props.panel.style.width = width + 'px'
}
this.lastWidth = width
if (finish) {
this.finishSettingWidth()
if (this.props.resizeFinishCallback) {
this.props.resizeFinishCallback(this.lastWidth, this.lastLeft, this.isAtMaxWidth(), this.isCollapsed())
}
}
return width
}
setLeft = (left: number) => {
@@ -187,10 +202,6 @@ class PanelResizer extends Component<Props, State> {
}
handleWidthEvent(event?: MouseEvent) {
if (this.props.widthEventCallback) {
this.props.widthEventCallback()
}
let x
if (event) {
x = event.clientX
@@ -201,7 +212,11 @@ class PanelResizer extends Component<Props, State> {
}
const deltaX = x - this.lastDownX
const newWidth = this.startWidth + deltaX
this.setWidth(newWidth, false)
const adjustedWidth = this.setWidth(newWidth, false)
if (this.props.widthEventCallback) {
this.props.widthEventCallback(adjustedWidth)
}
}
handleLeftEvent(event: MouseEvent) {
@@ -309,6 +324,7 @@ class PanelResizer extends Component<Props, State> {
return (
<div
className={classNames(
'panel-resizer',
'absolute right-0 top-0 z-panel-resizer',
'hidden h-full w-[4px] cursor-col-resize border-y-0 bg-[color:var(--panel-resizer-background-color)] md:block',
this.props.alwaysVisible || this.state.collapsed || this.state.pressed ? ' opacity-100' : 'opacity-0',

View File

@@ -0,0 +1,13 @@
import { ElementIds } from '@/Constants/ElementIDs'
export enum AppPaneId {
Navigation = 'NavigationColumn',
Items = 'ItemsColumn',
Editor = 'EditorColumn',
}
export const AppPaneIdToDivId = {
[AppPaneId.Navigation]: ElementIds.NavigationColumn,
[AppPaneId.Items]: ElementIds.ItemsColumn,
[AppPaneId.Editor]: ElementIds.EditorColumn,
}

View File

@@ -0,0 +1,60 @@
import { log, LoggingDomain } from '@/Logging'
const ENTRANCE_DURATION = 200
const EXIT_DURATION = 200
export async function animatePaneEntranceTransitionFromOffscreenToTheRight(elementId: string): Promise<void> {
log(LoggingDomain.Panes, 'Animating pane entrance transition from offscreen to the right', elementId)
const element = document.getElementById(elementId)
if (!element) {
return
}
const animation = element.animate(
[
{
transform: 'translateX(100%)',
},
{
transform: 'translateX(0)',
},
],
{
duration: ENTRANCE_DURATION,
easing: 'ease-in-out',
fill: 'forwards',
},
)
await animation.finished
animation.finish()
}
export async function animatePaneExitTransitionOffscreenToTheRight(elementId: string): Promise<void> {
log(LoggingDomain.Panes, 'Animating pane exit transition offscreen to the right', elementId)
const element = document.getElementById(elementId)
if (!element) {
return
}
const animation = element.animate(
[
{
transform: 'translateX(0)',
},
{
transform: 'translateX(100%)',
},
],
{
duration: EXIT_DURATION,
easing: 'ease-in-out',
fill: 'forwards',
},
)
await animation.finished
animation.finish()
}

View File

@@ -0,0 +1,342 @@
import { PANEL_NAME_NAVIGATION, PANEL_NAME_NOTES } from '@/Constants/Constants'
import { ElementIds } from '@/Constants/ElementIDs'
import useIsTabletOrMobileScreen from '@/Hooks/useIsTabletOrMobileScreen'
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
import { ApplicationEvent, classNames, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { useCallback, useEffect, useState } from 'react'
import { usePrevious } from '../ContentListView/Calendar/usePrevious'
import ContentListView from '../ContentListView/ContentListView'
import NoteGroupView from '../NoteGroupView/NoteGroupView'
import PanelResizer, { PanelResizeType, PanelSide, ResizeFinishCallback } from '../PanelResizer/PanelResizer'
import { AppPaneId, AppPaneIdToDivId } from './AppPaneMetadata'
import { useResponsiveAppPane } from './ResponsivePaneProvider'
import Navigation from '../Tags/Navigation'
import { useApplication } from '../ApplicationProvider'
import {
animatePaneEntranceTransitionFromOffscreenToTheRight,
animatePaneExitTransitionOffscreenToTheRight,
} from '@/Components/Panes/PaneAnimator'
import { isPanesChangeLeafDismiss, isPanesChangePush } from '@/Controllers/PaneController/panesForLayout'
import { log, LoggingDomain } from '@/Logging'
const NAVIGATION_PANEL_MIN_WIDTH = 48
const ITEMS_PANEL_MIN_WIDTH = 200
const PLACEHOLDER_NAVIGATION_PANEL_WIDTH = 220
const PLACEHOLDER_NOTES_PANEL_WIDTH = 400
const PanesSystemComponent = () => {
const application = useApplication()
const isTabletOrMobileScreenWrapped = useIsTabletOrMobileScreen()
const { isTabletOrMobile, isTablet, isMobile } = isTabletOrMobileScreenWrapped
const previousIsTabletOrMobileWrapped = usePrevious(isTabletOrMobileScreenWrapped)
const paneController = useResponsiveAppPane()
const previousPaneController = usePrevious(paneController)
const [renderPanes, setRenderPanes] = useState<AppPaneId[]>([])
const [panesPendingEntrance, setPanesPendingEntrance] = useState<AppPaneId[]>([])
const [panesPendingExit, setPanesPendingExit] = useState<AppPaneId[]>([])
const viewControllerManager = application.getViewControllerManager()
const [navigationPanelWidth, setNavigationPanelWidth] = useState<number>(
application.getPreference(PrefKey.TagsPanelWidth, PLACEHOLDER_NAVIGATION_PANEL_WIDTH),
)
const [navigationRef, setNavigationRef] = useState<HTMLDivElement | null>(null)
const [itemsPanelWidth, setItemsPanelWidth] = useState<number>(
application.getPreference(PrefKey.NotesPanelWidth, PLACEHOLDER_NOTES_PANEL_WIDTH),
)
const [listRef, setListRef] = useState<HTMLDivElement | null>(null)
const showPanelResizers = !isTabletOrMobile
const [_editorRef, setEditorRef] = useState<HTMLDivElement | null>(null)
const animationsSupported = isMobile
useEffect(() => {
if (!animationsSupported) {
return
}
const panes = paneController.panes
const previousPanes = previousPaneController?.panes
if (!previousPanes) {
setPanesPendingEntrance([])
return
}
const isPush = isPanesChangePush(previousPanes, panes)
if (isPush) {
setPanesPendingEntrance([panes[panes.length - 1]])
}
}, [paneController.panes, previousPaneController?.panes, animationsSupported])
useEffect(() => {
if (!animationsSupported) {
return
}
const panes = paneController.panes
const previousPanes = previousPaneController?.panes
if (!previousPanes) {
setPanesPendingExit([])
return
}
const isExit = isPanesChangeLeafDismiss(previousPanes, panes)
if (isExit) {
setPanesPendingExit([previousPanes[previousPanes.length - 1]])
}
}, [paneController.panes, previousPaneController?.panes, animationsSupported])
useEffect(() => {
setRenderPanes(paneController.panes)
}, [paneController.panes])
useEffect(() => {
if (!panesPendingEntrance || panesPendingEntrance?.length === 0) {
return
}
if (panesPendingEntrance.length > 1) {
console.warn('More than one pane pending entrance. This is not supported.')
return
}
void animatePaneEntranceTransitionFromOffscreenToTheRight(AppPaneIdToDivId[panesPendingEntrance[0]]).then(() => {
setPanesPendingEntrance([])
})
}, [panesPendingEntrance])
useEffect(() => {
if (!panesPendingExit || panesPendingExit?.length === 0) {
return
}
if (panesPendingExit.length > 1) {
console.warn('More than one pane pending exit. This is not supported.')
return
}
void animatePaneExitTransitionOffscreenToTheRight(AppPaneIdToDivId[panesPendingExit[0]]).then(() => {
setPanesPendingExit([])
})
}, [panesPendingExit])
useEffect(() => {
const removeObserver = application.addEventObserver(async () => {
const width = application.getPreference(PrefKey.TagsPanelWidth, PLACEHOLDER_NAVIGATION_PANEL_WIDTH)
setNavigationPanelWidth(width)
}, ApplicationEvent.PreferencesChanged)
return () => {
removeObserver()
}
}, [application])
const navigationPanelResizeWidthChangeCallback = useCallback((width: number) => {
setNavigationPanelWidth(width)
}, [])
const itemsPanelResizeWidthChangeCallback = useCallback((width: number) => {
setItemsPanelWidth(width)
}, [])
const handleInitialItemsListPanelWidthLoad = useCallback((width: number) => {
setItemsPanelWidth(width)
}, [])
const navigationPanelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.publishPanelDidResizeEvent(PANEL_NAME_NAVIGATION, width, isCollapsed)
},
[application],
)
const itemsPanelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, width, isCollapsed)
},
[application],
)
useEffect(() => {
if (isTablet && !previousIsTabletOrMobileWrapped?.isTablet) {
if (paneController.selectedPane !== AppPaneId.Navigation) {
paneController.removePane(AppPaneId.Navigation)
}
} else if (
!isTablet &&
previousIsTabletOrMobileWrapped?.isTablet &&
!paneController.panes.includes(AppPaneId.Navigation)
) {
paneController.insertPaneAtIndex(AppPaneId.Navigation, 0)
}
}, [isTablet, paneController, previousIsTabletOrMobileWrapped])
const computeStylesForContainer = (): React.CSSProperties => {
const panes = paneController.panes
const numPanes = panes.length
if (isMobile) {
return {}
}
switch (numPanes) {
case 1: {
return {
gridTemplateColumns: 'auto',
}
}
case 2: {
if (paneController.focusModeEnabled) {
return {
gridTemplateColumns: '0 1fr',
}
}
if (isTablet) {
return {
gridTemplateColumns: '1fr 2fr',
}
} else {
if (panes[0] === AppPaneId.Navigation) {
return {
gridTemplateColumns: `${navigationPanelWidth}px auto`,
}
} else {
return {
gridTemplateColumns: `${itemsPanelWidth}px auto`,
}
}
}
}
case 3: {
if (paneController.focusModeEnabled) {
return {
gridTemplateColumns: '0 0 1fr',
}
}
return {
gridTemplateColumns: `${navigationPanelWidth}px ${itemsPanelWidth}px 2fr`,
}
}
default:
return {}
}
}
const computeClassesForPane = (_paneId: AppPaneId, isPendingEntrance: boolean, index: number): string => {
const common = `app-pane app-pane-${index + 1} h-full content`
if (isMobile) {
return `absolute top-0 left-0 w-full flex flex-col ${common} ${
isPendingEntrance ? 'translate-x-[100%]' : 'translate-x-0 '
}`
} else {
return `flex flex-col relative overflow-hidden ${common}`
}
}
const computeClassesForContainer = (): string => {
if (isMobile) {
return 'w-full'
}
return 'grid'
}
const renderPanesWithPendingExit = [...renderPanes, ...panesPendingExit]
log(LoggingDomain.Panes, 'Rendering panes', renderPanesWithPendingExit)
return (
<div id="app" className={`app ${computeClassesForContainer()}`} style={{ ...computeStylesForContainer() }}>
{renderPanesWithPendingExit.map((pane, index) => {
const isPendingEntrance = panesPendingEntrance?.includes(pane)
const className = computeClassesForPane(pane, isPendingEntrance ?? false, index)
if (pane === AppPaneId.Navigation) {
return (
<Navigation
id={ElementIds.NavigationColumn}
ref={setNavigationRef}
className={classNames(className, isTabletOrMobile ? 'w-full' : '')}
key="navigation-pane"
application={application}
>
{showPanelResizers && navigationRef && (
<PanelResizer
collapsable={true}
defaultWidth={navigationPanelWidth}
hoverable={true}
left={0}
minWidth={NAVIGATION_PANEL_MIN_WIDTH}
modifyElementWidth={false}
panel={navigationRef}
resizeFinishCallback={navigationPanelResizeFinishCallback}
side={PanelSide.Right}
type={PanelResizeType.WidthOnly}
width={navigationPanelWidth}
widthEventCallback={navigationPanelResizeWidthChangeCallback}
/>
)}
</Navigation>
)
} else if (pane === AppPaneId.Items) {
return (
<ContentListView
id={ElementIds.ItemsColumn}
className={className}
ref={setListRef}
key={'content-list-view'}
application={application}
onPanelWidthLoad={handleInitialItemsListPanelWidthLoad}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
searchOptionsController={viewControllerManager.searchOptionsController}
linkingController={viewControllerManager.linkingController}
>
{showPanelResizers && listRef && (
<PanelResizer
collapsable={true}
defaultWidth={itemsPanelWidth}
hoverable={true}
left={0}
minWidth={ITEMS_PANEL_MIN_WIDTH}
modifyElementWidth={false}
panel={listRef}
resizeFinishCallback={itemsPanelResizeFinishCallback}
side={PanelSide.Right}
type={PanelResizeType.WidthOnly}
width={itemsPanelWidth}
widthEventCallback={itemsPanelResizeWidthChangeCallback}
/>
)}
</ContentListView>
)
} else if (pane === AppPaneId.Editor) {
return (
<ErrorBoundary key="editor-pane">
<NoteGroupView
id={ElementIds.EditorColumn}
innerRef={(ref) => setEditorRef(ref)}
className={className}
application={application}
/>
</ErrorBoundary>
)
}
})}
</div>
)
}
export default observer(PanesSystemComponent)

View File

@@ -1,4 +1,3 @@
import { ElementIds } from '@/Constants/ElementIDs'
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
import {
useEffect,
@@ -7,25 +6,31 @@ import {
createContext,
useCallback,
useContext,
useState,
memo,
useRef,
useLayoutEffect,
MutableRefObject,
} from 'react'
import { AppPaneId } from './AppPaneMetadata'
import { PaneController } from '../../Controllers/PaneController'
import { PaneController } from '../../Controllers/PaneController/PaneController'
import { observer } from 'mobx-react-lite'
type ResponsivePaneData = {
selectedPane: AppPaneId
toggleAppPane: (paneId: AppPaneId) => void
toggleNotesListOnTablets: () => void
toggleListPane: () => void
toggleNavigationPane: () => void
isNotesListVisibleOnTablets: boolean
isListPaneCollapsed: boolean
isNavigationPaneCollapsed: boolean
panes: PaneController['panes']
toggleAppPane: (paneId: AppPaneId) => void
presentPane: PaneController['presentPane']
popToPane: PaneController['popToPane']
dismissLastPane: PaneController['dismissLastPane']
replacePanes: PaneController['replacePanes']
removePane: PaneController['removePane']
insertPaneAtIndex: PaneController['insertPaneAtIndex']
setPaneLayout: PaneController['setPaneLayout']
focusModeEnabled: PaneController['focusModeEnabled']
}
const ResponsivePaneContext = createContext<ResponsivePaneData | undefined>(undefined)
@@ -62,27 +67,15 @@ const MemoizedChildren = memo(({ children }: ChildrenProps) => <>{children}</>)
const ResponsivePaneProvider = ({ paneController, children }: ProviderProps) => {
const currentSelectedPane = paneController.currentPane
const previousSelectedPane = paneController.previousPane
const currentSelectedPaneRef = useStateRef<AppPaneId>(currentSelectedPane)
const toggleAppPane = useCallback(
(paneId: AppPaneId) => {
paneController.setPreviousPane(currentSelectedPane)
paneController.setCurrentPane(paneId)
paneController.presentPane(paneId)
},
[paneController, currentSelectedPane],
[paneController],
)
useEffect(() => {
if (previousSelectedPane) {
const previousPaneElement = document.getElementById(ElementIds[previousSelectedPane])
previousPaneElement?.removeAttribute('data-selected-pane')
}
const currentPaneElement = document.getElementById(ElementIds[currentSelectedPane])
currentPaneElement?.setAttribute('data-selected-pane', '')
}, [currentSelectedPane, previousSelectedPane])
const addAndroidBackHandler = useAndroidBackHandler()
useEffect(() => {
@@ -104,32 +97,40 @@ const ResponsivePaneProvider = ({ paneController, children }: ProviderProps) =>
}
}, [addAndroidBackHandler, currentSelectedPaneRef, toggleAppPane])
const [isNotesListVisibleOnTablets, setNotesListVisibleOnTablets] = useState(true)
const toggleNotesListOnTablets = useCallback(() => {
setNotesListVisibleOnTablets((visible) => !visible)
}, [])
const contextValue = useMemo(
() => ({
(): ResponsivePaneData => ({
selectedPane: currentSelectedPane,
toggleAppPane,
isNotesListVisibleOnTablets,
toggleNotesListOnTablets,
presentPane: paneController.presentPane,
isListPaneCollapsed: paneController.isListPaneCollapsed,
isNavigationPaneCollapsed: paneController.isNavigationPaneCollapsed,
toggleListPane: paneController.toggleListPane,
toggleNavigationPane: paneController.toggleNavigationPane,
panes: paneController.panes,
popToPane: paneController.popToPane,
dismissLastPane: paneController.dismissLastPane,
replacePanes: paneController.replacePanes,
removePane: paneController.removePane,
insertPaneAtIndex: paneController.insertPaneAtIndex,
setPaneLayout: paneController.setPaneLayout,
focusModeEnabled: paneController.focusModeEnabled,
}),
[
currentSelectedPane,
isNotesListVisibleOnTablets,
toggleAppPane,
toggleNotesListOnTablets,
paneController.toggleListPane,
paneController.toggleNavigationPane,
paneController.panes,
paneController.isListPaneCollapsed,
paneController.isNavigationPaneCollapsed,
paneController.toggleListPane,
paneController.toggleNavigationPane,
paneController.presentPane,
paneController.popToPane,
paneController.dismissLastPane,
paneController.replacePanes,
paneController.removePane,
paneController.insertPaneAtIndex,
paneController.setPaneLayout,
paneController.focusModeEnabled,
],
)

View File

@@ -5,7 +5,7 @@ import Icon from '@/Components/Icon/Icon'
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { classNames } from '@standardnotes/utils'
import { keyboardStringForShortcut, PIN_NOTE_COMMAND } from '@standardnotes/ui-services'
import { useCommandService } from '../ApplicationView/CommandProvider'
import { useCommandService } from '../CommandProvider'
type Props = {
className?: string

View File

@@ -1,4 +1,4 @@
import { useApplication } from '@/Components/ApplicationView/ApplicationProvider'
import { useApplication } from '@/Components/ApplicationProvider'
import Icon from '@/Components/Icon/Icon'
import { ContentType, ItemCounter } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'

View File

@@ -1,8 +1,8 @@
import { TOGGLE_LIST_PANE_KEYBOARD_COMMAND, TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND } from '@standardnotes/ui-services'
import { useMemo } from 'react'
import { observer } from 'mobx-react-lite'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { useCommandService } from '../ApplicationView/CommandProvider'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import { useCommandService } from '../CommandProvider'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
const PanelSettingsSection = () => {
@@ -25,7 +25,7 @@ const PanelSettingsSection = () => {
<div className="hidden md:block pointer-coarse:md-only:hidden pointer-coarse:lg-only:hidden">
<MenuSwitchButtonItem
className="items-center"
checked={isNavigationPaneCollapsed}
checked={!isNavigationPaneCollapsed}
onChange={toggleNavigationPane}
shortcut={navigationShortcut}
>
@@ -33,7 +33,7 @@ const PanelSettingsSection = () => {
</MenuSwitchButtonItem>
<MenuSwitchButtonItem
className="items-center"
checked={isListPaneCollapsed}
checked={!isListPaneCollapsed}
onChange={toggleListPane}
shortcut={listShortcut}
>

View File

@@ -23,15 +23,14 @@ import Menu from '../Menu/Menu'
import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
import MenuRadioButtonItem from '../Menu/MenuRadioButtonItem'
export const focusModeAnimationDuration = 1255
type MenuProps = {
quickSettingsMenuController: QuickSettingsController
application: WebApplication
}
const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSettingsMenuController }) => {
const { closeQuickSettingsMenu, focusModeEnabled, setFocusModeEnabled } = quickSettingsMenuController
const { focusModeEnabled, setFocusModeEnabled } = application.paneController
const { closeQuickSettingsMenu } = quickSettingsMenuController
const [themes, setThemes] = useState<ThemeItem[]>([])
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])

View File

@@ -1,5 +0,0 @@
export enum AppPaneId {
Navigation = 'NavigationColumn',
Items = 'ItemsColumn',
Editor = 'EditorColumn',
}

View File

@@ -1,28 +0,0 @@
import { useMemo, ReactNode } from 'react'
import { AppPaneId } from './AppPaneMetadata'
import { classNames } from '@standardnotes/utils'
import { useResponsiveAppPane } from './ResponsivePaneProvider'
type Props = {
children: ReactNode
className?: string
contentElementId?: string
paneId: AppPaneId
}
const ResponsivePaneContent = ({ children, className, contentElementId, paneId }: Props) => {
const { selectedPane } = useResponsiveAppPane()
const isSelectedPane = useMemo(() => selectedPane === paneId, [paneId, selectedPane])
return (
<div
id={contentElementId}
className={classNames('content flex flex-col', isSelectedPane ? 'h-full' : 'hidden md:flex', className)}
>
{children}
</div>
)
}
export default ResponsivePaneContent

View File

@@ -1,6 +1,6 @@
import { getDropdownItemsForAllEditors } from '@/Utils/DropdownItemsForEditors'
import { NoteType } from '@standardnotes/snjs'
import { useApplication } from '../ApplicationView/ApplicationProvider'
import { useApplication } from '../ApplicationProvider'
import { PredicateKeypath, PredicateKeypathTypes } from './PredicateKeypaths'
type Props = {

View File

@@ -1,28 +1,28 @@
import SmartViewsSection from '@/Components/Tags/SmartViewsSection'
import TagsSection from '@/Components/Tags/TagsSection'
import { WebApplication } from '@/Application/Application'
import { PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
import { ApplicationEvent, PrefKey, WebAppEvent } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import ResponsivePaneContent from '@/Components/ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '@/Components/ResponsivePane/AppPaneMetadata'
import { forwardRef, useEffect, useMemo, useState } from 'react'
import { AppPaneId } from '@/Components/Panes/AppPaneMetadata'
import { classNames } from '@standardnotes/utils'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
import UpgradeNow from '../Footer/UpgradeNow'
import RoundIconButton from '../Button/RoundIconButton'
import { isIOS } from '@/Utils'
import { PanelResizedData } from '@/Types/PanelResizedData'
import { PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
type Props = {
application: WebApplication
className?: string
children?: React.ReactNode
id: string
}
const Navigation: FunctionComponent<Props> = ({ application }) => {
const Navigation = forwardRef<HTMLDivElement, Props>(({ application, className, children, id }, ref) => {
const viewControllerManager = useMemo(() => application.getViewControllerManager(), [application])
const [panelElement, setPanelElement] = useState<HTMLDivElement>()
const [panelWidth, setPanelWidth] = useState<number>(0)
const { selectedPane, toggleAppPane } = useResponsiveAppPane()
const { toggleAppPane } = useResponsiveAppPane()
const [hasPasscode, setHasPasscode] = useState(() => application.hasPasscode())
useEffect(() => {
@@ -34,26 +34,16 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
}, [application])
useEffect(() => {
const removeObserver = application.addEventObserver(async () => {
const width = application.getPreference(PrefKey.TagsPanelWidth)
if (width) {
setPanelWidth(width)
return application.addWebEventObserver((event, data) => {
if (event === WebAppEvent.PanelResized) {
const { panel, width } = data as PanelResizedData
if (panel === PANEL_NAME_NAVIGATION) {
application.setPreference(PrefKey.TagsPanelWidth, width).catch(console.error)
}
}
}, ApplicationEvent.PreferencesChanged)
return () => {
removeObserver()
}
})
}, [application])
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.setPreference(PrefKey.TagsPanelWidth, width).catch(console.error)
application.publishPanelDidResizeEvent(PANEL_NAME_NAVIGATION, isCollapsed)
},
[application],
)
const NavigationFooter = useMemo(() => {
return (
<div
@@ -115,52 +105,33 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
return (
<div
id="navigation"
id={id}
className={classNames(
'pb-[50px] md:pb-0',
'sn-component section app-column h-full max-h-full overflow-hidden pt-safe-top md:h-full md:max-h-full md:min-h-0',
'w-[220px] xl:w-[220px] xsm-only:!w-full sm-only:!w-full',
selectedPane === AppPaneId.Navigation
? 'pointer-coarse:md-only:!w-48 pointer-coarse:lg-only:!w-48'
: 'pointer-coarse:md-only:!w-0 pointer-coarse:lg-only:!w-0',
className,
'sn-component section pb-[50px] md:pb-0',
'h-full max-h-full overflow-hidden pt-safe-top md:h-full md:max-h-full md:min-h-0',
)}
ref={(element) => {
if (element) {
setPanelElement(element)
}
}}
ref={ref}
>
<ResponsivePaneContent paneId={AppPaneId.Navigation} contentElementId="navigation-content">
<div
className={classNames(
'flex-grow overflow-y-auto overflow-x-hidden md:overflow-y-hidden md:hover:overflow-y-auto pointer-coarse:md:overflow-y-auto',
'md:hover:[overflow-y:_overlay]',
)}
>
<SmartViewsSection
application={application}
featuresController={viewControllerManager.featuresController}
navigationController={viewControllerManager.navigationController}
/>
<TagsSection viewControllerManager={viewControllerManager} />
</div>
{NavigationFooter}
</ResponsivePaneContent>
{panelElement && (
<PanelResizer
collapsable={true}
defaultWidth={150}
panel={panelElement}
hoverable={true}
side={PanelSide.Right}
type={PanelResizeType.WidthOnly}
resizeFinishCallback={panelResizeFinishCallback}
width={panelWidth}
left={0}
<div
id="navigation-content"
className={classNames(
'flex-grow overflow-y-auto overflow-x-hidden md:overflow-y-hidden md:hover:overflow-y-auto',
'md:hover:[overflow-y:_overlay] pointer-coarse:md:overflow-y-auto',
)}
>
<SmartViewsSection
application={application}
featuresController={viewControllerManager.featuresController}
navigationController={viewControllerManager.navigationController}
/>
)}
<TagsSection viewControllerManager={viewControllerManager} />
</div>
{NavigationFooter}
{children}
</div>
)
}
})
export default observer(Navigation)

View File

@@ -13,8 +13,6 @@ import {
useRef,
useState,
} from 'react'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { classNames } from '@standardnotes/utils'
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
@@ -37,8 +35,6 @@ const getIconClass = (view: SmartView, isSelected: boolean): string => {
}
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState, setEditingSmartView }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(view.title || '')
const inputRef = useRef<HTMLInputElement>(null)
@@ -54,8 +50,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState, setEdit
await tagsState.setSelectedTag(view, 'views', {
userTriggered: true,
})
toggleAppPane(AppPaneId.Items)
}, [tagsState, toggleAppPane, view])
}, [tagsState, view])
const onBlur = useCallback(() => {
tagsState.save(view, title).catch(console.error)

View File

@@ -19,10 +19,8 @@ import {
useRef,
useState,
} from 'react'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { classNames } from '@standardnotes/utils'
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
import { useFileDragNDrop } from '../FileDragNDropProvider'
import { LinkingController } from '@/Controllers/LinkingController'
import { TagListSectionType } from './TagListSection'
import { log, LoggingDomain } from '@/Logging'
@@ -44,8 +42,6 @@ const PADDING_PER_LEVEL_PX = 21
export const TagsListItem: FunctionComponent<Props> = observer(
({ tag, type, features, navigationController: navigationController, level, onContextMenu, linkingController }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
@@ -94,8 +90,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
await navigationController.setSelectedTag(tag, type, {
userTriggered: true,
})
toggleAppPane(AppPaneId.Items)
}, [navigationController, tag, type, toggleAppPane])
}, [navigationController, tag, type])
const onBlur = useCallback(() => {
navigationController.save(tag, title).catch(console.error)

View File

@@ -4,7 +4,7 @@ import { NavigationController } from '@/Controllers/Navigation/NavigationControl
import { CREATE_NEW_TAG_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useMemo } from 'react'
import { useCommandService } from '../ApplicationView/CommandProvider'
import { useCommandService } from '../CommandProvider'
type Props = {
tags: NavigationController